打印
[方案相关]

HC32F460开发之UART+DMA接收不定长数据

[复制链接]
2375|20
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
610u|  楼主 | 2023-10-27 12:31 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
前言
使用hc32平台做产品显示板的开发,主板会通过串口不定时上报设备状态,显示板接收到数据包后,解析并根据主板上报的数据显示设备相应的状态到lcd屏上。关于显示板串口接收这一块,原本打算直接COPY demo例程,串口接收到数据后,进一次中断接收一个字节,一次次下来把整包数据读出来,但是因为考虑到显示板在刷屏过程中,主板可能会上报设备状态,频繁的中断可能会影响刷屏速度。综合考虑,还是打算用上DMA来减少串口中断的次数。

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


使用特权

评论回复
沙发
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功能的使用,手册上也有比较详细的说明

使用特权

评论回复
5
610u|  楼主 | 2023-10-27 12:31 | 只看该作者

使用特权

评论回复
6
610u|  楼主 | 2023-10-27 12:36 | 只看该作者
从上述内容我们可以看到,uart接收的timeout功能是需要用到timer0定时器的,关于uart接收timeout功能最主要的就是设置timeout的时长,关于timer0的初始化例程中也有代码可以参考。
/**
* [url=home.php?mod=space&uid=247401]@brief[/url] usart4 timer0初始化
*
*/
static void Usart4Timer0Init(void)
{
        stc_clk_freq_t stcClkTmp;
        stc_tim0_base_init_t stcTimerCfg;
        stc_tim0_trigger_init_t StcTimer0TrigInit;

        MEM_ZERO_STRUCT(stcClkTmp);
        MEM_ZERO_STRUCT(stcTimerCfg);
        MEM_ZERO_STRUCT(StcTimer0TrigInit);

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

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

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

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

        /* Config timer0 hardware trigger */
        StcTimer0TrigInit.Tim0_InTrigEnable = false;
        StcTimer0TrigInit.Tim0_InTrigClear = true;
        StcTimer0TrigInit.Tim0_InTrigStart = true;
        StcTimer0TrigInit.Tim0_InTrigStop = false;
        TIMER0_HardTriggerInit(LCD_TMR_UNIT, Tim0_ChannelB, &StcTimer0TrigInit);
}

使用特权

评论回复
7
610u|  楼主 | 2023-10-27 12:36 | 只看该作者
这里需要注意的是,不同的uart对应不同的timer0通道,这里需要根据实际硬件去配置。

使用特权

评论回复
8
610u|  楼主 | 2023-10-27 12:36 | 只看该作者
当uart接收Timeout中断触发时,我们就可以到UsartTimeoutIrqCallback中断函数做进一步的接收处理。
那么如何获取到接收到的一帧数据的长度?

使用特权

评论回复
9
610u|  楼主 | 2023-10-27 12:36 | 只看该作者
在手册中我们可以看到DMA中有这么一个寄存器,因此我们可以利用这个寄存器。在启动DMA接收数据前,我们可以先往这个寄存器写一个较大的值,在一帧数据接收完毕后(TIMEOUT中断发生),在Timeout中断中再去读这个寄存器的值,前后的值相减就是此次接收的一帧数据的长度。

使用特权

评论回复
10
610u|  楼主 | 2023-10-27 12:36 | 只看该作者
二、代码实现
简单介绍完原理,下面讲下代码的实现
首先是相关的宏定义
/* DMA config */
#define _DMA_CH_REG_OFFSET(ch)              ((ch) * 0x40ul)
#define _DMA_CH_REG(reg_base, ch)           (*(volatile uint32_t *)((uint32_t)(reg_base) + _DMA_CH_REG_OFFSET(ch)))

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

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


/* USART channel definition */
#define LCD_USART_CH                                                (M4_USART4)
       
/* USART baudrate definition */
#define LCD_USART_BAUDRATE                         (115200ul)

/* USART RX Port/Pin definition */
#define LCD_USART_RX_PORT                   (PortB)
#define LCD_USART_RX_PIN                    (Pin03)
#define LCD_USART_RX_FUNC                   (Func_Usart4_Rx)
       
/* USART TX Port/Pin definition */
#define LCD_USART_TX_PORT                   (PortB)
#define LCD_USART_TX_PIN                    (Pin04)
#define LCD_USART_TX_FUNC                   (Func_Usart4_Tx)

#define LCD_USART_DMA_UNIT                                        (M4_DMA2)
#define LCD_USART_DMA_CH                                        (DmaCh2)
#define LCD_DMA_TRG_SEL                                            (EVT_USART4_RI)

/* Timer0 unit definition */
#define LCD_TMR_UNIT                                     (M4_TMR02)
#define LCD_TMR_FCG_PERIPH                             (PWC_FCG2_PERIPH_TIM02)

#define LCD_USART_RTO_NUM                               (INT_USART4_RTO)
#define LCD_USART_RTO_IRQn                          (Int005_IRQn)

/* USART interrupt number  */
#define LCD_USART_EI_NUM                                  (INT_USART4_EI)
#define LCD_USART_EI_IRQn                           (Int013_IRQn)

使用特权

评论回复
11
610u|  楼主 | 2023-10-27 12:37 | 只看该作者
串口的初始化

/**
* @brief 串口初始化
*
* @param USARTx 串口号
* @param u32Baudrate 波特率
*/
void Usart_Init(M4_USART_TypeDef *USARTx, uint32_t u32Baudrate)
{
        stc_irq_regi_conf_t stcIrqRegiCfg;

    uint32_t u32Fcg1Periph = PWC_FCG1_PERIPH_USART1 | PWC_FCG1_PERIPH_USART2 | \
                             PWC_FCG1_PERIPH_USART3 | PWC_FCG1_PERIPH_USART4;
    stc_usart_uart_init_t stcInitCfg = {
        UsartIntClkCkOutput,
        UsartClkDiv_1,
        UsartDataBits8,
        UsartDataLsbFirst,
        UsartOneStopBit,
        UsartParityNone,
        UsartSampleBit8,
        UsartStartBitFallEdge,
        UsartRtsEnable,
    };

        MEM_ZERO_STRUCT(stcIrqRegiCfg);

    USART_DeInit(USARTx);

    /* Initialize DMA */
    Usart4DmaInit();

    /* Initialize Timer0 */
    Usart4Timer0Init();

    /* Enable peripheral clock */
    PWC_Fcg1PeriphClockCmd(u32Fcg1Periph, Enable);

    PORT_DebugPortSetting(1<<2, Disable);
    PORT_DebugPortSetting(1<<4, Disable);

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

    /* Initialize UART */
    USART_UART_Init(USARTx, &stcInitCfg);
       
    /* Set baudrate */
           USART_SetBaudrate(USARTx, u32Baudrate);

        /* IRQ init */
    Usart4IrqInit();
    USART_FuncCmd(USARTx, UsartTimeOut, Enable);        //
    USART_FuncCmd(USARTx, UsartTimeOutInt, Enable);        //

        USART_FuncCmd(USARTx, UsartTx, Enable);
        USART_FuncCmd(USARTx, UsartRx, Enable);
        USART_FuncCmd(USARTx, UsartRxInt, Enable);
}

使用特权

评论回复
12
610u|  楼主 | 2023-10-27 12:37 | 只看该作者
串口初始化这里有个问题需要注意下,因为我们选用的PB3,PB4引脚,默认是JTAG调试引脚,因此,我们想配置成USART口使用时,有部分寄存器需要设置下

使用特权

评论回复
13
610u|  楼主 | 2023-10-27 12:37 | 只看该作者
这里我们需要使能uart的Timeout功能还有Timeout中断。
 /**
* @brief USART4中断初始化
*
*/
static void Usart4IrqInit(void)
{
        stc_irq_regi_conf_t stcIrqRegiCfg;

    /* Set USART RX timeout error IRQ */
    stcIrqRegiCfg.enIRQn = LCD_USART_RTO_IRQn;
    stcIrqRegiCfg.pfnCallback = &Usart4TimeoutIrqCallback;
    stcIrqRegiCfg.enIntSrc = LCD_USART_RTO_NUM;
    enIrqRegistration(&stcIrqRegiCfg);
    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);

    /* Set USART RX error IRQ */
    stcIrqRegiCfg.enIRQn = LCD_USART_EI_IRQn;
    stcIrqRegiCfg.pfnCallback = &Usart4ErrIrqCallback;
    stcIrqRegiCfg.enIntSrc = LCD_USART_EI_NUM;
    enIrqRegistration(&stcIrqRegiCfg);
    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);
}

使用特权

评论回复
14
610u|  楼主 | 2023-10-27 12:37 | 只看该作者
DMA的初始化
/**
* @brief USART4 dma初始化
*
*/       
static void Usart4DmaInit(void)
{
    stc_dma_config_t stcDmaInit;

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

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

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

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

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

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

    /* Set DMA trigger source. */
    DMA_SetTriggerSrc(LCD_USART_DMA_UNIT, LCD_USART_DMA_CH, LCD_DMA_TRG_SEL);
}

使用特权

评论回复
15
610u|  楼主 | 2023-10-27 12:37 | 只看该作者
最后是uart接收Timeout中断处理
/**
* @brief USART4接收timeout回调
*
*/
static void Usart4TimeoutIrqCallback(void)
{
    uint8_t i;
    TIMER0_Cmd(LCD_TMR_UNIT, Tim0_ChannelB, Disable);
    USART_ClearStatus(LCD_USART_CH, UsartRxTimeOut);

        DMA_Cmd(LCD_USART_DMA_UNIT, Disable);

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

        DMA_SetDesAddress(LCD_USART_DMA_UNIT, LCD_USART_DMA_CH, (uint32_t)(&g_ucRecvBuf_FIFO[0]));
        DMA_SetTransferCnt(LCD_USART_DMA_UNIT, LCD_USART_DMA_CH, 0x8000);
        DMA_Cmd(LCD_USART_DMA_UNIT, Enable);
}

使用特权

评论回复
16
610u|  楼主 | 2023-10-27 12:38 | 只看该作者
最后演示效果如下:

使用特权

评论回复
17
李旭昂| | 2024-3-7 11:25 | 只看该作者
610u 发表于 2023-10-27 12:36
在手册中我们可以看到DMA中有这么一个寄存器,因此我们可以利用这个寄存器。在启动DMA接收数据前,我们可以 ...

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

使用特权

评论回复
评论
zkevin8879 2024-7-9 09:06 回复TA
进入不了中断,一般是中断配置不正确,仔细检查自己的代码,并对比与别人正常代码不同的地方,第二,检查TIM0时钟配置,看是不是时钟没开启 还是时钟不准,HC32F460这个料的内部晶振不是很准,建议你用外部晶振 
18
小夏天的大西瓜| | 2024-3-27 10:31 | 只看该作者
启动DMA接收数据是比较高效的一种处理数据手段

使用特权

评论回复
19
suncat0504| | 2024-7-30 20:26 | 只看该作者
利用定时器的那种,可靠性怎么样?

使用特权

评论回复
20
suncat0504| | 2024-7-30 20:26 | 只看该作者
如果出现外部设备同时发两帧数据,前后间隔很小的那种,会有影响不?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

49

主题

517

帖子

0

粉丝