Linux 串口編程


串口編程     

       串口在Linux中也是一個設備文件(一切皆文件),這一部分從裸機開發轉變過來還需要一定時間適應,不過可以去看看野火的Linux教程,中關於使用shell操作串口的示例有一個宏觀的的體驗和認知。回到通過程序使用串口收發數據,其也就如同讀寫一個普通文件一般,一般步驟是

1、打開串口設備(字符設備)(open系統調用)。

2、配置串口。

3、然后就是如同讀寫文件一樣使用,read和write函數進程數據的收發了。

4、最后就是使用完后的關閉close操作。

第一步打開字符設備通過調用open,這個過程和打開一個普通文件可能的唯一區別可能就是需要加上O_NOCTTY選項實際測試不加上好像也不會因為接收到0x03而導致程序退出(Ctrl+c == ascii(0x03))。

然后就是配置串口的約定參數如struct termios

  {
    tcflag_t c_iflag;        /* input mode flags */
    tcflag_t c_oflag;        /* output mode flags */
    tcflag_t c_cflag;        /* control mode flags */
    tcflag_t c_lflag;        /* local mode flags */
    cc_t c_line;            /* line discipline */
    cc_t c_cc[NCCS];        /* control characters */
    speed_t c_ispeed;        /* input speed 
    speed_t c_ospeed;        /* output speed */
  };

1.1 c_iflag 輸入設置

負責控制串口輸入數據的處理配置。

 

標志  作用 標志 作用
IGNPAR  忽略楨錯誤和奇偶校驗 IGNBRK 忽略 BREAK 條件
INPCK  打開輸入奇偶校驗 PARMRK 標記奇偶錯,只有設置INPCK並且沒有設置 IGNPAR 才有效
ISTRIP  去掉字符第8位 IGNCR 忽略輸入中的回車CR
ICRNL 將輸入的CR轉換為 NL,除非設了IGNCR INLCR 將輸入的NL(換行)轉換為CR
IXON 啟用輸出的 XON/XOFF 流控制 IXOFF 啟用輸入的 XON/XOFF 流控制
IXANY 嘗試任何字符可做重啟輸出信號,默認只能START字符恢復輸出 IUCLC  (no posix)將輸入中的大寫字母映射為小寫字母
BRKINIT 如果IGNBRK沒有設置當這是一個控制終端時將發送SIGINT信號 IUTF8 (no posix)輸入是utf8字符允許在cooked 模式下正確進行字符擦除
IMAXBEL 

(no posix)輸入隊列滿時提示(ring bell)

   

 

使用軟件流控制是啟用 IXON、IXOFF 和 IXANY 選項:
opt.c_iflag |= (IXON | IXOFF | IXANY);
相反,要禁用軟件流控制是禁止上面的選項:
opt.c_iflag &= ~(IXON | IXOFF | IXANY);

除此之外還有一個更粗暴的方式就是

opt.c_iflag = 0;

opt.c_iflag |= IXOFF;

1.2 c_oflag 輸出設置

負責控制串口輸出數據的處理配置。

標志  作用 標志 作用
OPOST  執行輸出處理 OFILL  對於延遲使用填充符
ONLCR  將NL轉換為CR-NL OCRNL  將輸出的CR轉換為NL
ONLRET  不輸出CR回車 ONOCR  在0列(行首)不輸出CR
OFDEL  填充符為DEL,否則為 NULL OLCUC  將輸出的小寫字符轉換為大寫字符 
XTABS  將制表符擴充為空格 BSDLY  退格延遲屏蔽
CRDLY  CR 延遲屏蔽 CMSPAR 標志或空奇偶性
FFDLY  換頁延遲屏蔽 NLDLY  新行延遲屏蔽

啟用輸出處理
啟用輸出處理需要在 c_oflag 成員中啟用 OPOST 選項,其操作方法如下:

options.c_oflag |= OPOST;

使用原始輸出
就是禁用輸出處理,使數據能不經過處理、過濾地完整地輸出到串口。
當 OPOST 被禁止,c_oflag 其它選項也被忽略,其操作方法如下:

options.c_oflag &= ~OPOST;

 

1.3 c_cflag 控制選項

可設置串口的波特率、數據位、奇偶校驗、停止位以及硬件流控制。

標志 作用 標志 作用
CSIZE  數據位屏蔽  CS7 7位數據 
CS5  5位數據 CS8 8位數據 
CS6 6位數據  CLOCAL 忽略modem控制線 
PARENB 校驗使能  PARODD 寄校驗使能,否則是偶校驗 
CSTOPB 兩位停止位,否則為1位  CREAD 打開接收 
CRTSCTS  硬件流控( no posix)    

比如關閉硬件流控,115200波特率,8位數據位,1位停止,偶校驗配置如下:

    //設置串口輸出波特率
    cfsetospeed(&opt, B115200);
    //設置串口輸入波特率
    cfsetispeed(&opt, B115200);
    //設置數據位數
    opt.c_cflag &= ~CSIZE;
    opt.c_cflag |= CS8;
    //偶校驗位
    opt.c_cflag |= PARENB;
    opt.c_cflag &= ~PARODD;
    //設置停止位 1bit
    opt.c_cflag &= ~CSTOPB;
    //激活本地連接和接受使能選項
    opt.c_cflag |= CLOCAL | CREAD;

 

1.3 c_lflag 控制選項

控制串口驅動怎樣控制輸入字符。

標志 作用 標志 作用
ISIG 使能特殊字符的信號發送  NOFLSH  在中斷或退出鍵后禁用刷清
ICANON  啟用規范輸入,默認開啟 IEXTEN  啟用擴充的輸入字符處理
XCASE   ECHOCTL  如果設置了 ECHO,除了 TAB, NL, START, 和 STOP 之外的 ASCII 控制信號被回顯為^X字符形式,
ECHOPRT   ECHO  回送輸入關閉
ECHOE

如果設置了 ICANON,字符 ERASE

擦除前一個輸入字符,WERASE 擦除前一個詞

ECHONL  如果設置了 ICANON,回送NL
ECHOK  如果設置了 ICANON,字符KILL刪除當前行。 ECHOKE  如果設置了 ICANON,回顯 KILL 時將刪除一行中的每個字符,如同指定了 ECHOE 和 ECHOPRT 一樣
PENDIN   TOSTOP  對於后台輸出發送SIGTTOU信號

經典輸入
經典輸入是以面向行設計的。輸入字符會被放入一個緩沖之中,這樣可以與用戶以交互的方式編輯緩沖的內容,直到收到CR(carriage return)或者LF(line feed)字符。

options.c_lflag |= (ICANON | ECHO | ECHOE);

原始輸入
輸入字符只是被原封不動的接收。一般情況中,如果要使用原始輸入模式,程序中需要去掉ICANON,ECHO,ECHOE和ISIG選項:

options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

1.4 c_cc[NCCS] 控制字符

主要用於控制串口在收到指定字符設備時的處理方式,如果設置對應的控制字符為1,則特殊字符的特殊功能將啟用,比如

//當ISIG設置時收到Ctrl+c(或0x0177)是認為這是一個中斷,將發送SIGINT信號
options.c_cc[VINTR] = 1;

 

其中VMIN和VTIME是比較特殊的兩個控制字符

當 MIN > 0,TIME > 0 時

計時器在收到第一個字節后啟動, 在計時器超時之前 (TIME 的時間到) , 若已收到 MIN個字節,則 read 返回 MIN 個字節,否則,在計時器超時后返回實際接收到的字節。

注意:因為只有在接收到第一個字節時才開始計時,所以至少可以返回 1 個字節。這種情形中,在接到第一個字節之前,調用者阻塞。如果在調用 read 時數據已經可用,則如同在 read 后數據立即被接到一樣。

當 MIN > 0,TIME = 0 時

MIN 個字節完整接收后,read 才返回,這可能會造成 read 無限期地阻塞。

當 MIN = 0, TIME > 0 時

TIME 為允許等待的最大時間,計時器在調用 read 時立即啟動,在串口接到 1 字節數據或者計時器超時后即返回,如果是計時器超時,則返回 0。

當 MIN = 0,TIME = 0 時

如果有數據可用,則 read 最多返回所要求的字節數,如果無數據可用,則 read 立即返回 0。

     如果只使用四線串口,需要關閉流控,否則可能會出現無法接收數據的問題。其次是幾個標志位的名字很像,千萬不要不把另一個的標志位的宏賦值到另一個標志位上這樣牛頭不對馬嘴的還不容易發現問題。

參考代碼:

  1 /**
  2   * @brief  初始化串口
  3   * @param  device 設備
  4   * @retval -1 失敗
  5   *           0 ok
  6   */
  7 int init_serial(char *device)
  8 {
  9     struct termios opt;
 10     int uart_fd;
 11     uart_fd = open(device,O_RDWR|O_NOCTTY);
 12     if(uart_fd<0){
 13         printf("The %s device open failed.\n",device);
 14         return -1;    
 15     }
 16     #if 1
 17     //fcntl(uart_fd, F_SETFL, FNDELAY);
 18     //清空串口接收緩沖區
 19     tcflush(uart_fd, TCIOFLUSH);
 20     // 獲取串口參數 opt
 21     tcgetattr(uart_fd, &opt); 
 22     //設置串口輸出波特率
 23     cfsetospeed(&opt, B115200);
 24     //設置串口輸入波特率
 25     cfsetispeed(&opt, B115200);
 26     //設置數據位數
 27     opt.c_cflag &= ~CSIZE;
 28     opt.c_cflag |= CS8;
 29     //偶校驗位
 30     opt.c_cflag |= PARENB;
 31     opt.c_cflag &= ~PARODD;
 32     //設置停止位 1bit
 33     opt.c_cflag &= ~CSTOPB;
 34     //激活本地連接和接受使能選項
 35     opt.c_cflag |= CLOCAL | CREAD;
 36     //關閉流控 接收回車符
 37     //opt.c_cflag &= ~IGNCR;
 38     //opt.c_cflag |= IXOFF;
 39     //原始數據輸出
 40     opt.c_oflag &= (~ONLCR);
 41     opt.c_oflag &= (~OCRNL);
 42     //關閉echo
 43     opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
 44     //輸入關閉流控 接收回車符
 45     opt.c_iflag |= IXOFF;
 46     opt.c_iflag &= (0|IXOFF);
 47     #endif
 48     //更新配置
 49     while(1)
 50     {
 51         errno = 0;
 52         if(tcsetattr(uart_fd, TCSANOW, &opt) != 0)
 53         {
 54             if(errno == EINTR)
 55             {
 56                 continue;
 57             }
 58             #ifdef PRINTF_MSG
 59             else if(errno == EINVAL)
 60             {
 61                 printf("config para elligal\n");
 62             }
 63             else if((errno == ENOTTY )||(errno == EBADF))  
 64             {
 65                 printf("fd error\n");
 66             }
 67             else
 68             {
 69                 printf("Device %s is set Fail%d\n");
 70             }
 71             #endif
 72             return -1;    
 73         }
 74         break;
 75     }
 76     #ifdef PRINTF_MSG
 77     printf("Device %s is set to 115200bps,8bits data , 1bit stop\n",device);
 78     #endif
 79     return uart_fd;
 80 }
 81 /**
 82   * @brief  讀取串口數據
 83   * @param  buff 數據幀
 84   * @retval  數據長度
 85   *           
 86   */
 87 int recv_data_from_serial(int uart_fd,char *buf)
 88 {
 89     int ret,len=0,data_len;
 90     struct  pollfd fds[1];
 91     fds[0].fd = uart_fd;
 92     fds[0].events = POLLIN;
 93     while(1)
 94     {
 95         ret = poll(fds,1,0);
 96         #ifdef PRINTF_MSG
 97         //perror("uart poll");
 98         #endif /*PRINTF_MSG*/
 99         if(ret < 0)
100         {
101             /*調用出錯*/
102             if(errno == EINTR)
103             {
104                 /* 系統調用出錯,可重試 */
105                 continue;
106             }
107             else
108             {
109                 /* 系統調用出錯,致命錯誤 */
110                 #ifdef PRINTF_MSG
111                 printf("uart read fail exit\n");
112                 #endif /*PRINTF_MSG*/
113                 exit(-1);
114             }
115             
116         }
117         else if(ret >= 1)
118         {
119             /*有數據可讀*/
120             if(fds[0].revents & (POLLIN|POLLPRI))
121             {
122                 if(ioctl(uart_fd,FIONREAD,&data_len)<0)
123                 {
124                     /* retry */
125                     continue;    
126                 }
127                 ret = read(uart_fd,buf,data_len);
128                 if(ret <= 0)
129                 {
130                     /*讀失敗*/
131                 }
132                 else if(ret)
133                 {
134                     len += ret;
135                     buf += ret;
136                 }
137             }
138         }
139         else
140         {
141             /* no data to read */
142             break;            
143         }
144         
145     }
146     return len;
147 }
148 /**
149   * @brief  通過串口發送數據
150   * @param  uart_fd 串口描述符
151   * @param  data 數據幀
152   * @param  lenth 數據長度
153   * @retval 1 數據長度非法
154   *           0 ok
155   */
156 int send_data_from_serial(int uart_fd,char *data,int lenth)
157 {
158     int ret,len=0;
159     struct  pollfd fds;
160     fds.fd = uart_fd;
161     fds.events = POLLOUT;
162     while(1)
163     {
164         ret = poll(&fds,1,0);
165         if(ret < 0)
166         {
167             /*調用出錯*/
168             if(errno == EINTR)
169             {
170                 /* 系統調用出錯,可重試 */
171                 continue;
172             }
173             else
174             {
175                 /* 系統調用出錯,致命錯誤 */
176                 #ifdef PRINTF_MSG
177                 printf("uart write fail exit\n");
178                 #endif /*PRINTF_MSG*/
179                 exit(-1);
180             }    
181         }
182         else if(ret == 1)
183         {
184             /*可寫*/
185             if(fds.revents & POLLOUT)
186             {
187                 ret = write(uart_fd,data+len,lenth);
188                 if(ret < 0)
189                 {
190                     /*寫失敗*/
191                 }
192                 else if(ret > 0)
193                 {
194                     len+=ret;
195                     /*寫完了*/
196                     if(len == lenth)
197                     {
198 
199                         break;
200                     }
201                 }
202             }
203         }
204         else
205         {
206             /* 退出 */
207             break;            
208         }
209     }
210     return len;
211 }
212 
213 int deinit_serial(int uart_fd)
214 {
215     close(uart_fd);
216     return 0;
217 }
View Code

參考:https://blog.csdn.net/flfihpv259/article/details/53786604


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM