Linux (Android) 串口通信教程
写文章不易,转载请注明,作者:甜牛奶蛋糕 出处:https://blog.****.net/sweetmilkcake/article/details/82929458
Demo功能
在学习Linux的串口通信程序之前,我们先来看看Demo的效果,这样比先来一大堆串口知识介绍更加有学习动力,毕竟是能运行的。本文章不会讲解串口的原理,如何接线等,只会讲解在Linux(Android)下如何使用串口。
程序的功能是发送4个8bit的数据,然后serial_comm不断接受数据并且回传会串口,从下图可以看出,程序正常运行。
这里的数据格式是以非标准(Raw)的格式进行传输,即以16进制进行传输,末尾不会发送回车符。这种方式普遍用在两个嵌入式IC之间的通信,而标准格式,则需要在末尾加回车符,数据才能读到。
这里推荐大家使用AccessPort这个软件(免费的),下载地址:http://www.sudt.com,能收发非标准模式,以及标准模式。而另一款SecureCRT软件只能收发标准模式,所以我调试非标准模式时一般都用这个软件。其实选择这个软件的原因嘛,仅仅是界面比较好看,哈哈。
串口配置
一般串口配置有:1、串口号。2、波特率。3、校验位。4、数据位。5、停止位。只要配置这些信息,我们就可以使用Window串口进行调试,而Linux的串口也是类似,代码中也是类似的操作。
Linux一切设备都是文件,串口设备的访问就是访问/dev/ttyS*,我们用ls -l /dev/ttyS*查看当前系统注册的设备,并确认其 运行权限,看是否可读可写。这里也是需要注意的地方,不然调试半天,才发现是没权限访问,那就尴尬了。从下面看出当前系统有3个串口设备其中ttyS0是debug打印log的串口,ttyS1是蓝牙用的串口,ttyS3才是没人使用的串口。ttyS3怎么产生,请查看之前的文章:https://blog.****.net/sweetmilkcake/article/details/82749764
petrel-p1:/ # ls -l /dev/ttyS*
crw------- 1 root root 252, 0 2018-01-01 08:00 /dev/ttyS0
crw-rw---- 1 bluetooth net_bt_stack 252, 1 2018-01-01 08:01 /dev/ttyS1
crw------- 1 root root 252, 3 2018-01-01 08:06 /dev/ttyS3
查看串口是否被占用
之前调过一个Bug,就是我的程序无论怎么都接受不到正常数据,甚至进行自己收发回环测试都不行(回环测试,串口端的TX和RX互联,自己进行收发处理,这样可以快捷测试串口的状态),当时也是搞了好久,一度怀疑是硬件问题,最后才发现是有另外一个程序一直访问同一个串口设备,不断的进行读写(它启动的早),导致我的数据不能正常输出,后面直接把它kill掉就好了,当然这是快捷的方法,最终修改还是要两个程序进行协调。那么问题来了,如果你刚接手别人的项目,对别人的系统又不熟悉,那么怎么确保自己使用的串口没有问题呢?其实还是有方法的,我们可以使用lsof | grep ttyS查看串口设备的占用情况。就像下面例子一样,我把serial_comm放到后台运行,所以看到它在占用着我们的ttyS3。用这种方法就可以很快进行问题定位。
petrel-p1:/ # lsof | grep ttyS
droid.blu 3044 bluetooth 74u CHR 252,1 0t0 4227 /dev/ttyS1
petrel-p1:/ # serial_comm &
[1] 19274
petrel-p1:/ # serial test start.
petrel-p1:/ # lsof | grep ttyS
droid.blu 3044 bluetooth 74u CHR 252,1 0t0 4227 /dev/ttyS1
serial_co 19274 root 3u CHR 252,3 0t0 4228 /dev/ttyS3
详细配置代码
// dev control date end
// 02 01 12 7F
#define SERIAL_REC_NUM 4
// VTIME and VMIN is very important.
// VTIME: Time to wait for data (tenths of seconds)
#define SERIAL_VTIME 1
// VMIN: Minimum number of characters to read
#define SERIAL_VMIN SERIAL_REC_NUM
int serial_set(int fd, int speed, int bits, int event, int stop)
{
struct termios ttys;
memset(&ttys, 0, sizeof(ttys));
// Enable the receiver and set local mode
// CLOCAL: Local line - do not change "owner" of port
// CREAD: Enable receiver
ttys.c_cflag |= (CLOCAL | CREAD);
// Mask the character size bits
// CSIZE: Bit mask for data bits
ttys.c_cflag &= ~CSIZE;
switch (speed) {
case 9600:
// B9600: 9600 baud
cfsetispeed(&ttys, B9600);
cfsetospeed(&ttys, B9600);
break;
case 115200:
// B115200: 115,200 baud
cfsetispeed(&ttys, B115200);
cfsetospeed(&ttys, B115200);
break;
default:
cfsetispeed(&ttys, B115200);
cfsetospeed(&ttys, B115200);
break;
}
switch (bits) {
case 7:
// 7 data bits
ttys.c_cflag |= CS7;
break;
case 8:
// 8 data bits
ttys.c_cflag |= CS8;
break;
default:
ttys.c_cflag |= CS8;
break;
}
switch (event) {
case 'o':
case 'O':
// PARENB: Enable parity bit
ttys.c_cflag |= PARENB;
// INPCK: Enable parity check
// ISTRIP: Strip parity bits
ttys.c_cflag |= (INPCK | ISTRIP);
// PARODD: Use odd parity instead of even
ttys.c_cflag |= PARODD;
break;
case 'e':
case 'E':
ttys.c_cflag |= PARENB;
ttys.c_cflag |= (INPCK | ISTRIP);
ttys.c_cflag &= ~PARODD;
break;
case 'n':
case 'N':
ttys.c_cflag &= ~PARENB;
break;
default:
ttys.c_cflag &= ~PARENB;
break;
}
switch (stop) {
case 1:
// CSTOPB: 2 stop bits (1 otherwise)
ttys.c_cflag &= ~CSTOPB;
break;
case 2:
ttys.c_cflag |= CSTOPB;
break;
default:
ttys.c_cflag &= ~CSTOPB;
break;
}
// VTIME: Time to wait for data (tenths of seconds)
ttys.c_cc[VTIME] = SERIAL_VTIME;
// VMIN: Minimum number of characters to read
ttys.c_cc[VMIN] = SERIAL_VMIN;
// Hardware flow control using the CTS (Clear To Send) and RTS (Request To Send) signal lines
// CNEW_RTSCTS, CRTSCTS: Enable hardware flow control (not supported on all platforms)
// CNEW_RTSCTS: Also called CRTSCTS
//ttys.c_cflag |= CRTSCTS; // Enable hardware flow control
ttys.c_cflag &= ~CRTSCTS; // Disable hardware flow control
// Choosing Canonical Input
// Canonical input is line-oriented. Input characters are put into a buffer
// which can be edited interactively by the user until a CR (carriage return)
// or LF (line feed) character is received.
// When selecting this mode you normally select the ICANON, ECHO, and ECHOE options
//ttys.c_lflag |= (ICANON | ECHO | ECHOE);
// Choosing Raw Input
// Raw input is unprocessed. Input characters are passed through exactly as they are received,
// when they are received. Generally you'll deselect the ICANON, ECHO, ECHOE, and ISIG options when using raw input
ttys.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// OPOST: Postprocess output (not set = raw output)
//ttys.c_oflag |= OPOST; // Choosing Processed Output
ttys.c_oflag &= ~OPOST; // Choosing Raw Output
// Flushes the input and/or output queue.
// TCIFLUSH: flushes data received but not read.
// TCOFLUSH: flushes data written but not transmitted.
// TCIOFLUSH: flushes both data received but not read, and data written but not transmitted.
tcflush(fd, TCIOFLUSH);
// Sets the serial port settings immediately.
// TCSANOW: Make changes now without waiting for data to complete
// TCSADRAIN: Wait until everything has been transmitted
// TCSAFLUSH: Flush input and output buffers and make the change
if (tcsetattr(fd, TCSANOW, &ttys) != 0) {
perror("serial set fail!\n");
return -2;
}
return 0;
}
代码注释已经非常详细了,下面一一详细介绍各变量含义以及,相关注意点:
- ttys.c_cflag |= (CLOCAL | CREAD); - 本地端口,使能接收器
- ttys.c_cflag &= ~CSIZE; - 打开数据位的开关,才能设置波特率、停止位等
- cfsetispeed(&ttys, B9600); - 设置输入的波特率为9600
- cfsetospeed(&ttys, B9600); - 设置输出的波特率为9600
- ttys.c_cflag |= CS8; - 8位数据位
- ttys.c_cflag &= ~PARENB; - 取消奇偶校验
- ttys.c_cflag &= ~CSTOPB; - 一位停止位
- ttys.c_cc[VTIME] = SERIAL_VTIME; - 读取数据的最小时间
- ttys.c_cc[VMIN] = SERIAL_VMIN; - 读取数据的最小位数
- ttys.c_cflag &= ~CRTSCTS; - 不使用硬件流控制
- ttys.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); - 选择非标准输入
- ttys.c_oflag &= ~OPOST; - 选择非标准输出
- tcflush(fd, TCIOFLUSH); - 刷新串口缓冲区的数据
- tcsetattr(fd, TCSANOW, &ttys) - 使配置生效
在进行串口通信设计时需要注意以下地方:
- ttys.c_cc[VTIME]和ttys.c_cc[VMIN]这两个会影响串口接收端的行为,我这里设置为ttys.c_cc[VTIME]=1,ttys.c_cc[VMIN]=4,串口open的时候设置为阻塞读取,ttys.c_cc[VTIME]=1的意思是读取数据超过100ms则会立即返回错误,ttys.c_cc[VMIN]=4的意思时每次需要读取4个自己数据,不然等待数据,两个加一起就是每次阻塞读取4个数据,超时100ms则会退出。其中ttys.c_cc[VMIN]=4一般由开发者自行定义,一般不太复杂的通信,这个固定就好,加入一些数据校验的措施即可保证数据的准确性。ttys.c_cc[VTIME]=1这个是为了保证数据线收到干扰或者接受数据不全而把当前错误数据进行丢弃,不然会打乱接受数据的顺序,影响准确性,相当于一个自我恢复措施。
- tcflush(fd, TCIOFLUSH);这里设置为清空输入和输出Buffer的数据,设置好串口的状态,如果假如串口Buffer里面有数据,则会导致后续接受不正常。
串口测试
main函数的代码非常简单,打开串口并对串口接收到的数据进行回传。
int main(int argc, char *argv[])
{
int fd = 0;
int ret = 0;
int n = 0;
char buf[SERIAL_REC_NUM];
printf("serial test start.\n");
fd = open("/dev/ttyS3", O_RDWR | O_NOCTTY);
if (fd < 0) {
fprintf(stderr, "%s, open serial failed!\n", __FUNCTION__);
return -1;
}
// Baud rate: 115200, Data bits: 8, Parity: None, Stop bits: 1
ret = serial_set(fd, 115200, 8, 'N', 1);
if (ret) {
fprintf(stderr, "%s, serial set failed!\n", __FUNCTION__);
return -1;
}
while (1) {
memset(&buf, 0, sizeof(buf));
// blocking here
n = read(fd, &buf, SERIAL_REC_NUM);
if (n < SERIAL_REC_NUM) {
fprintf(stderr, "serial read fail!, n = %d\n", n);
} else {
printf("buf[0] = 0x%x, buf[1] = 0x%x, buf[2] = 0x%x, buf[3] = 0x%x, \n",
buf[0], buf[1], buf[2], buf[3]);
n = write(fd, &buf, SERIAL_REC_NUM);
if (n < SERIAL_REC_NUM) {
fprintf(stderr, "serial write fail!, n = %d\n", n);
}
}
}
close(fd);
printf("serial test end.\n");
return 0;
}