Hi3061M——不定长串口接收实现

news2024/10/25 13:15:37

这里写目录标题

    • 前言
    • 串口接收流程
    • 串口中断函数
      • ReadITCallBack1中断接收函数
    • 补充
    • 结果展示

前言

Hi3061M给了很多相关的串口案例,但大多数是定长的,指定长度进行接收读取,而实际需求往往需要用到不定长的接收。

串口接收流程

首先介绍下Hi3061M串口接收流程,本篇主要介绍中断实现方式。
中断的方式接收数据在案例中的实现步骤首先是对串口进行初始化配置,然后注册中断函数****HAL_UART_IrqHandler,里面就是中断接收的主要实现代码。

    HAL_UART_Init(&g_uart);
    HAL_UART_RegisterCallBack(&g_uart, UART_TRNS_IT_ERROR, (UART_CallbackType)UART0InterruptErrorCallback);
    HAL_UART_RegisterCallBack(&g_uart, UART_WRITE_IT_FINISH, (UART_CallbackType)WriteCallBack);
    HAL_UART_RegisterCallBack(&g_uart, UART_READ_IT_FINISH, (UART_CallbackType)ReadCallBack);
    IRQ_Register(IRQ_UART0, HAL_UART_IrqHandler, &g_uart);
    IRQ_SetPriority(IRQ_UART0, 1); /* 1 is priority value */
    IRQ_EnableN(IRQ_UART0);

其次在主函数中调用HAL_UART_ReadIT(&g_uart, g_str, rxDataLength);将对应的存储接收数据的数组地址和接收数据固定长度传入,最后赋值给g_uart串口句柄对应的结构体变量。

        uartHandle->rxbuff = saveData;
        uartHandle->rxBuffSize = dataLength;
        if (uartHandle->fifoMode == true) {
            uartHandle->baseAddress->UART_IMSC.reg |= 0x7D0;  /* Enable rx interrupt and rx timeout interrupt */
        } else {
            uartHandle->baseAddress->UART_IMSC.reg |= 0x20780;  /* Enable rx not empty interrupt */
        }

然后根据是否开启了FIFO模式(一般开启,本篇主要介绍开启该模式下的串口),设置开启对应的中断,即接收中断和接收超时中断。

开启中断后,如果接收到数据就会触发中断,从而进入中断函数,然后进行对应的数据接收处理。

串口中断函数

在这里插入图片描述
中断函数的内容主要就是根据中断屏蔽后状态寄存器的各个参数来触发,至于为什么是屏蔽后状态寄存器,而不少中断原状态寄存器来判断,我暂时没明白,但是试过了使用原中断状态寄存器的值判断同样也可以。

在介绍ReadITCallBack1中断接收函数函数之前,先介绍初始化中一个重要的参数设置。

    g_uart.fifoTxThr = UART_FIFODEPTH_SIZE8;
    g_uart.fifoRxThr = UART_FIFODEPTH_SIZE8;

UART 支持16 x 8bit的发送FIFO和16 x 12bit的接收FIFO,接收FIFO阈值和发送FIFO 阈值,决定触发UART中断触发点。例如:接收FIFO≥UART_FIFODEPTH_SIZE4 ,当接 收FIFO存放数据增长到大于等于4个字节数据时,触发接收中断;发送 FIFO≤UART_FIFODEPTH_SIZE4 ,当发生FIFO存放数据减少到小于等于4个数据时, 触发发送中断。UART的接收FIFO阈值点和发送FIFO阈值点一共可以配置多种,具体的阈值枚举定uart/uart_v1/inc/uart_ip.h。

这个参数的意义在于一次性可读取的FIFO大小,字节数,设置为UART_FIFODEPTH_SIZE8,即增长到8时会触发中断,然后对FIFO进行读取,也就是说中断函数中本次接收最多为8字节。注意这非常重要,这也意味着中断接收数据存在两种情况
第一种情况,数据少于设定阈值,小于UART_FIFODEPTH_SIZE8,比如我设置为8字节,发送给MCU6字节数据,那么串口进入中断接收一次即可完成数据接收和存储。
第二种情况,数据多于设定阈值,比如设置阈值为8字节,发送给MCU15字节数据,而FIFO在8字节时,会触发接收中断,意味着会先将前8字节数据在中断函数中进行接收处理,后续剩余字节则下次再次进入中断函数中进行处理。也就是会进入两次中断,但是如果设置为UART_FIFODEPTH_SIZE15,最大为15,如果是这样的话,就进入一次中断函数就可以将接收完毕。
注意以上是本人调试的经验总结而出,与具体设计可能存在偏颇,如有问题,望指正,但是从测试结果来看确实是如此。

ReadITCallBack1中断接收函数

接下来详细介绍ReadITCallBack1中断接收函数。这是改写的不定长的数据接收函数,以回车换行符号作为结束标记,可以参考实现。

unsigned char USART_RX_STA=0;       //接收状态标记	0表示开始接收,1表示接收到0d, 2表示接收完毕
static void ReadITCallBack1(UART_Handle *uartHandle)
{
    UART_ASSERT_PARAM(uartHandle != NULL);
    UART_ASSERT_PARAM(uartHandle->rxbuff != NULL);
    UART_ASSERT_PARAM(IsUARTInstance(uartHandle->baseAddress));
    if (uartHandle->rxState == UART_STATE_BUSY_RX) {
        unsigned int tmp;
        while (USART_RX_STA!=2) {
            if (uartHandle->baseAddress->UART_FR.BIT.rxfe == 0x01) {    /* True when the RX FIFO is empty */
                break;
            }
            tmp = uartHandle->baseAddress->UART_DR.reg;
            if(USART_RX_STA==1)//接收到了0x0d
            {
                if((tmp & 0xFF)!=0x0a)USART_RX_STA =0;//接收错误,重新开始
                else USART_RX_STA = 2;	//接收完成了 
            }
            else //还没收到0X0D
            {	
                if((tmp & 0xFF)==0x0d)USART_RX_STA = 1;
                else
                {
                    *(uartHandle->rxbuff)=(tmp & 0xFF);
                    uartHandle->rxbuff++;	 
                }
            }
        }
        if (USART_RX_STA!=2) {
            uartHandle->baseAddress->UART_IMSC.reg &= 0xFFFDFFAF;   /* Disable rxim ,rtim and rxfneim */
            uartHandle->rxState = UART_STATE_READY;
        }
        uartHandle->baseAddress->UART_ICR.reg |= 0x20050;      /* Clear rxic, rtic and rxfneic */
        if (uartHandle->userCallBack.ReadItFinishCallBack != NULL && USART_RX_STA!=2) {
            USART_RX_STA = 0;
            uartHandle->userCallBack.ReadItFinishCallBack(uartHandle);
        }
    }
    return;
}

通过设置一个接受标志USART_RX_STA,0表示接受,1表示接收到了0x0d,就是对应的回车符号,2表示接收完0x0d后成功接收到0x0a,也就是完整的接收到了结束标志,表示接收完成。整个函数的逻辑就是先循环判断是否接收完成,如果没有就进行数据接收,接收到0x0d,就修改标志,并马上确定接下来的数据是否为0x0d,如果为0x0d则表示接收完成,如果不是则接收错误结束标识,需重新接收。
后面就是接收完成后,对中断寄存器进行清零,并屏蔽接收中断(因为main函数中接收中断是通过函数主动开启主动接收的,其实这也可以优化,无需main主动触发,设置为中断接收,有数据来就触发中断并接收数据,这需要另外设置接收数组。),最后还要清除接收标志,此外调用配置的回调函数。
另外接收的长度会受限制与数组大小,这个由用户自定义设置,可以对接收长度进行计数并判断是否超出长度,可以充分利用USART_RX_STA的8个位进行计数,3位做标志,5位做计数,也可以扩展,优化空间还有很多。
另外注意的一个点就是

            if (uartHandle->baseAddress->UART_FR.BIT.rxfe == 0x01) {    
            /* True when the RX FIFO is empty */
                break;
            }

这一句非常重要,这是判断fifo是否为空, 如果为空,两个情况(上面提到的),一个是串口接收完成,也就是USART_RX_STA=2了,完成一次接收了;另一个情况是,串口未读取到结束标志,但是单次接收完成了,也就是设置为UART_FIFODEPTH_SIZE8后,接收完8字节了但是未读取到结束标志,说明后面还有数据。
此时中断会跳出接收循环,但是不会运行屏蔽中断使能(因为USART_RX_STA!=2),同样不会运行标志清零和回调函数,因为此次接收还不算完成,但是会清除中断标志,等待下次中断触发,接下来的数据进入fifo同样会触发中断,从而继续进行数据接收,直到接收到结束标志为止。

这样就实现了以回车换作为结束标志的中断不定长接收实现,注意我个人感觉只有这种设置结束标志的方式是唯一的不定长接收方式,另外的方法应该是不太行,判空是不太行的因为涉及到了前面说的第二种情况,需要多次进入中断接收的情况,除非笃定一次的数据会少于设定的阈值UART_FIFODEPTH_SIZEX。

补充

至于main函数中,几乎不需要进行修改,这是中断接收后写的例程中的main函数

static volatile unsigned char g_str[20] = {0};
void UART_InterruptTxAfterRx(void)
{
    SystemInit();
    DBG_PRINTF("UART Init finish, please enter characters:\r\n");
    unsigned int rxDataLength = 0;  /* The receive length is 10 */
    g_flag = false;
    HAL_UART_ReadIT(&g_uart, g_str, rxDataLength);
    while (1) {
        if (g_flag == true) {
            g_flag = false;
            unsigned int txDataLength = CountString(g_str); /* string length of the data to be sent after receiving */
            HAL_UART_WriteIT(&g_uart, g_str, txDataLength);
            BASE_FUNC_DELAY_MS(5);
            ClearString(g_str);
            HAL_UART_ReadIT(&g_uart, g_str, rxDataLength);
        }
    }
    return;
}

其中rxDataLength 可以不用管,设置为0即可,因为我们没有用到这个参数,不需要根据这个进行判断。
此外这个例程中需要加入延迟BASE_FUNC_DELAY_MS(5);具体延迟时间需要大于2MS应该就可以,不然写数据会出现问题,因为ClearString(g_str);函数可能会清除掉接收的数据导致发送异常。g_str接收数组的大小可以自行设置。

结果展示

uart

有什么问题欢迎一起沟通交流~
觉得还不错就点个小赞吧,谢谢

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

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

相关文章

Android终端GB28181音视频实时回传设计探讨

技术背景 好多开发者,在调研Android平台GB28181实时回传的时候,对这块整体的流程,没有个整体的了解,本文以大牛直播SDK的SmartGBD设计开发为例,聊下如何在Android终端实现GB28181音视频数据实时回传。 技术实现 Andr…

C++——红黑树(带头结点)

红黑树 红黑树的概念红黑树的定义红黑树的性质红黑树的优点操作原理例图: 红黑树的实现红黑树的框架红黑树的插入实现头结点的作用红黑树的插入步骤(简易理解版带图) 红黑树的插入具体代码详解红黑树的旋转代码红黑树的查验 红黑树的概念 红…

基于矢量瓦片技术的GIS引擎

矢量地图是通过对点线面坐标信息集合的管理和渲染实现优于栅格画面质量的一种gis展示技术,涉及不同坐标系变换,视窗比例尺换算等。当你遇到海量坐标数据和属性信息需要管理时你就不得不在有限内存和庞大数据间左右为难,将地图矢量数据进行分块…

LabVIEW提高开发效率技巧----时序分析

一、什么是时序分析? 时序分析是优化LabVIEW程序性能的重要步骤。它通过分析程序各个部分的执行时间,帮助开发者找到程序运行中的瓶颈,并进行有针对性的优化。在LabVIEW中,Profile Performance and Memory工具是进行时序分析的关…

浏览器中使用模型

LLM 参数越来越小,使模型跑在端侧成为可能,为什么要模型跑在端侧呢,首先可以节省服务器的算力,现在 GPU 的租用价格还是比较的高的,例如租用一个 A10 的卡1 年都要 3 万多。如果将一部分算力转移到端侧通过小模型进行计…

Linux中真实的调度算法,进程地址空间,命令行参数

文章目录 Linux中真正的调度算法补充 命令行参数什么是命令行参数?命令行参数的用途如何在不同的编程语言中使用命令行参数命令行参数好处 Linux中真正的调度算法 这是Linux2.6的内核中进程队列的数据结构 其中有这两个指针*active,*expired,而Linux为…

论文及其创新点学习cvpr2022 On the Integration of Self-Attention and Convolution

代码地址 https://github.com/LeapLabTHU/ACmix https://gitee.com/mindspore/models 论文创新点,将注意力机制 和卷积 相结合 # encoding: utf-8author: duhanyue start time: 2024/10/13 10:04 import torch import torch.nn as nn def position(H, W, is_cudaT…

邮票鉴赏系统| 邮票鉴赏系统平台|基于java和vue的邮票鉴赏系统设计与实现(源码+数据库+文档)

邮票鉴赏系统\ 目录 基于java和vue的邮票鉴赏系统设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂码农|毕设布道师,阿里…

用 Gemini Google 生成图片的魔法

用 Gemini Google 生成图片的魔法指南 你是否曾经想过,用一些简单的文本描述来生成一张图片?这听起来像是科幻小说中的魔法,但实际上,这就是 Gemini Google 的魔力!在这篇文章中,我将向你详细介绍如何使用…

【HarmonyOS NEXT】实现页面水印功能

关键词:鸿蒙、水印、Watermark、页面、触摸问题 注:本期文章同样适用 OpenHarmony 的开发 在app开发过程中时常会出现敏感信息页面,为保护信息安全和及时的数据追踪,通常会采用给页面加水印的形式,那么本期文章会介绍…

自回归视觉生成里程碑!比ControlNet 和 T2I-Adapter 快五倍!北大腾讯提出CAR:灵活、高效且即插即用的可控框架

文章链接:https://arxiv.org/pdf/2410.04671 项目链接:https://github.com/MiracleDance/CAR 亮点直击 CAR是首个为自回归模型家族设计的灵活、高效且即插即用的可控框架。CAR基于预训练的自回归模型,不仅保留了原有的生成能力,还…

sherpa-ncnn 语言模型简单对比

在昨天把系统搞崩溃前,对sherpa-ncnn的中文模型做了一个简单的对比。这次使用的分别是sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13(以下简称bilingual-zh-en-2023-02-13)和sherpa-ncnn-streaming-zipformer-small-bilingual…

服务器数据恢复—EMC存储RAID5磁盘阵列数据恢复案例

服务器数据恢复环境: 一台EMC某型号存储设备,该存储中有一组由12块(包括2块热备盘)STAT硬盘组建的raid5阵列。 服务器故障: 该存储在运行过程中突然崩溃,raid瘫痪。数据恢复工程师到达现场对故障存储设备进…

GPT联网分析到底有多强?实测效果告诉你答案!

文章目录 零、前言一、gpt-4o操作指导gpt4o 二、感受 零、前言 早上在聊到博主在选择平台时,要选择哪个平台发展。 通过GPT查询并分析了小红书,微信视频号,抖音和B站的用户群体。 由此可举一反三,如何让GPT联网分析,…

部署私有仓库以及docker web ui应用

官方地址:https://hub.docker.com/_/registry/tags 一、拉取registry私有仓库镜像 docker pull registry:latest 二、运⾏容器 docker run -itd -v /home/dockerdata/registry:/var/lib/registry --name "pri_registry1" --restartalways -p 5000:5000 …

如何针对项目中的技术难点准备面试?——黑马点评为例

最核心的,包装和准备 个人项目,怎么包装?一定要写出代码才可以吗? 你可以在系统A中实现就可以,了解其中实现的细节,怎么跟面试官对线等等,这些话术到位了之后,再把它融入到系统B&a…

《CUDA编程》7.全局内存的合理使用

上一章简单的介绍了一下各种内存,本章开始详细讲解各个内存的合理使用,在所有设备中,全局内存的访问速度最慢,是CUDA程序的一个性能瓶颈,所以值得特别关注 1 全局内存的合并与非合并访问 对全局内存的访问将触发内存事…

LabVIEW如何实现高精度定时器

在LabVIEW中实现高精度定时器通常需要考虑以下几个方面:定时器的精度要求、操作系统的调度机制、硬件资源(如计时器、触发器)等。以下是几种常见的实现方式: ​ 1. 使用 Wait(ms) 或 Wait Until Next ms Multiple VI 这两个函数…

【无人机设计与控制】PID_积分滑模_积分反步四旋翼无人机轨迹跟踪控制算法

摘要 本文基于四旋翼无人机设计与控制,提出了一种结合PID控制、积分滑模控制以及积分反步控制的轨迹跟踪算法。该算法通过调节无人机的运动轨迹,提升其在复杂环境下的稳定性与抗扰动能力。实验结果表明,该算法能有效改善无人机的轨迹跟踪精度…

【python实操】python小程序之计算对象个数、游戏更新分数

引言 python小程序之计算对象个数、游戏更新分数 文章目录 引言一、计算对象个数1.1 题目1.2 代码1.3 代码解释1.3.1 代码结构1.3.2 模块解释1.3.3 解释输出 二、游戏更新分数2.1 题目2.2 代码2.3 代码解释2.3.1 定义 Game 类2.3.2 创建 Game 实例并调用方法 三、思考3.1 计算对…