我的战队
【分享帖】从STM32开始的RoboMaster生活:进阶篇 IV
本文已经同步发布于作者部署的私人博客
为了更好的排版和观看体验


从STM32开始的RoboMaster生活:进阶篇 IV [DMA]

1.0 什么是DMA
1.1 DMA的定义
Direct Memory Access:直接存储器访问 是所有现代计算设备的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于CPU的大量中断负载。否则,CPU需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU对于其他的工作来说就无法使用。DMA传输将数据从一个地址空间复制到另外一个地址空间。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA传输是高效能嵌入式系统算法和网络最关键的部分。
从上图可以看出,DMA和CPU是同等地位的,CPU能进行的数据转移操作,DMA都能做,或者说,DMA就是只能做数据转移操作的迷你CPU。
如果要简单理解的话,DMA就是CPU小弟,当CPU觉得把一大串数据转移到另一个地方这种任务太麻烦,还有更重要的事情需要做的时候,就可以把这个任务丢给DMA去干,DMA干完或者出问题了跟CPU说一声就行了。
1.2 DMA的内部结构

  • STM32 –> DMA –> Stream –> Channel


    • STM32F4家族拥有两个DMA,其中只有DMA2能够进行M2M操作(稍后会讲)。

    • 每个DMA一般拥有8个Stream。

    • 每个Stream有多个Channel选项,但是一次只能进行一个Channel的传输,具体的配置与选项请参考芯片对应的数据手册。


2.0 什么情况下使用DMA?
2.1 UART

  • 这是最常见的使用情况,当有大量的数据从UART读入或者写入的时候,DMA将CPU解放出来,然后自己单干,让CPU处理更重要的事情。

2.2 ADC

  • 一般在需要ADC时的通道扫描模式下,需要DMA来处理,本章不讲解这部分,放到后面专门讲ADC的篇章。

2.3 SD

  • 需要大量往SD里面读写数据的时候一般也用DMA来处理,本章不讲解这部分。

3.0 DMA的使用方法
3.1 传输方向

  • P2P


    • Peripheral to Peripheral

      :从外设到外设 在RM中暂时用不上,不讲解

  • P2M


    • Peripheral to Memory

      :从外设到内存 当传感器从UART传来数据的时候用

  • M2P


    • Memory to Peripheral

      :从内存到外设 从UART传出数据控制执行器的时候用

  • M2M


    • Memory to Memory

      :从内存到内存 MCU内部的数据转移,常见于Buffer之间互相转移数据,或者从Buffer读写数据

    • 只有DMA2能够执行M2M操作。


3.2 特殊选项

  • First In First Out是STM32里面的DMA的特殊功能,但是一般来说,RM就算不用FIFO,DMA也能完美处理数据,所以暂时不需要,也同样不讲解。

3.3 传输模式
3.3.1 Normal Mode

  • 常用的默认模式,在该模式下,任务完成后就停止DMA,如果还需要用到DMA,需要再次手动启动。

3.3.2 Circular Mode / Continuous Mode

  • 可选模式,在该模式下,任务完成后,DMA的Buffer清零,再次执行任务。也就是说,永远重复执行下去,除非手动停止。

3.4 传输方式
3.4.1 DMA泛用方法(需要用到寄存器)
3.4.1.1 Polling
需要注意的是,DMA的Polling其实根本毫无意义,因为在这个过程中,CPU还是全程参与了,并没有解放CPU,所以,虽然这种方法我还是列出来了,但是除了Debug时可能会用到,其他情况下建议不要用。
[code]HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength);[/code]

  • 参数


    • hdma:指向DMA Stream配置结构体

    • SrcAddress:需要传输的数据的地址

    • DstAddress:数据传输到的目标地址

    • DataLength:传输的数据大小

  • 返回值


    • HAL_StatusTypeDef:如果传输配置成功,返回HAL_OK;如果该DMA Stream正在被占用,返回HAL_BUSY


[code]huart.Instance->R3|=USART_CR3_DMAT;[/code]

  • 通过修改CR3寄存器对应的bit,开启DMA传输

[code]HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, HAL_DMA_LevelCompleteTypeDef CompleteLevel, uint32_t Timeout);[/code]

  • 参数


    • hdma:指向DMA Stream配置结构体

    • CompleteLevel:指定DMA的完成程度

    • Timeout:传输的最大时限

  • 返回值


    • HAL_StatusTypeDef:如果传输成功,返回HAL_OK;如果传输出错,返回HAL_ERROR;如果传输超过最大时限,返回HAL_TIMEOUT


[code]huart.Instance->R3&=~USART_CR3_DMAT;[/code]

  • 通过修改CR3寄存器对应的bit,结束DMA传输

3.4.1.2 Interrupt
[code]HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength);[/code]

  • 参数


    • hdma:指向DMA Stream配置结构体

    • SrcAddress:需要传输的数据的地址

    • DstAddress:数据传输到的目标地址

    • DataLength:传输的数据大小

  • 返回值


    • HAL_StatusTypeDef:如果传输配置成功,返回HAL_OK;如果该DMA Stream正在被占用,则返回HAL_BUSY


[code]huart.Instance->R3|=USART_CR3_DMAT;[/code]

  • 通过修改CR3寄存器对应的bit,开启DMA传输

[code]void XferCpltCallback(DMA_HandleTypeDef *hdma){
......
}[/code]

  • 在写代码的时候,在main.c中创建XferCpltCallback函数

  • 在该函数中填写DMA传输结束后,需要执行的代码

[code]huart.Instance->R3&=~USART_CR3_DMAT;[/code]

  • 通过修改CR3寄存器对应的bit,结束DMA传输

3.4.2 UART特化方法(全HAL库实现)
[code]HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);[/code]

  • 参数


    • huart:指向UART引脚配置结构体

    • pData:指向需要传输的数据

    • Size:传输数据的大小

  • 返回值


    • HAL_StatusTypeDef:如果传输配置成功,返回HAL_OK;如果传输出错,返回HAL_ERROR;如果该DMA Stream正在被占用,则返回HAL_BUSY


[
code]HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);[/code]

  • 参数


    • huart:指向UART引脚配置结构体

    • pData:指向需要传输的数据

    • Size:传输数据的大小

  • 返回值


    • HAL_StatusTypeDef:如果传输配置成功,返回HAL_OK;如果传输出错,返回HAL_ERROR;如果该DMA Stream正在被占用,则返回HAL_BUSY

[code]void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
......
}[/code]

  • 在写代码的时候,在main.c中创建HAL_UART_TxCpltCallback函数

  • 在该函数中填写UART发送结束后,需要执行的代码

[code]void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
......
}[/code]

  • 在写代码的时候,在main.c中创建HAL_UART_RxCpltCallback函数

  • 在该函数中填写UART接收结束后,需要执行的代码

4.0 练习项目
4.1 项目简介

  • 一键发送信息:按下用户自定义按钮,就从UART发送出预先设定好的信息,完成后在板子上亮红灯0.2s。

  • 键盘回响:按下键盘,从STM32返回对应的字符,完成后在板子上亮绿灯0.2s。然后,再加上上面一键发送信息的功能。

4.2 项目代码
完整的工程文件可以在这里找到!
4.2.1 一键发送信息

  • DMA泛用方法 Polling


  • DMA泛用方法 Interrupt

4.2.2 键盘回响 代码链接:https://github.com/AlchemicRonin/-STM32-RoboMaster-

4.3 效果展示

  • 因为按键回响已经包含了一键发送信息的功能,所以视频部分就只录了按键回响的效果

[media=x,760,570]http://player.bilibili.com/player.html?aid=97217251&cid=165950467?page=1[/media]

本文已经同步发布于作者部署的私人博客
为了更好的排版和观看体验


请问这篇文章对你有用吗?
【分享帖】从STM32开始的RoboMaster生活:进阶篇 IV
所有评论
暂无更多
关于作者
0 关注Ta
0 文章
0 经验值
0 获赞