拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32学习22--DMA

一、DMA简介1、DMADirect Memory Accss直接存储器访问。数据搬运不占用CPU由DMA控制器完成内存↔外设、内存↔内存数据搬运无需CPU干预。2、12个独立可配置的通道DMA17个通道DMA25个通道3、每个通道都支持软件触发和特定的硬件触发一般存储器之间用软件外设和存储器之间用硬件。4、STM32F103C8T6只有DMA17个通道5、DMA属于AHB外设时钟是RCC_AHBPeriphClockCmd不是APB1/APB2。二、核心硬件1、寄存器DMA每个通道都有一组寄存器以DMA1_Channelx为例寄存器作用对应结构体成员CCR通道配置寄存器使能、方向、循环、地址递增、位宽、优先级、M2M模式DMA_InitTypeDef大部分参数CPAR外设地址寄存器DMA_PeripheralBaseAddrCMAR内存地址寄存器DMA_MemoryBaseAddrCNDTR传输计数寄存器0~65535,剩余要传输的次数DMA_BufferSizeCIFR/CCRIFR中断标志、中断清除寄存器DMA_GetFlagStatus/DMA_ClearFlag注CNDTR总传输次数每搬完1个数据硬件自动-1,。标准库函数DMA_SetCurrDataCounter通道开启时也不生效先关DMA写入DMA_SetCurrDataCounter该函数再打开DMA。即2、3种传输方向1外设→存储器ADC采样、串口接收数组硬件触发2存储器→外设数组发送串口、SPI输出硬件触发以上2种需要外设输出DMA请求信号比如串口TX为空、RX收到字节、ADC转换结束否则DMA只是待命不会主动搬运等外设发出请求才搬运。所以除了配置DMA必须打开外设的DMA请求使能USART_DMACmd()ADC_Cmd()3存储器→存储器M2MSRAM复制软件触发禁止循环模式3、2种工作模式1DMA_Mode_Normal正常模式计数器CNDTR(DMA通道x传输数量寄存器(DMA_CNDTRx)(x 1…7)自减到0通道自动停止再次传输必须先关闭DMA重写BufferSize再使能。即DMA_Cmd(DISABLE)→修改BufferSize→DMA_CmdENABLE。2DMA_Mode_Circular循环模式计数到0硬件自动重装初始计数、初始地址持续循环。适合ADC连续采样、环形缓冲区注M2M内存拷贝不能开启Circular会无限搬运死机3地址递增PerpheralInc外设地址递增外设寄存器 DR固定一般关闭DISABLE;MemoryInc内存地址递增数组缓存一般开启Enable注自增步长数据宽度Byte1HalfWord2Word44数据宽度PerpheralDataSize/MemoryDataSizeByte8bitHalfWord16bitWord32bit源、目标最好宽度一致避免错位5通道优先级4档LOW/Medium/High/VeryHigh多个DMA同时请求时需要仲裁DMA1和2之间没有优先级比较6通道映射每个DMA通道绑定固定外设请求不能随便选通道比如CHI1ADC1CH4USART1_TXCH5USART1_RXCH6USART2_RXCH7USART2_TX同一个通道同一时刻只能服务一个外设。4、标志位函数DMA每个通道有3个事件标志TCTransfer Complete传输完成CNTDR计数到0HTHalf Transfer半传输CNTDR降到一半TETransfer Error传输错误总线访问出错三、DMA结构体uint32_t DMA_PeripheralBaseAddr 外设基地址uint32_t DMA_MemoryBaseAddr存储器基地址uint32_t DMA_DIR传输方向uint32_t DMA_BufferSize 传输次数最大65535CNDTR寄存器uint32_t DMA_PeripheralInc外设地址自增uint32_t DMA_MemoryInc内存地址自增uint32_t DMA_PeripheralDataSize外设位宽uint32_t DMA_MemoryDataSize;内存位宽uint32_t DMA_Mode 模式normal/circularuint32_t DMA_Priority通道优先级uint32_t DMA_M2M内存到内存使能四、代码部分练习1内存到内存拷贝M2MDMA1_Channel1实现每秒11、MyDMA.c文件#include stm32f10x.h // Device header uint16_t MyDMA_Size; void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size) { MyDMA_Size Size; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //DMA初始化 DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_BufferSize Size;//CNDTR初始值 DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC;//外设作为源外设→内存//DMA_DIR_PeripheralDST//外设作为目标内存→外设 DMA_InitStructure.DMA_M2M DMA_M2M_Enable ;//软件触发disable是硬件触发 DMA_InitStructure.DMA_PeripheralBaseAddr AddrA;//外设基地址 DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Enable; DMA_InitStructure.DMA_MemoryBaseAddr AddrB ;//存储器基地址 DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_Priority DMA_Priority_Medium; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_Init(DMA1_Channel1,DMA_InitStructure); //先关闭 DMA_Cmd(DMA1_Channel1,DISABLE); } void MyDMA_Transfer() { DMA_Cmd(DMA1_Channel1,DISABLE); DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size); DMA_Cmd(DMA1_Channel1,ENABLE); while(DMA_GetFlagStatus(DMA1_FLAG_TC1) RESET); DMA_ClearFlag(DMA1_FLAG_TC1); }2、MyDMA.h文件#ifndef __MYDMA_H #define __MYDMA_H void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size); void MyDMA_Transfer(); #endif3、main函数#include stm32f10x.h // Device header #include Delay.h #include MyDMA.h #include OLED.h 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); OLED_ShowString(1,1,DataA); OLED_ShowString(3,1,DataB); OLED_ShowHexNum(1,8,(uint32_t)DataA,8); OLED_ShowHexNum(3,8,(uint32_t)DataB,8); 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); while(1) { 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(); 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); } }4、注意事项1时钟是AHB不是APB。2通道选错每个通道绑定固定外设不能随便选一个通道就用ADC/USART看参考手册3开启DMA通道后修改BufferSize必须先DMA_CmdDISABLE否则CNDTR无效4M2M模式开始Circular循环直接死循环5Normal模式传输完成CNDTR变成0通道不再响应硬件请求想要再次传输必须先关闭DMA再重写BufferSize然后使能DMA_Cmd6内存地址对齐Word32位传输内存数组尽量4字节对齐否则会异常练习2ADC1配合DMA循环采样外设→内存循环模式DMA不停把ADC转换结果写入数组CNDTR到0后自动重装CPU随时读取数组的值拿到采样值不需要中断1、AD.c文件#include stm32f10x.h // Device header uint16_t AD_Value[4]; void AD_Init() { RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //ADC分频最大14MHz RCC_ADCCLKConfig(RCC_PCLK2_Div6); //GPIOA配置模拟输入 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); 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 ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_ContinuousConvMode ENABLE; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right ; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None ;//不使用外部触发使用软件触发 ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_NbrOfChannel 4; ADC_InitStructure.ADC_ScanConvMode ENABLE; ADC_Init(ADC1,ADC_InitStructure); //DMA初始化 DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_BufferSize 4;// DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC;/// DMA_InitStructure.DMA_M2M DMA_M2M_Disable ;// DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR;// DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord;// DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable;// DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)AD_Value ;// DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord;// DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable;// DMA_InitStructure.DMA_Priority DMA_Priority_Medium;// DMA_InitStructure.DMA_Mode DMA_Mode_Circular;// DMA_Init(DMA1_Channel1,DMA_InitStructure);//ADC1只能通道1 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);//软件触发转换 }2、AD.h文件#ifndef __AD_H #define __AD_H void AD_Init(); extern uint16_t AD_Value[4]; #endif3、main函数#include stm32f10x.h #include Delay.h #include AD.h #include OLED.h int main() { OLED_Init(); AD_Init(); OLED_ShowString(1,1,AD0:); OLED_ShowString(2,1,AD1:); OLED_ShowString(3,1,AD2:); while(1) { OLED_ShowNum(1,5,AD_Value[0],4); OLED_ShowNum(2,5,AD_Value[1],4); OLED_ShowNum(3,5,AD_Value[2],4); Delay_ms(100);//防止转换快数值闪烁 } }4、注意事项1循环模式下不要轮询TC完成标志TC会反复置1。2开启外设DMA使能DMA配置完还要调用ADC_DMACmd打开外设DMA请求信号。五、中断1、DMA没有独立的状态寄存器状态全部在中断状态寄存器ISR分两类1状态标志硬件状态位只读不管开不开中断都会置1就算关闭DMA所有中断DMA硬件运行事件发生标志位同样会变2中断使能位CCR寄存器控制是否产生中断输出开中断CCR寄存器里对应中断使能位要置1才会输出到NVIC产生中断如果不开中断可以轮询读取ISR的标志位。3清除中断只能IFCR不能直接写ISR2、轮询VS中断轮询查询硬件状态标志不开启DMA中断不开NVIC不开CCR里的TCIE允许传输完成中断只读ISR的状态标志ISR.TCIFx读取通道x传输完成状态标志TCIF返回SET/RESET库函数FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);清除状态标志函数void DMA_ClearFlag(uint32_t DMAy_FLAG);中断允许中断配置NVIC开DMA写中断服务函数在中断服务函数里依然是读取ISR标志清除IFCRvoid DMA1_Channel1_IRQHandler() { if(DMA_GetITStatus(DMA1_IT_TC1)SET) { //其他代码 DMA_ClearITPendingBit(DMA1_IT_TC1); } }二者区别DMA_GetFlagStatus()只读取ISR硬件状态不看CCR中断使能位适合轮询模式DMA_GetITStatus()读ISR状态标志并且同时判断CCR寄存器对应中断使能位是否打开适合中断模式本文完
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门