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)

 

posted @ 2023-07-10 23:27  caesura_k  阅读(38)  评论(0编辑  收藏  举报