在上一次课中,只讲了UART的硬件协议,没有讲环形缓冲区。
本节课就讲解环形缓冲区。
环形缓冲区它就是一个数组,是一个长条形的缓冲区。
开始的时候读写位置都指向0:r = w = 0
,所谓读写位置就是数组的下标。
想想看,一开始的时候就是空的,那空是怎么判断的? if (r == w) 就是空
那么怎么写?写一个数据:
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
我们再来讲读的操作。
这个图里面,w的位置已经绕了一圈。
读的时候怎么读:
val = buf[r];
r = (r + 1) %len
去读出一个数据之后,要更新一下读的位置,读位置的更新,也要 % len
,r位置也会绕圈
这图里面,r的位置也要绕圈了.
所以什么叫环形缓冲区,你不断的写,不断的读,不断写不断的读,r,w会绕着跑好几圈。
对于at指令,我们也只是使用at指令,来使用外接的WiFi模块。
并不涉及WiFi模块里面深层次的知识,后面我们我们会编写串口程序来操作WiFi模块,就会用到环形缓冲区。
但比如说我接收到数据之后,我会马上就处理完,马上清空整个buffer,我自然就不需要环形缓冲区那么复杂。
AT指令在视频中已经讲解很详细了,有问题的学员,可以去论坛提问:百问网官网:点击答疑论坛进入
布置一下预习的视频和文档:
答: 是的,求模也是除法。
答: 对环形缓冲区,如果说只有一个消费者(读数据)、只有一个生产者(写数据)的话,就不需要做互斥操作。如果有多个人写数据、或者有多个人要使用数据,那就要做一些互斥的操作。
答: 在FreeRTOS里,是这样的:
正常来说,我们写入新的数据时应该写红色位置
写红色位置,就表示说你后面写入的数据呢,是到后面才读
先进先出的关系:FIFO( First in first out)
那如果说我有些数据非常紧急,我想把它写到最前面去: 就是图片上蓝色位置
这也是可以的, 这就是环形缓冲区的增强版
RTT的环形缓冲区的方向,是不是表示这个意思?我估计,我还没有去看到rtt的具体实现
答: 环形缓冲区可以大概率的避免数据的丢失,但是如果数据一下子来很多的话,无论什么算法都没有办法避免数据的丢失。
因为你分配了100兆的空间,我就跟你说:突然要来1000兆的数据,
你分配了1000兆的空间,我就跟你说:突然要来1T的数据。
绝对的、保证数据不丢失的方法是没有的。
我们写程序的时候,根据实际的使用情况,来确定buffer的大小。
答: 写数据的时候, 你可以判断:满的话就返回错误。
答: 在多任务系统中,读和写可以同时进行。对于多任务系统 ,本来就是任务可以同时运行嘛,假设有一个写任务、一个读任务,他们就是同时操作这个buffer。
答: 增加容错处理, 或者增加环形缓冲区的长度。
答: 首先串口数据的来源肯定是一个字符一个字符的接收,所以最底层的环形缓冲区肯定是单字节。
答: 满了就丢弃,我给大家贴一下代码。
上面的代码,是GIT仓库里面的,大家更新一下这个仓库就可以看到:
答: 一般出错的话,就是:
如果数据本来就那么多, 你就只能够从处理的效率上入手
比如说:
或者说:硬件设计上就要多次重传。
答: 你可以用其他办法,就比如说你在环形缓冲区中增加一个count变量。
根据这个count变量来分辨是空还是满,这样的话,这个环境缓冲区,满的时候就是真正的满了。
答: 比如我们的main里面,就可以一直读环形buffer,他一直读、等待你的输入,根据你的输入来操作。
答: 没什么区别,主要就是调整读和写的位置,可以从尾部回到头部。
答: 基本上没有,这本来就是软件上的概念,满了之后你可以返回错误。
答: 你可不要去移植Linux里面的那些kfifo,linux考虑的东西太全了,非常庞大。
答: 这要看你的设计,既然是缓冲区,就用来协调双方的。
如果说我的程序要处理数据非常快,你给的数据根本就不够处理,那我干嘛还要用环形缓冲区。
答: 也可以使用,主要是对于数据的发送,我们可以控制。
对于数据的接收,我们不知道数据什么时候来,所以使用环形缓冲区来接收数据。
答: 发送是丢数据?这个问题挺容易查:
答: 还是那句话,如果我收到一个完整的数据之后可以马上处理,马上清空buff,就不需要环形缓冲区
答: 可以,但是讲到smartconfig就偏离rtos的主线了。
答: MQTT不会讲,如果大家感兴趣的话,讲完RTOS时候,我们可以用mqtt来做一下实验,我们同事对mqtt了解挺多。
答: 不会变,static变量保存在data段。
答: 对于按键消除抖动,我们一般来说都会用到定时器。
在很多系统中,都是使用定时器来处理消抖。
在中断服务程序里面,他并不是马上去确定按键。
而是启动一个定时器,说:20ms后处理。
既然是抖动,就是说这个电平在不断的、快速的变化,多次产生中断。
每产生一个中断,都把定时器的时间往后推20ms。
最后一个中断产生时,他也会往后推20ms。
也就是说多次中断,他们最终只会触发一次定时器。
最后一次中断发生时,抖动已经消除。
最后一次中断,再过20毫秒,这个时候再去读按键。
答: FreeRTOS只是提供了多任务的功能,他并没有提供底层的驱动。也就是说对硬件的操作,跟你之前开发的裸机程序是完全一样的。rt-thread跟freertos的差别在于:rtt提供了驱动程序的框架。也就是说,如果别人为rtt写了驱动程序,你就可以直接拿来用
答: 我说的定时器是一般的定时器,不是引脚的定时器。有些芯片的引脚,它自带防抖动的功能。但是我讲了这个是一般的定时器,跟引脚没关系。
答: printf这个函数是你写的吗?不是,那他是谁提供的?就是Microlib。所以你要用printf等函数,必须勾选上这个库
答: 可靠性很好, 单纯的内核机制都很成熟了。