STM32:rtthread_f1移植

发布时间 2023-07-10 23:33:01作者: caesura_k

本文开始移植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系统初始化完成,可以使用串口1调试,在main函数中创建线程,然后将线程挂载到优先级表后即可运行;

  rtthread_f1demo: 将rtthread nano3.0.3版本移植到stm32f1上; (gitee.com)