【Ai-WB2入门篇】DMA数据传输

[复制链接]
查看1769 | 回复6 | 2024-8-29 16:25:17 | 显示全部楼层 |阅读模式
DMA(Direct Memory Access)是一种内存存取技术,可以独立地直接读写系统内存,而不需处理器介入处理。 在同等程度的处理器负担下,DMA是一种快速的数据传送方式。 Ai-WB2的DMA控制器有4组独立专用通道,管理外围设备和内存之间的数据传输以提高总线效率。主要有三种类型传输包括内存至内存、内存至外设、外设至内存。并文持LL!链接列表功能。 使用上由软件配置传输数据大小、数据源地址和目标地址。
本文将详细介绍如何使用Ai-WB2的DMA模块。
一:DMA介绍
Ai-WB2的DMA有如下特点:
· 4组独立专用通道
· 独立控制来源与目标存取宽度(单字节、双字节、四字节)
· 每个通道独立作为读写缓存
· 每个通道可被独立的外设硬件触发或是软件触发更
· 支持外设包括UART、I2C、SPI、ADC.
· 八种流程控制
      · DMA流程控制,来源内存、目标内存
      · DMA流程控制,来源内存、目标外设
      · DMA流程控制,来源外设、目标内存
      · DMA流程控制,来源外设、目标外设
      · 目标外设流程控制,来源外设、目标外设
      · 目标外设流程控制,来源内存、目标外设
      · 来源外设流程控制,来源外设、目标内存
      · 来源外设流程控制,来源外设、目标外设
· 支持LLI链表功能,提高DMA效率
DMA工作原理
当一个设备试图通过总线直接向另一个设备传输数据时,它会先向 CPUQ发送DMA请求信号。外设通过DMA 向CPU提出接管总线控制权的总线请求,CPU收到该信号后,在当前的总线周期结束后,会按DMA信号的优先级和提出DMA请求的先后顺序响应DMA信号。CPU对某个设备接口响应DMA请求时,会让出总线控制权。于是在DMA控制器的管理下,外设和存储器直接进行数据交换,而不需CPU干预。数据传送完毕后,设备会向CPU发送DMA结束信号,交还总线控制权。
DMA包含一组 AHB Master 接口和一组 AHB Slave 接口。AHB Master 接口根据当前配置需求通过系 统总线主动存取内存或是外设,做为数据搬移的端口。AHB Slave 接口作为配置 DMA的接口,只支持32-bit 存取。
DMA配置
DMA共支持4路通道,各通道之间互不干涉,可以同时运行,下面是DMA通道x的配置过程:
      1. 在DMA_COSrcAddr存器中设置32-bit来源地址
      2. 在DMA_CODstAddr寄存器中设置32-bit目标地址
      3. 地址自动雾加,可通过配置DMA_COContro|.存器中S(来源)、DI(日标)设定是否开启地址自动雾加模式,设置为1时,开启地址自动累加模式
      4. 设置传输数据宽度,可通过配置DMA_COControl寄存器中STW(来源)、DTW(日标)位,宽度选项有单字节、双自结、四字节
      5. Burst型态,可通过配置DMA_COControl寄存器中SBS(来源)、DBS(目标)位来设置,配置选项有Single、INCR4、INCR8、INCR16
      6. 需要特别注意的是所配置的组合,单笔burst不能超过16字节
      7. 设置数据传输长度的范围为:0-4095

DMA支持的设备如下:
可通过配置SrcPerioheral(来源!和DstPeripheral(目标)来决定当前DMA配合的外设,关系为0-3:UART/6-7:12C/10-11:SPL/22-23ADCIDAC
二:DMA驱动API介绍
由于目前 bl_iot_sdk的hosal层对DMA的封装还未完善,因此,需要结合b1602的SOC层驱动AP1、HAL层和hosal层驱动API使用。下面将演示如何进行内存之间数据传输。UART、I2C、ADC、SPI等外设的DMA使用将不在这里介绍了,具体可参考相关外设的DMA使用。
      · DMA的HOSAL高级驱动API在文件components/platform/hosal/include/hosal_dma.h中定义;
      · HAL层的驱动APl在 components/platform/hosal/b1602_hal/bl_dma.h 中定义;
      · SOC层的驱动APl在 components/platform/soc/b1602/stdDriver/inc/b1602_dma.h 中定义。
HOSAL高级驱动API如下:
· int hosal_dma_init(void:初始化DMA。I2C、UART、SPI、ADC等外设使用DMA时,都需要调用此函数。
· 返回值:成功时返回0,否则返回非零值。
· hosal_dma_chan_t hosal_dma_chan_request(int flag):请求一个可用DMA通道。参数说明如下:
      · flag:DMA请求标志。一般情况下该值为0。
      · 返回值:当返回值小于0时,表示请求失败;否则返回值为DMA通道值
· int hosal_dma_chan_release(hosal_dma_chan_t chan):释放已经请求分配的DMA通道。参数说明如下:
      · chan:已经请求完成的可用DMA通道
      · 返回值:成功时返回0,否则返回非零值。
· int hosal_dma_chan_start(hosal_dma_chan_t chan):启动通道传输。参数说明如下:
      · chan:已经请求完成的可用DMA通道
      · 成功时返回0,否则返回非零值。
· int hosal_dma_chan_stop(hosal_dma_chan_t chan):停止DMA传输。参数说明如下:
      · chan:已经请求完成的可用DMA通道
      · 返回值:成功时返回0,否则返回非零值
· int hosal_dma_irq_callback_set(hosal_dma_chan_t chan, hosal_ dma_irq_t pfn, void *p_arg):注册 DMA中断Q回调函数。参数说明如下:
      · chan:已经请求完成的可用DMA通道
      · pfn:DMA中断回调函数指针。其定义如下:
  1.   typedef void (*hosal_dma_irq_t)(void *p_arg, uint32_t flag);
复制代码
      · p_arg:回调函数参数
      · 返回值:成功时返回0,否则返回非零值,
· int hosal_dma_finalize(void):释放DMA。成功时返回0,否则返回非零值。
SOC层常用的驱动函数如下:
· void DMA_channel_Init(DMA_channel_cfg_Type *chcfg):DMA通道初始化。参数说明如下:
      · chCfg:DMA通道配置。定义如下:
  1.   /**
  2.    *  @brief DMA channel Configuration Structure type definition
  3.    */
  4.   typedef struct {
  5.       uint32_t srcDmaAddr;                     /*!< Source address of DMA transfer */
  6.       uint32_t destDmaAddr;                    /*!< Destination address of DMA transfer */
  7.       uint32_t transfLength;                   /*!< Transfer length, 0~4095, this is burst count */
  8.       DMA_Trans_Dir_Type dir;                  /*!< Transfer dir control. 0: Memory to Memory, 1: Memory to peripheral, 2: Peripheral to memory */
  9.       DMA_Chan_Type ch;                        /*!< Channel select 0-4 */
  10.       DMA_Trans_Width_Type srcTransfWidth;     /*!< Transfer width. 0: 8  bits, 1: 16  bits, 2: 32  bits */
  11.       DMA_Trans_Width_Type dstTransfWidth;     /*!< Transfer width. 0: 8  bits, 1: 16  bits, 2: 32  bits */
  12.       DMA_Burst_Size_Type srcBurstSzie;        /*!< Number of data items for burst transaction length. Each item width is as same as tansfer width.
  13.                                                    0: 1 item, 1: 4 items, 2: 8 items, 3: 16 items */
  14.       DMA_Burst_Size_Type dstBurstSzie;        /*!< Number of data items for burst transaction length. Each item width is as same as tansfer width.
  15.                                                    0: 1 item, 1: 4 items, 2: 8 items, 3: 16 items */
  16.       uint8_t srcAddrInc;                      /*!< Source address increment. 0: No change, 1: Increment */
  17.       uint8_t destAddrInc;                     /*!< Destination address increment. 0: No change, 1: Increment */
  18.       DMA_Periph_Req_Type srcPeriph;           /*!< Source peripheral select */
  19.       DMA_Periph_Req_Type dstPeriph;           /*!< Destination peripheral select */
  20.   }DMA_Channel_Cfg_Type;
复制代码
      · 返回值:无
· void DMA_channel_update_srcMemcfg(uint8_t ch, uint32_t memAddr, uint32_t len):更新Source地址和长度
· void DMA_channel_Update_DstMemcfg(uint8_t ch, uint32_t memAddr, uint32_t len):更新目标地址和长度
三:DMA使用示例
下面将演示DMA的内存到内存传输模式。这个模式启动后,DMA会根据设定好的搬移数量(TransferSize),将数据从来源地址搬到目标地址,传输完毕后DMA控制器Q会自动回到空闲状态,等待下一次的搬运。配置步骤如下:
      1. 将寄存器DMA_C0SrcAddr的值设置为来源的内存地址
      2. 将寄存器DMA_CODstAddr的值设置为目标的内存地址
      3. 选择传输模式,将寄存器DMA_COConfig[FLOWCTRL]位的值设置为0,即选择memory-to-memory模式
      4. 设置DMA COControl寄存器中对应的位的教值1、S位设置为1,开启地址自动累加模式,DTW、STW位分别设置来源和目标的传输竞度,DBS、SBS位分别设置来源和目标的burst型态,
      5. 选择合适的通道,使能DMA,完成数据传输
使用示例代码如下:
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <FreeRTOS.h>
  4. #include <task.h>
  5. #include <stdio.h>
  6. #include <stdbool.h>
  7. #include <hosal_dma.h>
  8. #include <hosal_adc.h>
  9. #include <blog.h>
  10. #include "bl602_dma.h"

  11. #define TAG "dma_demo"

  12. #define DMA_DATA_SIZE 1024

  13. uint8_t rx_data[DMA_DATA_SIZE];
  14. uint8_t tx_data[DMA_DATA_SIZE];

  15. DMA_Channel_Cfg_Type txchCfg = {
  16.         (uint32_t)((unsigned char*)&tx_data[0]),
  17.          (uint32_t)((unsigned char*)&rx_data[0]),
  18.         DMA_DATA_SIZE,
  19.         DMA_TRNS_M2M,
  20.         DMA_CH0,
  21.         DMA_TRNS_WIDTH_8BITS,
  22.         DMA_TRNS_WIDTH_8BITS,
  23.         DMA_BURST_SIZE_4,
  24.         DMA_BURST_SIZE_4,
  25.         DMA_MINC_ENABLE,
  26.         DMA_MINC_ENABLE,
  27.         DMA_REQ_NONE,
  28.         DMA_REQ_NONE,
  29. };

  30. void verify_data(void){
  31.     bool ret = true;
  32.     for(size_t i = 0;i < DMA_DATA_SIZE;i++){
  33.         if(rx_data[i] != tx_data[i]){
  34.             printf("dma transfer failed:%d,%d\r\n",rx_data[i],tx_data[i]);
  35.             ret = false;
  36.             break;
  37.         }
  38.     }
  39.     if(ret){
  40.         printf("dma transfer success\r\n");
  41.     }
  42. }

  43. void dma_irq_callback(void* p_arg, uint32_t flag) {
  44.     switch (flag) {
  45.         case HOSAL_DMA_INT_TRANS_COMPLETE:
  46.             printf("dma transfer done,start verify data...\r\n");
  47.             verify_data();
  48.             break;
  49.         case HOSAL_DMA_INT_TRANS_ERROR:
  50.             printf("dma transfer error\r\n");
  51.             break;
  52.     }
  53. }

  54. void dma_m2m_demo(void) {
  55.     // 填充数据
  56.     for (size_t i = 0; i < DMA_DATA_SIZE;i++) {
  57.         tx_data[i] = 'A';
  58.         rx_data[i] = 0;
  59.     }

  60.     hosal_dma_chan_t chan = hosal_dma_chan_request(0);
  61.     if (chan < 0) {
  62.         printf("request dma chan failed\r\n");
  63.         return;
  64.     }
  65.    
  66.         txchCfg.ch = chan;
  67.         DMA_Channel_Init(&txchCfg);
  68.         hosal_dma_irq_callback_set(chan, dma_irq_callback, (void*)&chan);

  69.     hosal_dma_chan_start(chan);
  70. }

  71. void main(void) {
  72.     hosal_dma_init();
  73.     dma_m2m_demo();
  74. }
复制代码
内存到外设
在这种工作模式下,DMA会根据设定好的搬移数量(TransferSize), 把数据从来源端搬至内部缓存,当缓存空间不够时自动暂停, 待有足够的缓存空间时继续,直到设定的搬移数量达到。另外一方面当目标外设请求触发会将目标配置burst到目标地址,直到达到设定搬移数
量完成自动回到空闲状态,等待下一次启动
具体配置流程如下:
      1. 将寄存器DMA_C0SrcAddr的值设置为来源的内存地址
      2. 将寄存器DMA_CODstAddr的值设置为目标的外设地址
      3. 选择传输模式,将寄存器DMA_COConfig[FLOWCTRL]位的值设置为1,即选择Memory-to-peripheral模式
      4. 设置DMA_COControl寄存器中对应的位的数值:DI、SI位设置为1,开启地址自动累加模式, DTW、STW位分别设置来源和目标的传输竞度,DBS、SBS位分别设置来源和目标的burst型态
      5. 选择合适的通道,使能DMA,完成数据传输
外设到内存
在这种工作模式下,当来源外设请求触发时将来源配置burst到缓存,直到设定的搬移数量达到停止。另外一方面,当内部缓存足够一次目标burst数量时,DMA会自动将缓存的内容搬到目标地址直到达到设定搬移数量完成自动回到空闲状态,等待下一次启动具体配置流程如下:
      1. 将寄存器DMA_C0SrcAddr的值设置为来源的外设地址
      2. 将寄存器DMA_CODstAddr的值设置为目标的内存地址
      3. 选择传输模式,将寄存器DMA_COConfigIFLOWCTRL]位的值设置为2,即选择Peripheral-to-memory模式
      4. 设置DMA_COControl寄存器中对应的位的教值:Dl、Sl位设置为1,开启地址自动雾加模式。DIW、STW位分别设置来源和目标的传输竞度,DBS、SBS位分别设置来源和目标的burst型态,
      5. 选择合适的通道,使能DMA,完成数据传输

本帖被以下淘专辑推荐:

用心做好保姆工作
回复

使用道具 举报

WildboarG | 2024-8-29 18:01:51 | 显示全部楼层
还有一种内存到内存
回复 支持 反对

使用道具 举报

粉肠 | 2024-8-29 20:53:27 | 显示全部楼层
园长厉害
回复

使用道具 举报

bzhou830 | 2024-8-30 08:52:21 | 显示全部楼层
太快了,跟不上了都
选择去发光,而不是被照亮
回复 支持 反对

使用道具 举报

爱笑 | 2024-8-30 08:55:08 | 显示全部楼层
WildboarG 发表于 2024-8-29 18:01
还有一种内存到内存

详细说说,我添加上去。
用心做好保姆工作
回复 支持 反对

使用道具 举报

djy876 | 2024-9-9 10:42:19 | 显示全部楼层
学习打开
回复

使用道具 举报

Yhue | 6 天前 | 显示全部楼层
本帖最后由 Yhue 于 2024-11-17 12:51 编辑

在看 LCD SPI 驱动的时候发现了这个,感谢园长的好文
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则