理论知识#
物理信号#
硬件电路引脚#
- 实现模块点对点通信
- 在两模块均已供电的情况下,只需3条线即可( Rx,Tx,GND)
- Rx,Receiver,接收端
- Tx,Transmitter,发送端
- GND,Ground,接地端:两模块需共地,以区分 Rx & Tx 线上的电平高低
- 部分具有流控策略的设备还需 RTS & CTS 两根线
接地方式#
设备一 | 设备二 |
---|---|
RXD | TXD |
TXD | RXD |
GND | GND |
信号与电平格式#
- 传输0/1码元
- 高电平为1(3.3V),低电平为0(0V)
比特率与波特率#
- 比特率( bit/s,bps ):每秒钟传输二进制码元数量
- 波特率( symbol/s ):每秒钟传输有效信息码元的数量
- UART通信中,比特率=波特率
- 例 十六进制字符编码中,如果一秒传输1000个字符,每个字符对应一种唯一的电平信号,那么波特率就是1000symbol/s;由于一个十六进制字符要用四位二进制编码表示,那么它的比特率就是4000bit/s
异步通信(Async)#
不需等待某一方时钟信号同步后再发送,但通信内容较长时可能会因时钟不统一而错位。
如何解决错位问题呢?
可通过多次采样发现甚至解决错位问题(常见采样次数为8和16)
* 对于USART(即UART & SART)通信,这种SART具有时钟线同步策略。
收发模式#
仅接受、仅发送、收发双向。
链路传输#
UART协议#
- 点到点通信
- 仅有校验与帧格式
- 没有地址或ID的概念
- 没有冲突检测、避免和仲裁的说法
- 没有地址便没有滤波器与掩码
误码校验#
Why?
减少硬件偶然错误的信息编码被解析利用
但仍可能存在强干扰化境导致信道电压波动而改变电平
如何实现?
奇偶校验算法
- 对已发送的一段比特流进行计算,统计1的个数决定1bit的校验位
- 偶数个1为0,奇数个1为1,则是偶校验
- 奇数个1为0,偶数个1为1,则是奇校验
- 等效于异或操作,对前面的比特流进行异或
- 异或结果直接作为校验位,是偶校验
- 异或结果取反作为校验位,是奇校验
- 例 01010101,偶校验位为0,奇校验位为1
UART帧数据格式#
分段 | 长度 bit | 备注 |
---|---|---|
起始位 | 1 | 标志帧起始,必须为低电平0 |
数据位 | 5~8 | 先发低位后发高位,一般常见的都是8位,也就是一个字节. 也有7位的,相当于发ASCII码 |
校验位 | 1 | 将数据位通过奇偶校验算法得到的值放入该位 |
停止位 | 1 | 标志帧结束,必须为高电平1 |
空闲位 | % | 链路上不传输任何信息,必须为高电平1 |
通信方式#
阻塞式通信#
发送一段数据,在没有发送完所有数据之前,一直停留在此发送函数(可设定阻塞时间),这个过程中会阻塞别的程序运行;
中断式通信#
发送数据的过程在中断中进行,这个中断实际上是发送数据寄存器空的中断。
比如说你要发送10个字节的数据,那么这10个字节肯定不会一次性发送完,只能是一个一个字节地发送。每发送一个字节,发送数据寄存器就会有一个为空的标志,以提示可以发送下一个字节了,这样在发送数据的过程中CPU不会一直在等待,而是用中断来提示需要进行发送的时候再发送。不影响主程序进行。
DMA中断通信#
DMA(Direct Memory Access,直接内存访问)是一种计算机系统中用于数据传输的机制。它允许数据在外设和内存之间直接传输,而不需要CPU的介入,从而减轻了CPU的负担,提高了数据传输的效率。
举个栗子:
想象一下我们搬家的场景:你要把家里的一些东西从旧房子搬到新房子。在传统的情况下,你可能要亲自搬每一箱东西,把它们从旧房子搬到新房子。这就相当于CPU传统地处理数据传输的方式。 现在,有一支搬家队,他们专门负责搬家。你只需要告诉他们从哪里搬,搬到哪里,然后他们就会自己完成这项任务。而你可以利用这段时间去做其他事情,不需要亲自动手。这就有点类似于DMA的工作原理。 在计算机中,CPU通常会处理数据的传输工作,就像你亲自搬家一样。但有了DMA,就好比有了一支专门负责数据传输的队伍。CPU只需要告诉DMA从哪里搬,搬到哪里,然后就可以去处理其他任务了。DMA负责在外设和内存之间直接传输数据,而不需要CPU一直参与。
简而言之,DMA就像是一支搬家队伍,负责在不需要CPU亲自操劳的情况下完成数据传输任务,从而提高了系统的效率。
该内容摘抄自CSDNTM32CubeMx+HAL库:USART串口收发数据的三种方式
空闲中断#
当单片机的UART歇下来的时候,会把之前连续接收的内容自动拼装成一段有效的信息存到缓冲区中
DMA空闲中断#
代码示例#
阻塞式#
- 配置UART通信模块USART1
- 设置USART1为 asynchronous 模式
- 设置波特率为115200
- 设置数据长度为8bit
- 不需要奇偶校验
- 一位停止位
- 收发双向通信
1 | //声明变量以存储UART的收发数据 |
1 | HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); |
中断式#
- 注意要配置 使能 USART1 global interrupt
- 主函数负责发送,中断回调函数负责就收
- 随意收发长数据
1 | //声明变量以存储UART的收发数据 |
1 | while(1) { |
1 | //定义中断实函数 |
空闲中断#
- 实现UART随意发送定长数据,可接收一定长度以内的数据
- 主函数只负责发送,中断回调函数负责接收
代码在 中断式 的程序基础上修改
- 将
HAL_UART_Receive_IT(&huart1,ExData,5);
换成HAL_UARTEx_ReceiveToIdle_IT(&huart1, RxData, 5);
- 中断回调函数名称改为
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
DMA中断#
- 配置USART的DMA中断
- Cube使能UART中断
- 添加发送DMA
- 添加接收DMA
- DMA选项
- 普通模式:支持定时搬运或代码主动调用搬运
- 循环模式:一直搬运
- 搬运时,内存需要自增地址,否则一段话会搬运到同一个地方去造成覆盖
- FIFO(First In First Out)
- 当接收信息太多时,DMA可能搬不过来。FIFO队列可以先把来不及搬运的内容缓存下来排队,一个一个让DMA搬运,但代价是让信息的时效性降低
- 代价比较:信息丢失 vs 信息时效性差,根据这个来决定是否使用FIFO以及FIFO的长度
- 实现UART随意收发定长数据
- 主函数只负责发送,中断回调函数负责接收
代码在 中断式 的基础上进行修改(即 将 IT 换为 DMA )
- 将
HAL_UART_Transmit_IT(&huart1, TxData, 5);
换为HAL_UART_ Transmit_DMA(&huart1, TxData, 5);
- 将
HAL_UART_Receive_IT(&huart1, RxData, 5);
换成HAL_UART_Receive_DMA(&huart1, RxData, 5);
DMA空闲中断#
- UART随意收发定长数据,可接收一定长度内的数据
- 主函数只负责发送,中断回调函数负责接收
代码在 DMA中断 的基础上进行修改
- 将
HAL_UART_Receive_DMA(&huart1, TxData, 5);
换为HAL_UARTEx_ReceiveToIdle_DMA(&huart1, TxData, 5);
- 将中断回调函数名改为
HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
串口查询函数#
void HAL_UART_GetState(); //判断UART的接收是否结束,或者发送数据是否忙碌
食用方法:
1 | while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX) //检测UART1发送结束 |
我们可以查看一下 void HAL_UART_GetState();
的定义:
1 | HAL_UART_StateTypeDef HAL_UART_GetState(UART_HandleTypeDef *huart) |
让我们深入查看一下 HAL_UART_StateTypeDef
的定义,包含如下状态:
1 | HAL_UART_STATE_RESET:UART外设已重置但未初始化。 |
总结#
- 阻塞式
HAL_UART_Transmit(&huart1, TxData, 5, 1000);
HAL_UART_Receive(&huart1, RxData, 5, 1000);
- 中断式(使能中断)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){}
//接收回调函数HAL_UART_Transmit_IT(&huart1, TxData, 5);
HAL_UART_Receive_IT(&huart1, RxData, 5);
- 空闲中断(使能中断)
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){}
HAL_UART_Transmit_IT(&huart1, TxData, 5);
HAL_UARTEx_ReceiveToIdle_IT(&huart1, RxData, 5);
- DMA中断(配置DMA且使能中断)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){}
HAL_UART_Transmit_DMA(&huart1, TxData, 5);
HAL_UART_Receive_DMA(&huart1, RxData, 5);
- DMA空闲中断(配置DMA且使能中断)
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){}
HAL_UART_Transmit_DMA(&huart1, TxData, 5);
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RxData, 5);