本文主要介绍stm32中断、DMA通信原理和编程方法,在理论学习的基础上,使用stm32tubemx和HAL库,完成STM32中断方式点灯、中断方式的串口通信以及DMA方式的串口通信编程练习。
一、什么是中断
中断通常被定义为一个事件,该事件能够改变处理器执行指令的顺序。这样的事件与 CPU 芯片内外部硬件电路产生的电信号相对应。
中断是指计算机在执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得 CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序,待处理完毕后又返回原来被中断处继续执行或调度新的进程执行 的过程。
中断分为 同步中断 和 异步中断。
同步中断——同步中断是当指令执行时由 控制单元产生的,之所以称为同步,是因为只有在一条指令终止执行后 CPU 才会发出中断 异步中断——异步中断是由其他硬件设备依照 CPU 时钟信号随机产生的。
通常我们所说的中断指的是异步中断,我们将同步中断称为异常。(异常是由程序的错误产生的,或者是由内核必须处理的异常条件产生的)
特点:
- 应用程序不必关心中断的发生与处理
- 中断服务程序不必关心应用程序的执行状态
- 中断是“上层应用”与“底层代码”的“分割边界”
中断的作用 跟据中断的定义,我们可以通过中断使处理器转而去优先运行正常控制流之外的代码。
当中断信号达到肘, CPU 必须停止它当前正在做的事情,并且切换到一个另一个活动。为了做到这就要在内核态堆钱保存程序计数器的当前值 (寄存器的内容) ,并把与中断类型相关的地址放进程序计数量。
中断处理 是指CPU响应中断,转入中断处理程序,系统开始处理中断。 中断响应 是指CPU收到中断请求后转向相应的事件处理程序。
开中断后,系统就可以响应其他的中断了,关中断后,系统不响应其他的中断除非优先级高的中断。
中断屏蔽 是指在中断请求产生后,系统用软件方式有选择地封锁部分中断而允许其余部分中断仍能得到响应。
中断的类型及优先级 中断的类型
- 硬中断:通过处理器中断信号线产生的中断
- 软中断:通过非法指令或特殊指令触发的中断
中断优先级
- 多个中断同时出现时,处理器先响应高优先级的中断
- 低优先级中断的ISR执行时,可以被高优先级中断再次打断
- ISR比App Code拥有更高的执行优先级
二、STM32外部中断模式控制LED亮灭
1、任务要求
用 stm32F103 核心板的 GPIOA 端一管脚接一个LED,GPIOB 端口一引脚接一个开关(用杜邦线模拟代替)。采用 中断模式 编程,当开关接 高电平 时,LED 亮灯 ;接 低电平 时,LED 灭灯。
2、设计思路
这里我选用的核心板为 STM32F103C8T6 最小核心板
设置 PA5 端接 LED, PB5 模拟开关,通过 PB5 接入高低电平来模拟开关开断
A5 输出控制灯的亮灭,设置为 GPIO_Output B5 模拟开关,设置为 GPIO_EXTI5 A0 持续输出高电平,设置为 GPIO_Output,当开关 B5 接到 A0 时, LED 亮灯 A2 持续输出低电平,设置为 GPIO_Output,当开关 B5 接到 A2 时, LED 灭灯
3、创建工程
芯片选择
外设设置
-
设置 SYS,点击 System Core ->SYS ,将 Debug 改为 Serial Wire -
设置指示灯 LED 引脚 PA5 ,设置引脚模式为输出模式 GPIO_Output,对于 LED 对应的 PA5 管脚,名字设为 LED,其他默认即可 -
设置按键引脚 PB5,设置引脚为外部中断功能,PB5 与外部中断线 EXIT5 连接 ,设置为GPIO_EXIT5,GPIO mode 设置为 External Interrupt Mode with Rising/Falling edge trigger detection ,即上升沿和下降沿都可以触发,名字设为 SWITCH
External Interrupt Mode with Rising edge trigger detection上升沿 External Interrupt Mode with Falling edge trigger detection下降沿 External Interrupt Mode with Rising/Falling edge trigger detection上升沿和下降沿
配置中断优先级
- 大多数情况下不必设置中断优先级,直接使用中断编号设置的默认中断优先级
时钟设置
- 将HCLK设置为36MHz
生成工程
- 设置工程名字,存储路径和编译环境
4、代码编写
-
打开生成的项目,找到 stm32f1xx_it.c -
找到 EXTI9_5_IRQHandler 这个函数,右键HAL_GPIO_EXTI_IRQHandler 这个语句,选择 Go to Definition of ’ … ,或者是直接按F12跳到该函数 -
这时,会提示如下信息,系统提示找不到该函数 -
修复方法很简单,点击魔法棒,把 Browse Information 沟选上,然后 rebuild 重新编译一次 -
此时再次跳转就可以成功跳转到该函数处了,HAL_GPIO_EXTI_IRQHandler 是一个中断服务函数,当捕获到上升沿或者下降沿,就会触发中断,进入到这个函数里面 -
然后就会执行 HAL_GPIO_EXTI_Callback(GPIO_Pin) 函数,此函数为回调函数,向下找就可以找到该函数,我们打开可以发现前面有个weak。前面的 __weak 表示此函数为虚函数,这个函数该函数是给用户自己重写的,可以在这里根据不同的中断来执行不同的处理。在这里我们需要根据B5的不同中断来实现A5电平的转变,从而实现LED的亮灭。 -
在将该函数替换成下面这一段代码 函数代码:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == SWITCH_Pin){
GPIO_PinState pinState = HAL_GPIO_ReadPin(SWITCH_GPIO_Port,SWITCH_Pin);
if(pinState==GPIO_PIN_RESET)
HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);
}
}
5、编译
-
点击魔法棒,在 Output 下勾选 Create HEX File -
在 Target 下将编译器改为 Version 5 -
Debug下选择 Use Simulator,将 Dialog DLL 改为 DARMSTM.DLL,将 Paremeter 改为 -pSTM323F103C8 -
点击 Rebuild 进行编译
6、硬件连接
这里分为 烧录电路的连接 和 实验电路的连接
烧录电路的连接: 连接方法:
USB转TTL | STM32F103C8T6 |
---|
GND | G | 3V3 | 3V3 | RXD | PA9 | TXD | PA10 |
注意将核心板上的BOOT0设置为1,BOOT1设置为0
实验电路的连接:
引出端口 | 目的端口 |
---|
3.3V | LED正极 | A5 | LED负极 | B5 | A0/A2 |
7、烧录
8、运行效果
GPIOB5 端口一引脚接一个开关(用杜邦线模拟代替),当开关 B5 接到 A2 时,输入 高电平 ,LED 亮灯 ;当开关 B5 接到 A0 时,输入 低电平 ,LED 灭灯。
实现效果:
可以发现,当B5不接输入的时候,LED灯会一直闪烁,这是因为GPIO悬空时的值是不确定的,,一定要上拉或下拉,有的单片机内部集成了上拉或下拉电阻,就不需要在外围电路加电阻了。于是,我做了如下改进
9、改进
在使用 CubeMX 配置 STM32 时,将 PB5 设置为 Pull-Up,即设置为下拉,在不接入输入时,默认输出高电平,不触发中断,LED 保持熄灭,其他步骤不变
实现效果:
三、HAL库UART函数库介绍
UART结构体定义
UART_HandleTypeDef huart1;
1、串口发送/接收函数
HAL_UART_Transmit();串口发送数据,使用超时管理机制
HAL_UART_Receive();串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT();串口中断模式发送
HAL_UART_Receive_IT();串口中断模式接收
HAL_UART_Transmit_DMA();串口DMA模式发送
HAL_UART_Transmit_DMA();串口DMA模式接收
说明: 串口发送数据
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
功能: 串口发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)
参数:
UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1:别名为huart1 *pData:需要发送的数据 Size:发送的字节数 Timeout:最大发送时间,发送数据超过该时间退出发送
示例:
HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff);
2、串口中断函数
HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
HAL_UART_ErrorCallback();
串口接收中断回调函数
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
功能: HAL库的中断进行完之后,并不会直接退出,而是会进入中断回调函数中,用户可以在其中设置代码,串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改。
参数:
UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
举例:
HAL_UART_RxCpltCallback(&huart1)
{
}
串口中断处理函数
HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
功能: 对接收到的数据进行判断和处理 判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用。 如果接收数据,则会进行接收中断处理函数
如果发送数据,则会进行发送中断处理函数
3、串口查询函数
HAL_UART_GetState();
举例
while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX)
四、中断方式实现串口通信
1、任务要求
采用串口中断方式重做上周的串口通信作业,分别实现: 1)当stm32接收到字符“s”时,停止持续发送“hello windows!”; 当接收到字符“t”时,持续发送“hello windows!”(提示:采用一个全局标量做信号灯); 2)当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”(提示:要将接收到的连续字符保存到一个字符数组里,进行判别匹配)。
2、创建工程
-
打开 STM32CubeMX,点击 ACCESS TO MCU SELECTOR -
选择对应的芯片,这里我用到的是 STM32F103C8T6,在搜索框输入 STM32F103C8,双击搜索到的芯片 -
设置 RCC -
设置SYS,将 Debug 改为 Serial Wire -
设置USART1,设置MODE为异步通信,波特率为115200 Bits/s,传输数据长度为8 Bit,奇偶检验无,停止位1 -
设置NVIC,点击 NVIC Settings 一栏使能接收中断 -
修改项目名称、存储位置和编译器版本,创建项目 -
打开项目,点击 Open Project
3、代码编写
在main函数前定义全局变量:
char c;
char hello[]="hello windows!\n";
char tips[]="CommandError\n";
char tips1[]="Start.....\n";
char tips2[]="Stop......\n";
int flag=0;
在main函数中设置接收中断: 函数说明: 函数原型
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能
功能:串口中断接收,以中断方式接收指定长度数据。 大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。 接收到数据时,会触发串口中断。 再然后,串口中断函数处理,直到接收到指定长度数据 而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)
参数
UART_HandleTypeDef *huart UATR的别名 huart1 *pData 接收到的数据存放地址 Size 接收的字节数
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
main函数中的while循环里面添加传输代码:
if(flag==1){
HAL_UART_Transmit(&huart1, (uint8_t *)&hello, strlen(hello),0xFFFF);
HAL_Delay(1000);
}
这里用到了 strlen() 函数,需要在 main.c 前面加上一句 #include "string.h" ,来声明包含 strlen() 函数的头文件
在main函数下面重写中断处理函数:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(c=='s'){
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF);
}
else if(c=='t'){
flag=1;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF);
}
else {
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF);
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
}
** main.c 修改部分代码:**
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include <string.h>
void SystemClock_Config(void);
char c;
char hello[]="hello windows!\n";
char tips[]="CommandError\n";
char tips1[]="Start.....\n";
char tips2[]="Stop......\n";
int flag=0;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
while (1)
{
if(flag==1){
HAL_UART_Transmit(&huart1, (uint8_t *)&hello, strlen(hello),0xFFFF);
HAL_Delay(1000);
}
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(c=='s'){
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF);
}
else if(c=='t'){
flag=1;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF);
}
else {
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF);
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
4、编译
-
点击魔法棒,在 Output 下勾选 Create HEX File -
在 Target 下将编译器改为 Version 5 -
Debug下选择 Use Simulator,将 Dialog DLL 改为 DARMSTM.DLL,将 Paremeter 改为 -pSTM323F103C8 -
点击 Rebuild 进行编译
5、硬件连接
连接方法:
USB转TTL | STM32F103C8T6 |
---|
GND | G | 3V3 | 3V3 | RXD | PA9 | TXD | PA10 |
注意将核心板上的BOOT0设置为1,BOOT1设置为0
6、烧录
打开 FlyMcu 进行烧录
7、运行效果
-
打开串口调试助手 SSCOM,打开并配置串口设置 -
点击打开串口 -
这里一定要取消勾选加回车换行,否则就会出现下面这种情况,因为勾选后,串口助手会在发送数据后面加上回车换行,然后就会导致不能实现正常的功能 -
在发送框输入 “t”,点击发送,电脑向 stm32发送字母 “t”,stm32 接收到之后输出提示信息,并开始发送数据,每间隔一秒发送一次 hello windows! -
在发送框输入 “s”,点击发送,电脑向 stm32发送字母 “s”,stm32 接收到之后输出提示信息,并停止发送数据 -
再次发送 “t” 和 “s”,stm32 仍然可以收发信息
实现效果:
8、进阶练习
当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”(提示:要将接收到的连续字符保存到一个字符数组里,进行判别匹配)。
(1)新建工程,按照上面的串口通信重新创一个即可
(2)在 mian.c 中写入以下代码
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "string.h"
uint8_t aRxBuffer;
uint8_t Uart1_RxBuff[256];
uint8_t str1[20] = "stop stm32";
uint8_t str2[20] = "go stm32";
uint8_t Uart1_Rx_Cnt = 0;
uint8_t cAlmStr[] = "êy?Yò?3?(′óóú256)\r\n";
unsigned int flag = 1;
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
while (1)
{
if(flag == 1)
{
printf("ppm Hello windows!\r\n");
}
else
{
}
HAL_Delay(500);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart);
if (strcmp(Uart1_RxBuff, str1) == 0) flag = 0;
if (strcmp(Uart1_RxBuff, str2) == 0) flag = 1;
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);
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
(3)编译烧录
(4)实现效果 实现效果:
五、DMA简介
什么是DMA 我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,
CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?
因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理。
DMA就是基于以上设想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作–计算、控制等。
直接存储器访问 (DMA) DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。 DMA传输过程的初始化和启动由CPU完成,传输过程由DMA控制器来执行,无需CPU参与,从而节省CPU资源,提高利用率。
DMA传输方式 DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:
普通模式: 传输结束后(即要传输数据的数量达到零),将不再产生DMA操作。若 开始新的DMA传输,需在关闭DMA通道情况下,重新启动DMA传输。 循环模式: 可用于处理环形缓冲区和连续数据流(例如ADC扫描模式)。当激活循 环模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值 进行加载, 并继续响应DMA请求。
DMA传输参数 数据传输需要: 1 数据的源地址 2 数据传输位置的目标地址 3 传递数据多少的数据传输量 4 进行多少次传输的传输模式
当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时 达到传输终点,结束DMA传输 ,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输。 也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。
DMA数据传输的四个要素 ① 传输源 :DMA数据传输的来源 ② 传输目标:DMA数据传输的目的 ③ 传输数量:DMA传输数据的数量 ④ 触发信号:启动一次DMA数据传输的动作
DMA控制器特点
STM32F411微控制器具备两个DMA控制器:DMA1和DMA2,每个控制器有8个数据流,每个数据流可以映射到8个通道(或请求);
每一个DMA控制器用于管理一个或多个外设的存储器访问请求,并通过总线仲裁器来协调各个DMA请求的优先级;
数据流(stream)是用于连接传输源和传输目标的数据通路,每个数据流可以配置为不同的传输源和传输目标,这些传输源和传输目标称为通道(Channel);
具备16字节的FIFO。使能FIFO功能后,源数据先送入FIFO,达到FIFO的触发阈值后,再传送到目标地址。
DMA工作框图 上方的框图,我们可以看到STM32内核,存储器,外设及DMA的连接,这些硬件最终通过各种各样的线连接到总线矩阵中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设和谐的使用总线来传输数据。
有DMA的情况下,ADC采集的数据是怎样存放到SRAM中的? 在发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。DMA传输结束,如果有更多的请求时,外设可以启动下一个周期。
总之,每次DMA传送由3个操作组成:
- 从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
- 存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
- 执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。
DMA方式的接口函数
六、STM32采用DMA方式实现串口通信
1、任务要求
STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据。
2、创建工程
-
打开 STM32CubeMX,点击 ACCESS TO MCU SELECTOR -
选择对应的芯片,这里我用到的是 STM32F103C8T6,在搜索框输入 STM32F103C8,双击搜索到的芯片 -
设置RCC -
设置串口 -
使能中断 -
DMA设置 点击 DMA Settings 的Add添加通道,传输速率设置为中速 Medium -
模式设置为Normal,右侧选择Memory -
在System view下选择DMA,点击Add,添加MEMTOMEM -
时钟设置 -
设置好工程名称、存储路径和编译器版本之后,导出Keil工程文件即可 -
打开工程
3、代码编写
在 main.c 文件添加如下代码
uint8_t hello[] = "hello windows!\n";
在主函数 while 循环里面加入如下代码:
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)hello, sizeof(hello));
HAL_Delay(1000);
4、编译
-
点击魔法棒,在 Output 下勾选 Create HEX File -
在 Target 下将编译器改为 Version 5 -
Debug下选择 Use Simulator,将 Dialog DLL 改为 DARMSTM.DLL,将 Paremeter 改为 -pSTM323F103C8 -
点击 Rebuild 进行编译
5、硬件连接
连接方法:
USB转TTL | STM32F103C8T6 |
---|
GND | G | 3V3 | 3V3 | RXD | PA9 | TXD | PA10 |
注意将核心板上的BOOT0设置为1,BOOT1设置为0
6、烧录
打开 FlyMcu 进行烧录
7、运行效果
七、总结
本篇文章主要讲到stm32中断、DMA通信原理和编程方法,并且在理论学习的基础上,通过使用stm32tubemx和HAL库,完成STM32中断方式点灯、中断方式的串口通信以及DMA方式的串口通信等编程练习。嵌入式学习不能只知道学习理论知识而不动手实践,也不能天天敲代码而原理一点也不懂,两者要兼得。只有同时掌握理论知识和实践方法,才能学好嵌入式。希望大家在学习嵌入式的过程中,也要做到理论与实践并重。能够做到理论指导实践,利用实践操作来加深对理论知识的理解,二者相辅相成。
参考列表: 1.stm32外部中断模式控制灯亮灭 2.HAL库中断方式进行串口通信 3.stm32hal库串口DMA收发 4.【STM32】HAL库 STM32CubeMX教程十一—DMA (串口DMA发送接收) 5.【嵌入式12】DMA通信原理及编程实验,DMA方式向上位机连续发送数据 6.【嵌入式11】HAL库实验中断开关点灯及串口通信
|