您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么在極小硬件中運用Go語言”,在日常操作中,相信很多人在怎么在極小硬件中運用Go語言問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么在極小硬件中運用Go語言”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
STM32F030F4P6 給人留下了很深的印象:
CPU: Cortex M0 48 MHz(最低配置,只有 12000 個邏輯門電路)
RAM: 4 KB,
Flash: 16 KB,
ADC、SPI、I2C、USART 和幾個定時器
以上這些采用了 TSSOP20 封裝。正如你所見,這是一個很小的 32 位系統。
如果你想知道如何在這塊開發板上使用 Go 編程,你需要反復閱讀硬件規范手冊。你必須面對這樣的真實情況:在 Go 編譯器中給 Cortex-M0 提供支持的可能性很小。而且,這還僅僅只是第一個要解決的問題。
我會使用 Emgo,但別擔心,之后你會看到,它如何讓 Go 在如此小的系統上盡可能發揮作用。
在我拿到這塊開發板之前,對 stm32/hal 系列下的 F0 MCU 沒有任何支持。在簡單研究參考手冊后,我發現 STM32F0 系列是 STM32F3 削減版,這讓在新端口上開發的工作變得容易了一些。
如果你想接著本文的步驟做下去,需要先安裝 Emgo
cd $HOMEgit clone https://github.com/ziutek/emgo/cd emgo/egcgo install
然后設置一下環境變量
export EGCC=path_to_arm_gcc # eg. /usr/local/arm/bin/arm-none-eabi-gccexport EGLD=path_to_arm_linker # eg. /usr/local/arm/bin/arm-none-eabi-ldexport EGAR=path_to_arm_archiver # eg. /usr/local/arm/bin/arm-none-eabi-ar export EGROOT=$HOME/emgo/egrootexport EGPATH=$HOME/emgo/egpath export EGARCH=cortexm0export EGOS=noosexport EGTARGET=f030x6
更詳細的說明可以在 Emgo 官網上找到。
要確保 egc
在你的 PATH
中。 你可以使用 go build
來代替 go install
,然后把 egc
復制到你的 $HOME/bin
或 /usr/local/bin
中。
現在,為你的第一個 Emgo 程序創建一個新文件夾,隨后把示例中鏈接器腳本復制過來:
mkdir $HOME/firstemgocd $HOME/firstemgocp $EGPATH/src/stm32/examples/f030-demo-board/blinky/script.ld .
在 main.go
文件中創建一個最基本的程序:
package main func main() {}
文件編譯沒有出現任何問題:
$ egc$ arm-none-eabi-size cortexm0.elf text data bss dec hex filename 7452 172 104 7728 1e30 cortexm0.elf
第一次編譯可能會花點時間。編譯后產生的二進制占用了 7624 個字節的 Flash 空間(文本 + 數據)。對于一個什么都沒做的程序來說,占用的空間有些大。還剩下 8760 字節,可以用來做些有用的事。
不妨試試傳統的 “Hello, World!” 程序:
package main import "fmt" func main() { fmt.Println("Hello, World!")}
不幸的是,這次結果有些糟糕:
$ egc/usr/local/arm/bin/arm-none-eabi-ld: /home/michal/P/go/src/github.com/ziutek/emgo/egpath/src/stm32/examples/f030-demo-board/blog/cortexm0.elf section `.text' will not fit in region `Flash'/usr/local/arm/bin/arm-none-eabi-ld: region `Flash' overflowed by 10880 bytesexit status 1
“Hello, World!” 需要 STM32F030x6 上至少 32KB 的 Flash 空間。
fmt
包強制包含整個 strconv
和 reflect
包。這三個包,即使在精簡版本中的 Emgo 中,占用空間也很大。我們不能使用這個例子了。有很多的應用不需要好看的文本輸出。通常,一個或多個 LED,或者七段數碼管顯示就足夠了。不過,在第二部分,我會嘗試使用 strconv
包來格式化,并在 UART 上顯示一些數字和文本。
我們的開發板上有一個與 PA4 引腳和 VCC 相連的 LED。這次我們的代碼稍稍長了一些:
package main import ( "delay" "stm32/hal/gpio" "stm32/hal/system" "stm32/hal/system/timer/systick") var led gpio.Pin func init() { system.SetupPLL(8, 1, 48/8) systick.Setup(2e6) gpio.A.EnableClock(false) led = gpio.A.Pin(4) cfg := &gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain} led.Setup(cfg)} func main() { for { led.Clear() delay.Millisec(100) led.Set() delay.Millisec(900) }}
按照慣例,init
函數用來初始化和配置外設。
system.SetupPLL(8, 1, 48/8)
用來配置 RCC,將外部的 8 MHz 振蕩器的 PLL 作為系統時鐘源。PLL 分頻器設置為 1,倍頻數設置為 48/8 =6,這樣系統時鐘頻率為 48MHz。
systick.Setup(2e6)
將 Cortex-M SYSTICK 時鐘作為系統時鐘,每隔 2e6 次納秒運行一次(每秒鐘 500 次)。
gpio.A.EnableClock(false)
開啟了 GPIO A 口的時鐘。False
意味著這一時鐘在低功耗模式下會被禁用,但在 STM32F0 系列中并未實現這一功能。
led.Setup(cfg)
設置 PA4 引腳為開漏輸出。
led.Clear()
將 PA4 引腳設為低,在開漏設置中,打開 LED。
led.Set()
將 PA4 設為高電平狀態,關掉LED。
編譯這個代碼:
$ egc$ arm-none-eabi-size cortexm0.elf text data bss dec hex filename 9772 172 168 10112 2780 cortexm0.elf
正如你所看到的,這個閃爍程序占用了 2320 字節,比最基本程序占用空間要大。還有 6440 字節的剩余空間。
看看代碼是否能運行:
$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; reset run; exit'Open On-Chip Debugger 0.10.0+dev-00319-g8f1f912a (2018-03-07-19:20)Licensed under GNU GPL v2For bug reports, read http://openocd.org/doc/doxygen/bugs.htmldebug_level: 0adapter speed: 1000 kHzadapter_nsrst_delay: 100none separateadapter speed: 950 kHztarget halted due to debug-request, current mode: Thread xPSR: 0xc1000000 pc: 0x0800119c msp: 0x20000da0adapter speed: 4000 kHz** Programming Started **auto erase enabledtarget halted due to breakpoint, current mode: Thread xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000da0wrote 10240 bytes from file cortexm0.elf in 0.817425s (12.234 KiB/s)** Programming Finished **adapter speed: 950 kHz
如果你不是一個 Go 程序員,但你已經聽說過一些關于 Go 語言的事情,你可能會說:“Go 語法很好,但跟 C 比起來,并沒有明顯的提升。讓我看看 Go 語言的通道和協程!”
接下來我會一一展示:
import ( "delay" "stm32/hal/gpio" "stm32/hal/system" "stm32/hal/system/timer/systick") var led1, led2 gpio.Pin func init() { system.SetupPLL(8, 1, 48/8) systick.Setup(2e6) gpio.A.EnableClock(false) led1 = gpio.A.Pin(4) led2 = gpio.A.Pin(5) cfg := &gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain} led1.Setup(cfg) led2.Setup(cfg)} func blinky(led gpio.Pin, period int) { for { led.Clear() delay.Millisec(100) led.Set() delay.Millisec(period - 100) }} func main() { go blinky(led1, 500) blinky(led2, 1000)}
代碼改動很小: 添加了第二個 LED,上一個例子中的 main
函數被重命名為 blinky
并且需要提供兩個參數。 main
在新的協程中先調用 blinky
,所以兩個 LED 燈在并行使用。值得一提的是,gpio.Pin
可以同時訪問同一 GPIO 口的不同引腳。
Emgo 還有很多不足。其中之一就是你需要提前規定 goroutines(tasks)
的最大執行數量。是時候修改 script.ld
了:
ISRStack = 1024;MainStack = 1024;TaskStack = 1024;MaxTasks = 2; INCLUDE stm32/f030x4INCLUDE stm32/loadflashINCLUDE noos-cortexm
棧的大小需要靠猜,現在還不用關心這一點。
$ egc$ arm-none-eabi-size cortexm0.elf text data bss dec hex filename 10020 172 172 10364 287c cortexm0.elf
另一個 LED 和協程一共占用了 248 字節的 Flash 空間。
通道是 Go 語言中協程之間相互通信的一種推薦方式。Emgo 甚至能允許通過中斷處理來使用緩沖通道。下一個例子就展示了這種情況。
package main import ( "delay" "rtos" "stm32/hal/gpio" "stm32/hal/irq" "stm32/hal/system" "stm32/hal/system/timer/systick" "stm32/hal/tim") var ( leds [3]gpio.Pin timer *tim.Periph ch = make(chan int, 1)) func init() { system.SetupPLL(8, 1, 48/8) systick.Setup(2e6) gpio.A.EnableClock(false) leds[0] = gpio.A.Pin(4) leds[1] = gpio.A.Pin(5) leds[2] = gpio.A.Pin(9) cfg := &gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain} for _, led := range leds { led.Set() led.Setup(cfg) } timer = tim.TIM3 pclk := timer.Bus().Clock() if pclk < system.AHB.Clock() { pclk *= 2 } freq := uint(1e3) // Hz timer.EnableClock(true) timer.PSC.Store(tim.PSC(pclk/freq - 1)) timer.ARR.Store(700) // ms timer.DIER.Store(tim.UIE) timer.CR1.Store(tim.CEN) rtos.IRQ(irq.TIM3).Enable()} func blinky(led gpio.Pin, period int) { for range ch { led.Clear() delay.Millisec(100) led.Set() delay.Millisec(period - 100) }} func main() { go blinky(leds[1], 500) blinky(leds[2], 500)} func timerISR() { timer.SR.Store(0) leds[0].Set() select { case ch <- 0: // Success default: leds[0].Clear() }} //c:__attribute__((section(".ISRs")))var ISRs = [...]func(){ irq.TIM3: timerISR,}
與之前例子相比較下的不同:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
添加了第三個 LED,并連接到 PA9 引腳(UART 頭的 TXD 引腳)。
時鐘(TIM3
)作為中斷源。
新函數 timerISR
用來處理 irq.TIM3
的中斷。
新增容量為 1 的緩沖通道是為了 timerISR
和 blinky
協程之間的通信。
ISRs
數組作為中斷向量表,是更大的異常向量表的一部分。
blinky
中的 for
語句被替換成 range
語句。
為了方便起見,所有的 LED,或者說它們的引腳,都被放在 leds
這個數組里。另外,所有引腳在被配置為輸出之前,都設置為一種已知的初始狀態(高電平狀態)。
在這個例子里,我們想讓時鐘以 1 kHz 的頻率運行。為了配置 TIM3 預分頻器,我們需要知道它的輸入時鐘頻率。通過參考手冊我們知道,輸入時鐘頻率在 APBCLK = AHBCLK
時,與 APBCLK
相同,反之等于 2 倍的 APBCLK
。
如果 CNT 寄存器增加 1 kHz,那么 ARR 寄存器的值等于更新事件(重載事件)在毫秒中的計數周期。 為了讓更新事件產生中斷,必須要設置 DIER 寄存器中的 UIE 位。CEN 位能啟動時鐘。
時鐘外設在低功耗模式下必須啟用,為了自身能在 CPU 處于休眠時保持運行: timer.EnableClock(true)
。這在 STM32F0 中無關緊要,但對代碼可移植性卻十分重要。
timerISR
函數處理 irq.TIM3
的中斷請求。timer.SR.Store(0)
會清除 SR 寄存器里的所有事件標志,無效化向 NVIC 發出的所有中斷請求。憑借經驗,由于中斷請求無效的延時性,需要在程序一開始馬上清除所有的中斷標志。這避免了無意間再次調用處理。為了確保萬無一失,需要先清除標志,再讀取,但是在我們的例子中,清除標志就已經足夠了。
下面的這幾行代碼:
select {case ch <- 0: // Successdefault: leds[0].Clear()}
是 Go 語言中,如何在通道上非阻塞地發送消息的方法。中斷處理程序無法一直等待通道中的空余空間。如果通道已滿,則執行 default
,開發板上的LED就會開啟,直到下一次中斷。
ISRs
數組包含了中斷向量表。//c:__attribute__((section(".ISRs")))
會導致鏈接器將數組插入到 .ISRs
節中。
blinky
的 for
循環的新寫法:
for range ch { led.Clear() delay.Millisec(100) led.Set() delay.Millisec(period - 100)}
等價于:
for { _, ok := <-ch if !ok { break // Channel closed. } led.Clear() delay.Millisec(100) led.Set() delay.Millisec(period - 100)}
注意,在這個例子中,我們不在意通道中收到的值,我們只對其接受到的消息感興趣。我們可以在聲明時,將通道元素類型中的 int
用空結構體 struct{}
來代替,發送消息時,用 struct{}{}
結構體的值代替 0,但這部分對新手來說可能會有些陌生。
讓我們來編譯一下代碼:
$ egc$ arm-none-eabi-size cortexm0.elf text data bss dec hex filename 11096 228 188 11512 2cf8 cortexm0.elf
新的例子占用了 11324 字節的 Flash 空間,比上一個例子多占用了 1132 字節。
采用現在的時序,兩個閃爍協程從通道中獲取數據的速度,比 timerISR
發送數據的速度要快。所以它們在同時等待新數據,你還能觀察到 select
的隨機性,這也是 Go 規范所要求的。
STM32F030F4P6
開發板上的 LED 一直沒有亮起,說明通道從未出現過溢出。
我們可以加快消息發送的速度,將 timer.ARR.Store(700)
改為 timer.ARR.Store(200)
。 現在 timerISR
每秒鐘發送 5 條消息,但是兩個接收者加起來,每秒也只能接受 4 條消息。
到此,關于“怎么在極小硬件中運用Go語言”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。