当前位置: 首页 > news >正文

江科大DMA直接存储器访问hal库实现

 DMA 直接存储器访问,通过硬件自动在外设与存储器之间进行数据的传输,不需要CPU的介入,减少了CPU的数据传递这种简单工作的消耗。

例如上次的ADC多通道,我们需要在主函数循环里面不断判断ADC的状态再读取ADC转换结束的值,但是如果我们通过DMA数据转运,把ADC转换完成的数据,存放到一个数组内,就不需要我们在主函数里面对ADC进行任何操作,直接在指定数组里面读取我们需要的数据即可。

减轻了CPU的负担,提高程序的速度。

DMA的相关hal库函数

DMA初始化结构体

typedef struct
{uint32_t Direction;                 /*传输方向*/uint32_t PeriphInc;                 /*外设自增与否*/uint32_t MemInc;                    /*存储器自增与否*/uint32_t PeriphDataAlignment;       /*外设数据对齐方式*/uint32_t MemDataAlignment;          /*存储器数据对齐方式 */uint32_t Mode;                      /*DMA转运模式*/uint32_t Priority;                  /*DMA转运优先级供DMA仲裁使用 */
} DMA_InitTypeDef;

DMA句柄 

typedef struct __DMA_HandleTypeDef
{DMA_Channel_TypeDef        *Instance; /*我们选择的DMA外设地址*/DMA_InitTypeDef            Init;  /*DMA初始化结构体*/HAL_LockTypeDef            Lock; /*DMA句柄锁*/__IO HAL_DMA_StateTypeDef  State; /*DMA传输状态*/void                       *Parent;/*用于存储一个指向父对象的指针,便于对象间的层级关联 */void                       (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);     /*DMA转运完成回调函数 */void                       (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); /*DMA转运过半回调函数 */void                       (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);    /*DMA转运出错回调函数 */void                       (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);    /*DMA转运传输被异常终止回调函数*/__IO uint32_t              ErrorCode; /*DMA错误类型*/DMA_TypeDef                *DmaBaseAddress; /*DMA的基地址*/uint32_t                   ChannelIndex; /*DMA通道索引*/} DMA_HandleTypeDef;    

DMA初始化相关函数 

HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);
HAL_StatusTypeDef HAL_DMA_DeInit (DMA_HandleTypeDef *hdma);

DMA运行函数,前面两个是开启DMA传输函数,之间两个是DMA中断函数,最后一个DMA轮询函数,询问DMA的传输状态。 

HAL_StatusTypeDef HAL_DMA_Start (DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength);
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength);
HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma);
HAL_StatusTypeDef HAL_DMA_Abort_IT(DMA_HandleTypeDef *hdma);
HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, uint32_t CompleteLevel, uint32_t Timeout);

DMA状态获取函数,错误获取函数。 

HAL_DMA_StateTypeDef HAL_DMA_GetState(DMA_HandleTypeDef *hdma);
uint32_t HAL_DMA_GetError(DMA_HandleTypeDef *hdma);

 DMA数据转运

DMA的设置就这么点,和标准库相比,他把原地址和目标地址,传输长度放到了开始函数里面,更加方便我们的数据传输,以及DMA通道的重复利用。

使用memtomem模式,DMA1的通道1,优先级为中断,模式为普通模式,原地址自增,目标地址自增,传输的数据大小都为字节大小。

江科大的DMA初始化函数里面,首先就是失能了DMA,这样一开始初始化的时候就不会直接开始数据传递,容纳后在传输的函数里面,再次失能DMA,在写入传输次数,才开始使能DMA,DMA使能后就开始传输,等待传输完成,清除传输完成标志位。

HAL_DMA_Start() 在循环中重复调用却只执行一次,这是因为 STM32 HAL 库的 DMA 设计机制导致的,按照江科大的写入传输计数器,指定将要转运的次数,本质就是修改CNDTR寄存器,我们对hal库进行修改CNDTR寄存器操作就能正常实现江科大的功能,但是HAL_DMA_Start在正常模式下只能调用一次,按照豆包的说法,就是正常模式调用一次后,硬件自动禁用通道(CCR 的 EN 位清零),但 HAL 库内部状态可能仍标记为 “忙碌”,从而在start函数里面对DMA通道状态的判断的时候出错,导致不能正常开启转运。

if(HAL_DMA_Start(&hdma_memtomem_dma1_channel1, (uint32_t)DataA, (uint32_t)DataB, 4)==HAL_BUSY) DataA[0]+=10;

通过这个判断句,我们可以清晰的看出确实如此,多次调用DMA_Start的时候一直显示的就是DMA在忙碌状态,任何解决这个忙碌状态才是更需要思考的部分。

而直接修改传输次数,在传输完成的时候,CNDTR为0,我们修改了传输次数,DMA就继续传输,而不会对DMA的状态进行判断,跳过了DMA的状态判断,进而能够完成数据的传输。

初始化的校准部分我们通过

HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef* hadc)

来完成,这是在ADC_Ex里面。 

void MyDMA_Transfer(void)
{   __HAL_DMA_DISABLE(&hdma_memtomem_dma1_channel1);// 启动 DMA 传输hdma_memtomem_dma1_channel1.Instance->CNDTR = 4;//if(HAL_DMA_Start(&hdma_memtomem_dma1_channel1, (uint32_t)DataA, (uint32_t)DataB, 4)==HAL_BUSY) DataA[0]+=10;__HAL_DMA_ENABLE(&hdma_memtomem_dma1_channel1);while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1) == RESET)// 清除完成标志__HAL_DMA_CLEAR_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1);
}

在主函数前进行一次DMA的Start,然后在循环里面不断设置传输次数

HAL_DMA_Start(&hdma_memtomem_dma1_channel1, (uint32_t)DataA, (uint32_t)DataB, 4);

把转换函数改成这个hal库的函数即可,其余的不需要我们的操作。

DMA+AD多通道

DMA+AD多通道就是DMA在外设与寄存器中间进行数据传输的最好例子,也是DMA帮助外设传递数据不需要CPU干预的最好验证。

因为我们模式是连续转换模式,扫描模式,所以配置ADC的时候就得把转换通道修改成我们所用到的四个通道,并对通道的序号进行排序扫描。他就会按照通道0,1,2,3,这样的顺序进行扫描,再回到0123不断进行,再扫描结束后会把转换结果发送到ADC规则组存储器,DMA在这个时候取走数据然后传输到我们指定的位置即可,所以对于原地址,不需要自增,目标地址就需要不断自增,没转换完成一次,就自增一次,然后全部转换完成又重新从头开始转换,DMA转运的地址也重新从头开始传输。

DMA的配置 ,这边数据的长度变成了半字(16位),且外设地址不自增,存储器地址自增,模式也改成了循环模式。

二配置完成后,我们先在主函数前面校准一次ADC,再开启一次ADC_DMA转换,就能自己一直连续转换了,我们只需要在循环里面不断读取我们存放转移数据的数组内容即可。

但是这边就会发现问题,就是OLED屏幕是全黑的,或者说OLED上面的数据变化缓慢且有明显的滞后性。我网上找了B站UP主野生绿波电龙看他的视频他也出现了这个问题,他说是DMA中断自动开启了,导致中断频繁主程序不能正常运行,我按照他视频内的方法:

把Force DMA channels Interrupts关闭,再把DMA的全局中断使能关掉。

这样下来程序就能正常运行了,这也给我们一个BUG的寻找方法,我们不仅要对代码进行差错,还得对代码的逻辑性进行查询错误,比如说中断的频繁触发导致主程序不能正常运行,这个点我们也得记下来,以后说不定就会遇到这类型的BUG。

HAL_ADCEx_Calibration_Start(&hadc1);//校准ADC
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)AD_Value,4);

 再主函数前面校准ADC后启动一次ADC_DMA传输即可自动连续扫描传输。

配置无误且关掉了DMA中断就能正常显示了。

相关文章:

  • Pytorch里面多任务Loss是加起来还是分别backward? | Pytorch | 深度学习
  • @JsonFormat时区问题
  • AB Sciex QTRAP 4500联用 Waters I Class plus 到货后如何安装和性能测试
  • Flink集成资源管理器
  • Android.mk解析
  • Web前端开发: 什么是JavaScript?
  • HC32f460的定时器时间分析
  • 可视化图解算法44:旋转数组的最小数字
  • 图解深度学习 - 特征工程(DL和ML的核心差异)
  • 决策树引导:如何选择最适合你的机器学习算法
  • [20250522]目前市场上主流AI开发板及算法盒子的芯片配置、架构及支持的AI推理框架的详细梳理
  • 支持电子病历四级的云HIS系统,云HIS系统源码,医院管理信息系统
  • 动态规划应用场景 + 代表题目清单(模板加上套路加上题单)
  • 低代码平台搭建
  • 辛格迪客户案例 | 青山利康实施ERP(BIP),同步开展计算机化系统验证(CSV)
  • Python之os模块(文件和目录操作、进程管理、环境变量访问)
  • 使用arXiv.org上的资源进行学术研究
  • IDEA 编程语言 MoonBit:为 AI 与大型系统而生,无缝调用 Python
  • AI智能体工具调研分享(未完待续)
  • **代换积分法**或**变量替换法**)
  • 360免费wifi连不上/网站性能优化方法
  • php网站怎么做302/seo优化服务是什么意思
  • 可以和朋友合资做网站吗/怎么做推广让别人主动加我
  • 网页版微信怎么删除聊天记录/seo网站关键词排名软件
  • 惠州网站建设/顾问
  • 广东微信网站建设哪家专业/品牌营销策划书