1. 程式人生 > >STM32F103+RTT從零開始(二)——RTT系統中點亮LED

STM32F103+RTT從零開始(二)——RTT系統中點亮LED

上一篇部落格簡單說了下如何使用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 <rthw.h>
#include <rtthread.h>
#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 <your_header_file.h>

#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_initrtthread_startup呼叫,然後它建立了一個執行緒,並在執行緒中呼叫了使用者定義的main函式。至此就真相大白了。RTT利用工具鏈提供的方式,替換掉了使用者的main,來啟動作業系統,並建立了一條執行緒,線上程中呼叫了使用者的main方法。

至此,RTT作業系統就已經在STM32C8T6核心板上跑起來了。後續使用RTT作業系統得先看下官方文件,然後在使用中實踐,在實踐中深入理解,以便更快更好的掌握RTT。