DMA(Direct Memory Access) 直接存储器存取,是单片机的一个外设,它的主要功能是用来搬数据,但是不需要占用 CPU,即在传输数据的时候,CPU 可以干其他的事情,好像是多线程一样。数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是 SRAM 或者是 FLASH。DMA 控制器包含了 DMA1 和 DMA2,其中 DMA1 有 7 个通道,DMA2 有 5 个通道,这里的通道可以理解为传输数据的一种管道。 要注意的是 DMA2 只存在于大容量的单片机中。
DMA1 各个通道的请求映像
DMA2 各个通道的请求映像
其中 ADC3、SDIO 和 TIM8 的 DMA 请求只在大容量产品中存在,这个在具体项目时 要注意。
1. 打开 STM32CubeMX 软件,点击“新建工程”
2. 选择 MCU 和封装
3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置
4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire
在 Connectivity
中选择 USART1
设置,并选择 Asynchronous
异步通信
波特率为 115200 Bits/s
。传输数据长度为 8 Bit
。奇偶检验 None
,停止位 1
,接收和发送都使能
。
使能串口接收中断
点击 DMA Settings
添加 USART1 TX 和 USART1 RX 分别对应DMA1 的通道4和通道5。
Normal
表示单次传输,传输一次后终止传输。Circular
表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止。Peripheral
表示外设地址自增。Memory
表示内存地址自增。Byte
一个字节。Half Word
半个字,等于两字节。Word
一个字,等于四字节。输入项目名和项目路径
选择应用的 IDE 开发环境 MDK-ARM V5
每个外设生成独立的 ’.c/.h’
文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
点击 GENERATE CODE 生成代码
新建一个变量
uint8_t sendBuff[] = "USART test by DMA\r\n";
在 man.c 中的主循环添加以下代码:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)sendBuff, sizeof(sendBuff));
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
通过串口助手可以看到在接收区有数据不断的打印输出
注意:如果不开启串口中断,则程序只能发送一次数据,程序不能判断DMA传输是否完成,USART一直处于busy状态。
在 main.c 头部添加全局变量 Buffer
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */
uint8_t Buffer[1];
/* USER CODE END PV */
在 stm32f1xx_it.c 头部声明全局变量 Buffer
/* External variables --------------------------------------------------------*/
extern UART_HandleTypeDef huart1;
/* USER CODE BEGIN EV */
extern uint8_t Buffer[1];
/* USER CODE END EV */
在 main.c 中,while 循环前,串口初始化后,添加接收中断开启函数,这样在第一次接收到数据的时候才会触发中断。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_DMA(&huart1, (uint8_t *)Buffer, 1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在 stm32f1xx_it.c
这个文件的最下面添加 HAL_UART_RxCpltCallback()
/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
HAL_UART_Receive_DMA(&huart1, (uint8_t *)Buffer, 1);
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Buffer, 1);
}
}
/* USER CODE END 1 */
通过串口助手发送 OK,可以看到接收到 O,这是因为设置的接收数据是一个字符,如果要接收更多字符,请加大 Buffer。
特点:
uint8_t recvBuff[BUFFER_SIZE]; //接收数据缓存数组
volatile uint8_t recvLength = 0; //接收一帧数据的长度
volatile uint8_t recvDndFlag = 0; //一帧数据接收完成标志
在 main.h 中添加以下宏定义与变量:
#define BUFFER_SIZE 256
extern uint8_t recvBuff[BUFFER_SIZE]; //接收数据缓存
extern volatile uint8_t recvLength; //接收一帧数据的长度
extern volatile uint8_t recvDndFlag; //一帧数据接收完成标志
在 main.c 中,while 循环前,串口初始化后,添加空闲中断和DMA接收开启函数,这样在第一次接收到数据的时候才会触发中断。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断
HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在 stm32f1xx_it.c
这个文件的最下面修改 USART1_IRQHandler()
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint32_t tmpFlag = 0;
uint32_t temp;
tmpFlag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
if((tmpFlag != RESET))//idle标志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
HAL_UART_DMAStop(&huart1); //
temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数
recvLength = BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
recvDndFlag = 1; // 接受完成标志位置1
HAL_UART_Transmit_DMA(&huart1, recvBuff, recvLength);
recvLength = 0;//清除计数
recvDndFlag = 0;//清除接收结束标志位
memset(recvBuff,0,recvLength);
HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);//重新打开DMA接收,不然只能接收一次数据
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
用户代码要加在 USER CODE BEGIN N
和 USER CODE END N
之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。
• 由 Leung 写于 2021 年 1 月 18 日
• 参考:STM32CubeMX系列教程6:直接存储器访问 (DMA)
《嵌入式-STM32开发指南》第二部分 基础篇 - 第7章DMA(HAL库)