本篇博文主要用于记录在使用rt-thread系统中遇到的一些问题,经验,软件bug及使用技巧,长期更新。
rt-thread操作系统版本:3.1.2或3.1.3
1、2019.11.25 modbus软件包,mbrtu_m.c函数238行中/*RT_ASSERT(( eSndState == STATE_M_TX_IDLE ) || ( eSndState == STATE_M_TX_XFWR ));*/,此断言删除,由于Modbus主机在从机断开时,主机eSndState可能等于STATE_M_TX_XMIT,触发断言,导致程序死机。
2、特别 注意rt thread默认工程中,rtconfig.h中开启了RT_DEBUG宏,此宏会对程序中的断言进行使用,在发布程序中请关闭断言,一旦发布程序中发生了断言,程序的某个功能模块或任务将会死机。
3、软件定时的最大定时时间一定要注意,查看rt_timer_start()函数中代码,可以发现,RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);这条断言已经限制了最大的定时时间为0xffffffff/2个系统时钟tick,如果创建定时器时,超过此值,启动定时器时,程序在停止在这条断言中,为什么tick值只能达到一半呢,因为tick定义为有符号数,负值表示永久延时。
4、pipe.c中第35行的pipe_fops_open函数中,当申请到fifo内存失败时,要返回错误。同理rt_pipe_open函数也做同样的修改。
if (device->ref_count == 0)
{
pipe->fifo = rt_ringbuffer_create(pipe->bufsz);
/*申请内存失败时要返回错误 zhaoshimin 20191203*/
if(pipe->fifo == RT_NULL)
{
rt_mutex_release(&(pipe->lock));
return -1;
}
}
5、timer.c函数中在459行的rt_timer_control函数中增加一个查询定时器运行状态的功能。如下图所做的修改。当应用程序在使用定时器,有些情况需要查询定时器的运行状态,如果停止了就要启动,此时就需要增加的这个功能
6、STM32F103系列芯片的IWG独立看门狗,使用芯片内部的单独的40KHz时钟源,IWG功能在连接JLINK仿真器调试时,默认关闭不起作用,请注意。程序中增加看门狗设备后,喂狗程序建议创建一个次低优先级的线程用于喂狗,如果当应用程序中有任务挂掉后,喂狗线程无法运行,导致看门狗发生复位。空闲进程中也可以喂狗,使用钩子函数的方式,但是调用的喂狗程序太过频繁,所以不建议使用。
7、非阻塞socket通信 tcpclient.c函数中,原来官方提供的代码,存在着内存泄露问题,使用前请仔细的阅读代码,搞清楚逻辑,最后再商用。否则程序长时间运行后就无法联网了,我在实际应用中踩过坑。rtthread的其他的软件包,使用时也请好好阅读源码,并且做详细的测试,软件包由大家供现,每个使用者的用法又不太完全一样,难免有bug存在,使用时要特别注意。
8、物联网设备的无线通信测试方法,编写一个测试软件,通过设备的接入平台向设备发送数据。对于通信的测试方法:测试大量数据发送给设备,比如每隔10ms发送一包数据;测试发送大包数据,比如一包1000字节的数据;测试在当接入平台与设备间通信异常,比如无线信号弱时,发送数据在平台产生积压后,无线信号变好后,大量收到平台积压数据。以上三种极端测试都要求设备软件不死机,当异常数据消失后,还能正常接收数据并做出相应的处理。
9、sscanf函数的高级应用,sscanf函数可以实现从一个字符串中解析出指定的子字符串或数字,是做字符串处理时一个非常有用的函数,同时sscanf还支持正则表达式。sscanf最基本的功能可以查阅相关资料,这里重点介绍他支持的正则表达式。
%[a-z]表示选择小写字母, %[^a-z]表示选择非小写字母,^表示“非”, %*[0-9]表示跳过数字, *表示跳过。
sscanf("Hello word", "%s", str); str=Hello
sscanf("Hello word zsm","%*s%s", str ); str= word
sscanf("Hello word zSm", "%*[A-Z]%[a-z ]", str ) str=ello word z
sscanf("123 Entering Passive Mode (3489,456,678,90,45,87)", "%d%*[^0-9]%d,%d,%d,%d,%d,%d",&a,&b,&c,&d,&e,&f,&g); 提取这个字符串中的数字,结果为a=123, b=3489,c=456,d=678,e=90,f=45,g=87
特别注意sscanf("123 Entering Passive Mode (489,456,678,90,45,87)", "%d%*[^0-9]%d,%d,%d,%d,%d,%d",&a,&b,&c,&d,&e,&f,&g);这个函数在keil 5中运行的出来的结果是错误的,即其中b只取到了9。经过修改代码测试分析,keil中sscanf函数在执行这种%*[^0-9]时能成功的过滤了这个字符串中的空格,字母,括号,但是它会多过滤掉'48'这两个字符,导致提取出来的b值错误。如下图,我分析可能是keil中自带的sscanf有问题。
在keil中要正确提取这个字符串中数字的方法是使用:sscanf("123 Entering Passive Mode (489,456,678,90,45,87)", "%d%*[^(](%d,%d,%d,%d,%d,%d",&a,&b,&c,&d,&e,&f,&g);
10、有符号数和无符号数的比较大小时,编译器会默认把有符号数当成无符号数处理,这在比较一个负数和一个正数的大小时就出错了。在比较一个有符号数和无符号数时请对无符号做强制类型转换。
unsigned long a = 234;
signed char b = -2;
if(b < a)
{
printf("b < a");
}
else
{
printf("b > a");
}
/*以上的运行结果是 b < a 有符号数char b的值是-2,在32位处理器进行比较时中会扩展成32位有符号数据,即0xFFFFFFFFE,默认按无符号数进行比较 就是 b(0xFFFF FFFE) > a(234)*/
unsigned long a = 300;
signed char b = -2;
if(b < (signed long)a)
{
printf("b < a");
}
else
{
printf("b > a");
}
/*以上的运行结果是 b < a 有符号数char b的值是-2,在32位CPU中进行比较运算时的数据会自动扩展成32位有符号数据0xFFFFFFFE,对应的有符号数还是-2 ,按有符号数进行比较 就是 b(-2) < a(300)*/
11、memcpy函数,此函数根据输入的参数,输入的参数简单时,编译器会把这个函数直接优化成类似内联函数的方式,直接生成汇编不进行函数调用操作,如代码的示例。
unsigned char arr[10] = {1,2,3,4,5,6,8,9,10};
memcpy(&arr[2], arr, 5);
在KEIL5 ARM编译器下优化的结果如下图,可以看出,memcpy这个函数直接优化成4句汇编代码,汇编代码对内存的复制操作是按字(4字节)来复制的,所以memcpy函数运行后数组中的内容是{1,2,1,2,3,4,3,8,9,10}
12、rt thread 3.1.3 lwip协议的操作系统接口部分驱动, 调用netdev_dhcp_enabled(netdev, RT_FALSE)关闭dhcp功能后,再调用netdev_dhcp_enabled(netdev, RT_TRUE)打开DHCP功能时,无法再进行ip地址的获取的,解决办法在ethernetif.c程序的168行,底层调用dhcp功能处增加dhcp功能打开与关闭的功能。代码如下:
13、rt-thread 3.1.3 的select功能中poll.c, 由于poll调用底层的设备驱动poll函数,此函数返回错误时,会导致select返回1,产生错误的结果,需要修改poll中的相关函数来处理,一共修改2处,如下
14、sal_socket.c文件中的sal_accept函数没有处理netdev设备down的情况,增加上相关代码,如下
15、dhcp功能在打开的情况下无法关闭,修改ethernetif.c程序中的底层lwip_netdev_set_dhcp函数,增加对dhcp功能的操作。
16、 lwip 2.0.2协议在断开网线的情况下,执行关闭TCP连接的操作,停留在状态FIN_WAIT_1状态,
连接建立时拔下网线,在msh接口,执行命令tcpserver --stop 关闭开发板上的服务器程序连接。再输入netstate 查看lwip链接的状态,发现链接并不能马上关闭,如下图,链接处于FIN_WAIT_1状态,要过很长的时间才能关闭释放。经过查看lwip的源程序,tcp.c程序中的981行 tcp_slowtmr函数完成了tcp连接的超时释放处理,函数处理FIN_WAIT_1状态的链接,根据tcp连接关闭的4次握手操作,服务器(开发板)发送FIN_WAIT_1后,等待客户端(电脑)应答ACK进而转入FIN_WAIT_2,由于网线断开,服务器(开发板)程序收到不ack而处于FIN_WAIT_1状态。tcp_slowtmr函数中处理这个状态的超时是按照tcp数据重发逻辑进行的,即采用超时后指数级时间退让再进行下一次数据发送,这个时间总的算下来,可达10多个分钟或更久。
经过查看lwip网站,此问题已经相关开发者在去年就提出过,bug的网址:http://savannah.nongnu.org/bugs/ ... m_id=56161#comment3
开发者提出的修改建议并没有得到lwip作者的赞同,原因是作者认为这样修改会违背tcpip协议的标准,但是这种bug确实在实际应用中容易出现,并且不能允许这个tcp连接长时间不释放而无法建立新的连接。
我个人建议采用讨论中修改办法,即在 tcp_slowtmr函数中增加对FIN_WAIT_1状态的超时处理。如下图标示的修改方法。
17、lwip的应用层线程的堆栈要设置大一些,在正常网络连接建立的情况的堆栈的消耗达小于2048字节,当网线断开时,线程不断的尝试建议连接的时,堆栈的消耗更大,会稍稍大于2048字节,并且产生了堆栈溢出,导致这个应用线程死机,并且阻塞住了lwip协议内部使用的信号量,引起lwip协议卡死,网络不通了。所以建议堆栈设置为3072,即3KB的大小。
18、在解决kawaii mqtt协议的相关的bug时,采用正点原子阿波罗开发板,3.1.3版本的系统,lwip2.0.2, 未对mqtt协议中有部分的ack list, message list链表的操作未使用互斥信号量保护时,第一次连接服务器后,free显示的动态内存占用为22616,断开网络一次再连接上后动态内存占用为22620,增加了4个字节。后面再4次断开网络,连接网络后,动态内存占用为22620,增加了4个字节。当对协议中的未使用互斥信号量的地方增加信号量保护后,从第一次连网后,到后面断开网络,动态内存占用22616,保持稳定,看来互斥信号量保护全局变量会影响内存的占用。
19、STM32L431芯片的选项字节的FLASH_OPTR寄存器的位16设置为0时,即硬件看门狗,芯片的IWDG默认为上电启用,会使程序产生复位。需要通过修改选项字节来进行关闭,关闭的程序如下。
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
OBInit.OptionType = OPTIONBYTE_USER;
OBInit.USERType = OB_USER_IWDG_SW;
OBInit.USERConfig = FLASH_OPTR_IWDG_SW;
HAL_FLASHEx_OBProgram(&OBInit);
20、可重入,可多次实例化程序模块的编写方法
可重入程序模块的编写方法,却程序模块中不使用全局变量,可多次实例化的方法呢,我今天看kawii mqtt, web client程序包时豁然开朗,那就是程序中对于要使用的全局变量从调用者处输入,即程序模块内部对于全局变量采用动态内存分配的方式,返回指针给调用者。所有对于一种程序中支持2个mqtt协议的链接,只需要creat 2个mqtt协议的结构就可以,做为输入参数传输给mqtt协议。许多高级语言编程的内部的机制就是这样的一个原理来实现的,从底层技术通到了顶层了。