不含stm32 底层的代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MyStm32Code/DOC/05_DMA.md

227 lines
7.7 KiB

2 years ago
## 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 采集, 采集立刻复制数据, 轮询处理数据, 判断是否收集完成