第四课时:UART 基础知识
本课主题:UART(串口)的基础知识,包括数据帧格式、UART 与 USART 的区别,以及两个串口收发的小实验。
课程视频:UART 基础知识
一、单片机与其他设备之间的通信连接
两台设备之间通信,接线必须交叉,数据也有固定的格式。先看接线。
Tx 是数据发送引脚,负责把数据发出去,Rx 是数据接收引脚,负责从对面收数据。所以单片机和其他设备相连时,自己的 Tx 要接对方的 Rx,自己的 Rx 要接对方的 Tx,也就是交叉连接。接反了就收不到数据,这是接线时最容易犯的错。
1.1 数据格式
串口线上没有时钟线,收发双方全靠事先约定好的格式来对齐,这个格式叫数据帧。一帧由三部分组成:起始位、数据位、停止位。
串口平时空闲时线是高电平。要开始发数据,先发一个起始位,也就是把线拉低一个位的时间,告诉对方我要开始发了。接着是数据位,这里用电压来表示数据,发送低电压代表 0,发送高电压代表 1。数据位发完,再发停止位,方法就是保持一段时间的高电平。数据位的长度是可以设定的,习惯上以字节,也就是 8 位来传输。
数据位长度不同,一帧能装的信息也不同。8 位正好是一个字节;如果加上校验位,8 位数据加 1 位校验就变成 9 位。图里把 8 位无校验、8 位有校验、9 位无校验、9 位有校验这几种情况并排列了出来,方便对照。
1.2 我的疑问
- 这些位数代表的是什么东西呢?
- 知道这些有什么用处呢?
1.3 疑问解答
数据位长度,说白了就是一帧里装数据的格子数。串口是把数据拆成一个个二进制位,也就是 0 和 1,一位一位地发;设成 8 位,就是每次连发 8 个 0 或 1,正好装下一个字节。所以位数指的是这一步能承载多少信息,位数越多,一次能装的数据越多,但传得也越慢。
知道这些的用处在于,收发双方必须把这些参数约定成一样,包括数据位长度、校验方式、停止位、波特率,否则接收方会把每一帧切错,收到的就全是乱码。这也是为什么在 CubeMX 和串口调试助手里要填 Data Bits、Parity、Stop Bits、Baud Rate,这几个选项正是对着这张数据格式图来的。
1.4 校验位
数据在传输途中可能出错,校验位就是用来粗查这种错误的。它分奇校验和偶校验,靠数数据位里 1 的个数来判断。
以发送十进制 85 为例,它的二进制是 1010101,其中有四个 1,是偶数个。用奇校验时,校验位补一个 1,让 1 的总数变成奇数;用偶校验时,校验位补 0,保持 1 的总数是偶数。接收方收到后同样数一遍 1 的个数,如果奇偶性对不上,就说明传输过程中出了错。
1.5 一位的时间
一位代表传输一个高低电压所消耗的时间,这个时间由波特率决定。
波特率是每秒钟传输位的数量。9600 波特率就是每秒传 9600 个位,一位等于 1/9600 秒,约 0.1 毫秒;115200 波特率每秒传 115200 个位,一位约 8.7 微秒。收发双方的波特率必须一致,否则每一位的时间对不上,数据就全乱了。
二、UART 与 USART 的区别
UART 和 USART 名字很像,工作模式却不一样。
UART 的全称是 Universal Asynchronous Receiver/Transmitter,也就是通用异步收发器,只支持异步工作模式,只有 Tx、Rx 两根线。USART 是 Universal Synchronous/Asynchronous Receiver/Transmitter,也就是通用同步异步收发器,同步和异步都支持,除了 Tx、Rx,还多出一根 CK 时钟线。也就是说,USART 比 UART 多了一种带时钟的同步模式,用它就能补上 UART 只支持异步的短板。
三、简单数据发送实验
3.1 连接单片机与电脑
单片机要跟电脑通信,中间需要一个 USB 转 TTL 模块,把电脑的 USB 信号转成串口能识别的电平。
模块的 GND、TXD、RXD 接出来和单片机相连。还是要记住发送接接收,所以模块的 TXD 接单片机的 Rx,模块的 RXD 接单片机的 Tx。
在这块 STM32F103C8T6 上,USART1 用的引脚是固定的:PA10 是 USART1_RX,也就是数据接收引脚;PA9 是 USART1_TX,也就是数据发送引脚。
实际接线就是这样,USB 转 TTL 模块的 GND、RXD、TXD 分别接到板子的 PA10、PA9 一带,另外还有 ST-Link 负责下载和调试。
接着打开 CubeMX 配置。在 Connectivity 分类里找到 USART,这颗芯片一共有三个串口。
USART1、USART2、USART3 分别对应串口 1、串口 2、串口 3,这次用串口 1。
点开之后先选模式。
Mode 里有好几个选项。Asynchronous 是异步模式,Synchronous 是同步模式,Single Wire 是半双工,后面还有多处理器通信、IrDA、LIN、智能卡等。这里选 Asynchronous,也就是异步模式。
选好异步模式后,CubeMX 会自动把引脚配好。可以看到 PA9 被设成了 USART1_TX,引脚模式自动变成复用输出推挽,最大输出速度是 High;PA10 被设成 USART1_RX,自动变成输入、No pull。要记住 CubeMX 会把 RX 自动配置为输入、TX 自动配置为输出,接线的时候千万不能接反。
3.2 串口参数与生成的代码
模式选好之后,往下配置串口参数,这些参数和前面讲的数据帧格式一一对应。
Baud Rate 是波特率,这里默认 115200 Bits/s;Word Length 是数据位长度,默认 8 Bits,也就是 8 位数据位;Parity 是校验方式,默认 None;Stop Bits 是停止位长度,默认 1。
停止位长度可以选 1 位或者 2 位,这里用 1 位。
校验方式有不使用校验位、偶校验、奇校验三种,这里选 None,也就是不使用校验位。综合下来,本课的串口默认配置就是 8 位数据位、无校验、1 位停止位、波特率 115200。
配置好生成工程,打开 main.c 看初始化流程。
MX_GPIO_Init 负责 IO 引脚的初始化,MX_USART1_UART_Init 负责 USART1 串口的初始化,两者分工明确。
除此之外,CubeMX 还自动生成了一个句柄(handle),写法是 UART_HandleTypeDef huart1,它包含了跟串口 1 有关的所有数据,后面收发数据的函数都要用到它。这里只需要记住有这么个句柄叫 huart1 就行。
3.3 发送数据的接口
要把数据发出去,用的是 HAL_UART_Transmit。
它的作用是通过串口向外发送数据,参数 huart 填串口句柄的指针,也就是 &huart1,pData 填要发送的数据的指针。
写发送代码之前,先认识一下单片机里常用的整数类型。
带符号整数用 int,无符号整数多一个 u,写作 uint。int8_t 和 uint8_t 是 8 位,int16_t 和 uint16_t 是 16 位,32 位、64 位依次类推。8 位无符号数范围是 0 到 255,16 位是 0 到 65535。后面发数据时 pData 要求是 uint8_t 指针,所以这些长度要提前心里有数。
完整的参数一共有四个:huart 是句柄,pData 是要发送的数据,Size 是要发送的字节数,Timeout 是超时时间,单位毫秒。返回值方面,HAL_OK 是成功,HAL_ERROR 是发送出错,HAL_BUSY 是串口忙,HAL_TIMEOUT 是发送超时。最后这个 Timeout 参数这里填 HAL_MAX_DELAY,表示无限期地等下去,直到发送完成为止。
3.4 实战
发送的数据可以是字节、数组、字符,也可以是字符串,写法略有不同。
发单个字节 0x5a,指针用 &byteNumber,长度写 1;发数组,指针直接用数组名 byteArray,长度写 5;发单个字符,因为 pData 要的是 uint8_t 指针,需要把 char 强转一下,写成 (uint8_t *)&ch;发字符串,指针用 (uint8_t *)str,长度用 strlen(str) 自动算。照着这个套路来就行。
四、通过电脑发送数据控制开发板
第二个实验反过来,让电脑发数据、开发板收数据。CubeMX 里的配置和前面一模一样,还是默认配置。
数据帧仍然是 8 位无校验、波特率 115200。本次实验的目标是,用电脑通过串口发指令,控制板载 LED 的熄灭与亮起。
工程里同样有串口 1 的句柄 huart1。
接收数据用的函数是 HAL_UART_Receive,作用和 Transmit 刚好相反,参数含义一样,只是把发送换成了接收,pData 变成了接收缓冲区的指针。
完整的参数和返回值也与发送类似,这里同样用 HAL_MAX_DELAY,一直等,直到接收完成。
把接收和判断拼起来就是完整逻辑:先读一个字节到 dataRcvd,再判断它是什么。这里有一个很重要的点,接收到的数据要加上引号,写成 ‘0’ 或 ‘1’,代表的是字符;如果写成不带引号的 0 或 1,那是数值,判断永远不成立,起不到任何作用。
uint8_tdataRcvd;HAL_UART_Receive(&huart1,&dataRcvd,1,HAL_MAX_DELAY);if(dataRcvd=='0')// 收到字符 '0',熄灭 LED{HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);// 板载灯是开漏,写 1 熄灭}elseif(dataRcvd=='1')// 收到字符 '1',点亮 LED{HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);// 板载灯是开漏,写 0 点亮}需要说明一下,原视频这张图里的 else if 分支也写成了 GPIO_PIN_SET,那是笔误。板载 LED 是开漏接法,写 0 才点亮,所以这里应该是 GPIO_PIN_RESET,上面的代码已经改正。
复习速记
- 串口默认配置:8 数据位、无校验、1 停止位、波特率 115200。
- 收发双方的帧格式必须一致,否则收到的全是乱码。
- 起始位是一个下降沿;停止位是保持一段高电平。
- 接线要交叉:TX 接 RX、RX 接 TX,千万别接反,TX 是发、RX 是收。
- 校验位分奇校验和偶校验,靠 1 的个数判断有没有传错。
- 发送指令是 HAL_UART_Transmit(&huart1, 数据指针, 数量, 超时),接收是 HAL_UART_Receive,参数一样。
- 判断收到的字符要加引号,写成 ‘0’、‘1’;写成 0、1 没有作用,那是数值不是字符。
- HAL_MAX_DELAY 表示无限期等待,是超时参数专用。
- 初始化:MX_GPIO_Init() 管 IO,MX_USART1_UART_Init() 管串口;句柄是 huart1。