## DMA
F103ZET  usart2 DMA


***RX引脚配置为上拉  Pull-up***

### CubeMX配置DMA  
#### dma.h dma.c 初始化
```C
void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 2, 2);
  HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);

  /* DMA1_Channel7_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 2, 2);
  HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);

}

```

#### usart.c
定义DMA 结构体
DMA_HandleTypeDef hdma_usart2_rx;
DMA_HandleTypeDef hdma_usart2_tx;


对应串口 HAL_UART_MspInit
```c
    /* USART2 DMA Init */
    /* USART2_RX Init */
    hdma_usart2_rx.Instance = DMA1_Channel6;
    hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart2_rx.Init.Mode = DMA_NORMAL;
    hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx);

    /* USART2_TX Init */
    hdma_usart2_tx.Instance = DMA1_Channel7;
    hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart2_tx.Init.Mode = DMA_NORMAL;
    hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart2_tx);

    /* USART2 interrupt Init */
    HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART2_IRQn);
```

对应的  HAL_UART_MspDeInit
    HAL_DMA_DeInit(uartHandle->hdmarx);
    HAL_DMA_DeInit(uartHandle->hdmatx);

### stm32f1xx_it.h .c
extern DMA_HandleTypeDef 引入DMA定义
    void DMA1_Channel6_IRQHandler(void);
void DMA1_Channel7_IRQHandler(void);

void DMA1_Channel6_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma_usart2_rx);
}


void DMA1_Channel7_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma_usart2_tx);
}


###  main.h   main.c
#include "dma.h"
 MX_DMA_Init();  在串口之前初始化，否则不能输出


### usart  dma 发送接收
HAL_UART_Transmit_DMA();串口DMA模式发送
HAL_UART_Transmit_DMA();串口DMA模式接收

  HAL_UART_Receive_DMA(&huart2, Rxbuf, sizeof(Rxbuf));
  UART一旦开启DMA之后，DMA收发中断都是强制开启的，所以DMA收发函数也可以编写回调函数。

```c
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 
{
    if(huart->Instance==USART2) 
    {
      HAL_UART_Transmit_DMA(&huart2, ACKbuf, sizeof(ACKbuf));
      // HAL_UART_Receive_IT(&huart2, Rxbuf, sizeof(Rxbuf));  // 重新使用串口接收中断
    }
}
```

###  DMA 相关函数
__HAL_DMA_GET_COUNTER  获取DMA剩余未接收数据

HAL_UART_Transmit 串口阻塞方式发送函数

HAL_UART_Transmit_IT  串口中断方式发送函数

HAL_UART_Receive_IT  串口中断方式接收数据

HAL_UART_Transmit_DMA  串口DMA方式发送函数

HAL_UART_Receive_DMA   串口DMA方式接收函数
 
HAL_UART_TxCpltCallback  串口发送完成回调函数

HAL_UART_RxCpltCallback  串口接收完成回调函数-- 和中断模式函数一样

HAL_UART_RxHalfCpltCallback   串口接收过半回调函数

###  DMA 模式
普通模式：当传输数据量为0时，需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
循环模式：则当传输数据量为0时，会自动将传输数据量设置为满的，这样数据就能不断的传输。双缓冲


  但有时为了安全，当我们设置的DMA缓冲区大小大于一次完整的数据时，当接收完一次数据后，Counter值还没有变为0，这时不会自动指向另一个缓冲区，所以这时候我们就需要手动更改指向下一个缓冲区。（也就是当第一帧数据传输完成后，Counter值不会自动填满，且内存区域还是指向Memory0，然后我们将剩余数据量保存下来，再将Counter值填满，接着把DMA指向Memory1，最后通过判断剩余数据量来决定是否对数据进行处理）
 
双缓冲的数据存储格式
uint8_t sbus_rx_buffer[2][18u]; 
DMA_InitStruct.DMA_BufferSize = 18;
DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];
DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration
DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);


空闲中断计算 长度,及中间数据处理方法参考
```c
uint32_t temp;
    if(USART1 == huart1.Instance)//判断是否为串口1中断
    {
        if(RESET != __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))//如果为串口1空闲
        {
            __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除中断标志
            HAL_UART_DMAStop(&huart1);//停止DMA接收
            temp  = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//获取DMA当前还有多少未填充
            Rx_len_1 =  BUFFERSIZE_1 - temp; //计算串口接收到的数据个数
            HAL_UART_Transmit_DMA(&huart1,ReceiveBuff_1,Rx_len_1);//发送数据
            Rx_len_1=0;//接收数据长度清零
            HAL_UART_Receive_DMA(&huart1,ReceiveBuff_1,BUFFERSIZE_1);//开启下一次接收
        }    
    }

```

####  DMA 调试
监控半满，满状态。监控满后，指针是否重回开头接收
可以串口监控
huart.Init.BaudRate
huart->RxXferSize   为Rx需要接收的大小
__HAL_DMA_GET_COUNTER(huart2.hdmarx)  可以获取剩余的空间 

  const uint8_t                 *pTxBuffPtr;      /*!< Pointer to UART Tx transfer Buffer */
  uint16_t                      TxXferSize;       /*!< UART Tx Transfer size              */
  __IO uint16_t                 TxXferCount;      /*!< UART Tx Transfer Counter           */
  uint8_t                       *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */
  uint16_t                      RxXferSize;       /*!< UART Rx Transfer size              */
  __IO uint16_t                 RxXferCount;      /*!< UART Rx Transfer Counter           */


 __HAL_DMA_GET_COUNTER(huart2.hdmarx)  可以获取剩余的空间 
 获得 dma-init-CNDTR值

 定义接收数据的结构
 ```C
typedef struct
{
uint8_t RxBuffer[RCV_BUF_LENGTH];    /* 接收缓冲区 */
uint16_t RxEndIndex;                    /* 尾索引值 */
FlagStatus RxEndFlag ;                  /* 接收结束标识 */
}Usart_BufTypeDef;
 ```

 半满中断标志：  
 标准库 ：DMA_ITConfig(DMA1_Channel4, DMA_IT_HT, ENABLE); 
 HAL库： __HAL_UART_ENABLE_IT(&huart2, DMA_IT_HT);
 可以查DMA中断位置，中断函数要清除半满标记

 开启半满中断后，可以看到半满的LED 翻转
 如何解决  后续出现的错误？？  获得空余数

       // __HAL_DMA_CLEAR_FLAG(&hdma_usart2_rx, DMA_IT_HT);
      __HAL_DMA_ENABLE_IT(&hdma_usart2_rx, DMA_IT_HT );
      __HAL_UART_CLEAR_FLAG(&huart2, DMA_IT_HT);

解决串口发送DMA只能发送一次的问题
      注意，在DMA传输过程中，会将串口从READY状态改为发送BUSY状态（软件修改）：huart->gState = HAL_UART_STATE_BUSY_TX;但是在DMA完成本次传输工作以后，并没有将串口从发送BUSY状态改回READY状态

DMA 中断函数可以如下处理一些错误

    hdma->XferHalfCpltCallback

    if (hdma->XferErrorCallback != NULL)
    {
      /* Transfer error callback */
      hdma->XferErrorCallback(hdma);
    }


###  思路：
DMA 采集， 采集立刻复制数据， 轮询处理数据， 判断是否收集完成