教你如何编写串口接收程序 您所在的位置:网站首页 月经第三天垂体泌乳素正常值是多少 教你如何编写串口接收程序

教你如何编写串口接收程序

#教你如何编写串口接收程序| 来源: 网络整理| 查看: 265

导读:学单片机的大概最先、最常写的通信程序应该就是串口程序了,但是如何写出一个健壮且高效的串口接收程序呢?接下来鱼鹰将根据多年的开发经验教你如何编写串口接收程序。

本篇文章包含以下内容,很长,但干货满满,就看你能吸收多少了:

传入参数指针

互斥锁释放顺序

数据帧检查

串口空闲

通信吞吐量

内容很多,鱼鹰慢慢写,道友您也请慢慢看。

为了更好的理解接下来的知识点,鱼鹰将设计一个串口框架,让道友心中有一个参考方向。

本篇重点在于解决如何写一个健壮、高效的串口接收数据,发送与接收处理过程略讲。

帧格式

先聊聊帧格式,一般来说,一个数据帧有以下几部分内容:

帧头

帧头用于分辨一个数据帧的起始,这个帧头必须足够特殊才行,因为它是分辨一个帧的起始,那么什么样的帧头是足够特殊的数据呢?保证这个数据在一个帧内最好只出现一次的数据,那就是帧头,比如0x55、0xAA之类的。而且最好有两个字节以上,这样帧头才更加独一无二。

但是数据域内的数据你是没办法保障不包含和帧头一样的数据。

那么如果不凑巧,除了帧头外其他部分也有这样的两个字节的帧头,那会出现什么问题?

几乎不会出现问题。因为一般来说数据都是一帧一帧发送的,只要你前面的数据帧传输正确,那么即使下一帧的数据中有和帧头一样的数据(包括帧头)也没有问题,因为帧头判断已经在开始就判断成功了,就不会继续判断后面的数据是否是帧头了。

那么为什么说是几乎,因为如果上一帧数据接收错误,那么程序必须再找一次帧头才行(单字节接收时是如此,采用空闲中断的话就不需要这么麻烦),这就导致找帧头的时候在帧头数据之外寻找了,很可能这些数据就有帧头。

但是即使帧头数据之外的假帧头真的存在,也没关系,还有第二重保障,那就是校验,即使找到了一个错误的帧头,那么数据校验这一关也很难过去,所以放宽心。

如果校验也凑巧通过了,那还有第三重保障:帧尾。应该到不了这里吧,毕竟这比中彩票还难。

又要上一帧数据接收错误,还要当前帧除了帧头之外还有帧头,另外你还能跳过校验的检查(还有功能字、长度信息的检查),太难了。所以只要通过了这些检查,你就可以认为这个数据帧是可用的了。所以一帧数据接收错误,导致的问题最多只是丢失了这帧数据,对后续接收是不会有影响的(前提是你这个接收程序设计的足够好),发送端在发送超时后再发送一次即可,所以重发机制很重要。

事实上,如果你采用串口空闲中断,帧头、帧尾都可以不用,但一般来说,帧头都会保留,帧尾可以不需要,这是为了当单片机没有串口空闲中断时考虑,当然也可能有其他考虑,所以帧头得保留。

功能字

功能字主要用于说明该数据帧的功能,当然也可以作为函数指针的索引,一个索引值代表了一个具体功能,据此可找到对应的功能函数。

比如,设计一个函数指针数组,通过功能字进行索引,进而跳转到对应的功能函数中处理。

特别注意的是,设计功能字的时候,要考虑兼容性,对数据帧的功能进行划分,不要想到一个算一个,功能字也不要随便安排,不然在以后增加数据帧的时候会很麻烦。

比如说,只有一个字节的功能字,前四位作为一个大类,后四位作为大类中具体类。这样就可以将系统数据通信帧分为16个大类,每个大类下有16个可用的具体类,当你增加功能字的时候,就可以根据你的设计来确定属于哪个大类了,然后再插入进去。这样在管理、维护这些通信数据时你会发现很方便。

这个思想其实在ARM内核的中断系统和设计 uCOS II 任务优先级的时候都有,而鱼鹰在设计项目的通信协议的时候就是运用了这些思想。

(图片来源于《权威指南》)

长度

长度信息也是一个非常关键的数据,别小看了它,因为它,鱼鹰用了将近一个星期的时间才把一个HardFaul问题解决了,虽然这个程序bug不是我写的(鱼鹰一直用的是串口空闲接收方式,这个bug自然而然就跳过了),但确实很容易出错。

因为它是决定了你这个数据域长度的关键信息(一般长度信息代表数据域的长度,而不包含其它部分长度),也是这个数据帧的长度信息(加上固定字节长度就是帧长度了),更是接收程序还要接收多少数据的关键信息(对于空闲中断接收方式不算关键,这里的不关键是指不会造成程序异常问题)。

比如说你的程序刚好将帧头、帧尾、功能字判断完毕,然后中断程序因为种种原因导致没有及时接收串口数据,那么你可能得到的就是错误的数据,然后这个错误的长度数据就可能导致你的栈帧或者全局变量被破坏(单字节接收情况下就可能出现,因为鱼鹰碰到过),这是很严重的事情。所以在接收数据域的数据之前一定一定要判断这个长度信息(空闲中断除外)是否合法,不合法的话及时扔掉这帧数据,开始下一帧的数据检查。

所以为了保证及时接收数据,最好采用DMA传输。

数据域

这个没啥好说的,就是整个帧你真正需要发送的数据。而为了让你的发送函数能接收各种类型的数据,那么把参数类型设置为 void * 会是不错的选择。

校验

一个数据在接收过程中可能会被干扰,导致接收到错误的数据,那么如何保证这帧数据的完整与准确性呢,就在校验这一关了。

校验有很多方式,和校验、CRC校验等(奇偶校验是针对一个字节的,不是数据帧)。

和校验算法简单,CPU运算量小,累加最后只取最低字节即可(注意不是高字节,想想为什么),或者保存累加和的变量就是一个字节空间,这样就不需要额外操作了。

CRC校验,这个算法复杂,理解起来比较困难,但一般来说可以直接拿来用,因为它是对每一位(bit)进行校验,所以纠错率很高,几乎不存在发现不了的数据错误,但正因为对每一位进行检查,所以CPU运算量较大,但是有的单片机是可以硬件计算CRC校验值的(比如stm32)。不过现在CPU运算速度都挺快的,软件运算也是可以接受的。

那么该怎么校验呢?是从帧头开始到数据域部分,还是说直接校验数据部分?其实都可以,区别就是运算量问题,不过问题不大(最好是从头开始校验,以保证整帧数据的准确性)。

帧尾

前面说了,帧尾在空闲中断中可以不用,RXNE中断接收时其实也可以不用,当然也可以加上,好处就是当你用串口助手查看数据流时,可以观察出一帧数据是否发送完整了。

最后再说说为什么在数据域前面设计四个字节大小,除了协议本身需要外,还有一个原因就是强制类型转化需要,我们知道,一般来说,赋值时都有字节对齐的限制(实际上有的CPU可以不对齐进行赋值),stm32是32位的,那么四字节对齐是最合适的,这样就可以直接将我们收到的数据转化为需要的数据类型了。

传输过程

聊完了帧格式,再从大的方向看串口的传输过程:

当发送端发送第一帧数据包时,接收端通过某种方式接收(串口接收非空RXNE中断、串口空闲IDLE中断),为了让串口能够触发空闲中断,必须在发送端两个发送帧之间插入一段空闲时间(就是在此时间内不发数据,红色部分),保证空闲中断的准确触发。

同理,为了让发送端也能正常接收接收端的数据,也需要控制接收端的发送,不能在返回一帧数据时立马发送下一帧数据,不然触发不了发送端的空闲中断。

事实上,有些程序员设计的发送、接收过程比这个简单一些。即只有当接收端接收到一帧数据并返回一帧数据之后,发送端才能继续发送数据,这样一来,我们只需要控制好接收端的频率,就可以控制整个通信过程,也能控制通信频率。

但为什么还要设计成第一种传输情况呢?这是为了充分利用串口,增大数据吞吐率(这个后面再说)。

另外,不知道你是否观察到图中的每个数据帧占用的时间是不一样的,这是因为每个数据帧不可能都是一样长的,它们是不定长的数据包,所以你的定时不能从发送开始定时,而是从发送完成后开始定时控制空闲时间。

软件设计

上面所有的内容都是设定一些条件、需求,那么该如何实现软件设计呢?毕竟说的再多,如果不能实现这些,又有什么用呢?talk is cheap, show me the code。

下图设计了三个数据帧:GetVision(),GetSN(),GetMsg()。

GetVision()用于获取硬件版本号、软件版本号。

GetSN()用于获取产品序列号,用于识别唯一设备。

GetMsg()用于获取消息,可以获取各种传感器数据,事实上,如果数据量多的话,根据传感器的不同,会根据需要设计各种不同的数据帧(功能字不同)。

在软件设计上一般都会对这些函数设计一个统一的函数类型,用函数指针数组统一管理。

既要统一,又要体现差异性,函数参数就显得很有必要了。

这里设计了两个参数,一个是void* (无类型指针),一个是length(长度)。

无类型指针主要是用于传递数据域的数据地址,而数据域的数据可能是整型、浮点型、结构体、枚举、联合体等,为了保证传入的各种数据类型在不通过强制转化情况下都能兼容,设计为 void * 就显得很有必要了。

实际上为了显得更专业性,加上 const 修饰会是不错的选择,因为这可以保证缓存数据不被修改(事实上只能保证不被程序员修改,而不能保证程序本身,这个后面会解释)。

长度,长度参数是一个很关键的参数,为了保证长度的准确性,建议使用sizeof 获取。

有人觉得sizeof 好像一个函数,会不会导致效率低下啊,毕竟每次通信都要计算一次长度,那你就大特大错了。事实上,只要你的类型定义定义好了(不管是内置的类型定义还是自定义的结构体、枚举、联合体),编译器都能确定 sizeof 最终的数据长度,根本不存在计算过程。

用 sizeof 的两个好处:

1、可以忽略字节对齐问题(不同平台不能忽略,比如window和单片机通信)。因为编译器为了数据读写效率更高,一般会对数据进行地址对齐,这样一来手工计算一个数据类型的长度变得麻烦(当然你可以说使用某些手段让数据不进行对齐,这个另说),而 sizeof 将智能且准确计算数据大小。

2、当你使用 sizeof 时,兼容性更强,也显得更专业。程序修修改改很正常,一个数据结构改来改去也很正常,特别是开发初期更是如此。但是不管你怎么改,只要在编译器看来是固定长度的数据类型,那么 sizeof 就能在链接程序前计算出来;并且即使你后来加了数据不对齐的限制(加了这个限制后,很可能数据大小变小),也能准确计算。别问为什么,就是这么任性。

所以为了减小出错的可能性、减少劳动量,sizeof 是不错的选择。

当接收到数据地址和长度信息后,就可以进行发送了。

因为只有数据域的数据,为了组成一帧完整的数据,就必须加入打包过程。加上数据帧头、功能字、数据长度、校验等数据。

当一帧数据打包好之后,就可以进行发送了,发送可以采用循环查询发送,也可使用发送空TXE中断,当然还是建议使用DMA发送,这样你可以还没等它发送完就可处理其它事情了。

以上就是发送过程,接收过程也是同理,根据功能字来调用相应的函数进行回复。

事实上,如果只是数据的传输过程,完全可以使用一个发送函数实现数据的特异性传输,这样就可以减少一层数据传递,但是有些通信帧不只是数据的传输,可能在接收、发送时作一些其他处理(比如清除、设置某些标志位),所以需要再增加一层,用于进行差异性处理。

以上就是本篇内容的基础内容了,你以为快完了?你错了,现在只是刚开始而已,鱼鹰写本篇笔记的最终目的还在后面。

这只是前菜,正文才刚开始。

串口接收遇到的那些问题

以下内容不会用太多的笔墨描述如何写发送、接收函数,而是重点关注串口接收过程中可能遇到的一些问题,如果说描述到了发送、接收函数,别会错意,顺带的。

以下大部分问题都是因为采用RXNE(接收不为空)中断方式导致的问题,只有一个问题是鱼鹰从前没有考虑到,也是IDLE + DMA方式不可忽略的问题。

这就是为什么鱼鹰建议采用IDLE + DMA 的原因,不仅是因为效率问题,更因为它能避免很多问题,当然水平足够高的话,采用RXNE也是完全(“完全”就未必,里面有一个问题是RXNE方式难以避免的问题)没有问题。

事实上,即使鱼鹰采用RXNE方式接收数据,也能避免以下大部分的问题,因为鱼鹰的基础足够扎实,会在一开始编写代码的时候自然而然避免一些问题的出现。

但是看完以下内容后,相信各位道友写出一个高效且健壮的串口接收程序根本不是问题,因为这就是所谓的经验啊。

传入参数指针

前面鱼鹰已经提到了需要一个指针作为函数的参数,这里说说这个指针问题。

我们知道,为了维护方便,也是为了节省空间,一般都会将类似的功能整合成一个函数,比如串口经常要用的发送、接收功能,但是所发送的数据内存空间可能就处于五湖四海了,他们通过指针来指向将要发送的数据。

为了节省 RAM 空间或者其它不为人知的原因,传递给发送函数的指针就是实际发送数据的地址,并且在计算校验值的时候也是直接使用这个地址进行校验计算,然后采用循环查询的方式发送数据,这样一来,就不必拷贝一个数据的副本进行发送,而是直接从数据源的地址进行发送,节省了部分 RAM 空间。

但是这样真的好吗?

你是否考虑过在计算数据帧校验值的时候,数据源改变了的问题呢?

比如说你采用和校验,数据一开始是0x55,计算数据帧的校验和值为tx_sum,然后被中断程序或者DMA修改了这个数据源,变成了 0xAA,此时你再使用这个数据地址进行发送,接收端接收到了0xAA,接收端计算校验和的时候是 rx_sum,那么rx_sum 必然不等于 tx_sum,然后接收端就认为该数据帧是错误的,然后丢失这帧数据,而这种情况是比较少见的,但确实是会偶尔出现接收错误的情况(当时发现这个问题时始终不得其解,明明我发送的是这个校验值,为什么你计算的校验值是另一个?开始怀疑是校验函数的问题,但其他数据帧计算时没有问题,只有一种数据帧会出现问题,然后鱼鹰怀疑是数据源的问题,是的,鱼鹰很快就怀疑数据源的问题,但当时验证时,只改了校验那部分地址,发送时的地址还是使用源地址,导致问题还是没解决,过了好久之后才发现这个发送地址没改,囧。所以说,即使你的思路是对的,但如果你解决时错了,问题也很严重)。

如果说接收端(从机)具有重发机制,那么问题不是很大,丢失一帧数据而已,再重发就是,但事实是,一般串口设计成主从模式,主机会在没有接收到从机的应答数据时会进行重发,但是从机一般不会主动重发数据的,它无法判断主机是否成功接收,而从机一般会在成功发送完数据后开始清除一些标志位(比如键盘按键数据清空,不然主机下次获取按键信息时还是同一个按键数据),事实上这个动作必须在对方成功接收才能进行(否则这次按键信息就丢失了),从这个角度来看,我们必须设计一个机制用于判断主机是否成功接收。

I2C、CAN总线都有应答信号,但这是这些是总线自带的特性,我们不可能在接收到一个字节后发送一个应答信号给主机,那么是否有其他办法呢。

人们很容易想到的一个办法就是在主机收到正确数据后,主动发送一帧专用数据帧用于清除这个标志(这个帧和普通帧一样,所以可以确保主机数据能准确送达从机,因为如果超时没有送达,会触发重发机制)。这样只要在获取完这帧数据后,再额外的发送一帧数据用于对方确认即可,从机接收到后,即可开始着手清除一些标志位。

但这样会有一个问题,因为这种特殊的需要从机确认的数据包(其他类型数据不需要确认是因为如果主机没有正确收到数据还可以继续获取,获取的数据是一样并没有关系,但这种需要从机确认,一旦从机认为发送成功了,数据就被清除了这种情况就需要确认,典型的就是按键信息了),我们需要额外处理并占用发送带宽。这是鱼鹰不愿忍受的。

那么是否有更好的办法?

或许我们可以从 USB 协议中获得启发(这是在写这篇笔记的时候想到的,当时写按键板代码的时候没有想到过,但因为当时测试时传输成功率100%,所以就放弃处理这种情况了)。

USB协议是典型的主从机制,主机不主动获取数据,从机是无法主动发送数据的。那么从机是如何确定对方成功接收数据了呢?

一个bit的翻转位。

每当主机成功发送一帧数据后再发送下一帧数据时,就会翻转这个位,从机就可以根据这个位判断主机是属于重发数据(重发数据表示主机接收失败了)还是新数据了,这样从机就能从下一帧数据确定上一帧数据是否成功发送了。

而主机发送的数据是由从机发送应答包确定的,和上面的串口协议类似,所以这个方向的数据是没有问题的。

那么我们该如何重新设计这个协议呢?可以尽可能的不改变原来协议的情况下实现吗?

或许可以从功能字出发。

为了保证对功能字的原有定义保持基本不变,使用最高 bit 作为这个特殊的位,这个 bit 开始是 0,之后主机每接收一个从机应答数据就进行翻转,如果因为没有接收到从机的应答数据,就会使用相同的翻转位重复发送;而从机也根据这个bit来确定自己的上一帧数据对方是否接收(对比上一帧数据的翻转位),如果主机没有成功接收,就不清除标志位(之后主机会重发数据再次获取),否则清除标志位,。

因为是鱼鹰刚想到的,就不多说了,仅提供一个思路。

现在回到指针那块的问题。

现在已经知道,如果你在计算校验和与发送的过程中出现源数据改变的情况,就会导致数据帧校验失败,那么有什么解决办法?

如果说你坚持使用查询方式发送来节省部分空间,那么只要在计算校验值之前拷贝一份源数据,然后用这份数据计算并发送即可。

另一种方法就是,直接把整帧数据拷贝到一个数据缓存中,使用DMA发送。

现在还有一个问题,那就是如果我想发送一个数据域为空的数据该怎么发送?

一般来说,在使用指针的时候,不会使用 NULL 空指针,但是在数据为空的情况下,就需要使用 NULL 指针了,并长度设置为 0,这个时候在检查指针的时候,不能看到空指针就退出函数,还要判断长度信息,当长度为0时在打包时就不拷贝源数据,但最终还是要发送数据帧的(当时别人写的代码将指针和长度判断同时放到了 for 循环的条件里面,鱼鹰觉得效率太低,导致修改代码是没考虑指针为空的情况,所以导致了一个小bug)。

互斥锁释放顺序

现在考虑第二个问题:互斥锁释放顺序问题。

如果没有采用队列方式接收数据,而是主机发送完成后等待接收从机数据后再发送下一帧数据,那么该如何处理互斥锁的问题?

我们知道为了保证数据的同步,保证在接收到一帧数据进行处理时,不能被新的数据帧冲掉,这时就要加入一个互斥锁,表示我正在处理数据,下面的数据我接收不了,这样就能保证你正在处理的数据不会被新来的数据修改掉,从而进行正确的处理。

那么这个标志位(互斥锁)该什么时候清掉(释放掉)呢?

一般来说标志位,一般越早清掉越好,比如外部中断标志位,进入中断后,一般首先会清理标志位,这样即使你正在处理本次中断的程序,那么即使这时再来了中断,也不会丢失中断信息(有悬起标志位),这样就可以在处理完这次中断后,立马进行下一次中断的处理了(前提是优先级足够高)。

但是如果你清理太早或者清理太晚会怎样?

比如说你在接收到一帧数据后(数据帧所有检查完成),开始设置标志位,当主程序查询到这个标志时(一般数据处理不会放在中断中),如果马上开始清除这个标志……嗯,一般来说不会有问题。

那么什么时候会出现问题?当你的主程序查询到这这个标志时开始清除标志,然后处理、返回数据给主机,如果此时主机超时重发数据时,,因为这个时候你虽然在处理数据,但是因为你的标志位已经被清除了,所以接收程序就会开始往接收缓存区存数据了,当你存完之后再回到数据处理那里,你的缓存区可能就不是你想要的数据了。

可能你会说,既然是重发,那么数据应该是相同的吧?好吧,你赢了,鱼鹰编不下去了,这种情况(有重发机制)下清理太早好像是不会出现问题,但你怎么知道对方是采用副本进行重发数据的呢,如果重发时它又从源数据中拷贝后再进行重发会出现什么问题?比如时间信息,开始第一帧数据是11:59,CPU刚把11拷贝到用户空间,被串口中断程序打断,导致下一帧接收的数据是12:00,此时回到主程序继续拷贝,拷贝00,数据的完整性被破坏,这样导致的结果就是11:00,而实际上时间是12:00,这就是你打断数据处理过程的后果。

现在再说说清理太晚会怎么样。

当你的主程序查询到这个标志后,暂时不清除,而是等到从机发送完应答数据之后再清除标志,此时因为从机采用查询方式(查询方式表明从机发送完最后一个字节后后开始清除标志位,也就代表了主机就差最后一个字节没有接收了,这样发送和清除之间间隔时间较短,而采用 DMA 方式的话,发送和清除的间隔时间更短,因为可能DMA还没开始发送第一帧数据,清除工作就已经完成了),或者因为其他原因(比如中断处理)导致发送和清除之间的时间很长这种特殊情况,这样可能主机已经开始下发下一帧数据了,但是因为此时标志还没有清除,不能接收数据,所以主机这一帧数据就这样丢失了。

那么这个清除标志位最合适的时机是什么时候?

你锁定资源利用完的时候。

现在来看看,这个互斥锁锁定的是什么资源?对,就是接收缓存,那么接收缓存什么时候用完?当然是在数据处理完成之时。也就是说在数据处理完之前、发送数据之前清除最合适。

这样就不会因为处理其他事情而导致清除操作过晚而丢失下一帧数据了,因为此时主机还没收到从机上传的数据,也就不会马上开始下一帧数据的传输了。

说到清除,你觉得,需要把整个缓冲区进行清零操作吗?这个问题在以往的文章解释过,就看你是否看完了。

数据帧检查

你是否会对接收的数据进行检查?如果不进行检查会发生什么?

我们知道一帧数据中,每个部分都有各自的含义,甚至有些部分可能在某些数据帧中不存在,比如数据域部分,我们需要根据长度信息来判断数据域部分是否存在。

但是你能保证你所接收的数据都是准确的吗?你能确保在工作环境下不会因为各种干扰导致数据长度信息由0x05变成0x85(最高位翻转)吗?,如果出现了会导致什么后果?

假设你采用RXNE中断方式来一个、一个字节的接收数据,分析如下:

因为是单字节接收数据,所以你需要把所有接收的数据当成数据流,根据帧头信息来确定帧的开始,一旦确定帧头信息之后,你就可以根据接下来的一系列数据保证一帧数据的结束,同时开始新帧的接收……

初看这个接收流程没有问题,但是真的如此吗?

但是就像前面所说,你能保证你的数据没有问题吗?如果说你接收到一个长度信息,本来是0x05,但是最终接收的数据是0x85,这就意味着你接下来的数据域的长度是0x85,根据你的接收流程,你需要再接收0x85个字节之后,才能判断校验字节是否正确。

可能你会说,虽然你的长度信息由0x05变成了0x85,之后接收校验过程肯定是失败的,那么这帧数据就会被接收程序丢弃,从而导致接收程序进入重新寻找帧头的流程,这个过程不是挺正常的吗?按理说上述异常情况是能被接收流程处理掉的。

那么首先确认一点,上述异常能被接收流程处理吗?

答案是能!

既然上述异常是能被接收状态机处理的,那么还会有什么问题?

问题就出在这个错误数据本身!

因为你是根据错误数据来决定接下来需要接收多少数据,而一般来说,接收缓存大小设置为最大帧的长度,那么就出现一个问题,你的缓存够你接收0x85个字节吗?

如果说你开辟的接收缓存空间很大,足够接收这么多数据,那么就算遇上以上情况,也是没有任何问题,但是万一你比较节省资源,缓存不够大会出现什么情况?

这就涉及到内存分配问题:

你的串口缓存一般在 Data区域,一旦你接收的数据超出了你开辟的空间,那么必然导致缓存空间溢出!

那么缓存空间溢出会导致什么危害?

我们通过上图可以知道,一旦缓存溢出,必然导致该缓存周围的数据出现异常(数据被篡改),如果你的其它代码刚好需要这个数据作为重要参考,而你在使用的时候又没有对这个数据的有效性进行检查,那么可能导致另一个灾难性后果,而这个后果又导致了其他后果,从而导致雪崩效应。

而你修复这个bug时,你以为修复了,但你只修复了表面,真正内在bug还存在!

所以,千万别太相信内存中的数据,每一个数据的输入都要进行严格检查,这个数据可以错误,但是不能导致程序崩溃!

所以千万别写能篡改别人数据的代码,这是很危险的事情,也是很难解决的bug,因为你不知道它会在什么时候篡改哪里的数据!

再假如你的接收缓存放在栈中了呢(稍微有C语言常识的程序员都不会把串口接收缓存放在栈中,但鱼鹰偏偏遇到过这种代码,而为了解决这个bug整整花了一星期,这还是在bug复现率高的情况下)?

根据前面的图可知,栈一般存放在高地址,并且一般栈生长方向为向低地址生长。如果出现上述情况(接收的数据大于开辟的栈缓存空间)会发生什么?

栈帧被破坏!

灰色部分因为接收的数据太多,导致原本存在的栈数据被串口的接收的数据修改了(注意篡改的数据可能不是连续的,因为每一次进入时,开辟的那部分栈空间可能都不在同一个地址),假如这个数据是保存返回寄存器LR的,那么必然导致返回错误,极可能触发HardFault中断!

那么有什么办法解决栈被破坏的问题?

最有效的方式鱼鹰觉得是使用ITM,如果无法在线调试,可以尝试DMA循环传输PC指针值(但是如何得到这个值?毕竟这个寄存器本身是没有地址概念的)到一块内存中,这样就可以得到最后正常执行的代码地址,从而定位错误代码的位置。

如果单片机不支持这些功能呢?鱼鹰现有的知识体系好像无法解决,只能佛系调bug了(看和bug之间的缘分),囧。

前面说了由于外部工作环境导致数据长度信息错误从而出现数组溢出这种情况,如果说你保证工作环境非常好,不可能出现这种干扰,是否还会出现问题?

当然会!

前面分析了外在原因,现在分析内在原因,你的接收程序能保证及时接收发送端发送过来的数据吗?如果不及时接收数据会出现什么问题?

我们知道,一个系统一般都有很多中断需要处理,如果说你的接收程序的中断优先级不是最高的,那么很可能出现接收程序无法及时接收的情况,即RXNE中断来临时,因更高优先级中断需要处理,而且处理时间较长,那么就会出现当前接收的字节因为没有接收完成而被后续的数据冲掉,即出现ORE(溢出错误)。

这样会导致什么问题?

数据域信息(也可能是校验值等数据)当成了长度信息(为什么只讨论长度,而不讨论功能字之类的数据,难道他们不会出现ORE的情况吗?),这样一来,如果这个数据很大,接收程序就会以为接下来还需要接收很多数据才能完成一帧的接收,导致后果和前面分析的数据干扰一样严重。

那么采用RXNE接收方式时该怎么解决这种问题?

检查长度信息的合理性,只要长度信息不会导致缓存溢出即可。

但是上面的解决方案会导致什么问题?

因为你的程序设计问题(采用RXNE接收导致不能及时处理),使得原本能接收的数据无法及时处理(DMA可以及时处理),最终使得当前这一帧数据无法正常接收(如果错误的长度信息够大的话,还有可能接下来很多帧数据都无法接收),这你能接受吗?

但是采用DMA为什么就不会有上述问题,除了DMA能自动接收数据提高效率之外,还有一点就是它不根据接收的数据来判断接下来还需要接收多少数据,而是根据设定的接收数据长度来接收的(如果加入IDLE中断,可以提前结束接收工作),这就避免了上述的缓存溢出和接收不及时问题。

最后再分析上述接收的另一个问题,那就是一帧数据中可能出现没有数据域的情况,这种情况该怎么处理?

只要根据长度信息分开处理即可。如果不对没有数据域的情况分开处理,那么你接收的下一个数据直接就是校验值,而你的接收流程却认为这是数据域的数据,必然导致校验失败。

现在总结使用RXNE方式接收的几个问题:

1、缓存溢出。

缓存溢出有两种可能,第一种就是环境干扰导致长度信息出错,从而出现缓存溢出情况;第二种情况就是因为接收不及时,导致数据错位,如果刚好是长度信息错误,并且这个长度信息太大,而你的代码未对长度进行检查,那么也会出现缓存溢出bug,而这种bug一旦出现,很难发现。所以在代码中对数据的合理性检查是非常有必要的一件事。

2、中断及时处理。

如果中断不及时处理,会导致数据错位,轻则丢失至少一帧数据,重则缓存溢出!

3、状态机是否需要接收数据部分。

由于数据帧有可能没有数据域的情况,所以必须区别处理,保证代码接收的准确性,否则有可能把校验值当成数据了,这样必然无法通过校验,这一帧数据必然会丢失!

串口空闲

前面一直提到串口空闲,也大概明白串口的作用,但是一些细节问题还是需要好好说一下的。

第一个问题,如何清除串口空闲中断标志位?

很多人会使用USART_ClearFlag标准库函数进行清除,但是当你跳转到该函数原型时,你会看到如下说明:

你会看到很多标志位是无法通过该函数清除的。

那么该如何清除IDLE标志呢?其实上面的注释已经进行了说明。

PE、FE、NE、ORE、IDLE标志位的清除是通过一个软件序列进行清除的:首先通过USART_GetFlagStatus读取USART_SR寄存器的值,然后通过USART_ReceiveData函数读取USART_DR的值即可。

那么这里就有一个问题,是否这些标志问题的清除都要单独编写清除序列呢?

答案是否定的。

因为这些标志位都是由同一种序列进行清除的,所以只要一个清除序列就会把所有的标志位都进行清除了(同样一旦执行了这个序列,也就意味着你无法再通过USART_SR寄存器获得标志位了)。

为了保证获取标志位,我们可以在清除序列之前把USART_SR寄存器的值保存到副本中,然后再读取USART_DR寄存器的值保存到副本来实现清除功能,注意该序列应该无条件执行(不在某个判断语句中)。这样后续我们就可以使用这个 USART_SR 的副本判断哪一个标志置位了,同样也可以使用 USART_DR 的副本获取串口数据,而为了实现以上效果,USART_GetFlagStatus这个函数就不合适了,只能直接操作寄存器去实现。

第二个问题,在线调试时对空闲中断会有影响吗?

我们知道,KEIL能够将一个结构体的数据全部读取出来,而库函数将串口模块的所有寄存器都封装在一个结构体中,这样就会出现一个问题,如果你的窗口是实时刷新的,当你使用KEIL读取串口模块寄存器的时候(不管是使用peripheral窗口还是Watch窗口),就会出现先读取SR再读取DR的情况, 这样就有可能出现KEIL和单片机CPU读取这两个寄存器冲突的情况。

如果全速运行时,KEIL 先执行了这个序列(通过调试器读取这两个寄存器的值),单片机CPU再读取SR寄存值,必然是无法读取到正确标志位的,因为这些标志位已经被KEIL的读取序列清除了(这个情况鱼鹰确实碰到过,当时明明下发了数据,但是单片机无法获取标志位),所以在调试串口时,注意不要让 KEIL 去读取这些寄存器(即关闭这些窗口,只有在必须的情况下才开启),防止出现莫名其妙的情况。

第三个问题,空闲中断能准确触发吗?

如果从接收端考虑的话,如果触发了空闲中断,那么必然满足了条件才触发的,而不是意外触发的(嗯,我们要相信STM32),但从发送端考虑的话,有可能出现一帧数据断续发送,导致一帧数据触发多次空闲中断,所以如果是简单的DMA+空闲中断方式接收是很有问题的(空闲出现就认为一帧结束了,就会把一帧数据当成两帧处理,这样肯定无法通过数据检查的)。

那么先来分析为什么会出现一帧数据多次触发空闲中断情况。我们知道linux、windows系统并不是实时系统,当应用程序需要发送一帧数据时,可能并没有连续发送,而是发送完一个字节后去处理其他事情后才发送下一个字节,这样一来,如果耽误的时间够长,就会触发串口的空闲中断,从而一帧数据当成两帧处理了。

那有什么方法可以解决呢?鱼鹰提供两种解决思路。

第一种,使用两个缓存空间,一个缓存空间专门用于接收串口数据,将接收到的数据存放到另一个缓存,这个缓存采用字节队列的方式进行管理,应用程序从缓存队列中一个字节一个字节的取出数据进行处理(注意检查数据有效性),这样就能保证及时处理。但是因为空闲中断不再可靠,所以空闲中断不再作为判断一帧数据结束的依据(根据长度信息判断),而是只在空闲中断中将已接收数据复制到字节队列缓存中,这样就可以处理意外的空闲中断。

第二种,还是一个缓存空间,还是DMA+空闲方式处理,但是需要增加额外的条件。就是当进入空闲中断后,不再直接处理,而是获取当前接收时刻,然后在处理数据的时候根据这个时刻来判断是否达到足够的空闲时间,只有在进入空闲中断后并达到一定延时之后才认为一帧数据结束了,这样可以避免一些非常短的空闲时间(鱼鹰公众号提供过的代码使用这种方式)。

以上问题是就是鱼鹰以前使用空闲中断从未考虑的问题,鱼鹰并不知道使用空闲中断还可能出现误触发的情况,但是既然知道了,就要想办法解决。但是为什么以前使用空闲中断时没有出现通信问题呢?

事实上不是没有问题,而是有可能把分散的一帧数据的两部分直接丢弃了而已,因为有重发机制,所以即使丢弃一帧数据,也能通信正常,而且这种一帧数据分散成两部分的概率还是挺低的,ubuntu(linux系统)下大概千分之三左右的样子。

第四个问题,如果单片机没有空闲中断又该如何做?

当我们使用 RXNE 的同时其实我们也可以使用空闲中断,这样也能确定一帧数据的结束(但是要注意前面的误触发问题)。但是如果有些低端单片机(如 51 )没有空闲中断又该怎么办?

其实我们可以从 stm32 的空闲中断得到相应的启发。

所谓空闲中断,就是当串口接收到数据后,在应该接收数据的时刻,发送方并没有发送数据,所以串口模块置位空闲标志位,从而引起空闲中断。

那么我们是否可以软件模拟串口模块的这个功能,从而确定一帧数据的结束呢?

答案是肯定的(前提是每一帧数据之间有空闲时间)。

我们可以使用一个定时器,定时器向上计数。当接收到一个字节数据后,初始化计数器并启动定时器,这样一旦有一段时间没有接收到串口数据(也就不再初始化计数器),那么定时器溢出,进入溢出中断,而这个溢出中断就类似于串口的空闲中断(在溢出中断中关闭定时器以达到清除空闲中断标志的作用),这样就达到了串口空闲中断的效果(和前面问题的第二种解决方案类似)。

通信吞吐量

在以上分析过程中,都是采用主机发送,从机接收后再回复主机的方式进行通信,虽然通信正常,但实际上效率比较低下,单位时间传输的数据量较少,如下图所示:

红色部分就是必要的空闲时间,可以看到左右两张图的通信频率是有差异的,右图中从机必须等待前一帧数据发送完毕才能处理数据,而左图可以在接收当前帧时处理上一帧数据,类似CPU的指令执行流水线。

(图片来源于《权威指南》)

我们也可以将串口接收分为二级流水线:接收、处理,如此一来,我们最少需要两个缓存空间,当一个缓存在接收时,另一个缓存就进行数据处理。发送端可能不等接收端发送完应答数据,它就已经开始发送下一帧数据了,只要相邻两帧数据保证一定发送间隔,就能正常触发中断。

同理,因为接收端也不再慢悠悠的等待接收数据,而是可能有好几帧数据等着它处理,所以为了确保发送端能正常触发空闲中断,也需要控制发送间隔。

为了最大程度利用串口,我们可以使用队列管理很多缓存空间(当只有两个缓存时,可以直接使用异或运算进行缓存切换),比如 uCOS II 中我们可以利用系统的内存管理服务和队列服务实现有效管理,并且当有非常紧急的通信任务时,还可以插入到队头优先处理。

但是增大吞吐量时,比如对重发机制和从机数据的确认有一定影响,需要考虑清楚。

对于如何提高通信量,鱼鹰经验不多,就不多说了(或许可以从网络通信过程中得到答案)。

如果要用一句话总结本篇笔记内容,那就是使用空闲中断+DMA+队列+内存管理+定时控制方式接收串口数据会是不错的选择。

原文标题:如何写一个健壮且高效的串口接收程序?

文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

扫一扫,分享给好友

复制链接分享 评论

发布评论请先 登录

相关推荐

RT-Studio中打开终端,显示unable to open serial port是为什么? 如图所示,RT-Studio中打开终端,显示unable to open serial port 这是我可以用的串口,没有一个可以成功打开。 可以看... 发表于 03-18 11:06 • 1次 阅读 RTT应用层串口读写与注册时候的默认规则是什么呢 应用层的串口问题 应用层使用串口时,需要先指定以何种方式(轮询、中断或者DMA)去打开串口,然后再进行串口的操作。这部分... 发表于 03-18 10:08 • 333次 阅读 探讨一下RT-Thread的FinSH组件上的串口问题 结合串口使用过程的反馈信息,本章节将结合 FinSH组件的串口相关问题 和 应用层上使用串口设备的相关问题 这两个方面进行分析... 发表于 03-18 09:57 • 149次 阅读 为什么给RT-Thread串口发一帧数据,收到多帧全0的数据? 我在RT-Thread中,开通UART2 DMA接收后,发现一个问题:在给一个射频标签读写器发送一帧数据后而Rt-thread串口收到多... 发表于 03-17 15:02 • 814次 阅读 为什么给RT-Thread串口发一帧数据,收到多帧全0的数据? 小编科普一下有关单片机串口驱动框架封装的知识 在 serial.c 的 rt_serial_write 中         /* DMA mode Tx */      &nbs... 发表于 03-17 09:21 • 1399次 阅读 如何解决DMA发送串口数据时出现的数据覆盖问题? 大神好。我在用DMA发送串口数据时会出现数据覆盖的现象 我用这种方式打开并配置串口DAM: g_uart2obc = rt_device_find(... 发表于 03-16 09:49 • 494次 阅读 RT-Thread开通UART2 DMA接收后,RT-Thread串口发一帧数据收到多个无效帧怎么解决? 我在RT-Thread中,开通UART2 DMA接收后,发现一个问题:在给一个射频标签读写器发送一帧数据后而Rt-thread串口多收到... 发表于 03-16 09:43 • 496次 阅读 怎样去更改RT-Thread Studio中的控制台串口号呢 1.使用RT-Thread Studio 创建了RT-Thread nano工程时候,我们选择了控制台串口是UART1,填写了发送脚和接收脚。如下图... 发表于 03-15 10:24 • 414次 阅读 MSP430串口通信(入门级) MSP430串口通信(入门级) 发表于 03-14 15:44 • 13次 阅读 分享一种使用RT-Thread Studio配置STM32L496工程无法运行的解决办法   问题:​使用RT-Thread Studio配置一个STM32L496的工程。填写好调试串口的引脚和基本工程信息后直接编译程序。下载到... 发表于 03-14 11:10 • 281次 阅读 RT-Thread串口的硬件工作模式有哪几种呢?怎样去使用 简述回想大家在移植一个新的BSP或者芯片时,如何验证是否移植成功呢?是的,msh控制台窗口走一波 RTT 的 logo 信息,输出成... 发表于 03-11 15:03 • 2911次 阅读 带你认识这些接口,知道长什么样子 对TTL电平的器件来说,当输入电压高于2V时,才会被识别为逻辑1,输入的低电平低于1.2V时,才会被.... 的头像 硬件攻城狮 发表于 03-11 10:38 • 198次 阅读 单片机没有串口时如何打印调试信息 输出调试信息是嵌入式开发中必不可少的调试利器,嵌入式开发的一个特点是很多时候没有操作系统,或者没有文.... 的头像 硬件攻城狮 发表于 03-10 17:19 • 539次 阅读 单片机没有串口时如何打印调试信息 串口和并口的区别是什么 串口和并口是对立的两种概念,就像单车道和多车道一样。串口通讯是将每一个字节的数据以按位的形式传输给数.... 发表于 03-10 09:36 • 113次 阅读 3线串口共阴极8段6位或10段4位LED驱动控制专用电路A AiP1620是LED驱动控制专用电路,内部集成有MCU数字接口、数据锁存器等电路。本产品主要应用于.... 发表于 03-07 15:57 • 22次 阅读 CP2104-USB转串口驱动 CP2104-USB转串口驱动 发表于 03-07 15:18 • 35次 阅读 加速MCU开发的Curiosity Nano系列评估板 对于MCU厂商来讲,在芯片产品之外,配套的生态资源显得愈发重要。这也就意味着,每一款新产品推出之后,.... 的头像 得捷电子DigiKey 发表于 03-07 08:48 • 282次 阅读 中国移动MCU与STM32F103系列兼容性对比 ST是MCU领域当之无愧的大佬,而ST最经典的MCU无疑就是STM32F103了。由于收到市场热捧,.... 发表于 03-04 15:00 • 26次 阅读 中国移动MCU与STM32F103系列兼容性对比 ESP8266WIFI模块之串口编程(一) 在我学习单片机串口发送与接收数据的时候,对串口的掌握不熟悉,看了不少的学习资料。今天与大家分享使用H.... 发表于 03-04 09:52 • 40次 阅读 ESP8266WIFI模块之串口编程(一) 光纤项目系统概述、功能及结构 光纤项目是明德扬承接自某研究所的项目。该项目功能是接收4路光纤数据,经过内部DDR3缓存后,通过PC.... 发表于 03-02 11:39 • 38次 阅读 光纤项目系统概述、功能及结构 如何在STM32中要实现数据通讯 z在stm32中要实现数据通讯,首先要设置相关的寄存器,这里不做相关的介绍,直接说代码相关的能内容及.... 的头像 嵌入式应用开发 发表于 03-02 08:56 • 263次 阅读 如何在STM32中要实现数据通讯 鸣驹智能超级网口产品使用手册 鸣驹智能超级网口产品使用手册免费下载。 发表于 02-21 15:25 • 30次 阅读 串口示波器软件NS-Scope概述 *主要功能:硬件连接;参数配置;运行参数;数据查询。 *程控对象:示波器。 *程控接口:兼容市面上所.... 的头像 纳米软件(系统集成) 发表于 02-15 17:08 • 328次 阅读 串口示波器软件NS-Scope概述 在MCU中,如何实现串口的不定长数据包接收? 在MCU的应用中,经常需要通过串口进行不定长数据包的传输。发送方很简单,不需特别的考虑,而接收方则需.... 发表于 02-10 12:16 • 35次 阅读 在MCU中,如何实现串口的不定长数据包接收? KEIL调试经验总结 KEIL 调试方法,但是到底该怎么使用这些方法呢?这篇文章将介绍个人的调试经验。 的头像 硬件攻城狮 发表于 02-10 11:42 • 555次 阅读 KEIL调试经验总结 串口通讯的起始、数据、停止位都是怎么分配的? 串口是串行接口(serial port)的简称,也称为串行通信接口或COM接口。串口通信是指采用串行.... 发表于 02-09 10:59 • 25次 阅读 串口通讯的起始、数据、停止位都是怎么分配的? 干货 | STM32串口波特率大小计算 STM32下的波特率和串口外设时钟息息相关,USART 1的时钟来源于APB2,USART 2-5的.... 发表于 02-08 16:37 • 39次 阅读 干货 | STM32串口波特率大小计算 STM32串口之空闲中断 NBiot模块一般都是串口接口,使用AT指令集,对接中国移动onenet平台。先用串口助手去测试,流.... 发表于 02-08 16:17 • 16次 阅读 STM32串口之空闲中断 建议收藏 | 实用STM32的串口控制平台的实现 玩过Linux的朋友, 是不是对Linux无所不能的串口Shell命令控制台羡慕不已, 要是自己做的.... 发表于 02-08 15:54 • 17次 阅读 建议收藏 | 实用STM32的串口控制平台的实现 串口发送的这几种写法,你用过几种? 它们分别表示数据在发送过程中,在两个不同的阶段中的完成情况。TXE 表示数据被从发送缓冲区中取走,转.... 发表于 02-08 15:16 • 16次 阅读 串口发送的这几种写法,你用过几种? 利用沁恒赤菟V307实现八串口服务器 串口服务器,提供串口转网络的功能,将串口转换成TCP/IP协议网络接口,实现串口和网络接口的数据双向.... 的头像 RTThread物联网操作系统 发表于 01-27 18:43 • 798次 阅读 利用沁恒赤菟V307实现八串口服务器 串口驱动框架剖析及性能提升   轮询模式占用 CPU 最高,但是实现也是最简单的;DMA 占用 CPU 最少,实现也是最麻烦的;.... 的头像 RTThread物联网操作系统 发表于 01-26 16:36 • 621次 阅读 Modbus转Profinet网关YT-PN-03的特点 本方案是移通创联Modbus转Profinet网关连接STM液位仪的应用方案,用到的设备为西门子12.... 发表于 01-18 18:20 • 46次 阅读 Modbus转MQTT网关的简单介绍 Modbus作为常用的通讯协议,应用非常广泛,很多的设备都支持Modbus协议如PLC、流量计、水表.... 发表于 01-18 18:06 • 54次 阅读 小编科普什么是串口线呢 大家可能都听说过串口线,但是串口线的真正含义是什么你知道吗 的头像 工程师邓生 发表于 01-16 10:12 • 994次 阅读 美格智能:智能模组实现真正意义上的万物智联 什么是智能模组,相信每个人心中都有自己的概念。美格智能作为智能模组的创领者是如何定义的呢? 的头像 美格智能 发表于 01-14 11:46 • 686次 阅读 STM32软件学习笔记(一)基于HAL库的STM32F429单片机串口打印程序   目前ST官方有提供两种库文件,一种是标准库,一种是HAL库,在HAL库问世之前,标准库一直被嵌入.... 发表于 01-14 10:44 • 32次 阅读 STM32软件学习笔记(一)基于HAL库的STM32F429单片机串口打印程序 低成本国产无线串口GC433-TC007模块优势简析 一、GC433-TC007模块简介 GC433-TC007是硅传科技推出的一款超高性价比无线数传模块.... 发表于 01-13 17:24 • 315次 阅读 解决串口开启DMA接受却只能接收到第一个字节之后就再也接受不到数据的问题 设备:单片机:STM32G070 MDK:V5.25问题描述:发现串口接受不到数据,只能接受到.... 发表于 01-12 19:06 • 80次 阅读 解决串口开启DMA接受却只能接收到第一个字节之后就再也接受不到数据的问题 串口调试工具软件免费下载 串口调试工具软件免费下载 发表于 01-05 11:06 • 111次 阅读 RT-Thread X RISC-V创新应用设计大赛获奖作品公布 RT-Thread X RISC-V创新应用设计大赛自2021年9月14日举办以来,历时3个多月赛程.... 的头像 电子发烧友论坛 发表于 01-05 09:41 • 562次 阅读 210429-基于单片机的温湿度串口监控系统设计 温湿度是我们日常生产和生活中实时在接触到的物理量,但是它是看不到的,仅凭感觉只能感觉大概,传统的指针.... 发表于 12-31 18:55 • 52次 阅读 210429-基于单片机的温湿度串口监控系统设计 【STM32学习】(6)串口1的使用(USART1) 单片机串口是必须要学习的,而且很重要,在数据传输已经程序调试中都很重要。本博客先学习一下USART1.... 发表于 12-28 19:27 • 77次 阅读 【STM32学习】(6)串口1的使用(USART1) stm32 程序二次加载:串口 使用串口二次加载程序即:不需要拆机就能够对产品进行升级,通过Bootloader就可以完成这项工作。.... 发表于 12-28 19:18 • 53次 阅读 stm32 程序二次加载:串口 通过串口利用printf函数输出数据 一。printf函数格式printf函数具有强大的输出功能%表示格式化字符串输出目前printf支持.... 发表于 12-28 19:11 • 75次 阅读 通过串口利用printf函数输出数据 STM32学习之串口收发程序编写,小白都能理解的方法 前言Q: 为什么写这篇文章?笔者其实也是小白,看了一些书籍和网上的教程,发现有一个比较严重的问题——.... 发表于 12-28 19:06 • 66次 阅读 STM32学习之串口收发程序编写,小白都能理解的方法 串口屏储存器不够自己扩展怎么操作? 随着串口屏硬件平台的发展,串口屏Flash内存芯片由大容量并口Nand型、全面进化到了小容量SPI .... 发表于 12-28 11:21 • 1134次 阅读 串口屏储存器不够自己扩展怎么操作? 迪文串口屏的安装方法之卡扣结构带外壳智能屏 一、固定嵌入旋转式 以DMT48270C043-15WT型号为例:   迪文科技是一家专注于人机交互.... 发表于 12-28 10:39 • 746次 阅读 迪文串口屏的安装方法之卡扣结构带外壳智能屏 STM32F07xx单片机串口1重映射配置 void USART1_Config(unsigned int BaudRate){ GPIO.... 发表于 12-27 19:25 • 44次 阅读 STM32F07xx单片机串口1重映射配置 STM32单片机串口空闲中断+DMA接收不定长数据 在上一篇文章STM32单片机串口空闲中断接收不定长数据中介绍了利用串口空闲中断接收不定长数据,这种方.... 发表于 12-27 19:24 • 90次 阅读 STM32单片机串口空闲中断+DMA接收不定长数据 嵌入式单片机学习之串口接收数据实例 //通过判断接收连续2个字符之间的时间差不大于10ms来决定是不是一次连续的数据.//如果2个字符接.... 发表于 12-27 19:22 • 57次 阅读 嵌入式单片机学习之串口接收数据实例 STM32单片机串口空闲中断接收不定长数据 在使用单片机的串口通信功能时,常用的接收数据方法是通过固定的字节数来判断一帧数是否发送完成,或者是通.... 发表于 12-27 19:22 • 50次 阅读 STM32单片机串口空闲中断接收不定长数据 STM32CubeMX-串口开启DMA进行数据传输 STM32CubeMX笔记-串口开启DMA进行数据传输 发表于 12-27 18:40 • 97次 阅读 STM32CubeMX-串口开启DMA进行数据传输 STM8L051F3串口接收中断超时判断数据结束 目标:实现由PC的串口调试软件发一串任意长(小于512字节)的数据给STM8L051F3串口,然后S.... 发表于 12-27 18:31 • 66次 阅读 STM8L051F3串口接收中断超时判断数据结束 STM8 串口波特率与设置值不一致问题 最近调试STM8L串口通讯,发现设置了波特率115200,实际波特率却是57600。心想这种问题一般.... 发表于 12-27 18:30 • 85次 阅读 STM8 串口波特率与设置值不一致问题 STM8S001串口总进接收中断 无法发送数据 最小系统电路图参考我之前发的。出现上述问题可能缺少或错误以下几项的设置:1. IO口没有初始化GP.... 发表于 12-24 19:16 • 45次 阅读 STM8S001串口总进接收中断 无法发送数据 STM32串口的介绍与使用(原理、结构体、发送字符串、printf函数重定向) 串口相关知识1.定义2.通信概念3.USART简介串口的结构体串口发送字符(STM32发送到上位机).... 发表于 12-24 19:08 • 123次 阅读 STM32串口的介绍与使用(原理、结构体、发送字符串、printf函数重定向) STM32简易示波器 STM32简易示波器(串口接收AD采样)第一次写博客,也是想分享一下自己的心得体会。本人大三,因为准.... 发表于 12-24 19:00 • 72次 阅读 STM32简易示波器 Ubuntu16之STM32开发--点灯和串口通信 简介环境如下:硬件: NUCLEO-F767ZI 板初始配置: STM32CubeMX (HAL库).... 发表于 12-24 18:57 • 66次 阅读 Ubuntu16之STM32开发--点灯和串口通信 FreeRTOS+STM32F103串口通信错误解决方法 在调试FreeRTOS系统时,在串口中断中用队列存储数据,然后再定时器中断中用队列接收数据,并通过串.... 发表于 12-24 18:45 • 75次 阅读 FreeRTOS+STM32F103串口通信错误解决方法


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有