一、环形缓冲区
在上一次课中,只讲了UART的硬件协议,没有讲环形缓冲区。
本节课就讲解环形缓冲区。
环形缓冲区它就是一个数组,是一个长条形的缓冲区。
开始的时候读写位置都指向0:r = w = 0
,所谓读写位置就是数组的下标。
想想看,一开始的时候就是空的,那空是怎么判断的?if (r == w) 就是空
1.1 写操作
那么怎么写?写一个数据:
buf[w] = val;
w = w+1;
需要注意的是,当w到达数组的最右边时:你要防止w越界。
w = 6时,下一个位置是多少?那下一个位置应该是绕回来,变成0。
用数学表达式就这样:w = (w+1) % len
,即w = (6+1) %7 = 0
上图里面长度是7(0~6共7个),当w=6的时候,下一个w就是7(即0)。
这个计算长度的方法,有没有改进的方案?
在单片机里面,除法运算是非常消耗CPU资源的,并且还得添加除法库,太浪费flash了。
所以能不用除法,就不要用除法。
我们怎么做呢?
我们可以让长度, len = 2的n次方
,比如2、4、8、16、32。
这时候模的运算,就可以变成与的操作:
len = 8;
w % 8 就是 w & (8-1)
下面我来画图举例:
大家要用位运算来看这个问题,8-1=7,在二进制里面就是三个1,
val = 0~7时, val &7 都等于 val
val等于8时,val & 7 = 0
当然也可以使用if来判断,比如:
if (w++ >= 7)
{
w=0;
}
从效率上来说:他先判断,判断之后再去跳转,指令有多条,
如果使用位的清除操作的话,只需要一条指令,
但是现在芯片的运行速度都那么高,浪费一两条指令问题不大,主要还是使用位清除代码更漂亮。
另外,如果是直接溢出,也是可行,不过只能是指定长度,不实用。
比如,比如读写位置都用unsigned char类型:unsigned char r, w;
w =255;
w++后就是0,这个限制就很明显了,长度必须是256,
如果是unsigned int w的话,这个长度就必须是: 2^32。
缓冲区空的时候是:r == w
满的时候呢?如果真正地满了:
来看这图,假设w等于6时候, 还要写入一个数据:
写入数据之后,W就指向下一个位置,就是0
这个时候,即缓冲区满的时候,也是 r== w,空和满都是:r == w。
这样的话我们不好写程序判断,所以就退一步,满:下一个写位置 == r
我在写数据之前先判断一下: 下一个写的位置, 是不是等于读的位置,
如果下一个写的位置等于读的位置的话, 我就假设满了,
我宁愿空出一个空位, 就是为了方便写程序。
空: r==w
满 : (w+1)%len == r
1.2 读操作
我们再来讲读的操作。
这个图里面,w的位置已经绕了一圈。
读的时候怎么读:
val = buf[r];
r = (r + 1) %len
去读出一个数据之后,要更新一下读的位置,读位置的更新,也要 % len
,r位置也会绕圈
这图里面,r的位置也要绕圈了.
所以什么叫环形缓冲区,你不断的写,不断的读,不断写不断的读,r,w会绕着跑好几圈。
二、AT指令
对于at指令,我们也只是使用at指令,来使用外接的WiFi模块。
并不涉及WiFi模块里面深层次的知识,后面我们我们会编写串口程序来操作WiFi模块,就会用到环形缓冲区。
但比如说我接收到数据之后,我会马上就处理完,马上清空整个buffer,我自然就不需要环形缓冲区那么复杂。
AT指令在视频中已经讲解很详细了,有问题的学员,可以去论坛提问:百问网官网:点击答疑论坛进入
三、预习安排
布置一下预习的视频和文档:
四、晚课学员提问
1. 问: %也是使用除法吗?
答: 是的,求模也是除法。
2. 问: 环形缓冲区操作中,不用做互斥吗?只能一对一?
答: 对环形缓冲区,如果说只有一个消费者(读数据)、只有一个生产者(写数据)的话,就不需要做互斥操作。如果有多个人写数据、或者有多个人要使用数据,那就要做一些互斥的操作。
3. 问: RTT的环形缓冲区,用一个位作为方向,是不是更优美?
答: 在FreeRTOS里,是这样的:
正常来说,我们写入新的数据时应该写红色位置
写红色位置,就表示说你后面写入的数据呢,是到后面才读
先进先出的关系:FIFO( First in first out)
那如果说我有些数据非常紧急,我想把它写到最前面去: 就是图片上蓝色位置
这也是可以的, 这就是环形缓冲区的增强版
RTT的环形缓冲区的方向,是不是表示这个意思?我估计,我还没有去看到rtt的具体实现
4. 问: 环形数组保存的是字符,如果我的串口 接收的是字符串,如果接收的一组字符串没有 处理完。被覆盖了怎么办?
答: 环形缓冲区可以大概率的避免数据的丢失,但是如果数据一下子来很多的话,无论什么算法都没有办法避免数据的丢失。
因为你分配了100兆的空间,我就跟你说:突然要来1000兆的数据,
你分配了1000兆的空间,我就跟你说:突然要来1T的数据。
绝对的、保证数据不丢失的方法是没有的。
我们写程序的时候,根据实际的使用情况,来确定buffer的大小。
5. 问: 假设环形缓存区是5,写入15个数据,怎么判断数据的正确性?
答: 写数据的时候, 你可以判断:满的话就返回错误。
6. 问: 串口的环形缓冲区的写和读是同时进行的,还是分别进行的?
答: 在多任务系统中,读和写可以同时进行。对于多任务系统 ,本来就是任务可以同时运行嘛,假设有一个写任务、一个读任务,他们就是同时操作这个buffer。
7. 问: 环形缓冲区中,被覆盖了怎么办?
答: 增加容错处理, 或者增加环形缓冲区的长度。
8. 问: 实际应用很少单字节读写的吧?
答: 首先串口数据的来源肯定是一个字符一个字符的接收,所以最底层的环形缓冲区肯定是单字节。
9. 问: 环形缓冲区满了怎么处理,读时会R==W就认为空了?
答: 满了就丢弃,我给大家贴一下代码。
上面的代码,是GIT仓库里面的,大家更新一下这个仓库就可以看到:
10. 问: 一般工程上的容错处理是怎么做?
答: 一般出错的话,就是:
- 数据来的太多
- 处理不过来
如果数据本来就那么多, 你就只能够从处理的效率上入手
比如说:
- 改进处理算法
- 在RTOS中,提高优先级
- 更换频率更快的芯片
或者说:硬件设计上就要多次重传。
11. 问: "我宁愿空出一个空位, 就是为了方便写程序",是不是意味着环形缓冲的能装的最大数据个数都是 最大长度-1个?
答: 你可以用其他办法,就比如说你在环形缓冲区中增加一个count变量。
根据这个count变量来分辨是空还是满,这样的话,这个环境缓冲区,满的时候就是真正的满了。
12. 问: 程序里面是什么条件的时候读缓冲区?
答: 比如我们的main里面,就可以一直读环形buffer,他一直读、等待你的输入,根据你的输入来操作。
13. 问: 环形缓冲区和读写一般数组有没区别?
答: 没什么区别,主要就是调整读和写的位置,可以从尾部回到头部。
14. 问: 环形缓冲区有没有什么满了触发中断之类的?
答: 基本上没有,这本来就是软件上的概念,满了之后你可以返回错误。
15. 问: 其实可以移植一个成熟的唤醒缓冲的程序,比如Linux中的kfifo或者别的?
答: 你可不要去移植Linux里面的那些kfifo,linux考虑的东西太全了,非常庞大。
16. 问: 一个mcu要用到3个UART需要几个环形缓冲区??
答: 这要看你的设计,既然是缓冲区,就用来协调双方的。
如果说我的程序要处理数据非常快,你给的数据根本就不够处理,那我干嘛还要用环形缓冲区。
17. 问: 串口数据发送为何不需要缓冲区?
答: 也可以使用,主要是对于数据的发送,我们可以控制。
对于数据的接收,我们不知道数据什么时候来,所以使用环形缓冲区来接收数据。
18. 问: 串口中断收发例程中串口发送丢数据根本原因是什么?收的不对吗。
答: 发送是丢数据?这个问题挺容易查:
- 确认数据是否写入硬件寄存器
- UART FIFO是否满了,导致写入无效。
19. 问: 那我做数据采集的时候那不就是必须要用环形缓冲区了吧?
答: 还是那句话,如果我收到一个完整的数据之后可以马上处理,马上清空buff,就不需要环形缓冲区
20. 问: esp8266 可以和手机直连互发消息么,为什么不采用这种形式,而采用连接同一个WIFI再发消息?
答: 可以,但是讲到smartconfig就偏离rtos的主线了。
21. 问: 会讲mqtt吗,之前做过这个连接阿里云?
答: MQTT不会讲,如果大家感兴趣的话,讲完RTOS时候,我们可以用mqtt来做一下实验,我们同事对mqtt了解挺多。
22. 问: static定义静态变量,定义的变量地址会不会改变?
答: 不会变,static变量保存在data段。
23. 问: 除了轮询消抖,还有其它消抖方式的吗?
答: 对于按键消除抖动,我们一般来说都会用到定时器。
在很多系统中,都是使用定时器来处理消抖。
在中断服务程序里面,他并不是马上去确定按键。
而是启动一个定时器,说:20ms后处理。
既然是抖动,就是说这个电平在不断的、快速的变化,多次产生中断。
每产生一个中断,都把定时器的时间往后推20ms。
最后一个中断产生时,他也会往后推20ms。
也就是说多次中断,他们最终只会触发一次定时器。
最后一次中断发生时,抖动已经消除。
最后一次中断,再过20毫秒,这个时候再去读按键。
24. 问: Freertos 这种操作系统,相对于单片机裸机是不是简单点?操作系统是不是调用现成的库或者函数?裸机要写到底层的驱动?
答: FreeRTOS只是提供了多任务的功能,他并没有提供底层的驱动。也就是说对硬件的操作,跟你之前开发的裸机程序是完全一样的。rt-thread跟freertos的差别在于:rtt提供了驱动程序的框架。也就是说,如果别人为rtt写了驱动程序,你就可以直接拿来用
25. 问: 按键消抖 是需要带有定时器功能的io吗 还是一般io就可以了?
答: 我说的定时器是一般的定时器,不是引脚的定时器。有些芯片的引脚,它自带防抖动的功能。但是我讲了这个是一般的定时器,跟引脚没关系。
26. 问: 讲到串口的fputc和fgetc时,视频里说一定要勾选MicroLib,是因为stdio.h这个头文件引用的东西都在MicroLib中吗?
答: printf这个函数是你写的吗?不是,那他是谁提供的?就是Microlib。所以你要用printf等函数,必须勾选上这个库
27. 问: rtos在工业应用的可靠性怎么样,经过那么多年的迭代,从内核和机制而言存在bug的可能性还大吗?大家都说在可靠性要求高的工业应用场合尽量使用裸机?
答: 可靠性很好, 单纯的内核机制都很成熟了。