文章出處

 

以下都是從友善之臂《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 
下面是測試時的界面:

image

 

/***************************************************************************
** 文件: 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;
}

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()