嵌入式Linux操作UART实例
1
引言
串口是我們實際工作中經常使用的一個接口,比如我們在Linux下使用的debug串口,它用來登錄Linux系統,輸出log。另外我們也會使用串口和外部的一些模塊通信,比如GPS模塊、RS485等。這里對Linux下串口使用做個總結,希望對大家有所幫助。
2
環境介紹
2.1.硬件
1) 網上的一個第三方做的NUC972開發板:
有興趣購買的朋友,可以去他們的淘寶店購買:
https://s.click.taobao.com/X8mza8w
這次要控制的是板子底板上DB9串口:
對應NUC972的PE3和PE2引腳。
2) 2根USB轉RS232線,一個用來連接板子的debug串口UART0,另外一個用來連接板子上的串口UART1.
2.2.軟件
1) 我們在上一篇《Linux學習系列六:操作GPIO》的基礎上改動下Linux內核配置,生成新的970uimage并燒寫到板子里。
2) uboot、rootfs使用板子里默認的,為了增加micorcom命令,需要使用busybox生成,然后通過U盤導入到板子里。Busybox具體使用參考《Linux學習系列五:Nand Flash根文件系統制作》
3)交叉工具鏈arm_linux_4.8.tar.gz
3
Busybox生成microcom命令
microcom命令類似于windows下的串口調試助手,在調試串口時非常有用,默認情況下板子里不支持這個命令,需要用busybox去生成。
1)busybox的使用如果大家有遺忘,可以參考《Linux 學習系列五:Nand Flash 根文件系統制作》中詳細介紹,首先我們把原來的~/nuc972/rootfs目錄里的內容給刪掉
2)進入到busybox目錄,make menuconfig,輸入/, 搜索microcom,找到配置它的位置
然后進入到對應的位置,把microcom選中。
3) 編譯make,安裝make install,然后壓縮一下生成rootfs.tar
4) 通過U盤導入到板子里,放到根目錄下解壓,這樣板子就支持microcom命令了。
4
內核配置
1)為了使用UART1,需要在內核里做如下配置:
Device Drivers --->
Character devices --->
?Serial drivers
[*] NUC970/N9H30 UART1 support
保存生成新的.config 文件。
2)make uImage,生成新的970uimage文件,將其單獨下載到板子里即可。
5
UART操作
5.1.命令行操作
我們將板子上的兩個串口同時和PC機連接,通過debug串口登錄Linux系統操作UART1,PC端打開串口調試助手,選擇UART1對應的串口,這樣板子通過UART1就可以和PC之間進行數據的收發了。
登錄板子后,輸入下面指令:
microcom -s 115200 /dev/ttyS1
? /dev下的ttyS1對應的就是UART1設備。
? microcom 命令后的-s 115200,表示設置波特率為115200bps。
? 如果你想了解microcom的詳細實現機制,可以到busybox的目錄miscutils查看microcom.c源代碼即可。
? 輸入上述命令后,當此串口收到數據后,就會自動在窗口中顯示出來,如果鍵盤輸入字符,就會自動通過此串口發送出去。我們可以雙向收發測試。
注意:
1) micrcom指令退出的方式是Ctrl+x,不是Ctrl+c,如果輸入Ctrl+c,它其實是發送了0x03字符。
2) 有些工程師喜歡用cat 指令去查看串口就沒有收到數,其實這是不對的,我們做下面這個測試,為了方便起見,我們讓PC端1s一次定時發送
? 使用micrcom的話,
microcom -s 115200 /dev/ttyS1
會看到在不斷的接收數據
我們Ctrl+x先關掉microcom,直接輸入
cat /dev/ttyS1
會有什么結果呢?
什么都沒有收到。
所以千萬不要直接用cat去判斷串口是否有數據接收,為什么有時能收到呢,那是因為串口設備在某個地方被打開(調用了open函數)了。
比如你讓microcom指令在后臺執行
microcom -s 115200 /dev/ttyS1 &
這時再使用cat指令就可以顯示數據了。
5.2.C語言串口編程
我們看下在C代碼里如何操作串口,下面是一個例子:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <asm/termios.h> #include <memory.h>#define DEV_NAME "/dev/ttyS1" int main (int argc, char *argv[]) {int fd;int len, i,ret;char?buf[]?=?"Hello?TopSemic!?\n";fd = open(DEV_NAME, O_RDWR | O_NOCTTY);if(fd?<?0){perror(DEV_NAME);return -1;}len = write(fd, buf, sizeof(buf));if (len < 0) {printf("write data error \n");}memset(buf,0x00,sizeof(buf));len = read(fd, buf, sizeof(buf));if?(len?<?0)?{printf("read error \n");return -1;}printf("%s",?buf);?return(0); }將它編譯后放到板子里,注意上述代碼沒有設置串口波特率,默認值是9600,需要在串口調試助手中正確配置,運行一下我們先看看效果:
交叉驗證下,我們把UART1的波特率設置為115200后,結果如下,可以看到是無法正確接收到數據的。
上述程序工作過程是串口先發送一串數據,然后一直停在read函數處不動,直到接收到數據后返回退出。此時串口工作在阻塞模式下。所謂阻塞和非阻塞的含義如下:
阻塞:
對于read,指當串口輸入緩存區沒有數據的時候,read函數將會阻塞在這里,直到串口輸入緩存區中有數據可讀取,read讀到了需要的字節數之后,返回值為讀到的字節數;
對于write,指當串口輸出緩沖區滿,或剩下的空間小于將要寫入的字節數,則write將阻塞,一直到串口輸出緩沖區中剩下的空間大于等于將要寫入的字節數,執行寫入操作,返回寫入的字節數。
非阻塞:
對于read,指當串口輸入緩沖區沒有數據的時候,read函數立即返回,返回值為-1。
對于write,指當串口輸出緩沖區滿,或剩下的空間小于將要寫入的字節數,則write將進行寫操作,寫入當前串口輸出緩沖區剩下空間允許的字節數,然后返回寫入的字節數。
在打開串口文件時,打開模式加上O_NDELAY可以以非阻塞方式打開串口;反之,不加上O_NDEAY,默認以阻塞方式打開串口。上述第一例子中沒有加O_NDEAY標志,所以工作在阻塞模式下,下面再看個例子,我們加上O_NDEAY
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <asm/termios.h> #include <memory.h>#define DEV_NAME "/dev/ttyS1"int main (int argc, char *argv[]) {int fd;int len, i,ret;char buf[] = "Hello TopSemic! \n";fd = open(DEV_NAME, O_RDWR | O_NOCTTY|O_NDELAY);if(fd < 0){perror(DEV_NAME);return -1;}len = write(fd, buf, sizeof(buf));if (len < 0){printf("write data error \n");}while(1){memset(buf,0x00,sizeof(buf));len = read(fd, buf, sizeof(buf));printf("len:%d \n",len);if(len>0)printf("%s", buf);usleep(100000);} }這時程序運行結果如下,在串口接收不到數據時,read函數立即返回,返回值是-1,當接收到數據后,返回值是接收到數據值長度。
大家可能注意到,上述代碼沒有關于串口的參數配置,比如波特率、校驗位、數據位、停止位的設置,實際應用中很可能是要修改這些參數的,最常見的就是修改波特率,下面例子在上面的基礎上修改如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <asm/termios.h> #include <memory.h> #include <signal.h>#define DEV_NAME "/dev/ttyS1" static struct termios newtios,oldtios; /*termianal settings */static int saved_portfd=-1; /*serial port fd */static void reset_tty_atexit(void) {if(saved_portfd != -1){tcsetattr(saved_portfd,TCSANOW,&oldtios);} }/*cheanup signal handler */ static void reset_tty_handler(int signal) {if(saved_portfd != -1){tcsetattr(saved_portfd,TCSANOW,&oldtios);}_exit(EXIT_FAILURE); }static set_port_attr (int portfd,int baudrate) {struct sigaction sa;/*get serial port parnms,save away */tcgetattr(portfd,&newtios);memcpy(&oldtios,&newtios,sizeof newtios);/* configure new values */cfmakeraw(&newtios); /*see man page */newtios.c_iflag |=IGNPAR; /*ignore parity on input */newtios.c_oflag &= ~(OPOST | ONLCR | OLCUC | OCRNL | ONOCR | ONLRET | OFILL); newtios.c_cc[VMIN]=1; /* block until 1 char received */newtios.c_cc[VTIME]=0; /*no inter-character timer */switch(baudrate) {case 9600:cfsetispeed(&newtios,B9600);cfsetospeed(&newtios,B9600);break;case 19200:cfsetispeed(&newtios,B19200);cfsetospeed(&newtios,B19200);break;case 38400:cfsetispeed(&newtios,B38400);cfsetospeed(&newtios,B38400);break;case 115200:cfsetispeed(&newtios,B115200);cfsetospeed(&newtios,B115200);break;}/* register cleanup stuff */atexit(reset_tty_atexit);memset(&sa,0,sizeof sa);sa.sa_handler = reset_tty_handler;sigaction(SIGHUP,&sa,NULL);sigaction(SIGINT,&sa,NULL);sigaction(SIGPIPE,&sa,NULL);sigaction(SIGTERM,&sa,NULL);/*apply modified termios */saved_portfd=portfd;tcflush(portfd,TCIFLUSH);tcsetattr(portfd,TCSADRAIN,&newtios);return portfd;}int main (int argc, char *argv[]) {int fd;int len, i,ret;char buf[] = "Hello TopSemic! \n";fd = open(DEV_NAME, O_RDWR | O_NOCTTY|O_NDELAY);if(fd < 0){perror(DEV_NAME);return -1;}set_port_attr (fd,115200);len = write(fd, buf, sizeof(buf));if (len < 0){printf("write data error \n");}while(1){ memset(buf,0x00,sizeof(buf));len = read(fd, buf, sizeof(buf));printf("len:%d \n",len);if(len>0)printf("%s", buf);usleep(100000);}return 0; }這時我們把波特率修改為115200了,大家可以驗證下,只有把uart1對應串口波特率設置為115200時才可以正確收發。
6
結束語
本期相關的資料在鏈接:?https://github.com/TopSemic/NUC972_Linux??07 Lesson7 操作UART 中。
推薦閱讀:
專輯|Linux文章匯總
專輯|程序人生
專輯|C語言
我的知識小密圈
總結
以上是生活随笔為你收集整理的嵌入式Linux操作UART实例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS中常用的手势
- 下一篇: oracle与mysql语法区别_mys