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数组中。并通过串口打印显示。

主要代码如下:

#define BUF_SIZE 16
uint32_t srcBuf[BUF_SIZE] = {
    0x00000000,0x11111111,0x22222222,0x33333333,
    0x44444444,0x55555555,0x66666666,0x77777777,
    0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
    0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
};

// 目标数组
uint32_t desBuf[BUF_SIZE];


int main(void)
{
  int i;
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
 
  // 启动DMA传输一直等到DMA_FLAG_TC2 = 1
  HAL_DMA_Start(&hdma_memtomem_dma1_channel2,(uint32_t)srcBuf, (uint32_t)desBuf, sizeof(uint32_t) * BUF_SIZE);
  // 这里配置的是通道2,要取得通道2的TC状态
  while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel2, DMA_FLAG_TC2) == RESET);
  // 通过串口打印数组内容
  for (i = 0; i < BUF_SIZE; i++)
	printf("Buf[%d] = %X\r\n", i, desBuf[i]);

  while (1) {
  }
}

5.1、MX_DMA_Init

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

void MX_DMA_Init(void)
{
  __HAL_RCC_DMA1_CLK_ENABLE();
  hdma_memtomem_dma1_channel2.Instance = DMA1_Channel2;
  hdma_memtomem_dma1_channel2.Init.Direction = DMA_MEMORY_TO_MEMORY;
  hdma_memtomem_dma1_channel2.Init.PeriphInc = DMA_PINC_ENABLE;
  hdma_memtomem_dma1_channel2.Init.MemInc = DMA_MINC_ENABLE;
  hdma_memtomem_dma1_channel2.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  hdma_memtomem_dma1_channel2.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  hdma_memtomem_dma1_channel2.Init.Mode = DMA_NORMAL;
  hdma_memtomem_dma1_channel2.Init.Priority = DMA_PRIORITY_LOW;
  if (HAL_DMA_Init(&hdma_memtomem_dma1_channel2) != HAL_OK)
  {
    Error_Handler();
  }

}

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

代码精简大致如下:

uint32_t tmp = 0U;
  if(hdma == NULL)
  {
    return HAL_ERROR;
  }

hdma->ChannelIndex = (((uint32_t)hdma->Instance - (uint32_t)DMA1_Channel1) / ((uint32_t)DMA1_Channel2 - (uint32_t)DMA1_Channel1)) << 2;
  hdma->DmaBaseAddress = DMA1;
  hdma->State = HAL_DMA_STATE_BUSY;
  tmp = hdma->Instance->CCR;
  tmp &= ((uint32_t)~(DMA_CCR_PL    | DMA_CCR_MSIZE  | DMA_CCR_PSIZE  | \
                      DMA_CCR_MINC  | DMA_CCR_PINC   | DMA_CCR_CIRC   | \
                      DMA_CCR_DIR));
  tmp |=  hdma->Init.Direction        |
          hdma->Init.PeriphInc           | hdma->Init.MemInc           |
          hdma->Init.PeriphDataAlignment | hdma->Init.MemDataAlignment |
          hdma->Init.Mode                | hdma->Init.Priority;

  hdma->Instance->CCR = tmp;
  hdma->ErrorCode = HAL_DMA_ERROR_NONE;
  hdma->State = HAL_DMA_STATE_READY;
  hdma->Lock = HAL_UNLOCKED;

  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

精简源码如下:

HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
  HAL_StatusTypeDef status = HAL_OK;
  __HAL_LOCK(hdma);

  if(HAL_DMA_STATE_READY == hdma->State)
  {
    hdma->State = HAL_DMA_STATE_BUSY;
    hdma->ErrorCode = HAL_DMA_ERROR_NONE;

    __HAL_DMA_DISABLE(hdma);
    DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
    __HAL_DMA_ENABLE(hdma);
  }
  else
  {
   __HAL_UNLOCK(hdma);  
   status = HAL_BUSY;
  }  
  return status;
}

这里的操作主要是配置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

精简源码如下:

static void DMA_SetConfig(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
  hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << hdma->ChannelIndex);
  hdma->Instance->CNDTR = DataLength;

  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;
  }
}

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打印

int main(void)
{
  int i;
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();

  for (i = 0; i < BUF_SIZE; i++)
	sendBuf[i] = 'A';
  // 将数据通过串口DMA发送
  HAL_UART_Transmit_DMA(&huart1, sendBuf, BUF_SIZE);
  while (1)
  {
	HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
	HAL_Delay(100);
  }
}

HAL_UART_Transmit:普通串口打印方法

HAL_UART_Transmit_DMA:DMA串口打印方法

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

外设到存储器代码

精简源码如下:

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();

  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能IDLE空闲中断
  HAL_UART_Receive_DMA(&huart1,rcvBuf,100); // 使能DMA接收中断

  while (1)
  {
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
		HAL_Delay(300);
  }
}


void USART1_IRQHandler(void)
{
  HAL_UART_IRQHandler(&huart1);
  if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET)) // 判断IDLE标志位是否被置位
  {
	__HAL_UART_CLEAR_IDLEFLAG(&huart1);// 清除标志位
	HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止干扰
	uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
	rcvLen = BUF_SIZE - temp; //计算数据长度
	HAL_UART_Transmit_DMA(&huart1, rcvBuf, rcvLen);//发送数据
	HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);//开启DMA
  }
}

#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)   ((((__INTERRUPT__) >> 28U) == UART_CR1_REG_INDEX)? ((__HANDLE__)->Instance->CR1 |= ((__INTERRUPT__) & UART_IT_MASK)): \
                                                           (((__INTERRUPT__) >> 28U) == UART_CR2_REG_INDEX)? ((__HANDLE__)->Instance->CR2 |= ((__INTERRUPT__) & UART_IT_MASK)): \
                                                           ((__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的最终定义如下

#define __HAL_UART_CLEAR_PEFLAG(__HANDLE__)     \
  do{                                           \
    __IO uint32_t tmpreg = 0x00U;               \
    tmpreg = (__HANDLE__)->Instance->SR;        \
    tmpreg = (__HANDLE__)->Instance->DR;        \
    UNUSED(tmpreg);                             \
  } while(0U) 

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

俯仰一世_1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值