STM32 F765的串口DMA+IDLE中断的发送接收不定长数据

项目上正好用到了这个功能,特此记录下来备忘,方便后人

简单说要注意的几个点

  1. 串口DMA的初始化
  2. 串口IDLE中断的处理
  3. 接收和发送的逻辑控制
  4. 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中断后

  1. 清除中断标志
  2. DMA传输停止
  3. 检测到有效长度后,算出接收到的有效数据长度
  4. 修改标志位,方便主函数里面的函数处理接收到的数据
  5. 再次打开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才可以进行发送,否则不进行
    
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值