本帖最后由 woai32lala 于 2024-2-28 10:12 编辑
[url=home.php?mod=space&uid=760190]@21小跑堂 #申请原创#[/url] DMA串口超时中断+DMA数据传输
1、前言
在实际应用中,经常会用到串口接收不定长数据的问题,一帧数据中包括帧头、帧尾、数据、校验码等。常用的接收数据的方式是用串口中断,每接收一个数据,中断一次,然后去读取,去判断是不是帧头,再去接收其他的数据,这种不停中断的方式很浪费CPU的资源。 因此通常在STM32中,会有串口空闲中断这种操作方式,这种操作方式在我之前的帖子中已有详细的介绍,采用串口dma接收+空闲中断的时候,如果两帧数据的发送时间间隔大于设置波特率发送一个字节的时间,则stm32会认为触发依次空闲中断。
这次我们采用的是HC32F460,找遍了数据手册也没有找到空闲中断这一条说明,后来发现他有一个叫做超时接收中断,其实和STm32的串口空闲中断差不多,只不过STM32间隔时间是对应波特率下一个字节的时间,而HC32F460的超时中断时间可自己设置,如果也设置为对应波特率下1个字节的时候,那么就和STM32作用一致,可以说是HC32F460空闲中断时间可设置。 它是HC32中USART主要特性之一,如下图所示。 2、应用 通过数据手册可以发现,用于超时中断判定的定时器只能是定时器0,根据选择的串口选择对应的定时器通道,比如这次我们选用的串口3,就选择定时器0 单元2 A通道 下面有个比较坑的点,就是定时器0的时钟源设置,默认为Tim0_XTAL32,如下图所示。 这里当时我忘改了,我没有外接32.768KHz时钟,但是莫名其妙的能用,一直没找到原因。 查看寄存器CMU,发现XTAL32 和LRC都在震荡,就挺晕的,以为没有接XTLA32它会自动判断切换为内部LRC,但是查看定时器的时钟源还是XTLA32. 后来找了供应商说产生的时钟是外部干扰造成。 因为你本身开启这个时钟源 输入这个时钟引脚是浮空的,会继续计数,但是计数频率肯定是不对的,还是要焊接32.768KHz晶振或者切换为内部LRC。 我们将时钟切换为内部LRC,内部RC也是32.768Khz. 计数模式我们设置为异步,因为时钟为非同步时钟。
static void Timer0Init(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(TMR_FCG_PERIPH, Enable);
/* Clear CNTAR register for channel A */
TIMER0_WriteCntReg(TMR_UNIT, Tim0_ChannelA, 0u);
TIMER0_WriteCntReg(TMR_UNIT, Tim0_ChannelB, 0u);
/* Config register for channel A */
stcTimerCfg.Tim0_CounterMode = Tim0_Async;
stcTimerCfg.Tim0_AsyncClockSource = Tim0_LRC;
stcTimerCfg.Tim0_ClockDivision = Tim0_ClkDiv8;
stcTimerCfg.Tim0_CmpValue = 32u;
TIMER0_BaseInit(TMR_UNIT, Tim0_ChannelA, &stcTimerCfg);
/* Clear compare flag */
TIMER0_ClearFlag(TMR_UNIT, Tim0_ChannelA);
/* Config timer0 hardware trigger */
StcTimer0TrigInit.Tim0_InTrigEnable = false;
StcTimer0TrigInit.Tim0_InTrigClear = true;
StcTimer0TrigInit.Tim0_InTrigStart = true;
StcTimer0TrigInit.Tim0_InTrigStop = false;
TIMER0_HardTriggerInit(TMR_UNIT, Tim0_ChannelA, &StcTimer0TrigInit);
}
设置比较时间为320,当两次串口数据接收时间间隔大于320 * 1/32.768k = 0.009765625s时,即触发一次串口接收超时中断。串口波特率为115200,发送一个字节的时间为1/115200*10 = 0.00008680555555555556(这里发送一个字节数据=起始位1bit+数据位8bit+停止位1bit,加起来就是10bit),我们设置的间隔时间已经远远超过发送一个字节的时间。 static void UsartTimeoutIrqCallback(void)
{
TIMER0_Cmd(TMR_UNIT, Tim0_ChannelA,Disable);
DMA_ChannelCmd(M4_DMA1, DmaCh0, Disable); //超时重启DMA,以进行新一轮的接收
USART_ClearStatus(USART_CH, UsartRxTimeOut);
rx_cnt = BUFF_SIZE - M4_DMA1->MONDTCTL0_f.CNT;//获取接收到的数据量
DMA_SetTransferCnt(M4_DMA1, DmaCh0, BUFF_SIZE);
DMA_ChannelCmd(M4_DMA1, DmaCh0, Enable);
}
设置最大的接收数据为200个字节,M4_DMA1->MONDTCTL0_f.CNT这个数值是倒数值,值为设置的接收值数量,比如接收10个数据,该值为190。 因此用BUFF_SIZE - M4_DMA1->MONDTCTL0_f.CNT 设置值- 剩余的值就是实际接收到的数据量。 DMA初始化
/**
*******************************************************************************
** \brief Initialize DMA.
**
** \param [in] None
**
** \retval None
**
******************************************************************************/
static void DmaInit(void)
{
stc_dma_config_t stcDmaInit;
stc_irq_regi_conf_t stcIrqRegiCfg;
/* Enable peripheral clock */
PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_DMA1 | PWC_FCG0_PERIPH_DMA2,Enable);
/* Enable DMA. */
DMA_Cmd(DMA_UNIT,Enable);
/* Initialize DMA. */
MEM_ZERO_STRUCT(stcDmaInit);
stcDmaInit.u16BlockSize = 1u; /* 1 block */
stcDmaInit.u16TransferCnt = (uint16_t)m_stcRxBufHanlde.u8Size; /* Transfer count */
stcDmaInit.u32SrcAddr = ((uint32_t)(&USART_CH->DR)+2ul); /* Set source address. */
stcDmaInit.u32DesAddr = (uint32_t)(m_stcRxBufHanlde.au8Buf); /* 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(DMA_UNIT, DMA_CH, &stcDmaInit);
DMA_SetTransferCnt(M4_DMA1, DmaCh0, BUFF_SIZE);
/* Enable the specified DMA channel. */
DMA_ChannelCmd(DMA_UNIT, DMA_CH, Enable);
/* Clear DMA flag. */
DMA_ClearIrqFlag(DMA_UNIT, DMA_CH, TrnCpltIrq);
/* Enable peripheral circuit trigger function. */
PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_AOS,Enable);
/* Set DMA trigger source. */
DMA_SetTriggerSrc(DMA_UNIT, DMA_CH, DMA_TRG_SEL);
/* Set DMA block transfer complete IRQ */
stcIrqRegiCfg.enIRQn = DMA_BTC_INT_IRQn;
stcIrqRegiCfg.pfnCallback = &DmaBtcIrqCallback;
stcIrqRegiCfg.enIntSrc = DMA_BTC_INT_NUM;
enIrqRegistration(&stcIrqRegiCfg);
NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);
}
3、测试 我们通过串口助手一次发送92个数据,然后我们可以看到watch2里面的数据跟串口助手发送的数据一致。
|
HC32F460的超时中断+DMA的案例讲解,以更加灵活稳定的方式处理不定长的串口数据接收。