带有详细注释的串口测试程序

以下都是从友善之臂《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;
}

作者:kissazi2 
出处:http://www.cnblogs.com/kissazi2/ 
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载:http://www.cnblogs.com/kissazi2/p/3203242.html

时间: 2024-11-01 10:10:04

带有详细注释的串口测试程序的相关文章

带有详细注释的 Redis 3.0 代码 (github.com)

 Redis 3.0 源码注释 本项目是注释版的 Redis 3.0 源码, 原始代码来自: https://github.com/antirez/redis . 这份注释是我在创作新版<Redis 设计与实现>期间, 为了了解 Redis 的内部实现而制作的, 所有在书中有介绍的内容, 在源码中都进行了相应的注释. 在注释的过程中, 除了少量空格和空行方面的调整外, 没有对原始代码进行任何其他改动, 最大程度地保证了代码的"原汁原味". 希望这份注释源码能给大家学习和

OpenCV2.4版本的camshiftdemo.cpp的详细注释

要我怎么感谢这位仁兄... #include "opencv2/video/tracking.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> #include <ctype.h> using namespace cv; using namespace std; Mat

【iOS开发】30多个iOS常用动画,带详细注释

30多个iOS常用动画,带详细注释 // // CoreAnimationEffect.h // CoreAnimationEffect // // Created by VincentXue on 13-1-19. // Copyright (c) 2013年 VincentXue. All rights reserved. // import   @interface CoreAnimationEffect : NSObject pragma mark - Custom Animation

android 导航-求一份android百度地图gps导航开发代码(带有详细代码)

问题描述 求一份android百度地图gps导航开发代码(带有详细代码) 各位大神,给一份android百度地图gps导航开发代码,带有详细讲解,如果有视频就更好了,.有没有?有没有?有没有?跪求-- 解决方案 以前回答过类似的问题,姐姐毫无保留地给了代码,题主拿到代码就高高兴兴蹦蹦跳跳地跑啦.所以除非lz有诚意先采纳了,否则姐姐一般不会先给出代码了. 解决方案二: 百度API里面不是有Demo么

大神,C++代码递归代码求详细注释,特别是if中的看不懂,谢谢了

问题描述 大神,C++代码递归代码求详细注释,特别是if中的看不懂,谢谢了 void foo(int x, int y) { if (x > 1000) return; if (x == y && x == 1) { printf("%d %d ", 1, 1); foo(x + y, x); return; } else { printf("%d ", x); foo(x + y, x); } } 解决方案 void foo(int x, i

游戏编程-求有详细注释的matlab 智能算法相关的代码

问题描述 求有详细注释的matlab 智能算法相关的代码 网盘,邮箱454170989@qq.com 无聊者勿扰![ 解决方案 ? <神经网络入门> .? (连载之一) 用平常语言介绍神经网络(Neural Networks in Plain English) 因为我们没有很好了解大脑,我们经常试图用最新的技术作为一种模型来解释它.在我童年的时候,我们都坚信大脑是一部电话交换......<br/><strong>答案就在这里:</strong><a t

Jquery实现瀑布流布局(备有详细注释)_jquery

本文实例讲述了Jquery实现瀑布流布局的方法.分享给大家供大家参考.具体如下: 瀑布流布局最近真的很流行,很多人都跟我一样想知道是怎么做出来的吧,经过网上搜索大量的参考结合N边的实验今天终于被我写出来了,为了便于大家理解我使用了jQuery(当然用源生js代码执行的效率会高一些,但是很多人多源生js不是很熟练). <!doctype html> <html> <head> <meta charset="utf-8"> <title

求delphi7编写ModBus RTU通讯实例,最好有详细注释 在线等????????

问题描述 求delphi7编写ModBus RTU通讯实例,最好有详细注释 在线等???????? 求delphi7编写ModBus RTU通讯实例,最好有详细注释 在线等???????? 解决方案 http://download.csdn.net/detail/xauterp/3967887http://www.pudn.com/downloads259/sourcecode/windows/detail1194933.html

DirectShow中写push模式的source filter流程 + 源代码(内附详细注释)

虽然网上已有很多关于DirectShow写source filter的资料,不过很多刚开始学的朋友总说讲的不是很清楚(可能其中作者省略了许多他认为简 单的过程),读者总希望看到象第一步怎么做,第二步怎么做....这样的demo.其实写你的第一个filter是有一定难度的,只要过了这关以后 就容易多了.由于最近需要自己写一个push推模式的source filter,加上刚激活了Blog,不好意思Blog上没有一篇文章,所以将写这个filter的过程写下来 ,为了照顾刚开始学的朋友,我采用第一步第