溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么在極小硬件中運(yùn)用Go語言

發(fā)布時(shí)間:2022-04-15 14:13:07 來源:億速云 閱讀:107 作者:iii 欄目:編程語言

這篇文章主要介紹“怎么在極小硬件中運(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

更多的 Go 語言編程

如果你不是一個(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,}

與之前例子相比較下的不同:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 添加了第三個(gè) LED,并連接到 PA9 引腳(UART 頭的 TXD 引腳)。

  3. 時(shí)鐘(TIM3)作為中斷源。

  4. 新函數(shù) timerISR 用來處理 irq.TIM3 的中斷。

  5. 新增容量為 1 的緩沖通道是為了 timerISR 和 blinky 協(xié)程之間的通信。

  6. ISRs 數(shù)組作為中斷向量表,是更大的異常向量表的一部分。

  7. 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í)用的文章!

向AI問一下細(xì)節(jié)

免責(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)容。

AI