项目上正好用到了这个功能,特此记录下来备忘,方便后人
简单说要注意的几个点
- 串口DMA的初始化
- 串口IDLE中断的处理
- 接收和发送的逻辑控制
- F7的D-Cache一致性的问题
串口DMA的初始化
我这里用的是串口3
对应的接收和发送DMA mode用的是Normal,不是Circle
初始化完引脚,时钟,DMA后记得使能对应的串口中断
然后还需要编写一个接收使能的函数,如下
执行这个函数会进入一次IDLE中断,这里需要自行处理这种情况
可以通过标志位的方式屏蔽掉这个不正常的进入,或者清除标志位的方式都可以吧
uint8_t UART_Init(void)
{
uint8_t res = HAL_OK;
MX_DMA_Init();
res = MX_USART1_UART_Init();
if (res == HAL_OK)
{
UART1_RX_EN();
}
res = MX_USART3_UART_Init();
if (res == HAL_OK)
{
UART3_RX_EN();
}
return res;
}
void UART3_RX_EN(void)
{
__HAL_UART_ENABLE_IT(&huart3,UART_IT_IDLE);
//这里需要补充一句Cache无效化的函数(2020-04-23)
HAL_UART_Receive_DMA(&huart3,Uart3.Rx_Buf,UART_DMA_BUF_LEN);
}
串口IDLE中断的处理方式 & F7的D-Cache一致性的问题&接收和发送的逻辑控制
上述三个问题接下来一并说了
首先我这里定义了一个结构体的方式管理数据缓存和长度,还有有效数据标志,可能还有更机智的方法,待研究,至少目前这样的方法我觉得用的效果就已经很好了
typedef struct
{
uint8_t Rx_Buf[UART_DMA_BUF_LEN];//可以定义一个宏定义管理缓冲区的长度
uint8_t Idle_Flag;//可以理解为有效数据接收标志
uint16_t Rx_Len;//收到数据的长度
}ST_DMA_UART;
如下是我的串口空闲中断的处理
STM32 F7系列有Cache,这个加快CPU的执行速度,但是在用到涉及DMA的功能的时候就需要考虑它对代码产生的负面影响!!
这个可以具体去搜索STM32 F7 Cache一致性的文章 读一读,这里不过多阐述
我现在的理解就是如果涉及到DMA的操作,最好在函数开始执行之前加上指定内存地址的变量 D Cache无效化的操作
当然这个不是绝对的,但是这个应该是一个调试代码的方向。我加了以后代码的功能正常了,即使调试的时候打断点数据也不会乱掉。。。
这个D Cache还是挺复杂的,值得好好深入研究下
SCB_InvalidateDCache_by_Addr((uint32_t *)Uart3.Rx_Buf,UART_DMA_BUF_LEN);
这种处理的逻辑应该是比较常见的
简单来说就是进入有效的IDLE中断后
- 清除中断标志
- DMA传输停止
- 检测到有效长度后,算出接收到的有效数据长度
- 修改标志位,方便主函数里面的函数处理接收到的数据
- 再次打开DMA接收
void UART_Receive_IDLE(UART_HandleTypeDef *huart)
{
uint32_t temp;
if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
HAL_UART_DMAStop(huart);
temp = huart->hdmarx->Instance->NDTR;
if(huart->Instance == huart3.Instance)
{
if(UART_DMA_BUF_LEN - temp!=0)
{
Uart3.Rx_Len = UART_DMA_BUF_LEN - temp;
Uart3.Idle_Flag=DMA_RECV;
}
}
}
}
void USART3_IRQHandler(void)
{
UART_Receive_IDLE(&huart3);//
HAL_UART_IRQHandler(&huart3);
}
主函数里的接收函数如下
进来处理Cache一致性的问题
接着判断接收是否有效,长度是否有效,在进入对于的处理机制里面,处理完以后,清除掉长度值和标志位
uint8_t UART3_DualCPU_DataRX()
{
if ((Uart3.Idle_Flag == DMA_RECV) && (Uart3.Rx_Len != 0))
{
//执行对应的处理操作
Uart3.Idle_Flag = DMA_NOT_RECV;//自己弄个宏定义反应接收的情况
Uart3.Rx_Len = 0;
}
}
然后发送函数
进来处理Cache一致性的问题
接着填你要发的数据
然后调用底层发送
底层发送函数一定要填你要发送的长度
我这里的调试时如果不写那一句设定长度的话,数据的长度会不对
void UART3_DualCPU_DataTX()
{
SCB_InvalidateDCache_by_Addr((uint32_t *)uart3_buf,UART3_BUF_LEN);//要加一句这个,确保数据不会因为Cache的问题乱掉
//处理发送数据的代码
UART3_DMA_TX(uart3_buf, UART3_BUF_LEN);
}
void UART3_DMA_TX(uint8_t * str,uint16_t len)
{
__HAL_DMA_SET_COUNTER(&hdma_usart3_tx,len);//这里要设置长度!!!
HAL_UART_Transmit_DMA(&huart3, str, len);
}
总的来说,这个F7的串口DMA基本上就是这么弄的了
效果很棒,基本上都是发送函数一运行完,接收方就收到了,然后再处理发回来,马上我就能收到,速度很快了
我这个是用在两个CPU对向发送数据上的,觉得很好了
之前也找了很多很多的代码看过
F7的代码实现和F4的还是有很大不同的,特别是Cache的问题,要特别注意
2020-4-23更新
在操作DMA之前,无论发送还是接收,都需要加一句SCB_InvalidateDCache_by_Addr或者SCB_CleanInvalidateDCache_by_Addr,这个具体加哪一个要试一试,我这里加SCB_CleanInvalidateDCache_by_Addr,因为我的串口DMA是单次调用的,不是循环模式,所以重新开启收功能之前都需要调用一遍失能Cache函数,然后收发使用到的缓存都需要32字节对齐,这个是SCB_CleanInvalidateDCache_by_Addr或者SCB_CleanInvalidateDCache_by_Addr要求的,估计以前用了这个没起作用,应该也和这个有关,没有32字节对齐。
补充一些几个关键函数的调用路径和简单的内部实现描述
HAL_UART_Transmit_DMA
1. 全局状态 gState = HAL_UART_STATE_BUSY_TX
2. 调用HAL_DMA_Start_IT
3. DMA状态hdma->State = HAL_DMA_STATE_BUSY;
4. 开启外设DMA __HAL_DMA_ENABLE(hdma);
5. 执行 SET_BIT(huart->Instance->CR3, USART_CR3_DMAT); 数据就被发出去了
UART_EndTransmit_IT
1. 全局状态 huart->gState = HAL_UART_STATE_READY;
2. HAL_UART_TxCpltCallback(huart);
UART_EndRxTransfer(huart);
1. 接收状态恢复 huart->RxState = HAL_UART_STATE_READY;
2. 解除中断跳转 huart->RxISR = NULL;
HAL_UART_Receive_DMA
1. 判断if (huart->RxState == HAL_UART_STATE_READY),不是READY的话之前退出
2. 接收状态 huart->RxState = HAL_UART_STATE_BUSY_RX;
3. 调用HAL_DMA_Start_IT
4. DMA状态hdma->State = HAL_DMA_STATE_BUSY;
5. 开启外设DMA __HAL_DMA_ENABLE(hdma);
HAL_UART_DMAStop
1. 判断是不是有发送请求
如果有,停止发送
2. 判断是不是有接收请求
如果有,停止接收
3. 终止DMA传输 HAL_DMA_Abort
调用 __HAL_DMA_DISABLE(hdma);关闭外设DMA
更新20-07-19
在调用DMA发送之前一定要做一个判断,判断上一次发送有没有完成,以免出现发送间隔过快,前一次发送没有完成后一次马上又开始导致HAL库函数出现HAL_BUSY的返回值,具体做法可以在调用前判断发送完成标志,或者在HAL库串口发送完成回调函数里面做一个标志位,完成即置位,在发送前判断改标志必须为1才可以进行发送,否则不进行