STM32学习记录(八):DMA
什么是DMA?
DMA在之前的学习中已经用过了。那么,什么是DMA?
Direct memory access (DMA) is used in order to provide high-speed data transfer between peripherals and memory as well as memory to memory. Data can be quickly moved by DMA without any CPU actions. This keeps CPU resources free for other operations.
直接内存访问(Direct memory access:DMA)可用于外设和内存、内存和内存之间的高速数据传输。数据可以在没有CPU的参与下被快速移动,这使得CPU资源可用于其他操作。简而言之,DMA能减轻CPU的压力。
DMA框图
STM32F103C8T6提供了1个7通道的DMA,其结构框图如下:
每个通道都可以处理位于固定地址的外围寄存器和存储器地址之间的DMA传输,也可以实现内存到内存(Memory-to-memory)模式的DMA传输。
不同的外设使用的DMA通道可能不一样,比如,ADC1只能向DMA1的通道1发送请求、TIM1_UP(TIM1发生更新时)向DMA1的通道5发送请求,如下表所示:
DMA示例
DMA配置的流程图
下面是没有使用DMA中断的配置流程图:
DMA+ADC1读取内部温度传感器
见另一篇博客:STM32学习记录(七):ADC
DMA使用M2M模式实现数据搬运
DMA有内存到内存(Memory to Memory)模式,使用DMA将数据从一个内存地址搬运到另一个内存地址,传输的数据大小可以是8位(字节)、16位(半字)、32位(字)。DMA初始化结构体有几个参数要注意:
DMA_PeripheralBaseAddr
:默认是外设地址,在M2M模式下是内存的地址DMA_MemoryBaseAddr
:内存的地址DMA_PeripheralDataSize
:外设数据大小,8位、16位或32位DMA_BufferSize
:缓冲区数据个数DMA_DIR
:数据搬运的方向。方向可为:外设--->内存、外设<----内存DMA_PeripheralInc
:外设地址是否递增,取决于数据的个数,如果用一个长度为8的数组,地址就需要递增DMA_MemoryInc
:内存地址是否递增,取决于数据的个数DMA_Mode
:有常规(normal)模式和循环(circular)模式,当DMA使用内存到内存模式时,只能选择常规模式
对于DMA1中断,有几种中断源:
DMA1_IT_GL1
,DMA1通道1全局中断DMA1_IT_TC1
,DMA1通道1传输完成中断DMA1_IT_HT1
,DMA1通道1传输一半中断DMA1_IT_TE1
,DMA1通道1传输错误中断
中断处理函数都是先判断外设的中断标志位,再执行相应的操作,最后清除中断标志位。
下面的例子用DMA1将SRC_Buffer
数组的数据搬运到DST_Buffer
数组中,可以直接从DST_Buffer
数组中读取DMA搬运的数据,并通过串口发送出去
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
#include <stdio.h>
#define BufferSize 8
void DMA_Configuration(void);
void RCC_Configuration(void);
void NVIC_Configuration(void);
uint8_t BufferCompare(const uint32_t *buffer1, uint32_t *buffer2, uint32_t buffersize);
const uint32_t SRC_Buffer[BufferSize] = {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20}; //DMA要搬运的数据
uint32_t DST_Buffer[BufferSize]; //DMA搬运的数据的目的内存地址
uint32_t CurrDataCounterEnd = 0x01; //DMA剩余要搬运的数据单元个数,等于0时表示DMA搬运完成
int main(void)
{
COM_Init(); //串口初始化,采样数据
RCC_Configuration(); //配置时钟
NVIC_Configuration(); //配置NVIC
DMA_Configuration(); //配置DMA
/* 等待数据传输完成 */
while(CurrDataCounterEnd != 0)
{
}
/* 判断数据传输是否丢失,返回1就向串口发送数据 */
if(BufferCompare(SRC_Buffer, DST_Buffer, BufferSize))
{
for(int i = 0; i < BufferSize; i++)
{
printf("%lX ", DST_Buffer[i]); //以16进制形式打印
delay_ms(20);
}
}
while (1)
{
}
}
void RCC_Configuration(void)
{
/* 开启DMA时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
}
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn; //使能DMA1通道1中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStructure);
}
/* 比较两个内存地址的数据是否相等 */
uint8_t BufferCompare(const uint32_t *buffer1, uint32_t *buffer2, uint32_t buffersize)
{
while(buffersize--)
{
if(*buffer1 != *buffer2)
return 0;
buffer1++;
buffer2++;
}
return 1;
}
void DMA_Configuration(void)
{
/* 反初始化DMA1通道1 */
DMA_DeInit(DMA1_Channel1);
/* 配置DMA初始化结构体 */
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SRC_Buffer; //配置外设基地址,这里是ADC1数据寄存器的地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DST_Buffer; //配置内存基地址,这里是数据存放的内存地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //外设数据大小,32位, 双ADC模式
DMA_InitStructure.DMA_BufferSize = BufferSize; //缓存大小,以数据单元为一个单位,比如数据是16位,BufferSize = 8表示有8x16位的数据
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设作为数据传输的来源
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //开启DMA的内存到内存模式
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //内存数据大小,32位
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址递增
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //常规模式,即只读取1次数据到缓冲区
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //优先级高
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //外设地址递增
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //初始化DMA
/* 使能DMA1传输完成中断 */
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
DMA_Cmd(DMA1_Channel1, ENABLE); //使能DMA
}
/* DMA1通道1中断处理函数 */
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1) != RESET)
{
CurrDataCounterEnd = DMA_GetCurrDataCounter(DMA1_Channel1); //DMA还需传输的数据单元个数
DMA_ClearITPendingBit(DMA1_IT_TC1); //清除TC标志位
}
}