文章目录

  • 一. 串口应用程序编程介绍
    • 1.串口的作用
    • 2. 本套视频特别说明
  • 二. 硬件知识_UART硬件介绍
    • 1. 串口的硬件介绍
    • 2. 串口的参数
    • 3. 串口电平
    • 4. 串口内部结构
  • 三. TTY体系中设备节点的差别
    • 1. 傻傻分不清
    • 2. 要讲历史了
      • 2.1 电传机teletype
      • 2.2 计算机需要控制
        • 2.2.1 使用teletype
        • 2.2.2 teletype被淘汰了
        • 2.2.3 个人电脑和虚拟终端
    • 3. 在Ubuntu上演示
      • 3.1 各类设备节点的差别
      • 3.2 /dev/ttyN(N=1,2,3,...)
      • 3.3 /dev/tty0
      • 3.4 /dev/tty
      • 3.5 Terminal和Console的差别
      • 3.6 /dev/console
  • 四. TTY驱动程序框架
    • 1. 行规程的引入
    • 2. TTY驱动程序框架
  • 五. 在STM32MP157上做串口实验的准备工作(imux6ull不用操作,了解一下修改驱动程序)
    • 1. 出厂系统不支持扩展板上的UART
    • 2. 只关注APP开发的话
    • 3. 从头修改设备树文件
      • 3.1 确定硬件资源
      • 3.2 使能设备树节点
      • 3.3 通过Pinctrl指定引脚
      • 3.4 指定设备别名
      • 3.5 编译、使用
  • 六. Linux串口应用编程
    • 1. 串口API
    • 2. 串口收发实验
      • 2.1 接线
        • 2.1.1 IMX6ULL
      • 2.2 编程
      • 2.3 上机实验
        • 2.3.1 IMX6ULL
    • 3. GPS模块实验
      • 3.1 GPS简介
      • 3.2 GPS模块硬件
      • 3.3 GPS模块数据格式
      • 3.4 编程
      • 3.5 接线
        • 3.5.1 IMX6ULL
      • 3.6 上机实验
        • 3.6.1 IMX6ULL

一. 串口应用程序编程介绍

1.串口的作用

UART:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),简称串口。
调试:移植u-boot、内核、应用程序时,主要使用串口查看打印信息
外接各种模块

2. 本套视频特别说明

Linux驱动大全中UART子系统里,有UART应用编程,也有UART驱动程序的深入讲解。
我们把Linux驱动大全中UART应用编程部分的课程,放到Linux应用编程基础里。
观看视频时,要注意源码路径有变化:
视频中使用的GIT仓库是:

doc_and_source_for_drivers

源码路径为:

doc_and_source_for_drivers\IMX6ULL\source\09_UART
doc_and_source_for_drivers\STM32MP157\source\A7\09_UART

你学习应用编程时,GIT仓库是:

 01_all_series_quickstart

源码路径为:

01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source\14_UART

二. 硬件知识_UART硬件介绍

1. 串口的硬件介绍

UART的全称是Universal Asynchronous Receiver and Transmitter,即异步发送和接收。
串口在嵌入式中用途非常的广泛,主要的用途有:
打印调试信息;
外接各种模块:GPS、蓝牙;
串口因为结构简单、稳定可靠,广受欢迎。
通过三根线即可,发送、接收、地线。

通过TxD->RxD把ARM开发板要发送的信息发送给PC机。
通过RxD->TxD线把PC机要发送的信息发送给ARM开发板。
最下面的地线统一参考地。

2. 串口的参数

波特率:一般选波特率都会有9600,19200,115200等选项。其实意思就是每秒传输这么多个比特位数(bit)。
起始位:先发出一个逻辑”0”的信号,表示传输数据的开始。
数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。小端传输。
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据
传送的正确性。
停止位:它是一个字符数据的结束标志。
怎么发送一字节数据,比如‘A‘?
‘A’的ASCII值是0x41,二进制就是01000001,怎样把这8位数据发送给PC机呢?
a.双方约定好波特率(每一位占据的时间);
b.规定传输协议
原来是高电平,ARM拉低电平,保持1bit时间;
PC在低电平开始处计时;
ARM根据数据依次驱动TxD的电平,同时PC依次读取RxD引脚电平,获得数据;

前面图中提及到了逻辑电平,也就是说代表信号1的引脚电平是人为规定的。
如图是TTL/CMOS逻辑电平下,传输‘A’时的波形:

如图是RS-232逻辑电平下,传输‘A’时的波形:

在-12V至-3V之间,就认为是逻辑1,在+3V至+12V之间就为逻辑0。
RS-232的电平比TTL/CMOS高,能传输更远的距离,在工业上用得比较多。
市面上大多数ARM芯片都不止一个串口,一般使用串口0来调试,其它串口来外接模块。

3. 串口电平

ARM芯片上得串口都是TTL电平的,通过板子上或者外接的电平转换芯片,转成RS232接口,连接到电
脑的RS232串口上,实现两者的数据传输。

现在的电脑越来越少有RS232串口的接口,当USB是几乎都有的。因此使用USB串口芯片将ARM芯片上
的TTL电平转换成USB串口协议,即可通过USB与电脑数据传输。
上面的两种方式,对ARM芯片的编程操作都是一样的。

4. 串口内部结构

ARM芯片是如何发送/接收数据?
如图所示串口结构图:

要发送数据时,CPU控制内存要发送的数据通过FIFO传给UART单位,UART里面的移位器,依次将数据
发送出去,在发送完成后产生中断提醒CPU传输完成。
接收数据时,获取接收引脚的电平,逐位放进接收移位器,再放入FIFO,写入内存。在接收完成后产生
中断提醒CPU传输完成。

三. TTY体系中设备节点的差别

参考资料:
解密TTY:https://www.cnblogs.com/liqiuhao/p/9031803.html
彻底理解Linux的各种终端类型以及概念:https://www.cnblogs.com/liqiuhao/p/9031803.html
Linux终端和Line discipline图解:https://blog.csdn.net/dog250/article/details/78818612
What Are Teletypes, and Why Were They Used with Computers?:https://www.howtogeek.com/727213/what-are-teletypes-and-why-were-they-used-with-computers/

1. 傻傻分不清

/dev/ttyS0、/dev/ttySAC0、/dev/tty、/dev/tty0、/dev/tty1、/dev/console,它们有什么差别?

TTY/Terminal/Console/UART,
它们有什么差别?

2. 要讲历史了

2.1 电传机teletype

teletype,更准确地说是teleprinter,是一种通信设备,可以用来发送、接收文本信息。
teletype是一家公司的名字,它生产的teleprinter实在太有名,结果公司名变成了这类产品的名字:
teleprinter都被称为teletype了。

teletype被用来传输商业电报,想像一下:
把两台teletype的线缆接在一起,或者使用无线技术连接两台teletype
这边打字,另一边就可以接收到信息并通过纸张打印出来
注意:这两台teletype可以离得很远

2.2 计算机需要控制

2.2.1 使用teletype

teletype的简称就是TTY
最古老的计算机支持的是批处理模型:
怎么编程?卡片打孔,然后喂给计算机。
怎么得到输出信息?计算机根据结果在卡片上打孔,需要专人翻译这些卡片。
如果把两台teletype的其中一台,替换为计算机,不就更方便了吗?可以即时输入指令、即时看到结果。
于是teletype变成了计算机的终端、Terminal,远端之意。
teletype和计算机可以放在一个房间里,也可以放在很远很远的地方。

teletype是通过串口(UART)跟计算机相连的

那时计算机很贵,发展出多用户系统,多个用户可以分时使用计算机,降低成本:

2.2.2 teletype被淘汰了

1960年代,CRT显示器+键盘,替代了teletype:
显示器替代了纸张
速度更快
成本更低
它仍然只是一个终端(terminal),通过线缆连接到计算机
虽然不再是teletype,但是它的驱动程序仍然叫做TTY

2.2.3 个人电脑和虚拟终端

上图长得像电脑,但是它只是一个终端,它要连接到计算机才能工作。
这才是电脑:计算单元(CPU、硬盘、内存等等)、终端(键盘、显示器)都齐全了:

硬件上只有一套键盘、显示器。
但是在Linux系统中,我们可以打开多个命令行程序(也叫terminal、shell),每个程序都对应一个"虚拟终端"。

3. 在Ubuntu上演示

按住键盘:Ctrl+Alt+F3启动一个虚拟终端,Ctrl+Alt+F4再启动一个虚拟终端。
在里面切换为root用户:

sudo passwd root // 如果su root不成功,就先设置root密码
su root

3.1 各类设备节点的差别

由于历史原因,下图中两条红线之内的代码被称为TTY子系统。
它既支持UART,也支持键盘、显示器,还支持更复杂的功能(比如伪终端)。

3.2 /dev/ttyN(N=1,2,3,…)

/dev/tty3、/dev/tty4:表示某个程序使用的虚拟终端

// 在tty3、tty4终端来回切换,执行命令
echo hello > /dev/tty3
echo hi > /dev/tty4

3.3 /dev/tty0

/dev/tty0:表示前台程序的虚拟终端
你正在操作的界面,就是前台程序
其他后台程序访问/dev/tty0的话,就是访问前台程序的终端,切换前台程序时,/dev/tty0是变化

// 1. 在tty3终端执行如下命令
// 2. 然后在tty3、tty4来回切换
while [ 1 ]; do echo msg_from_tty3 > /dev/tty0; sleep 5; done

3.4 /dev/tty

/dev/tty表示本程序的终端,可能是虚拟终端,也可能是真实的终端。
程序A在前台、后台间切换,它自己的/dev/tty都不会变。

// 1. 在tty3终端执行如下命令
// 2. 然后在tty3、tty4来回切换
while [ 1 ]; do echo msg_from_tty3 > /dev/tty; sleep 5; done

3.5 Terminal和Console的差别

Terminal含有远端的意思,中文为:终端。Console翻译为控制台,可以理解为权限更大、能查看更多信息。
比如我们可以在Console上看到内核的打印信息,从这个角度上看:
Console是某一个Terminal
Terminal并不都是Console。
我们可以从多个Terminal中选择某一个作为Console
很多时候,两个概念混用,并无明确的、官方的定义

3.6 /dev/console

选哪个?内核的打印信息从哪个设备上显示出来?
可以通过内核的cmdline来指定,
比如: console=ttyS0 console=tty
我不想去分辨这个设备是串口还是虚拟终端,
有没有办法得到这个设备?
有!通过/dev/console
console=ttyS0时:/dev/console就是ttyS0
console=tty时:/dev/console就是本程序的终端
console=tty0时:/dev/console就是前台程序的虚拟终端
console=ttyN时:/dev/console就是/dev/ttyN
console有多个取值时,使用最后一个取值来判断

四. TTY驱动程序框架

参考资料
解密TTY :https://www.cnblogs.com/liqiuhao/p/9031803.html
彻底理解Linux的各种终端类型以及概念:https://blog.csdn.net/dog250/article/details/78766716
Linux终端和Line discipline图解:https://blog.csdn.net/dog250/article/details/78818612
What Are Teletypes, and Why Were They Used with Computers?:https://www.howtogeek.com/727213/what-are-teletypes-and-why-were-they-used-with-computers/

1. 行规程的引入

以下文字引用自参考资料解密TTY:
大多数用户都会在输入时犯错,所以退格键会很有用。这当然可以由应用程序本身来实现,但是根据
UNIX设计“哲学”,应用程序应尽可能保持简单。为了方便起见,操作系统提供了一个编辑缓冲区和一些
基本的编辑命令(退格,清除单个单词,清除行,重新打印),这些命令在行规范(line discipline)内
默认启用。高级应用程序可以通过将行规范设置为原始模式(raw mode)而不是默认的成熟或准则模
式(cooked and canonical)来禁用这些功能。大多数交互程序(编辑器,邮件客户端,shell,及所有
依赖curses或readline的程序)均以原始模式运行,并自行处理所有的行编辑命令。行规范还包含字符
回显和回车换行(译者注:\r\n 和 \n)间自动转换的选项。如果你喜欢,可以把它看作是一个原始的内
核级sed(1)。
另外,内核提供了几种不同的行规范。一次只能将其中一个连接到给定的串行设备。行规范的默认规则
称为N_TTY(drivers/char/n_tty.c,如果你想继续探索的话)。其他的规则被用于其他目的,例如管理
数据包交换(ppp,IrDA,串行鼠标),但这不在本文的讨论范围之内。

2. TTY驱动程序框架

五. 在STM32MP157上做串口实验的准备工作(imux6ull不用操作,了解一下修改驱动程序)

本节课程源码在GIT仓库里

01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source\14_UART\
00_stm32mp157_devicetree_for_uart8

1. 出厂系统不支持扩展板上的UART

2. 只关注APP开发的话

把GIT仓库中这个dtb文件复制到开发板上去就可以:

doc_and_source_for_drivers\STM32MP157\source\A7\09_UART\00_stm32mp157_devicetree
_for_uart8\dtb
stm32mp157c-100ask-512d-lcd-v1.dtb

操作方法:
Ubuntu上:复制dtb文件到NFS目录:

$ cp stm32mp157c-100ask-512d-lcd-v1.dtb ~/nfs_rootfs/

开发板上挂载NFS文件系统
vmware使用NAT(假设windowsIP为193.168.1.100)

[root@100ask:~]# mount -t nfs -o nolock,vers=3,port=2049,mountport=9999
193.168.1.100:/home/book/nfs_rootfs /mnt

vmware使用桥接,或者不使用vmware而是直接使用服务器:假设Ubuntu IP为
193.168.1.137

[root@100ask:~]# mount -t nfs -o nolock,vers=3
193.168.1.137:/home/book/nfs_rootfs /mnt

更新设备树

[root@100ask:~]# mount /dev/mmcblk2p2 /boot
[root@100ask:~]# cp /mnt/stm32mp157c-100ask-512d-lcd-v1.dtb /boot
[root@100ask:~]# sync

重启开发板
在串口可以看到以下设备节点

ls /dev/ttySTM*
/dev/ttySTM0
/dev/ttySTM1
/dev/ttySTM3

3. 从头修改设备树文件

3.1 确定硬件资源


打开STM32MP157底板原理图,可知UART8_TX、UART8_RX使用引脚PE1、PE0,如下图:

3.2 使能设备树节点

在STM32MP157的内核设备树文件 arch/arm/boot/dts/stm32mp151.dtsi 中,已经设置了uart8节
点:

我们需要使能这个节点(改为okay),往下看。

3.3 通过Pinctrl指定引脚

光使能UART8还不行,还需要为它选择引脚。
修改 arch/arm/boot/dts/stm32mp15xx-100ask.dtsi ,如下:

&uart8 {pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart8_pins_mx>;
pinctrl-1 = <&uart8_sleep_pins_mx>;
status = "okay";
};

这些Pinctrl信息已经在 arm/boot/dts/stm32mp157-100ask-pinctrl.dtsi 中有了,无需我们添加:

3.4 指定设备别名

UART8对应的设备节点是哪个?它的驱动程序需要从"别名"里确定编号。
修改 arch/arm/boot/dts/stm32mp157c-100ask-512d-v1.dts ,如下:

aliases {ethernet0 = &ethernet0;
serial0 = &uart4; //debug
serial1 = &usart6; //rs485
serial2 = &usart1; //bluetooth
serial3 = &uart8; // extend board uart, /dev/ttySTM3
};

3.5 编译、使用

设置工具链

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihfexport PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/armbuildroot-linux-gnueabihf_sdk-b

编译设备树:
在Ubuntu的STM32MP157内核目录下执行如下命令,
得到设备树文件: arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb

make dtbs

复制到NFS目录:

$ cp arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb ~/nfs_rootfs/

开发板上挂载NFS文件系统
vmware使用NAT(假设windowsIP为193.168.1.100)

[root@100ask:~]# mount -t nfs -o nolock,vers=3,port=2049,mountport=9999
193.168.1.100:/home/book/nfs_rootfs /mnt

vmware使用桥接,或者不使用vmware而是直接使用服务器:假设Ubuntu IP为
193.168.1.137

[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

更新设备树

[root@100ask:~]# mount /dev/mmcblk2p2 /boot
[root@100ask:~]# cp /mnt/stm32mp157c-100ask-512d-lcd-v1.dtb /boot
[root@100ask:~]# sync

重启开发板
在串口可以看到以下设备节点

ls /dev/ttySTM*
/dev/ttySTM0
/dev/ttySTM1
/dev/ttySTM3

六. Linux串口应用编程

参考资料:
Serial Programming Guide for POSIX Operating Systems:https://digilander.libero.it/robang/rubrica/serial.htm#CONTENTS
Linux串口编程:https://www.cnblogs.com/feisky/archive/2010/05/21/1740893.html
Linux串口—struct termios结构体:https://blog.csdn.net/yemingzhu163/article/details/5897156
这个是转载,排版更好看: https://www.cnblogs.com/sky-heaven/p/9675253.html
本节课程源码在GIT仓库里:

01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source\14_UART\
01_app_send_recv
02_gps

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结构体。

2. 串口收发实验

本实验用过把串口的发送、接收引脚短接,实现自发自收:使用write函数发出字符,使用read函数读
取字符。

2.1 接线

2.1.1 IMX6ULL

2.2 编程

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{struct termios newtio,oldtio;if ( tcgetattr( fd,&oldtio) != 0) { perror("SetupSerial 1");return -1;}bzero( &newtio, sizeof( newtio ) );newtio.c_cflag |= CLOCAL | CREAD; newtio.c_cflag &= ~CSIZE; newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/newtio.c_oflag  &= ~OPOST;   /*Output*/switch( nBits ){case 7:newtio.c_cflag |= CS7;break;case 8:newtio.c_cflag |= CS8;break;}switch( nEvent ){case 'O':newtio.c_cflag |= PARENB;newtio.c_cflag |= PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;case 'E': newtio.c_iflag |= (INPCK | ISTRIP);newtio.c_cflag |= PARENB;newtio.c_cflag &= ~PARODD;break;case 'N': newtio.c_cflag &= ~PARENB;break;}switch( nSpeed ){case 2400:cfsetispeed(&newtio, B2400);cfsetospeed(&newtio, B2400);break;case 4800:cfsetispeed(&newtio, B4800);cfsetospeed(&newtio, B4800);break;case 9600:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;case 115200:cfsetispeed(&newtio, B115200);cfsetospeed(&newtio, B115200);break;default:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;}if( nStop == 1 )newtio.c_cflag &= ~CSTOPB;else if ( nStop == 2 )newtio.c_cflag |= CSTOPB;newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: * 比如VMIN设为10表示至少读到10个数据才返回,* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)* 假设VTIME=1,表示: *    10秒内一个数据都没有的话就返回*    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回*/tcflush(fd,TCIFLUSH);if((tcsetattr(fd,TCSANOW,&newtio))!=0){perror("com set error");return -1;}//printf("set done!\n");return 0;
}int open_port(char *com)
{int fd;//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);fd = open(com, O_RDWR|O_NOCTTY);if (-1 == fd){return(-1);}if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/{printf("fcntl failed!\n");return -1;}return fd;
}/** ./serial_send_recv <dev>*/
int main(int argc, char **argv)
{int fd;int iRet;char c;/* 1. open *//* 2. setup * 115200,8N1* RAW mode* return data immediately*//* 3. write and read */if (argc != 2){printf("Usage: \n");printf("%s </dev/ttySAC1 or other>\n", argv[0]);return -1;}fd = open_port(argv[1]);if (fd < 0){printf("open %s err!\n", argv[1]);return -1;}iRet = set_opt(fd, 115200, 8, 'N', 1);if (iRet){printf("set port err!\n");return -1;}printf("Enter a char: ");while (1){scanf("%c", &c);iRet = write(fd, &c, 1);iRet = read(fd, &c, 1);if (iRet == 1)printf("get: %02x %c\n", c, c);elseprintf("can not get data\n");}return 0;
}

2.3 上机实验

2.3.1 IMX6ULL

先设置工具链:

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihfexport PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linuxgnueabihf_sdk-buildroot/bin

编译、执行程序:

1. Ubuntu上
arm-buildroot-linux-gnueabihf-gcc -o serial_send_recv serial_send_recv.c
2. 板子上
/mnt/serial_send_recv /dev/ttymxc5

3. GPS模块实验

3.1 GPS简介

全球定位系统(Global Positioning System,GPS)是一种以空中卫星为基础的高精度无线电导航的定位系统,它在全球任何地方以及近地空间都能够提供准确的地理位置、车行速度及精确的时间信息。GPS主要由三大组成部分:空间部分、地面监控部分和用户设备部分。GPS系统具有高精度、全天候、用广泛等特点。
太空卫星部分由多颗卫星组成,分成多个轨道,绕行地球一周约12小时。每个卫星均持续发射载有卫星轨道数据及时间的无线电波,提供地球上的各种接收机来应用。
地面管制部分,这是为了追踪及控制太空卫星运行所设置的地面管制站,主要工作为负责修正与维护每个卫星能够正常运转的各项参数数据,以确保每个卫星都能够提供正确的讯息给使用者接收机来接收
使用者接收机(即用户设备),追踪所有的GPS卫星,并实时的计算出接收机所在位置的坐标、移动速度及时间。我们日常接触到的是用户设备部分,这里使用到的GPS模块即为用户设备接收机部分。

3.2 GPS模块硬件

GPS模块与外部控制器的通讯接口有多种方式,这里我们使用串口进行通讯,波特率为9600bps,1bit停止位,无校验位,无流控,默认每秒输出一次标准格式数据。
GPS模块外观如下图所示,通过排线与控制器进行供电和通讯。该模块为集成模块,没有相关原理图。

3.3 GPS模块数据格式

GPS使用多种标准数据格式,目前最通用的GNSS格式是NMEA0183格式。NMEA0183是最终定位格式,即将二进制定位格式转为统一标准定位格式,与卫星类型无关。这是一套定义接收机输出的标准信息,有几种不同的格式,每种都是独立相关的ASCII格式,逗点隔开数据流,数据流长度从30-100字符不等,通常以每秒间隔持续输出。
NVMEA0183格式主要针对民用定位导航,与专业RTCM2.3/3.0和CMR+的GNSS数据格式不同。通过NMEA0183格式,可以实现GNSS接收机与PC或PDA之间的数据交换,可以通过USB和COM口等通用数据接口进行数据传输,其兼容性高,数据传输稳定。这里我们使用串口进行是通讯,通信框图如下图所示。
我们使用串口接收数据,收到的数据包含:

$GPGGA(GPS定位数据)、
$GPGLL(地理定位信息)、
$GPGSA(当前卫星信息)、
$GPGSV(可见卫星状态信息)、
$GPRMC(推荐最小定位信息)、
$GPVTG(地面速度信息)。

这里我们只分析$GPGGA (Global Positioning System Fix Data)即可,它包含了GPS定位经纬度、质量
因子、HDOP、高程、参考站号等字段。其标准格式如下:

$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*hh

$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。

3.4 编程

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{struct termios newtio,oldtio;if ( tcgetattr( fd,&oldtio) != 0) { perror("SetupSerial 1");return -1;}bzero( &newtio, sizeof( newtio ) );newtio.c_cflag |= CLOCAL | CREAD; newtio.c_cflag &= ~CSIZE; newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/newtio.c_oflag  &= ~OPOST;   /*Output*/switch( nBits ){case 7:newtio.c_cflag |= CS7;break;case 8:newtio.c_cflag |= CS8;break;}switch( nEvent ){case 'O':newtio.c_cflag |= PARENB;newtio.c_cflag |= PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;case 'E': newtio.c_iflag |= (INPCK | ISTRIP);newtio.c_cflag |= PARENB;newtio.c_cflag &= ~PARODD;break;case 'N': newtio.c_cflag &= ~PARENB;break;}switch( nSpeed ){case 2400:cfsetispeed(&newtio, B2400);cfsetospeed(&newtio, B2400);break;case 4800:cfsetispeed(&newtio, B4800);cfsetospeed(&newtio, B4800);break;case 9600:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;case 115200:cfsetispeed(&newtio, B115200);cfsetospeed(&newtio, B115200);break;default:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;}if( nStop == 1 )newtio.c_cflag &= ~CSTOPB;else if ( nStop == 2 )newtio.c_cflag |= CSTOPB;newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: * 比如VMIN设为10表示至少读到10个数据才返回,* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)* 假设VTIME=1,表示: *    10秒内一个数据都没有的话就返回*    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回*/tcflush(fd,TCIFLUSH);if((tcsetattr(fd,TCSANOW,&newtio))!=0){perror("com set error");return -1;}//printf("set done!\n");return 0;
}int open_port(char *com)
{int fd;//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);fd = open(com, O_RDWR|O_NOCTTY);if (-1 == fd){return(-1);}if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/{printf("fcntl failed!\n");return -1;}return fd;
}int read_gps_raw_data(int fd, char *buf)
{int i = 0;int iRet;char c;int start = 0;while (1){iRet = read(fd, &c, 1);if (iRet == 1){if (c == '$')start = 1;if (start){buf[i++] = c;}if (c == '\n' || c == '\r')return 0;}else{return -1;}}
}/* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF> */
int parse_gps_raw_data(char *buf, char *time, char *lat, char *ns, char *lng, char *ew)
{char tmp[10];if (buf[0] != '$')return -1;else if (strncmp(buf+3, "GGA", 3) != 0)return -1;else if (strstr(buf, ",,,,,")){printf("Place the GPS to open area\n");return -1;}else {//printf("raw data: %s\n", buf);sscanf(buf, "%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]", tmp, time, lat, ns, lng, ew);return 0;}
}/** ./serial_send_recv <dev>*/
int main(int argc, char **argv)
{int fd;int iRet;char c;char buf[1000];char time[100];char Lat[100]; char ns[100]; char Lng[100]; char ew[100];float fLat, fLng;/* 1. open *//* 2. setup * 115200,8N1* RAW mode* return data immediately*//* 3. write and read */if (argc != 2){printf("Usage: \n");printf("%s </dev/ttySAC1 or other>\n", argv[0]);return -1;}fd = open_port(argv[1]);if (fd < 0){printf("open %s err!\n", argv[1]);return -1;}iRet = set_opt(fd, 9600, 8, 'N', 1);if (iRet){printf("set port err!\n");return -1;}while (1){/* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF>*//* read line 读函数*/iRet = read_gps_raw_data(fd, buf);/* parse line 解析函数*/if (iRet == 0){iRet = parse_gps_raw_data(buf, time, Lat, ns, Lng, ew);}/* printf  */if (iRet == 0){printf("Time : %s\n", time);printf("ns   : %s\n", ns);printf("ew   : %s\n", ew);printf("Lat  : %s\n", Lat);printf("Lng  : %s\n", Lng);/* 纬度格式: ddmm.mmmm */sscanf(Lat+2, "%f", &fLat);fLat = fLat / 60;fLat += (Lat[0] - '0')*10 + (Lat[1] - '0');/* 经度格式: dddmm.mmmm */sscanf(Lng+3, "%f", &fLng);fLng = fLng / 60;fLng += (Lng[0] - '0')*100 + (Lng[1] - '0')*10 + (Lng[2] - '0');printf("Lng,Lat: %.06f,%.06f\n", fLng, fLat);}}return 0;
}

3.5 接线

3.5.1 IMX6ULL

3.6 上机实验

3.6.1 IMX6ULL

先设置工具链:

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihfexport PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linuxgnueabihf_sdk-buildroot/bin

编译、执行程序:

1. Ubuntu上
arm-buildroot-linux-gnueabihf-gcc -o gps_read gps_read.c
2. 板子上
/mnt/gps_read /dev/ttymxc5

嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十八)串口编程相关推荐

  1. 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(一) 嵌入式Linux开发基本概念以及开发流程介绍

    文章目录 1.linux开发初了解 1.1 嵌入式Linux开发的基本概念 1.1.1关于Git的背景介绍 1.1.2关于repo的背景介绍 1.1 3 一些关于此背景知识的介绍 1.1.4关于Lin ...

  2. 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(八)IMX6ULL开发板编译第一个程序以及驱动

    文章目录 1. IMX6ULL开发板初次操作 1.1 100ask_imx6ull开发板硬件资源简介 1.1.1 100ask imx6ull mini开发板 1.2 100ask_imx6ull开发 ...

  3. iMX6ULL驱动开发 | 让imx6ull开发板支持usb接口FC游戏手柄

    手边有一闲置的linux开发板iMX6ULL一直在吃灰,不用来搞点事情,总觉得对不住它.业余打发时间就玩起来吧,总比刷某音强.从某多多上买来一个usb接口的游戏手柄,让开发板支持以下它,后续就可以接着 ...

  4. 嵌入式linux ASoC架构声卡驱动开发

    嵌入式linux ASoC架构声卡驱动开发 文章目录 嵌入式linux ASoC架构声卡驱动开发 需求分析 ASoC架构下声卡驱动代码结构 codec驱动 snd_soc_register_codec ...

  5. EasyARM-i.MX280A—Linux系统开发板(开发日记1-登录开发板)

    今天,心仪的板子终于到了,作为一个外行来说,既有对板子开发的期待,又有对自己技术的担忧.为了记录一下自己的开发过程,同时也是自己的成长过程,特地开了个专栏,我知道这将是一个漫长的过程,我会不断的更新, ...

  6. 转载:百为STM32开发板教程之十二——NAND FLASH

    http://bbs.21ic.com/icview-586200-1-1.html 百为STM32开发板教程之十二--NAND FLASH 参考资料: 百为stm32开发板光盘V3\百为stm32开 ...

  7. 小日历实验目的Android,【连载】【星光闪电STM32F407开发板】第十二章 RTC实验

    [连载][星光闪电STM32F407开发板]第十二章 RTC实验 [复制链接] 第十二章RTC实验 12.1 实验目的 本实验旨在掌握STM32的实时时钟RTC的使用,利用其测量日期时间,数据手册请参 ...

  8. 【嵌入式小白学习】--在STM32开发板上实现简单的串口通信

    嵌入式小白学习--在STM32开发板上实现简单的串口通信 这里写目录标题 嵌入式小白学习--在STM32开发板上实现简单的串口通信 Part1. STM32环境的搭建 Part2. 编写代码 Part ...

  9. 树莓派Pico W无线WiFi开发板使用方法及MicroPython网络编程实践

    树莓派Pico W开发板是树莓派基金会于2022年6月底推出的一款无线WiFi开发板,它支持C/C++和MicroPython编程.本文介绍树莓派Pico W无线WiFi开发板的使用方法及MicroP ...

  10. 6467t 1080php,DM6467T开发板领航——开发环境的文件配置

    DM6467T开发板领航--开发环境的文件配置 继上一篇DM6467T开发领航--开发环境安装 按照我们上一篇DM6467T开发领航--开发环境安装的安装方法,最主要要修改的文件就是/home/dm6 ...

最新文章

  1. 090_HTMLCollection和NodeList对象
  2. 经典C语言程序100例之六一
  3. 最优化方法外罚函数法Matlab,最优化方法 第三篇(罚函数法).pdf
  4. 受困于敏捷开发的数据与架构?肿么办?
  5. 每日一道剑指offer-反转链表
  6. 三菱fx2n64mr说明书_三菱FX2N-64MR-D编程手册(FX系列可编程控制器) - 三菱
  7. 《算法分析》——布线问题
  8. 通达oa SQL注入day
  9. Apple M1 开启HiDPI的新方法,无需关闭SIP,无需SwitchResX
  10. 卧槽!还有这种事!马斯克的SpaceX-API 竟然开源了!登顶GitHub热榜!
  11. python 生成器
  12. 高德h5定位误差_高德地图定位JS API不准确问题
  13. 终于得空,写两句了......
  14. 太空探测器 java_宇宙究竟有多大?这个探测器或将告诉你答案
  15. IO、文件、NIO【三】
  16. php捕捉Warning、Notice错误
  17. python支持向量机SVM (sklearn)
  18. SQL SERVER 生僻字查询问题和关键字COLLATE
  19. 打开设备管理器的快捷方式
  20. 大学期间技术学习方面最有成就感的事?

热门文章

  1. PermissionError: [Errno 13] Permission denied: ‘E:\\data\\lianxi_out\\csv‘权限错误
  2. 2018 Python零基础大神运维自动化就业教程最新版Python运维就业 1-5部分
  3. 九二、node+cheerio爬虫学习
  4. Django项目创建
  5. 娃娃专用开源系统Qimo OS
  6. 高精度真空度程序控制在真空解冻过程中的应用
  7. 外汇市场中一些比较常见且非常实用的外汇交易策略
  8. 实用java完成万年历的制作
  9. torch中的NLLLoss与CrossEntropyLoss
  10. 用dot函数C语言画矩形,Graphviz绘图 - DOT语言