嵌入式Linux系列第7篇:使用串口(嵌入式linux 串口)
1.引言
串口是我們實(shí)際工作中經(jīng)常使用的一個接口,比如我們在Linux下使用的debug串口,它用來登錄Linux系統(tǒng),輸出log。另外我們也會使用串口和外部的一些模塊通信,比如GPS模塊、RS485等。這里對Linux下串口使用做個總結(jié),希望對大家有所幫助。
2.環(huán)境介紹
2.1.硬件
1) NUC972開發(fā)板:
這次要控制的是板子底板上DB9串口:
對應(yīng)NUC972的PE3和PE2引腳。
2) 2根USB轉(zhuǎn)RS232線,一個用來連接板子的debug串口UART0,另外一個用來連接板子上的串口UART1.
2.2.軟件
1) 我們在上一篇《Linux學(xué)習(xí)系列六:操作GPIO》的基礎(chǔ)上改動下Linux內(nèi)核配置,生成新的970uimage并燒寫到板子里。
2) uboot、rootfs使用板子里默認(rèn)的,為了增加micorcom命令,需要使用busybox生成,然后通過U盤導(dǎo)入到板子里。Busybox具體使用參考《Linux學(xué)習(xí)系列五:Nand Flash根文件系統(tǒng)制作》
3)交叉工具鏈arm_linux_4.8.tar.gz
3.Busybox生成microcom命令
microcom命令類似于windows下的串口調(diào)試助手,在調(diào)試串口時(shí)非常有用,默認(rèn)情況下板子里不支持這個命令,需要用busybox去生成。
1)busybox的使用如果大家有遺忘,可以參考《Linux 學(xué)習(xí)系列五:Nand Flash 根文件系統(tǒng)制作》中詳細(xì)介紹,首先我們把原來的~/nuc972/rootfs目錄里的內(nèi)容給刪掉
2)進(jìn)入到busybox目錄,make menuconfig,輸入/, 搜索microcom,找到配置它的位置
然后進(jìn)入到對應(yīng)的位置,把microcom選中。
3)編譯make,安裝make install,然后壓縮一下生成rootfs.tar
4) 通過U盤導(dǎo)入到板子里,放到根目錄下解壓,這樣板子就支持microcom命令了。
4.內(nèi)核配置
1)為了使用UART1,需要在內(nèi)核里做如下配置:
Device Drivers —>
character devices —>
Serial drivers
[*] NUC970/N9H30 UART1 support
保存生成新的.config 文件。
2)make uImage,生成新的970uimage文件,將其單獨(dú)下載到板子里即可。
5.UART操作
5.1.命令行操作
我們將板子上的兩個串口同時(shí)和PC機(jī)連接,通過debug串口登錄Linux系統(tǒng)操作UART1,PC端打開串口調(diào)試助手,選擇UART1對應(yīng)的串口,這樣板子通過UART1就可以和PC之間進(jìn)行數(shù)據(jù)的收發(fā)了。
登錄板子后,輸入下面指令:
microcom -s 115200 /dev/ttyS1
/dev下的ttyS1對應(yīng)的就是UART1設(shè)備。
microcom 命令后的-s 115200,表示設(shè)置波特率為115200bps。
如果你想了解microcom的詳細(xì)實(shí)現(xiàn)機(jī)制,可以到busybox的目錄miscutils查看microcom.c源代碼即可。
輸入上述命令后,當(dāng)此串口收到數(shù)據(jù)后,就會自動在窗口中顯示出來,如果鍵盤輸入字符,就會自動通過此串口發(fā)送出去。我們可以雙向收發(fā)測試。
注意:
1) micrcom指令退出的方式是Ctrl x,不是Ctrl c,如果輸入Ctrl c,它其實(shí)是發(fā)送了0x03字符。
2) 有些工程師喜歡用cat 指令去查看串口就沒有收到數(shù),其實(shí)這是不對的,我們做下面這個測試,為了方便起見,我們讓PC端1s一次定時(shí)發(fā)送
使用micrcom的話,
microcom -s 115200 /dev/ttyS1
會看到在不斷的接收數(shù)據(jù)
我們Ctrl x先關(guān)掉microcom,直接輸入
cat /dev/ttyS1
會有什么結(jié)果呢?
什么都沒有收到。
所以千萬不要直接用cat去判斷串口是否有數(shù)據(jù)接收,為什么有時(shí)能收到呢,那是因?yàn)榇谠O(shè)備在某個地方被打開(調(diào)用了open函數(shù))了。
比如你讓microcom指令在后臺執(zhí)行
microcom -s 115200 /dev/ttyS1 &
這時(shí)再使用cat指令就可以顯示數(shù)據(jù)了。
5.2.C語言串口編程
我們看下在C代碼里如何操作串口,下面是一個例子:
//————————————————–// Copyright (c) Topsemic//————————————————–#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);}
將它編譯后放到板子里,注意上述代碼沒有設(shè)置串口波特率,默認(rèn)值是9600,需要在串口調(diào)試助手中正確配置,運(yùn)行一下我們先看看效果:
交叉驗(yàn)證下,我們把UART1的波特率設(shè)置為115200后,結(jié)果如下,可以看到是無法正確接收到數(shù)據(jù)的。
上述程序工作過程是串口先發(fā)送一串?dāng)?shù)據(jù),然后一直停在read函數(shù)處不動,直到接收到數(shù)據(jù)后返回退出。此時(shí)串口工作在阻塞模式下。所謂阻塞和非阻塞的含義如下:
阻塞:
對于read,指當(dāng)串口輸入緩存區(qū)沒有數(shù)據(jù)的時(shí)候,read函數(shù)將會阻塞在這里,直到串口輸入緩存區(qū)中有數(shù)據(jù)可讀取,read讀到了需要的字節(jié)數(shù)之后,返回值為讀到的字節(jié)數(shù);
對于write,指當(dāng)串口輸出緩沖區(qū)滿,或剩下的空間小于將要寫入的字節(jié)數(shù),則write將阻塞,一直到串口輸出緩沖區(qū)中剩下的空間大于等于將要寫入的字節(jié)數(shù),執(zhí)行寫入操作,返回寫入的字節(jié)數(shù)。
非阻塞:
對于read,指當(dāng)串口輸入緩沖區(qū)沒有數(shù)據(jù)的時(shí)候,read函數(shù)立即返回,返回值為-1。
對于write,指當(dāng)串口輸出緩沖區(qū)滿,或剩下的空間小于將要寫入的字節(jié)數(shù),則write將進(jìn)行寫操作,寫入當(dāng)前串口輸出緩沖區(qū)剩下空間允許的字節(jié)數(shù),然后返回寫入的字節(jié)數(shù)。
在打開串口文件時(shí),打開模式加上O_NDELAY可以以非阻塞方式打開串口;反之,不加上O_NDEAY,默認(rèn)以阻塞方式打開串口。上述第一例子中沒有加O_NDEAY標(biāo)志,所以工作在阻塞模式下,下面再看個例子,我們加上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); }}
這時(shí)程序運(yùn)行結(jié)果如下,在串口接收不到數(shù)據(jù)時(shí),read函數(shù)立即返回,返回值是-1,當(dāng)接收到數(shù)據(jù)后,返回值是接收到數(shù)據(jù)值長度。
大家可能注意到,上述代碼沒有關(guān)于串口的參數(shù)配置,比如波特率、校驗(yàn)位、數(shù)據(jù)位、停止位的設(shè)置,實(shí)際應(yīng)用中很可能是要修改這些參數(shù)的,最常見的就是修改波特率,下面例子在上面的基礎(chǔ)上修改如下:
#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;}
這時(shí)我們把波特率修改為115200了,大家可以驗(yàn)證下,只有把uart1對應(yīng)串口波特率設(shè)置為115200時(shí)才可以正確收發(fā)。
6.結(jié)束語
本篇為大家介紹了Linux下UART的使用,如果實(shí)現(xiàn)收發(fā)數(shù)據(jù),如何配置波特率等參數(shù),以及如何使用microcom 命令調(diào)試等。