一、串口的作用
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> 10 11 /* 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; 15 16 if ( tcgetattr( fd,&oldtio) != 0) { 17 perror("SetupSerial 1"); 18 return -1; 19 } 20 21 bzero( &newtio, sizeof( newtio ) ); 22 newtio.c_cflag |= CLOCAL | CREAD; 23 newtio.c_cflag &= ~CSIZE; 24 25 newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/ 26 newtio.c_oflag &= ~OPOST; /*Output*/ 27 28 switch( nBits ) 29 { 30 case 7: 31 newtio.c_cflag |= CS7; 32 break; 33 case 8: 34 newtio.c_cflag |= CS8; 35 break; 36 } 37 38 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 } 54 55 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 } 78 79 if( nStop == 1 ) 80 newtio.c_cflag &= ~CSTOPB; 81 else if ( nStop == 2 ) 82 newtio.c_cflag |= CSTOPB; 83 84 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 */ 92 93 tcflush(fd,TCIFLUSH); 94 95 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定位数据)、GPGGA(GPS 定位数据)、GPGLL (地理定位信息)、(当前卫星信息)、GPGSA(当前卫星信息)、GPGSA(当前卫星信息)、GPGSV(可见卫星状态信息)、 (推荐最小定位信息)、GPRMC(推荐最小定位信息)、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> 10 11 /* 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; 15 16 if ( tcgetattr( fd,&oldtio) != 0) { 17 perror("SetupSerial 1"); 18 return -1; 19 } 20 21 bzero( &newtio, sizeof( newtio ) ); 22 newtio.c_cflag |= CLOCAL | CREAD; 23 newtio.c_cflag &= ~CSIZE; 24 25 newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/ 26 newtio.c_oflag &= ~OPOST; /*Output*/ 27 28 switch( nBits ) 29 { 30 case 7: 31 newtio.c_cflag |= CS7; 32 break; 33 case 8: 34 newtio.c_cflag |= CS8; 35 break; 36 } 37 38 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 } 54 55 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 } 78 79 if( nStop == 1 ) 80 newtio.c_cflag &= ~CSTOPB; 81 else if ( nStop == 2 ) 82 newtio.c_cflag |= CSTOPB; 83 84 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 */ 92 93 tcflush(fd,TCIFLUSH); 94 95 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