目录
【DMA的简介】
DMA(Direct Memory Access,直接存储器访问) 提供在外设与内存、存储器和存储器、外设与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存的工作来说就无法使用。
【DMA的意义】
代替 CPU
搬运数据,为
CPU
减负
● 数据搬运的工作比较耗时间
● 数据搬运工作时效要求高(有数据来就要搬走)
● 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)
【搬运什么数据?如何搬运数据?】
存储器、外设
这里的外设指的是:spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设
这里的存储器包括:自身的闪存(flash)、内存(SRAM)、外设的存储设备,它们都可以作为访问地源或者目的
这里的存储器包括:自身的闪存(flash)、内存(SRAM)、外设的存储设备,它们都可以作为访问地源或者目的
【DMA框图】

【DMA数据传输方向】
● 存储器→
存储器(例如:复制某特别大的数据buf、内部FLASH 向内部SRAM 复制数据
)
● 存储器→外设 (例如:将某数据
buf
写入串口
TDR
寄存器)
● 外设→存储器 (例如:将串口
RDR
寄存器写入某数据
buf、ADC采集
)
● 外设→外设
【DMA 控制器】
如果外设要想通过DMA 来传输数据,必须先给DMA 控制器发送DMA 请求,DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且DMA 控制器收到应答信号之后,就会启动DMA 的传输,直到传输完毕。
STM32F103有2
个
DMA
控制器,
DMA1
有
7
个通道,
DMA2
有
5
个通道。
一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的
DMA
请求,则按照优先级进行响应。
注意:DMA2 只存在于大容量的单片机中
DMA1有
7
个通道:
DMA2有5个通道
【DMA通道的优先级】
当发生多个DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器管理。仲裁器
优先级管理采用软件+硬件:
● 软件: 每个通道的优先级可以在DMA_CCRx
寄存器中设置,有
4
个等级:最高级>高级>
中级
>
低级
● 硬件: 如果2
个请求,它们的软件优先级相同,则较低编号的通道优先级高的优先权。
比如:如果软件优先级相同,通道2优先于通道4
在大容量产品和互联型产品中,DMA1 控制器拥有高于DMA2 控制器的优先级。
【DMA的传输方式】
● DMA_Mode_Normal(正常模式)
一次DMA数据传输完后,停止
DMA
传送 ,也就是只传输一次
● DMA_Mode_Circular(循环传输模式)
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式
【指针递增模式】
外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。
【注意事项】
若把 HAL_ADC_Start_DMA 的最后一个参数明确写值时,需要改为实际采集数据个数的2倍
如:HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adcConvertValue,8); 则一次DMA仅搬运4次
ADC采集时,只能采集到一半的数据_adc采样突然变为一半-优快云博客
【demo · DMA_memory_to_memory】
demo概述:使用DMA
的方式将数组
A
的内容复制到数组
B
中,搬运完之后打印数组
B
的内容
该demo包含两个方向:DMA_memory_to_peripheral,DMA_peripheral_to_memory
所用函数:
HAL_DMA_Start // DMA启动函数
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t
DstAddress, uint32_t DataLength)
// 参数一:DMA_HandleTypeDef *hdma,DMA通道句柄
// 参数二:uint32_t SrcAddress, 源内存地址
// 参数三:uint32_t DstAddress, 目标内存地址
// 参数四:uint32_t DataLength, 传输数据长度 注意:需要乘以sizeof(uint32_t)
// 返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
注意:数据长度需要乘以sizeof(uint32_t),或者直接用sizeof(srcBuf)
__HAL_DMA_GET_FLAG
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__))
// 参数一:HANDLE,DMA通道句柄
// 参数二:FLAG,数据传输标志。DMA_FLAG_TCx表示数据传输完成标志
// DMA_FLAG_TCx:传输完成标志(Transfer Complete)。当DMA传输完成时,该标志位被置位。可以使用该标志位来检测DMA传输是否已经完成。
// DMA_FLAG_HTx:半传输标志(Half Transfer)。当DMA传输的一半数据大小完成时,该标志位被置位。可以使用该标志位来检测DMA传输是否已经完成了一半。
// DMA_FLAG_TEx:传输错误标志(Transfer Error)。当DMA传输过程中发生错误时,该标志位被置位。可以使用该标志位来检测DMA传输是否发生错误。
// DMA_FLAG_GLx:全局标志(Global Flag)。该标志位是一个组合标志位,用于检测DMA传输过程中的各种状态。包括TCx、HTx和TEx标志位。如果任何一个标志位被置位,该标志位也将被置位。可以使用该标志位检测DMA传输的整体状态。
// 返回值:FLAG的值(SET/RESET)
句柄:
CubeMX在CubeMX_USART_NVIC基础上增加DMA功能
/* main.c */
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
#include "printf.h"
#include "string.h"
#define BUF_SIZE 16
uint32_t srcBuf[BUF_SIZE] = // 源数组
{
0x00000000,0x11111111,0x22222222,0x33333333,
0x44444444,0x55555555,0x66666666,0x77777777,
0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
};
uint32_t desBuf[BUF_SIZE]; // 目标数组
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1, &buf, 1);
printf("hello zzq\r\n");
HAL_Delay(1000);
// 开启数据传输
HAL_DMA_Start(&hdma_memtomem_dma1_channel1,(uint32_t)srcBuf, (uint32_t)desBuf, sizeof(uint32_t) * BUF_SIZE);
while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1) == RESET); // 等待数据传输完成
// 打印数组内容
for (int i = 0; i < BUF_SIZE; i++)
{
printf("Buf[%d] = %X\r\n", i, desBuf[i]);
}
while (1)
{
if(UART1_RX_STA & 0x8000)
{
printf("收到数据:"); // 把“”中的内容打印到串口
printf("%s",UART1_RX_Buffer); // 将收到的数据发送到串口
//HAL_UART_Transmit(&huart1, UART1_RX_Buffer, UART1_RX_STA & 0x3fff, 0xffff); // 将收到的数据发送到串口
while(huart1.gState != HAL_UART_STATE_READY); // 等待发送完成
printf("\r\n");
UART1_RX_STA = 0; // 重新开始下一次接收
memset(UART1_RX_Buffer,0,sizeof(UART1_RX_Buffer)); // 清空缓冲区
}
【demo · DMA_USART】
demo概述:使用DMA的方式接收串口数据,并将数据搬运串到口发送,同时CPU用于控制LED闪烁
所用函数:
HAL_UART_Transmit_DMA // 串口DMA发送
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData,
uint16_t Size)
// 参数一:UART_HandleTypeDef *huart,串口句柄,即别名
// 参数二:uint8_t *pData,待发送数据首地址
// 参数三:uint16_t Size,待发送数据长度
// 返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
HAL_UART_Receive_DMA // 串口DMA接收
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
// 参数一:UART_HandleTypeDef *huart,串口句柄,即别名
// 参数二:uint8_t *pData,待接收数据首地址
// 参数三:uint16_t Size,待接收数据长度
// 返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
__HAL_DMA_GET_FLAG
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__))
// 参数一:HANDLE,DMA通道句柄
// 参数二:FLAG,数据传输标志。DMA_FLAG_TCx表示数据传输完成标志
// DMA_FLAG_TCx:传输完成标志(Transfer Complete)。当DMA传输完成时,该标志位被置位。可以使用该标志位来检测DMA传输是否已经完成。
// DMA_FLAG_HTx:半传输标志(Half Transfer)。当DMA传输的一半数据大小完成时,该标志位被置位。可以使用该标志位来检测DMA传输是否已经完成了一半。
// DMA_FLAG_TEx:传输错误标志(Transfer Error)。当DMA传输过程中发生错误时,该标志位被置位。可以使用该标志位来检测DMA传输是否发生错误。
// DMA_FLAG_GLx:全局标志(Global Flag)。该标志位是一个组合标志位,用于检测DMA传输过程中的各种状态。包括TCx、HTx和TEx标志位。如果任何一个标志位被置位,该标志位也将被置位。可以使用该标志位检测DMA传输的整体状态。
// 返回值:FLAG的值(SET/RESET)
__HAL_UART_CLEAR_IDLEFLAG
#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
// 参数一:HANDLE,串口句柄
// 返回值:无
HAL_UART_DMAStop
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
// 参数一:UART_HandleTypeDef *huart,串口句柄
// 返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
__HAL_DMA_GET_COUNTER
#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)
// 参数一:HANDLE,串口句柄
// 返回值:未传输数据大小
CubeMX中打开GPIO·PC13,打开UART1,开启UART1_NVIC,开启DMA_UART1
代码逻辑:
如何判断串口接收是否完成?如何知道串口收到数据的长度?
使用串口空闲中断(IDLE):
● 串口空闲时,触发空闲中断● 空闲中断标志位由硬件置1,软件清零完整流程:● 使能IDLE空闲中断● 使能DMA接收中断● 收到串口接收中断,DMA不断传输数据到缓冲区● 一帧数据接收完毕,串口暂时空闲,触发串口空闲中断● 在中断服务函数中,清除中断标志位,关闭DMA传输(防止干扰)● 计算刚才收到了多少个字节的数据● 处理缓冲区数据,开启DMA传输,开始下一帧接收
/* stm32f1xx.it.c */
// 在void USART1_IRQHandler(void)函数中修改
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET))// 判断IDLE标志位是否被置位,SET表示一帧数据发送完毕
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除IDLE空闲标志位
HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止干扰
uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx); // 获得未传输完成数据长度
rcvLen = BUF_SIZE - temp; // 计算还需要传输数据长度
HAL_UART_Transmit(&huart1, (const unsigned char *)"收到数据:", strlen("收到数据:"),100);
HAL_UART_Transmit_DMA(&huart1, rcvBuf, rcvLen); // 发送数据
HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE); // 开启DMA
}
/* USER CODE END USART1_IRQn 1 */
}
/* main.h */
#define BUF_SIZE 10
extern uint8_t rcvBuf[BUF_SIZE]; // 接收数据缓存数组
extern uint8_t rcvLen;
/* main.c */
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
uint8_t rcvBuf[BUF_SIZE] = {0}; // 接收数据缓存数组
uint8_t rcvLen = 0; // 接收一帧数据的长度
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能IDLE空闲中断
HAL_UART_Receive_DMA(&huart1,rcvBuf,BUF_SIZE); // 使能DMA接收中断
while (1)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(300);
}
}