|
1、linux下串口编程,实现两个文件ttyS.c和ttyS.h文件。说白了也就是read和write,不过需要注意一些termios的参数配置。
- /*
- * 非标准模式
- * ICANON---代表规范化方式
- * ISIG---代表CTRL+C,否则0x03不能显示
- * ICRNL和IGNCR----会导致0x0D变成0x0A
- * 最后一个设置避免0x13丢失
- */
- options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
- options.c_iflag &= ~(ICRNL|IGNCR);
-
- options.c_iflag &= ~(BRKINT | INPCK | ISTRIP | IXON);
复制代码- /*
- * 设置等待时间和最小接收字符
- *
- * VTIME定义要求等待的时间量(取值不能大于cc_t)
- * VMIN定义了要求等待的最小字节数
- * options.c_cc[VTIME] = X; //设置从获取到1个字节后开始计时的超时时间
- * options.c_cc[VMIN] = Y; //设置要求等待的最小字节数
- *
- * 在原始模式下对read()函数的影响:
- * X=0 Y!=0 函数read()只有在读取了Y个字节的数据或者收到一个信号的时候才返回
- * X!=0 Y=0 即使没有数据可以读取,read()函数等待X时间量后返回
- * X!=0 Y!=0 第一个字节数据到时开始,最先满足收到Y个字节或达超时时间X任意一个条件,read()返回
- * X=0 Y=0 即使读取不到任何数据,函数read也会立即返回
- */
- options.c_cc[VTIME] = 0;
- options.c_cc[VMIN] = 1;
复制代码 基本上就这些比较重要吧,其他的都很简单,纯c操作。
2、开始分析程序,也学学坛友的好习惯,代码详细贴出来分析,其实我还是不大习惯,以前基本上只是说重点,明白人一看就懂,好比这个帖子,有用的就前面这一点点东西,当然老鸟除外。
1)主程序,算是一个test程序吧,测试串口接收和发送
- /**
- * brief main
- * note 测试
- * param none
- * retval none
- */
- int main(int argc, char **argv)
- {
- char ttyDev[20];
- int ttysFD = -1;
-
- if (argc == 2) {
- strcpy(ttyDev, argv[1]);
- } else {
- strcpy(ttyDev, "/dev/ttyATH0");
- }
-
- ttyS_dbgLOG("#TTYS\n");
- ttysFD = ttyS_Open(ttyDev);
- if (ttysFD == -1) {
- ttyS_dbgLOG("ttyDev Open Failure\n");
- }
- if ( ttyS_Init(ttysFD, 115200, 'N', 8, 1, 'N') == FALSE ) {
- ttyS_dbgLOG("ttyDev Init Failure\n");
- } else {
- ttyS_dbgLOG("ttyDev Init Success\n");
- }
- ttyS_Send( ttysFD, "ttyDev OK", strlen("ttyDev OK") );
-
- while (1) {
- bzero(&ttyRX, TTYS_BUF_MAX);
- ttyS_Receive( ttysFD, ttyRX, TTYS_BUF_MAX );
- }
-
- ttyS_Close(ttysFD);
- return 1;
- }
复制代码
基本上按流程走,打开、初始化、初始化好发送字符来测试发送,接收采用超时接收,非阻塞方式(后面会详细介绍)。
2)打印调试函数,采用宏方式,在后面可以直接通过宏关闭。
- /**
- * brief ttyS_dbgHEX
- * note ttyS调试打印HEX
- * param None
- * retval None
- */
- void ttyS_dbgHEX(char *txBuf, int Len)
- {
- int i;
- if (Len >= 8) {
- for ( i=0; i<Len/8; i++ ) {
- ttyS_dbgLOG("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n",
- txBuf[8*i+0],
- txBuf[8*i+1],
- txBuf[8*i+2],
- txBuf[8*i+3],
- txBuf[8*i+4],
- txBuf[8*i+5],
- txBuf[8*i+6],
- txBuf[8*i+7] );
- }
- }
- if (Len%8 > 0) {
- for ( i=(Len/8)*8; i<Len; i++ ) {
- ttyS_dbgLOG("0x%02x ", txBuf[i]);
- }
- ttyS_dbgLOG("\n");
- }
- }
复制代码
后面调试会用到这个函数,当然建议大家有良好的调试习惯。
3)ttyS_Open打开相应PORT的串口,使用类似/dev/ttySAC1的名字。
- /**
- * brief ttyS_Open
- * note ttyS打开函数
- * param None
- * retval None
- */
- int ttyS_Open(char* Port)
- {
- #define ENABLE_TERMINAL_ISATTY 0
-
- int fd;
- fd = open( Port, O_RDWR | O_NOCTTY | O_NDELAY);
- if ( FALSE == fd ) {
- ttyS_dbgLOG("Open ttyS failure\n");
- return(FALSE);
- }
- /* 判断串口的状态是否为阻塞状态
- * 注意非阻塞设置方式:fcntl(fd, F_SETFL, 0)
- * 注意阻塞设置方式:fcntl(fd, F_SETFL, FNDELAY)
- */
- if ( fcntl(fd, F_SETFL, 0) < 0 ) {
- ttyS_dbgLOG("Fcntl ttyS failure\n");
- return(FALSE);
- }
-
- /* 测试是否为终端设备 */
- #if ENABLE_TERMINAL_ISATTY
- if ( 0 == isatty(STDIN_FILENO) ) {
- ttyS_dbgLOG("Isatty failure\n");
- return(FALSE);
- } else {
- ttyS_dbgLOG("Isatty success\n");
- }
- #endif
- ttyS_dbgLOG("Open ttyS fd = %d\n", fd);
- return fd;
- }
复制代码
主要涉及到串口打开open中的flag,需要注意O_RDWR | O_NOCTTY | O_NDELAY。
O_RDWR :串口可读写,也就是收发允许。
O_NOCTTY:不想成为“控制终端”控制的程序,不说明这个标志的话,任何输入都会影响你的程序。
O_NDELAY:不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。
还有一点需要注意的是:可以在打开后使用cntl来改变串口F_SETFL属性,是否阻塞,这个关系到程序结构。
4)ttyS_Close关闭串口函数,传入参数为打开的串口句柄。
- /**
- * brief ttyS_Close
- * note ttyS关闭
- * param None
- * retval None
- */
- int ttyS_Close(int fd)
- {
- if ( close(fd) == -1 ) {
- return FALSE;
- } else {
- return TRUE;
- }
- }
复制代码
主要是close的用法,关闭操作。
5)ttyS_Send发送函数,主要是write写操作
- /**
- * brief ttyS_Send
- * note ttyS发送
- * param None
- * retval None
- */
- int ttyS_Send(int fd, char *txBuf, int Len)
- {
- int realLen = 0;
- realLen = write(fd, txBuf, Len);
- if ( realLen == Len ) {
- ttyS_dbgLOG("ttyS send %d\n", realLen);
- ttyS_dbgHEX(txBuf, realLen);
- return realLen;
- } else {
- tcflush(fd, TCOFLUSH);
- return FALSE;
- }
- }
复制代码
6)ttyS_Receive接收函数,使用非阻塞方式,select超时接收
- /**
- * brief ttyS_Receive
- * note ttyS接收
- * param None
- * retval None
- */
- int ttyS_Receive(int fd, char *rxBuf, int Len)
- {
- int realLen;
-
- #define ENABLE_NOBLOCK_READ 1
-
- #if ENABLE_NOBLOCK_READ
- int fsSel;
- fd_set fsRead;
- struct timeval time;
- FD_ZERO(&fsRead);
- FD_SET (fd, &fsRead);
- /* 超时时间25毫秒 */
- time.tv_sec = 0;
- time.tv_usec = 25000;
- /* 使用select实现非阻塞的串口接收 */
- fsSel = select( fd+1, &fsRead, NULL, NULL, &time );
-
- if ( FD_ISSET(fd, &fsRead) ) {
- realLen = read(fd, rxBuf, Len);
- ttyS_dbgLOG("ttyS receive %d\n", realLen);
- ttyS_dbgHEX(rxBuf, realLen);
- return realLen;
- } else {
- return FALSE;
- }
- #else
- realLen = read(fd, rxBuf, Len);
- ttyS_dbgLOG("ttyS receive %d\n", realLen);
- ttyS_dbgHEX(rxBuf, realLen);
- return realLen;
- #endif
- }
复制代码
需要注意select的使用:
机制中提供一fd_set的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由用户完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。
常见用法:
- fd_set set;
- FD_ZERO(&set); /*将set清零使集合中不含任何fd*/
- FD_SET(fd, &set); /*将fd加入set集合*/
- FD_CLR(fd, &set); /*将fd从set集合中清除*/
- FD_ISSET(fd, &set); /*在调用select()函数后,用FD_ISSET来检测fd在fdset集合中的状态是否变化返回整型,当检测到fd状态发生变化时返回真 */
复制代码
还有其他很多用法,比如select的返回值,这个在socket编程中使用的很多,后面我会分享一个socket 客户端的例子,比较详细的。说明一下,第一个fd+1这个是函数规定的,不要纠结这个,是他的机制。
好了基本上就是这一点点分析了,其实想学学不是靠我们分析的,要自己学会分析问题,不懂的man看看,还不懂在去网上找找别人的分析。其实要说开发,主要靠文档,api reference、doc、install、readme、test等等,都是最好的学习资料。不要局限网络,太杂了,没有太多参考价值。
|
|