STM32串口通信
串口通信在STM32开发中有着非常实用的功能,作为最基础的通信模块和调试模块,学好STM32串口接受和发送有着重要的意义。
本文可以看作是江科大STM32入门教程的学习笔记。
一、串口通信
通信接口
通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统
(如何理解通信可以扩展硬件系统:借助通信接口,可以实现单片机与单片机,单片机与电脑,单片机与其他模块之间的通信)
通信协议:制定通信的规则,通信双方按照协议规则进行数据收发
PS:
- 全双工意味着有两根数据线
- 异步意味着不需要时钟线
- 单端信号意味着引脚电平是对地(GND)的电压差,所以需要共地
- 点对点意味着设备之间是面对面通信,不能挂载其他设备(可以理解为一对一和一对多的区别)
硬件电路
- TX、RX、GND必须接
- TX、RX要交叉连接
- 设备1和设备2都有独立供电时VCC可不接,否则要接
电平标准
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
- RS232电平逻辑与通常认知的0V以上为逻辑1相反
串口参数及时序
数据帧有10位和11位两种,区别是数据位末尾有无校验位(数据位是8位还是9位)
-
10位数据帧:1位起始位+8位数据位+1位停止位
-
11位数据帧:1位起始位+9位数据位(8位有效载荷+1位校验位)+1位停止位
-
波特率:串口通信的速率(串口是异步通信,没有时钟线,所以要约定好通信的速率,是保证双方接收发送无误的重要参数。例如1000bps表示1s发送1000位,1位1ms,通信双方就会每隔1ms来同步接收或发送数据)
-
起始位:标志一个数据帧的开始,固定为低电平(起始信号:高电平===>低电平)
-
数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行(例如数据帧:11110000,则实际数据是00001111)
-
校验位:用于数据验证,根据数据位计算得来
-
停止位:用于数据帧间隔,固定为高电平(停止信号:低电平===>高电平)
二、STM32串口通信
- USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
- 自带波特率发生器,最高达4.5Mbits/s
- 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
- 可选校验位(无校验/奇校验/偶校验)
STM32F103C8T6 USART资源: USART1、 USART2、 USART3
- 发送数据寄存器(TDR)和接收数据寄存器(RDR)占用同一个内存地址。程序中只表现为一个寄存器数据寄存器(DR)。
- 发送流程:发送数据进入TDR,然后检测发送移位寄存器是否有数据正在移位(是否有数据正在发送),如果没有,数据从TDR进入发送移位寄存器,此时会置一个标志位:TXE(发送数据寄存器空),说明TDR没有数据了,此时就可以往TDR写入新的数据。同时在发送控制器的驱动下发送移位寄存器会向右一位一位的移出数据到TX引脚。ps:标志位TXE只代表数据从TDR进入发送移位寄存器,并不代表该数据发送完成。
- 接收流程:在接收控制器的驱动下从RX引脚读取数据,读取完一字节数据后,数据进入RDR,此时RXNE(接收数据寄存器非空)置1,检测到RXNE置1后,读走数据。
波特率发生器
- 发送器和接收器的波特率由波特率寄存器BRR里的DIV确定
- 计算公式:波特率 = fPCLK2/1 / (16 * DIV)
- 例如:波特率为9600bps
9600=72M/(16*DIV)
==>DIV= 468.75
所以USART_BRR:0001 1101 0100.1100
ps:使用库函数可以直接写不需要计算
电路原理图
初始化
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//UART1 TX---PA9
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//UART1 RX---PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
- 初始化总结:
1.开启USART1和GPIO的时钟(需要注意你所用的外设是哪个外设总线的设备,必须对应正确)
2.初始化GPIO引脚(模式:输入/输出;引脚号)
3.初始化USART1结构体(波特率;有无硬件流控;输入/输出模式;校验位;停止位;字长)
4.开启RXNE标志位到NVIC的输出
5.设置中断优先级的分组方式
6.NVIC设置(中断通道;优先级)
7.USART1使能
发送函数
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //byte写入发送数据寄存器(TDR)
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
//等待TXE标志位置1
}
- 发送函数总结:
1.void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{
assert_param(IS_USART_ALL_PERIPH(USARTx));
assert_param(IS_USART_DATA(Data));
USARTx->DR = (Data & (uint16_t)0x01FF);
}
(uint16_t)0x01FF 是一个16位的无符号整数,其二进制表示为 0000 0001 1111 1111 ;所以该语句是将 Data 的低9位写入到 USART 的数据寄存器中,因为对于DR寄存器来说,只有低9位(8:0)是有用的。也可以理解为清除Data的高7位。
2.等待发送数据寄存器空标志位(TXE)置1,表示数据全部进入发送移位寄存器,可以向外移位发送了。标志位不用手动清除,下一次send data时会自动清除。
中断接收函数(中断函数)
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
- 中断接收函数总结:
1.RXNE标志位置1表示接收数据寄存器非空,即接收到数据,提示我们需要处理接收的数据。
2.uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
{
assert_param(IS_USART_ALL_PERIPH(USARTx));
return (uint16_t)(USARTx->DR & (uint16_t)0x01FF);
}
接收数据函数实质也是对DR寄存器的操作,因为前面说过发送数据寄存器(TDR)和接收数据寄存器(RDR)内存地址是同一个,在程序中都表现为数据寄存器(DR),区别在于发送是给DR赋值,接收是读取DR。
3.手动清除一下RXNE标志位
总结
因为入门的时候是跟着江科大的视频学习的,就想着边学边写一个学习笔记,方便字节会过来复习。
内容基本和视频内容一致,个别地方加入了自己的理解。
欢迎指正。