STM32 Getting Started Tutorial (DMA)

Reference tutorial:[8-1] DMA direct memory access_bilibili_bilibili

1. DMA (Direct Memory Access) direct memory access:

(1) DMA can provide high-speed data transmission between peripherals and memory or memory and memory without CPU intervention, saving CPU resources.

(2) 12 independently configurable channels: DMA1 (7 channels), DMA2 (5 channels).

(3) Each channel supports software triggering and specific hardware triggering.

(4) DMA resources of STM32F103C8T6: DMA1 (7 channels).

2. Memory image:

type

initial address

memory

use

ROM (not lost when power is off)

0x0800 0000

Program memory Flash

Store compiled program code and constant data in C language (such as const-modified variables)

0x1FFF F000

system memory

Store BootLoader for serial port download

0x1FFF F800

option byte

Store some configuration parameters independent of program code

RAM (lost when power off)

0x2000 0000

Running memory SRAM

Store temporary variables during operation

0x4000 0000

Peripheral register

Store configuration parameters of each peripheral

0xE000 0000

Core Peripheral Registers

Stores the configuration parameters of each peripheral of the kernel

3. DMA block diagram:

(1) Except for the kernel in the upper left corner, other modules can basically be regarded as memories.

(2) In order to access memory efficiently and orderly, STM32 designed a bus matrix. The left end of the bus matrix is ​​the active unit, which has access rights to the memory. The right end is the passive unit, and their memory can only be read by the active unit on the left. Write.

①The kernel can access the memory on the right through DCode and the system bus. The DCode bus is specifically used to access Flash, and the system bus accesses other memories.

② Since DMA needs to transfer data, DMA must also have the initiative to access. DMA can access the memory on the right through the DMA bus. There are several channels in DMA, and the source address and destination address of each channel's data transfer can be set separately, so that they can work independently from the CPU. The arbiter is used to decide which channel uses the DMA bus. Channels in the same DMA can only time-share one DMA bus. If a conflict occurs, the arbiter It will be allocated according to the priority of the channel.

③ There is also an arbiter in the bus matrix. If both DMA and CPU need to access the same target, the CPU's access will be suspended, but the bus arbiter will ensure that the CPU gets half of the bus bandwidth so that the CPU can work normally.

④If the CPU or DMA directly accesses Flash, it can only read Flash; SRAM is running memory and can be read and written at will.

(3) There is an AHB slave device in the DMA. This is the register of the DMA itself, which is connected to the AHB bus. The CPU can enter the AHB bus through the system bus, so as to DMA is configured.

(4)The peripheral on the right can send a request to DMA, which triggers DMA to transfer data. (When configuring peripherals, you need to open the DMA channel to send requests to DMA)

4. DMA basic structure:

(1) DMA'sdata transfer direction can be from peripheral to memory, or from memory to peripheral, or It's memory to memory.

(2) The starting point and end point of data transfer have three related parameters:

①The first parameter isstarting address, which points to the address of the first data that needs to be transferred and the address responsible for receiving the data. address.

②The second parameter isdata width. This parameter specifies how many bits of data are transferred at a time.

③The third parameter iswhether the address is incremented. It specifies whether the address needs to be moved in the next transfer after one data transfer is completed. To the next position, the specificmove step size is determined by the data width. (Generally, the register side does not need to increment the address, but the memory side requires )

(3)The transmission counter is a decrementing counter, which is used to specify the number of data transfers. When the counter value is 0, the starting point /The "auto-increment address" of the end point will be restored to the starting address to prepare for the next data transfer;The automatic reloader determines whether to restore the counter value to the initial value when it reaches 0< /span> (after one round of transfer, the value of the transfer counter is set to the initial value, and DMA immediately starts the next round of transfer). Loop mode (the value of the transfer counter will not be automatically reset after the transfer is completed, and the DMA will temporarily stop working) and single-shot mode, corresponding to

(4)DMA has two triggering methods: software trigger and hardware trigger. When M2M=1, DMA selects software trigger; when M2M=0, DMA selects hardware trigger.

①The logic of software triggering here is not to call a certain function to trigger once, but to trigger continuously at the fastest speed within the range that DMA can bear (software triggering and loop mode cannot be used at the same time).

② Generally, the data transfer between memory and memory is triggered by software selection.

③The hardware trigger source can choose ADC, serial port, timer, etc. When using these modules, choose hardware trigger.

DMA receives a trigger, and the transmission counter decreases by one time.

(5)DMA requires switch control to start.

(6)Conditions for DMA transfer data:

①DMA_Cmd使能DMA

The value of the transmission counter must be greater than 0.

③ Demand exists Touch signal.

(7)When the transfer counter is equal to 0 and there is no automatic reloading, DMA will not process it regardless of whether there is a trigger signal or not. Transshipment, then needs to use DMA_Cmd to turn off DMA, then give the transmission counter a value greater than 0, and then use DMA_Cmd to enable DMA to start the next transshipment.

5. DMA request:

(1) When DMA is triggered by hardware, the peripheral requesting DMA needs to open its own channel to DMA, for example, ADC1 needs to DMA sends a request, then when configuring ADC1, you need to open the channel between ADC1 and DMA.

(2) Each channel of DMA corresponds to a different hardware trigger source and cannot be matched at will.

6. Data width and alignment:

7. DMA data transfer:

(1) See the figure below, transfer the array DataA in SRAM to another array DataB in SRAM:

①The starting address of the starting point is the first address of the DataA array, and the starting address of the end point is the first address of the DataB array; the width of the transferred data is 8 bits, and each transfer transfers exactly one element; obviously the address should be incremented during the transfer process.

②The direction parameter should represent the data transfer of DataA to DataB.

③The Data array has 7 elements, and the initial value of the transmission counter should be 7. Automatic reloading is not required (a simple value copy only needs to be done once).

④The data transfer between memory and memory can be triggered by software.

(2) Connect the lines as shown in the figure below,and copy the OLED display project folder as a template for use .

(3) Add the MyDMA.h file and MyDMA.c file to the System group of the project to encapsulate the code of the DMA module.

①MyDMA.h file:

#ifndef __MyDMA_H
#define __MyDMA_H

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);

#endif

②MyDMA.c file:

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;    //记录传入的Size值(一轮转运的数据个数/传输计数器初值)

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
	MyDMA_Size = Size;  //记录传入的Size值(一轮转运的数据个数/传输计数器初值)
	
	//开启DMA的时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	//初始化传输参数
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;  //起点的起始地址(起点的基地址)
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //每次转运1个字节
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;  //起点的地址自增
	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;      //终点的起始地址(终点的基地址)
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;          //每次转运1个字节
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;          //终点的地址自增
	DMA_InitStructure.DMA_BufferSize = Size;           //传输计数器初值
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;      //单次模式(计数器自减为0,一轮转运结束)
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设站点(DMA_PeripheralBaseAddr)作为数据源(方向参数)
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;        //使用软件触发(DMA开启后,只要传输计数器的值不为0,转运会一直进行)
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;  //指定优先级(本例对优先级无要求)
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	//DMA1_Channel1:选择DMA1的通道1进行AddrA->AddrB的数据转运
	
	//暂时不使能DMA,需要数据转运时调用MyDMA_Transfer函数即可
	//DMA_Cmd(DMA1_Channel1, ENABLE);
}

void MyDMA_Transfer(void)
{
	DMA_Cmd(DMA1_Channel1, DISABLE);  //失能DMA(修改传输计数器前需要关闭DMA)
	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);  //修改传输计数器的值(置为初值)
	DMA_Cmd(DMA1_Channel1, ENABLE);   //使能DMA
	//传输计数器的值被置为初值,由于是软件触发模式,DMA直接开始一轮数据转运
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);   //等待一轮转运完成,传输计数器自减为0后标志位会被置为1
	DMA_ClearFlag(DMA1_FLAG_TC1);     //清空标志位,为下一轮转运做准备
}

(4) There are DMA related functions in the stm32f10x_dma.h file.

[1]DMA_DeInit function: restore default configuration.

[2]DMA_Init function: Initialize DMA.

[3]DMA_StructInit function: Assign a default value to the structure.

[4]DMA_Cmd function: Enable DMA (and also turn off DMA).

[5]DMA_ITConfig function: interrupt output enable.

[6]DMA_SetCurrDataCounter function: Write data to the DMA transfer counter.

[7]DMA_GetCurrDataCounter function: Returns the value of the DMA transfer counter.

[8]DMA_GetFlagStatus function: Get the status flag bit.

[9]DMA_ClearFlag function: Clearance check position.

[10]DMA_GetITStatus function: Get the interrupt status.

[11]DMA_ClearITPendingBit function: clear the interrupt pending bit.

(5) Paste the following code in the main.c file, then compile it, download the program to the development board, and observe the display of the OLED screen.

#include "stm32f10x.h"                  // Device headerCmd
#include "OLED.h"
#include "MyDMA.h"
#include "Delay.h"

//DataA中的数据需要转运到DataB中
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
uint8_t DataB[] = {0, 0, 0, 0};

int main()
{
	OLED_Init();
	MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
	//将DMA的通道1配置为用于实现DataA->DataB的数据转运,一共转运4个数据)
	
	OLED_ShowString(1, 1, "DataA:");
	OLED_ShowString(3, 1, "DataB:");
	OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);  //显示数组DataA的地址
	OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);  //显示数组DataB的地址
	//STM32中的地址都是32位的
	//OLED_ShowHexNum不能接收地址类型数据,需要强制类型转换
	
	while(1)
	{
		/*改变DataA数组元素的值*/
		DataA[0] ++;
		DataA[1] ++;
		DataA[2] ++;
		DataA[3] ++;
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
	
		OLED_ShowHexNum(4, 1, DataB[0], 2);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);
		
		Delay_ms(1000);
		
		MyDMA_Transfer();    //启用DMA进行一轮数据转运(本例中和值拷贝的结果一样)
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
	
		OLED_ShowHexNum(4, 1, DataB[0], 2);
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);
		
		Delay_ms(1000);
	}
}

8. ADC scan mode + DMA:

(1) After the ADC is triggered, it will convert the analog voltages on the 7 channels in sequence. After the conversion of each channel is completed, DMA is required to perform a data transfer to transfer the contents of the data register in the ADC to the ADValue in the SARM. Array, each element in the array corresponds to the voltage value of a channel.

①The starting address of the starting point is the address of the data register in the ADC, and the address does not need to be incremented during the transfer process; the starting address of the end point is the first address of the ADValue array, and the address needs to be incremented during the transfer process.

②The transfer data width is 16 bits, and the 16-bit data of the ADC data register can be transferred only once.

③ADC converts a total of 7 channels of analog values, and the initial value of the transmission counter should be 7. If the ADC adopts single scan mode, then the DMA transfer counter does not use automatic reloading, and triggers the ADC next time Just assign a value to the DMA counter when; if the ADC adoptscontinuous scan mode, then the DMA transmission counter is a> (when ADC starts the next round of conversion, DMA should also start the next round of transfer). To use automatic reloading

④The trigger source of DMA should be the hardware trigger of ADC. ADC will generate a DMA request every time it completes the conversion of a channel.

(2) Connect the lines as shown in the figure below,and copy the AD multi-channel project folder as a template for use .

(3) Modify the AD.h file and AD.c file:

①AD.h file:

#ifndef __AD_H
#define __AD_H

void AD_Init(void);
void AD_GetValue(void);

extern uint16_t AD_Value[4];

#endif

②AD.c file: (The code is configured according to ADC single conversion and DMA single transfer mode, and the comments includeADC continuous conversion Description of mode matching DMA loop mode)

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];  //存放ADC1中4个通道的转换结果

void AD_Init(void)
{
	//开启ADC、DMA和GPIO的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	//配置ADCCLK的分频器
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);  //ADCCLK = 72MHz / 6 = 12MHz
	
	//配置PA0~PA3为模拟输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//配置多路开关,将4个通道接入规则组
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);  //通道0接入规则组
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);  //通道1接入规则组
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);  //通道2接入规则组
	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);  //通道3接入规则组
	
	//配置AD转换器
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;     //独立模式
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;   //不选择外部触发源(本例使用软件触发)
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;    //单次转换
	//ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;   可以使用连续转换模式
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;           //扫描模式
	ADC_InitStructure.ADC_NbrOfChannel = 4;                //序列中的通道数为4
	ADC_Init(ADC1, &ADC_InitStructure);
	
	//初始化DMA传输参数
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;   //起点的起始地址是ADC1的数据寄存器
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  //每次转运2个字节(半字,STM32中1个字32位)
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //起点(寄存器)的地址不自增
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;        //终点的起始地址是AD_Value数组首地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;          //每次转运2个字节(半字,STM32中1个字32位)
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;           //终点(存储器)的地址自增
	DMA_InitStructure.DMA_BufferSize = 4;               //传输计数器初值(ADC1配置了4个通道,一共需要转运4个数据)
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;       //单次模式(计数器自减为0,一轮转运结束)
	//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;   ADC连续转换模式需要配合DMA循环模式
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //外设站点(DMA_PeripheralBaseAddr)作为数据源(方向参数)
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;        //使用硬件触发
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;  //指定优先级(本例对优先级无要求)
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	//DMA1_Channel1:ADC1的硬件触发接在DMA1的通道1上,只能选择DMA的通道1
	
	//打开ADC1和DMA间的通道,允许ADC1向DMA发送请求
	ADC_DMACmd(ADC1, ENABLE);
	
	//使能DMA
	DMA_Cmd(DMA1_Channel1, ENABLE);
	
	//开关控制(开启ADC)和校准
	ADC_Cmd(ADC1, ENABLE);
	ADC_ResetCalibration(ADC1);  //复位校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);  //等待复位校准完成(复位完成后该位自动置为0)
	ADC_StartCalibration(ADC1);  //开始校准
	while(ADC_GetCalibrationStatus(ADC1) == SET);       //等待校准完成(校准完成后该位自动置为0)

    //ADC_SoftwareStartConvCmd(ADC1, ENABLE); ADC连续转换模式下可以在初始化时就触发ADC,让ADC不停地进行转换
}

void AD_GetValue(void)  //获取一次ADC1中4个通道的转换结果并存放在AD_Value数组中
{
	//DMA循环模式下AD_Value数组中的值本来就不断更新,可以不需要AD_GetValue函数
	//推荐使用ADC连续转换模式配合DMA循环模式,节省软件资源
	
	DMA_Cmd(DMA1_Channel1, DISABLE);  //失能DMA(修改传输计数器前需要关闭DMA)
	DMA_SetCurrDataCounter(DMA1_Channel1, 4);  //修改传输计数器的值(置为初值)
	DMA_Cmd(DMA1_Channel1, ENABLE);   //使能DMA
	
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);    //软件触发ADC进行转换
	//扫描模式会将4个通道逐个转换一遍,每转换一个通道,数据寄存器更新一次,然后向DMA发送请求
	//DMA将数据寄存器的数据转到AD_Value数组,ADC继续转换下一个通道,DMA传输计数器值-1
	//DMA传输计数器值为0,说明4个通道的数据全部更新到AD_Value数组中,一轮转运完成
	
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);   //等待一轮转运完成,传输计数器自减为0后标志位会被置为1
	DMA_ClearFlag(DMA1_FLAG_TC1);     //清空标志位,为下一轮转运做准备
}

(4) Paste the following code in the main.c file, compile it, download the program to the development board, and debug it according to the comments in the main function.

#include "stm32f10x.h"                  // Device headerCmd
#include "OLED.h"
#include "AD.h"
#include "Delay.h"

int main()
{
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1,1,"AD0:");
	OLED_ShowString(2,1,"AD1:");
	OLED_ShowString(3,1,"AD2:");
	OLED_ShowString(4,1,"AD3:");
	
	while(1)
	{
		AD_GetValue();  //获取一次ADC1中4个通道的转换结果并存放在AD_Value数组中
		
		OLED_ShowNum(1,5, AD_Value[0],4);  //拧动电位器
		OLED_ShowNum(2,5, AD_Value[1],4);  //光敏传感器受到的光照越弱,模拟电压值越高
		OLED_ShowNum(3,5, AD_Value[2],4);  //热敏传感器受热越大,模拟电压值越低
		OLED_ShowNum(4,5, AD_Value[3],4);  //对射式红外传感器感应的光越弱,模拟电压值越高
		Delay_ms(100);  //更新显示的时间间隔
	}
}

Guess you like

Origin blog.csdn.net/Zevalin/article/details/134745164