STM32学习22--DMA

发布时间:2026/10/5 5:26:02

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寄存器对应中断使能位是否打开适合中断模式本文完
延伸阅读

更多相关文章

2026/10/2 16:27:35

十分钟精通《三步擒龙》策略:全套指标解析

** 台上一分钟台下十年功**,虽然《三步擒龙》策略战法集成了多年的私募机构投资经验,但是如果没有彻底搞懂策略战法的使用,到最后也会竹篮打水一场空,只有彻底精通掌握《三步擒龙》策略战法才能对待主力量化交易知己知彼&#xff…

2026/9/29 14:21:19

Java入门零基础指南:手把手教你搭建第一个可运行的Java项目

很多刚接触编程的零基础小伙伴,首选的入门语言基本都是Java。作为目前就业生态最完善、岗位需求最多的编程语言,Java稳定性强、应用场景广,不管是后端开发、软件开发还是企业项目搭建,都占据着举足轻重的地位。但我发现绝大多数新…

2026/10/4 11:48:45

Java微服务实战:基于Spring Cloud Alibaba的全链路架构搭建

接触Java开发的朋友都清楚,现在企业后端开发早已告别传统单体架构。早些年我们做项目,所有业务代码、逻辑处理、接口服务全部堆砌在一个工程里,开发简单、初期迭代快,但项目体量变大后,各种问题接踵而至。代码臃肿混乱…

2026/10/5 5:22:22

PyQt5+OpenCV实现暗通道先验图像去雾:从原理到桌面工具

简介:这是一份基于PyQt5与OpenCV的暗通道先验图像去雾系统毕业设计源码包,面向计算机、人工智能、电子信息等专业学习者,可用于课程实践、毕业设计或科研参考。系统以经典暗通道先验理论为核心,借助NumPy与OpenCV完成透射率估计、…

2026/10/5 5:22:22

STM32软件模拟IIC驱动AHT21B温湿度传感器实战

前阵子有个做环境监控的活儿,需要在一款基于 STM32 的主控板上加一路温湿度采集,传感器选来选去,最后定了 AHT21B。这个芯片精度不错,成本也低,通信接口是 IIC。不过实际用的时候,板子上的两个硬件 I2C 外设…

2026/10/5 5:22:22

暗通道先验去雾实战:PyQt5+OpenCV桌面系统开发与参数调优

简介:这是一套基于PyQt5与OpenCV的暗通道先验图像去雾系统毕业设计项目,面向计算机视觉、人工智能及电子信息工程等专业的学生与研究者,可作为课程实践、毕业设计或科研项目的参考方案。项目以Python为核心,结合numpy数值计算库&a…

2026/10/5 5:22:22

大模型API Token成本计算实战:Python脚本与优化指南

1. 从一次账单异常说起:为什么Token成本值得单独算一笔账上个月帮一个朋友看他团队的API账单,发现一个很有意思的现象:他们做的是一个文档摘要类的小工具,日活不高,请求量也不算夸张,但月度费用比预期高出了…

2026/10/5 5:22:22

MCGS触摸屏Modbus批量读取优化:从原理到配置,解决画面刷新慢

遇到过这样一个现场:一台MCGS触摸屏通过RS485接了一台变频器,画面上放了电压、电流、频率、母线电压、温度等20多路实时数据,运行后数值刷新总慢半拍,切换页面明显卡顿。现场工程师怀疑触摸屏性能不行,换了个更贵的型号…

2026/10/5 5:17:21

Linux下迈德威视工业相机接入OpenCV的完整指南

做机器视觉项目,最绕不开的一环就是把工业相机“喂”给图像处理库。我最近在Linux环境下做一个视觉检测的方案,相机用的是迈德威视(MindVision),图像处理这边选OpenCV,说实话这条链路不算难,但坑…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑