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