[方案相关] HC32F460开发之UART+DMA接收不定长数据

[复制链接]
3777|20
 楼主| 610u 发表于 2023-10-27 12:31 | 显示全部楼层 |阅读模式
前言
使用hc32平台做产品显示板的开发,主板会通过串口不定时上报设备状态,显示板接收到数据包后,解析并根据主板上报的数据显示设备相应的状态到lcd屏上。关于显示板串口接收这一块,原本打算直接COPY demo例程,串口接收到数据后,进一次中断接收一个字节,一次次下来把整包数据读出来,但是因为考虑到显示板在刷屏过程中,主板可能会上报设备状态,频繁的中断可能会影响刷屏速度。综合考虑,还是打算用上DMA来减少串口中断的次数。

一、过程说明
由于之前做过类似的工作,这里对整个过程的说明就不再赘述,具体可以看之前的文章GD32开发之UART+DMA接收不定长数据,下面只介绍HC平台和GD平台两者之间关于这一块的不同点,以及对应的解决方法。
99618653b3d0276ab1.png

 楼主| 610u 发表于 2023-10-27 12:31 | 显示全部楼层

上面这张图是GD32关于UART+DMA接收不定长数据包的一个简要的流程图。我们可以看到,GD32是有IDLEF这么个寄存器标志位的,通过这个标志位,我们可以随时知道到一帧数据是否已经结束,从而再去做对应的接收操作。但是翻看HC的用户手册,却找不到HC平台有类似的寄存器标志位,取而代之的,是更加灵活的TIMEOUT功能。
 楼主| 610u 发表于 2023-10-27 12:31 | 显示全部楼层
 楼主| 610u 发表于 2023-10-27 12:31 | 显示全部楼层
关于TIMEOUT功能的使用,手册上也有比较详细的说明
 楼主| 610u 发表于 2023-10-27 12:31 | 显示全部楼层
 楼主| 610u 发表于 2023-10-27 12:36 | 显示全部楼层
从上述内容我们可以看到,uart接收的timeout功能是需要用到timer0定时器的,关于uart接收timeout功能最主要的就是设置timeout的时长,关于timer0的初始化例程中也有代码可以参考。
  1. /**
  2. * [url=home.php?mod=space&uid=247401]@brief[/url] usart4 timer0初始化
  3. *
  4. */
  5. static void Usart4Timer0Init(void)
  6. {
  7.         stc_clk_freq_t stcClkTmp;
  8.         stc_tim0_base_init_t stcTimerCfg;
  9.         stc_tim0_trigger_init_t StcTimer0TrigInit;

  10.         MEM_ZERO_STRUCT(stcClkTmp);
  11.         MEM_ZERO_STRUCT(stcTimerCfg);
  12.         MEM_ZERO_STRUCT(StcTimer0TrigInit);

  13.         /* Timer0 peripheral enable */
  14.         PWC_Fcg2PeriphClockCmd(LCD_TMR_FCG_PERIPH, Enable);

  15.         /* Clear CNTAR register for channel A */
  16. //        TIMER0_WriteCntReg(LCD_TMR_UNIT, Tim0_ChannelA, 0u);
  17.         TIMER0_WriteCntReg(LCD_TMR_UNIT, Tim0_ChannelB, 0u);

  18.         /* Config register for channel A */
  19.         stcTimerCfg.Tim0_CounterMode = Tim0_Async;
  20.         stcTimerCfg.Tim0_AsyncClockSource = Tim0_XTAL32;
  21.         stcTimerCfg.Tim0_ClockDivision = Tim0_ClkDiv2;
  22.         stcTimerCfg.Tim0_CmpValue = 20u;
  23.         TIMER0_BaseInit(LCD_TMR_UNIT, Tim0_ChannelB, &stcTimerCfg);

  24.         /* Clear compare flag */
  25.         TIMER0_ClearFlag(LCD_TMR_UNIT, Tim0_ChannelB);

  26.         /* Config timer0 hardware trigger */
  27.         StcTimer0TrigInit.Tim0_InTrigEnable = false;
  28.         StcTimer0TrigInit.Tim0_InTrigClear = true;
  29.         StcTimer0TrigInit.Tim0_InTrigStart = true;
  30.         StcTimer0TrigInit.Tim0_InTrigStop = false;
  31.         TIMER0_HardTriggerInit(LCD_TMR_UNIT, Tim0_ChannelB, &StcTimer0TrigInit);
  32. }
 楼主| 610u 发表于 2023-10-27 12:36 | 显示全部楼层
这里需要注意的是,不同的uart对应不同的timer0通道,这里需要根据实际硬件去配置。
8970653b3e456140e.png
 楼主| 610u 发表于 2023-10-27 12:36 | 显示全部楼层
当uart接收Timeout中断触发时,我们就可以到UsartTimeoutIrqCallback中断函数做进一步的接收处理。
那么如何获取到接收到的一帧数据的长度?
31171653b3e54535d6.png
 楼主| 610u 发表于 2023-10-27 12:36 | 显示全部楼层
在手册中我们可以看到DMA中有这么一个寄存器,因此我们可以利用这个寄存器。在启动DMA接收数据前,我们可以先往这个寄存器写一个较大的值,在一帧数据接收完毕后(TIMEOUT中断发生),在Timeout中断中再去读这个寄存器的值,前后的值相减就是此次接收的一帧数据的长度。
 楼主| 610u 发表于 2023-10-27 12:36 | 显示全部楼层
二、代码实现
简单介绍完原理,下面讲下代码的实现
首先是相关的宏定义
  1. /* DMA config */
  2. #define _DMA_CH_REG_OFFSET(ch)              ((ch) * 0x40ul)
  3. #define _DMA_CH_REG(reg_base, ch)           (*(volatile uint32_t *)((uint32_t)(reg_base) + _DMA_CH_REG_OFFSET(ch)))

  4. #define READ_DMA_CH_REG(reg_base, ch)            (_DMA_CH_REG((reg_base), (ch)))

  5. #define DMA_DTCTL_CNT_Pos                   (16ul)                               /*!< DMA_DTCTLx: CNT Position */
  6. #define DMA_DTCTL_CNT_Msk                   (0xFFFFul << DMA_DTCTL_CNT_Pos)      /*!< DMA_DTCTLx: CNT Mask 0xFFFF0000 */
  7. #define DMA_DTCTL_CNT                       (DMA_DTCTL_CNT_Msk)


  8. /* USART channel definition */
  9. #define LCD_USART_CH                                                (M4_USART4)
  10.        
  11. /* USART baudrate definition */
  12. #define LCD_USART_BAUDRATE                         (115200ul)

  13. /* USART RX Port/Pin definition */
  14. #define LCD_USART_RX_PORT                   (PortB)
  15. #define LCD_USART_RX_PIN                    (Pin03)
  16. #define LCD_USART_RX_FUNC                   (Func_Usart4_Rx)
  17.        
  18. /* USART TX Port/Pin definition */
  19. #define LCD_USART_TX_PORT                   (PortB)
  20. #define LCD_USART_TX_PIN                    (Pin04)
  21. #define LCD_USART_TX_FUNC                   (Func_Usart4_Tx)

  22. #define LCD_USART_DMA_UNIT                                        (M4_DMA2)
  23. #define LCD_USART_DMA_CH                                        (DmaCh2)
  24. #define LCD_DMA_TRG_SEL                                            (EVT_USART4_RI)

  25. /* Timer0 unit definition */
  26. #define LCD_TMR_UNIT                                     (M4_TMR02)
  27. #define LCD_TMR_FCG_PERIPH                             (PWC_FCG2_PERIPH_TIM02)

  28. #define LCD_USART_RTO_NUM                               (INT_USART4_RTO)
  29. #define LCD_USART_RTO_IRQn                          (Int005_IRQn)

  30. /* USART interrupt number  */
  31. #define LCD_USART_EI_NUM                                  (INT_USART4_EI)
  32. #define LCD_USART_EI_IRQn                           (Int013_IRQn)
 楼主| 610u 发表于 2023-10-27 12:37 | 显示全部楼层
串口的初始化

  1. /**
  2. * @brief 串口初始化
  3. *
  4. * @param USARTx 串口号
  5. * @param u32Baudrate 波特率
  6. */
  7. void Usart_Init(M4_USART_TypeDef *USARTx, uint32_t u32Baudrate)
  8. {
  9.         stc_irq_regi_conf_t stcIrqRegiCfg;

  10.     uint32_t u32Fcg1Periph = PWC_FCG1_PERIPH_USART1 | PWC_FCG1_PERIPH_USART2 | \
  11.                              PWC_FCG1_PERIPH_USART3 | PWC_FCG1_PERIPH_USART4;
  12.     stc_usart_uart_init_t stcInitCfg = {
  13.         UsartIntClkCkOutput,
  14.         UsartClkDiv_1,
  15.         UsartDataBits8,
  16.         UsartDataLsbFirst,
  17.         UsartOneStopBit,
  18.         UsartParityNone,
  19.         UsartSampleBit8,
  20.         UsartStartBitFallEdge,
  21.         UsartRtsEnable,
  22.     };

  23.         MEM_ZERO_STRUCT(stcIrqRegiCfg);

  24.     USART_DeInit(USARTx);

  25.     /* Initialize DMA */
  26.     Usart4DmaInit();

  27.     /* Initialize Timer0 */
  28.     Usart4Timer0Init();

  29.     /* Enable peripheral clock */
  30.     PWC_Fcg1PeriphClockCmd(u32Fcg1Periph, Enable);

  31.     PORT_DebugPortSetting(1<<2, Disable);
  32.     PORT_DebugPortSetting(1<<4, Disable);

  33.     /* Initialize USART IO */
  34.     PORT_SetFunc(LCD_USART_RX_PORT, LCD_USART_RX_PIN, LCD_USART_RX_FUNC, Disable);
  35.     PORT_SetFunc(LCD_USART_TX_PORT, LCD_USART_TX_PIN, LCD_USART_TX_FUNC, Disable);

  36.     /* Initialize UART */
  37.     USART_UART_Init(USARTx, &stcInitCfg);
  38.        
  39.     /* Set baudrate */
  40.            USART_SetBaudrate(USARTx, u32Baudrate);

  41.         /* IRQ init */
  42.     Usart4IrqInit();
  43.     USART_FuncCmd(USARTx, UsartTimeOut, Enable);        //
  44.     USART_FuncCmd(USARTx, UsartTimeOutInt, Enable);        //

  45.         USART_FuncCmd(USARTx, UsartTx, Enable);
  46.         USART_FuncCmd(USARTx, UsartRx, Enable);
  47.         USART_FuncCmd(USARTx, UsartRxInt, Enable);
  48. }
 楼主| 610u 发表于 2023-10-27 12:37 | 显示全部楼层
串口初始化这里有个问题需要注意下,因为我们选用的PB3,PB4引脚,默认是JTAG调试引脚,因此,我们想配置成USART口使用时,有部分寄存器需要设置下
 楼主| 610u 发表于 2023-10-27 12:37 | 显示全部楼层
这里我们需要使能uart的Timeout功能还有Timeout中断。
  1. /**
  2. * @brief USART4中断初始化
  3. *
  4. */
  5. static void Usart4IrqInit(void)
  6. {
  7.         stc_irq_regi_conf_t stcIrqRegiCfg;

  8.     /* Set USART RX timeout error IRQ */
  9.     stcIrqRegiCfg.enIRQn = LCD_USART_RTO_IRQn;
  10.     stcIrqRegiCfg.pfnCallback = &Usart4TimeoutIrqCallback;
  11.     stcIrqRegiCfg.enIntSrc = LCD_USART_RTO_NUM;
  12.     enIrqRegistration(&stcIrqRegiCfg);
  13.     NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
  14.     NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
  15.     NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);

  16.     /* Set USART RX error IRQ */
  17.     stcIrqRegiCfg.enIRQn = LCD_USART_EI_IRQn;
  18.     stcIrqRegiCfg.pfnCallback = &Usart4ErrIrqCallback;
  19.     stcIrqRegiCfg.enIntSrc = LCD_USART_EI_NUM;
  20.     enIrqRegistration(&stcIrqRegiCfg);
  21.     NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
  22.     NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
  23.     NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);
  24. }
 楼主| 610u 发表于 2023-10-27 12:37 | 显示全部楼层
DMA的初始化
  1. /**
  2. * @brief USART4 dma初始化
  3. *
  4. */       
  5. static void Usart4DmaInit(void)
  6. {
  7.     stc_dma_config_t stcDmaInit;

  8.     /* Enable peripheral clock */
  9.     PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_DMA1 | PWC_FCG0_PERIPH_DMA2,Enable);

  10.     /* Enable DMA. */
  11.     DMA_Cmd(LCD_USART_DMA_UNIT, Enable);

  12.     /* Initialize DMA. */
  13.     MEM_ZERO_STRUCT(stcDmaInit);
  14.     stcDmaInit.u16BlockSize = 1u; /* 1 block */
  15.     stcDmaInit.u16TransferCnt = (uint16_t)0x8000;   /* Transfer count */
  16.     stcDmaInit.u32SrcAddr = ((uint32_t)(&LCD_USART_CH->DR)+2ul);        /* Set source address. */
  17.     stcDmaInit.u32DesAddr = (uint32_t)(&g_ucRecvBuf_FIFO[0]);    /* Set destination address. */
  18.     stcDmaInit.stcDmaChCfg.enSrcInc = AddressFix;           /* Set source address mode. */
  19.     stcDmaInit.stcDmaChCfg.enDesInc = AddressIncrease;      /* Set destination address mode. */
  20.     stcDmaInit.stcDmaChCfg.enIntEn = Enable;                /* Enable interrupt. */
  21.     stcDmaInit.stcDmaChCfg.enTrnWidth = Dma8Bit;            /* Set data width 8bit. */
  22.     DMA_InitChannel(LCD_USART_DMA_UNIT, LCD_USART_DMA_CH, &stcDmaInit);

  23.     /* Enable the specified DMA channel. */
  24.     DMA_ChannelCmd(LCD_USART_DMA_UNIT, LCD_USART_DMA_CH, Enable);

  25.     /* Clear DMA flag. */
  26.     DMA_ClearIrqFlag(LCD_USART_DMA_UNIT, LCD_USART_DMA_CH, TrnCpltIrq);

  27.     /* Enable peripheral circuit trigger function. */
  28.     PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_AOS,Enable);

  29.     /* Set DMA trigger source. */
  30.     DMA_SetTriggerSrc(LCD_USART_DMA_UNIT, LCD_USART_DMA_CH, LCD_DMA_TRG_SEL);
  31. }
 楼主| 610u 发表于 2023-10-27 12:37 | 显示全部楼层
最后是uart接收Timeout中断处理
  1. /**
  2. * @brief USART4接收timeout回调
  3. *
  4. */
  5. static void Usart4TimeoutIrqCallback(void)
  6. {
  7.     uint8_t i;
  8.     TIMER0_Cmd(LCD_TMR_UNIT, Tim0_ChannelB, Disable);
  9.     USART_ClearStatus(LCD_USART_CH, UsartRxTimeOut);

  10.         DMA_Cmd(LCD_USART_DMA_UNIT, Disable);

  11.         /* 获取接收到的数据的字节长度 单位:字节 */
  12.         g_ucRecvCnt_FIFO = 0x8000 - ((READ_DMA_CH_REG(&LCD_USART_DMA_UNIT->MONDTCTL0, LCD_USART_DMA_CH) & DMA_DTCTL_CNT) >> DMA_DTCTL_CNT_Pos);
  13.        
  14.     PRO_LOG(LOG_DEBUG, "\r\n");
  15.     for(i = 0; i < g_ucRecvCnt_FIFO; i++)
  16.     {
  17.         PRO_LOG(LOG_DEBUG, "g_ucRecvBuf_FIFO[%d]: 0x%x. \r\n", i, g_ucRecvBuf_FIFO[i]);
  18.     }
  19.     PRO_LOG(LOG_DEBUG, "\r\n");

  20.         DMA_SetDesAddress(LCD_USART_DMA_UNIT, LCD_USART_DMA_CH, (uint32_t)(&g_ucRecvBuf_FIFO[0]));
  21.         DMA_SetTransferCnt(LCD_USART_DMA_UNIT, LCD_USART_DMA_CH, 0x8000);
  22.         DMA_Cmd(LCD_USART_DMA_UNIT, Enable);
  23. }
 楼主| 610u 发表于 2023-10-27 12:38 | 显示全部楼层
最后演示效果如下:
87426653b3eb178f0f.png
李旭昂 发表于 2024-3-7 11:25 | 显示全部楼层
610u 发表于 2023-10-27 12:36
在手册中我们可以看到DMA中有这么一个寄存器,因此我们可以利用这个寄存器。在启动DMA接收数据前,我们可以 ...

博主你好  我这里在使用小华串口超时接收中断时,配置好了定时器  但一直进不去超时中断中去,可以帮忙解决一下吗

评论

进入不了中断,一般是中断配置不正确,仔细检查自己的代码,并对比与别人正常代码不同的地方,第二,检查TIM0时钟配置,看是不是时钟没开启 还是时钟不准,HC32F460这个料的内部晶振不是很准,建议你用外部晶振  发表于 2024-7-9 09:06
小夏天的大西瓜 发表于 2024-3-27 10:31 | 显示全部楼层
启动DMA接收数据是比较高效的一种处理数据手段
suncat0504 发表于 2024-7-30 20:26 | 显示全部楼层
利用定时器的那种,可靠性怎么样?
suncat0504 发表于 2024-7-30 20:26 | 显示全部楼层
如果出现外部设备同时发两帧数据,前后间隔很小的那种,会有影响不?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

53

主题

568

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部