本文主要学习stm32中断、DMA通信原理和编程方法。使用stm32tubemx和HAL库分别完成中断模式编程和串口通信中断实验。
目录
一、STM32中断,DMA通信原理编程
1、STM32中断
(1)中断概念
整个中断过程分为三步
1.中断发生:当CPU在处理某一事件A时, 发生了另一事件B,请求CPU迅速去处理
2.中断处理:CPU暂停当前的工作,转去处理事件B
3.中断返回:当CPU将事件B处理完毕后,再回到事件A中被暂停的地方继续处理事件A
中断程序执行过程示意图:
中断响应过程:
(2)中断通道
微控制器片内集成了很多外设,对于单个外设而言,它通常具备若干个可以引起中断的中断源,而该外设的所有中断源只能通过指定的中断通道向内核申请中断。
例如STM32支持84个中断(16个内部,68个外部);16级可编程的中断优先级设置。
(3)中断优先级
1.多个中断同时出现时,处理器先响应高优先级的中断
2.低优先级中断的ISR执行时,可以被高优先级中断再次打断
3.ISR比App Code拥有更高的执行优先级
2、DMA通信原理
(1)DMA基本概念
DMA,全称Direct Memory Access,即直接存储器访问。
DMA传输将数据从一个地址空间复制到另一个地址空间,-用于在外设与存储器之间以及存储器与存储器之间进行高速数据传输。DMA传输过程的初始化和启动由CPU完成,传输过程由DMA控制器来执行,无需CPU参与,从而节省CPU资源,提高利用率。
(2)STM32的DMA控制器特点
1、STM32F411微控制器具备两个DMA控制器:DMA1和DMA2,每个控制器有8个数据流,每个数据流可以映射到8个通道(或请求);
2、每一个DMA控制器用于管理一个或多个外设的存储器访问请求,并通过总线仲裁器来协调各个DMA请求的优先级;
3、数据流(stream)是用于连接传输源和传输目标的数据通路,每个数据流可以配置为不同的传输源和传输目标,这些传输源和传输目标称为通道(Channel);
4、具备16字节的FIFO。使能FIFO功能后,源数据先送入FIFO,达到FIFO的触发阈值后,再传送到目标地址。
(3)DMA的主要特征
每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发,这些功能通过软件来配置
1、在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推);
2、独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐;
3、支持循环的缓冲器管理;
4、每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求;
5、存储器和存储器间的传输、外设和存储器、存储器和外设之间的传输;
6、闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标;
可编程的数据传输数目:最大为65535。
(4)DMA的数据传输方式
1、普通模式
传输结束后(即要传输数据的数量达到零),将不再产生DMA操作。若开始新的DMA传输,需在关闭DMA通道情况下,重新启动DMA传输。
2、循环模式
可用于处理环形缓冲区和连续数据流(例如ADC扫描模式)。当激活循环模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值进行加载, 并继续响应DMA请求。
二、采用中断模式控制LED灯
1、新建工程,配置实验环境
打开STM32cubeMX程序,点击File下的New Project新建一个工程
选择芯片,这里以STM32F103C8为例,然后点击中间出现的一列芯片的信息,再点击Start Project就行了
点击System Core,进入里面的SYS,在debug那里选择Serial Wire
配置时钟了,进入上面的RCC,将HSE那里设置为Crystal/Ceramic Resonator
来到Clock Configuration界面,把PLLCLK右边选上,时钟频率选择36MHz.
接下来就是设置输出寄存器,进入GPIO界面,点击相应的引脚,选择GPIO_Output,我这里选择的是PB5
接着初始化一个GPIO端口模拟按键,我这里选择的是PA1,右击选择GPIO_EXTI10模式
在左边的GPIO Mode and Configuration中点击刚刚选择的PA1,PB5,配置如下图所示
接下来是配置NVIC
在System Core中选择NVIC,勾选图示位置,使能外部中断,并配置优先级(本例一个管脚,默认为0)
2、生成项目工程文件
在配置好上述的实验环境之后,即可准备生成工程文件。
在Project Manager–>Project下,配置好自己的项目名和路径,然后IDE那项改为MDK-ARM,版本根据自己的需求选择,这里选择V5版本。
进入Code Generate界面,勾选生成初始化.c/.h文件,最后点击GENERATE CODE,生成代码。
3、编写中断函数
打开工程文件夹中的keil文件,点击stm32f1xx_it.c找到已配置好的中断处理函数
将用户自定义中断处理函数放到一个名为void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)的函数中即可。
代码如下:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if( GPIO_Pin == A1_EXTI_Pin)//判断外部中断源
{
HAL_GPIO_TogglePin(DM1_GPIO_Port, DM1_Pin);//翻转LED状态
}
}
将以上代码插入到main.c函数中:
编译程序
结果显示编译无误 !
4、烧录程序
5、实验结果
三、中断方式实现串口通信
1、题目要求
完成一个STM32的USART串口通讯程序(查询方式即可,暂不要求采用中断方式),要求:
1)设置波特率为115200,1位停止位,无校验位;
2)STM32系统给上位机(win10)连续发送“hello windows!”。win10采用“串口助手”工具接收。
根据题目,我设置,串口自动发送Hello windows!,如果接收到其他数据,则返回其他数据,之后继续发送Hello windows!
2、创建和设置工程
基础设置和前面那个工程一致,主要是配置USART1的中断
接下来是时钟设置
编辑工程名字等基础信息,和之前的那个一样,名字自己修改,之后生成代码,打开keil文件。
3、编写代码
在main.c和usart.c中添加头文件#include “stdio.h”
之后,在usart.c文件中,添加如下代码,进行重定义
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
//#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#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)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0x0001);
return ch;
}
#endif
在main.c函数中,在while循环中添加发送数据
printf("Hello windows!\r\n");
HAL_Delay(500);
在main.c中添加如下定义,用来接收串口数据
uint8_t aRxBuffer; //接收中断缓冲
uint8_t Uart1_RxBuff[256]; //接收缓冲
uint8_t Uart1_Rx_Cnt = 0; //接收缓冲计数
uint8_t cAlmStr[] = "数据溢出(大于256)\r\n";
添加开启接收中断的语句
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
在main.c下部添加中断回调函数
/**
* @brief Rx Transfer completed callbacks.
* @param huart pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval None
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_UART_TxCpltCallback could be implemented in the user file
*/
if(Uart1_Rx_Cnt >= 255) //溢出判断
{
Uart1_Rx_Cnt = 0;
memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff));
HAL_UART_Transmit(&huart1, (uint8_t *)&cAlmStr, sizeof(cAlmStr),0xFFFF);
}
else
{
Uart1_RxBuff[Uart1_Rx_Cnt++] = aRxBuffer; //接收数据转存
if((Uart1_RxBuff[Uart1_Rx_Cnt-1] == 0x0A)&&(Uart1_RxBuff[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
{
HAL_UART_Transmit(&huart1, (uint8_t *)&Uart1_RxBuff, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去
Uart1_Rx_Cnt = 0;
memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff)); //清空数组
}
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //再开启接收中断
}
4、编译与烧录
编译代码:
代码编译无误,接下来就可以烧录了。
使用flymcu软件,选择刚刚生成的.hex文件,点击开始编程进行烧录
5、实验结果
借用串口调试助手,可以得到以下实验结果
当下方发送数据,例如good,串口会进入中断,发送good,之后回到原循环,继续发送Hello windows。
四、STM32采用串口DMA方式
1、题目要求
STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据。
2、建立和设置工程文件
新建工程,选择STM32F103C8芯片
进入RCC,将HSE设置为Crystal/Ceramic Resonator
点击SYS,在debug那里选择Serial Wire
接着点击Connnectivity,选择USART1,Mode选择Asynchronous,此时在下面就可以看到配置好的一些基础参数:波特率为115200 Bits/s;传输数据长度为8 Bit;无奇偶检验;停止位为1;接收和发送都使能
DMA设置
点击DMA Settings
的Add添加通道,传输速率设置为中速Medium
模式设置为Normal
,右侧选择Memory
在System view
下选择DMA
来到Clock Configuration界面,点击HSE右边,PLLCLK右边,设置APB1分频器为/2
到这工程配置基本完成,生成工程的步骤这里便不再赘述。
3、代码编写
打开main.c添加代码
uint8_t Senbuff[] = "Hello world!"; //定义数据发送数组
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff));
HAL_Delay(1000);
4、代码编译和程序烧录
烧录程序
5、程序运行结果
C8T6核心板boot0接0,打开野火串口助手,打开串口即可接收信号
四、总结
这次实验内容较多,需要建立三个工程,实验过程中学习了关于中断实验的知识,虽然也遇到了一些困难,但是通过网上参考成功的解决了问题,希望在以后能学到更多关于这方面的知识。
五、参考文献
https://blog.csdn.net/qq_54496810/article/details/121027311
https://blog.csdn.net/as480133937/article/details/104827639/
https://www.cnblogs.com/breezy-ye/articles/12157442.html
https://blog.csdn.net/as480133937/article/details/104827639/