• stm32之dma


            DMA(Direct Memory Access)—直接存储器存取,是单片机的一个外设,它的主要功能是用来搬数据,但是不需要占用 CPU ,即在传输数据的时候, CPU 可以干其他的事情,好像是多线程一样。
    数据传输流向大致有以下三种:
    • 存储器   ---->  存储器
    • 存储器   ---->  外设
    • 外设       ---->  存储器

    一、DMA请求

            STM32F103有2 DMA 控制器,DMA17个通道,DMA25个通道。

            如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求,DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。请求是有优先级的,DMA控制器会根据优先级的高低来处理请求。

    DMA1 请求列表

    示例:DMA1的通道4可以用来处理USART1_TX 和 TIM4_CH2等

    DMA2 请求列表

    二、仲裁器

            仲裁器根据通道请求的优先级来启动外设/ 存储器的访问。
    优先级分为硬件+软件
    软件:每个通道的优先权可以在 DMA_CCRx寄存器中设置,有 4 个等级:
             最高级>高级>中级>低级
    硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优
    先权。0 > 1 > .....> 7

    三、DMA传输方式

    • DMA_Mode_Normal(正常模式): 一次DMA数据传输完后,停止传送 ,即只传输一次
    • DMA_Mode_Circular(循环传输模式):当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式

    四、中断

            每个DMA 通道都可以在 DMA 传输过半、传输完成和传输错误时产生中断。为应用的灵活性考
    虑,通过设置寄存器的不同位来打开这些中断。另外还有一个GIFx,来表示是否产生了HTIF、TCIF、TEIF

    五、存储器   ---->  存储器 case

    存储器到存储器代码

    将数组srcBuf的数据复制到desBuff数组中。并通过串口打印显示。

    主要代码如下:

    1. #define BUF_SIZE 16
    2. uint32_t srcBuf[BUF_SIZE] = {
    3. 0x00000000,0x11111111,0x22222222,0x33333333,
    4. 0x44444444,0x55555555,0x66666666,0x77777777,
    5. 0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
    6. 0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
    7. };
    8. // 目标数组
    9. uint32_t desBuf[BUF_SIZE];
    10. int main(void)
    11. {
    12. int i;
    13. HAL_Init();
    14. SystemClock_Config();
    15. MX_GPIO_Init();
    16. MX_DMA_Init();
    17. MX_USART1_UART_Init();
    18. // 启动DMA传输一直等到DMA_FLAG_TC2 = 1
    19. HAL_DMA_Start(&hdma_memtomem_dma1_channel2,(uint32_t)srcBuf, (uint32_t)desBuf, sizeof(uint32_t) * BUF_SIZE);
    20. // 这里配置的是通道2,要取得通道2的TC状态
    21. while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel2, DMA_FLAG_TC2) == RESET);
    22. // 通过串口打印数组内容
    23. for (i = 0; i < BUF_SIZE; i++)
    24. printf("Buf[%d] = %X\r\n", i, desBuf[i]);
    25. while (1) {
    26. }
    27. }

    5.1、MX_DMA_Init

    代码如下,就是配置了一些DMA的初始化

    1. void MX_DMA_Init(void)
    2. {
    3. __HAL_RCC_DMA1_CLK_ENABLE();
    4. hdma_memtomem_dma1_channel2.Instance = DMA1_Channel2;
    5. hdma_memtomem_dma1_channel2.Init.Direction = DMA_MEMORY_TO_MEMORY;
    6. hdma_memtomem_dma1_channel2.Init.PeriphInc = DMA_PINC_ENABLE;
    7. hdma_memtomem_dma1_channel2.Init.MemInc = DMA_MINC_ENABLE;
    8. hdma_memtomem_dma1_channel2.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    9. hdma_memtomem_dma1_channel2.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    10. hdma_memtomem_dma1_channel2.Init.Mode = DMA_NORMAL;
    11. hdma_memtomem_dma1_channel2.Init.Priority = DMA_PRIORITY_LOW;
    12. if (HAL_DMA_Init(&hdma_memtomem_dma1_channel2) != HAL_OK)
    13. {
    14. Error_Handler();
    15. }
    16. }

    DMA1_Channel2: DMA1 通道2的外设地址,

    Direction: 传输方向

    • DMA_MEMORY_TO_MEMORY
    • DMA_PERIPH_TO_MEMORY
    • DMA_MEMORY_TO_PERIPH

    这里设置的是DMA_MEMORY_TO_PERIPH,操作的是寄存器CCR2的位14

    PeriphInc:外设是否自增,操作的是CCR2的位6

    这里外设是存储器DMA_PINC_ENABLE

    MemInc:外设是否自增,操作的是CCR2的位7

    PeriphDataAlignment自增时的单位,操作的是CCR2的位8、9 位

    MemDataAlignment: 自增时的单位,操作的是CCR2的位10、11 位,图同上

    Mode:是否循环执行DMA,操作的是CCR2的位5

    Priority优先级操作的是CCR2的位12、13

    • DMA_PRIORITY_LOW
    • DMA_PRIORITY_MEDIUM
    • DMA_PRIORITY_HIGH
    • DMA_PRIORITY_VERY_HIGH

    5.2、HAL_DMA_Init

    代码精简大致如下:

    1. uint32_t tmp = 0U;
    2. if(hdma == NULL)
    3. {
    4. return HAL_ERROR;
    5. }
    6. hdma->ChannelIndex = (((uint32_t)hdma->Instance - (uint32_t)DMA1_Channel1) / ((uint32_t)DMA1_Channel2 - (uint32_t)DMA1_Channel1)) << 2;
    7. hdma->DmaBaseAddress = DMA1;
    8. hdma->State = HAL_DMA_STATE_BUSY;
    9. tmp = hdma->Instance->CCR;
    10. tmp &= ((uint32_t)~(DMA_CCR_PL | DMA_CCR_MSIZE | DMA_CCR_PSIZE | \
    11. DMA_CCR_MINC | DMA_CCR_PINC | DMA_CCR_CIRC | \
    12. DMA_CCR_DIR));
    13. tmp |= hdma->Init.Direction |
    14. hdma->Init.PeriphInc | hdma->Init.MemInc |
    15. hdma->Init.PeriphDataAlignment | hdma->Init.MemDataAlignment |
    16. hdma->Init.Mode | hdma->Init.Priority;
    17. hdma->Instance->CCR = tmp;
    18. hdma->ErrorCode = HAL_DMA_ERROR_NONE;
    19. hdma->State = HAL_DMA_STATE_READY;
    20. hdma->Lock = HAL_UNLOCKED;
    21. return HAL_OK;

    其中channgelindex的计算是如下

    hdma->ChannelIndex = (((uint32_t)hdma->Instance - (uint32_t)DMA1_Channel1) / ((uint32_t)DMA1_Channel2 - (uint32_t)DMA1_Channel1)) << 2;

    本案例用的是通道2,所以Instance = DMA1_Channel2, 最终结果是1 << 2, 即4 = 0x100,这里用位表示的通道,而不是0 1 2 3 等表示。

    5.3、HAL_DMA_Start

    精简源码如下:

    1. HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
    2. {
    3. HAL_StatusTypeDef status = HAL_OK;
    4. __HAL_LOCK(hdma);
    5. if(HAL_DMA_STATE_READY == hdma->State)
    6. {
    7. hdma->State = HAL_DMA_STATE_BUSY;
    8. hdma->ErrorCode = HAL_DMA_ERROR_NONE;
    9. __HAL_DMA_DISABLE(hdma);
    10. DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
    11. __HAL_DMA_ENABLE(hdma);
    12. }
    13. else
    14. {
    15. __HAL_UNLOCK(hdma);
    16. status = HAL_BUSY;
    17. }
    18. return status;
    19. }

    这里的操作主要是配置dma的状态包括State,ErrorCode等,最主要的是DMA_SetConfig 寄存器的配置。

    State:类型是HAL_DMA_StateTypeDef,有四种状态

    • HAL_DMA_STATE_RESET             
    • HAL_DMA_STATE_READY             
    • HAL_DMA_STATE_BUSY              
    • HAL_DMA_STATE_TIMEOUT

    ErrorCode:是各个宏,有5个宏

    • HAL_DMA_ERROR_NONE:没有错误
    • HAL_DMA_ERROR_TE: 发送数据错误
    • HAL_DMA_ERROR_NO_XFER:无持续的传输
    • HAL_DMA_ERROR_TIMEOUT:超时错误
    • HAL_DMA_ERROR_NOT_SUPPORTED:不支持的模式

    5.4、DMA_SetConfig

    精简源码如下:

    1. static void DMA_SetConfig(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
    2. {
    3. hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << hdma->ChannelIndex);
    4. hdma->Instance->CNDTR = DataLength;
    5. if((hdma->Init.Direction) == DMA_MEMORY_TO_PERIPH)
    6. {
    7. hdma->Instance->CPAR = DstAddress;
    8. hdma->Instance->CMAR = SrcAddress;
    9. }
    10. else
    11. {
    12. hdma->Instance->CPAR = SrcAddress;
    13. hdma->Instance->CMAR = DstAddress;
    14. }
    15. }

    hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << hdma->ChannelIndex);

    1、(DMA_ISR_GIF1 << hdma->ChannelIndex) 开启是的通道二的全局中断

    2、把值传给IFCR寄存器,设置CGIF2为1,意义清除DMA_ISR对应的GIF,TEIF,HTIF,TCIF标志

      if((hdma->Init.Direction) == DMA_MEMORY_TO_PERIPH)
      {
        hdma->Instance->CPAR = DstAddress;
        hdma->Instance->CMAR = SrcAddress;
      }
      else
      {
        hdma->Instance->CPAR = SrcAddress;
        hdma->Instance->CMAR = DstAddress;
     

    1、配置CPAR(外设地址寄存器),CMAR(内存地址寄存器)

    2、根据Direction(这里是CCR2寄存器的DIR位)的方向来配置,

    如果是内存到外设,CMAR就是源头,CPAR就是目的

    如果是其它,那么   CMAR就是目的,CPAR就是源头

    5.5、__HAL_DMA_GET_FLAG

    while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel2, DMA_FLAG_TC2) == RESET);

    #define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__)   (DMA1->ISR & (__FLAG__))

    这里获取的是TCIF2,如果数据未传输完成TCIF2 = 0, 否则等于1

    获取ISR寄存器中通道2中的TCIF2

    六、存储器   ---->  外设 case

    存储器到外设代码   

    将内存中的数据通过串口DMA打印

    1. int main(void)
    2. {
    3. int i;
    4. HAL_Init();
    5. SystemClock_Config();
    6. MX_GPIO_Init();
    7. MX_DMA_Init();
    8. MX_USART1_UART_Init();
    9. for (i = 0; i < BUF_SIZE; i++)
    10. sendBuf[i] = 'A';
    11. // 将数据通过串口DMA发送
    12. HAL_UART_Transmit_DMA(&huart1, sendBuf, BUF_SIZE);
    13. while (1)
    14. {
    15. HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
    16. HAL_Delay(100);
    17. }
    18. }

    HAL_UART_Transmit:普通串口打印方法

    HAL_UART_Transmit_DMA:DMA串口打印方法

    七、外设   ---->  存储器 case

    外设到存储器代码

    精简源码如下:

    1. int main(void)
    2. {
    3. HAL_Init();
    4. SystemClock_Config();
    5. MX_GPIO_Init();
    6. MX_DMA_Init();
    7. MX_USART1_UART_Init();
    8. __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能IDLE空闲中断
    9. HAL_UART_Receive_DMA(&huart1,rcvBuf,100); // 使能DMA接收中断
    10. while (1)
    11. {
    12. HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
    13. HAL_Delay(300);
    14. }
    15. }
    16. void USART1_IRQHandler(void)
    17. {
    18. HAL_UART_IRQHandler(&huart1);
    19. if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET)) // 判断IDLE标志位是否被置位
    20. {
    21. __HAL_UART_CLEAR_IDLEFLAG(&huart1);// 清除标志位
    22. HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止干扰
    23. uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
    24. rcvLen = BUF_SIZE - temp; //计算数据长度
    25. HAL_UART_Transmit_DMA(&huart1, rcvBuf, rcvLen);//发送数据
    26. HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);//开启DMA
    27. }
    28. }
    29. #define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)   ((((__INTERRUPT__) >> 28U) == UART_CR1_REG_INDEX)? ((__HANDLE__)->Instance->CR1 |= ((__INTERRUPT__) & UART_IT_MASK)): \
    30.                                                            (((__INTERRUPT__) >> 28U) == UART_CR2_REG_INDEX)? ((__HANDLE__)->Instance->CR2 |= ((__INTERRUPT__) & UART_IT_MASK)): \
    31.                                                            ((__HANDLE__)->Instance->CR3 |= ((__INTERRUPT__) & UART_IT_MASK)))

    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能IDLE空闲中断

    __HAL_UART_ENABLE_IT是一个宏,里面有个 >> 28 ,是因为中断标识包括UART_IT_IDLE

    在定义的时候<< 28了,所以在使用的时候需要 >> 28,第28 29位是CR1,CR2,CR3,再和UART_IT_MASK进行操作就得到了CRx 上的某一个中断标识位

    另外USART_CRx寄存器中的16-31位是保留位,

    HAL_UART_Receive_IT:普通串口接收中断

    HAL_UART_Receive_DMA: DMA串口接收中断

    __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)

    #define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__)) 

    得到串口SR寄存器中的IDLE位(只读)

    HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止干扰
            /*****处理数据先关闭DMA,处理完成之后再打开***/

    HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);//开启DMA

     __HAL_UART_CLEAR_IDLEFLAG的最终定义如下

    1. #define __HAL_UART_CLEAR_PEFLAG(__HANDLE__)     \
    2.   do{                                           \
    3.     __IO uint32_t tmpreg = 0x00U;               \
    4.     tmpreg = (__HANDLE__)->Instance->SR;        \
    5.     tmpreg = (__HANDLE__)->Instance->DR;        \
    6.     UNUSED(tmpreg);                             \
    7.   } while(0U) 

     这个宏的意思是清楚标志位,只看到它依次读了SR,DR寄存器,并没有清除??? 是不是很奇怪,这时就需要手册了。

    手册寄存器USART_SR中对IDLE位有这样一个描述

    先读SR,再读DR 就可以清除标识位。

  • 相关阅读:
    阿里云香港云服务器公网带宽价格表及测试IP地址
    (2022版)一套教程搞定k8s安装到实战 | Secret
    四维图新用户大会|CEO程鹏:极致性价比时代,我们能为汽车智能化提供什么?
    2024022701-信息安全(二)——密码学
    国家网络安全周2023时间是什么时候?有什么特点?谁举办的?
    [C]精炼分析状态机FSM
    java读写锁
    查看Oracle_表名、字段名、注释、进程及杀进程等常用语句
    探索树堆Treap和红黑树的优势和劣势
    【Linux ls -l列出的权限信息解读】
  • 原文地址:https://blog.csdn.net/TSC1235/article/details/133189536