目录
1.引入
1.1 通信接口
通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统
通信协议:制定通信的规则,通信双方按照协议规则进行数据收发
名称 | 引脚 | 双工 | 时钟 | 电平 | 设备 |
---|---|---|---|---|---|
USART | TX、RX | 全双工 | 异步 | 单端 | 点对点 |
I2C | SCL、SDA | 半双工 | 同步 | 单端 | 多设备 |
SPI | SCLK、MOSI、MISO、CS | 全双工 | 同步 | 单端 | 多设备 |
CAN | CAN_H、CAN_L | 半双工 | 异步 | 差分 | 多设备 |
USB | DP、DM | 半双工 | 异步 | 差分 | 点对点 |
1.2 串口
具体一点的内容看嵌入式驱动开发中的UART子系统对串口的硬件简介。
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力
上图左边是USB转串口模块,中间是陀螺仪传感器模块(左边的是接串口、右边的引脚接I2C),右边是蓝牙串口模块
数据低位先行
- 简单双向串口通信有两根通信线(发送端TX和接收端RX)
- TX与RX要交叉连接
- 当只需单向的数据传输时,可以只接一根通信线
- 当电平标准不一致时,需要加电平转换芯片
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
- TTL电平:+3.3V或+5V表示1,0V表示0
- RS232电平:-3-15V表示1,+3+15V表示0
- RS485电平:两线压差+2+6V表示1,-2-6V表示0(差分信号)
串口参数及时序:
- 波特率:串口通信的速率,每秒传送的码元个数(二进制调制下就等同于每秒传递的比特数)
- 起始位:标志一个数据帧的开始,固定为低电平
- 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
- 校验位:用于数据验证,根据数据位计算得来
- 停止位:用于数据帧间隔,固定为高电平
串口时序:
2.USART
对应芯片参考手册的USART部分
2.1 简介
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器
USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
自带波特率发生器,最高达4.5Mbits/s。其实就是个分频器,波特率发生器对时钟进行一个分频,就能得到想要的波特率时钟,在该时钟下进行收发,就是指定的通信波特率。
可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
可选校验位(无校验/奇校验/偶校验)
支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
STM32F103C8T6 USART资源: USART1(APB2总线上的)、 USART2(APB1总线上的)、 USART3(APB1总线上的)
2.2 框图
来自参考手册
1. 数据接口模块
- TX/RX:分别为串行数据发送和接收引脚,是USART的主要外部接口,用于与外部设备通信。
- nRTS/nCTS:硬件流控引脚,分别表示请求发送(Request to Send)和清除发送(Clear to Send)信号。如果发送数据发送的太快,接收方来不及接收,就会发生数据丢失或覆盖的情况,为解决这种情况就靠这两根引脚。nRTS:你能不能发数据给我(接对方的nCTS);nCTS:我能不能发数据给你(接对方的nRTS)
- IrDA模块:支持红外通信的编码和解码模块,可通过配置CR3寄存器启用。
2. 发送器部分
发送数据寄存器 (TDR)
- 用于存储需要发送的数据,写操作通过CPU或DMA完成。
- 数据从TDR被传输至发送移位寄存器,开始按位输出到TX引脚。
发送移位寄存器
- 数据从TDR进入移位寄存器,并以配置的波特率逐位发送。
- 发送时序由USART_BRR寄存器提供的分频时钟控制。
发送控制逻辑
- 控制寄存器CR1中的TE位:使能发送功能。
- 状态寄存器 (SR)中的TXE中断:当TDR为空时触发,提示CPU或DMA准备下一个数据。
3. 接收器部分
接收移位寄存器
- 从RX引脚按位接收数据,并将其组合成完整的数据字节。
- 数据组合完成后传递到接收数据寄存器 (RDR)。
接收数据寄存器 (RDR)
- 接收的数据存储在RDR中,可通过CPU读取。
- 数据从RDR读取后,触发RXNE中断,提示CPU完成一次数据接收。
接收控制逻辑
- 控制寄存器CR1中的RE位:使能接收功能。
- 状态寄存器 (SR)中的RXNE标志:指示RDR中有新数据可供读取。
4. 控制逻辑模块
中断控制
中断源 (CR1, CR2, CR3):
-
- CR1中的TXEIE、RXNEIE等控制发送和接收中断。
- CR2控制错误中断,如帧错误和过载错误。
- CR3用于流控中断,如CTS中断。
状态寄存器 (SR):
-
- 包括多种标志位,如TXE(发送数据寄存器空)、RXNE(接收数据寄存器非空)和PE(奇偶校验错误)。
5. 时钟生成模块
USART_BRR寄存器
- 波特率寄存器,用于配置发送和接收的波特率。
- 波特率计算公式:
其中,DIV_Mantissa
表示整数部分,DIV_Fraction
表示小数部分。 - 波特率由系统时钟提供,并通过分频器(/16)生成。
6. 模式配置模块
CR1、CR2、CR3寄存器
- CR1:基本功能配置,例如使能USART、发送、接收等。
- CR2:帧格式设置(停止位长度、时钟使能)。
- CR3:高级功能,例如DMA支持、流控和红外功能。
7. DMA支持
- 支持通过DMA直接操作TDR和RDR寄存器,减轻CPU负担。
- DMA触发器:根据TXE(发送)和RXNE(接收)状态,触发DMA完成数据传输。
8. 数据传递路径
- 发送路径:
-
- CPU/DMA将数据写入TDR。
- 数据进入发送移位寄存器。
- 移位寄存器按照波特率逐位发送到TX引脚。
- 接收路径:
-
- RX引脚接收到串行数据。
- 数据进入接收移位寄存器,组合成数据字节。
- 数据传递至RDR,等待CPU读取。
2.3 基本机构图
注意:无论是发送还是接收,数据都是低位先行。
2.4 数据帧
字长设置:
在时钟的上升沿进行数据的读取。对于9位字长,一般第9位会选择为校验位,毕竟前面8位刚好就是1字节,uint8。同理对于8位字长的数据,一般最后一位不选择为校验位。
停止位配置:
stm32的停止位可以配置停止位长度为0.5、1、1.5、2,就是停止位是时长不同。
起始位侦测采样:
对于输入的数据,要对其进行采用,最后就时钟的上升沿采用的时候刚好对于数据位的正中间,防止因为一些噪声等导致数据位有波形,造成采洋不准。
而为了实现这部分的功能,输入的这部分电路对采样时钟进行了细分,会以波特率的16倍频率进行采样(即在一位的时间里可以对一位的数据进行16次采样)。
而上图就是对起始位进行16次的采样,出现下降沿时,采样采到0,而后面还会继续采样,防止下降沿是因为某些抖动而导致的,并不是代表起始位。当在每3次采样里至少有2个0,就代表这个位确实是起始位。
数据采样:
这部分就是前面提到的,为了保证数据的正确,尽量在数据位的中间对该为数据进行采样。
2.5 波特率发生器
发送器和接收器的波特率由波特率寄存器BRR里的DIV确定
计算公式:波特率 = fPCLK2/1 / (16 * DIV)
为什么还要除以16???上面讲解数据采样的时候有说过,一位数据的采样次数是16次,也就是16个时钟的时间。因此 fPCLK2/1 /DIV取到的波特率还得除以16,才是实际上每秒传输码元的个数。
就当成时钟频率去理解就行,1位数据需要16个时钟,DIV分频后的时钟频率还得再去除以16,做到和每一位的数据相对应。
2.6 数据包
把多个字节(数据帧)给打包一起发送
2.6.1 数据模式
HEX模式/十六进制模式/二进制模式:以原始数据的形式显示
文本模式/字符模式:以原始数据编码后的形式显示
2.6.2 HEX数据包
这种为了避免载荷数据和包头包尾出现数据重复的现象,可以使用载荷数据限制大小、载荷数据限制长度和增加包头包尾的数量来解决。
至于可变包长,更适合载荷数据不会和包头包尾数据重复的情况下使用。
2.6.3 文本数据包
2.6.4 HEX数据包接收
2.6.5 文本数据包接收
3.结构体和相关API
3.1 结构体
typedef struct
{
uint32_t USART_BaudRate; /*!< This member configures the USART communication baud rate.
The baud rate is computed using the following formula:
- IntegerDivider = ((PCLKx) / (16 * (USART_InitStruct->USART_BaudRate)))
- FractionalDivider = ((IntegerDivider - ((u32) IntegerDivider)) * 16) + 0.5 */
uint16_t USART_WordLength; /*!< Specifies the number of data bits transmitted or received in a frame.
This parameter can be a value of @ref USART_Word_Length */
uint16_t USART_StopBits; /*!< Specifies the number of stop bits transmitted.
This parameter can be a value of @ref USART_Stop_Bits */
uint16_t USART_Parity; /*!< Specifies the parity mode.
This parameter can be a value of @ref USART_Parity
@note When parity is enabled, the computed parity is inserted
at the MSB position of the transmitted data (9th bit when
the word length is set to 9 data bits; 8th bit when the
word length is set to 8 data bits). */
uint16_t USART_Mode; /*!< Specifies wether the Receive or Transmit mode is enabled or disabled.
This parameter can be a value of @ref USART_Mode */
uint16_t USART_HardwareFlowControl; /*!< Specifies wether the hardware flow control mode is enabled
or disabled.
This parameter can be a value of @ref USART_Hardware_Flow_Control */
} USART_InitTypeDef;
\1. USART_BaudRate
作用:配置USART通信的波特率(每秒比特传输速率)。
计算公式:
-
PCLKx
是外设时钟频率。BaudRate
是配置的波特率。- 波特率寄存器
USART_BRR
的整数部分由USARTDIV
的整数值构成,小数部分由其余值通过四舍五入计算得出。
取值范围:常用波特率值(如
9600
,19200
,38400
,115200
等)。实际取值需确保PCLKx
能支持目标波特率。
\2. USART_WordLength
作用:指定每帧传输或接收的数据位数(包括有效数据位和可选的校验位)。
可取值:
-
USART_WordLength_8b
:8位数据帧(默认)。USART_WordLength_9b
:9位数据帧(通常用于特殊协议或需要扩展数据位时)。
注意:
-
- 如果使用奇偶校验,帧中最高位(第8或第9位)被用作校验位。
\3. USART_StopBits
作用:设置每帧发送结束时的停止位数,用于标识帧结束。
可取值:
-
USART_StopBits_1
:1个停止位(默认)。USART_StopBits_0.5
:0.5个停止位。USART_StopBits_2
:2个停止位(用于慢速通信或特定协议)。USART_StopBits_1.5
:1.5个停止位(特定场景)。
注意:停止位设置需与接收设备保持一致。
\4. USART_Parity
作用:配置是否启用奇偶校验以及校验模式(奇校验或偶校验)。
可取值:
-
USART_Parity_No
:无校验(默认)。USART_Parity_Even
:偶校验。USART_Parity_Odd
:奇校验。
注意:
-
- 启用校验后,帧中最高有效位会被占用,用于传输校验位。
- 校验位由硬件自动生成,无需用户干预。
\5. USART_Mode
作用:指定USART的工作模式(发送、接收或两者均启用)。
可取值:
-
USART_Mode_Rx
:仅接收模式。USART_Mode_Tx
:仅发送模式。USART_Mode_Tx_Rx
:同时启用发送和接收模式。
典型应用:
-
- 仅接收模式常用于传感器数据读取。
- 同时启用发送和接收模式适用于双向通信(如UART调试、RS232通信等)。
\6. USART_HardwareFlowControl
作用:设置硬件流控制模式,用于控制数据流的启停(基于RTS和CTS信号)。
可取值:
-
USART_HardwareFlowControl_None
:无硬件流控(默认)。USART_HardwareFlowControl_RTS
:仅启用RTS流控(发送设备请求发送时使能)。USART_HardwareFlowControl_CTS
:仅启用CTS流控(接收设备允许发送时使能)。USART_HardwareFlowControl_RTS_CTS
:同时启用RTS和CTS流控。
注意:硬件流控适用于需要精确控制数据流的场景,如避免数据溢出或丢失。
3.2 API
3.2.1 初始化相关函数
void USART_DeInit(USART_TypeDef* USARTx)
- 作用:将指定USART外设的寄存器恢复为默认复位值。
- 使用场景:在重新配置USART外设之前使用,确保状态清零。
- 示例:
USART_DeInit(USART1); // 将USART1寄存器重置为默认值
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
- 作用:根据
USART_InitTypeDef
结构配置USART的波特率、数据位、停止位、校验、工作模式和硬件流控。 - 使用场景:初始化USART通信。
- 示例:
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Tx_Rx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1, &USART_InitStructure);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct)
作用:将
USART_InitTypeDef
结构初始化为默认值。默认值:
-
- 波特率为
9600
。 - 8位数据帧。
- 1个停止位。
- 无校验。
- 收发模式均启用。
- 无硬件流控。
- 波特率为
示例:
USART_InitTypeDef USART_InitStructure;
USART_StructInit(&USART_InitStructure); // 初始化为默认值
USART_Init(USART1, &USART_InitStructure);
3.2.2 时钟相关函数
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct)
- 作用:配置USART同步模式下的时钟设置(如极性、相位等)。
- 使用场景:仅在同步模式下(如SPI通信)配置时钟信号。
- 示例:
USART_ClockInitTypeDef USART_ClockInitStructure;
USART_ClockInitStructure.USART_Clock = USART_Clock_Enable;
USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
USART_ClockInitStructure.USART_CPHA = USART_CPHA_1Edge;
USART_ClockInitStructure.USART_LastBit = USART_LastBit_Enable;
USART_ClockInit(USART1, &USART_ClockInitStructure);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct)
- 作用:将
USART_ClockInitTypeDef
结构初始化为默认值(同步模式时钟禁用)。 - 示例:
USART_ClockInitTypeDef USART_ClockInitStructure;
USART_ClockStructInit(&USART_ClockInitStructure);
USART_ClockInit(USART1, &USART_ClockInitStructure);
3.2.3 控制相关函数
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)
- 作用:使能或禁用USART外设。
- 示例:
USART_Cmd(USART1, ENABLE); // 启用USART1
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)
作用:配置指定的USART中断。
中断类型:
-
USART_IT_TXE
:发送数据寄存器空中断。USART_IT_RXNE
:接收数据寄存器非空中断。USART_IT_PE
:奇偶校验错误中断。
示例:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 启用接收非空中断
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState)
作用:使能或禁用USART的DMA请求。
参数:
-
USART_DMAReq_Tx
:发送数据DMA请求。USART_DMAReq_Rx
:接收数据DMA请求。
示例:
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); // 启用发送DMA
3.2.4 数据收发函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
- 作用:发送一个数据帧。
- 示例:
USART_SendData(USART1, 'A'); // 发送字符 'A'
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送完成
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
- 作用:接收一个数据帧。
- 示例:
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
uint16_t data = USART_ReceiveData(USART1); // 接收数据
}
3.2.5 状态和中断相关函数
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
作用:获取指定的USART标志状态。
常见标志:
-
USART_FLAG_TXE
:发送数据寄存器空标志。USART_FLAG_RXNE
:接收数据寄存器非空标志。USART_FLAG_ORE
:溢出错误标志。
示例:
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
// 接收到数据
}
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG)
- 作用:清除指定标志位(如溢出错误等)。
- 示例:
USART_ClearFlag(USART1, USART_FLAG_ORE); // 清除溢出错误标志
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
- 作用:检查指定中断是否发生。
- 示例:
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
// 接收非空中断发生
}
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT)
- 作用:清除中断挂起标志。
- 示例:
USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除接收非空中断挂起标志
4.实验
CH340 USB转串口的原理图
4.1 串口发送
User:
Hardware:
其中对于发送数据,仍然要注意的是低位数据开始传送!!!!这里是数据帧字长为8,从低位开始传输,这个其实也不用太在意,因为调用提供的USART_SendData
函数的时候内部已经帮我们做好了。
但是当我们要传输数字的时候,需要注意将其每一位数都给拆开然后再挨个传输出去。比如你要发送21(十进制),那么就要先十位的2,在发送个位的1,就是要把每一位数都给转换成一个数据帧,在这里也就是uint8_t:
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
/**
* 函 数:次方函数(内部使用)
* 返 回 值:返回值等于X的Y次方
*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; //设置结果初值为1
while (Y --) //执行Y次
{
Result *= X; //将X累乘到结果
}
return Result;
}
/**
* 函 数:串口发送数字
* 参 数:Number 要发送的数字,范围:0~4294967295
* 参 数:Length 要发送数字的长度,范围:0~10
* 返 回 值:无
*/
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++) //根据数字长度遍历数字的每一位
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字
}
}
假设咱们调用了Serial_SendNumber(123,3);
123是要传输的十进制的数(uinit32_t),得将每一位都给拆开成一个数据帧,也就是uint8_t a = 1、uint8_t b= 2、uint8_t c= 3,将a、b、c挨个传输。 逐位拆解数字并以 ASCII 码的形式发送。
这时候就可以采用取余的方式获取到低位。参2就是传这个数字的位数,这里是3。
通过调用Serial_Pow
函数去对数字的不相关的位数进行去除,比如第一次for时,Serial_Pow
传的参2是2,最后返回是100,123/100会得到1(注意是int的形式!!),1%10就可以知道百位数就是1了。其余的位数也是同理。
调用 Serial_SendNumber(123, 3)
的具体过程:
第1次循环(
i=0
):-
Serial_Pow(10, 3-0-1) = Serial_Pow(10, 2) = 100
。123 / 100 = 1
,1 % 10 = 1
,对应字符'1'
。- 发送
'1'
。
第2次循环(
i=1
):-
Serial_Pow(10, 3-1-1) = Serial_Pow(10, 1) = 10
。123 / 10 = 12
,12 % 10 = 2
,对应字符'2'
。- 发送
'2'
。
第3次循环(
i=2
):-
Serial_Pow(10, 3-2-1) = Serial_Pow(10, 0) = 1
。123 / 1 = 123
,123 % 10 = 3
,对应字符'3'
。- 发送
'3'
。
4.2 串口发送 + 接收
User:
Hardware:
4.3 HEX数据包收发
User:
Hardware:
需要注意的是:
32中中断是可以嵌套的。如果在OLED显示数据的时候,中途又发生了中断进行接收数据,对Serial_RxPacket数组进行写入,就可能会导致输出的前一半是上次的数据,后一半是新来的数据。这个需要注意。
但是这种HEX数据包多是用于传感器的各个独立数据、陀螺仪的XYZ数据等,它们相邻的数据包都是具有连续性的,即使相邻数据混在一起了也没啥关系。如果想避免这种问题,可以在中断处理函数中添加一个标志位,在上一个数据发送完后再标志为可以接收。
4.4 文本数据包收发
User:
Hardware:
当在发送区输入指定的数据包点击发送后,会调用到中断函数USART1_IRQHandler
,根据数据包进行解析,将载荷数据放置到全局数组Serial_RxPacket
中。然后在main函数中通过strcmp
函数去对该数组和对应的指令进行比较,执行相应的操作:
stm32通过Serial_SendString
函数将是LED否开启成功的状态通过串口传输到串口助手,会在接收区显示。
5.FlyMcu串口下载&STLINK Utility
5.1 FlyMcu
通过串口给stm32下载程序。
需要先将设置成:系统存储器模式。
红色是跳线帽地接法,之后记得按一次复位键。
需要注意的是这个烧录软件只支持USART1下的引脚。
打开keil中的工程,编译生成一个.hex文件
之后重新编译一下:
就可以看到串口下载所需要的文件
之后打开烧录助手,进行相关配置,选择.hex文件,开始编程进行烧录即可。
之后将32的跳线帽重新接回主闪存启动模式,点击复位键,就可以看到程序的运行现象。