您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么在極小硬件中運(yùn)用Go語言”,在日常操作中,相信很多人在怎么在極小硬件中運(yùn)用Go語言問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”怎么在極小硬件中運(yùn)用Go語言”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
STM32F030F4P6 給人留下了很深的印象:
CPU: Cortex M0 48 MHz(最低配置,只有 12000 個(gè)邏輯門電路)
RAM: 4 KB,
Flash: 16 KB,
ADC、SPI、I2C、USART 和幾個(gè)定時(shí)器
以上這些采用了 TSSOP20 封裝。正如你所見,這是一個(gè)很小的 32 位系統(tǒng)。
如果你想知道如何在這塊開發(fā)板上使用 Go 編程,你需要反復(fù)閱讀硬件規(guī)范手冊(cè)。你必須面對(duì)這樣的真實(shí)情況:在 Go 編譯器中給 Cortex-M0 提供支持的可能性很小。而且,這還僅僅只是第一個(gè)要解決的問題。
我會(huì)使用 Emgo,但別擔(dān)心,之后你會(huì)看到,它如何讓 Go 在如此小的系統(tǒng)上盡可能發(fā)揮作用。
在我拿到這塊開發(fā)板之前,對(duì) stm32/hal 系列下的 F0 MCU 沒有任何支持。在簡(jiǎn)單研究參考手冊(cè)后,我發(fā)現(xiàn) STM32F0 系列是 STM32F3 削減版,這讓在新端口上開發(fā)的工作變得容易了一些。
如果你想接著本文的步驟做下去,需要先安裝 Emgo
cd $HOMEgit clone https://github.com/ziutek/emgo/cd emgo/egcgo install
然后設(shè)置一下環(huán)境變量
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
更詳細(xì)的說明可以在 Emgo 官網(wǎng)上找到。
要確保 egc
在你的 PATH
中。 你可以使用 go build
來代替 go install
,然后把 egc
復(fù)制到你的 $HOME/bin
或 /usr/local/bin
中。
現(xiàn)在,為你的第一個(gè) Emgo 程序創(chuàng)建一個(gè)新文件夾,隨后把示例中鏈接器腳本復(fù)制過來:
mkdir $HOME/firstemgocd $HOME/firstemgocp $EGPATH/src/stm32/examples/f030-demo-board/blinky/script.ld .
在 main.go
文件中創(chuàng)建一個(gè)最基本的程序:
package main func main() {}
文件編譯沒有出現(xiàn)任何問題:
$ egc$ arm-none-eabi-size cortexm0.elf text data bss dec hex filename 7452 172 104 7728 1e30 cortexm0.elf
第一次編譯可能會(huì)花點(diǎn)時(shí)間。編譯后產(chǎn)生的二進(jìn)制占用了 7624 個(gè)字節(jié)的 Flash 空間(文本 + 數(shù)據(jù))。對(duì)于一個(gè)什么都沒做的程序來說,占用的空間有些大。還剩下 8760 字節(jié),可以用來做些有用的事。
不妨試試傳統(tǒng)的 “Hello, World!” 程序:
package main import "fmt" func main() { fmt.Println("Hello, World!")}
不幸的是,這次結(jié)果有些糟糕:
$ 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
包強(qiáng)制包含整個(gè) strconv
和 reflect
包。這三個(gè)包,即使在精簡(jiǎn)版本中的 Emgo 中,占用空間也很大。我們不能使用這個(gè)例子了。有很多的應(yīng)用不需要好看的文本輸出。通常,一個(gè)或多個(gè) LED,或者七段數(shù)碼管顯示就足夠了。不過,在第二部分,我會(huì)嘗試使用 strconv
包來格式化,并在 UART 上顯示一些數(shù)字和文本。
我們的開發(fā)板上有一個(gè)與 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
函數(shù)用來初始化和配置外設(shè)。
system.SetupPLL(8, 1, 48/8)
用來配置 RCC,將外部的 8 MHz 振蕩器的 PLL 作為系統(tǒng)時(shí)鐘源。PLL 分頻器設(shè)置為 1,倍頻數(shù)設(shè)置為 48/8 =6,這樣系統(tǒng)時(shí)鐘頻率為 48MHz。
systick.Setup(2e6)
將 Cortex-M SYSTICK 時(shí)鐘作為系統(tǒng)時(shí)鐘,每隔 2e6 次納秒運(yùn)行一次(每秒鐘 500 次)。
gpio.A.EnableClock(false)
開啟了 GPIO A 口的時(shí)鐘。False
意味著這一時(shí)鐘在低功耗模式下會(huì)被禁用,但在 STM32F0 系列中并未實(shí)現(xiàn)這一功能。
led.Setup(cfg)
設(shè)置 PA4 引腳為開漏輸出。
led.Clear()
將 PA4 引腳設(shè)為低,在開漏設(shè)置中,打開 LED。
led.Set()
將 PA4 設(shè)為高電平狀態(tài),關(guān)掉LED。
編譯這個(gè)代碼:
$ egc$ arm-none-eabi-size cortexm0.elf text data bss dec hex filename 9772 172 168 10112 2780 cortexm0.elf
正如你所看到的,這個(gè)閃爍程序占用了 2320 字節(jié),比最基本程序占用空間要大。還有 6440 字節(jié)的剩余空間。
看看代碼是否能運(yùn)行:
$ 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
如果你不是一個(gè) Go 程序員,但你已經(jīng)聽說過一些關(guān)于 Go 語言的事情,你可能會(huì)說:“Go 語法很好,但跟 C 比起來,并沒有明顯的提升。讓我看看 Go 語言的通道和協(xié)程!”
接下來我會(huì)一一展示:
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)}
代碼改動(dòng)很小: 添加了第二個(gè) LED,上一個(gè)例子中的 main
函數(shù)被重命名為 blinky
并且需要提供兩個(gè)參數(shù)。 main
在新的協(xié)程中先調(diào)用 blinky
,所以兩個(gè) LED 燈在并行使用。值得一提的是,gpio.Pin
可以同時(shí)訪問同一 GPIO 口的不同引腳。
Emgo 還有很多不足。其中之一就是你需要提前規(guī)定 goroutines(tasks)
的最大執(zhí)行數(shù)量。是時(shí)候修改 script.ld
了:
ISRStack = 1024;MainStack = 1024;TaskStack = 1024;MaxTasks = 2; INCLUDE stm32/f030x4INCLUDE stm32/loadflashINCLUDE noos-cortexm
棧的大小需要靠猜,現(xiàn)在還不用關(guān)心這一點(diǎn)。
$ egc$ arm-none-eabi-size cortexm0.elf text data bss dec hex filename 10020 172 172 10364 287c cortexm0.elf
另一個(gè) LED 和協(xié)程一共占用了 248 字節(jié)的 Flash 空間。
通道是 Go 語言中協(xié)程之間相互通信的一種推薦方式。Emgo 甚至能允許通過中斷處理來使用緩沖通道。下一個(gè)例子就展示了這種情況。
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,}
與之前例子相比較下的不同:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
添加了第三個(gè) LED,并連接到 PA9 引腳(UART 頭的 TXD 引腳)。
時(shí)鐘(TIM3
)作為中斷源。
新函數(shù) timerISR
用來處理 irq.TIM3
的中斷。
新增容量為 1 的緩沖通道是為了 timerISR
和 blinky
協(xié)程之間的通信。
ISRs
數(shù)組作為中斷向量表,是更大的異常向量表的一部分。
blinky
中的 for
語句被替換成 range
語句。
為了方便起見,所有的 LED,或者說它們的引腳,都被放在 leds
這個(gè)數(shù)組里。另外,所有引腳在被配置為輸出之前,都設(shè)置為一種已知的初始狀態(tài)(高電平狀態(tài))。
在這個(gè)例子里,我們想讓時(shí)鐘以 1 kHz 的頻率運(yùn)行。為了配置 TIM3 預(yù)分頻器,我們需要知道它的輸入時(shí)鐘頻率。通過參考手冊(cè)我們知道,輸入時(shí)鐘頻率在 APBCLK = AHBCLK
時(shí),與 APBCLK
相同,反之等于 2 倍的 APBCLK
。
如果 CNT 寄存器增加 1 kHz,那么 ARR 寄存器的值等于更新事件(重載事件)在毫秒中的計(jì)數(shù)周期。 為了讓更新事件產(chǎn)生中斷,必須要設(shè)置 DIER 寄存器中的 UIE 位。CEN 位能啟動(dòng)時(shí)鐘。
時(shí)鐘外設(shè)在低功耗模式下必須啟用,為了自身能在 CPU 處于休眠時(shí)保持運(yùn)行: timer.EnableClock(true)
。這在 STM32F0 中無關(guān)緊要,但對(duì)代碼可移植性卻十分重要。
timerISR
函數(shù)處理 irq.TIM3
的中斷請(qǐng)求。timer.SR.Store(0)
會(huì)清除 SR 寄存器里的所有事件標(biāo)志,無效化向 NVIC 發(fā)出的所有中斷請(qǐng)求。憑借經(jīng)驗(yàn),由于中斷請(qǐng)求無效的延時(shí)性,需要在程序一開始馬上清除所有的中斷標(biāo)志。這避免了無意間再次調(diào)用處理。為了確保萬無一失,需要先清除標(biāo)志,再讀取,但是在我們的例子中,清除標(biāo)志就已經(jīng)足夠了。
下面的這幾行代碼:
select {case ch <- 0: // Successdefault: leds[0].Clear()}
是 Go 語言中,如何在通道上非阻塞地發(fā)送消息的方法。中斷處理程序無法一直等待通道中的空余空間。如果通道已滿,則執(zhí)行 default
,開發(fā)板上的LED就會(huì)開啟,直到下一次中斷。
ISRs
數(shù)組包含了中斷向量表。//c:__attribute__((section(".ISRs")))
會(huì)導(dǎo)致鏈接器將數(shù)組插入到 .ISRs
節(jié)中。
blinky
的 for
循環(huán)的新寫法:
for range ch { led.Clear() delay.Millisec(100) led.Set() delay.Millisec(period - 100)}
等價(jià)于:
for { _, ok := <-ch if !ok { break // Channel closed. } led.Clear() delay.Millisec(100) led.Set() delay.Millisec(period - 100)}
注意,在這個(gè)例子中,我們不在意通道中收到的值,我們只對(duì)其接受到的消息感興趣。我們可以在聲明時(shí),將通道元素類型中的 int
用空結(jié)構(gòu)體 struct{}
來代替,發(fā)送消息時(shí),用 struct{}{}
結(jié)構(gòu)體的值代替 0,但這部分對(duì)新手來說可能會(huì)有些陌生。
讓我們來編譯一下代碼:
$ egc$ arm-none-eabi-size cortexm0.elf text data bss dec hex filename 11096 228 188 11512 2cf8 cortexm0.elf
新的例子占用了 11324 字節(jié)的 Flash 空間,比上一個(gè)例子多占用了 1132 字節(jié)。
采用現(xiàn)在的時(shí)序,兩個(gè)閃爍協(xié)程從通道中獲取數(shù)據(jù)的速度,比 timerISR
發(fā)送數(shù)據(jù)的速度要快。所以它們?cè)谕瑫r(shí)等待新數(shù)據(jù),你還能觀察到 select
的隨機(jī)性,這也是 Go 規(guī)范所要求的。
STM32F030F4P6
開發(fā)板上的 LED 一直沒有亮起,說明通道從未出現(xiàn)過溢出。
我們可以加快消息發(fā)送的速度,將 timer.ARR.Store(700)
改為 timer.ARR.Store(200)
。 現(xiàn)在 timerISR
每秒鐘發(fā)送 5 條消息,但是兩個(gè)接收者加起來,每秒也只能接受 4 條消息。
到此,關(guān)于“怎么在極小硬件中運(yùn)用Go語言”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。