用过开发板的朋友们都知道,UART在一个芯片中是很珍贵的资源,它可以被配置为485功能,也可以配置232功能,而在工业通讯中,485,232都是常用的通讯方式。
这里多说一句题外话:一直没有找到linux比较好的编辑工具,vim虽然功能强大,但是没有鼠标,总感觉不爽,gedit虽然有鼠标,但是缩进功能实在是不行,这里推荐一款本人一直用的,感觉用的不错,就是大家熟悉的eclipse for c++,这个有Linux版本,用起来虽然没有windows下那么爽,但是比大多数(至少笔者用过的)都好用。
首先说,Linux的串口通讯和我们逻辑编程的编程方法是不同的:
裸机下需要配置寄存器,然后如果中断方式,设置中断函数,如果查询方式,就查询状态位,判断是否有数据过来,如果要发送数据则就是往发送寄存器发送数据,然后就自动发出去了。
Linux下可不同,那些所谓的驱动都已经写好了,我们需要调用的是Linux统一封装的数据结构和函数来操作串口,对于不同的芯片,只要是Linux操作系统,都可以用这一类型的东西操作串口。
在Linux中所有串口配置数据都可以用struct termios来描述,里面有许多的成员函数,像我们配置波特率,数据位长度和奇偶校验,具体配置方法可以参考这篇程:http://www.linuxidc.com/Linux/2007-12/9815p3.htm
下面是笔者写的程序,大家可以拿来当做模板来使用,主要实现对UART0的操作:
- #include <unistd.h>
- #include <stdio.h>
- #include <termios.h>
- #include <fcntl.h>
- #include <string.h>
- #include <time.h>
- //为了保证用户输入的波特率是个正确的值,所以需要这两个数组验证,对于设置波特率时候,前面要加个B
- int speed_arr[] = { B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300,
- B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300, };
- int name_arr[] = {115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300,
- 115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300, };
- /*-----------------------------------------------------------------------------
- 函数名: set_speed
- 参数: int fd ,int speed
- 返回值: void
- 描述: 设置fd表述符的串口波特率
- *-----------------------------------------------------------------------------*/
- void set_speed(int fd ,int speed)
- {
- struct termios opt;
- int i;
- int status;
- tcgetattr(fd,&opt);
- for(i = 0;i < sizeof(speed_arr)/sizeof(int);i++)
- {
- if(speed == name_arr[i]) //找到标准的波特率与用户一致
- {
- tcflush(fd,TCIOFLUSH); //清除IO输入和输出缓存
- cfsetispeed(&opt,speed_arr[i]); //设置串口输入波特率
- cfsetospeed(&opt,speed_arr[i]); //设置串口输出波特率
- status = tcsetattr(fd,TCSANOW,&opt); //将属性设置到opt的数据结构中,并且立即生效
- if(status != 0)
- perror("tcsetattr fd:"); //设置失败
- return ;
- }
- tcflush(fd,TCIOFLUSH); //每次清除IO缓存
- }
- }
- /*-----------------------------------------------------------------------------
- 函数名: set_parity
- 参数: int fd
- 返回值: int
- 描述: 设置fd表述符的奇偶校验
- *-----------------------------------------------------------------------------*/
- int set_parity(int fd)
- {
- struct termios opt;
- if(tcgetattr(fd,&opt) != 0) //或许原先的配置信息
- {
- perror("Get opt in parity error:");
- return -1;
- }
- /*通过设置opt数据结构,来配置相关功能,以下为八个数据位,不使能奇偶校验*/
- opt.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
- | INLCR | IGNCR | ICRNL | IXON);
- opt.c_oflag &= ~OPOST;
- opt.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
- opt.c_cflag &= ~(CSIZE | PARENB);
- opt.c_cflag |= CS8;
- tcflush(fd,TCIFLUSH); //清空输入缓存
- if(tcsetattr(fd,TCSANOW,&opt) != 0)
- {
- perror("set attr parity error:");
- return -1;
- }
- return 0;
- }
- /*-----------------------------------------------------------------------------
- 函数名: serial_init
- 参数: char *dev_path,int speed,int is_block
- 返回值: 初始化成功返回打开的文件描述符
- 描述: 串口初始化,根据串口文件路径名,串口的速度,和串口是否阻塞,block为1表示阻塞
- *-----------------------------------------------------------------------------*/
- int serial_init(char *dev_path,int speed,int is_block)
- {
- int fd;
- int flag;
- flag = 0;
- flag |= O_RDWR; //设置为可读写的串口属性文件
- if(is_block == 0)
- flag |=O_NONBLOCK; //若为0则表示以非阻塞方式打开
- fd = open(dev_path,flag); //打开设备文件
- if(fd < 0)
- {
- perror("Open device file err:");
- close(fd);
- return -1;
- }
- /*打开设备文件后,下面开始设置波特率*/
- set_speed(fd,speed); //考虑到波特率可能被单独设置,所以独立成函数
- /*设置奇偶校验*/
- if(set_parity(fd) != 0)
- {
- perror("set parity error:");
- close(fd); //一定要关闭文件,否则文件一直为打开状态
- return -1;
- }
- return fd;
- }
- /*-----------------------------------------------------------------------------
- 函数名: serial_send
- 参数: int fd,char *str,unsigned int len
- 返回值: 发送成功返回发送长度,否则返回小于0的值
- 描述: 向fd描述符的串口发送数据,长度为len,内容为str
- *-----------------------------------------------------------------------------*/
- int serial_send(int fd,char *str,unsigned int len)
- {
- int ret;
- if(len > strlen(str)) //判断长度是否超过str的最大长度
- len = strlen(str);
- ret = write(fd,str,len);
- if(ret < 0)
- {
- perror("serial send err:");
- return -1;
- }
- return ret;
- }
- /*-----------------------------------------------------------------------------
- 函数名: serial_read
- 参数: int fd,char *str,unsigned int len,unsigned int timeout
- 返回值: 在规定的时间内读取数据,超时则退出,超时时间为ms级别
- 描述: 向fd描述符的串口接收数据,长度为len,存入str,timeout 为超时时间
- *-----------------------------------------------------------------------------*/
- int serial_read(int fd, char *str, unsigned int len, unsigned int timeout)
- {
- fd_set rfds;
- struct timeval tv;
- int ret; //每次读的结果
- int sret; //select监控结果
- int readlen = 0; //实际读到的字节数
- char * ptr;
- ptr = str; //读指针,每次移动,因为实际读出的长度和传入参数可能存在差异
- FD_ZERO(&rfds); //清除文件描述符集合
- FD_SET(fd,&rfds); //将fd加入fds文件描述符,以待下面用select方法监听
- /*传入的timeout是ms级别的单位,这里需要转换为struct timeval 结构的*/
- tv.tv_sec = timeout / 1000;
- tv.tv_usec = (timeout%1000)*1000;
- /*防止读数据长度超过缓冲区*/
- //if(sizeof(&str) < len)
- // len = sizeof(str);
- /*开始读*/
- while(readlen < len)
- {
- sret = select(fd+1,&rfds,NULL,NULL,&tv); //检测串口是否可读
- if(sret == -1) //检测失败
- {
- perror("select:");
- break;
- }
- else if(sret > 0) <SPAN style="WHITE-SPACE: pre"> </SPAN>//检测成功可读
- {
- ret = read(fd,ptr,1);
- if(ret < 0)
- {
- perror("read err:");
- break;
- }
- else if(ret == 0)
- break;
- readlen += ret; //更新读的长度
- ptr += ret; //更新读的位置
- }
- else //超时
- {
- printf("timeout!\n");
- break;
- }
- }
- return readlen;
- }
- int main()
- {
- int fd;
- int ret;
- char str[]="hello linux serial!";
- char buf[100];
- fd = serial_init("/dev/ttyS1",115200,1);
- if(fd < 0)
- {
- perror("serial init err:");
- return -1;
- }
- ret = serial_send(fd,str,22);
- printf("send %d bytes!\n",ret);
- serial_read(fd,buf,100,5000);
- printf("the buf is :%s\n",buf);
- close(fd);
- return 0;
- }
做几点简要的说明:
1.高级的一点芯片,如ARM9一般有个UART口专门用于打印调试信息,其对应的设备文件是ttyS0,然后UART0对应 ttyS1,以此类推,笔者这里打开的是ttyS1那就是UART0
2.可以看到一点,我定义了一个struct termios opt,对UART的所有配置信息都是在这个结构体中完成。
3.还是那句,发送和接受数据就和读写文件一样简单,发送就是write操作,读就是read操作
4.在读的时候,笔者用的是循环查询的方式,在Linux中,一般用的方法可以创建一个线程,用这个线程来循环调用select方法,来反复查询UART对应的设备文件是否可读(对应于是否有数据从串口传入),这种阻塞方式不是盲目等待,后来有调度机制,可以代替裸机编程中的中断方式。
下面是运行结果:
![linux](/linuxfile/logo.gif)