STM32CubeMX系列教程8:配置工程模板(串口+不定长数据收发+DMA+IDLE中断+软中断)
文章目录
- 摘要
- 生成工程
- 配置外设
- 1、配置时钟与Debug
- 2、配置串口与DMA
- 3、配置定时器与中断
- 配置时钟树
- 配置工程设置
- 点击`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){ //判断中断线是否为EXTI0EXTI->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
中对应的中断线回调函数名称相同。
STM32CubeMX系列教程8:配置工程模板(串口+不定长数据收发+DMA+IDLE中断+软中断)相关推荐
- 嵌入式Linux 串口编程系列3——通过VTIM、VMIN、select实现串口不定长数据接收功能
上一篇文章中,我们详细分析了VTIM和VMIN的功能, <嵌入式Linux 串口编程系列2--termios的VMIN和VTIME深入理解> 也明白了这两个参数设计的初衷和使用方法,接下来 ...
- java串口设备中断_利用DMA双缓冲或半完成中断实现串口不定长数据的接收
在<HAL版本DMA循环模式串口数据收发>中介绍了利用DMA循环模式进行串口数据的收发,STM32F4xx的DMA还提供了双缓冲的功能,采用双缓冲模式,可以在一个DMA完成接收后,对其缓冲 ...
- STM32从零到一,从标准库移植到HAL库,UART串口1以DMA模式收发不定长数据代码详解+常见问题 一文解析
前言 本文的参考资料 感谢提供标准库版本的CSDN同学:这两篇文章至少是我看过的最详细的标准库配置DMA版本.而且代码实测稳定能用. STM32 | DMA配置和使用如此简单(超详细)_...| .. ...
- STM32单片机串口空闲中断+DMA接收不定长数据
在上一篇文章STM32单片机串口空闲中断接收不定长数据中介绍了利用串口空闲中断接收不定长数据,这种方式有一个问题就是串口每接收到一个字节就会进入一次中断,如果发送的数据比较频繁,那么串口中断就会不停打 ...
- STM32单片机串口空闲中断接收不定长数据
在使用单片机的串口通信功能时,常用的接收数据方法是通过固定的字节数来判断一帧数是否发送完成,或者是通过固定的结束标志位来表示一帧数据发送完成.但是有时候会遇到发送的数据长度不固定,也没有固定的结束标志 ...
- 串口IDLE空闲中断+DMA实现接收不定长数据基于stm32cubemx
引言:对于串口接收一些不定长的数据,必须面对一个问题:怎么判断一帧数据接收是否完成?通常使用RXNE非空中断配合简单的数据协议,在数据中加入帧头.帧尾,在程序中判断是否接收到帧尾来确定数据接收完毕,因 ...
- STM32CubeMX系列教程04_STM32CubeMX各窗口界面描述
说明: 本文原创作者『strongerHuang』 首发于微信公众号『嵌入式专栏』,同时也更新在我的个人网站:EmbeddedDevelop 标签:STM32. STM32CubeMX. LL库. H ...
- STM32使用串口IDLE中断的两种接收不定长数据的方式
现在有很多数据处理都要用到不定长数据,而单片机串口的RXNE中断一次只能接收一个字节的数据,没有缓冲区,无法接收一帧多个数据,现提供两种利用串口IDLE空闲中断的方式接收一帧数据,方法如下: 方法1: ...
- android 串口一直打开_STM32之串口DMA接收不定长数据
STM32之串口DMA接收不定长数据 引言 在使用stm32或者其他单片机的时候,会经常使用到串口通讯,那么如何有效地接收数据呢?假如这段数据是不定长的有如何高效接收呢? 同学A:数据来了就会进入串口 ...
最新文章
- centos 网卡聚合及Cisco交换机链路聚合
- python 光标位置输入文字_Python 移动光标位置的方法
- Android的消息机制
- 红米note3android驱动,红米note3 mtp驱动
- 【UVA】10012 - How Big Is It?(暴力)
- *13.图的存储方式
- 如何查看linux下串口信息
- Opencv判断是否加载图片的两种方法
- c语言动态内存分配数组,【C】动态内存分配
- [react-router] React-Router 4怎样在路由变化时重新渲染同一个组件?
- php5..6中文帮助,6.5. IDE integration
- Linux下 对文件行数打乱(乱序排列)
- 不止操作系统,智能手机才更需要开源!
- 基于卷积神经网络的图像情感分析模型,Python实现
- java jdbc mysql util_Java,Scala:JDBCUtil,MySqlUtil,PhoenixJDBC
- linux下创建svn仓库及用户
- 牛客练习赛20:A. 礼物(组合数学/小球与盒子问题)
- word涂改涂掉图片_【最新】干部档案涂改检讨书-word范文 (20页)
- License授权方案
- PostgreSQL数据库部署之 :PostgreSQL pgadmin4 the application server could not be contacted