rtthread:f1代码移植
本文开始移植rtthread的代码到正点原子的板子上;参考资料为野火的教程,需要搭配野火教程使用;
使用源码是作为pack包放在arm-keil官网下载的nano3.0.3版本;nano版本精简方便解构;gittee上的master版本组件又多又杂不利于初学;
本来想用3.1.5版本源码的,但是移植过程会有代码报错又莫名其妙好了,等3.0.3移植完后再试试吧;
在开始移植rtthread源码前,先看看rtthread源码的文件分布;
1 源码文件
1.1 bsp: rtthread移植好的厂商评估板的完整例程文件夹,以及board.c和rtconfig.h;保留board.c和rtconfig.h即可;
1.2 components RT-Thread 的各个组件代码,例如 finsh,gui 等;
1.3 docs: 没啥;
1.4 include: RT-Thread 内核的头文件;
1.5 libcpu: arm架构和risc-v架构下各类的内核代码;复制用的那一个xx.s以及cpuport.c就可以了,多了报错;
1.6 src: RT-Thread 内核的源文件;
2 代码修改
board.c中systick相关都删掉,然后rt_hw_board_init()中改用SysTick_Config()函数;然后加个board.h头文件;
rtconfig.h修改了栈空间为256->512,ststick参数的周期为100->1000;就修改完了rtthread源码配置;
每次记录删删改改感觉太冗余了,之后每章节代码修改通过查看提交记录可知;
rtthread_f1demo: 将rtthread nano3.0.3版本移植到stm32f1上; (gitee.com)
3 rt_kprintf()串口调试
//rtthread.h中,这里是ifndef,不是ifdef;这个字符串的参数居然是用(...),第一次见,先放着; #ifndef RT_USING_CONSOLE #define rt_kprintf(...) #define rt_kputs(str) #else void rt_kprintf(const char *fmt, ...); //定义了RT_USING_CONSOLE,所以执行的是这里的两个函数; void rt_kputs(const char *str); #endif
//if定义了RT_USING_DEVICE设备驱动,则使用设备函数;else使用rt_hw_console_output()函数;所以使用的是rt_hw_console_output(); void rt_kprintf(const char *fmt, ...) { va_list args; rt_size_t length; static char rt_log_buf[RT_CONSOLEBUF_SIZE]; va_start(args, fmt);//1 这个函数也不知道啥用,先放着不管; /* the return value of vsnprintf is the number of bytes that would be * written to buffer had if the size of the buffer been sufficiently * large excluding the terminating null byte. If the output string * would be larger than the rt_log_buf, we have to adjust the output * length. */ //2 这个rtthread的字符处理函数老是看不懂,之前那个rt_object对象的字符处理函数也是一样看不懂;先放着吧; length = rt_vsnprintf(rt_log_buf, sizeof(rt_log_buf) - 1, fmt, args); if (length > RT_CONSOLEBUF_SIZE - 1) length = RT_CONSOLEBUF_SIZE - 1; #ifdef RT_USING_DEVICE if (_console_device == RT_NULL) { rt_hw_console_output(rt_log_buf); } else { rt_uint16_t old_flag = _console_device->open_flag; _console_device->open_flag |= RT_DEVICE_FLAG_STREAM; rt_device_write(_console_device, 0, rt_log_buf, length); _console_device->open_flag = old_flag; } #else rt_hw_console_output(rt_log_buf); //这个函数的弱声明也在kservice.c中,重写在board.c中; #endif va_end(args); //3 这个函数也不知道啥用,先放着不管; } RTM_EXPORT(rt_kprintf); //4 这个函数也不知道啥用,先放着不管; #endif //rtdef.h 不知道啥用,先放着; #define va_start(v,l) __builtin_va_start(v,l) #define va_end(v) __builtin_va_end(v)
//使用usart1作为rt_kprintf() output,函数弱声明在kservice.c中;与单片机串口1的重定向并不冲突; //这个函数是可以被打断的,通过静态变量rt_scheduler_lock_nest来统计嵌套的次数; void rt_hw_console_output(const char *str) { rt_enter_critical(); //"..."字符串的数据末尾会自动添加 "\0" ,所以可以通过"\0"来判断字符串是否结尾; while (*str!='\0'){ if (*str=='\n'){ USART_SendData(USART1, '\r'); while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE)) ; } USART_SendData(USART1, *str++); while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE)) ; } rt_exit_critical(); }
4 main函数扩展
MDK编译器中当原函数已经封装成外部库函数不可修改,或者已经编码烧录在rom中的时候,可以通过标识符对原函数进行打补丁操作;
在rtthread中使用其对main函数进行了补丁操作,扩展了main函数,在调用main函数之前先初始化了rtthread系统;
4.1 函数补丁demo
4.1.1 ¥Super$$:标识函数原型,目的是标识函数原型给编译器回调;(格式问题此处用¥代替了$,具体格式见代码;)
4.1.2 ¥Sub$$: 标识函数原型的补丁函数,编译器会将补丁函数放到原型函数之前或之后执行;
4.1.3 被标识函数及原型函数需要是全局函数或者弱声明函数,且被标识函数需要是代码编译的时候就编译好的静态链接,不能是实时执行的动态链接;想想也知道啦;
extern void ExtraFunc(void); //补丁操作; extern void $Super$$foo(void): void $Sub$$foo(void) { ExtraFunc(); //补丁操作; $Super$$foo(); //通过调用$Super$$foo()去回调原函数; }
4.2 函数扩展main( )
以下为rtthread中main函数扩展的逻辑流程;
那么问题来了,这个main_thread_entry( )函数是什么时候从优先级表中删除的呢?他要是没有从优先级表中移除那岂不是会一直调用main函数?
这个问题先放着;
//components.c 在执行main函数之前会先调用$Sub$$main()函数,等会通过调用$Super$$main()函数回到main函数; #if defined (__CC_ARM) extern int $Super$$main(void); //通过$Super$$来标识函数扩展函数原型是main函数; int $Sub$$main(void) //通过$Sub$$对main函数进行打补丁操作; { rt_hw_interrupt_disable(); rtthread_startup(); return 0; } #elif defined(__ICCARM__) //.... #elif defined(__GNUC__) //... #endif //components.c 初始化了$Super$$main()函数所在线程,定时器线程,空闲线程,然后启动switch_to调度函数; int rtthread_startup(void) { rt_hw_interrupt_disable(); rt_hw_board_init(); //NOTE: please initialize heap inside board initialization //rt_show_version(); rt_system_timer_init(); rt_system_scheduler_init(); #ifdef RT_USING_SIGNALS rt_system_signal_init(); #endif rt_application_init(); //main_thread_entry的节点放入优先级表中、悬起线程; rt_system_timer_thread_init(); //timer定时器初始化; rt_thread_idle_init(); //idle空闲线程初始化; rt_system_scheduler_start(); //启动调度函数switch_to汇编; /* never reach here */ return 0; } //components.c #ifndef RT_USING_HEAP ALIGN(8) static rt_uint8_t main_stack[RT_MAIN_THREAD_STACK_SIZE]; struct rt_thread main_thread; #endif void main_thread_entry(void *parameter) { extern int main(void); extern int $Super$$main(void); rt_components_init(); #if defined (__CC_ARM) $Super$$main(); //回调执行main函数; #elif defined(__ICCARM__) || defined(__GNUC__) main(); #endif }
5 空闲线程
空闲线程作为rtthread系统的资源回收线程,主要是作为线程的删除回收工作;空闲线程是唯一不允许阻塞运行的线程;
//idle.c 在补丁main函数中的idle初始化函数; void rt_thread_idle_init(void) { /* initialize thread */ rt_thread_init(&idle, "tidle", rt_thread_idle_entry, RT_NULL, &rt_thread_stack[0], sizeof(rt_thread_stack), RT_THREAD_PRIORITY_MAX - 1, 32); /* startup */ rt_thread_startup(&idle); } //idle.c idle的线程函数; static void rt_thread_idle_entry(void *parameter) { while (1) { #ifdef RT_USING_IDLE_HOOK if (rt_thread_idle_hook != RT_NULL) { rt_thread_idle_hook(); } #endif rt_thread_idle_excute(); } }
//idle.c //1:rt_list_remove(&thread->tlist); 2:rt_thread_exit(); 3:如果是RT_Object_Class_State那么就不释放内存了; //4: RT_KERNEL_FREE(栈); 5:rt_object_delete从容器中移除object_list,然后释放thread结构体内存; void rt_thread_idle_excute(void) { /* Loop until there is no dead thread. So one call to rt_thread_idle_excute * will do all the cleanups. */ while (_has_defunct_thread()) { rt_base_t lock; rt_thread_t thread; #ifdef RT_USING_MODULE rt_module_t module = RT_NULL; #endif RT_DEBUG_NOT_IN_INTERRUPT; /* disable interrupt */ lock = rt_hw_interrupt_disable(); /* re-check whether list is empty */ if (_has_defunct_thread()) { /* get defunct thread */ thread = rt_list_entry(rt_thread_defunct.next, struct rt_thread, tlist); #ifdef RT_USING_MODULE /* get thread's parent module */ module = (rt_module_t)thread->module_id; /* if the thread is module's main thread */ if (module != RT_NULL && module->module_thread == thread) { /* detach module's main thread */ module->module_thread = RT_NULL; } #endif /* remove defunct thread */ rt_list_remove(&(thread->tlist)); /* lock scheduler to prevent scheduling in cleanup function. */ rt_enter_critical(); /* invoke thread cleanup */ if (thread->cleanup != RT_NULL) thread->cleanup(thread); #ifdef RT_USING_SIGNALS rt_thread_free_sig(thread); #endif /* if it's a system object, not delete it */ if (rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE) { /* unlock scheduler */ rt_exit_critical(); /* enable interrupt */ rt_hw_interrupt_enable(lock); return; } /* unlock scheduler */ rt_exit_critical(); } else { /* enable interrupt */ rt_hw_interrupt_enable(lock); /* may the defunct thread list is removed by others, just return */ return; } /* enable interrupt */ rt_hw_interrupt_enable(lock); #ifdef RT_USING_HEAP #if defined(RT_USING_MODULE) && defined(RT_USING_SLAB) /* the thread belongs to an application module */ if (thread->flags & RT_OBJECT_FLAG_MODULE) rt_module_free((rt_module_t)thread->module_id, thread->stack_addr); else #endif /* release thread's stack */ RT_KERNEL_FREE(thread->stack_addr); /* delete thread object */ rt_object_delete((rt_object_t)thread); #endif #ifdef RT_USING_MODULE if (module != RT_NULL) { extern rt_err_t rt_module_destroy(rt_module_t module); /* if sub thread list and main thread are all empty */ if ((module->module_thread == RT_NULL) && rt_list_isempty(&module->module_object[RT_Object_Class_Thread].object_list)) { module->nref --; } /* destroy module */ if (module->nref == 0) rt_module_destroy(module); } #endif } }
6 小结
至此,rtthread系统初始化完成,可以使用串口1调试,在main函数中创建线程,然后将线程挂载到优先级表后即可运行;
所以那些线程能够一直存在是因为有循坏加阻塞延时;如果线程里没有循坏,执行完就被idle删除了,比如$函数;
rtthread_f1demo: 将rtthread nano3.0.3版本移植到stm32f1上; (gitee.com)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?