1 DMA简介
-
DMA:Data Memory Access,直接存储器访问,主要功能是可以将数据从一个地方搬运到另一个地方,且不占用CPU内存。
-
DMA1:有7个通道,可以实现P->M外设到内存(ADC)、M->P内存到外设、M->M内存到内存。
-
DMA2:只存在于大容量设备中,有5个通道,可以实现P->M外设到内存(ADC)、M->P内存到外设、M->M内存到内存。
2 DMA框图
- 数据发送方给DMA控制器发送DMA请求,DMA收到请求后,启动DMA传输。
- 在参考手册中有不同的DMA请求映像。
- 其中ADC3和SDIO、TMI8只在大容量产品中。
-
DMA有12个管道,7个DMA1,5个DMA2,每个通道可以接收多个外设的请求,但一个时间只能接受一个。
-
多个DMA通道请求时,需要仲裁器判断响应先后。
-
仲裁器管理分为两个阶段:
- 第一阶段属于软件阶段, 可以在DMA_CCRx 寄存器中PL位设置,有 4 个等级:非常高、高、中和低四个优先级。
- 第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高, 比如通道 0 高于通道 1。 在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。
3 初始化结构体
typedef struct
{
/*数据从哪里来到哪里去*/
uint32_t DMA_PeripheralBaseAddr; // 外设地址 DMA_CPARx 32位,将地址写在该寄存器,一般来自GPIO的IDR寄存器。
uint32_t DMA_MemoryBaseAddr; // 存储器地址 DMA_CMARx 32位,数据从内存来则将地址写到该处。
uint32_t DMA_DIR; // 传输方向 DMA_CCRx中的DIR第4位,0从外设读,1从内存读。 M->M由位14MEM2MEM来决定。
/*数据传多少,单位是什么*/
uint32_t DMA_BufferSize; // 传输数目 DMA_CNDTRx低16位有效,一次可以传65536个数据
uint32_t DMA_PeripheralInc; // 外设地址增量模式 DMA_CCRx 6位PINC 置1递增
uint32_t DMA_MemoryInc; // 存储器地址增量模式 DMA_CCRx 7位MINC 置1递增
uint32_t DMA_PeripheralDataSize; // 外设数据宽度 DMA_CCRx的10、11位PSIZE来决定 00 8位 、01 16位 、10 32位
uint32_t DMA_MemoryDataSize; // 存储器数据宽度 DMA_CCRx的8、9位MSIZE来决定 00 8位 、01 16位 、10 32位
// 备注:当传输宽度不一致时会发生错误,在参考手册的可编程的数据传输宽度、对齐方式和数据大小端表中。
/*什么时候传输结束*/
uint32_t DMA_Mode; // 模式选择 DMA_CCRx 5位CIRC决定 置1循环模式重复发送 需要DMA_IFCR来判断是否完成
uint32_t DMA_Priority; // 优先级设置 DMA_CCRx 寄存器中PL位设置,有 4 个等级:非常高、高、中和低四个优先级。
uint32_t DMA_M2M; // 传输方向 M->M由DMA_CCRx中的位14MEM2MEM来决定。
}DMA_InitTypeDef;
/*初始化DMA*/
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
/*DMA使能,控制DMA_CCRx的第0位EN置1工作*/
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
/*查看传输是否完成,通常调用TC参数*/
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
4 M to M代码
4.1 流程
1)配置DMA结构体。
2)使能DMA。
4.2 bsp_mtm.h
#ifndef BSP_MTM_H__
#define BSP_MTM_H__
#include "stm32f10x.h"
#define BUFFER_SIZE 32 // 数据尺寸宏
#define MTM_DMA_CLK RCC_AHBPeriph_DMA1 // DAM时钟宏
#define MTM_DMA_Channel DMA1_Channel6 // DAM的通道
#define MTM_DMA_Flag DMA1_FLAG_TC6 // 通道传输完成标志位
extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
extern uint32_t aDST_Buffer[BUFFER_SIZE];
void MTM_DMA_Config(void);
uint8_t Buffer_Cmp(const uint32_t *sBuff, uint32_t *dBuff, uint16_t Len);
#endif
4.3 bsp_mtm.c
#include "bsp_mtm.h"
/* 定义aSRC_Const_Buffer数组作为DMA传输数据源
* const关键字将aSRC_Const_Buffer数组变量定义为常量类型
* 表示数据存储在内部的FLASH中,若有的数据未初始化则全为0
*/
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};
/* 定义DMA传输目标存储器
* 存储在内部的SRAM中
*/
uint32_t aDST_Buffer[BUFFER_SIZE];
//typedef struct
//{
// uint32_t DMA_PeripheralBaseAddr; // 外设地址
// uint32_t DMA_MemoryBaseAddr; // 存储器地址
// uint32_t DMA_DIR; // 传输方向
// uint32_t DMA_BufferSize; // 传输数目
// uint32_t DMA_PeripheralInc; // 外设地址增量模式
// uint32_t DMA_MemoryInc; // 存储器地址增量模式
// uint32_t DMA_PeripheralDataSize; // 外设数据宽度
// uint32_t DMA_MemoryDataSize; // 存储器数据宽度
// uint32_t DMA_Mode; // 模式选择
// uint32_t DMA_Priority; // 通道优先级
// uint32_t DMA_M2M; // 存储器到存储器模式
//}DMA_InitTypeDef;
void MTM_DMA_Config(void){
// 声明初始化结构体
DMA_InitTypeDef DMA_InitStruct;
// 打开DMA时钟
RCC_AHBPeriphClockCmd(MTM_DMA_CLK,ENABLE);
// 初始化结构体
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer; // 外设地址数组名转32位
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer; // 存储器地址
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC ; // 传输方向
DMA_InitStruct.DMA_BufferSize = BUFFER_SIZE; // 传输多少个
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 外设地址递增
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; // 外设数据尺寸
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; // 内存数据尺寸
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; // 循环发送模式
DMA_InitStruct.DMA_Priority = DMA_Priority_High; // 优先级高
DMA_InitStruct.DMA_M2M = DMA_M2M_Enable; // 打开MTM
// 初始化
DMA_Init(MTM_DMA_Channel,&DMA_InitStruct); // MTM可以任何通道
// 清楚对应标志位
DMA_ClearFlag(DMA1_FLAG_TC6);
// DMA使能
DMA_Cmd(MTM_DMA_Channel,ENABLE); // 使能DMA
}
// 比较传输结过
uint8_t Buffer_Cmp(const uint32_t *sBuff, uint32_t *dBuff, uint16_t Len){
while(Len --){
if(*sBuff != *dBuff){
// 数据不相等
return 0;
}
// 地址递增
sBuff ++;
dBuff ++;
}
return 1; // 当比较结束时,返回1
}
4.3 main.c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_mtm.h"
int main(void){
MTM_DMA_Config();
LED_Config();
// 检测传输是否完成
while(DMA_GetFlagStatus(MTM_DMA_Flag) == RESET);
if(Buffer_Cmp(aSRC_Const_Buffer, aDST_Buffer,BUFFER_SIZE)){
LED_B(OFF);
LED_R(OFF);
LED_G(ON);
}else{
LED_B(OFF);
LED_G(OFF);
LED_R(ON);
}
while(1){
}
}
5 M to P代码
5.1 流程
1)初始化UART。
2)初始化DMA。
3)清除DMA的TC标志位。
4)在主函数中通过USART_DMACmd来发送,DMA_GetFlagStatus判断是否结束。
5)如果不判断是否结束,马上调用USART则会覆盖内容。
6)MTP的USART的地址不能递增,且DIR的外设为dest。
5.2 bsp_mtp.h
#ifndef BSP_MTP_H__
#define BSP_MTP_H__
#include "stm32f10x.h"
#include <stdio.h>
// 内存数据
#define SENDBUFF_SIZE 5000
extern uint8_t SendBuff[SENDBUFF_SIZE];
// DMA宏定义
#define MTP_DMA_CLK RCC_AHBPeriph_DMA1 // DAM时钟宏
#define MTP_DMA_Channel DMA1_Channel4 // TX对应通道
#define MTP_DMA_Flag DMA1_FLAG_TC4 // 通道传输完成标志位
// USART宏 外设地址
#define USART_DR_ADDRESS (USART1_BASE+0x04)
// 移植串口
#define DEBUG_USARTx USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
#define DEBUG_USART_GPIO_CLK RCC_APB2Periph_GPIOA
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART_IRQ USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandler
/*初始化USART*/
void USART_Config(void);
void USART_SendByte(uint8_t Data);
/*DMA初始化*/
void MTP_DMA_Config(void);
#endif
5.3 bsp_mtp.c
#include "bsp_mtp.h"
/*串口移植:不要中断*/
/*初始化USART*/
void USART_Config(void){
// 两个结构体
USART_InitTypeDef USART_InitStruct;
GPIO_InitTypeDef GPIO_InitStruct;
// 打开GPIO时钟
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK,ENABLE);
// 打开串口时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK,ENABLE);
// 配置TX的GPIO结构体
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT,&GPIO_InitStruct);
// 配置RX的GPIO结构体
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT,&GPIO_InitStruct);
// 配置串口初始化结构体
USART_InitStruct.USART_BaudRate = DEBUG_USART_BAUDRATE;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(DEBUG_USARTx,&USART_InitStruct);
USART_Cmd(DEBUG_USARTx,ENABLE);
}
void USART_SendByte(uint8_t Data){
USART_SendData(DEBUG_USARTx,Data);
while(USART_GetFlagStatus(DEBUG_USARTx,USART_FLAG_TXE) == RESET);
}
int fputc(int ch, FILE *f){
USART_SendByte((uint8_t)ch);
while(USART_GetFlagStatus(DEBUG_USARTx,USART_FLAG_TXE) == RESET);
return ch;
}
// DMA配置
void MTP_DMA_Config(void){
// 声明初始化结构体
DMA_InitTypeDef DMA_InitStruct;
// 打开DMA时钟
RCC_AHBPeriphClockCmd(MTP_DMA_CLK,ENABLE);
// 初始化结构体
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)USART_DR_ADDRESS; // 外设地址
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)SendBuff; // 存储器地址
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST ; // 传输方向
DMA_InitStruct.DMA_BufferSize = SENDBUFF_SIZE; // 传输多少个
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据尺寸
DMA_InitStruct.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; // 内存数据尺寸
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; // 不循环发送模式
DMA_InitStruct.DMA_Priority = DMA_Priority_High; // 优先级高
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; // 关闭MTM
// 初始化
DMA_Init(MTP_DMA_Channel,&DMA_InitStruct); // 传递到外设只能用专门通道
// 清楚对应标志位
DMA_ClearFlag(MTP_DMA_Flag);
// DMA使能
DMA_Cmd(MTP_DMA_Channel,ENABLE); // 使能DMA
}
5.4 main.c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_mtp.h"
uint8_t SendBuff[SENDBUFF_SIZE];
int main(void){
uint16_t i = 0;
for(i = 0;i < SENDBUFF_SIZE;i++){
SendBuff[i] = 'a';
}
USART_Config();
MTP_DMA_Config();
// 打开DMA与UART时钟 STM32F10X_HD_VL才开用UART5
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);
while(DMA_GetFlagStatus(MTP_DMA_Flag) == RESET);
/*立马调用printf会覆盖*/
printf("\n传输完成\n");
while(1){
}
}