发新帖本帖赏金 40.00元(功能说明)我要提问
12下一页
返回列表
[方案相关]

DMA串口超时中断+DMA数据传输

[复制链接]
779|24
手机看帖
扫描二维码
随时随地手机跟帖
woai32lala|  楼主 | 2024-2-27 16:09 | 显示全部楼层 |阅读模式
本帖最后由 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主要特性之一,如下图所示。
1248065938eda81432.png
2、应用
  通过数据手册可以发现,用于超时中断判定的定时器只能是定时器0,根据选择的串口选择对应的定时器通道,比如这次我们选用的串口3,就选择定时器0 单元2 A通道
9165265938eed5591a.png
下面有个比较坑的点,就是定时器0的时钟源设置,默认为Tim0_XTAL32,如下图所示。
7038265938efb6024a.png
这里当时我忘改了,我没有外接32.768KHz时钟,但是莫名其妙的能用,一直没找到原因。
查看寄存器CMU,发现XTAL32 和LRC都在震荡,就挺晕的,以为没有接XTLA32它会自动判断切换为内部LRC,但是查看定时器的时钟源还是XTLA32.
6190265938f0b6d23c.png     908165938f12b5a7c.png
2220965938f1b7df3d.png
后来找了供应商说产生的时钟是外部干扰造成。 因为你本身开启这个时钟源  输入这个时钟引脚是浮空的,会继续计数,但是计数频率肯定是不对的,还是要焊接32.768KHz晶振或者切换为内部LRC。
我们将时钟切换为内部LRC,内部RC也是32.768Khz.
7623865938f29305ad.png
计数模式我们设置为异步,因为时钟为非同步时钟。
8124165938f3f84773.png
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里面的数据跟串口助手发送的数据一致。
4313165938fe82bd5d.png
1987765938fee22f10.png



uart_irq_timeout_share.zip

6.81 MB

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 40.00 元 2024-02-29
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2024-2-29 17:21 回复TA
HC32F460的超时中断+DMA的案例讲解,以更加灵活稳定的方式处理不定长的串口数据接收。 
micoccd| | 2024-2-27 17:04 | 显示全部楼层
感觉超时中断没有空闲中断好用啊

使用特权

评论回复
woai32lala|  楼主 | 2024-2-27 17:16 | 显示全部楼层
micoccd 发表于 2024-2-27 17:04
感觉超时中断没有空闲中断好用啊

关键这个芯片没有空闲中断哇,只有通过超时中断来判断

使用特权

评论回复
qintian0303| | 2024-2-28 08:39 | 显示全部楼层
超时中断超过几个时钟属于超时啊

使用特权

评论回复
woai32lala|  楼主 | 2024-2-28 08:49 | 显示全部楼层
qintian0303 发表于 2024-2-28 08:39
超时中断超过几个时钟属于超时啊

这个可以配置
4797865de83192043d.png

使用特权

评论回复
stormwind123| | 2024-2-28 12:20 | 显示全部楼层
超时中断的触发条件是什么?

使用特权

评论回复
laocuo1142| | 2024-2-28 12:20 | 显示全部楼层
DMA传输过程中如何保证数据的完整性?

使用特权

评论回复
flycamelaaa| | 2024-2-28 13:00 | 显示全部楼层
DMA传输是否支持连续传输?

使用特权

评论回复
804879880| | 2024-2-29 14:26 | 显示全部楼层

使用特权

评论回复
woai32lala|  楼主 | 2024-2-29 14:44 | 显示全部楼层
stormwind123 发表于 2024-2-28 12:20
超时中断的触发条件是什么?

时间控制

使用特权

评论回复
woai32lala|  楼主 | 2024-2-29 14:44 | 显示全部楼层
flycamelaaa 发表于 2024-2-28 13:00
DMA传输是否支持连续传输?

支持

使用特权

评论回复
储小勇_526| | 2024-2-29 15:55 | 显示全部楼层
正好项目中用到这个功能,非常nice,又学到一个新的知识。

使用特权

评论回复
804879880| | 2024-3-1 09:09 | 显示全部楼层

使用特权

评论回复
李旭昂| | 2024-3-7 11:22 | 显示全部楼层
博主你好,我这里配置好定时器 接收中断和超时中断,一直进不去超时中断是什么原因呀,可以帮忙解决一下吗,太感谢了

使用特权

评论回复
woai32lala|  楼主 | 2024-3-7 13:01 | 显示全部楼层
李旭昂 发表于 2024-3-7 11:22
博主你好,我这里配置好定时器 接收中断和超时中断,一直进不去超时中断是什么原因呀,可以帮忙解决一下吗 ...

先看看发送的引脚对不对

使用特权

评论回复
李旭昂| | 2024-3-7 13:15 | 显示全部楼层
woai32lala 发表于 2024-3-7 13:01
先看看发送的引脚对不对

引脚没问题,可以正常进入接收中断,但是一直没有触发超时中断,可以加您下吗

使用特权

评论回复
woai32lala|  楼主 | 2024-3-8 08:34 | 显示全部楼层
李旭昂 发表于 2024-3-7 13:15
引脚没问题,可以正常进入接收中断,但是一直没有触发超时中断,可以加您下吗 ...

可以看看仿真时DMA的数据有没有变化

使用特权

评论回复
李旭昂| | 2024-3-8 09:21 | 显示全部楼层
woai32lala 发表于 2024-3-8 08:34
可以看看仿真时DMA的数据有没有变化

没有使用DMA进行接收,用的串口接收中断,只能使用DMA吗?

使用特权

评论回复
woai32lala|  楼主 | 2024-3-8 09:41 | 显示全部楼层
李旭昂 发表于 2024-3-8 09:21
没有使用DMA进行接收,用的串口接收中断,只能使用DMA吗?

。。。你想表达什么,DMA只是个数据获取的通道,大量的数据用串口中断会占用时间。这里用的超时中断是指距离接收上一帧数据多长时间没有接收到会触发一次超时中断

使用特权

评论回复
李旭昂| | 2024-3-8 09:56 | 显示全部楼层
woai32lala 发表于 2024-3-8 09:41
。。。你想表达什么,DMA只是个数据获取的通道,大量的数据用串口中断会占用时间。这里用的超时中断是指 ...

不是的哥,我现在配置的是接收中断+超时中断的方式,没有去用DMA进行数据接收,现在的问题是接收中断可以触发,但是超时中断一直触发不了

使用特权

评论回复
发新帖 本帖赏金 40.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

103

主题

508

帖子

5

粉丝