前言:
在Linux系統中,串口設備以文件的形式存在,通常位于/dev目錄下,如ttyS0、ttyUSB0等。這些設備文件可以用于讀取和寫入數據。要使用串口設備,需要打開相應的設備文件。在打開串口時,可以使用O_RDWR選項標志進行讀寫操作,同時使用O_NOCTTY選項標志告訴Linux“本程序不作為串口的‘控制終端’”,以避免一些輸入字符影響進程運行。下面讓我們對串口應用編程進行一個簡單的入門學習吧。
目錄
?一、串口的作用
二、UART硬件介紹
1.使用串口
2. mini2440:
3. JZ2440:?
三、TTY體系中設備節點的差別
1.什么是TTY?
2.各類設備節點的差別
?四、TTY驅動程序的框架
五、回環?
1.串口API
2.串口收發實驗
六、GPS 模塊
1.GPS 模塊硬件
2.GPS 模塊數據格式
?一、串口的作用
UART:通用異步收發傳輸器(Universal Asynchronous Receiver/Transmitter),簡稱串口。
????????調試:移植u-boot、內核、應用程序時,主要使用串口查看打印信息
????????外接各種模塊
串口因為結構簡單、穩定可靠,廣受歡迎。
通過三根線即可,發送、接收、地線。
?
二、UART硬件介紹
1.使用串口
(1)波特率
(2)格式:數據位,停止位,校驗位,流量控制
怎么樣發送1Byte,比如‘A’????????
????????'A' = 0x41 = 0100 0001
(1) 雙方約定波特率: 每一位占據的時間,假設為1秒
(2) 邏輯電平
?如果TTL想轉化為RS-232的話需要電平轉化芯片
2. mini2440:
3. JZ2440:?
波特率為115200格式為8n1時每秒可以傳輸的字節為:11520 byte
三、TTY體系中設備節點的差別
/dev/ttySo、/dev/ttySACO、/dev/tty.ldev/tty0、/dev/tty1、/devlconsole,
它們有什么差別?
TTYTerminal/ConsolE/UART,
它們有什么差別?
1.什么是TTY?
????????teletype,更準確地說是teleprinter,是一種通信設備,可以用來發送、接收文本信息。
????????teletype是一家公司的名字,它生產的teleprinter實在太有名,結果公司名變成了這類產品的名字:teleprinter都被稱為teletype了。
teletype被用來傳輸商業電報,想象一下:
????????把兩臺teletype的線纜接在一起,或者使用無線技術連接兩臺telety這邊打字,另一邊就可以接收到信息并誦過紙張打印出來。
將?teletype 的另一端直接與電腦連接起來,這樣TTY就與計算機關聯起來了。
????????以前的計算機非常的昂貴,可能會有很多地方是共用一臺電腦,通過終端進行操作計算機,一個系統存在多個終端。
Terminal和Console的差別
????????Terminal含有遠端的意思,中文為:終端。Console翻譯為控制臺,可以理解為權限更大、能查看更多信息。比如我們可以在Console上看到內核的打印信息,從這個角度上看:????????Console是某一個Terminal
????????Terminal并不都是Console。
????????我們可以從多個Terminal中選擇某一個作為Console
????????很多時候,兩個概念混用,并無明確的、官方的定義
????????隨著時代的發展又出現了新的設備。
????????隨著時代的發展個人電腦和虛擬終端也進入了人們的生活中
在Ubuntu上演示:
按住鍵盤:Ctrl+Alt+F3啟動一個虛擬終端,Ctrl+Alt+F4再啟動一個虛擬終端。在里面切換為root用戶:
sudo passwd root //如果su root不成功,就先設置root密碼su root
2.各類設備節點的差別
?/devlconsole
比如: coisole=ttyS0 console=tty
不想去分辨這個設備是串口還是虛擬端,有沒有辦法得到這個設備?
有!通過/devlconsole!
console=ttyS0時: /devlconsole就是ttyso
console=tty時:/devlconsole就是前臺程序的虛擬終端console=tty0時: /devlconsole就是前臺程序的虛擬終端
console=ttyN時:/devlconsole就是/dev/ttyN
console有多個取值時,使用最后一個取值來判斷
?四、TTY驅動程序的框架
????????大多數用戶都會在輸入時犯錯,所以退格鍵會很有用。這當然可以由應用程序本身來實現,但是根據UNIX設計"哲學",應用程序應盡可能保持簡單。為了方便起見,操作系統提供了一個編輯緩沖區和一些基本的編輯命令(退格清除單個單詞,清除行,重新打印),這些命令在行規范((line discipline)內默認啟用。高級應用程序可以通過將行規范設置為原姓模式(raw mode)而不是默認的成熟或準則模式(cooked and canonical)來禁用這些功能。
五、回環?
1.串口API
????????在Linux系統中,操作設備的統一接口就是: open/ioctl/read/write。
????????對于UART,又在ioctl之上封裝了很多函數,主要是用來設置行規程。所以對于UART,編程的套路就是:
?????????????????open
????????????????設置行規程,比如波特率、數據位、停止位、檢驗位、RAW模式、一有數據就返回? ? ? ? ? ? ? ? ? ? ? ? read/write
????????怎么設置行規程??
????????行規程的參數用結構體 termios 來表示,可以參考 Linux 串口—struct termios 結構體:
https://blog.csdn.net/yemingzhu163/article/details/5897156
這些函數在名稱上有一些慣例:
?tc:terminal contorl
?cf: control flag
????????主要是需要設置好 termios 中的參數,這些參數很復雜,可以參考 Linux 串口—struct termios 結構體。?
1 #include <stdio.h>2 #include <string.h>3 #include <sys/types.h>4 #include <errno.h>5 #include <sys/stat.h>6 #include <fcntl.h>7 #include <unistd.h>8 #include <termios.h>9 #include <stdlib.h>1011 /* set_opt(fd,115200,8,'N',1) */12 int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)13 {14 struct termios newtio,oldtio;1516 if ( tcgetattr( fd,&oldtio) != 0) {17 perror("SetupSerial 1");18 return -1;19 }2021 bzero( &newtio, sizeof( newtio ) );22 newtio.c_cflag |= CLOCAL | CREAD;23 newtio.c_cflag &= ~CSIZE;2425 newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/26 newtio.c_oflag &= ~OPOST; /*Output*/2728 switch( nBits )29 {30 case 7:31 newtio.c_cflag |= CS7;32 break;33 case 8:34 newtio.c_cflag |= CS8;35 break;36 }3738 switch( nEvent )39 {40 case 'O':41 newtio.c_cflag |= PARENB;42 newtio.c_cflag |= PARODD;43 newtio.c_iflag |= (INPCK | ISTRIP);44 break;45 case 'E':46 newtio.c_iflag |= (INPCK | ISTRIP);47 newtio.c_cflag |= PARENB;48 newtio.c_cflag &= ~PARODD;49 break;50 case 'N':51 newtio.c_cflag &= ~PARENB;52 break;53 }5455 switch( nSpeed )56 {57 case 2400:58 cfsetispeed(&newtio, B2400);59 cfsetospeed(&newtio, B2400);60 break;61 case 4800:62 cfsetispeed(&newtio, B4800);63 cfsetospeed(&newtio, B4800);64 break;65 case 9600:66 cfsetispeed(&newtio, B9600);67 cfsetospeed(&newtio, B9600);68 break;69 case 115200:70 cfsetispeed(&newtio, B115200);71 cfsetospeed(&newtio, B115200);72 break;73 default:74 cfsetispeed(&newtio, B9600);75 cfsetospeed(&newtio, B9600);76 break;77 }7879 if( nStop == 1 )80 newtio.c_cflag &= ~CSTOPB;81 else if ( nStop == 2 )82 newtio.c_cflag |= CSTOPB;8384 newtio.c_cc[VMIN] = 1; /* 讀數據時的最小字節數: 沒讀到這些數據我就不返回! */85 newtio.c_cc[VTIME] = 0; /* 等待第1個數據的時間:86 * 比如VMIN設為10表示至少讀到10個數據才返回,87 * 但是沒有數據總不能一直等吧? 可以設置VTIME(單位是10秒)88 * 假設VTIME=1,表示:89 * 10秒內一個數據都沒有的話就返回90 * 如果10秒內至少讀到了1個字節,那就繼續等待,完全讀到VMIN個數據再返回91 */9293 tcflush(fd,TCIFLUSH);9495 if((tcsetattr(fd,TCSANOW,&newtio))!=0)96 {97 perror("com set error");98 return -1;99 }
100 //printf("set done!\n");
101 return 0;
102 }
103
104 int open_port(char *com)
105 {
106 int fd;
107 //fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
108 fd = open(com, O_RDWR|O_NOCTTY);
109 if (-1 == fd){
110 return(-1);
111 }
112
113 if(fcntl(fd, F_SETFL, 0)<0) /* 設置串口為阻塞狀態*/
114 {
115 printf("fcntl failed!\n");
116 return -1;
117 }
118
119 return fd;
120 }
121
122
123 /*
124 * ./serial_send_recv <dev>
125 */
126 int main(int argc, char **argv)
127 {
128 int fd;
129 int iRet;
130 char c;
131
132 /* 1. open */
133
134 /* 2. setup
135 * 115200,8N1
136 * RAW mode
137 * return data immediately
138 */
139
140 /* 3. write and read */
141
142 if (argc != 2)
143 {
144 printf("Usage: \n");
145 printf("%s </dev/ttySAC1 or other>\n", argv[0]);
146 return -1;
147 }
148
149 fd = open_port(argv[1]);
150 if (fd < 0)
151 {
152 printf("open %s err!\n", argv[1]);
153 return -1;
154 }
155
156 iRet = set_opt(fd, 115200, 8, 'N', 1);
157 if (iRet)
158 {
159 printf("set port err!\n");
160 return -1;
161 }
162
163 printf("Enter a char: ");
164 while (1)
165 {
166 scanf("%c", &c);
167 iRet = write(fd, &c, 1);
168 iRet = read(fd, &c, 1);
169 if (iRet == 1)
170 printf("get: %02x %c\n", c, c);
171 else
172 printf("can not get data\n");
173 }
174
175 return 0;
176 }
第152行: 打開設備節點
第156行:設置波特率和格式
164 while (1) 165 { 166 scanf("%c", &c); 167 iRet = write(fd, &c, 1); 168 iRet = read(fd, &c, 1); 169 if (iRet == 1) 170 printf("get: %02x %c\n", c, c); 171 else 172 printf("can not get data\n"); 173 }
第164~173行: 進入循環后等待用戶輸入數據,得到數據后發給指定的串口,再讀取指定串口上的數據,讀到后在命令行中打印出來??
104 int open_port(char *com)
?第104~120行:打開設備節點
?第113行:
? ? ? ? ? ? ? ?1.fcntl (fd,FSETFL,FNDELAY);讀數據時不等待,沒有數據就返回0
???????????????2.fcntl (fd, F_SETFL,0);讀數據時,沒有數據阻塞
12 int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
第12~102行:?設置波特率和格式
?
2.串口收發實驗
????????通過把串口的發送、接收引腳短接,實現自發自收:使用 write 函數 發出字符,使用 read 函數讀取字符。
1. Ubuntu 上
arm-buildroot-linux-gnueabihf-gcc -o serial_send_recv serial_send_recv.c
2. 板子上
/mnt/serial_send_recv /dev/ttymxc5
六、GPS 模塊
????????全球定位系統(Global Positioning System,GPS)是一種以空中衛星為 基礎的高精度無線電導航的定位系統,它在全球任何地方以及近地空間都能夠提 供準確的地理位置、車行速度及精確的時間信息。GPS 主要由三大組成部分:空 間部分、地面監控部分和用戶設備部分。GPS 系統具有高精度、全天候、用廣泛 等特點。
1.GPS 模塊硬件
????????GPS 模塊與外部控制器的通訊接口有多種方式,這里我們使用串口進行通訊, 波特率為 9600bps,1bit 停止位,無校驗位,無流控,默認每秒輸出一次標準格式數據。
2.GPS 模塊數據格式
????????GPS 使用多種標準數據格式,目前最通用的 GNSS 格式是 NMEA0183 格式。 NMEA0183 是最終定位格式,即將二進制定位格式轉為統一標準定位格式,與衛星類型無關。這是一套定義接收機輸出的標準信息,有幾種不同的格式,每種都是獨立相關的 ASCII 格式,逗點隔開數據流,數據流長度從 30-100 字符不等, 通常以每秒間隔持續輸出。
????????我們使用串口接收數據,收到的數據包含:$GPGGA(GPS 定位數據)、$GPGLL (地理定位信息)、$GPGSA(當前衛星信息)、$GPGSV(可見衛星狀態信息)、 $GPRMC(推薦最小定位信息)、$GPVTG(地面速度信息)。
???????? 這里我們只分析$GPGGA (Global Positioning System Fix Data)即可, 它包含了 GPS 定位經緯度、質量因子、HDOP、高程、參考站號等字段。其標準格式如下:
$XXGGA 語句各字段的含義和取值范圍各字段的含義和取值范圍見下表所示, XX 取值有: ? ????????GPGGA:單 GPS
????????BDGGA:單北斗
????????GLGGA:單 GLONASS
????????GNGGA:多星聯合定位
例子:$GPGGA,074529.82,2429.6717,N,11804.6973,E,1,8,1.098, 42.110,,,M,,*76。
1 #include <stdio.h>2 #include <string.h>3 #include <sys/types.h>4 #include <errno.h>5 #include <sys/stat.h>6 #include <fcntl.h>7 #include <unistd.h>8 #include <termios.h>9 #include <stdlib.h>1011 /* set_opt(fd,115200,8,'N',1) */12 int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)13 {14 struct termios newtio,oldtio;1516 if ( tcgetattr( fd,&oldtio) != 0) {17 perror("SetupSerial 1");18 return -1;19 }2021 bzero( &newtio, sizeof( newtio ) );22 newtio.c_cflag |= CLOCAL | CREAD;23 newtio.c_cflag &= ~CSIZE;2425 newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/26 newtio.c_oflag &= ~OPOST; /*Output*/2728 switch( nBits )29 {30 case 7:31 newtio.c_cflag |= CS7;32 break;33 case 8:34 newtio.c_cflag |= CS8;35 break;36 }3738 switch( nEvent )39 {40 case 'O':41 newtio.c_cflag |= PARENB;42 newtio.c_cflag |= PARODD;43 newtio.c_iflag |= (INPCK | ISTRIP);44 break;45 case 'E':46 newtio.c_iflag |= (INPCK | ISTRIP);47 newtio.c_cflag |= PARENB;48 newtio.c_cflag &= ~PARODD;49 break;50 case 'N':51 newtio.c_cflag &= ~PARENB;52 break;53 }5455 switch( nSpeed )56 {57 case 2400:58 cfsetispeed(&newtio, B2400);59 cfsetospeed(&newtio, B2400);60 break;61 case 4800:62 cfsetispeed(&newtio, B4800);63 cfsetospeed(&newtio, B4800);64 break;65 case 9600:66 cfsetispeed(&newtio, B9600);67 cfsetospeed(&newtio, B9600);68 break;69 case 115200:70 cfsetispeed(&newtio, B115200);71 cfsetospeed(&newtio, B115200);72 break;73 default:74 cfsetispeed(&newtio, B9600);75 cfsetospeed(&newtio, B9600);76 break;77 }7879 if( nStop == 1 )80 newtio.c_cflag &= ~CSTOPB;81 else if ( nStop == 2 )82 newtio.c_cflag |= CSTOPB;8384 newtio.c_cc[VMIN] = 1; /* 讀數據時的最小字節數: 沒讀到這些數據我就不返回! */85 newtio.c_cc[VTIME] = 0; /* 等待第1個數據的時間:86 * 比如VMIN設為10表示至少讀到10個數據才返回,87 * 但是沒有數據總不能一直等吧? 可以設置VTIME(單位是10秒)88 * 假設VTIME=1,表示:89 * 10秒內一個數據都沒有的話就返回90 * 如果10秒內至少讀到了1個字節,那就繼續等待,完全讀到VMIN個數據再返回91 */9293 tcflush(fd,TCIFLUSH);9495 if((tcsetattr(fd,TCSANOW,&newtio))!=0)96 {97 perror("com set error");98 return -1;99 }
100 //printf("set done!\n");
101 return 0;
102 }
103
104 int open_port(char *com)
105 {
106 int fd;
107 //fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
108 fd = open(com, O_RDWR|O_NOCTTY);
109 if (-1 == fd){
110 return(-1);
111 }
112
113 if(fcntl(fd, F_SETFL, 0)<0) /* 設置串口為阻塞狀態*/
114 {
115 printf("fcntl failed!\n");
116 return -1;
117 }
118
119 return fd;
120 }
121
122
123 int read_gps_raw_data(int fd, char *buf)
124 {
125 int i = 0;
126 int iRet;
127 char c;
128 int start = 0;
129
130 while (1)
131 {
132 iRet = read(fd, &c, 1);
133 if (iRet == 1)
134 {
135 if (c == '$')
136 start = 1;
137 if (start)
138 {
139 buf[i++] = c;
140 }
141 if (c == '\n' || c == '\r')
142 return 0;
143 }
144 else
145 {
146 return -1;
147 }
148 }
149 }
150
151 /* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF> */
152 int parse_gps_raw_data(char *buf, char *time, char *lat, char *ns, char *lng, char *ew)
153 {
154 char tmp[10];
155
156 if (buf[0] != '$')
157 return -1;
158 else if (strncmp(buf+3, "GGA", 3) != 0)
159 return -1;
160 else if (strstr(buf, ",,,,,"))
161 {
162 printf("Place the GPS to open area\n");
163 return -1;
164 }
165 else {
166 //printf("raw data: %s\n", buf);
167 sscanf(buf, "%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]", tmp, time, lat, ns, lng, ew);
168 return 0;
169 }
170 }
171
172
173 /*
174 * ./serial_send_recv <dev>
175 */
176 int main(int argc, char **argv)
177 {
178 int fd;
179 int iRet;
180 char c;
181 char buf[1000];
182 char time[100];
183 char Lat[100];
184 char ns[100];
185 char Lng[100];
186 char ew[100];
187
188 float fLat, fLng;
189
190 /* 1. open */
191
192 /* 2. setup
193 * 115200,8N1
194 * RAW mode
195 * return data immediately
196 */
197
198 /* 3. write and read */
199
200 if (argc != 2)
201 {
202 printf("Usage: \n");
203 printf("%s </dev/ttySAC1 or other>\n", argv[0]);
204 return -1;
205 }
206
207 fd = open_port(argv[1]);
208 if (fd < 0)
209 {
210 printf("open %s err!\n", argv[1]);
211 return -1;
212 }
213
214 iRet = set_opt(fd, 9600, 8, 'N', 1);
215 if (iRet)
216 {
217 printf("set port err!\n");
218 return -1;
219 }
220
221 while (1)
222 {
223 /* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF>*/
224 /* read line */
225 iRet = read_gps_raw_data(fd, buf);
226
227 /* parse line */
228 if (iRet == 0)
229 {
230 iRet = parse_gps_raw_data(buf, time, Lat, ns, Lng, ew);
231 }
232
233 /* printf */
234 if (iRet == 0)
235 {
236 printf("Time : %s\n", time);
237 printf("ns : %s\n", ns);
238 printf("ew : %s\n", ew);
239 printf("Lat : %s\n", Lat);
240 printf("Lng : %s\n", Lng);
241
242 /* 緯度格式: ddmm.mmmm */
243 sscanf(Lat+2, "%f", &fLat);
244 fLat = fLat / 60;
245 fLat += (Lat[0] - '0')*10 + (Lat[1] - '0');
246
247 /* 經度格式: dddmm.mmmm */
248 sscanf(Lng+3, "%f", &fLng);
249 fLng = fLng / 60;
250 fLng += (Lng[0] - '0')*100 + (Lng[1] - '0')*10 + (Lng[2] - '0');
251 printf("Lng,Lat: %.06f,%.06f\n", fLng, fLat);
252 }
253 }
254
255 return 0;
256 }
257
123 int read_gps_raw_data(int fd, char *buf)
?第123行對應到255行:GPS原始數據,讀取一行數據
152 int parse_gps_raw_data(char *buf, char *time, char *lat, char *ns, char *lng, char *ew)
?第151~170行:進行數據解析
?
167 sscanf(buf, "%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]", tmp, time, lat, ns, lng, ew);
?注:%[^] 剔除不包含符合
?第181~186 :定義這五個數據?
?第228行:iRet == 0表明讀到數據
?第234~252行:讀到數據并進行數據解析,如果解析成功則將這些信息打印出來
第181~186行 :定義這五個數據。
第242~251行:將經緯度轉變并且打印出來。
1. Ubuntu 上
arm-buildroot-linux-gnueabihf-gcc -o gps_read gps_read.c
2. 板子上
/mnt/gps_read /dev/ttymxc5
?
?