rt-thread------串口V1(三)接收

news2024/12/25 9:05:04

系列文章目录

rt-thread 之 fal移植
rt-thread 之 生成工程模板
STM32------串口理论篇
rt-thread------串口V1版本(一)配置
rt-thread------串口V1版本(二)发送篇

文章目录

  • 系列文章目录
  • 一、串口的接收
    • 中断接收
    • DMA接收

一、串口的接收

串口接收常见的分中断接收和DMA接收,虽然RTT框架提供了轮训接收,但是其使用场景不多也不展开讨论。在RTT串口配置中设置了一个ringbuf,其作用是用来接收数据的。无论使用中断接收还是DMA接收数据都会存入ringbuf中。
在这里插入图片描述
配置时还注册了一个接收回调函数其作用是在接收完成(或者DMA半满、满中断时)时通知应用层代码作相应处理。下面详细讲讲这两种接收方式的代码实现。

中断接收

在RTT论坛刷到一个大佬的帖子给出了中断接收的流程。
在这里插入图片描述
中断接收无论如何都会触发单片机中断向量表中的中断函数,只需要去查找中断函数中具体实现即可。以usart2为例:

void USART2_IRQHandler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();
    uart_isr(&(uart_obj[UART2_INDEX].serial));
    /* leave interrupt */
    rt_interrupt_leave();
}

其中uart_isr函数展开后与中断接收相关的如下:

static void uart_isr(struct rt_serial_device *serial)
{
	...
	/* UART in mode Receiver -------------------------------------------------*/
	if ((__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) != RESET) &&
	        (__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_RXNE) != RESET))
	{
	    rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_IND);
	}
...
    else
    {
        if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_ORE) != RESET)
        {
            __HAL_UART_CLEAR_OREFLAG(&uart->handle);
        }
        if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_NE) != RESET)
        {
            __HAL_UART_CLEAR_NEFLAG(&uart->handle);
        }
        if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_FE) != RESET)
        {
            __HAL_UART_CLEAR_FEFLAG(&uart->handle);
        }
        if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_PE) != RESET)
        {
            __HAL_UART_CLEAR_PEFLAG(&uart->handle);
        }
  }

如果是接收中断则进入rt_hw_serial_isr函数否则则去清除一些错误标志位。显然重点在if成立的条件语句中。其源码如下所示,进入函数是个状态机,显然是想让这部分代码复用。只需要查看RT_SERIAL_EVENT_RX_IND相关代码。

void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
{
    switch (event & 0xff)
    {
        case RT_SERIAL_EVENT_RX_IND:
        {
            int ch = -1;
            rt_base_t level;
            struct rt_serial_rx_fifo* rx_fifo;

            /* interrupt mode receive */
            rx_fifo = (struct rt_serial_rx_fifo*)serial->serial_rx;
            RT_ASSERT(rx_fifo != RT_NULL);
			/* 读取串口数据部分 */
           /* 回调函数部分 */
  }

上述代码可以分成两部分读取数据和回调函数调用部分。

 while (1)
            {
                ch = serial->ops->getc(serial);
                if (ch == -1) break;
                /* disable interrupt */
                level = rt_hw_interrupt_disable();

                rx_fifo->buffer[rx_fifo->put_index] = ch;
                rx_fifo->put_index += 1;
                if (rx_fifo->put_index >= serial->config.bufsz) rx_fifo->put_index = 0;

                /* if the next position is read index, discard this 'read char' */
                if (rx_fifo->put_index == rx_fifo->get_index)
                {
                    rx_fifo->get_index += 1;
                    rx_fifo->is_full = RT_TRUE;
                    if (rx_fifo->get_index >= serial->config.bufsz) rx_fifo->get_index = 0;

                    _serial_check_buffer_size();
                }
                /* enable interrupt */
                rt_hw_interrupt_enable(level);
            }

读取数据部分是负责把串口接收数据读取到ringbuf中,使用serial->ops->getc(serial)函数,看这个名字就能猜到是获取串口接收的一个字节数据。对于STM32来说就是将串口的DR寄存器通过这个函数返回。看一下具体代码的实现,果然如此:

static int stm32_getc(struct rt_serial_device *serial)
{
    int ch;
    struct stm32_uart *uart;
    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct stm32_uart, serial);
    ch = -1;
    if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) != RESET)
    {
    ...
        ch = uart->handle.Instance->DR & stm32_uart_get_mask(uart->handle.Init.WordLength, uart->handle.Init.Parity);
	...
    }
    return ch;
}

将DR寄存器中的值存入ringbuf,还对ringbuf满的情况做了一下异常处理,ringbuf is_full标志位会置一,数据会覆盖最早的数据导致数据丢失,所以需要保证应用层及时从ringbuf中取出数据。

再看看回调函数部分,若回调函数不为空,且这次就收到数据则调用一次接收回调函数。通常回调函数中释放一个信号量通知应用层程序从ringbuf中读取数据。其实这也看出了中断方式的一个缺陷,每接收一字节放一次ringbuf,调用一次回调函数,应用层也只能一字节一字节接收。为了减CPU的使用率还提供了DMA的方式。

			/* 调用回调函数部分 */
            /* invoke callback */
            if (serial->parent.rx_indicate != RT_NULL)
            {
                rt_size_t rx_length;

                /* get rx length */
                level = rt_hw_interrupt_disable();
                rx_length = (rx_fifo->put_index >= rx_fifo->get_index)? (rx_fifo->put_index - rx_fifo->get_index):
                    (serial->config.bufsz - (rx_fifo->get_index - rx_fifo->put_index));
                rt_hw_interrupt_enable(level);
                if (rx_length)
                {
                    serial->parent.rx_indicate(&serial->parent, rx_length);
                }
            }
            break;
        }

DMA接收

依然是大佬的流程:
在这里插入图片描述
这里触发串口中断的判断变了,变成了DMA半满、满中断和空闲中断触发串口中断,三者是的关系。后面会讲一下这个带来一些与预期不一致的结果。
接收中断中如果开启DMA则会执行以下代码

  else if ((uart->uart_dma_flag) && (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_IDLE) != RESET)
             && (__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_IDLE) != RESET))
    {
        level = rt_hw_interrupt_disable();
        recv_total_index = serial->config.bufsz - __HAL_DMA_GET_COUNTER(&(uart->dma_rx.handle));
        recv_len = recv_total_index - uart->dma_rx.last_index;
        uart->dma_rx.last_index = recv_total_index;
        rt_hw_interrupt_enable(level);

        if (recv_len)
        {
            rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_DMADONE | (recv_len << 8));
        }
        __HAL_UART_CLEAR_IDLEFLAG(&uart->handle);
    }
    else if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) &&
            (__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_TC) != RESET))
    {
        if ((serial->parent.open_flag & RT_DEVICE_FLAG_DMA_TX) != 0)
        {
            HAL_UART_IRQHandler(&(uart->handle));
        }
        UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TC);
    }

DMA在处理的过程中也是调用rt_hw_serial_isr与中断一致,接收长度需要根据DMA接收长度增加个小计算得出。后续处理与中断接收也基本一致,将数据更新进ringbuf再调用回调函数。最大的区别是DMA一次会接收一帧或者半帧数据才进一次中断。回调函数处理也会有所不一样,官方例程也不在释放信号量了,通过消息队列传出串口接收数据大小,交给应用层处理。
下面好好讲一下驱动框架会正对多数项目设计或者按照较为全面的方式去设计,这难免会和自己项目存在一些冲突。若使用RTT提供的V1版本DMA接收配合modbus协议则会出现一些问题,其问题是modbus会把帧长度不对的数据丢掉,而DMA半满和满中断也会触发回调函数发送串口接收的字节数,这就导致会有一帧数据被截成两段触发两次串口中断,最后数据被应用层丢弃。那就回归modbus协议断帧的本质是3.5个字符没接收到数据,所以改成DMA空闲中断触发串口中断即可,修改如下所示:
在这里插入图片描述
那么为什么RTT一开始DMA接收版本代码需要这两个回调函数也触发串口中断呢?存在即有意义,如果有些串口协议需要通过半满中断读取这一帧的长度呢?在满中断和空闲中断增加一个和帧的操作也能得到一个完整的帧。
个人观点是RTT可以将这些做成用户可选择的模式,用户可以根据自己实际项目选择是三个中断或的形式触发还是一个空闲中断触发。
参考:
RTT串口V1版本的使用分析及问题排查指南(一)

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

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

相关文章

从一次netty分享漫谈

从一次netty分享漫谈 1.前言 上周五&#xff0c;笔者所在的开发小组&#xff0c;组织了一场分享&#xff0c;内容是netty的入门。笔者所在的团队&#xff0c;基本上就是在各条业务线中活蹦乱跳&#xff0c;有经验的看官&#xff0c;到这里已经可以给出分享效果的总体预测&…

Gradle 各个版本下载

每次都要找下载地址&#xff0c;还是记录一下好找点。 http://services.gradle.org/distributions

Unreal 5 官方在Niagara里模拟大型群体笔记

官方视频地址&#xff1a;https://www.bilibili.com/video/BV1FX4y1T7z2/ 如果需要&#xff0c;请查看官方视频。 性能测试 在讲解Niagara之前&#xff0c;视频首先做了一个性能测试&#xff0c;首先放置了100个AI角色&#xff0c;可以想目标角色移动的ai&#xff0c;然后测试…

C语言:猜凶手

题目&#xff1a; 日本某地发生了一件谋杀案&#xff0c;警察通过排查确定杀人凶手必为4个嫌疑犯的一个。 以下为4个嫌疑犯的供词: A说&#xff1a;不是我。 B说&#xff1a;是C。 C说&#xff1a;是D。 D说&#xff1a;C在胡说 已知3个人说了真话&#xff0c;1个人说的是假话。…

山西电力市场日前价格预测【2023-07-03】

日前价格预测 预测明日&#xff08;2023-07-03&#xff09;山西电力市场全天平均日前电价为333.50元/MWh。其中&#xff0c;最高日前电价为398.66元/MWh&#xff0c;预计出现在15: 15。最低日前电价为280.73元/MWh&#xff0c;预计出现在24: 00。 以上预测仅供学习参考&#x…

Spring第一讲:Spring基础概念和环境搭建

一、Spring是什么 Spring 是 Java EE 编程领域的一款轻量级的开源框架&#xff0c;由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立&#xff0c;它的目标就是要简化 Java 企业级应用程序的开发难度和周期。 Spring 自诞生以来备受青睐&#xff0c;一直被广大开发…

二叉树各种函数的实现

如果你觉得迷茫&#xff0c;那就尽可能选择比较困难的路。 目录 前言&#xff1a; &#x1f340;一.通过前序遍历创建二叉树 &#x1f341;二.二叉树的四种遍历 &#x1f342;1.二叉树的前序遍历 &#x1f33c;2.二叉树的中序遍历 &#x1f34c;3.二叉树的后序遍历 …

Mac VSCode配置运行单个C++文件

题外话&#xff1a;VSCode一键整理代码快捷键&#xff1a;ShiftoptionF 方法一&#xff1a;命令行直接编译 g -o 想创建的可执行文件名 ./cpp文件名 ./可执行文件名 以test.cpp为例&#xff0c;我创建的可执行文件名为test&#xff0c;运行结果如下&#xff1a; 方法二&#…

SpringCloud-Nacos配置管理

文章目录 Nacos配置管理统一配置管理在nacos中添加配置文件从微服务拉取配置 配置热更新方式一方式二 配置共享1&#xff09;添加一个环境共享配置2&#xff09;在user-service中读取共享配置3&#xff09;运行两个UserApplication&#xff0c;使用不同的profile3&#xff09;运…

React教程(由浅到深)

文章目录 1. 基本语法1.1 初体验Hello React1.2 JSX语法的基本使用1.2.1 语句与表达式说明 1.3. React面向组件编程1.3.1 函数组件与类组件 1.4 组件实例的三大特性1.4.1 state数据存储状态1.4.2 props的使用1.4.2.1基本使用1.4.2.2 做限制类型&#xff0c;默认值使用1.4.2.3 简…

2、boostrap 多数据类型表单

fileinput 视频图片文本数据表单 插件下载地址&#xff1a;https://github.com/kartik-v/bootstrap-fileinput/ 1、多类型数据from测试 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</tit…

Jeston Xavier NX 模块将系统迁移到NVME存储

大家好&#xff0c;我是虎哥&#xff0c;最近完成了自己设计的第一个Xavier NX的载板设计和打样&#xff0c;虽然还有一些小的不完善的地方&#xff0c;但是可以正常使用&#xff0c;这里记录和分享一下我自己设计的载板上如何实现系统迁移。 我自己使用SDK Manager 安装了所有…

c# Invoke使用

在多线程编程中&#xff0c;我们经常要在工作线程中去更新界面显示&#xff0c;而在多线程中直接调用界面控件的方法是错误的做法&#xff0c;Invoke 和 BeginInvoke 就是为了解决这个问题而出现的&#xff0c;使你在多线程中安全的更新界面显示。 正确的做法是将工作线程中涉…

青少年机器人技术一级考试备考重点(三):简单机械

随着机器人技术的飞速发展&#xff0c;越来越多的青少年开始关注并参与其中。青少年机器人技术考试作为一项评估学生机器人技术水平的重要考试&#xff0c;备受广大青少年和家长的关注。为了更好地备战青少年机器人技术一级考试&#xff0c;了解考试的学习要点和备考重点是非常…

【C++初阶】11. list的使用及模拟实现

1. list的介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向其前一个元素和后一个元素。list与…

清华青年AI自强作业hw5:基于CNN实现CIFAR10分类任务

清华青年AI自强作业hw5&#xff1a;基于CNN实现CIFAR10分类任务 简述作业实现遇到的问题相关链接 一起学AI系列博客&#xff1a;目录索引 简述 hw5作业为利用深度卷积神经网络实现CIFAR_10数据集十分类问题&#xff0c;帮助理解CNN的前向传播结构。 CIFAR-10是一个常用的彩色图…

现代处理器结构

本文翻译自&#xff1a;Modern Microprocessors A 90-Minute Guide!&#xff0c;&#xff0c;我认为原文是相当好的计算机体系结构方面的概述&#xff0c;与时代相结合是国内计算机课本普遍缺失的一环&#xff0c;本文可作为一个有效的补充&#xff0c;向原作者和其他译者表示感…

青岛大学_王卓老师【数据结构与算法】Week03_09_线性表的链式表示和实现9_学习笔记

本文是个人学习笔记&#xff0c;素材来自青岛大学王卓老师的教学视频。 一方面用于学习记录与分享&#xff0c;另一方面是想让更多的人看到这么好的《数据结构与算法》的学习视频。 如有侵权&#xff0c;请留言作删文处理。 课程视频链接&#xff1a; 数据结构与算法基础–…

软件测试面试-银行篇

今天参加了一场比较正式的面试&#xff0c;汇丰银行的视频面试。在这里把面试的流程记录一下&#xff0c;结果还不确定&#xff0c;但是面试也是自我学习和成长的过程&#xff0c;所以记录下来大家也可以互相探讨一下。 请你做一下自我介绍&#xff1f;&#xff08;汇丰要求英…

如何在VUE项目中使用svg图标

一文带你搞定svg图标的使用&#xff01; 文章目录 前言一、SVG相较于字体图标的优点二、使用步骤1.新建一个vue2项目2.安装项目依赖3 .在src目录下新建文件夹4.创建svg-icon组件5.在main.js中引入icons下的index.js6.配置 vue.config.js7.步骤完毕&#xff0c;检验成果 总结 前…