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

上一篇博客簡單說了下如何使用Keil創建STM32F103的工程,並且完成了LED點亮,及讓LED等閃爍的功能,那是諸多同學學習單片機的起手式。本篇博客繼續上一篇博客的內容,依舊是點亮LED,不同的是,這次點亮LED等,是在RT-Thread操作系統中進行的。

創建工程

創建一個Keil工程,芯片依舊選擇STM32F103C8T6,然後在Manage Run-Time Environment對話框中選擇需要用的的軟件組件,與上文不同的是,我們需要把RTT一起勾上,這裏的RTOS入口,可以通過RTT的官網搜索pack進行安裝。如下圖:
這裏寫圖片描述

上圖中,紅線框中即爲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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章