《STM32从零开始学习历程》@EnzoReventon
USART串口通讯实验篇
最近开始接触了STM32F4xx系列单片机,对于我这个从零开始学习的小白来说,可谓困难重重,我是跟着“正点原子”和“野火”这两个STM32培训机构来学习的,开发板买的是“正点原子”F4系列,视频在之前学习过程中主要看的是“正点原子”的,现在主要参考“野火”的视频,个人感觉“正点原子”的程序框架和“野火”的视频比较适合我,因此两者结合了一下,各取所长吧。
正因为在学习过程中困难重重,才让我下定决心把自己所遇到的困难以及解决困难的方法全部记录下来,也算是学习笔记,与大家分享,有不足之处还请各位大佬多多指教。
在开始之前,我觉得资料非常重要,不管是官方的还是非官方的,一定要保证这些资料在你电脑能够最快开启的位置。
1. 实验准备
软件:Keil μVision5 v5.33(MDK5),串口助手XCOM V2.6
环境:Windows10 Enterprise x64
芯片:STM32F406ZGT6
设备:正点原子STM32F4探索者开发板
仿真器:ST-Link
参考手册:
[野火EmbedFire]《STM32库开发实战指南——基于野火霸天虎开发板》
[正点原子]STM32F4开发指南-库函数版本_V1.2
[ST]《STM32F4xx中文参考手册》
[ST]《STM32F407xx》
一些usart,串口通讯的理论知识在这里就不详细介绍了。 有需要了解同学可以看看其他文章,他们讲的很详细很到位。
2. 实现功能
通过串口助手给STM32发送字符串,STM32能够将所接收到的字符串返回。
3. 硬件设计流程
- 在明确需要实现功能的条件下,对硬件端口进行选择。本实验中选择USART1进行通讯实验。
查阅相关文档:USART1 TX RX端口可以使用PA9/PA10或者PB6/PB7。
其余串口通讯引脚配置如下表3-1所示:
表3-1 STM32F407ZGT6芯片USART引脚表
在本实验中选择USART1串口,复用PB6/PB7为通讯引脚。
正点原子与野火官方教程都为USART1 PA9/PA10为引脚。
查询硬件原理图,找到USART1串口位置,以及引脚接口的信息。
在开发板上找到U17,发现USART1串口就是USB232口,使用数据线将其与电脑连接即可。
如果需要使用其他串口,也是如此,查阅硬件手册,找到相应的串口即可。
在正点原子开发板上默认将PA9(TX)与RXD / PA10(RX)与TXD相连接了。如下图所示:
PA9 PA10 与 TX RX连接图 使用跳帽连接
由于本文是使用USART1 PB6/PB7为引脚,所以需要使用杜邦线将PB6(TX)与RXD / PB7(RX)与TXD相连接。如下图所示:
PB6 PB7 与 TX RX连接图 使用杜邦线连接
至此,通讯口与引脚的接线已经完成。
- 将ST-LINK , USB口与电脑连接即可。
4. 程序设计流程
对于程序而言,正点原子与野火的案例程序我都进行了分析,个人感觉对野火的案例程序比较好理解(可能是我比较菜,正点原子的案例程序还在进一步消化中),因此本文的程序设计是基于野火的案例程序。
- 串口初始化时钟使能:RCC_APBxPeriphClockCmd();
GPIO初始化时钟使能:RCC_AHBxPeriphClockCmd(); - 引脚复用映射:GPIO_PinAFConfig();
- GPIO端口模式配置:GPIO_Init();
- 串口参数初始化:USART_Init();
- 开启中断并且初始化:NVIC_Init();记得要在main中对NVIC进行分组配置。
- 串口使能:USART_Cmd();
- 编写中断服务函数:USARTx_IRQHandler();
在中断服务函数中我们通常会用到一些判断标志位,有了这些判断标志位对我们设计程序会有很大的帮助。如下表4-1所示。
表4-1 一些常用的中断标志位
中断事件 | 事件标志 | 使能控制位 |
---|---|---|
发送数据寄存器为空 | TXE | TXEIE |
CTS标志 | CTS | CTSIE |
发送完成 | TC | TCIE |
准备好读取接收到的数据 | RXNE | RXNEIE |
检测到上溢错误 | ORE | RXNEIE |
检测到空闲线路 | IDLE | IDLEIE |
奇偶校验错误 | PE | PEIE |
断路标志 | LBD | LBDIE |
多缓冲通信中的噪声标志、上溢错误和帧错误 | NF/ORE/FE | EIE |
- 串口数据收发:void USART_SendDate(); uint16_t USART_ReceiveDate();
- 串口传输状态获取:FlagStatus: USART_GetFlagStatus(); void USART_ClearITPendingBit()
5. 代码设计与分析
1.初始化函数
void uart_init(u32 bound)
{
============================================================
//GPIO端口设置 配置结构体名称
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
============================================================
//使能GPIOA时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
//使能USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
============================================================
//串口1对应引脚复用映射 GPIOB6复用为USART1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_USART1);
//GPIOB7复用为USART1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_USART1);
//USART1端口配置
//GPIOA2与GPIOA3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
//复用功能
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
//速度50MHz
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//推挽复用输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
//上拉
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
//初始化PA9,PA10
GPIO_Init(GPIOB,&GPIO_InitStructure);
============================================================
//USART1 初始化设置
//波特率设置
USART_InitStructure.USART_BaudRate = bound;
//字长为8位数据格式
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
//一个停止位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
//无奇偶校验位
USART_InitStructure.USART_Parity = USART_Parity_No;
//无硬件数据流控制
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
//收发模式
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
//初始化串口1
USART_Init(USART1, &USART_InitStructure);
============================================================
//使能串口1
USART_Cmd(USART1, ENABLE);
//开启相关中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
============================================================
//Usart1 NVIC 配置
//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
//子优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;
//IRQ通道使能
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//根据指定的参数初始化NVIC寄存器、
NVIC_Init(&NVIC_InitStructure);
}
- 配置字符发送函数
注释:SendByte函数通过调用库函数USART_SendData()用来向指定的USART中发送一个ASCII码值字符,两个形参一个是USART,另一个是待发送的字符。
USART_GetFlagStatus()用来获取USART发送完成事件(USART_FLAG_TXE),当检测到发送完成则跳出当前while循环。
void USART_SendByte(USART_TypeDef * pUSARTx, uint8_t ch)
{
USART_SendData(pUSARTx,ch);
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE) == RESET)
{ }
}
- 配置字符串发送函数
USART_SendString是一个用来发送字符串的函数,其实就是不断调用USART_SendByte函数来一个一个的发送字符,直到检测到有空格“\0”停止发送。
最后同样调用USART_GetFlagStatus函数,来检测所有数据是否已经发送完毕,发送给完毕后跳出当前循环。
void USART_SendString(USART_TypeDef * pUSARTx, char *str)
{
unsigned int k = 0;
do{
USART_SendByte(pUSARTx, *(str + k));
k++;
} while(*(str + k) != '\0');
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC) == RESET)
{ }
- 编写中断服务函数
配置好中断之后,当USART接收到数据就会执行中断服务函数USART1_IRQHandler。
在中断服务函数中,同样通过使用USART_GetFlagStatus函数来获取USART是否接收到了数据这个标志位,如果确实接收到了,用USART_ReceiveData把接收到的数据存储在temp中,并使用USART_SendData()发送给原设备。
void USART1_IRQHandler(void) //串口1中断服务程序
{
uint8_t temp;
if ( USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET)
{
temp = USART_ReceiveData(USART1);
USART_SendData(USART1,temp);
}
}
- 主函数
主函数很简单,只要调用子函数即可,配置波特率为115200.
int main(void)
{
//设置系统中断优先级分组2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//延时初始化
delay_init(168);
//串口初始化波特率为115200
uart_init(115200);
USART_SendString(USART1, "This is a test!");
while(1)
{ }
}
6. 调试
将程序编译下载到设备,打开XCOM串口调试软件。
首先会出现主程序中发送的"This is a test!"。
在发送窗口中输入字符串,点击发送。
至此,试验结束。
7. 小结
这个实验不难,新手入门还是建议代码一行一行的敲一下,了解下底层的知识。
其次,需要学会查阅各种手册,不管是硬件还是软件,都要进行充分的研读。
我在这次实验完成之前,没有充分查阅硬件手册,导致PB6/PB7配置好了一直接收不到数据,后来发现没有拔掉PA9/PA10的跳帽,也没有将USART1 的TX RX与PB6/PB7相互连接,以为仅需要编写代码就可以实现串口之间的切换,实践证明,玩转嵌入式是需要软硬结合的!
此外,本人还在学习中,如有不足请大佬们多多指教,如有侵权立删!