【嵌入式】HC32F460串口接收超时中断+DMA

这篇具有很好参考价值的文章主要介绍了【嵌入式】HC32F460串口接收超时中断+DMA。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一 项目背景

        项目需要使用一款UART串口编码器,编码器的数据以波特率57600持续向外发送。但这组数据包没有固定的包头和校验尾,仅仅是由多圈圈数和单圈角度组成的六字节数据码,这样接收到的数组无法确定实际的下标,所以这边考虑用串口接收超时中断+DMA来实现。

二 原理说明

【1】UART原理说明:参考【嵌入式】NXP/LPC使用GPIO+定时器模拟UART串口接收

【2】超时中断原理说明:接收的数据包通过逻辑分析仪,如下所示:

hc32 串口,嵌入式,单片机,嵌入式硬件,C语言,串口文章来源地址https://www.toymoban.com/news/detail-632507.html

        由上面的数据可以看到,两个包之间的发送间隔为500us左右,而一个包的发送时间为170us(波特率为57600,那么每位数据是17us,一个包10位数据,就是170us),所以只要在串口收发的过程中加一个定时器,设定超时时间为400us(大于170us,小于500us即可),那么 只要超时了,说明下一次收到的位即为起始位

        STM32中有一个空闲中断(IDLE)的概念,而HC32中没有,取而代之的是串口接收超时中断,两者基本功能是类似的,都是在串口超过一段时间没有接收数据之后触发的一个中断功能。HC32F460的用户手册中对此也有详细说明(我们这边用的是USART4串口,所以相对应的需需要使用 Timer0 Unit2 B 通道):

hc32 串口,嵌入式,单片机,嵌入式硬件,C语言,串口

【3】DMA原理说明:DMA(Direct Memory Access,直接存储器访问) 是单片机的一个外设,它的主要功能是用来搬移数据,但是不需要占用 CPU,即在传输数据的时候, CPU 可以干其他的事情,好像多线程一样。(具体可以参考:串口DMA传输模式)

hc32 串口,嵌入式,单片机,嵌入式硬件,C语言,串口

         这边用到DMA,是因为编码器发送数据比较快,若是一直进中断会挤占CPU的资源,所以考虑用DMA改进。

三 设计实现--超时定时器部分

【1】超时定时器初始化( Timer0 Unit2 B 通道 ),这个过程中主要关注一下定时器时间的设置,如下面的 stcTimerCfg.Tim0_CmpValue = 4200 ,它的时钟源是 Tim0_Pclk1 ,在HC32F460中,这个时钟是168MHz的一半,即84MHz,时钟的分频系数为8,根据公式:

T=CmpValue*ClockSource*ClockDivision

其中,T = 400us,ClockSource=1/84MHz,ClockDivision=8,计算出CmpValue=4200:

void Timer0_Config(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(PWC_FCG2_PERIPH_TIM02, Enable);

    /* Clear CNTAR register for channel B */
    TIMER0_WriteCntReg(M4_TMR02, Tim0_ChannelB, 0u);

    /* Config register for channel B */
    stcTimerCfg.Tim0_CounterMode = Tim0_Sync;
    stcTimerCfg.Tim0_SyncClockSource = Tim0_Pclk1;
    stcTimerCfg.Tim0_ClockDivision = Tim0_ClkDiv8;
    stcTimerCfg.Tim0_CmpValue = 4200;
    TIMER0_BaseInit(M4_TMR02, Tim0_ChannelB, &stcTimerCfg);

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

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

四 设计实现--串口部分

【1】串口初始化:

/* USART baudrate definition */
#define USART4_BAUDRATE                  (57600)
/* USART Interrupt Number */
#define USART4_IRQn                      (Int025_IRQn)
#define USART4_ERR_IRQn                  (Int026_IRQn)
#define USART4_RTO_IRQn                  (Int029_IRQn)
/* USART RX Port/Pin definition */
#define USART4_RX_PORT                   (PortE)
#define USART4_RX_PIN                    (Pin14)
#define USART4_RX_FUNC                   (Func_Usart4_Rx)

void initUART4(void)
{
    en_result_t enRet = Ok;
    stc_irq_regi_conf_t stcIrqRegiCfg;
    
    /*配置串口使用的时钟和基本通信配置*/
    const stc_usart_uart_init_t stcInitCfg = {
        UsartIntClkCkOutput,
        UsartClkDiv_1,
        UsartDataBits8,
        UsartDataLsbFirst,
        UsartOneStopBit,
        UsartParityNone,
        UsartSampleBit8,
        UsartStartBitFallEdge,
        UsartRtsEnable,
    };
    
    /*打开时钟*/
    PWC_Fcg1PeriphClockCmd(PWC_FCG1_PERIPH_USART4, Enable);
    
    /*配置相应的IO作为串口的RX引脚*/
    PORT_SetFunc(USART4_RX_PORT, USART4_RX_PIN, USART4_RX_FUNC, Disable);

    /*初始化串口配置*/
    enRet = USART_UART_Init(M4_USART4, &stcInitCfg);
    if (enRet != Ok)while (1);
    /*串口波特率设置*/
    enRet = USART_SetBaudrate(M4_USART4, USART4_BAUDRATE);
    if (enRet != Ok)while (1);
    
    /*设置串口接收中断*/
    stcIrqRegiCfg.enIRQn = USART4_IRQn;
    stcIrqRegiCfg.pfnCallback = &Usart4RxIrqCallback;
    stcIrqRegiCfg.enIntSrc = INT_USART4_RI;
    enIrqRegistration(&stcIrqRegiCfg);
    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);
    
    /*设置串口接收错误中断*/
    stcIrqRegiCfg.enIRQn = USART4_ERR_IRQn;
    stcIrqRegiCfg.pfnCallback = &Usart4ErrIrqCallback;
    stcIrqRegiCfg.enIntSrc = INT_USART4_EI;
    enIrqRegistration(&stcIrqRegiCfg);
    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);
    
    /*设置接收超时中断*/
    stcIrqRegiCfg.enIRQn = USART4_RTO_IRQn;
    stcIrqRegiCfg.pfnCallback = &Usart4TimeoutIrqCallback;
    stcIrqRegiCfg.enIntSrc = INT_USART4_RTO;
    enIrqRegistration(&stcIrqRegiCfg);
    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);

    
    USART_FuncCmd(M4_USART4, UsartRx, Enable);//使能接收
    USART_FuncCmd(M4_USART4, UsartRxInt, Enable);//使能接收中断
    USART_FuncCmd(M4_USART4, UsartTimeOut, Enable);//使能超时
    USART_FuncCmd(M4_USART4, UsartTimeOutInt, Enable);//使能超时中断
}

【2】串口接收中断回调:

#define ENCODER_LEN      6
uint8_t ecd_buf[ENCODER_LEN];
uint8_t ecd_timeout_flag;

static void Usart4RxIrqCallback(void)
{
    static uint8_t cnt = 0;
    while(1)
	{
		if (Set == USART_GetStatus(M4_USART4, UsartRxNoEmpty)) 
		{
            if(ecd_timeout_flag == 1)  //如果超时,下一个接收到的即为起始位
                cnt = 0;
            ecd_buf[cnt++] = USART_RecData(M4_USART4);
            ecd_timeout_flag = 0;
            
            if(cnt > 5)
                cnt = 0;
		}
        else
            break;
	}
}

【3】串口接收错误中断回调:

static void Usart4ErrIrqCallback(void)
{
    if (Set == USART_GetStatus(M4_USART4, UsartFrameErr))
        USART_ClearStatus(M4_USART4, UsartFrameErr);

    if (Set == USART_GetStatus(M4_USART4, UsartParityErr))
        USART_ClearStatus(M4_USART4, UsartParityErr);

    if (Set == USART_GetStatus(M4_USART4, UsartOverrunErr))
        USART_ClearStatus(M4_USART4, UsartOverrunErr);
}

【4】串口接收超时中断回调:

static void Usart4TimeoutIrqCallback(void)
{
    ecd_timeout_flag = 1;  //下一次接收为通讯码的开始位
    TIMER0_Cmd(M4_TMR02, Tim0_ChannelB,Disable);
    USART_ClearStatus(M4_USART4, UsartRxTimeOut);
}

        到这边为止,就可以正常的读到编码器的数据了,而且是以编码器的发送顺序排列在ecd_buf数组中,只要处理该数组就可以取到编码器的多圈圈数和单圈角度。

        下面的DMA部分是想改进一下控制方案,使得不那么频繁地进入接收中断,以减小CPU的资源消耗。

五 设计实现--DMA部分

【1】DMA初始化和中断,其中主要关注几点:

        一是接收的数据需要映射到ecd_buf的地址:

        (stcDmaInit.u32DesAddr = (uint32_t)(&ecd_buf))

        二是发送数据模式需要改为递增:

        (stcDmaInit.stcDmaChCfg.enDesInc = AddressIncrease):

static void DmaBtcIrqCallback(void)
{
    USART_ClearStatus(M4_USART4, UsartRxTimeOut);  //清楚接收超时标志
    DMA_ClearIrqFlag(M4_DMA1, DmaCh0, BlkTrnCpltIrq);
}

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(M4_DMA1,Enable);

    /* Initialize DMA. */
    MEM_ZERO_STRUCT(stcDmaInit);
    stcDmaInit.u16BlockSize = 1u; /* 1 block */
    stcDmaInit.u32SrcAddr = ((uint32_t)(&M4_USART4->DR)+2ul); /* Set source address. */
    stcDmaInit.u32DesAddr = (uint32_t)(&ecd_buf);     /* 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(M4_DMA1, DmaCh0, &stcDmaInit);

    /* Enable the specified DMA channel. */
    DMA_ChannelCmd(M4_DMA1, DmaCh0, Enable);

    /* Clear DMA flag. */
    DMA_ClearIrqFlag(M4_DMA1, DmaCh0, TrnCpltIrq);

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

    /* Set DMA trigger source. */
    DMA_SetTriggerSrc(M4_DMA1, DmaCh0, EVT_USART4_RI);

    /* Set DMA block transfer complete IRQ */
    stcIrqRegiCfg.enIRQn = Int030_IRQn;
    stcIrqRegiCfg.pfnCallback = &DmaBtcIrqCallback;
    stcIrqRegiCfg.enIntSrc = INT_DMA1_BTC0;
    enIrqRegistration(&stcIrqRegiCfg);
    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);
}

【2】串口接收中断就不用了,由DMA直接接收即可:

//串口接收错误中断回调
static void Usart4ErrIrqCallback(void)
{
    if (Set == USART_GetStatus(M4_USART4, UsartFrameErr))
        USART_ClearStatus(M4_USART4, UsartFrameErr);

    if (Set == USART_GetStatus(M4_USART4, UsartParityErr))
        USART_ClearStatus(M4_USART4, UsartParityErr);

    if (Set == USART_GetStatus(M4_USART4, UsartOverrunErr))
        USART_ClearStatus(M4_USART4, UsartOverrunErr);
}

//串口接收超时中断回调
static void Usart4TimeoutIrqCallback(void)
{
    TIMER0_Cmd(M4_TMR02, Tim0_ChannelB,Disable);
    USART_ClearStatus(M4_USART4, UsartRxTimeOut);
    
    DMA_ChannelCmd(M4_DMA1, DmaCh0, Disable);  //超时重启DMA,以进行新一轮的接收
    DMA_SetDesAddress(M4_DMA1, DmaCh0, (uint32_t)(ecd_buf));
	DMA_SetTransferCnt(M4_DMA1, DmaCh0, ENCODER_LEN);
    DMA_ChannelCmd(M4_DMA1, DmaCh0, Enable);
}

void initUART4(void)
{
    en_result_t enRet = Ok;
    stc_irq_regi_conf_t stcIrqRegiCfg;
    
    /*配置串口使用的时钟和基本通信配置*/
    const stc_usart_uart_init_t stcInitCfg = {
        UsartIntClkCkOutput,
        UsartClkDiv_1,
        UsartDataBits8,
        UsartDataLsbFirst,
        UsartOneStopBit,
        UsartParityNone,
        UsartSampleBit8,
        UsartStartBitFallEdge,
        UsartRtsEnable,
    };
    
    DmaInit();
    
    /*打开时钟*/
    PWC_Fcg1PeriphClockCmd(PWC_FCG1_PERIPH_USART4, Enable);
    
    /*配置相应的IO作为串口的RX引脚*/
    PORT_SetFunc(USART4_RX_PORT, USART4_RX_PIN, USART4_RX_FUNC, Disable);

    /*初始化串口配置*/
    enRet = USART_UART_Init(M4_USART4, &stcInitCfg);
    if (enRet != Ok)while (1);
    /*串口波特率设置*/
    enRet = USART_SetBaudrate(M4_USART4, USART4_BAUDRATE);
    if (enRet != Ok)while (1);
    
    /*设置串口接收中断舍弃*/
    
    /*设置串口接收错误中断*/
    stcIrqRegiCfg.enIRQn = USART4_ERR_IRQn;
    stcIrqRegiCfg.pfnCallback = &Usart4ErrIrqCallback;
    stcIrqRegiCfg.enIntSrc = INT_USART4_EI;
    enIrqRegistration(&stcIrqRegiCfg);
    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);
    
    /*设置接收超时中断*/
    stcIrqRegiCfg.enIRQn = USART4_RTO_IRQn;
    stcIrqRegiCfg.pfnCallback = &Usart4TimeoutIrqCallback;
    stcIrqRegiCfg.enIntSrc = INT_USART4_RTO;
    enIrqRegistration(&stcIrqRegiCfg);
    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);
    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);
    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);

    
    USART_FuncCmd(M4_USART4, UsartRx, Enable);//使能接收
    USART_FuncCmd(M4_USART4, UsartRxInt, Enable);//使能接收中断
    USART_FuncCmd(M4_USART4, UsartTimeOut, Enable);//使能超时
    USART_FuncCmd(M4_USART4, UsartTimeOutInt, Enable);//使能超时中断
}

        项目中只需要用到串口数据的接收,所以这边没有DMA发送的内容。

六 总结

         综上,便可以通过串口接收超时中断或者串口接收超时中断+DMA进行接收了。通过DEBUG也可以看到ecd_buf中的数据按顺序排列为0x00,0x00,0xDD,0x2E,0x38,0x77,与逻辑分析仪中的一致:

hc32 串口,嵌入式,单片机,嵌入式硬件,C语言,串口

到了这里,关于【嵌入式】HC32F460串口接收超时中断+DMA的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 嵌入式开发--STM32用DMA+IDLE中断方式串口接收不定长数据

    之前讲过用 利用IDLE空闲中断来接收不定长数据 ,但是没有用到DMA,其实用DMA会更加的高效,MCU也可以腾出更多的性能去处理应该做的事情。 IDLE顾名思义,就是空闲的意思,即当监测到串口空闲超过1个串口的数据帧时,会使状态寄存器(SR或ISR)的IDLE位置位,如果此时控制

    2024年04月17日
    浏览(63)
  • [嵌入式软件][启蒙篇][仿真平台] STM32F103实现串口输出输入、ADC采集

    上一篇:[嵌入式软件][启蒙篇][仿真平台] STM32F103实现LED、按键 学C语言时,使用的printf()函数,就是通过串口打印出来的。 跟外部器件通信,比如GPS模块、蓝牙模块、wifi模块; 两个开发板之间通信,制定私有协议。 PC电脑通信,使用上位机显示数据或控制下位机。 操作:打

    2024年01月22日
    浏览(70)
  • STM32串口通信详解(嵌入式学习)

    时钟信号在电子领域中是指用于同步和定时电路操作的周期性信号。它在数字系统和通信系统中起着至关重要的作用,用于协调各个组件之间的数据传输和操作。 时钟信号有以下几个重要的方面: 频率:时钟信号的频率是指单位时间内信号周期的数量。它通常以赫兹(Hz)为

    2024年02月09日
    浏览(69)
  • 【嵌入式】openmv与stm32的串口通信

    参考:(文中部分图/文字/代码来自以下文章,部分内容由于时间久远已经找不到原作者,可联系注明或删除) PYTHON串口数据打包发送STM32接收数据解析 openmv中文文档 这里以openmv循迹代码为例 main.py 关于struct.pack: 函数原型:struct.pack(fmt, v1, v2, …) fmt是格式字符串 v1,v2是要转

    2024年02月14日
    浏览(43)
  • 嵌入式硬件——stm32F103C8T6

    如下图:    处理器核心:STM32F103C8T6内置了ARM Cortex-M3处理器,这是一种高性能、低功耗的32位RISC处理器,适用于嵌入式系统。 时钟速度:它可以工作在不同的时钟速度,最高主频可达72 MHz。 存储器:包括64KB的Flash程序存储器用于存储程序代码,以及20KB的SRAM用于存储数据和

    2024年02月05日
    浏览(65)
  • 【嵌入式学习-STM32F103-USART串口通信】

    4-1 基本流程 4-2 整体代码 4-2-1 main.c 4-2-2 Serial.c 4-2-3 Serial.h 5-1 查询 5-2 中断 5-3 整体代码 5-3-1 main.c 5-3-2 Serial.c 5-3-3 Serial.h 6-1 使用状态机接收数据包的思路 6-2 串口收发HEX数据包 6-2-1 main.c 6-2-2 Serial.c 6-2-3 Serial.h 6-3串口收发文本数据包 6-3-1 main.c 6-3-2 Serial.c 6-3-3 Serial.h 全双工:打

    2024年02月15日
    浏览(61)
  • 嵌入式_GD32使用宏开关进行Debug串口打印调试

    串口Debug是一种将数据通过串口发送的方法。通过使用printf函数,我们可以将需要发送的数据格式化为字符串,并通过串口发送出去。在C语言中,通常使用串口发送数据的函数为printf函数,但是需要将标准输出重定向到串口。 本文详细的介绍了如何重定向printf输出到串口输出

    2024年02月14日
    浏览(49)
  • 嵌入式学习笔记——STM32的USART收发字符串及串口中断

    上一篇中,介绍了串口收发相关的寄存器,通过代码实现了一个字节的收发,本文接着上面的内容,通过功能函数实现字符串的收发,然后引入中断解决收发过程中while()死等的问题。 根据昨天的字符发送函数,只需要稍作修改即可实现发送函数了,一个字符串的结尾会有一

    2024年02月03日
    浏览(79)
  • 【嵌入式知识08】STM32的USART串口通信,给上位机连续发送Hello Windows!

    本文主要介绍串口协议和RS-232、485标准,以及RS232、485电平与TTL电平的区别,了解\\\"USB/TTL转232\\\"模块的工作原理;并完成一个STM32的USART串口通讯程序。   串口通信(Serial Communication)的概念非常简单,串口按位(bit)发送和接收字节的通信方式。尽管比按字节(byte)的并行通信

    2024年02月13日
    浏览(50)
  • 【嵌入式】STM32F031K4U6、STM32F031K6U6、STM32F031K6T6主流ARM Cortex-M0基本型系列MCU规格参数

    一、电路原理图 【嵌入式】STM32F031K4U6、STM32F031K6U6、STM32F031K6T6主流ARM Cortex-M0基本型系列MCU —— 明佳达 二、规格参数 1、 STM32F031K4U6 (16KB)闪存 32UFQFPN 核心处理器:ARM® Cortex®-M0 内核规格:32 位单核 速度:48MHz 连接能力:I²C,IrDA,LINbus,SPI,UART/USART 外设:DMA,I²S,POR,

    2024年02月04日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包