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

news2024/11/19 3:16:56

一 项目背景

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

二 原理说明

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

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

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

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

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

         这边用到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,与逻辑分析仪中的一致:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/352925.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

8月起,《PMBOK®指南(第七版)》将被采用,考PMP的注意了!

PMP第七版教材采用时间定了!!!2023年【8月开始】第一次使用第七版教材,通知明显指出,第六版的关键知识任然还是有效的。第七版做的调整还是蛮大的,首次提出了项目管理的 12 项原则和8个项目绩效域&#xff…

Java基础语法小结来啦

简单的来说,一个java的程序他是有一系列对象的集合组成,通过对这些对象相互间调用的方式协同工作,下面就是我有关于Java基础语法的一些小结。 一、return简单使用 下面来一个Java程序,表示的是在self1这个包中我们创建了一个名叫…

Skywalking ui页面功能介绍

菜单栏 仪表盘:查看被监控服务的运行状态; 拓扑图:以拓扑图的方式展现服务之间的关系,并以此为入口查看相关信息; 追踪:以接口列表的方式展现,追踪接口内部调用过程; 性能剖析&am…

GEE学习笔记 八十:批量下载影像

最近问如何批量导出集合的小伙伴非常多,一个一个回复太麻烦,我这里直接给一段例子代码吧: var l8 ee.ImageCollection("LANDSAT/LC08/C01/T1_SR"); var roi /* color: #d63000 */ee.Geometry.Polygon( [[[115.64960937…

从0到1一步一步玩转openEuler--17 openEuler DNF(YUM)检查更新

文章目录17.1 检查更新17.2 升级17.3 更新所有的包和它们的依赖DNF是一款Linux软件包管理工具,用于管理RPM软件包。DNF可以查询软件包信息,从指定软件库获取软件包,自动处理依赖关系以安装或卸载软件包,以及更新系统到最新可用版本…

Nacos框架服务注册发现和配置中心原理

文章目录1.简介2.整体架构和原理2.1 服务发现注册原理2.1.1 注册和拉取数据2.1.2 Server集群一致性2.1.3 健康检查2.2 配置中心原理2.2.1 支持功能和资源模型2.2.2 server集群数据一致性问题2.2.3 client和server的通信监听改动方式2.2.4 client拉取数据2.2.5 client请求server…

kubernetes教程 --Pod生命周期

Pod生命周期 pod创建过程运行初始化容器(init container)过程运行主容器(main container)过程 容器启动后钩子(post start)、容器终止前钩子(pre stop)容器的存活性探测(…

利用设计模式、反射写代码

软件工程师和码农最大的区别就是平时写代码时习惯问题,码农很喜欢写重复代码而软件工程师会利用各种技巧去干掉重复的冗余代码。 业务同学抱怨业务开发没有技术含量,用不到设计模式、Java 高级特性、OOP,平时写代码都在堆 CRUD,个…

网站项目部署在k8s案例与Jenkins自动化发布项目(CI/CD)

在K8s平台部署项目流程 在K8s平台部署Java网站项目 制作镜像流程 第一步:制作镜像 使用镜像仓库(私有仓库、公共仓库): 1、配置可信任(如果仓库是HTTPS访问不用配置) # vi /etc/docker/daemon.json { "…

matlab 简单的水轮机系统的模糊pid控制仿真

1、内容简介略641-可以交流、咨询、答疑2、内容说明模糊介绍:Matlab4.2以后的版本中推出的模糊工具箱(Fuzzy Toolbox),为仿真模糊控制系统提供了很大的方便。 在Simulink环境下对PID控制系统进行建模是非常方便的,而模糊控制系统与PID控制系统…

DataFrame 循环处理效率的记录

几种工具的处理效率比较: 每次循环都使用复杂的操作尽可能拆分成向量化操作,也可转为numpy,再用numba加速。 对 DataFrame 中的数据做循环处理的效率: 方法一:下标循环 for i in range(len(df)): if df.iloc[i][…

GEE学习笔记 七十七:GEE学习方法简介

这是一篇关于学习方法的思考探索,当然我不会大篇文章介绍什么学习方法(因为我也不是这方面的专家?),这个只是总结一下我是如何学习GEE以及在学习中遇到问题时如何解决问题的。我写这篇文章的目的就是在和一些学习GEE的新同学接触…

Stable diffusion扩散模型相关原理

时隔两年半(2年4个月),我又回来研究生成技术了。以前学习研究GAN没结果,不管是技术上,还是应用产品上,结果就放弃了,现在基于diffusion的技术又把生成技术带上了一个新的高度。现在自己又来研究…

一款好的低代码开发平台应该是什么样?

一款好的低代码开发平台应该是什么样? 以企业级应用构建来讲,完成一个应用复杂度随着技术的进步、需求的细化、业务要求的变化并不是逐渐降低,而是逐渐提升。用户想要有更好的体验,复杂度更是成倍提升。 基于此,低代码…

【机器学习】Sklearn 集成学习-投票分类器(VoteClassifier)

前言 在【机器学习】集成学习基础概念介绍中有提到过,集成学习的结合策略包括: 平均法、投票法和学习法。sklearn.ensemble库中的包含投票分类器(Voting Classifier) 和投票回归器(Voting Regressor),分别对回归任务和分类任务的…

比Teambition、Worktile 更适合研发团队的几大工具盘点

Worktile 和 Teambitiom 哪个更好?两个产品各有特点。1.Teambition 优势:操作简单、个人版永不收费、更适合小型团队;2.Teambition 劣势:无法满足中大型团队复杂的项目管理、自定义能力弱、无法与钉钉以外的工具打通等&#xff1b…

再次遇到RuntimeError: CUDA error: an illegal memory access was encountered

之前遇到过一次记录下来了,第一次遇到 翻看之前的记录,首先想着如何让pycharm准确地显示错误。 1:os.environ[‘CUDA_LAUNCH_BLOCKING’] 1’,模型前加这句,但是我在train文件中已经加了,还是不清楚报错原因。 2:使用…

Python快速上手系列--三元表达式--入门篇

不知道你是否在写代码的时候会用到很多的if else的判断呢。如果是,不妨看完这一篇文章,看看是否对你有一定的帮助。先来看看三元表达式的写法:那么怎么写呢,看看:这里的意思是,如果为真,则输入1…

RTX40 系列游戏本与台式机显卡 AI 计算力对比

RTX40 系列游戏本还有几天就上市了,商家选了个比较特别的日子,2 月 22 号 22:00,真是有心了。为了用游戏本做 AI 的朋友选的时候有的放矢,特意查了一下 RTX40 系列的 CUDA 核心与频率,计算一下 FP32 TFLOPS&#xff0c…