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删除了,比如$Submain$$函数;
rtthread_f1demo: 将rtthread nano3.0.3版本移植到stm32f1上; (gitee.com)