STM32CubeMX系列教程8:配置工程模板(串口+不定长数据收发+DMA+IDLE中断+软中断)
文章目录摘要生成工程配置外设1、配置时钟与Debug2、配置串口与DMA3、配置定时器与中断配置时钟树配置工程设置点击`GENERATE CODE`生成工程修改源码配置软中断配置串口声明变量重定向printf实现IDLE中断处理封装DMA串口发送函数实现串口数据处理函数实现外部中断EXTI0回调函数添加IDLE中断处理函数到串口中断回调函数启动串口接收实现定时器中断回调添加用户代码编译下载工程异常
文章目录
摘要
记录一下如何配置一个好用的工程模板,工程模板启动串口1通信,串口使用DMA方式收发,使用IDLE中断实现不定长数据的接收发送。通过软中断实现串口数据收发处理的实时性,能够在接收到一帧串口数据后立刻触发串口数据处理中断。
相关工程源码已推送到Gitee:STM32-code
生成工程
这里以STM32F103C8T6单片机为例,其他型号的单片机配置方法大同小异。
配置外设
1、配置时钟与Debug
打开STM32CubeMX,选择一款单片机,使能SWD调试功能。启动外部高速时钟源。
2、配置串口与DMA
打开USART1配置界面,启动异步通信,设定波特率115200Bits/s,勾选串口1全局中断,添加USART1_RX
与USART1_TX
DMA请求。
3、配置定时器与中断
使能定时器TIM1功能,将定时器配置为1ms中断,根据查阅STM32数据手册,可知TIM1挂在APB2时钟总线上,最大值频率为72Mhz,所以这里我们将定时器时钟源设为内部时钟源,定时器分频值为72-1,分频后的频率为1Mhz,定时器重载值为1000-1;就可以将定时器中断配置为每经过1ms,进入一次定时器中断,使能定时器中断。
- 选择TIM1.。
- 启用内部时钟源。
- 设定定时器72分频,分频后定时器时钟频率为1Mhz。
- 定时器重载值1000次,每经过1ms进入一次中断。
- 使能自动重载。
使能定时器更新中断,某些单片机的定时器不能单独使能更新中断,只有全局中断TIMx global interrupt
,那么使能全局中断即可。
配置时钟树
进入时钟配置界面。设定外部晶振频率,时钟源和系统主频。
- 根据实际情况设定外部晶振频率,这里我使用的外部晶振频率是8MHz。
- 设定PLL时钟源为外部时钟源。
- 设定系统时钟来自于PLL时钟。
- 设定系统主频HCLK为72MHz,确定后,软件自动配置时钟树的各个分频值。
注意:画红框的地方表示挂接定时器TIM1的时钟线APB2时钟速度为72Mhz,定时器配置时设定的分频值与此相同。
配置工程设置
- 进入Project Manager界面。
- 先配置工程目录。
- 选择工程保存的位置。
- 设定工程名称。
- 选择开发环境,这里我使用的时Keil-MDK,所以选择MDK-ARM。
- 代码生成设置。
- 选则仅复制需要使用的库文件,如果选择第一项复制全部文件,后期编译代码时会比较慢。
- 将各个外设初始化函数放在单独的
.c/.h
文件中,如果不选,则所有外设的初始化函数都放在main.c
文件中。
点击GENERATE CODE
生成工程
修改源码
配置软中断
软中断的详细配置说明请转STM32CubeMX系列教程6:外部中断EXTI与软中断SWI查阅。
打开main.c
文件,在while(1)
循环前的/* USER CODE BEGIN 2 */
与/* USER CODE END 2 */
之间加入软中断配置语句,这里我使用外部中断线0作为软中断。并设定中断0的抢占优先级为最低,仅高于主循环。以免打断其他中断。如果对串口处理优先级有要求,可以调高。
/* USER CODE BEGIN 2 */
EXTI->IMR |= 1<<0;
HAL_NVIC_SetPriority(EXTI0_IRQn, 15, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
/* USER CODE END 2 */
配置串口
声明变量
打开usart.c
文件,在文件头/* USER CODE BEGIN 0 */
与/* USER CODE END 0 */
之间声明一些变量。
/* USER CODE BEGIN 0 */
volatile uint8_t rx1_len = 0; //接收一帧数据的长度
volatile uint8_t rec1_end_flag = 0; //一帧数据接收完成标志
uint8_t rx1_buffer[BUFFER_SIZE]={0}; //接收数据缓存数组
/* USER CODE END 0 */
打开usart.h
文件,在文件头/* USER CODE BEGIN Includes */
与/* USER CODE END Includes */
之间对刚才在usart.c
文件中声明的变量做外部声明,使别的.c
文件也可使使用这些变量,添加stdio.h
和string.h
两个头文件。
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#define BUFFER_SIZE 500
extern volatile uint8_t rx1_len; //接收一帧数据的长度
extern volatile uint8_t rec1_end_flag; //一帧数据接收完成标志
extern uint8_t rx1_buffer[BUFFER_SIZE]; //接收数据缓存数组
void Usart1_Handle(void);
void DMA_Usart1_Send(uint8_t *buf,uint8_t len);//串口发送封装
void Usart1_IDLE(void);
/* USER CODE END Includes */
BUFFER_SIZE
表示串口每帧数据的最大长度这里时500个字节。
后面的这三个函数,是后面将要实现的函数,这里先做外部声明,后面在usart.c
文件中实现。
重定向printf
在usart.c
文件中,在上面声明变量的后面,/* USER CODE END 0 */
的前面,写入下面的代码,重定向printf到串口1上。
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR=(uint8_t)ch;
return ch;
}
#endif
注意:在重定义fputc()
函数中,USART1->SR
和USART1->DR
两个寄存器分别为串口1的状态寄存器和数据寄存器,不同型号的单片机这两个寄存器的名称不通,会引起编译报错找不到该寄存器,可查阅单片机寄存器手册找到正确的寄存器名称进行修改,其他代码不用变。如果要重定向到其他串口,仅需修改这两个寄存器即可。
实现IDLE中断处理
在usart.c
文件下面,找到/* USER CODE BEGIN 1 */
与/* USER CODE END 1 */
之间,写入IDLE中断处理函数。
/* USER CODE BEGIN 1 */
void Usart1_IDLE(void) //USART1的IDLE接收
{
uint32_t tmp_flag = 0;
uint32_t temp;
tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
if((tmp_flag != RESET))//idle标志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止
temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数
rx1_len = BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
rec1_end_flag = 1; // 接受完成标志位置1
EXTI->SWIER |= 1<<0; //触发软中断
}
}
void Usart1_IDLE(void)中判断IDLE标志位是否为真,如果为真则代表已经接收完成一帧数据,清除标志位并进行后面的处理。
接收完成1帧数据后,向CPU发起中断,让CPU前往串口处理函数中处理接收到的数据。
封装DMA串口发送函数
在void Usart1_IDLE(void)
函数的下面,实现DMA发送的函数,供其他函数使用。
void DMA_Usart1_Send(uint8_t *buf,uint8_t len) //串口发送封装
{
if(len == 0) return;
HAL_StatusTypeDef sta = HAL_UART_Transmit_DMA(&huart1,buf,len);
if(sta != HAL_OK) //判断是否发送正常,如果出现异常则进入异常中断函数
{
Error_Handler();
}
}
实现串口数据处理函数
在void DMA_Usart1_Send(uint8_t *buf,uint8_t len)
函数下面,实现串口数据处理函数。这里是将收到的数据再发回去,不需要的可以删除。
void Usart1_Handle() //USART1对接收的一帧数据进行处理
{
DMA_Usart1_Send(rx1_buffer, rx1_len); //将接收到的数据重新发送回去。
rx1_len = 0;//清除计数
rec1_end_flag = 0;//清除接收结束标志位
HAL_UART_Receive_DMA(&huart1,rx1_buffer,BUFFER_SIZE);//重新打开DMA接收
}
实现外部中断EXTI0回调函数
在void Usart1_Handle()
函数下面,也是/* USER CODE END 1 */
的上面,实现终端回调,当CPU收到中断申请时,将会执行下面的函数。
void EXTI0_IRQHandler(void)
{
if(EXTI->PR & 1<<0){ //判断中断线是否为EXTI0
EXTI->PR |= 1<<0; //清除中断线
Usart1_Handle(); //调用串口中断处理函数
}
}
/* USER CODE END 1 */
在函数中判断是否为中断线0,如果是EXTI0,则清除中断标志,并执行串口数据处理函数。
添加IDLE中断处理函数到串口中断回调函数
打开stm32f1xx_it.c
文件,在文件头加入usart.h
头文件。否则不能调用usart.c
中的函数。
/* USER CODE BEGIN Includes */
#include "usart.h"
/* USER CODE END Includes */
在stm32f1xx_it.c
文件中找到void USART1_IRQHandler(void)
函数,在函数中加入Usart1_IDLE();
函数。
/* USER CODE BEGIN USART1_IRQn 0 */
Usart1_IDLE();
/* USER CODE END USART1_IRQn 0 */
启动串口接收
打开main.c
文件,在while(1)
循环前的/* USER CODE BEGIN 2 */
与/* USER CODE END 2 */
中,配置软中断语句的后面,加入启动串口接收与启动定时器的语句。
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断
HAL_UART_Receive_DMA(&huart1,rx1_buffer,BUFFER_SIZE); //启动串口DMA接收
HAL_TIM_Base_Start_IT(&htim1); //启动定时器1
实现定时器中断回调
打开tim.c
文件,在文件末尾加入中断回调函数。判断是否时TIM1触发了中断
/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim1) //判断是否时TIM1触发中断
{
}
}
/* USER CODE END 1 */
添加用户代码
在main.c
中声明一个全局变量task1
用作一个间隔执行的任务标志。并在main.h
中做外部声明。
在主循环中,判断task1
是否等于500;如果大于等于500,则清空task1
并执行printf("hello world\r\n")
语句。
if(task1>=500){
task1 = 0;
printf("hello world\r\n");
}
打开timc
文件,在定时器回调函数中写入task1
自增的语句。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim1) //判断是否时TIM1触发中断
{
task1++;
}
}
编译下载工程
相关工程源码已推送到Gitee:STM32-code
可以看到,系统复位后,单片机循环发出数据,并对接收到的数据进行返回。
异常问题与解决方法
系统复位后能够接收到单片机发送的数据,但串口助手向单片机发送数据后,单片机卡死。
- 检查串口接收是否打开。
- 检查IDLE中断是否打开。
- 检查IDLE中断处理函数是否加入串口中断回调函数。
- 检查软中断配置是否正常。
- 检查软中断处理函数名称是否与启动文件
startup_stm32f103xb.s
中对应的中断线回调函数名称相同。

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)