当前位置: 首页 > 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中断就能正常显示了。

http://www.xdnf.cn/news/608041.html

相关文章:

  • 深度剖析 MCP SDK 最新版:Streamable HTTP 模式
  • 学习黑客Nmap 是什么?
  • 数据结构 -- 交换排序(冒泡排序和快速排序)
  • 信息安全管理与评估赛项参考答案-模块1网络平台搭建
  • 【软件测试】第三章·软件测试基本方法(基于需求的测试方法)
  • Trae+12306 MCP,10分钟搭建行程可视化助手
  • Gmsh 代码深度解析与应用实例
  • 【开源项目1】基于机器学习木马查杀引擎项目
  • 1.3 线性系统的时域分析法
  • kafka速度快的原理
  • 【时时三省】(C语言基础)对被调用函数的声明和函数原型
  • [Datagear] [SQL]实现分组统计同时带汇总行的两种方式对比分析
  • AI架构师的新工具箱:ChatGPT、Copilot、AutoML、模型服务平台
  • NtfsLookupAttributeByName函数分析之和Scb->AttributeName的关系
  • vim快速移动光标
  • 多路径传输(比如 MPTCP)控制实时突发
  • 动态规划经典三题_完全平方数
  • JFace中MVC的表格使用介绍
  • C++高效求解非线性方程组的实践指南
  • Ubuntu 18.04 升级内核到 5.X(< 5.10)
  • 【YOLOs-CPP-图像分类部署】03-解决报错
  • LSNet:以小见大,CVPR2025全新轻量级主干网络
  • Node.js 库大全
  • 怎么判断一个Android APP使用了KMM这个跨端框架
  • AI是否会取代人类?浔川问答①
  • 怎么判断一个Android APP使用了Tauri 这个跨端框架
  • css 里面写if else 条件判断
  • 量化indicators指标
  • @JsonFormat时区问题
  • 从渗透测试角度分析 HTTP 数据包