itop4412之 linux串口编程
目录
- 基本知识
- 结构体termio
- 串口初始化步骤
(一)基本知识
linux下的串口通常指RS232,比51,或stm32都要复杂些,当然实质是一样的,比如都有涉及的波特率,停止位,数据位,奇偶校验等,对于驱动,每个板子都会有所不同,但是实质都一样的。这里是itop4412的exynos4412,如果你是其他开发板希望对你有帮助。
(二)结构体termio
linux下的串口第一个就是要学习termio结构体,很重要!
位置: 内核目录“\arch\arm\include\asm\termios.h”, 或者用
man 3 tcgetattr也可以看到。
这里是TERMIOS(3).pdf—参考文档
链接: https://pan.baidu.com/s/1783ZZK1ICWP490ZJ89SDFA 提取码: 5btc
(三)串口初始化步骤是
- (1)读取当前参数
- (2)修改参数
- (3)配置参数
首先先看看几个常见的函数
(1):读取当前参数
(2):读取当前波特率—有两个(有一个输入,一个输出)
(3):设置新波特率—有两个(有一个输入,一个输出)
(4):清空串口BUFFER中的数据函数
(5):设置串口参数函数
之后看一个例程与分享自己学习所得
下面的参数是会遇到的,详细请参考man文档
在强调一遍,linux下的串口使用步骤:获取当前值----配置新结构体—生效
里面涉及两个termio结构体变量,一个新的,一个旧的。
串口配置函数
#include <termios.h>
#include <unistd.h>
/* struct termio {
unsigned short c_iflag; // input mode flags
unsigned short c_oflag; // output mode flags
unsigned short c_cflag; // control mode flags
//CREAD: Enable receiver; CLOCAL: Ignore modem control lines.
unsigned short c_lflag; // local mode flags
unsigned char c_line; // line discipline
unsigned char c_cc[NCC]; // control characters
}; */
/****************************************************************************************
** 串口配置函数
** tcgetattr():读取当前参数的函数
** int tcgetattr(int fd, struct termios *termios_p);
** bzero():置字节字符串前n个字节为零且包括‘\0’。
** extern void bzero(void *s, int n)
** s: 要置零的数据的起始地址
** n: 要置零的数据字节个数。
** tcflush():清空串口BUFFER中的数据函数
** int tcflush(int fd, int queue_selector);
** fd :是open返回的文件句柄。
** queue_selector:控制tcflush 的操作。有三个常用数值,
TCIFLUSH: 清除正收到的数据,且不会读取出来;
TCOFLUSH: 清除正写入的数据,且不会发送至终端;
TCIOFLUSH:清除所有正在发生的I/O 数据。
** 返回值:执行成功返回0,失败返回-1
** tcsetattr():设置新的参数。一般在初始化最后会使用这个函数
** int tcsetattr(int fd, int optional_actions,const struct termios *termios_p);
** fd:open返回的文件句柄。
** optional_actions:参数生效的时间。
TCSANOW: 不等数据传输完毕就立即改变属性;
TCSADRAIN:等待所有数据传输结束才改变属性;
TCSAFLUSH:清空输入输出缓冲区才改变属性。
** *termios_p :在旧的参数基础上修改的后的参数。
** 返回值:执行成功返回0,失败返回-1
****************************************************************************************/
int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio, oldtio; //新旧termios结构体变量
if(tcgetattr(fd, &oldtio) != 0) //读取当前参数
{
perror("SetupSerial 1");
return -1;
}
//新的结构体变量设置--就是对一个新结构清零初始化
bzero(&newtio, sizeof(newtio)); //将新结构体置零
newtio.c_cflag |= CLOCAL | CREAD; //通常同时使能,确保程序不被其他端口控制和信号干扰,同时串口驱动将读取进入的数据。
newtio.c_cflag &= ~CSIZE; //数据位清0
switch(nBits) //设置数据位--其他位自己设置
{
case 7:
newtio.c_cflag |= CS7; //七位
break;
case 8:
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_cflag |= PARENB; //允许校验位
newtio.c_cflag &= ~PARODD; //使用偶校验(清除该标志表示使用偶校验)
newtio.c_iflag |= (INPCK | ISTRIP); //启用极性 + 剥去第八位
break;
case 'N'://无校验
newtio.c_cflag &= ~PARENB; //无校验
break;
}
switch(nSpeed) //设置传输速度
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
case 460800:
cfsetispeed(&newtio, B460800);
cfsetospeed(&newtio, B460800);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if(nStop == 1) //设置停止位
{
newtio.c_cflag &= ~CSTOPB; //一位停止位
}
else if(nStop == 2)
{
newtio.c_cflag |= CSTOPB; //两位停止位
}
//如果VTIME=0,VMIN=0,不管能否读取到数据,read都会立即返回
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
tcflush(fd, TCIFLUSH); //清除所有正在发生的I/O 数据
//设置生效
if((tcsetattr(fd, TCSANOW, &newtio)) != 0) //不等数据传输完毕就立即改变属性
{
perror("com set error");
return -1;
}
return 0;
}
主函数
int main()
{
int fd;
int i = 100, w_length;
char *uart3 = "/dev/ttySAC3";
char *test = "Hello world!\n";
if((fd = open(uart3, O_RDWR|O_NDELAY|O_NOCTTY))<0)
{
printf("Open %s failed!!!\n", uart3);
}
else
{
printf("Open %s success!!!\n", uart3);
set_opt(fd, 115200, 8, 'N', 1);
while(i--)
{
w_length = write(fd, test, strlen(test));
if(w_length < 0)
{
printf("Write buffer error!!!\n");
exit(1);
}
else
{
printf("Having writing %d byte.\n",w_length);
}
sleep(1);
}
}
close(fd);
}
注意:
这是配置的一般性的过程,如果你在实际中遇到其他的问题,请仔细参看man文档, c_iflag, c_oflag, c_cflag 还涉及很多细小的配置。
比如:
(1):发送字符0X0d的时候,往往接收端得到的字符是0X0a,原因是因为在串口设置中c_iflag和c_oflag中存在从NL-CR和CR-NL的映射,即串口能把回车和换行当成同一个字符,可以进行如下设置屏蔽之:
newtio.c_iflag &= ~ (INLCR | ICRNL | IGNCR);
newtio.c_oflag &= ~(ONLCR | OCRNL);
(2):有时候,在用write发送数据时没有键入回车,信息就发送不出去,这主要是因为我们在输入输出时是按照**规范模式(ICANON )**接收到回车或换行才发送,而更多情况下我们是不必键入回车或换行的。此时应转换到行方式输入,不经处理直接发送,设置如下:
newtio.c_lflag &= ~ (ICANON | ECHO | ECHOE | ISIG);
(3):设置为原始模式传输数据的话,read函数返回的字符数是实际串口收到的字符数。Linux下直接用read读串口可能会造成堵塞,或者数据读出错误,此时可使用fcntl或者select等函数实现异步读取。用select先查询com口,再用read去读就可以避免上述错误。
(4):c_cc数组的VSTART和VSTOP元素被设定成DC1和DC3,代表ASCII标准的XON和XOFF字符(自己百度下),如果在传输这两个字符的时候就传不过去,需要把软件流控制屏蔽,即:
newtio.c_iflag &= ~ (IXON | IXOFF | IXANY);
XON/XOFF(继续/停止)是异步串行连接的计算机和其他元件之间的数据流控制协议。例如,计算机向打印机发送数据的速度通常快于打印机打印的速度,打印机包含一个缓冲器,用来存储数据,使打印机能够赶上计算机。如果在打印机赶上之前缓冲器变满了,打印机的小微处理器便发回一个XOFF信号来停止数据传送,打印完相当多的数据,缓冲存储器变空时,打印机发送XON信号,让计算机继续发送数据。“X”表示“发送器”,X/ON和X/OFF为开启和关闭发送器的信号。X/ON的实际信号为ASCII的Ctrl+Q键盘组合的位组合,X/OFF信号为Ctrl+S字符。
(5):在遇到其他问题我会在补充。