前言
使用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