搭建基於 STM32 和 rt-thread 的開發平臺

我們需要平臺

如果說,SharePoint 的價值之一在於提供了幾乎開箱即用的 innovation 環境,那麼,智能設備的開發平臺也一樣。不必每次都從頭開始,所以需要固定的工作室和開發平臺作爲創新的起點,這樣就會每次比從零開始“高一點點”。

當然,這裏不是沒有糾結的。平臺畢竟不是最終的產品,平臺太弱固然難以支撐創新,但平臺太強則臃腫和僵化同樣也會限制創新:面對成百上千的類型、接口的時候,即使做一個小玩意兒也要學上一年半載,任何人都會畏懼的。有那個時間,不如自己寫一個出來了。所以成功的開發平臺如 jQuey 和 jQuery UI 是拆開的,Bootstrap 是可以按模塊自定義下載的。

 

與 SharePoint 在企業協作領域的一家獨大不同,智能控制設備的平臺可謂五花八門,這就有點兒像 python 世界的 web framework。看似很繁榮,但是缺少標準,哪個都難以讓人下決心深入鑽研(貌似現在有往 django 靠攏的趨勢,這是件好事)。所以,在試過了網上各種開發板以後,Jony 下了決心要搭建自己的開發平臺,滿足自己的實際需要。

 

在這裏,我想起來《西遊記》裏面的故事:孫悟空學藝歸來回到花果山以後,第一件事情就是去找一件稱手的兵器,然後幾番周折,最終得到了如意金箍棒。認真對待技術的人,搭建自己的開發平臺,好像也有點兒這個意思。從這個角度說,《西遊記》真的不是一本簡單的魔幻現實主義小說。

 

智能控制設備的開發平臺,有 2 個大的技術決策:1,用什麼 MCU(微控制器);2,用什麼操作系統。

 

MCU 爲什麼是 STM32?

主流的 MCU 陣營有這麼幾個:

  • ST 意法半導體
    便宜、高性能!有固件庫可以方便開發,資料多。
    STM32F10x 系列,樣片在 10 元左右一個,20K 內存、72MHz 主頻、各種外設,已經可以做很多事情了。更吸引人的是,他們家的控制器的固件庫有通用性,熟悉了一個產品線的開發之後,比較容易能夠切換到其它的產品線。 
    說到通用性,所有基於相同核心的 CPU 其實都在某種程度上相通。如手機 ARM 內核。甚至在我看來,只要你是基於時鐘(數字脈衝)的、可以通過寄存器讀寫數據與指令的累加器,都是相通的,當然,實際掌握起來難度也是有的。
  • LPC 恩智普
    43xx 系列是雙核的控制器!性能那是沒話說,高端的 LPC 4370 ADC 採樣率是 8千萬次/秒,相比之下同是 ARM cortex-m4 內核的 ST F407 系列只有 7百萬次/秒。問題就是太 TMD 貴了,性價比不如 ST。
  • Freescale 飛思卡爾
    不熟,掠過。
  • AVR
    開發燒錄程序的時候容易鎖住芯片,變態!而且貴,真的貴。但是,大名鼎鼎的 Arduino 就是用的他們家的 MCU,奇了怪了。不過,如果你是從 51 系列轉過來的話,會有驚豔的感覺。 
  • 51 系列
    上個世紀的恐龍,慢、耗電。只不過架構簡單,所以還在被大量的當作入門學習用途。

ARM 9, 11 系列(包括樹莓派)已經不是簡單的控制器了(那就是小電腦),太過龐大,作爲微處理器還行,僅僅拿來做控制器則太過複雜、而且耗電。

而且都用到那個級別了,我爲什麼不弄個微型 PC 吶?!直接上 .net 也好拯救我幾個腦細胞。

 

補充:雖然 LPC 43xx 那麼貴,那麼複雜,那麼沒有性價比,但不知道爲什麼,我還是經常會想起它。

 

操作系統爲什麼是 rt-thread?

複雜的控制器平臺上面當然要有操作系統啦!

20k 字節的內存那顯然是跑不起 windows 的(DOS 都不行),且架構也不同。但是,微控制器用的開源操作系統已經有很多了,夠用了。

備選的有這麼幾個:

  • FreeRTOS
    太臃腫。已經到了 8.0 了,估計我是沒有機會看完它的源碼了(這種智能控制系統,看不完源碼誰敢用啊?!),簡單試用過以後,pass。
  • rt-thread
    中國人自己開發的,穩定版本是 1.2.1,有希望看完源碼。精簡、靠譜,自帶一個叫做 finsh 的片上調試工具,非常實用。各種信號量、互斥鎖、郵箱、事件等線程協同功能都有。Jony 去張江見過開發團隊,比較放心。缺點是:文件和代碼的組織方式和我的習慣有點兒“出入”,後面談到配置工程的時候會說到。 
    需要注意的是,rt-thread 2.0 版本的設計思想和 1.2 的完全不同,將會把 linux 納入進來,是的,不是在 linux 裏面嵌入 rt-thread,而是把 linux 嵌入到 rt-thread 裏面!想法是強大的,但對我的應用場景來說,有點兒太大了,所以呢,我還是用 1.2.1。
  • RTX
    要用 Keil 開發纔有,而且功能實在太少,就是簡單的輪詢調度任務,pass。不過,配合 Keil 調試起來真的是很方便,參考我另一篇文章:搭建使用 RTX51-Tiny 的 C51 Keil 項目環境
  • ucOS II
    看了網友的分析,比 rt-thread 佔資源,且功能還少,pass。

 

相比“裸機”跑代碼來說,在微控制器上面跑操作系統,設計和實現的難度瞬間上去了,但它能顯著降低應用的難度,這個紮實的地基雖然很難挖,但能支撐百層的高樓。凡是繞不過去的,還是早點兒開始接觸、掌握好。

 

獲取 rt-thread

萬能的 Github:https://github.com/RT-Thread/rt-thread 

rt-thread 的官網:http://www.rt-thread.org。文檔呢,官網是有的,不過,真的是隻能作爲參考,很明顯是開發人員的事後開發筆記整理的。目前還是隻能通過看代碼來理解詳細的使用方式,從文檔和論壇的隻言片語裏面,是難以還原真相的。不過,就如上面所說,rt-thread 的好處就是它的版本還比較小,即便缺乏文檔,也是可以看源碼看下去的。謝天謝地。

 

獲取 STM32 固件庫

如何從意法半導體的官網 http://www.st.com 上找到資料並下載下來,是個技術活兒。該官網的搜索功能是個奇葩,比百度的搜索結果還難以讓人理解。

基本上,只能先輸入想找的芯片的型號,然後從結果裏面再找下載。

 

STM32 的固件庫封裝了很多針對芯片的操作以及寄存器設置,如果不用固件庫,你要讓芯片的某個引腳輸出高電平,要這樣寫:GPIOD->BSRR = 0x00000005; 用了固件庫,你就可以這樣寫:GPIO_SetBits(GPIOD, GPIO_Pin_0|GPIO_Pin_2); (提示:2^0 + 2^2 = 5)。雖然可以對應起來,但實在沒有必要折磨自己,還是用固件庫吧。

 

配置工程目錄

曾經有高人說過“給我看他的數據結構,我就知道他的代碼是怎麼寫的。”我說“給我看他的目錄結構,我就知道他的方案是怎麼設計的。”(說到數據結構這件事,我不得不說,json 在這方面做出了巨大貢獻,如同 c 語言的 struct,只不過是跨語言平臺的。哪怕是做 SharePoint 項目,我也會先考慮定義好適當的 json 對象。)

 

當我說到“工程”的時候,其實是指 workspace、solution,而非“項目”。“項目”叫做 project。一個工程可以包含多個項目。“分而治之”是人類處理複雜事物的基本策略。

問:哪些會變化,哪些不會?

答:所有固件庫、開源組件都被認爲不會因爲我自己的應用需要而變化,只有自己應用寫的會。前者只需要隨着發行方升級即可,Github 上面拉下來也好、從官網下載文件解壓覆蓋也好,都可以。

 

工程目錄佈局規則如下:

  • ST MCU 固件庫目錄 STLib
    • MCU 系列子目錄,比如 STM32F10x、STM32F4xx
      • CMSIS
      • 外設驅動子目錄,如 STM32F10x_StdPeriph_Driver
    • 截圖示意
      ST 固件庫目錄
  • rt-thread 目錄
    • 裏面原樣保持從 Github 拉下來的目錄結構即可,如圖所示
      rt-thread 目錄
  • 工程根目錄
    • 工程文件。IAR 的工程文件擴展名是 .eww,如同 Visual Studio 的工程文件擴展名是 .sln 一樣。
    • 文檔目錄。
    • 項目目錄。有多少項目,就有多少個項目目錄。
      • 項目文件。IAR 的項目文件擴展名是 .ewp,如同 Visual Studio 的 C# 項目文件擴展名是 .csproj 一樣。
      • main.c 用 IAR 創建新的項目時,main.c 默認會放在項目目錄下面。我覺得放在那裏挺好,沒有必要改變。
      • App 目錄。裏面放和應用有關的程序文件。
        • 按照上面的路徑配置,假設在 App 目錄裏面有一個 app.c 的程序文件,則該文件到 ST 固件庫的相對路徑是 “../../STLib”,同樣,到 rt-thread 的相對路徑是“../../rt-thread”
      • Board 目錄。裏面放和電路板設置有關的程序文件,比如,串口的管腳定義。這個目錄裏面文件的意義,是把固件庫 & rt-thread 與 應用有關的程序文件隔離開來。個人認爲,這一層的作用是很重要的,要好好規劃。
      • Driver 目錄。裏面放置 rt-thread 提供的各種片上外設的驅動程序,需要從 rt-thread 的 bsp 子目錄裏面對應的芯片驅動中拷貝過來。之所以需要拷貝而非簡單的引用,是因爲這一層的驅動程序可能需要根據應用的需要做定製。
      • Fireware 目錄。裏面放置 ST 固件庫所需的文件,比如 stm32f10x_conf.h。
      • RT-Thread 目錄。裏面放置 rt-thread 所需的配置文件,比如 rtconfig.h。
      • 項目目錄截圖示意(項目名稱叫 LCD)
        項目目錄

 

配置工程

ST 微控制器 MCU 的開發 IDE,有 3 大陣營

  • 官方的 ST Studio
  • IAR
  • MDK(Keil)

不知道爲什麼,我選了 IAR。這中間當然有很多故事,因爲我的確 3 種都嘗試過,不過,年紀大了,已經記不得這些經歷了。不幸的是,rt-thread 雖然有針對 IAR 的移植,但是開發團隊用的是 MDK(Keil),於是在配置工程的時候多了一些波折。

 

下面是配置的步驟和對應的選項。

打開配置選項窗口 打開配置選項窗口
選擇合適的 Device 選擇合適的 Device
勾上“Using CMSIS” 勾上“Using CMSIS”
輸出所有的彙編鏈接信息 輸出所有的彙編鏈接信息
設置編譯器的頭文件搜索路徑以及與定義的宏 設置編譯器的頭文件搜索路徑以及與定義的宏 

頭文件搜索路徑如下:

$PROJ_DIR$\..\..\STLib\STM32F10x\CMSIS\CM3\DeviceSupport\ST\STM32F10x
$PROJ_DIR$\..\..\STLib\STM32F10x\STM32F10x_StdPeriph_Driver\inc
$PROJ_DIR$\App
$PROJ_DIR$\Board
$PROJ_DIR$\Driver
$PROJ_DIR$\Firmware
$PROJ_DIR$\RT-Thread
$PROJ_DIR$\..\..\RT-Thread\components\init
$PROJ_DIR$\..\..\RT-Thread\components\drivers\include\drivers
$PROJ_DIR$\..\..\RT-Thread\libcpu\arm\cortex-m3
$PROJ_DIR$\..\..\RT-Thread\libcpu\arm\common
$PROJ_DIR$\..\..\RT-Thread\components\finsh
$PROJ_DIR$\..\..\RT-Thread\include
$PROJ_DIR$\..\..\RT-Thread\components\drivers\include


預定義的宏如下:

STM32F10X_MD
USE_STDPERIPH_DRIVER
HSE_VALUE=((uint32_t)8000000)
SYSCLK_FREQ_24MHz=24000000


其中:
STM32F10X_MD 表示採用的 MCU 系列和密度,可以在芯片手冊裏面查到。
HSE_VALUE 是 MCU 外部晶振的頻率,按照你的實際需要修改。
SYSCLK_FREQ_24MHZ 是總線頻率,也是按照實際需要修改。
指定默認的鏈接器配置文件 指定默認的鏈接器配置文件 

需要使用 rt-thread 的 bsp 目錄下對應的 MCU 的配置文件。否則燒錄進去的程序在芯片中的佈局會不正確,導致 finsh 組件無法正確運行:finsh 移植遇到了問題,始終輸出"Null node"
指定調試工具 指定調試工具 

我用的 ST-LINK,所以選擇 ST-LINK。
USE Flash Loader USE Flash Loader
設置 ST-LINK 設置 ST-LINK 
好了以後可以點 OK 關閉選項設置對話框,然後開始項目文件設置。
引用 ST 固件庫 引用 ST 固件庫 

firmware 文件組下面的文件全部來自 STLib 文件夾,引用即可。但是,注意 system_stm32f10x.c 文件,這裏麪包含 MCU 初始化時鐘的設置。如果你沒有如上面步驟一樣在編譯器的預定義宏裏面定義時鐘,那麼,可以修改這個文件來實現。
引用 rt-thread 文件(不同的組件放在不同的子文件組裏面) 引用 rt-thread 文件(不同的組件放在不同的子文件組裏面) 
image 
image 
image 

注意,這裏面的 rtconfig.h 是拷貝到項目目錄下面的 RT-Thread 子目錄裏面的那個,因爲需要結合應用的需要進行修改。
拷貝 rt-thread bsp 目錄裏面的驅動程序到 Driver 目錄下 拷貝 rt-thread bsp 目錄裏面的驅動程序到 Driver 目錄下

 

開發示例

按照上面的方式建立好工程和項目以後,已經可以開始一個簡單的小程序做測試了,讓板上的 LED 每一秒閃爍一次並通過串口來顯示 finsh。

首先,在 Board 文件組下面建立 board.h、board.c 來定義自己的硬件環境的配置。

board.h 裏面的宏定義 STM32_SRAM_SIZE 很重要,需要根據你所用的 MCU 的實際內存大小來設置。

   1: /* board.h */
   2: #ifndef __BOARD_H__
   3: #define __BOARD_H__
   4:  
   5: #define STM32_EXT_SRAM          0
   6: //    <o>Begin Address of External SRAM
   7: //        <i>Default: 0x68000000
   8: #define STM32_EXT_SRAM_BEGIN    0x68000000 /* the begining address of external SRAM */
   9: //    <o>End Address of External SRAM
  10: //        <i>Default: 0x68080000
  11: #define STM32_EXT_SRAM_END      0x68080000 /* the end address of external SRAM */
  12: // </e>
  13:  
  14: // <o> Internal SRAM memory size[Kbytes] <8-64>
  15: //    <i>Default: 64
  16: #define STM32_SRAM_SIZE         20
  17: #define STM32_SRAM_END          (0x20000000 + STM32_SRAM_SIZE * 1024)
  18:  
  19: void board_initiate();
  20:  
  21: /* LED */
  22: #define USING_LED1
  23: #define led1_periph_clock       RCC_APB2Periph_GPIOC
  24: #define led1_port               GPIOC
  25: #define led1_pin                (GPIO_Pin_13)
  26:  
  27: #endif
   1: /* board.c */
   2: #include <rthw.h>
   3: #include <rtthread.h>
   4:  
   5: #include <stm32f10x.h>
   6: #include "board.h"
   7: #include "usart.h"
   8:  
   9: void board_initiate(){
  10:     SystemInit();
  11:     SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );
  12:  
  13:     rt_hw_usart_init();
  14:     rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
  15: }
  16:  
  17: void SysTick_Handler(void)
  18: {
  19:     rt_interrupt_enter();
  20:     rt_tick_increase();
  21:     rt_interrupt_leave();
  22: }

 

然後,在 Driver 文件組裏面建立 led.h、led.c 的驅動。

   1: /* led.h */
   2: #ifndef __LED_H__
   3: #define __LED_H__
   4:  
   5: #include <stdint.h>
   6:  
   7: void led_initiate(void);
   8: void led_on(uint8_t led);
   9: void led_off(uint8_t led);
  10:  
  11: #endif

   1: /* led.c */
   2: #include <stm32f10x.h>
   3: #include "led.h"
   4: #include <board.h>
   5:  
   6: void led_initiate(void)
   7: {
   8:     GPIO_InitTypeDef GPIO_InitStructure;
   9:     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
  10:     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
  11:  
  12: #ifdef USING_LED1
  13:     RCC_APB2PeriphClockCmd(led1_periph_clock,ENABLE);
  14:     GPIO_InitStructure.GPIO_Pin   = led1_pin;
  15:     GPIO_Init(led1_port, &GPIO_InitStructure);
  16: #endif
  17: }
  18:  
  19: void led_on(uint8_t n)
  20: {
  21: #ifdef USING_LED1
  22:     if(n!=0)return;
  23:     GPIO_SetBits(led1_port, led1_pin);
  24: #endif
  25: }
  26: void led_off(uint8_t n)
  27: {
  28: #ifdef USING_LED1
  29:     if(n!=0)return;
  30:     GPIO_ResetBits(led1_port, led1_pin);
  31: #endif
  32: }

 

然後,在 App 文件組裏面建立 app.h、app.c 文件,裏面的內容和具體應用相關。

   1: /* app.h */
   2: #ifndef __APP_H__
   3: #define __APP_H__
   4:  
   5: int app_initiate(void);
   6:  
   7: #endif /* __APP_H__ */

   1: /* app.c */
   2: #include <rthw.h>
   3: #include <rtthread.h>
   4:  
   5: #include "app.h"
   6: #include "led.h"
   7:  
   8: #ifdef  RT_USING_COMPONENTS_INIT
   9: #include <components.h>
  10: #endif  /* RT_USING_COMPONENTS_INIT */
  11:  
  12: static rt_uint8_t led_stack[256];
  13: static struct rt_thread led_thread;
  14: static void led_thread_entry(void* parameter)
  15: {
  16:     led_initiate();
  17:     while (1)
  18:     {
  19:         led_on(0);
  20:         rt_thread_delay( RT_TICK_PER_SECOND/2 );
  21:         led_off(0);
  22:         rt_thread_delay( RT_TICK_PER_SECOND/2 );
  23:  
  24:     }
  25: }
  26:  
  27: int app_initiate(void)
  28: {
  29:     rt_err_t result;
  30:     /* init led thread */
  31:     result = rt_thread_init(&led_thread,
  32:             "led",
  33:             led_thread_entry,
  34:             RT_NULL,
  35:             (rt_uint8_t*)&led_stack[0],
  36:             sizeof(led_stack),
  37:             20,
  38:             5);
  39:     if (result == RT_EOK)
  40:     {
  41:         rt_thread_startup(&led_thread);
  42:     }
  43:  
  44: #ifdef RT_USING_COMPONENTS_INIT
  45:     /* initialization RT-Thread Components */
  46:     rt_components_init();
  47: #endif
  48:  
  49:     return 0;
  50: }

其中 led 線程的堆棧長度是 256 字節,這是實踐出來的。一開始,可以給一個儘量大的堆棧尺寸,然後,運行時通過 finsh 的 list_thread() 命令輸出其實際使用的最大的堆棧尺寸,最後回來修改。

接下來打開 rtconfig.h 確保裏面的 #define RT_USING_FINSH 沒有被註釋掉。

 

最後,在 main.c 裏面加入啓動代碼。

   1: /* main.c */
   2: #include <rthw.h>
   3: #include <rtthread.h>
   4: #ifdef __CC_ARM
   5: extern int Image$$RW_IRAM1$$ZI$$Limit;
   6: #elif __ICCARM__
   7: #pragma section="HEAP"
   8: #else
   9: extern int __bss_end;
  10: #endif
  11:  
  12: #include <stdint.h>
  13: #include "board.h"
  14: #include "app.h"
  15:  
  16: int main()
  17: {
  18:     board_initiate();
  19: #ifdef RT_USING_HEAP
  20: #if STM32_EXT_SRAM
  21:     rt_system_heap_init((void*)STM32_EXT_SRAM_BEGIN, (void*)STM32_EXT_SRAM_END);
  22: #else
  23: #ifdef __CC_ARM
  24:     rt_system_heap_init((void*)&Image$$RW_IRAM1$$ZI$$Limit, (void*)STM32_SRAM_END);
  25: #elif __ICCARM__
  26:     rt_system_heap_init(__segment_end("HEAP"), (void*)STM32_SRAM_END);
  27: #else
  28:     /* init memory system */
  29:     rt_system_heap_init((void*)&__bss_end, (void*)STM32_SRAM_END);
  30: #endif
  31: #endif  /* STM32_EXT_SRAM */
  32: #endif /* RT_USING_HEAP */
  33:     /* init scheduler system */
  34:     rt_system_scheduler_init();
  35:     /* initialize timer */
  36:     rt_system_timer_init();
  37:     /* init timer thread */
  38:     rt_system_timer_thread_init();
  39:     /* init application */
  40:     app_initiate();
  41:     /* init idle thread */
  42:     rt_thread_idle_init();
  43:     /* start scheduler */
  44:     rt_system_scheduler_start();
  45:     /* never reach here */
  46:     return 0;
  47: }
  48:  
  49: void assert_failed(uint8_t* file, uint32_t line)
  50: {
  51:     while (1) ;
  52: }

 

電路設計

本着省錢、夠用的原則,我選擇了 STM32F103C8T6,不到 10 元一片,管腳對於我的測試應用足夠。

爲了確保 MCU 芯片可以滿足需要,建議先用 ST 自家出的 MicroXplorer 來規劃一下芯片上面外設和 IO 的分配,做到心中有數。

image

 

圍繞着芯片的選型,就可以開始電路設計了。

下面是 MCU 的核心電路:

MCU 核心電路

核心電路的 P1 是 SWD 的編程接口,其管腳的排列是與 ST 的 STM32F4 Discovery 開發板兼容的,這樣就可以直接通過 STM32F4 Discovery 開發板對自己的實驗電路進行調試了。

 

運行效果

然後,就可以編譯並下載到芯片中運行了。

下面貼一個實際的效果(裏面不僅僅有 LED 了)。

運行效果 

 

跑一下 finsh,可以看到很多有價值的系統運行數據,如內存的使用情況,各個線程的堆棧使用情況等等。

finsh

 

這樣,開發平臺搭建完成,接下來就看自己的想象力了。

P.S. 這段時間看似有點兒不務正業了,後面會把 SharePoint 的時間補回來的。

 

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