以下都是从友善之臂《04- Tiny6410 Linux开发指南-20111020》复制出来的,我所做的工作就是将友善之臂提供的源程序进行详细注释,另外将一些大函数分解成小函数。这段代码不长,但是涉及到多个比较不容易接触的C语言知识点。
说明:armcomtest 是友善之臂为了方便测试而开发的linux 下的简易实用串口终端程
序,它使用标准的系统调用,和硬件无关。一般Linux 系统系统启动后,串口 0,1,2对应的设
备名分别为/dev/ttySAC0,1,2,3
测试串口2 需要借助另一台带有串口的PC,使用我们提供的串口线和扩展小板( 选购
配件) ,连接好 COM2 和另一台PC的串口,并如前所述设置该PC的超级终端为波特率115200 ,
无流控制,其他默认。
在命令行下输入:
#armcomtest –d /dev/ttySAC1 - o
这时如果输入字符会在另一台PC的超级终端出现,反之亦然。
如果要测试串口3,则需要连接扩展小板的COM3 ,并在命令行输入:
#armcomtest –d /dev/ttySAC2 - o
下面是测试时的界面:
/*************************************************************************** ** 文件: comtest.c ** 描述: ** 以串口通讯的测试程序 ** **------------------------------------------------------------------------------------------------------ ********************************************************************************************************/ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <stdio.h> /*标准输入输出定义*/ #include <stdlib.h> /*标准函数库定义*/ #include <termio.h> /*PPSIX 终端控制定义*/ #include <unistd.h>/*Unix 标准函数定义,使用exit()*/ #include <fcntl.h>/*文件控制定义*/ #include <getopt.h>/*参数提取定义*/ #include <time.h> #include <errno.h>/*错误号定义*/ #include <string.h> #include <assert.h> int OutputHex = 0;//是否以十六进制发送。OutputHex为1时,以十六进制发送;为0,以字符串方式发送 int OutputToStdout = 0;//是否将消息同样发送一份到标准输出。为1时,发送;为0,不发送 int UseColor = 0; //是否使用颜色。为1时,使用颜色;为0,不使用颜色。 struct termios BackupTtyAttr;//终端属性的备份 int IsWrite=0; static void Error(const char *Msg) { fprintf (stderr, "%s\n", Msg); fprintf (stderr, "strerror() is %s\n", strerror(errno)); exit(EXIT_FAILURE); } static void Warning(const char *Msg) { fprintf (stderr, "Warning: %s\n", Msg); } static int SerialSpeed(const char *SpeedString) { int SpeedNumber = atoi(SpeedString); # define TestSpeed(Speed) if (SpeedNumber == Speed) return B##Speed TestSpeed(1200); TestSpeed(2400); TestSpeed(4800); TestSpeed(9600); TestSpeed(19200); TestSpeed(38400); TestSpeed(57600); TestSpeed(115200); TestSpeed(230400); Error("Bad speed"); return -1; } /** *@brief 打印错误信息 */ static void PrintUsage(void) { fprintf(stderr, "comtest - interactive program of comm port\n"); fprintf(stderr, "press [ESC] 3 times to quit\n\n"); fprintf(stderr, "Usage: comtest [-d device] [-t tty] [-s speed] [-7] [-c] [-x] [-o] [-h]\n"); fprintf(stderr, " -7 7 bit\n"); fprintf(stderr, " -x hex mode\n"); fprintf(stderr, " -o output to stdout too\n"); fprintf(stderr, " -c stdout output use color\n"); fprintf(stderr, " -h print this help\n"); exit(-1); } /******************************************************************************************************* ** 函数: WaitFdWriteable, 等待文件可写 **------------------------------------------------------------------------------------------------------ ** 参数: Fd 文件描述符 ** 返回: void ** 函数说明:使用inline标识符,防止因为函数频繁的调用占用大量的栈空间 ** 日期: 2013.06.19 ********************************************************************************************************/ static inline void WaitFdWriteable(int Fd) { fd_set WriteSetFD; //定义可写的设备集合 FD_ZERO(&WriteSetFD);//将可写的设备集合清空 FD_SET(Fd, &WriteSetFD);//将fd添加到可写的设备集合中 //select函数原型: int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); //值得说明的是:int maxfdp是一个整数值,是指需要测试的文件描述符的数目,测试的描述符范围从0到nfds-1.即所有文件描述符的最大值加1,不能错! if (select(Fd + 1, NULL, &WriteSetFD, NULL, NULL) < 0) {//判断是否有可写的设备,如果没有就一直阻塞,这里没有设置超时,如果没有可写的,会一直阻塞下去 //select函数中readfds、errorfds描述符集合都为空,表示不进行测试 Error(strerror(errno)); } } void setAttrByArgvs(){ } /******************************************************************************************************* ** 函数: OutputStdChar, 打开一个串口 **------------------------------------------------------------------------------------------------------ ** 参数: FILE *File 文件描述符 int OutputHex,是否以十六进制发送。OutputHex为1时,以十六进制发送;为0,以字符串方式发送 unsigned char aCharToSend将要发送的字符 ** 返回: void ** ** 日期: 2013.06.19 ********************************************************************************************************/ void OutputStdChar(FILE *File,int OutputHex,unsigned char aCharToSend) {//向设备写数据 char Buffer[10]; int Len = sprintf(Buffer, OutputHex ? "%.2X " : "%c", aCharToSend);//%.2X表示输出01,02样式的十六进制数 // int Len = sprintf(Buffer, 0x01);//%.2X表示输出01,02样式的十六进制数 fwrite(Buffer, 1, Len, File); } /******************************************************************************************************* ** 函数: SetFD, 设置文件描述符 **------------------------------------------------------------------------------------------------------ ** 参数:int argc char **argv int * CommFd 串口文件描述符 int * TtyFd 终端文件描述符 ** 返回: void **函数说明: **本函数通过对main函数的argc、argv进行参数的提取,实现对串口文件描述符(CommFd)和终 端文件描述符TtyFd的设置 ** 日期: 2013.06.19 ********************************************************************************************************/ void SetFD(int argc, char **argv,int * CommFd,int * TtyFd){ struct termios TtyAttr; //终端属性 int DeviceSpeed = B2400; //串口波特率 int TtySpeed = B2400; //终端波特率 int ByteBits = CS8; //数据位:8位 const char *DeviceName = "/dev/ttySAC1";//串口设备名 const char *TtyName = "/dev/tty"; //终端设备名,防止重要的信息被用户重定向 opterr = 0; printf("init........\n"); //通过Argc和Argv设置必要的参数 for (;;) { int c = getopt(argc, argv, "d:s:t:7xoch");//利用getopt将argv参数一个个提取出来 if (c == -1) break; switch(c) { case 'd'://设置串口的名称 DeviceName = optarg; break; case 't'://设置终端的名称 TtyName = optarg; break; case 's'://设置比特率 if (optarg[0] == 'd') {//设置串口的波特率 DeviceSpeed = SerialSpeed(optarg + 1); } else if (optarg[0] == 't') {//设置终端的波特率 TtySpeed = SerialSpeed(optarg + 1); } else TtySpeed = DeviceSpeed = SerialSpeed(optarg);//如果没有带d或t,直接将两个设备的波特率设置成相同的 break; case 'o'://设置同时将消息向标准输出(stdout)输出 OutputToStdout = 1; break; case '7'://设置数据位为7位 ByteBits = CS7; break; case 'x': OutputHex = 1;//以十六进制输出 printf("OutputHex = 1\n"); break; case 'c'://使用颜色标记 UseColor = 1; break; case '?': case 'h': default: PrintUsage();//输出main参数的说明 } }//end of for(;;) printf("end of getopt\n"); if (optind != argc)//判断参数是否符合要求 PrintUsage(); //输出main参数的说明 *CommFd = open(DeviceName, O_RDWR, 0);//以读写的方式打开 if (*CommFd < 0) Error("Unable to open device");//不能打开设备 if (fcntl(*CommFd, F_SETFL, O_NONBLOCK) < 0)//设置文件访问模式为非阻塞 Error("Unable set to NONBLOCK mode"); //不能使用NONBLOCK模式 memset(&TtyAttr, 0, sizeof(struct termios)); TtyAttr.c_iflag = IGNPAR;//忽略输入行中的中止状态 TtyAttr.c_cflag = DeviceSpeed | HUPCL | ByteBits | CREAD | CLOCAL;//DeviceSpeed: //HUPCL:关闭时挂断调制解调器;CREAD:启用字符接收器;CLOCAL:忽略所有调制解调器的状态行 TtyAttr.c_cc[VMIN] = 1;//设置MIN值,read调用将一直等待,直到有MIN个字符可读的时候才返回,返回是读取的字符数量。到达文件结尾的时候返回0 if (tcsetattr(*CommFd, TCSANOW, &TtyAttr) < 0)//立即对属性进行修改,不等当前输出完成 Warning("Unable to set comm port"); *TtyFd = open(TtyName, O_RDWR | O_NDELAY, 0);//只有在CTEAT模式下,才需要第三个参数,这边的第三个参数是没有作用的。 if (*TtyFd < 0) Error("Unable to open tty"); TtyAttr.c_cflag = TtySpeed | HUPCL | ByteBits | CREAD | CLOCAL; if (tcgetattr(*TtyFd, &BackupTtyAttr) < 0)//将当前Tty的属性备份在BackupTtyAttr,以便在程序退出时还原Tty的设置 Error("Unable to get tty"); if (tcsetattr(*TtyFd, TCSANOW, &TtyAttr) < 0) Error("Unable to set tty"); } /******************************************************************************************************* ** 函数: OutputCharUseColor 输出带颜色的字符 **------------------------------------------------------------------------------------------------------ ** 参数:char * colorCode 颜色代码 unsigned char aChar 要输出的字符 ** 返回: void ** 日期: 2013.06.19 ********************************************************************************************************/ void OutputCharUseColor(char * colorCode,unsigned char aChar){ if (OutputToStdout) { //同时向标准输出写消息 if (UseColor) fwrite(colorCode, 1, 8, stdout); OutputStdChar(stdout,OutputHex,aChar); if (UseColor) fwrite("\x1b[00m", 1, 8, stdout); fflush(stdout);//将stdout缓冲区中的数据立即输出,即在屏幕上显示 } } int uart_pthread(int argc, char **argv) { printf("into main\n"); int SendBufferIndex=0; char * SendBuffer=(char *)malloc(sizeof(char)*20);//发送缓存 /** TtyFD是为了防止与用户交互的信息被重定向,而没有在屏幕上显示。使用TtyFd可以直接将 不想被重定向的信息直接向用户终端(屏幕)输出。 **/ int CommFd, TtyFd; //串口、终端描述符 SetFD( argc, argv,&CommFd,&TtyFd); for (;;) { unsigned char aCharToSend = 0; fd_set ReadSetFD; //可读设备集合 FD_ZERO(&ReadSetFD); //清空可读设备集合 FD_SET(CommFd, &ReadSetFD);//将串口加入可读设备集合 FD_SET( TtyFd, &ReadSetFD);//将终端加入可读设备集合 # define max(x,y) ( ((x) >= (y)) ? (x) : (y) )//最大值函数,返回两个数中较大的数 if (select(max(CommFd, TtyFd) + 1, &ReadSetFD, NULL, NULL, NULL) < 0) {//同时测试串口和终端是否可读 Error(strerror(errno)); } # undef max if (FD_ISSET(CommFd, &ReadSetFD)) {//判断串口是否可读 while (read(CommFd, &aCharToSend, 1) == 1) {//从串口中读取一个char型,放在aCharToSend WaitFdWriteable(TtyFd);//会一直阻塞在这里,直到终端设备可写 if (write(TtyFd, &aCharToSend, 1) < 0) {//向屏幕输出收到的字符 Error(strerror(errno)); //如果写入错误,输出错误信息 } OutputCharUseColor("\x1b[01;34m",aCharToSend); } } // printf("------INTO if (FD_ISSET(TtyFd, &ReadSetFD)) "); if (FD_ISSET(TtyFd, &ReadSetFD)) { //判断终端是否可读 while (read(TtyFd, &aCharToSend, 1) == 1) {//从终端中读取一个值 // fprintf(stderr,"\n------SendBufferIndex:%d ",SendBufferIndex); SendBuffer[SendBufferIndex++]=aCharToSend; // fprintf(stderr, "\nRead From Tty"); static int EscKeyCount = 0; //按下Esc的次数 OutputCharUseColor("\x1b[01;31m",aCharToSend); if (aCharToSend == '\r') {//监测是否按下回车 SendBuffer[SendBufferIndex-1]='\0'; IsWrite=1; fprintf(stderr, "\x1b[01;34m you have enter :%s\n \x1b[00m",SendBuffer); // fprintf(stderr,"\n---11---IsWrite==%d ",IsWrite); } if(SendBufferIndex==19){ SendBuffer[SendBufferIndex]='\0'; // fprintf(stderr, "you have enter :%s\n",SendBuffer); IsWrite=1; } if(IsWrite==1){ // fprintf(stderr,"\n------WaitFdWriteable "); WaitFdWriteable(CommFd); if (write(CommFd, SendBuffer, SendBufferIndex) < 0) { Error(strerror(errno)); } IsWrite=0; SendBufferIndex=0; } if (aCharToSend == '\x1b') {//监测是否按下Esc fprintf(stderr, "EscKeyPressed\x1b\n"); EscKeyCount ++; if (EscKeyCount >= 3) goto ExitLabel; }else{ EscKeyCount = 0; } // fprintf(stderr,"\n---22---IsWrite==%d ",IsWrite); } } }//end of for (;;) ExitLabel: free(SendBuffer); if (tcsetattr(TtyFd, TCSANOW, &BackupTtyAttr) < 0)//恢复之前终端的设置 Error("Unable to set tty"); return 0; }