DMA Data Transfer on STM32F103: Memory-to-Memory and ADC Scanning Modes
DMA Overview
DMA (Direct Memory Access) enables high-speed data transfers directly between peripherals and memory or between memory regions without CPU intervention. This architecture significantly reduces processor overhead, allowing the CPU to专注于处理任务.
The STM32F103 series implements two DMA controllers with a combined total of 12 independently configurable channels: DMA1 provides 7 channels while DMA2 offers 5 channels. The STM32F103C8T6 microcontroller, a popular choice in the "Blue Pill" development board, contains only DMA1 with 7 available channels.
Each DMA channel supports both software triggering and hardware-triggered transfers initiated by specific peripheral requests.
Memory Map
Understanding STM32 memory organization is essential for configuring DMA transfers. The memory map defines addresses for flash, SRAM, and peripheral registers, allowing precise specification of source and destination addresses during DMA configuration.
DMA Architecture
The DMA controller connects directly to the AHB bus matrix, enabling concurrent data transfers without competing with the CPU for bus access. Each channel has dedicated request lines connected to specific peripherals, allowing automatic transfer initiation when peripheral flags are set.
DMA Configuration Structure
Configuring DMA requires setting several parameters:
| Parameter | Description |
|---|---|
| Peripheral Base Address | Source or destination address depending on transfer direction |
| Memory Base Address | Complementary address (destination for peripheral-to-memory, source for memory-to-peripheral) |
| Transfer Direction | Defines data flow: peripheral-to-memory or memory-to-memory |
| Data Width | Configurable for byte (8-bit), half-word (16-bit), or word (32-bit) transfers |
| Address Increment | Enables sequential access by incrementing addresses after each transfer |
| Transfer Count | Number of data units to transfer before completion |
| Transfer Mode | Normal mode (single transfer) or circular mode (continuous looping) |
| Priority | Software-configurable priority levels for simultaneous channel requests |
DMA Request Mapping
Each DMA channel connects to specific peripheral request lines. For example, DMA1 Channel 1 handles ADC1 requests, while Timer update events, USART TX/RX, and SPI transfers each map to designated channels. Proper channel selection ensures peripherals trigger the correct DMA instance.
Data Width and Alignment
DMA supports three data width configurations: byte (8-bit), half-word (16-bit), and word (32-bit). When source and destination widths differ, the DMA controller automatically handles data packing and unpacking. Misaligned transfers also work correctly—the controller manages boundary crossings transparently.
Memory-to-Memory Transfer Implementation
The following implementation demonstrates DMA-based memory transfer using software triggering:
#include "stm32f10x.h"
static uint16_t transferLength;
void DMA_Memory_Init(uint32_t sourceAddr, uint32_t destAddr, uint16_t length)
{
transferLength = length;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeDef config;
config.DMA_PeripheralBaseAddr = sourceAddr;
config.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
config.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
config.DMA_MemoryBaseAddr = destAddr;
config.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
config.DMA_MemoryInc = DMA_MemoryInc_Enable;
config.DMA_DIR = DMA_DIR_PeripheralSRC;
config.DMA_BufferSize = length;
config.DMA_Mode = DMA_Mode_Normal;
config.DMA_M2M = DMA_M2M_Enable;
config.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &config);
}
void DMA_StartTransfer(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, transferLength);
DMA_Cmd(DMA1_Channel1, ENABLE);
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "DMA_Memory.h"
uint8_t SourceData[] = {0x10, 0x20, 0x30, 0x40};
uint8_t DestData[] = {0x00, 0x00, 0x00, 0x00};
int main(void)
{
OLED_Init();
DMA_Memory_Init((uint32_t)SourceData, (uint32_t)DestData, 4);
OLED_ShowString(1, 1, "Source");
OLED_ShowString(3, 1, "Dest");
OLED_ShowHexNum(1, 8, (uint32_t)SourceData, 8);
OLED_ShowHexNum(3, 8, (uint32_t)DestData, 8);
while (1)
{
SourceData[0]++;
SourceData[1]++;
SourceData[2]++;
SourceData[3]++;
OLED_ShowHexNum(2, 1, SourceData[0], 2);
OLED_ShowHexNum(2, 4, SourceData[1], 2);
OLED_ShowHexNum(2, 7, SourceData[2], 2);
OLED_ShowHexNum(2, 10, SourceData[3], 2);
OLED_ShowHexNum(4, 1, DestData[0], 2);
OLED_ShowHexNum(4, 4, DestData[1], 2);
OLED_ShowHexNum(4, 7, DestData[2], 2);
OLED_ShowHexNum(4, 10, DestData[3], 2);
Delay_ms(1000);
DMA_StartTransfer();
Delay_ms(1000);
}
}
ADC Scanning Mode with DMA
Combining ADC scanning mode with DMA creates an efficient multi-channel acquisition system. The DMA controller automatically stores each ADC conversion result to consecutive memory locations:
#include "stm32f10x.h"
volatile uint16_t adcResults[4];
void ADC_MultiChannel_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitTypeDef gpioConfig;
gpioConfig.GPIO_Mode = GPIO_Mode_AIN;
gpioConfig.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
gpioConfig.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpioConfig);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
ADC_InitTypeDef adcConfig;
adcConfig.ADC_Mode = ADC_Mode_Independent;
adcConfig.ADC_DataAlign = ADC_DataAlign_Right;
adcConfig.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
adcConfig.ADC_ContinuousConvMode = ENABLE;
adcConfig.ADC_ScanConvMode = ENABLE;
adcConfig.ADC_NbrOfChannel = 4;
ADC_Init(ADC1, &adcConfig);
DMA_InitTypeDef dmaConfig;
dmaConfig.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
dmaConfig.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
dmaConfig.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
dmaConfig.DMA_MemoryBaseAddr = (uint32_t)adcResults;
dmaConfig.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
dmaConfig.DMA_MemoryInc = DMA_MemoryInc_Enable;
dmaConfig.DMA_DIR = DMA_DIR_PeripheralSRC;
dmaConfig.DMA_BufferSize = 4;
dmaConfig.DMA_Mode = DMA_Mode_Circular;
dmaConfig.DMA_M2M = DMA_M2M_Disable;
dmaConfig.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &dmaConfig);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
Key implementation details:
- Circular DMA mode ensures continuous data collection synchronized with continuous ADC conversions
- Half-word transfer size matches the 12-bit ADC result width stored in the 16-bit data register
- Memory address increment automatically advances through the results array after each conversion
- Peripheral address fixed since ADC_DR always contains the most recent conversion value
- DMA Channel 1 is mandatory for ADC1 requests on STM32F103 devices