本文通过串口收发实验来说明具体的串口的使用过程,以及与其他外设通过串口进行通信从而实现相关功能。
前言
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.
串口的几个重要的参数:
波特率,串口通信的速率
空闲,一般为高电平
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
数据位,发送数据帧,1为高电平,0为低电平。低位先行。
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)
打开keil5,这里我选择原子哥的跑马灯实验代码为基础进行拓展:
左侧Hardware添加.c和.h文件,地址是/hardware,确定添加。
首先,编写初始化串口2的函数:
1.配置时钟RCC开启USART和TX/RX对应的GPIO口
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//USART2挂载APB1总线
RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA挂载APB2总线
//对于哪个应用挂载哪个APB总线,可以根据代码自动补全功能快捷判断
2.初始化GPIO口
usart2对应引脚是PA2和PA3。RX引脚模式应该配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
首先需要设置结构体:观察GPIO结构体变量:
然后针对TX/RX进行不同的配置。如下:
GPIO_InitTypeDef GPIO_InitStructure;//声明一个结构体对象
//TX端口-PA2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;//这个对象的成员变量GPIO_Pin取值为pin2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//模式为复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHZ速度
GPIO_Init(GPIOA,&GPIO_InitStructure);
//RX端口-PA3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//这个对象的成员变量GPIO_Pin取值为pin3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//模式为浮空输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHZ速度
GPIO_Init(GPIOA,&GPIO_InitStructure);
3.进行串口的初始化
上面是对发送和接收端的端口的初始化,这里需要对串口初始化以开启串口功能。与GPIO类似,先实例化一个串口结构体对象,然后配置成员变量信息。
他的成员变量包括:
USART_BaudRate :串口通信使用的波特率 一般是9600或者是115200,这里给9600
USART_HardwareFlowControl :是否选择硬件流触发,一般这个也不选,所以选择无硬件流触发。
USART_Mode :串口的模式,发送模式还是接收模式,还是两者都有
USART_Parity :校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
USART_StopBits :停止位 有1、0.5、2位,我们这里选1位停止位
USART_WordLength :数据位 有8位和9位可以选择
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;//1位停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//八位数据位
USART_Init(USART2,&USART_InitStructure);
4.引入中断机制
串口初始化中断的目的是为了确保当串口接收到数据时能够及时地响应处理。如果不初始化中断,程序只能在轮询方式下检测串口是否有数据到来,这样会占用较多的CPU资源,并且可能会出现漏接数据的情况。而初始化中断后,当串口接收到数据时,系统就会立即进入中断服务程序进行处理,从而提高了串口数据的处理效率和准确性。
当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
初始化中断并配置好成员变量:
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);//开启串口2的中断接收
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel =USART2_IRQn;//选择串口2的中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//子优先级
NVIC_Init(&NVIC_InitStructure);
编写中断服务函数
当串口接收到数据时,会触发USART2的中断服务程序。在该函数内部,首先判断是否是RXNE标志位被置位,如果是则表示接收到了数据,将数据存储到RX_Data变量中,并设置Flag为1。然后通过调用USART_ClearITPendingBit函数清除RXNE标志位,以便下次能够正确地检测到串口是否有新的数据到来。
void USART2_IRQHandler(void){
if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据
{
u8 RX_Data=USART_ReceiveData(USART2);
u8 Flag=1;
USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
}
}
另外,原子哥编写的USART1串口函数是这样的:
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
这段代码是USART1的中断处理函数。当串口接收到数据时,会触发USART1的中断服务程序。在该函数内部,首先判断是否是RXNE标志位被置位,如果是则表示接收到了数据,将数据存储到Res变量中。然后通过对USART_RX_STA状态标志位进行操作,判断当前是否已经接收到完整的一帧数据,并将其存储到USART_RX_BUF缓冲区中。同时还需要注意接收错误的情况,如果出现错误需要重新开始接收。最后,如果支持操作系统,则需要在函数开始和结束时调用OSIntEnter和OSIntExit函数,以便操作系统能够正确地管理中断处理。
另外注意,实例化结构体时应该放在函数的一开始,否则会报错。并且,中断主要是用来接收数据的,串口发送数据不需要中断。
编写串口发送数据函数
void USART2_SendByte(USART_TypeDef * USARTx,uint8_t data){//串口收发每次都是一个字节,即8位数据
USART_SendData(USARTx,data);
//0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
while(!USART_GetFlagStatus(USARTx,USART_FLAG_TXE));
}
这段代码是用于向USARTx串口发送一个字节数据的函数。在函数中,首先调用USART_SendData函数将数据写入USARTx寄存器。然后通过循环等待的方式检查USART_FLAG_TXE标志位是否被置位,如果已经被置位则表示数据已经成功转移到移位寄存器,可以进行下一次发送。否则,需要继续等待直到TXE标志位被置位。该函数的作用是实现简单的串口发送功能。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "usart2.h"
/************************************************
ALIENTEK精英STM32开发板实验1
跑马灯实验
技术支持:www.openedv.com
淘宝店铺:http://eboard.taobao.com
关注微信公众平台微信号:"正点原子",免费获取STM32资料。
广州市星翼电子科技有限公司
作者:正点原子 @ALIENTEK
************************************************/
int main(void)
{
delay_init(); //延时函数初始化
LED_Init(); //初始化与LED连接的硬件接口
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Init_USART2();
while(1)
{
USART2_SendByte(USART2,0x08);
}
}
可以看到8被不断的发送给串口。
本文简单地介绍了stm32编写串口收发通信的有关方法,但没有涉及较复杂的应用,下一篇文章将深入串口的应用,使用stm32与esp8266通过串口完成通信。