×
单片机 > 单片机程序设计 > 详情

STM32F103+RTT从零开始(二)——RTT系统中点亮LED

发布时间:2020-06-09 发布时间:
|

上一篇博客简单说了下如何使用Keil创建STM32F103的工程,并且完成了LED点亮,及让LED等闪烁的功能,那是诸多同学学习单片机的起手式。本篇博客继续上一篇博客的内容,依旧是点亮LED,不同的是,这次点亮LED等,是在RT-Thread操作系统中进行的。


创建工程

创建一个Keil工程,芯片依旧选择STM32F103C8T6,然后在Manage Run-Time Environment对话框中选择需要用的的软件组件,与上文不同的是,我们需要把RTT一起勾上。如下图: 


上图中,红线框中即为RTT操作系统的组件,分别为设备驱动,系统内核以及shell。蓝线框中为Keil的RTX操作系统。我们现在要用的是RTT,所以勾选RTT的组件即可,其中Kernel为必选项,device drivers依赖kernel,shell又依赖device drivers。


shell也提一下,shell强翻成中文就是命令行外壳,如同linux操作系统一样,RTT也提供了一套共用户在命令行操作的操作接口。RTT提供的这套接口叫做finsh,主要用于调试、查看系统信息。finsh支持两种模式:1. C语言解释器模式, 为行文方便称之为c-style;2. 传统命令行模式,此模式又称为msh(module shell)。 

在大部分嵌入式系统中,一般开发调试都使用硬件调试器和printf日志打印,在有些情况下,这两种方式并不是那么好用。比如对于RT-Thread这个多线程系统,我们想知道某个时刻系统中的线程运行状态、手动控制系统状态。如果有一个shell,就可以输入命令,直接相应的函数执行获得需要的信息,或者控制程序的行为。这无疑会非常方便。finsh就是基于此而设计,它运行于开发板,可以使用串口/以太网/USB等与PC机进行通信。


创建工程后,相对上一篇博客创建的工程,项目中会多出了RTT,如下图。至于各个文件及其作用,后续使用的时候再逐步理解。我们当前最需要关注的是board.c和rtthread.h两个文件。从图中可以看出,只有这两个文件上没有标注钥匙,有钥匙标注的是不允许更改,也就是我们能更改就是这两个文件。后面再分析这两个文件。且走下一步。 


编写点灯程序

创建好工程后,开始编写点灯程序了,与上篇博客一样,直接贴上代码:


#include "rtthread.h"

#include "stm32f10x.h"

#include "stm32f10x_gpio.h"


int main(){


    GPIO_InitTypeDef gpioInit;


    //打开GPIOB的时钟

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

    //LED上拉连接GPIOB 12引脚,所以设置如下,推挽输出,Pin12,2MHz输出速度

    gpioInit.GPIO_Mode=GPIO_Mode_Out_PP;

    gpioInit.GPIO_Pin=GPIO_Pin_12;

    gpioInit.GPIO_Speed=GPIO_Speed_2MHz;

    GPIO_Init(GPIOB,&gpioInit);


    while(1){

        //点亮LED

        GPIO_ResetBits(GPIOB,GPIO_Pin_12);

        //延时0.5s

        rt_thread_delay(RT_TICK_PER_SECOND/2);

        //关闭LED

        GPIO_SetBits(GPIOB,GPIO_Pin_12);

        //延时0.5s

        rt_thread_delay(RT_TICK_PER_SECOND/2);

    }

}


这样编写程序后,编译通过,烧写后却发现LED根本无法按照预期进行工作,这是因为我们还缺少工作没有做。 

打开board.c,可以看到它上面有几句注释,根据注释,修改如下:


#include

#include

#include "stm32f10x_rcc.h"


// rtthread tick configuration

// 1. include header files

// 2. configure rtos tick and interrupt

// 3. add tick interrupt handler


// rtthread tick configuration

// 1. include some header file as need

//#include


#ifdef __CC_ARM

extern int Image$$RW_IRAM1$$ZI$$Limit;

#define HEAP_BEGIN    (&Image$$RW_IRAM1$$ZI$$Limit)

#elif __ICCARM__

#pragma section="HEAP"

#define HEAP_BEGIN    (__segment_end("HEAP"))

#else

extern int __bss_end;

#define HEAP_BEGIN    (&__bss_end)

#endif


#define SRAM_SIZE         8

#define SRAM_END          (0x20000000 + SRAM_SIZE * 1024)



/**

 * This function will initial STM32 board.

 */

void rt_hw_board_init()

{    

    // rtthread tick configuration

    // 2. Configure rtos tick and interrupt

    //SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);

    SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);

    /* Call components board initial (use INIT_BOARD_EXPORT()) */

#ifdef RT_USING_COMPONENTS_INIT

    rt_components_board_init();

#endif


#if defined(RT_USING_CONSOLE) && defined(RT_USING_DEVICE)

    rt_console_set_device(RT_CONSOLE_DEVICE_NAME);

#endif


#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)

    rt_system_heap_init((void*)HEAP_BEGIN, (void*)SRAM_END);

#endif

}


// rtthread tick configuration

// 3. add tick interrupt handler 

// tickvoid SysTick_Handler(void)

// {

//  /* enter interrupt */

//  rt_interrupt_enter();

// 

//  rt_tick_increase();

// 

//  /* leave interrupt */

//  rt_interrupt_leave();

// }


void SysTick_Handler(void)

{

//  /* enter interrupt */

rt_interrupt_enter();

// 

rt_tick_increase();

// 

//  /* leave interrupt */

rt_interrupt_leave();

}


再次编译,烧写程序,LED开始闪烁。


RTT第一次分析

board.c修改后,程序就正常工作了。可是为什么呢?根据经验来说,C程序不是从main开始的么,board中的程序又是何时执行的呢?在main中我们有死循环,如果是从main开始执行的,那么board.c的函数就绝对不可能执行了。


为什么不是从main开始执行的

Ctrl+F搜索rt_hw_board_init函数。发现他在components.c中的rtthread_startup调用,再搜索rtthread_startup结构发现其调用如下:


#if defined (__CC_ARM)

extern int $Super$$main(void);

/* re-define main function */

int $Sub$$main(void)

{

    rt_hw_interrupt_disable();

    rtthread_startup();

    return 0;

}

#elif defined(__ICCARM__)

extern int main(void);

/* __low_level_init will auto called by IAR cstartup */

extern void __iar_data_init3( void );

int __low_level_init(void)

{

    // call IAR table copy function.

    __iar_data_init3();

    rt_hw_interrupt_disable();

    rtthread_startup();

    return 0;

}

#elif defined(__GNUC__)

extern int main(void);

/* Add -eentry to arm-none-eabi-gcc argument */

int entry(void)

{

    rt_hw_interrupt_disable();

    rtthread_startup();

    return 0;

}

#endif



在上面预处理指令有三段,分别判断三个宏是否被定义——__CC_ARM、__ICCARM__、__GNUC__,这三个是什么呢?如果全局搜索,会发现在core_cm3.h中它们出现很多次了。 

ARM 系列目前支持三大主流的工具链,即ARM RealView (armcc), IAR EWARM (iccarm), and GNU Compler Collection (gcc),这三个就是用来指示当前使用的是哪个工具链。因为我们使用的就是RealView(Keil)了。 

可以看到$Super$$main和$Sub$$main,这又是什么呢? 

在某些情况下,无法修改现有符号,例如,由于符号位于外部库或 ROM 代码中。为了解决这个问题,Keil提供了使用 $Super$$ 和 $Sub$$ 模式来修补现有符号的方法。 $Super$$标识的是原函数, $Sub$$标识的是新函数。上面的代码就是它们用法的最好示例了。


extern int $Super$$main(void);

/* re-define main function */

int $Sub$$main(void)

{

    rt_hw_interrupt_disable();

    rtthread_startup();

    return 0;

}


这样,程序的执行就不是从用户写的main方法开始了。而是从这个$Sub$$main(void)开始的了。


main是怎么执行的

已经知道了程序不是从main开始执行的是RTT系统作的怪,那么用户写的main方法是何时执行的呢?接着搜索$Super$$main,得到其调用如下:


/* the system main thread */

void main_thread_entry(void *parameter)

{

    extern int main(void);

    extern int $Super$$main(void);


    /* RT-Thread components initialization */

    rt_components_init();


    /* invoke system main function */

#if defined (__CC_ARM)

    $Super$$main(); /* for ARMCC. */

#elif defined(__ICCARM__) || defined(__GNUC__)

    main();

#endif

}


接着搜索main_thread_entry得带代码如下:


void rt_application_init(void)

{

    rt_thread_t tid;


#ifdef RT_USING_HEAP

    tid = rt_thread_create("main", main_thread_entry, RT_NULL,

                           RT_MAIN_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3, 20);

    RT_ASSERT(tid != RT_NULL);

#else

    rt_err_t result;


    tid = &main_thread;

    result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,

                            main_stack, sizeof(main_stack), RT_THREAD_PRIORITY_MAX / 3, 20);

    RT_ASSERT(result == RT_EOK);

#endif


    rt_thread_startup(tid);

}


从名字就可以看得出来,这是在造线程啊,查阅下rtthread的官方文档果然如此。rt_application_init被rtthread_startup调用,然后它创建了一个线程,并在线程中调用了用户定义的main函数。至此就真相大白了。RTT利用工具链提供的方式,替换掉了用户的main,来启动操作系统,并创建了一条线程,在线程中调用了用户的main方法。


至此,RTT操作系统就已经在STM32C8T6核心板上跑起来了。后续使用RTT操作系统得先看下官方文档


关键字:STM32F103  RTT系统  点亮LED

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
AVR熔丝位操作时的要点和需要注意的相关事项