一、RTT概述
前言:
RTT有三个版本,分别为Nano版本,标准版本,Smart版本,具体区别可以参考RT-Thread文档中心,我就简单说一下。
1.Nano版本
Nano版本其实就是一个极简版的RT-Thread,它就像其它RTOS比如FreeRTOS,uCOS一样,仅仅是个内核,包含了线程管理等基本功能,体积较小。
2.标准版
而标准版在在内核上还有丰富的组件,软件包可以拓展。
比如说常用的组件有AT组件,这个组件可以很方便操作一些基于AT指令的物联网设备,比如常用的esp8266。如图,只需要选中AT组件,AT组件的源文件,宏开关就会自动被添加进工程。这个图形配置界面后面工程创建的时候会讲。
选中前后工程目录对比,选中的AT组件被添加进来了。
如下图,相关的宏开关也添加到rtconfig.h配置文件里了。
那问题来了,Nano版本就不能用添加这些组件,软件包功能了吗?我觉得是可以的,因为都是ANSI C写的(我猜的),尽管许多C标准(C99,C11之类),但后面的C标准都是在ANSI C(C89)上拓展的,理论上ANSI C完全可以兼容其它C标准。所以我们可以使用图形界面配置好后,手动将多出来的文件和宏开关搬到Nano版本,具体的我还没验证,后面再细说。
3.Smart版本
最后一个Smart版本就不说了,因为这是给带MMU的CPU用的,MCU用不了。
4.版本选择
4.1 版本概述
有关版本选择的详情可以看这里:rtthread有哪些版本。我就简单说一下自己的理解,Nano版本最新是3.1.5,rtthread仓库最新发布版本是4.1.1。而我推测nano3.1.5版本其实就是标准版3.1.5去除一些组件软件包。可以通过github的发布历史和nano的更新历史推测,我们先看看rtthread分支图(发红的代表会长期维护,其它的就不会了,分支图有些旧了):
而nano3.1.5应该就是lts-3.1.x分支下的lts-3.1.5青春版(阉割版)。再来对比rtthread与rtthread-nano仓库的更新历史,如下左图rtthread的3.1.x分支最新版本是3.1.5,如下右图,再看看发布时间:2021/6/8
接下来再看看rtthread-nano的更新历史,2021/6/28更新了3.1.5。
所以nano-rtthread3.1.5版本就是rtthread3.1.5的青春版,除组件和软件包部分,其它没有区别。
4.2 不同版本ROM,RAM资源占用
开发工具的问题后面再说,芯片是RISC-V内核的CHV307,我们先用RT-Thread Studio依次标准版4.0.4工程,再用MounRiver Studio生成nano/标准版3.1.3工程(其实说是标准版只是比nano版多了一个设备驱动)。别问我为什么不都用RT-Thread Studio,因为它没有chv307的nano版对比他们的资源占用。
虽然是不同的IDE,但是编译工具链都是GNU MCU RISC-V GCC。
标准版4.0.4:
换算一下:FLASH: 26.6KB RAM: 7.85KB
至于为什么FLASH占用3.1.3反而比4.0.4多了2KB,其实也不奇怪,3.1.x与4.0.x已经是两个不同的分支了,而且更新过程中删除修复一些东西,添加一些东西挺正常的。大家感兴趣的可以看看这个:3.1.x到4.1.0对比。要是有知道的,欢迎交流。
补充:
这里之前RT-Thread Studio显示rtthread版本是latest,我还以为是4.1.0,结果后来发现是4.0.4,那个对比我就懒得再换了。
结论
暂时先用MounRiver Studio的3.1.3标准版本开发,后面再尝试迁移到3.1.5或4.1.x
二、工程创建
前言
创建工程的方法有很多:手动移植,Env工具,RT-Thread Studio, STM32CubeMX(stm32系列),MounRiver Studio(ch32系列),还有直接找到移植好的BSP修改。方法很多,我就主要讲讲RT-Thread Studio和MounRiver Studio,想用MDK/IAR的可以参考这个:Env 用户手册。
1.RT-Thread Studio
首先下载安装,老生长谈了,就不再赘述,RT-Thread Studio 下载地址。这里也有介绍,RT-Thread Studio快速上手。我就讲讲工程目录结构,和通过图形化配置后是如何反应到工程源码里的。首先新建一个CH32V307评估板的工程。手里没有板子的可以创建一个QEMU模拟器工程,有兴趣的可以研究一下。
工程创建好后,目录结构如图:
然后用户文件在applications下,这是最基本的,board下放了一些板子初始化的代码,注意这里的初始化是给rtthread用的,我们不用管。figures的图片,不用理他。libcpu放的rtthread上下文切换的代码,非常底层,我们也不用管。libraries/hal_libraries放的是ch32的外设标准库和启动代码,不用管。
libraries/hal_drivers放的是rtthread设备模型驱动,其实就是再把标准库再包装一层,这层包装是统一的open,write,read,close之类的接口,包装后的好处是应用和驱动分离,兼容性极高,比如我要用ch32的串口1发送一个字符ch,标准库的话可以用
USART_SendData(USART1, ch);
它本质上是向串口1的数据寄存器写数据
USART1->DATAR = ch;
这样比较简单直接,但兼容性不好,如果我们换了一颗完全不同的芯片去实现同样的功能,那它标准库接口跟USART_SendData名字不一样,寄存器也不一样。而串口1用的又特别多,那我们就有的改了。
而rtthread提供的IO设备模型提供了一个框架来统一操作IO设备接口,兼容性好。
还是串口1发送一个字符ch,可以用
rt_device_write(uart1_device, 0, &ch, 1);
第一个参数是设备,可以是任何设备串口,SPI,IIC,SD卡等等。我们都只需要调用这一个接口就够了,当然,rt_device_write这是应用层,uart1_device这个串口1设备是怎么回事,为什么我们传入这个参数就可以向串口1写数据了,这就属于驱动层了。应用层的接口我们只需要使用它就好,不需要修改任何东西。
但驱动层不同,我们凭什么向应用层传了一个uart1_device就可以了,原因在于这个uart1_device是要我们自己根据芯片厂商提供的芯片手册和SDK实现的,比如这个uart1_device属于串口设备,那它就具有一个putc函数来发送字符,而rt_device_write就是调用uart1_device下的putc函数发送字符,那这个putc函数就要我们来实现这个回调函数了。简化一下代码就是这样的:
static int ch32_putc(char c)
{
USART1->DATAR = c;
return 1;
}
在把ch32_putc赋值给putc:
putc=ch32_putc;
当然,实际还要复杂很多,我都是简单的一些说明,感兴趣的可以看看这个:RT-Thread IO设备模型 其它我们还要实现串口设备uart1_device的初始化,中断传输,DMA传输等等,这些都实现之后再把uart1_device注册进IO设备模型框架,我们就可以自由的使用rt_device_write等一系列统一的应用层接口啦。
说了这么多,是不是觉得如果还是要自己实现那些驱动,那还不如直接标准库呢。错啦,我说IO设备模型更方便是因为市面上常见的芯片驱动RT-Thread官方或者其它仓库贡献者已经实现好了,我们用的时候只需要简单的通过图形化配置打开或者关闭就好了。但很不幸,我用的这颗ch32v307vct6好多设备驱动都没有,想用还得自己写驱动,还是等学有余力的时候再为仓库做贡献吧(笑)。
说了这么多,我们利用RT-Thread Studio图形化配置来开启一个IIC设备驱动。我们先看看未开启前的工程状态,首先如下图,通过rtconfig.h和RT-Thread Setting我们可以看到这个工程模板已经默认启用了串口驱动和GPIO驱动。
我们再来看看工程目录,如下图,hal_drivers目录目前只有drc_gpio.c和drv_uasrt.c两个源文件。而components里目前也只有pin.c和serial.c两个设备框架。
接下来我们通过RT-Thread Setting开启IIC设备驱动,如下图。
先看到rtconfig.h多了RT_USING_I2C宏。
再看看工程目录(下图),Amazing, 什么也没多!正常情况会多一个drv_i2c.c的啊,但是为什么没有呢,我前面说了,ch32v307的很多设备驱动都没有,要自己实现。我们再来看看components,果然多了一个IIC框架。
好,RT-Thread Studio的目录结构说完了,下面来说说MounRiver Studio。
还有使用RT-Thread Studio开发ch32v307有个bug,就是下载程序进去没反应,这是链接脚本有问题,是RT-Thread Studio针对ch32v的下载工具有问题,需要操作一下,这在我后面的踩坑记会细说。但这不是我不用RT-Thread Studio的主要原因,主要是用RT-Thread Studio给ch32v系列烧录程序会跳出来一个烧录界面,非常反人类,而MounRiver Studio会抛出一个进度条,就非常友好。
2.MounRiver Studio
这款IDE和RT-Thread Studio一脉相承,都是源于eclipse这个软件,操作方式大同小异,目录结构与RTT IDE略有差别。打开软件,首先File->new,就可以选择芯片和RTOS了,不过这里生成的RT-Thread是3.1.3版本的。
目录结构与RTT IDE差不多,我就浅说一下。
Core,Peripheral -> risc-v内核,标准库
Ld -> Link.ld链接脚本
Startup -> 启动文件
Debug -> 沁恒写的printf重定向和滴答定时器初始化,用的不多
drivers -> 设备驱动
obj -> 编译过程文件与镜像烧录文件
rtthread -> rtt源码
编译有个警告,跳到警告处,改一下就好
4.0.x是这么改的
if (obj) /* skip warning when disable debug */
{
RT_ASSERT(obj != object);
}
或者
struct rt_object *obj RT_UNUSED;
好了,这里的下载烧录没有任何问题,而且下载的时候是个进度条,不会跳出个界面让二次操作(这里再次diss RTT Studio)。
结论
在RTT Studio那个BUG(我后面的踩坑记有说)还有下载进度条没修复和更新的话,暂时我是先用MounRiver Studio,当然仅限ch32v系列,后面的演示也大都基于MRS的了。这里有人发现更新了的话记得踢我一下(笑)。
三、踩坑记
1.RT-Thread Studio烧录ch32v307问题
用RT-Thread Studio向ch32v307vct6烧录程序,有个前置条件要完成。
1.1坑中坑(这部分可以看看,不要跟着操作)
要先用沁恒的烧录工具WCHISPStudio将ch32v307vct6的ROM改为224K,RAM改为96K,
参考数据手册可以发现ch32v307的FLASH和RAM是可以自己配置大小的,第一次见。
用WCH-LINK配置是不行的(后来我顿悟了,WCH-LINK是可以直接配置的,非常简单,坑),要先把板子BOOT脚拨到从系统存储器启动,
然后可以通过芯片的USB或串口1用WCHISPStudio下载程序,我们仅仅修改下FLASH为224K,RAM为96K。之后就可以用RT-Thread Studio烧写程序了。将BOOT设置好,再将板子USB或者串口1连到电脑,点击WCHISPStudio搜索,就可以发现设备和查看FLASH了,如图
然后,然后。。。就不写了,说多了都是坑。
1.2踩坑顿悟
经历了前面的坑中坑,我发现用RT-Thread Studio向ch32v307烧录程序其实很简单,只要避开一个点就行。
我们先用RT-Thread Studio新建一个ch32v307的模板工程
这是模板main线程
int main(void)
{
rt_kprintf("MCU: CH32V307\n");
rt_kprintf("SysClk: %dHz\n",SystemCoreClock);
rt_kprintf("www.wch.cn\n");
LED1_BLINK_INIT();
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
while(1)
{
GPIO_SetBits(GPIOA,GPIO_Pin_0);
rt_thread_mdelay(500);
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
rt_thread_mdelay(500);
}
}
然后打开链接脚本link.lds看看FLASH和RAM大小
可以看到FLASH:224K RAM:96K,这里是根据自己手里的ch32v307修改的,因为ch32v307有好几种存储分配方式。
这个链接脚本没有任何问题,我们先编译下程序,再点击下载,会跳出一个界面
我们可以在这里用Get和Set按钮查看和修改芯片的存储分配,可以看到我的芯片现在存储分配实际256K ROM+64K RAM,和链接脚本里的224K ROM+96K RAM不同,所以你刚刚在RT-Thread Studio编译程序得到的镜像(bin,hex等)无论通过那种途径烧录到了芯片都是无效的!
我们想要程序起作用,要么按芯片实际存储分配修改链接脚本link.lds,要么根据link.lds设置芯片存储分配,我这里就直接修改link.lds了,
想修改芯片配置的直接在刚刚跳出的下载界面通过下拉框选择,在点击Set就好,非常简单。
然后我们再次编译程序点击下载界面的下载按钮,
有意思的来了,你会发现控制台输出空空如也!无论再怎么狂按复位间它都无动于衷。
没错,这就是我最开始说的那个BUG,这时候就要上WCH-LinkUtility了,可以到沁恒的官网下载,我们打开它,轻轻点击一下Get或者其它按钮,总之让WCH-LinkUtility与芯片进行一次通讯就行,程序就正常运转了,控制台有了输出,复位键也正常了,皆大欢喜!
控制台输出:
但当我们再次通过RT-Thread Studio下载程序后,芯片又没反应了。
所以,BUG确定了,是RT-Thread Studio那个下载工具的问题,如果通过WCH-LinkUtility或者其它工具下载,都是一切正常。
1.3 总结与吐槽
总结就是用RT-Thread Studio编译得到镜像文件,再通过其它烧录工具(WCH-LinkUtility/WCHISPStudio)下载程序,或者换IDE比如MounRiver Studio。这个问题仅限ch32。
希望有人能早日解决这个问题吧,还有能不能不要下载程序的时候跳出一个下载界面让用户二次操作吗,太反人类了,直接给一个进度条就好啦。
2.rtthread浮点打印问题(master分支已无该问题)
(目前master分支已无该问题,RT_ALIGN_SIZE默认为8了)主要讲讲MounRiver Studio下的浮点打印(RTT Studio)问题,RT-Thread Studio的话我没试过,不过论坛里的一些vsnprintf替换rt_vsnprintf应该是不可行的(gcc下 ch32v会抛出线程错误,其它的keil或许可以,不过没试过)。MounRiver Studio比较特殊,网上的方法都不行,要么浮点打印失败,要么引发rtthread错误,中间试了很多方法,涉及到一些编译器C库之类的,过程就不赘述了,
直接说结论和方法
MounRiver Studio裸机开发ch32想printf浮点的话(ch32官方已经为我们实现了printf重定向,如果其它芯片比如stm32就要自己重定向一下),需要勾选一个选项,如图:
MounRiver Studio RT-Thread开发ch32想rt_kprintf或者printf浮点的话暂未解决
目前先是不用浮点,用的话就把整数和小数部分分开打印。
rt_kprintf打印浮点需要一个补丁包rt_vsnprintf_full
关于ROM占用和浮点解决方案(已提issue)可以看这里:
ROM占用
rt_vsnprintf_full浮点输出全0问题
浮点问题解决了,大佬回复很快啊(这里感谢大佬 @mysterywolf),将rtconfig.h里的字节对齐宏改为8,
#define RT_ALIGN_SIZE 8
之后如果想用printf的,就是要勾选我前面提到的Use wchprintfloat
想用rt_kprintf的,就要添加一个补丁包
rt_vsnprintf_full
那就大功告成了(亲测)
printf和rt_vsnprintf_full补丁包ROM都是8KB,所以不用纠结。
持续踩坑···
四、工程随笔
写在前面的话
因为现在要用rtt和ch32做个东西,下面是学习过程中的一些随笔,都是一些收获和未来的学习计划,会按需求学习和使用rtt,所以有的会更新,有的不一定。
堆内存最大化(完结)
前言
前面说了我用的MRS IDE,它生成的模板工程,默认堆大小是4KB,可以到board.c里查看
如果都是动态创建的话,这肯定是不够用啊,多几个线程就用光了
所以我决定把堆分配搞到最大化,先看看RTT Studio的ch32v307模板是怎么做的
好吧,RTT Studio是16KB,可能是够了,但有点不满足,再看看RTT Studio的STM32模板是怎么做的
了解STM32堆栈分配的同学肯定一样就看出来了(不了解的也没关系,马上的ch32我会出手,笑),没错,这就是我要的堆内存最大化!把bss段结尾作为堆起始地址,
RAM的最高地址处作为堆结尾地址。
CH32V和STM32的链接脚本略有不同,CH32V的栈结尾是放在RAM最高地址处的,所以我们不能像STM32那么做。
但也只要略微修改一下就好,下面是我理解的修改过程和原理,嫌麻烦的可以直接到后面的实操部分。
理论部分
我们先下载一下MRS的模板工程到芯片,用free命令看看修改前的堆内存,方便对比(RTT Studio一样操作)
可以看到堆内存总大小:4072 B 已使用:2468 B 最大使用:2468 B
接着我们打开链接脚本Link.ld文件看看ch32v的各个段是怎么分配的,
点开Link.ld(RTT Studio是link.lds),看看SECTIONS(段分配),鉴于篇幅有点大,我就把各个段做的事情删了(删掉的我会用……代替),仅保留待会我们要用的东西
/* 初始化段,程序的入口 _start 存放在该段 /
.init :{......} >FLASH AT>FLASH
/ 存放中断向量表 /
.vector :{......} >FLASH AT>FLASH
/ 代码段 */
.text :{......} >FLASH AT>FLASH
/我看不懂的段,反正都是>FLASH AT>FLASH/
......
/重头戏来了,RAM/
.data :
{
......
/这里这个__global_pointer我看不懂是干嘛的,有懂得前辈指导一下嘛/
PROVIDE( __global_pointer$ = . + 0x800 );
......
/*这里的PROVIDE提供的符号,我们可以在C程序里以取地址的方式获得值,
*我们待会改堆起始地址和堆结束地址就要用到PROVIDE提供的符号
/
PROVIDE( _edata = .); / _edata代表data段结尾地址 */
}>RAM AT>FLASH
/*RAM AT>FLASH含义
这里表示data段(已初始化的静态/全局变量)是从FLASH复制到RAM的(这个功能由启动文件 startup_ch32v30x.S完成),所以data段会占用镜像文件(FLASH空间)/
.bss :
{
......
PROVIDE( _sbss = .); / _sbss代表bss段起始地址 /
......
/ _ebss代表bss段结尾地址 ,我们可以用它作为我们的堆起始地址,当然,后面也提供了另外的
符号_end,end 都是一样的/
PROVIDE( _ebss = .);
} >RAM AT>FLASH
/*RAM AT>FLASH含义
*这里表示bss段(未初始化的静态/全局变量)是从FLASH复制到RAM的(这个功能由启动文件 startup_ch32v30x.S完成),但是未初始化的静态或全局变量没有初值,启动文件搬运的时候只要 从RAM划出一块内存全部填0就好,所以bss段不会占用镜像文件(FLASH空间)/
PROVIDE( _end = _ebss);/我刚刚提到的堆内存起始地址/
PROVIDE( end = . );
.stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
{
/ 堆结束 ORIGIN(RAM) + LENGTH(RAM) - __stack_size/stack
w我们用它来作为堆结束地址/
PROVIDE( _heap_end = . );
. = ALIGN(4);
PROVIDE(_susrstack = . );/ 栈底 ORIGIN(RAM) + LENGTH(RAM) - __stack_size */
. = . + __stack_size;/查看完整Link.ld会发现__stack_size=2048/
PROVIDE( _eusrstack = .);
} >RAM
程序注释我写的比较详细,有需要的可以看看啊。由此我们可以得出通过这个ch32v链接脚本所得到的镜像文件(elf,bin,hex之类)结构和RAM分配情况,首先镜像文件结构(这里应该不够完整,少一些Header,符号表之类的,不是不写,是我也没完全了解。有知道的欢迎补充)
然后是RAM被初始化后的结构
知道了RAM结构,接下来的事情就好办了,只要把堆起始地址改为link.ld提供的_ebss/_end /end就可以了,堆结束地址改为_heap_end/_susrstack。OK,理论部分结束,下面开始实操。
实操部分
打开board.c
我们先看看原始代码
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
#define RT_HEAP_SIZE (1024)
static uint32_t rt_heap[RT_HEAP_SIZE]; // heap default size: 4K(1024 * 4)
RT_WEAK void *rt_heap_begin_get(void)
{
return rt_heap;
}
RT_WEAK void *rt_heap_end_get(void)
{
return rt_heap + RT_HEAP_SIZE;
}
#endif
改为下面这个:
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
/* 最大堆大小开关*/
#define USING_MAX_HEAP_SIZE 1
#if (USING_MAX_HEAP_SIZE == 0)
#define RT_HEAP_SIZE (1024)
static uint32_t rt_heap[RT_HEAP_SIZE]; // heap default size: 4K(1024 * 4)
void *rt_heap_begin_get(void)
{
return rt_heap;
}
void *rt_heap_end_get(void)
{
return rt_heap + RT_HEAP_SIZE;
}
#else
void rt_heap_begin_get(void)
{
return HEAP_BEGIN;
}
void rt_heap_end_get(void)
{
return HEAP_END;
}
#endif / END OF USING_MAX_HEAP_SIZE/
#endif
打开board.h可以看到模板工程已经定义了HEAP_BEGIN和HEAP_END,
但是他这个不对,__stack_size的值应该以以取地址方式获得,而且SRAM_SIZE也被写成立64K,那如果我们后面修改ch32v的FLASH和RAM配置的话,还要多改一下这里,所以直接用我这个
extern int _ebss,_heap_end;
#define HEAP_BEGIN ((void *)&_ebss)
#define HEAP_END ((void *)&_heap_end)
修改完成后编译下载,使用free命令查看堆内存分配。
堆内存总大小:61568 B 大约60KB了.
大功告成!(擦汗)RTT Studio一样的操作,大家自己搞搞就行了。
原作者:初级踩坑仔