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


前言

使用hc32平台做产品显示板的开发,主板会通过串口不定时上报设备状态,显示板接收到数据包后,解析并根据主板上报的数据显示设备相应的状态到lcd屏上。关于显示板串口接收这一块,原本打算直接COPY demo例程,串口接收到数据后,进一次中断接收一个字节,一次次下来把整包数据读出来,但是因为考虑到显示板在刷屏过程中,主板可能会上报设备状态,频繁的中断可能会影响刷屏速度。综合考虑,还是打算用上DMA来减少串口中断的次数。


一、过程说明

由于之前做过类似的工作,这里对整个过程的说明就不再赘述,具体可以看之前的文章GD32开发之UART+DMA接收不定长数据,下面只介绍HC平台和GD平台两者之间关于这一块的不同点,以及对应的解决方法。
在这里插入图片描述
上面这张图是GD32关于UART+DMA接收不定长数据包的一个简要的流程图。我们可以看到,GD32是有IDLEF这么个寄存器标志位的,通过这个标志位,我们可以随时知道到一帧数据是否已经结束,从而再去做对应的接收操作。但是翻看HC的用户手册,却找不到HC平台有类似的寄存器标志位,取而代之的,是更加灵活的TIMEOUT功能。
在这里插入图片描述
关于TIMEOUT功能的使用,手册上也有比较详细的说明
在这里插入图片描述
从上述内容我们可以看到,uart接收的timeout功能是需要用到timer0定时器的,关于uart接收timeout功能最主要的就是设置timeout的时长,关于timer0的初始化例程中也有代码可以参考。

/**
 * @brief 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);
}

这里需要注意的是,不同的uart对应不同的timer0通道,这里需要根据实际硬件去配置。
在这里插入图片描述
当uart接收Timeout中断触发时,我们就可以到UsartTimeoutIrqCallback中断函数做进一步的接收处理。
那么如何获取到接收到的一帧数据的长度?
在这里插入图片描述
在手册中我们可以看到DMA中有这么一个寄存器,因此我们可以利用这个寄存器。在启动DMA接收数据前,我们可以先往这个寄存器写一个较大的值,在一帧数据接收完毕后(TIMEOUT中断发生),在Timeout中断中再去读这个寄存器的值,前后的值相减就是此次接收的一帧数据的长度。

二、代码实现

简单介绍完原理,下面讲下代码的实现
首先是相关的宏定义

/* 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)

串口的初始化


/**
 * @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);
}

串口初始化这里有个问题需要注意下,因为我们选用的PB3,PB4引脚,默认是JTAG调试引脚,因此,我们想配置成USART口使用时,有部分寄存器需要设置下,具体参考关于HC32F460串口调试遇到的问题记录

这里我们需要使能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);
}

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);
}

最后是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);
}

最后演示效果如下:
在这里插入图片描述


总结

关于HC32 Uart+DMA接收不定长数据的内容就暂时介绍到这里,后续有其他内容再继续分享,源代码也已上传,需要的可以下载hc32f460petb_template.7z

  • 1
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
HC32F460是一款32位ARM Cortex-M4内核的微控制器,它内置了多个UART模块,可以用于串口通信。下面是使用UART模块进行串口通信的基本步骤: 1. 配置UART模块的工作参数,包括波特率、数据位、停止位、奇偶校验等。可以通过修改UART模块的寄存器来完成配置。 2. 使能UART模块,并设置相应的中断使能位。可以通过修改UART模块的寄存器来完成使能和中断设置。 3. 发送数据。可以通过写入UART模块的数据寄存器来发送数据。 4. 接收数据。可以通过读取UART模块的数据寄存器来接收数据。 下面是一个使用UART模块进行串口通信的示例代码: ```c #include "hc32f460.h" void UART_Init(void) { // 配置UART模块的工作参数 M0P_UART4->SCON_f.SM = 1; // 使能串口模式 M0P_UART4->SCON_f.REN = 1; // 使能接收 M0P_UART4->SCON_f.PM = 0; // 不使用奇偶校验 M0P_UART4->SCON_f.BD = 0; // 8位数据位 M0P_UART4->SCON_f.SBV = 1; // 使用中断方式接收数据 M0P_UART4->BGR = 0x1A; // 波特率为115200 // 使能UART模块,并设置相应的中断使能位 M0P_UART4->SCON_f.REN = 1; // 使能接收 M0P_UART4->SCON_f.TI = 1; // 清除发送中断标志位 M0P_UART4->SCON_f.RI = 1; // 清除接收中断标志位 M0P_UART4->SCON_f.TIE = 0; // 禁止发送中断 M0P_UART4->SCON_f.RIE = 1; // 使能接收中断 // 设置中断优先级 NVIC_ClearPendingIRQ(UART4_RX_IRQn); NVIC_SetPriority(UART4_RX_IRQn, 3); NVIC_EnableIRQ(UART4_RX_IRQn); } void UART_SendData(uint8_t data) { // 发送数据 M0P_UART4->SBUF = data; while (!M0P_UART4->SCON_f.TI); M0P_UART4->SCON_f.TI = 0; } uint8_t UART_ReceiveData(void) { // 接收数据 while (!M0P_UART4->SCON_f.RI); M0P_UART4->SCON_f.RI = 0; return M0P_UART4->SBUF; } void UART4_RX_IRQHandler(void) { // 处理接收中断 if (M0P_UART4->SCON_f.RI) { uint8_t data = UART_ReceiveData(); // 处理接收到的数据 } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Top0_0lll

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值