需求描述
使用寄存器操作把ROM中的数据通过DMA传输到RAM,然后把数据通过printf发送到串口验证是否正确。
DMA传输不涉及外设,所以通道随便选。我们选DMA1的1通道。
一般设置流程
时钟使能
代码
/* 1. 初始化DMA1时钟。DMA是挂在AHB总线上 */
RCC->AHBENR |= RCC_AHBENR_DMA1EN;寄存器


设置储存器方向
代码
/* 2. 设置传输方向和存储器到存储器模式 0:从外设读 1:从内存读*/
DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM;寄存器
这里设置解释虽然说的从外设读,实际上是将传出数据的设备称为外设,所以我们这里虽然ROM和RAM都是存储器,但是这里将ROM称为外设。



设置外设和存储器宽度
代码
/* 4. 设置外设和存储器数据宽度 00:8位 01:16位 10:32位*/
DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE;
DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE;寄存器
设置要一致,虽然我并不明白这里开了分开设置的通道的理由是什么,为什么不合并在一起?


设置外设和存储器地址自增
代码
/* 5. 外设和存储器地址自增 0:不自增 1:自增*/
DMA1_Channel1->CCR |= DMA_CCR1_MINC;
DMA1_Channel1->CCR |= DMA_CCR1_PINC;寄存器
顾名思义,当我们传入大量数据的时候,开启自增让我们无需手动重新定位存储器来存储数据。


传输中断使能
代码
/* 6. 开启传输完成中断使能 */
DMA1_Channel1->CCR |= DMA_CCR1_TCIE;寄存器


配置源和目的地地址
代码
/* 配置外设地址 */
DMA1_Channel1->CPAR = srcAddr;
/* 配置内存地址 */
DMA1_Channel1->CMAR = desAddr;寄存器



数据传输长度
代码
/* 配置要传输的数据长度 */
DMA1_Channel1->CNDTR = dataLength;寄存器

通道传输使能
代码
/* 开启数据传输 */
DMA1_Channel1->CCR |= DMA_CCR1_EN;寄存器


寄存器实现
dma.h
#ifndef __DMA_H
#define __DMA_H
#include "stm32f10x.h"
extern uint8_t isFinished;
// 初始化
void DMA1_Init(void);
// 数据传输
void DMA1_Transmit(uint32_t srcAddr, uint32_t destAddr, uint16_t dataLen);
#endifdma.c
#include "dma.h"
// 初始化
void DMA1_Init(void)
{
// 1. 开启时钟
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
// 2. DMA相关配置
// 2.1 数据传输方向: 存储器到存储器,从外设读
DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM;
DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
// 2.2 数据宽度: 8位 - 00
DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE;
DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE;
// 2.3 地址自增:开启自增
DMA1_Channel1->CCR |= DMA_CCR1_PINC;
DMA1_Channel1->CCR |= DMA_CCR1_MINC;
// 2.4 开启数据传输完成中断标志
DMA1_Channel1->CCR |= DMA_CCR1_TCIE;
// 3. NVIC配置
NVIC_SetPriorityGrouping(3);
NVIC_SetPriority(DMA1_Channel1_IRQn, 2);
NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
// 数据传输
void DMA1_Transmit(uint32_t srcAddr, uint32_t destAddr, uint16_t dataLen)
{
// 1. 设置外设地址
DMA1_Channel1->CPAR = destAddr;
// 2. 设置存储器地址
DMA1_Channel1->CMAR = srcAddr;
// 3. 设置传输的数据量
DMA1_Channel1->CNDTR = dataLen;
// 4. 开启通道,开始传输数据
DMA1_Channel1->CCR |= DMA_CCR1_EN;
}
// 中断服务程序
void DMA1_Channel1_IRQHandler(void)
{
// 判断中断标志位
if (DMA1->ISR & DMA_ISR_TCIF1)
{
// 清除中断标志位
DMA1->IFCR |= DMA_IFCR_CTCIF1;
// 关闭DMA通道
DMA1_Channel1->CCR &= ~DMA_CCR1_EN;
isFinished = 1;
}
}main.c
#include "usart.h"
#include "dma.h"
// 定义全局变量,表示数据传输完成
uint8_t isFinished = 0;
// 定义全局常量,放置在ROM中,作为数据源
const uint8_t src[] = {10,20,30,40};
// 定义变量数组,放置在RAM中,用来存储接收到的数据
uint8_t dest[4] = {0};
int main(void)
{
// 初始化
USART_Init();
DMA1_Init();
printf("Hello, world!\n");
// 打印变量和常量地址
printf("src = %p, dest = %p\n", src, dest);
// 开启DMA通道进行传输
DMA1_Transmit((uint32_t)src, (uint32_t)dest, 4);
while(1)
{
if (isFinished)
{
// 打印输出验证
for (uint8_t i = 0; i < 4; i++)
{
printf("%d\t", dest[i]);
}
isFinished = 0;
}
}
}HAL库实现
HAL库配置




DMA初始化代码
void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* Configure DMA request hdma_memtomem_dma1_channel1 on DMA1_Channel1 */
hdma_memtomem_dma1_channel1.Instance = DMA1_Channel1;
hdma_memtomem_dma1_channel1.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma1_channel1.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma1_channel1.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem_dma1_channel1.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_memtomem_dma1_channel1.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_memtomem_dma1_channel1.Init.Mode = DMA_NORMAL;
hdma_memtomem_dma1_channel1.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_memtomem_dma1_channel1) != HAL_OK)
{
Error_Handler();
}
}具体实现代码/main.c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* 注册中断回调函数 */
HAL_DMA_RegisterCallback(&hdma_memtomem_dma1_channel1,
HAL_DMA_XFER_CPLT_CB_ID,
dmaCompleteCallBack);
HAL_DMA_Start_IT(&hdma_memtomem_dma1_channel1, (uint32_t)src, (uint32_t)des, 4);
while (isFinished == 0)
;
printf("%s\r\n", des);
while (1)
{
}
}