1.0 UART简介
在Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty(Teletype)来简称各种类型的终端设备。对于嵌入式系统而言,最普遍采用的是UART(Universal Asynchronous Receiver / Transmitter )串行端口,日常生活中简称串口。
UART(通用异步收发器)是一种双向、串行、异步的通信总线,仅用一根数据接收线和一根数据发送线就能实现全双工通信。典型的串口通信使用3根线完成,分别是:发送线(TX)、接收线(RX)和地线(GND),通信时必须将双方的TX和RX交叉连接并且GND相连才可正常通信,如下图所示:
- 串行通信是指利用一条传输线将数据一位位地顺序传送,也可以用两个信号线组成全双工通信,如rs232。特点是通信线路简单,利用简单的线缆就可实现通信,降低成本,适用于远距离通信,但传输速度慢的应用场合。
- 异步通信以一个字符为传输单位,通信中两个字符间的时间间隔多少是不固定的,然而在同一个字符中的两个相邻位间的时间间隔是固定的。通俗说是两个uart设备之间通信的时候不需要时钟线,这时候就需要在两个uart设备上指定相同的传输速率,以及空闲位、起始位、校验位、结束位,也就是遵循相同的协议。
- 数据传送速率用波特率来表示,即每秒钟传送的二进制位数。例如数据传送速率为120字符/秒,而每一个字符为10位(1个起始位,7个数据位,1个校验位,1个结束位),则其传送的波特率为10×120=1200字符/秒=1200波特。
2.0 UART协议帧
- 空闲位:
UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平,表示当前线路上没有数据传输。
- 起始位:
每开始一次通信时发送方先发出一个逻辑”0”的信号(低电平),表示传输字符的开始。因为总线空闲时为高电平所以开始一次通信时先发送一个明显区别于空闲状态的信号即低电平。
- 数据位:
起始位之后就是我们所要传输的数据,数据位可以是5、6、7、8,9位等,构成一个字符(一般都是8位)。如ASCII码(7位),扩展BCD码(8位)。先发送最低位,最后发送最高位,使用低电平表示‘0’高电平表示‘1’完成数据位的传输。
- 奇偶校验位:
数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。校验位其实是调整个数,串口校验分几种方式:
- 1、无校验(no parity)。
- 2、奇校验(odd parity):如果数据位中“1”的数目是偶数,则校验位为“1”,如果“1”的数目是奇数,校验位为“0”。
- 3、偶校验(even parity):如果数据为中“1”的数目是偶数,则校验位为“0”,如果为奇数,校验位为“1”。
- 4、mark parity:校验位始终为1(不常用)。
- 5、parity:校验位始终为0(不常用)。
- 停止位:
它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备之间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟的机会。停止位个数越多,数据传输越稳定,但是数据传输速度也越慢。
波特率:
数据传输速率使用波特率来表示。单位bps(bits per second),常见的波特率9600bps、115200bps等等,其他标准的波特率是1200,2400,4800,19200,38400,57600。举个例子,如果串口波特率设置为9600bps,那么传输一个比特需要的时间是1/9600≈104.2us。
以9600,8-N-1(9600波特率,8个数据位,没有校验位,1位停止位)为例,这是目前最常用的串口配置,现在我们传输“O”“K”两个ASCII值,“O”的ASCII为79,对应的二进制数据为01001111,“K”对应的二进制数据为01001011,传输的格式数据如下图所示:
串口波特率为9600,1bit传输时间大约为104us,传送一个数据实际是10个比特(开始位-8个数据位-停止位),一个bytes传输速率实际为9600*8/10=7680bps。
3.0 代码相关内容
两个重要的结构体uart_port和uart_driver:
-
uart_driver:包含了串口设备名、串口驱动名、主次设备号、串口控制台(可选)等信息,还封装了tty_driver(底层串口驱动无需关心tty_driver)。
-
uart_port:用于描述一个UART端口(直接对应于一个串口)的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。
-
uart_driver需要驱动编写人员实现,并使用uart_register_driver注册到内核,卸载驱动的时候,使用uart_unregister_driver卸载。
-
uart_port用于描述一个具体的串口端口,驱动编写人员需要实现uart_port,然后使用uart_add_one_port函数向内核添加一个uart端口,卸载的时候uart_remove_one_port卸载。
uart_port里面有个非常重要的成员变量uart_ops,此结构体包含了针对uart端口进行的所有操作,需要驱动编写人员实现!串口驱动是和TTY结合起来的。
以串口log为例。我们使用的TX:18 RX:19。
对应原理图:
对应在设备树的代码配置信息:
杂项:
这些年芯片厂商基本都对常用的子系统做好了各硬件外设的linux内核底层支持,大部分不需要自己再去编写内核级别的驱动,基本配合设备树配置下就可以使用起来。UART子系统也是如此。
对于设备树的开发,如果没有原型(如新开发的一款芯片),对于设备树的设计流程如下:根据硬件设计的板级信息,结合DTS语法知识,完成.dts或.dtsi文件的编写,在通过scripts/dtc目录下的DTC工具进行编译,生成.dtb文件。Linux启动后,会加载编译完成的dtb文件到内核中,从而满足内核模块对于硬件和外设的操作要求。不过大多数情况下(非IC设计原厂的软件的工程师), 都会提供好支持官方开发板的相同芯片的DTS文件,而我们的主要工作就是根据硬件设计的变动,修改这个DTS的信息以适配目前应用的需求,那么工作就简单多了,具体分为以下几个步骤:
1.结合现有设备树的Node情况,修改成符合需求Node的dts文件,编译完成后,生成dtb文件
2.结合上节内容,实现字符驱动设备的添加,硬件部分操作替换成基于设备树操作的版本
3.下载,编写测试模块并进行测试
参考文章
- https://www.jianshu.com/p/90521ea7ac2d
- https://zhuanlan.zhihu.com/p/150504364
- https://www.jianshu.com/p/90521ea7ac2d
- 一文搞懂UART通信协议_51CTO博客_uart通信协议
- UART接口介绍_uart接口引脚定义-腾讯云开发者社区-腾讯云 (tencent.com)
- 1、UART协议详解-CSDN博客
- 嵌入式Linux学习笔记(四) 设备树和UART驱动开发 - 心的起始 - 博客园 (cnblogs.com)