序号 | 内容 | 链接 |
---|---|---|
1 | 多进程 | 点我访问 |
2 | 进程间通信 | 点我访问 |
3 | 多线程 | 点我访问 |
4 | 网络编程 | 点我访问 |
5 | shell | 点我访问 |
6 | Makefile | 点我访问 |
7 | 串口通信 | 点我访问 |
8 | I2C通信 | 点我访问 |
一 Linux下代码控制串口
Linux下把串口当成控制台对待,想对串口操作,其实就是对控制台操作;
Linux下有专门的串口驱动节点,通过系统io操作,这些节点的名字都有如下规律:
/dev/ttyS*
/dev/ttyAMA*
/dev/ttyO*
/dev/ttySAC*
…
至于你的开发板的串口驱动节点是哪一种名字,不同的开发板是不同的,所以要根据供货商提供的操作手册进行操作;
操作串口的方法:
系统IO -> open,read,write,close…
能用标准IO操作串口吗 -> 不能,因为串口是字符设备,标准io是以块为单位来传输的,不是以字符为单位传输的,所以不行。
1.1 判断驱动节点是否是串口设备
if(tcgetattr(fd, &oldtio) != 0){ //判断你的驱动节点是不是一个串口设备,假如不反回0,就不是串口设备
return -1;
}
1.2 struct termios详解
struct termios
{
unsigned short c_iflag; // 输入模式标志
unsigned short c_oflag; // 输出模式标志
unsigned short c_cflag; // 控制模式标志
unsigned short c_lflag; //区域模式标志或本地模式标志或局部模式
unsigned char c_line; //行控制line discipline
unsigned char c_cc[NCC]; // 控制字符特性
};
c_oflag参数
键 值 | 说 明 |
---|---|
OPOST | 处理后输出 |
OLCUC | 将输出的小写字符转换成大写字符(非POSIX) |
ONLCR | 将输出的NL(换行)转换成CR(回车)及NL(换行) |
OCRNL | 将输出的CR(回车)转换成NL(换行) |
ONOCR | 第一行不输出回车符 |
ONLRET | 不输出回车符 |
OFILL | 发送填充字符以延迟终端输出 |
OFDEL | 以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符为NULL |
NLDLY | 换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s) |
CRDLY | 回车延迟,取值范围为:CR0、CR1、CR2和 CR3 |
TABDLY | 水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3 |
BSDLY | 空格输出延迟,可以取BS0或BS1 |
VTDLY | 垂直制表符输出延迟,可以取VT0或VT1 |
FFDLY | 换页延迟,可以取FF0或FF1 |
c_cflag参数 | |
键 值 | 说 明 |
– | – |
CBAUD | 波特率(4+1位)(非POSIX) |
CBAUDEX | 附加波特率(1位)(非POSIX) |
CSIZE | 字符长度,取值范围为CS5、CS6、CS7或CS8 |
CSTOPB | 设置两个停止位 |
CREAD | 使用接收器 |
PARENB | 使用奇偶校验 |
PARODD | 对输入使用奇偶校验,对输出使用偶校验 |
HUPCL | 关闭设备时挂起 |
CLOCAL | 忽略调制解调器线路状态 |
CRTSCTS | 使用RTS/CTS流控制 |
c_lflag参数
键 值 | 说 明 |
---|---|
ISIG | 当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号 |
ICANON | 使用标准输入模式 |
XCASE | 在ICANON和XCASE同时设置的情况下,终端只使用大写。 |
ECHO | 显示输入字符 |
ECHOE | 如果ICANON同时设置,ERASE将删除输入的字符 |
ECHOK | 如果ICANON同时设置,KILL将删除当前行 |
ECHONL | 如果ICANON同时设置,即使ECHO没有设置依然显示换行符 |
ECHOPRT | 如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX) |
TOSTOP | 向后台输出发送SIGTTOU信号 |
c_cc支持的控制字符
宏 | 说 明 | 宏 | 说 明 |
---|---|---|---|
VINTR | Interrupt字符 | VEOL | 附加的End-of-file字符 |
VQUIT | Quit字符 | VTIME | 非规范模式读取时的超时时间 |
VERASE | Erase字符 | VSTOP | Stop字符 |
VKILL | Kill字符 | VSTART | Start字符 |
VEOF | End-of-file字符 | VSUSP | Suspend字符 |
VMIN | 非规范模式读取时的最小字符数 |
1.3 设置波特率
static speed_t getBaudrate(int baudrate)
{
switch(baudrate) {
case 0: return B0;
case 50: return B50;
case 75: return B75;
case 110: return B110;
case 134: return B134;
case 150: return B150;
case 200: return B200;
case 300: return B300;
case 600: return B600;
case 1200: return B1200;
case 1800: return B1800;
case 2400: return B2400;
case 4800: return B4800;
case 9600: return B9600;
case 19200: return B19200;
case 38400: return B38400;
case 57600: return B57600;
case 115200: return B115200;
case 230400: return B230400;
case 460800: return B460800;
case 500000: return B500000;
case 576000: return B576000;
case 921600: return B921600;
case 1000000: return B1000000;
case 1152000: return B1152000;
case 1500000: return B1500000;
case 2000000: return B2000000;
case 2500000: return B2500000;
case 3000000: return B3000000;
case 3500000: return B3500000;
case 4000000: return B4000000;
default: return -1;
}
}
struct termios newtio,oldtio;
/*struct termios
{
unsigned short c_iflag; // 输入模式标志
unsigned short c_oflag; // 输出模式标志
unsigned short c_cflag; // 控制模式标志
unsigned short c_lflag; //区域模式标志或本地模式标志或局部模式
unsigned char c_line; //行控制line discipline
unsigned char c_cc[NCC]; // 控制字符特性
}*/
speed_t bSpeed = B115200;
bSpeed = getBaudrate(nSpeed);
/* 设置输入波特率 */
cfsetispeed(&newtio, bSpeed);
/* 设置输出波特率 */
cfsetospeed(&newtio, bSpeed);
1.4 数据位
/* 设置数据位 */
switch(nBits){
case 5:
newtio.c_cflag |= CS5;
break;
case 6:
newtio.c_cflag |= CS6;
break;
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
default:
newtio.c_cflag |= CS8;
break;
}
1.5 设置校验位
/* 设置校验位 */
switch(nEvent){
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
case 'S':
newtio.c_cflag &= ~PARENB;
break;
default:
newtio.c_cflag &= ~PARENB;
break;
}
1.6 设置停止位
/* 设置停止位 */
switch (nStop){
case 1 :
newtio.c_cflag &= ~CSTOPB;
break;
case 2 :
newtio.c_cflag |= CSTOPB;
break;
default:
newtio.c_cflag &= ~CSTOPB;
break;
}
1.7 设置超时
下面两句代码就是核心部分:
先来了解一种情况,比如a发送给b 64个字节的数据,正常情况下我们都希望b一次性收完,但是实际情况
不可能是一次收完的,b都会断断续续的收到;
下面这两句代码就是为了防止上面的这种情况发生,希望b会"等一下",等待a发完,但是假如b等了,a又不发了
这样就很难受了,所以如下2个参数就是为了防止这种情况发生;
c_cc[VTIME] = 3;
c_cc[VMIN] = 64;
首先read是会阻塞的;
64的意思是最小要读取到64个字节才返回,否则阻塞在read函数上;
3的意思就是假如在3*100ms内收不到64个字节,就强制返回,解除read阻塞;
其实这两个参数还有其他含义,比如:
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 64;
代表的意思是:
首先read是会阻塞的;
死等,必须等到64个字节再返回,没有接受超时时间;
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
代表的意思是:
首先read是不会阻塞的;
收不到就算了,read是不阻塞的;
newtio.c_cc[VTIME] = 4;
newtio.c_cc[VMIN] = 0;
newtio.c_cc[VTIME] = 3;
newtio.c_cc[VMIN] = 64;
1.8 完整代码
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <net/if.h>
#include <sys/wait.h>
#include <errno.h>
#include <linux/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <pthread.h>
int g_fd;
#define DEBUG(fmt, arg...) printf(fmt, ##arg)
static speed_t getBaudrate(int baudrate)
{
switch(baudrate) {
case 0: return B0;
case 50: return B50;
case 75: return B75;
case 110: return B110;
case 134: return B134;
case 150: return B150;
case 200: return B200;
case 300: return B300;
case 600: return B600;
case 1200: return B1200;
case 1800: return B1800;
case 2400: return B2400;
case 4800: return B4800;
case 9600: return B9600;
case 19200: return B19200;
case 38400: return B38400;
case 57600: return B57600;
case 115200: return B115200;
case 230400: return B230400;
case 460800: return B460800;
case 500000: return B500000;
case 576000: return B576000;
case 921600: return B921600;
case 1000000: return B1000000;
case 1152000: return B1152000;
case 1500000: return B1500000;
case 2000000: return B2000000;
case 2500000: return B2500000;
case 3000000: return B3000000;
case 3500000: return B3500000;
case 4000000: return B4000000;
default: return -1;
}
}
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio; //创建
speed_t bSpeed = B115200;
if(tcgetattr(fd, &oldtio) != 0){ //判断你的驱动节点是不是一个串口设备,假如不反回0,就不是串口设备
return -1;
}
bzero(&newtio, sizeof(newtio));
// 忽略调制解调器线路状态 | 使用接受器
newtio.c_cflag |= (CLOCAL | CREAD);
/* 清空数据位的值 */
newtio.c_cflag &= ~CSIZE;
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/* 关闭处理后输出 */
newtio.c_oflag &= ~OPOST;
/* 设置数据位 */
switch(nBits){
case 5:
newtio.c_cflag |= CS5;
break;
case 6:
newtio.c_cflag |= CS6;
break;
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
default:
newtio.c_cflag |= CS8;
break;
}
/* 设置校验位 */
switch(nEvent){
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
case 'S':
newtio.c_cflag &= ~PARENB;
break;
default:
newtio.c_cflag &= ~PARENB;
break;
}
bSpeed = getBaudrate(nSpeed);
cfsetispeed(&newtio, bSpeed);
cfsetospeed(&newtio, bSpeed);
/* 设置停止位 */
switch (nStop){
case 1 :
newtio.c_cflag &= ~CSTOPB;
break;
case 2 :
newtio.c_cflag |= CSTOPB;
break;
default:
newtio.c_cflag &= ~CSTOPB;
break;
}
/*
下面两句代码就是核心部分:
先来了解一种情况,比如a发送给b 64个字节的数据,正常情况下我们都希望b一次性收完,但是实际情况
不可能是一次收完的,b都会断断续续的收到;
下面这两句代码就是为了防止上面的这种情况发生,希望b会"等一下",等待a发完,但是假如b等了,a又不发了
这样就很难受了,所以如下2个参数就是为了防止这种情况发生;
c_cc[VTIME] = 3;
c_cc[VMIN] = 64;
首先read是会阻塞的;
64的意思是最小要读取到64个字节才返回,否则阻塞在read函数上;
3的意思就是假如在3*100ms内收不到64个字节,就强制返回,解除read阻塞;
其实这两个参数还有其他含义,比如:
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 64;
代表的意思是:
首先read是会阻塞的;
死等,必须等到64个字节再返回,没有接受超时时间;
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
代表的意思是:
首先read是不会阻塞的;
收不到就算了,read是不阻塞的;
newtio.c_cc[VTIME] = 4;
newtio.c_cc[VMIN] = 0;
*/
newtio.c_cc[VTIME] = 3;
newtio.c_cc[VMIN] = 64;
tcflush(fd, TCIOFLUSH); //清空缓冲区
if((tcsetattr(fd, TCSANOW, &newtio)) != 0){ //把newtio的属性设置到当前的控制台
return -1;
}
return 0;
}
int open_port(int fd, char *path)
{
/*
O_NOCTTY: 不要把它当成串口控制台,因为它是通信串口,不是调试串口
O_NDELAY: 不要阻塞
*/
fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0)
{
perror("open_oprt failed");
return(-1);
}
DEBUG("fcntl: %d\n", fcntl(fd, F_SETFL, 0)); //再重新设置成非阻塞,保险
return fd;
}
/*
path_utf: 串口驱动节点,nuc972的驱动节点是/dev/ttyS*,不是所有的板子都叫ttySAC*,有的叫ttyO*,ttyS*,ttyAMA*等等
baud: 波特率,比如9600,115200等
databits:数据位,只有5,6,7,8
parity_utf: 校验位,只有'O','E','N','S'
stopbits: 停止位,只有1, 1.5, 2
例如:
char a = 'O';
OpenSerialPort("/dev/ttySAC2", 115200, 8, &a, 1);
*/
int OpenSerialPort(char *path_utf, int baud, int databits, char *parity_utf, int stopbits)
{
int fd, result;
fd = open_port(fd, path_utf);
result = set_opt(fd, baud, databits, parity_utf[0], stopbits);
if(result < 0){
close(fd);
return result;
}
return fd;
}
/* 注意,不要在子线程里scanf或者fgets */
void *read_thread(void *args)
{
char buf[64];
while(1){
memset(buf, 0 ,sizeof(buf));
read(g_fd, buf, sizeof(buf));
printf("recv=%s\n", buf);
}
}
int main(int argc, char **argv)
{
char buf[64];
char cmd[64]="hahahaha";
pthread_t tid;
g_fd = OpenSerialPort(argv[1], 9600, 8, "N", 1);
pthread_create(&tid, NULL, read_thread, NULL);
while(1){
write(g_fd, cmd, strlen(cmd));
sleep(1);
}
return 0;
}