光伏云项目杂记02 – 构建Ringbuff(环形缓冲)1

在做这个项目的时候,遇到一个问题。我们使用的STMF103C8T6,资源很可怜,看看:

图1 STM32F103C8T6的参数

72M的频率,内存只有20K。而这个芯片需要做的事情:

1 3个串口的收发;
2 3个灯的控制,让用户通过灯的闪烁判断处于什么状态,方便维护;
3 GPRS模块的协议管理;
4 与逆变器端的协议管理;
5 看门狗的任务处理;
6  服务器端协议的处理,包含应用端口和配置端口;

以FreeRTOS为架构,估计用了近20个任务来完成这些工作。每个任务给的堆栈是0.5K,加上串口需要的缓冲区,一番操作下来,内存基本用光光。一起开发的兄弟,暂且称为烟枪吧(抽烟很快,1分钟一根,当得起这个称呼)。每天嚷嚷,说内存严重不够用,不能让客户再提新的需求了。虽然通过map文件计算了每个任务所用的栈空间,尽可能不浪费。不过再改的话,程序就直接溢出了,问题出在哪都不知道。

我很同意他的看法,也很同情我们的处境。可是,客户又不是我家的,他想提要求,我又控制不住。何况有些要求还是很中肯的,咱们都是讲道理的,对吧?

那怎么办?

分析源代码,我盯上了串口处理的这段代码。三个串口,除去串口接收发送所需要建立的任务外,每个串口还用了256字节的缓冲区。另外,程序写得耦合性比较差,估计换个芯片,所有代码都得重写。

程序逻辑是这样的:

  1. 将缓冲区分为两段,A段128字节,B段128字节;
  2. 串口接收启用DMA传输,不占用CPU资源;
  3. 当触发串口接收DMA中断,中断处理程序将缓冲区指向A段,接收数据;
  4. 接收完毕后缓冲区指向B段;
  5. 有一个任务专门监听,发现有数据立即取走;
  6. 新的数据来了,DMA接收中断处理程序用B段缓冲区取走数据,收完后缓冲区指针再指向A段。

也就是说,当监听任务在处理数据的时候,DMA接收中断使用了另外一段缓冲区,大家互不干涉(当然程序中也利用了FreeRTOS的互斥量来实现)。

这样实现其实没有太大问题,因为精心计算了串口和DMA的处理时间。接收串口的数据越长,越不会出现问题。

问题就出在小数据上。如果串口接收大量的小数据,频繁的触发中断,CPU就有可能丢失数据了。(实际运行中没有出现过,连续发送1个字节的情况,在客户环境中可认为概率是0)

烟枪和我商量了一段时间,我们决定使用ringbuffer(环形缓冲)来实现。他把这个任务轻飘飘的交给我了,调他的板子去了。

没办法,开始干活吧。 Ringbuffer的基本原理,我大概了解。维基百科中也有说明:https://en.wikipedia.org/wiki/Circular_buffer

A 24-byte keyboard circular buffer. When the write pointer is about to reach the read pointer – because the microprocessor is not responding, the buffer will stop recording keystrokes and – in some computers – a beep will be played.

我能用的资源有限。必须实现原有的串口接收,不能丢数据;同时还有提供尽可能简单的接口,供应用层的函数调用。

因此,我选择使用Contiki中的代码,用它的核心思想来构建这个架构。当时估计错误,我不觉得能有多难,本以为一周左右能搞定。没想到对芯片的DMA以及串口不那么熟悉,整整花了我三周时间。

这是一段痛并快乐的回忆。

68 total views, no views today

光伏云项目杂记01—modbus串口丢包了

和深圳某家公司合作光伏云的项目,我们主要做数据传输模块,对方负责开发云端,光伏逆变器外采。针对不同的应用场景,做了三款产品。来来回回用了一段时间,反响还不错,和其他同类产品相比还比较稳定。图一是其中一款用得较多的数据传输模块。

图1 物联网透传设备-gprs dtu

前一段时间现场的工程师报了一个问题,处理过程比较有趣,就记录了下来。

数据传输模块(也即数据透传模块,简称DTU)安装在逆变器上,通过串口把逆变器的数据接收过来,DTU通过GPRS(2.5G)转发给服务器。DTU最重要的是不能断网,任何情况下导致断网都必须重新连接后台,保证数据的连续性。

工程师报告,使用组态软件连接DTU(串口连接),把数据发送到服务器。数据是按包的方式定时发送的,每过5分钟会有一个包丢失。有多个设备通过组态软件连接DTU,也就是说有很多包丢失了。

接到报告后,我开始着手搭建环境,模拟现场。工程师使用的是modbus slave,我找他要来相应的版本软件。DTU与一台小的Mini台式机相连,在我自己的阿里云上运行自己写的网络调试工具。

数据包一个个发出去,几个小时下来,没有出现丢包的现象。把modbus slave换成串口调试助手,也没有出现丢包现象。我把数据包调大,大概200字节一次发送,这次出现了。

联系后台软件工程师,用他的云平台配合modslave以及DTU配合测试。其中DTU与台式机通过USB转串的设备连接,后面也尝试直接用串口线连接。经过20小时的测试,现象不大一致。使用USB转串设备进行测试的,仍旧有拆包现象;使用原生串口的,没有发现拆包现象。不过,对比了服务器的后台记录,基本没有拆包,也有命令包直接就丢失了。

百思不得其解。要后台的工程师配合我,协调起来很麻烦。我回到阿里云,用自己的网络软件模拟后台。同时把modbus slave撤了,用自己以前的代码写了个串口调试的工具,继续测试。这回,不管是usb转串的还是原生串口连接,都没有出现丢包拆包。光伏云项目中,数据获取是有后端服务器软件发起的,由其发送命令包,通过DTU传送给设备,设备再通过DTU发给服务器。在测试中还发现一个现象,如果服务器发送命令包向设备要求数据,时间频率如果设为每5秒发一次,丢包现象还会出现;设置为30秒一次,则机会没有出现丢包过。

看起来有好几个问题混杂在一起,产生了这样奇怪的现象。首先,排除DTU本身固件的问题,DTU中采用了DMA机制,以单片机的处理速度,这点串口数据不应该会处理不了。最大的问题应该有两个:

1 modbus slave和串口调试助手在串口读写的编程中,有哪个地方没有处理好,导致了拆包的现象;

2 至于丢包现象,和服务器端的软件有关。工程师可能习惯了平常的internet,没有考虑到GPRS的网络稳定性远低于有线或者wifi网络。不能过于频繁的去塞数据。

分析完了,寻找证据。第二个问题好解决,请后端工程师改下通讯频率就行了,回头一起测试。 至于第一个问题,只能反汇编modbus slave了。很幸运,用IDA反汇编后,我读了半小时代码,找到了问题所在,如图2。

图2 用IDA反汇编modbus slave

问题出在SetCommTimeouts函数。定位到SetCommTimeouts函数的调用位置,其前三个参数分别为ReadIntervalTimeout=0xffffffff;ReadTotalTimeoutMultiplier=0x00; ReadTotalTimeoutConstant=0x00。也就是说,modbus slave读取一次缓冲区,读操作就完成了。

这样操作是有点问题的,对照我自己的代码,把三个参数分别改为:0x64,0x0A,0x3E8。这些修改是把exe当做文件编辑的,相当于在字节码上直接修改了。

Modbus slave的软件hack完了,重新搭建环境。4个小时后,测试结果显示,修改后的modbus slave没有出现拆包丢包现象。