2016年3月9日 星期三

在 ubuntu 上開發 STM32F469 Discovery 實驗板

這是今回的玩具:

STM32F469 Discovery, 應該是意法半導體官方的公板



背面:

它是設計成 Arduino 相容的插座

但是不知道是不是 SMD 製程上的問題, 它這個 SMD 的插座很容易分離

本來是打算把塑膠部份用鑷子翹起, 然後再一一解焊排針
誰知道輕輕一翹, 針腳就脫離焊點了, 一開始讓我嚇到以為連銅箔拉起
結果沒有, 排針乾乾淨淨的, 焊點上留下針腳的腳印
只有插槽兩端的腳會這樣, 中間的就黏得蠻穩的
如果真的拿去當 Arduino 的用戶可能拔插幾次, 邊邊的腳應該會脫離吧...
還好我不打算這麼做, 我把插槽全拆了想減少它的厚度以便我裝盒子

幾年前寫了篇文章:在 Linux 上編譯 stm32 library for stm32f10x
當時說 STM32 顯示不夠給力
幾年後無聊到處晃時看到了 STM32 家族新方案, 所以就買來玩玩
以前認為 STM32 只能畫簡單圖形而且不能 "滑", 結果新的就可以了
想必他們公司應該也受到來自客戶的壓力而把 2D 繪圖加速給補上去了
這顆如果從原始碼資料看應該是 2012 年就有了, 只是當時剛出來沒有玩具可買
現在則是滿街跑, 還是官方賣的公板
從軟體釋出紀錄看來, 提供到完整軟體支援包也是 2014 年的事了
所以他們也是花了不少時間翻身, 但我認為這翻身來的正是時候
因為 2015 以後平板手機衰退, 大家一窩蜂往 IoT 去
如果有 IoT 的應用需要有顯示器又不希望太耗電的, 這 STM32F4 系列正好卡進這需求區間
它可以顯示圖形, 也可以跑比較精細的 UI, 可是又不需要像應用處理器一定要掛 RAM 和 EMMC
跑 RTOS 又可以比應用處理器跑 Linux 更快速的開機, 看起來相當不錯
STM32 系列現在最新的方案是 STM32F7, 加入了 LCD 控制器, 可以更新同步面板
而 STM32F4 只能用非同步面板, 要上同步面板需轉換器, 例如:RA8835
更正:這顆 STM32F469 可以畫同步面板, 支援 TTL 面板

這玩具功能很強, 時脈到 180MHz, 週邊非常多, 玩起來可不容易
因此如同前篇, 我們會需要把官方的硬體抽象層 (HAL) 軟體移植上去
很多官方擴充軟體都會以這 HAL 為基礎去發展, 要加速開發這是必須的
開發時若要在 Linux 上開發, 官方有建議的方案:OpenSTM32
它是一個 eclipse 的 plugin, 網站需要註冊才可獲取說明細節
我覺得 eclipse 應該是用來寫 Java 比較適合, 因為 Java 佈署打包比較麻煩
但是 C 嘛...... makefile 和文字編輯器才是正統啊!XD
那個 eclipse 很吃記憶體的, 不過如果怕麻煩的用戶還是可以考慮
雖然以 STM32F4 新軟體的架構我不認為用 IDE 會輕鬆到哪裡去
但有興趣的用戶還是可以試試
如果和本實驗室一樣打算用文字編輯器搞請繼續閱讀XD

由於我拿的公板 stm32f469 discovery 燒錄器是內建在板上
如果想要像前篇 stm32f10x 那樣用 UART 燒錄會很麻煩
所以我找來 OpenOCD 來作為燒錄工具, 並以 stm32f10x 那包的 LD script 為基礎
發展可以編出讓這玩具動的 LD script, 上次我們拿別人做好的, 這次自己研究

玩這玩具需先準備三樣東西:

stm32cubef4.zip : 官方軟體包
openocd : 燒錄工具
gcc-arm-none-eabi-4.8.3-2014q1 : 編譯器

還有本實驗室修改並測過的編譯腳本:stm32f469-linux.zip

stm32cubef4.zip 去官方網站挖一下應該就有了, 檔案大小約三百多 MB
解壓縮出來會有個目錄:STM32Cube_FW_F4_V1.10.0
gcc-arm-none-eabi-4.8.3-2014q1 編譯器則是由 arduino-1.5.7 抽出
下載 arduino-1.5.7-linux64.tgz 這包, 從這路徑抽出:
arduino-1.5.7/hardware/tools/gcc-arm-none-eabi-4.8.3-2014q1
接著是 openocd
openocd 雖然 ubuntu 有內建, 但我抓下來發現 stm32f4 系列還沒有 patch
建議自己抓源碼編譯後使用, 這裡提供編譯流程供參考
由於網站可能會遷移, 若無法用請上 openocd 網站找編譯流程

# get openocd
sudo apt-get install libtool automake libusb-1.0-0-dev
git clone http://git.code.sf.net/p/openocd/code openocd-code
cd openocd-code
./bootstrap
./configure --prefix=/path/to/install/openocd
make -j16
make install
# 若成功, 先把 stm32f469 discovery 接上電腦再執行 openocd
# run openocd
cd /path/to/install/openocd
sudo ./bin/openocd -f interface/stlink-v2-1.cfg -f target/stm32f4x.cfg -s share/openocd/scripts

若運作正常應該可以看到訊息:

Open On-Chip Debugger 0.10.0-dev-00197-gafbad69 (2016-02-01-16:30)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
none separate
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Info : STLINK v2 JTAG v24 API v2 SWIM v11 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 2.980953
Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints

這樣就完成連線, 接著開另一個 console, 用 telnet 連進去

$ telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
>

這樣就可以操作 stm32f469 discovery
openocd 是一個除錯工具, 可和 GDB 整合, 不過我只拿它來燒錄用
啟動後和板子連線, 並產生一個 telnet 的伺服器, 用 telnet 連線即可下命令
輸入 shutdown 即可離開 openocd

準備到這裡就有編譯器也有燒錄軟體, 可以先測試燒錄一個簡單的 LED 軟體試試
解壓本實驗室的 stm32f469-linux.zip
把 stm32f469-linux/test/test.bin 複製到 openocd 的安裝目錄下
(即上面的 /path/to/install/openocd)
然後對 openocd 下命令 (也就是上面 telnet 那裡下)

> reset init                                
> flash write_image erase test.bin 0x08000000
> reset

執行輸出:

> reset init                                
stm32f4x.cpu: target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x080ae1c4 msp: 0x20043380
Unable to match requested speed 8000 kHz, using 4000 kHz
Unable to match requested speed 8000 kHz, using 4000 kHz
adapter speed: 4000 kHz
> flash write_image erase test.bin 0x08000000
auto erase enabled
device id = 0x10006434
flash size = 2048kbytes
stm32f4x.cpu: target state: halted
target halted due to breakpoint, current mode: Thread
xPSR: 0x61000000 pc: 0x20000046 msp: 0x20043380
wrote 16384 bytes from file test.bin in 0.503040s (31.807 KiB/s)
> reset

這三條的意思是:先重開機並初始化部份暫存器, 接著把軟體燒到 flash 中, 然後重開機執行
重開機並初始化部份暫存器應該是在 openocd 的腳本中寫的
可參考 openocd 下的 openocd/scripts/target/stm32f4x.cfg
大致上就是把 clock 和 pll 設定好後啟動, 讓 MCU 上的 flash 可以使用
接著根據 STM32F469 參考文件 (代號:RM0386 檔名:DM00127514.pdf)
文件章節 2.4 Boot configuration 中的描述, 接腳 BOOT0 若接到 0
會從 flash 開機, 且記憶體位址會在 0x08000000 - 0x080FFFFF
所以我們要把軟體放到 0x08000000 上
完成後應該可以看到板上的 LED2 (橘色) 在閃爍

接著我們來研究軟體內容
若要編譯這隻測試程式, 先在路徑中加入編譯器

export PATH=$PATH:/path/to/gcc-arm-none-eabi-4.8.3-2014q1/bin

然後把 STM32Cube_FW_F4_V1.10.0 這目錄移動到 stm32f469-linux 目錄下
接著進到 stm32f469-linux/test 下執行

make all

就可以完成編譯, 會得到 test.bin 這檔案, 這樣就完成編譯
它由兩隻原始碼組成:main.c startup.c
startup.c 是一切的開始, 執行後才跳到 main.c
這兩隻原始碼會依照 stm32f469ni.ld 來排列, 裡面這行:

KEEP(*(.isr_vectors))

指示 linker 要把 .isr_vectors 這個 section 放這裡, 也就是一開始的地方
.isr_vectors 的內容寫在 startup.c :

__attribute__ ((section(".isr_vectors")))
void (* const g_pfnVectors[])(void) = {
    (intfunc)((unsigned long)&_estack), /* The stack pointer after relocation */
    Reset_Handler,              /* Reset Handler */
    ...
}

這個陣列會從 test.bin 的最前面開始擺, 它是中斷向量
一開機第一個執行的是第二個向量:Reset_Handler
完整的中斷向量表可參考文件 RM0386 中的 11.2 節

Reset_Handler() 執行後接著執行 __Init_Data()
__Init_Data() 會把 bss 區間用 0 填滿, 然後把 _sidata 區間複製到 _sdata 區間
用 objdump 可以把所有 symbol 的位址撈出來

arm-none-eabi-objdump -t test.elf

裡面其中三項:

20000000 g       .data    00000000 _sdata
20000000 g     O .data    00000004 g_var
080009a0 g       .rodata    00000000 _sidata

_sdata 在 0x20000000, 那裡是 SRAM1 所在的位置 (參考文件 RM0386 2.4 節)
main.c 中有定義一個全域變數 g_var, 它被放在 0x20000000
_sidata 放在 0x080009a0, 如果減去 0x08000000, 也就是 flash 的起始位置
_sidata 應該會存放在 test.bin 中的 0x9a0 的位置, 可用 16 進位編輯器打開
試著修改全域變數 g_var 的初始值後重編, 應該可以看到 test.bin 中數值改變
這意思就是全域變數如果有設定初始值的, 它們會被保存在 _sidata 區間
但是 _sidata 區間是在 flash 中, 可是 g_var 卻指向 sram 記憶體
因此程式一起動就需要先把這些資料放進記憶體中供參照
早期編譯器會有一些放不同區段的問題, 可參考別人的文章:.bss section:C 語言所種下的因

不過 cortex m4 是相當新的架構, 通常我們會用新的編譯器去編, 像我們這次用 gcc-4.8
所以預設就已經都放在 _sdata 中
會特別關注這個項目是因為開發時下錯一些參數會導致 _sidata 中起始位置錯誤
程式執行起來死的莫名其妙, 經過仔細的二進位資料比對才發現這問題
後來忘記改了什麼才迴避掉, 碰這種很底層的東西需要不少根性仔細觀察...

makefile 中產生 bin 檔時有兩行:

$(OBJCOPY) -O binary -R .ExtQSPIFlashSection $(APP_NAME).elf $(APP_NAME).bin
$(OBJCOPY) -O binary -j .ExtQSPIFlashSection $(APP_NAME).elf $(APP_NAME)-spi.bin

這意思是:
把所有 .ExtQSPIFlashSection 區間以外的東西抽出來做成 xxx.bin
把所有 .ExtQSPIFlashSection 區間內的東西抽出來做成 xxx-spi.bin

分兩個檔案做的原因是 STM32F4 並沒有內建 eeprom
但是可以把外部 spi flash 以 memory map 的方式映射到系統中, 因此這兩塊要分開燒錄
xxx.bin 燒到 MCU 內建的 flash, xxx-spi.bin 則燒到板上的 spi flash

接著回到 startup.c
startup.c 中有許多長得像 void WEAK xxx_Handler(void); 這樣的定義
WEAK 是個 macro 寫在檔案開頭附近:#define WEAK __attribute__((weak))
這意思是如果沒有其他人定義 xxx_Handler 這函數
就用這檔案中定義的 xxx_Handler(){...};
如果其他地方發現有定義 xxx_Handler(), 那就用別的地方的函數取代當前這個
產生的效果就是 "預設中斷向量", 我們只要在別的地方定義 xxx_Handler()
不需要特別回來 startup.c 中把它刪除, 它們不會因為名字相同而報錯
而會自動取代原先預設的
目前預設的中斷向量都跳到 Default_Handler() 中, 內容是點亮紅色 LED 並無限迴圈卡死
開了中斷卻沒有設定中斷向量, 這就是軟體有問題, 卡死伺候!

以上就是 test 這隻簡單程式的說明
test 這隻程式只有引用 stm32cubef4.zip 中的標頭檔, 並沒有編譯 HAL 層
用這範例開始寫程式得全手動, 如果沒有把 clock 打開, 連 GPIO 都不會動!
接著我們看第二個範例:stm32f469-linux/fat-rw
fat-rw 的 makefile 加入了一些設定讓 HAL 可以編過
像是移除 -nostdlib, 官方的 UI 函式庫會引用 memcpy, strcpy 這類標準 C lib 的東西
因此要拿掉, 拿掉後就會帶來一堆和 C lib 有關的問題, 所以又加了一些那些專家才懂的東西
Linker error on a C project using eclipse
FreeRTOS Support Archive


編譯 fat-rw 這程式一樣是下這行:

make all

如果嫌檔案多速度慢可以:

make all -j16

用力給它操!XD
makefile 會引用 stm32cubef4.mk, 裡面有一堆檔案加入編譯
這版 STM32 的 HAL 長的和德州儀器的 cc2530 / cc2540 系列的軟體很像
不再像 stm32f10x 以前那樣固定一包來 link
這樣可以減少程式體積, 不需要的都不包進韌體中, 固定值的都寫死不用變數參照
代價是使用上很麻煩, 用戶得自己定義一個標頭檔說明哪些要哪些不要
德州儀器方案則是從編譯器參數下手, 用類似 GCC 的 -Ddefine 這種方式設定
編譯時得一個個把源碼加入, 而不是直接當 lib 來連接
源碼中 stm32f4xx_hal_conf.h 用來設定 HAL, ffconf.h 則用來設定 FAT lib
fat-rw 這隻程式會在 SD 卡上讀取 FAT 檔案系統
然後寫一筆資料到檔案中, 接著讀出來驗證看是否相符
這範例是從 stm32cubef4.zip 中抽出, 原先的流程還包含格式化
把我一張 8G 的卡清得乾乾淨淨, 真是感謝! (握拳)
如果想實驗格式化功能可把 main.c 中 FORMAT 那裡的程式解除註解即可
這函式庫可以讀寫還可以格式化, 實做得相當完整, 不過目前的設定只支援短檔名
unicode 檔名好像要再多掛一些程式, 可到 stm32cubef4.zip 中挖看看
路徑:STM32Cube_FW_F4_V1.10.0/Middlewares/Third_Party/FatFs/src/option
如果有需要了解 FAT 概念可參考前篇:讀取 FAT32 檔案系統

有了 HAL 層接著就是搞定 spi flash 燒錄問題
spi flash 在 STM32F4 中可以映射到記憶體位址中直接存取
從別人的腳本看來應該是映射到 0x90000000, 這個在文件 RM0386 中並沒有看到敘述
映射後可以讀, 但是沒法寫, 要寫 spi flash 要先擦除 (erase)
然後把指定區塊設為可寫入, 然後才能寫, 很麻煩的
由於整個指令很多, 透過 openocd 寫入得先做複雜初始化, 而且資料怎麼搬進去也不知道
試過 openocd 的 dump_image 指令即使在 memory mapped 狀態下也撈不出東西
load_image 指令我就不想試了
所以就做了這隻程式:stm32f469-linux/qspi-rw
qspi-rw 可以把 spi flash 中的資料 dump 到 SD 卡上
也可把 SD 卡上的檔案寫入 spi flash 中
修改 main.c 中的 mode 變數即可, 預設是寫入
stm32f469-linux/default-fw 裡面放了 stm32f469 discovery 預設韌體
使用方法:

把 STM32F469-Discovery-qspi-dump.bin 複製到 SD 卡上並改名為 QSPI.BIN
把 SD 卡插入 stm32f469 discovery, 然後燒錄 qspi-rw.bin 到 stm32f469
重新開機後橘燈閃爍代表燒錄中, 若出錯會亮紅燈, 成功會亮綠燈
燒錄整個 16MB 的 spi flash 需要六分鐘多! 請耐心等候
完成後移除 SD 卡, 然後燒錄 STM32F469-Discovery-fw-dump.bin 到 stm32f469
完成後就會恢復原廠 demo 用韌體, 有繪圖加速示範, 還有個小遊戲


由此可知即使在 MCU 上運行程式燒錄都要這麼久, 透過 openocd 操作就別試了
理論上可行, 但是要一直用 mww 指令一個一個搬, 別鬧惹!

dump spi flash 就把 qspi-rw 中的 main.c 裡改 mode = 1 後編譯燒錄即可
會把整個 spi flash 都寫到 SD 卡上, 檔名為 QSPIDMP.BIN
如果你只是要看一小塊 spi flash 有沒有寫錯, 可以先 reset
然後對 openocd 下這命令:

> mdw 0x90000000 16                    
0x90000000: 9d58a599 95179517 95179517 95179517 95179517 95379537 95379537 95379537
0x90000020: 95379537 95379537 95179517 95379537 9d379537 9d389d37 9d389d38 9d379d37

它會印出 0x90000000 後的 16 個 word 的內容
用這方法有個前提, 那就是當前 stm32f469 上的韌體有把 spi flash 設定為 memory mapped
如果手邊沒韌體可測, 可以把 qspi-rw 中的 main.c 裡改 mode = 0 後編譯燒錄
它會初始化 spi flash 為 memory mapped, 然後什麼事也不做

如果你很無聊, 想要用 openocd 的 mdw 指令來 dump 整顆 spi flash
沒問題!這我有玩過XD 首先要把韌體燒錄為含有 spi flash 設定為 memory mapped 的
然後重新執行 openocd, 命令中將輸出導到檔案:

sudo ./bin/openocd -f interface/stlink-v2-1.cfg -f target/stm32f4x.cfg -s share/openocd/scripts > qspi-dump.txt 2>&1

然後對 openocd 下命令:

> mdw 0x90000000 1048576
> mdw 0x90400000 1048576
> mdw 0x90800000 1048576
> mdw 0x90C00000 1048576

每個指令要執行 25 秒!XD
執行後用 stm32f469-linux/log2bin 下的工具處理, 即可做出 bin 檔
經過實驗比對和 SD 卡存下的內容相符

最後, 我們來編譯官方的 demo, 軟體在 stm32f469-linux/offical-demo
加入了一狗票關聯的 lib 和模組, 一樣是 make all
然後把 demo-spi.bin 燒入 spi flash, 接著把 demo.bin 燒入 stm32f469
結果:

圖片有正確顯示, 原始碼中建出 spi flash 區段的資料有正確燒錄

官方下載來的 demo 和出廠時的 demo 不太一樣, 少了前面鳥吃硬幣的遊戲
還有那些圖表的 demo, 有偷藏步的嫌疑XD
不過至少看起來都會動, 這包自製整理的編譯腳本應該是能用的

2 則留言:

  1. 您好,請問一下 ld file 是怎麼製作的呢?
    要查看哪些文件才可以無中生有,想學這一塊,謝謝

    回覆刪除
    回覆
    1. 這我還真不知道怎麼回答XD 會問這問題...我先假設你是沒有方向好了
      最直覺的是 Google 搜尋 "linker script tutorial" 就可以找到一堆
      找介紹你使用的平台文章先看看, 至少我當初是這樣幹的
      這問題無法直接回答是因為它和硬體高度相關, 也和這程式要運行的平台相關
      有 OS 或沒 OS 也會有差, 以我的情況是當我搞清楚硬體特性和軟體平台
      裡面寫啥就自動看懂, 不需要查太多資料, 就如同 python 裡一些奇怪語法
      找到論壇介紹時, 那老哥寫了一堆, 最後留了一句:
      當你需要它時, 你自然會來找它, 而且立刻知道怎麼用
      ld 腳本就是這樣的東西XD

      刪除

注意:只有此網誌的成員可以留言。