溫馨提示×

溫馨提示×

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

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

Go語言在小硬件上的運用

發(fā)布時間:2021-06-23 09:05:57 來源:億速云 閱讀:105 作者:chen 欄目:編程語言

這篇文章主要講解了“Go語言在小硬件上的運用”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Go語言在小硬件上的運用”吧!

在本文的 第一部分 的結(jié)尾,我承諾要寫關(guān)于接口的內(nèi)容。我不想在這里寫有關(guān)接口或完整或簡短的講義。相反,我將展示一個簡單的示例,來說明如何定義和使用接口,以及如何利用無處不在的 io.Writer 接口。還有一些關(guān)于反射reflection半主機semihosting的內(nèi)容。

Go語言在小硬件上的運用

STM32F030F4P6

接口是 Go 語言的重要組成部分。如果你想了解更多有關(guān)它們的信息,我建議你閱讀《高效的 Go 編程》 和 Russ Cox 的文章。

并發(fā) Blinky – 回顧

當(dāng)你閱讀前面示例的代碼時,你可能會注意到一中打開或關(guān)閉 LED 的反直覺方式。 Set 方法用于關(guān)閉 LED,Clear 方法用于打開 LED。這是由于在 漏極開路配置open-drain configuration 下驅(qū)動了 LED。我們可以做些什么來減少代碼的混亂?讓我們用 On 和 Off 方法來定義 LED 類型:

type LED struct {    pin gpio.Pin} func (led LED) On() {    led.pin.Clear()} func (led LED) Off() {    led.pin.Set()}

現(xiàn)在我們可以簡單地調(diào)用 led.On() 和 led.Off(),這不會再引起任何疑惑了。

在前面的所有示例中,我都嘗試使用相同的 漏極開路配置open-drain configuration來避免代碼復(fù)雜化。但是在最后一個示例中,對于我來說,將第三個 LED 連接到 GND 和 PA3 引腳之間并將 PA3 配置為推挽模式push-pull mode會更容易。下一個示例將使用以此方式連接的 LED。

但是我們的新 LED 類型不支持推挽配置,實際上,我們應(yīng)該將其稱為 OpenDrainLED,并定義另一個類型 PushPullLED

type PushPullLED struct {    pin gpio.Pin} func (led PushPullLED) On() {    led.pin.Set()} func (led PushPullLED) Off() {    led.pin.Clear()}

請注意,這兩種類型都具有相同的方法,它們的工作方式也相同。如果在 LED 上運行的代碼可以同時使用這兩種類型,而不必注意當(dāng)前使用的是哪種類型,那就太好了。 接口類型可以提供幫助:

package main import (    "delay"     "stm32/hal/gpio"    "stm32/hal/system"    "stm32/hal/system/timer/systick") type LED interface {    On()    Off()} type PushPullLED struct{ pin gpio.Pin } func (led PushPullLED) On()  {    led.pin.Set()} func (led PushPullLED) Off() {    led.pin.Clear()} func MakePushPullLED(pin gpio.Pin) PushPullLED {    pin.Setup(&gpio.Config{Mode: gpio.Out, Driver: gpio.PushPull})    return PushPullLED{pin}} type OpenDrainLED struct{ pin gpio.Pin } func (led OpenDrainLED) On()  {    led.pin.Clear()} func (led OpenDrainLED) Off() {    led.pin.Set()} func MakeOpenDrainLED(pin gpio.Pin) OpenDrainLED {    pin.Setup(&gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain})    return OpenDrainLED{pin}} var led1, led2 LED func init() {    system.SetupPLL(8, 1, 48/8)    systick.Setup(2e6)     gpio.A.EnableClock(false)    led1 = MakeOpenDrainLED(gpio.A.Pin(4))    led2 = MakePushPullLED(gpio.A.Pin(3))} func blinky(led LED, period int) {    for {        led.On()        delay.Millisec(100)        led.Off()        delay.Millisec(period - 100)    }} func main() {    go blinky(led1, 500)    blinky(led2, 1000)}

我們定義了 LED 接口,它有兩個方法: On 和 Off。 PushPullLED 和 OpenDrainLED 類型代表兩種驅(qū)動 LED 的方式。我們還定義了兩個用作構(gòu)造函數(shù)的 Make*LED 函數(shù)。這兩種類型都實現(xiàn)了 LED 接口,因此可以將這些類型的值賦給 LED 類型的變量:

led1 = MakeOpenDrainLED(gpio.A.Pin(4))led2 = MakePushPullLED(gpio.A.Pin(3))

在這種情況下,可賦值性assignability在編譯時檢查。賦值后,led1 變量包含一個 OpenDrainLED{gpio.A.Pin(4)},以及一個指向 OpenDrainLED 類型的方法集的指針。 led1.On() 調(diào)用大致對應(yīng)于以下 C 代碼:

led1.methods->On(led1.value)

如你所見,如果僅考慮函數(shù)調(diào)用的開銷,這是相當(dāng)廉價的抽象。

但是,對接口的任何賦值都會導(dǎo)致包含有關(guān)已賦值類型的大量信息。對于由許多其他類型組成的復(fù)雜類型,可能會有很多信息:

$ egc$ arm-none-eabi-size cortexm0.elf   text    data     bss     dec     hex filename  10356     196     212   10764    2a0c cortexm0.elf

如果我們不使用 反射,可以通過避免包含類型和結(jié)構(gòu)字段的名稱來節(jié)省一些字節(jié):

$ egc -nf -nt$ arm-none-eabi-size cortexm0.elf   text    data     bss     dec     hex filename  10312     196     212   10720    29e0 cortexm0.elf

生成的二進(jìn)制文件仍然包含一些有關(guān)類型的必要信息和關(guān)于所有導(dǎo)出方法(帶有名稱)的完整信息。在運行時,主要是當(dāng)你將存儲在接口變量中的一個值賦值給任何其他變量時,需要此信息來檢查可賦值性。

我們還可以通過重新編譯所導(dǎo)入的包來刪除它們的類型和字段名稱:

$ cd $HOME/emgo$ ./clean.sh$ cd $HOME/firstemgo$ egc -nf -nt$ arm-none-eabi-size cortexm0.elf   text    data     bss     dec     hex filename  10272     196     212   10680    29b8 cortexm0.elf

讓我們加載這個程序,看看它是否按預(yù)期工作。這一次我們將使用 st-flash 命令:

$ arm-none-eabi-objcopy -O binary cortexm0.elf cortexm0.bin$ st-flash write cortexm0.bin 0x8000000st-flash 1.4.0-33-gd76e3c72018-04-10T22:04:34 INFO usb.c: -- exit_dfu_mode2018-04-10T22:04:34 INFO common.c: Loading device parameters....2018-04-10T22:04:34 INFO common.c: Device connected is: F0 small device, id 0x100064442018-04-10T22:04:34 INFO common.c: SRAM size: 0x1000 bytes (4 KiB), Flash: 0x4000 bytes (16 KiB) in pages of 1024 bytes2018-04-10T22:04:34 INFO common.c: Attempting to write 10468 (0x28e4) bytes to stm32 address: 134217728 (0x8000000)Flash page at addr: 0x08002800 erased2018-04-10T22:04:34 INFO common.c: Finished erasing 11 pages of 1024 (0x400) bytes2018-04-10T22:04:34 INFO common.c: Starting Flash write for VL/F0/F3/F1_XL core id2018-04-10T22:04:34 INFO flash_loader.c: Successfully loaded flash loader in sram 11/11 pages written2018-04-10T22:04:35 INFO common.c: Starting verification of write complete2018-04-10T22:04:35 INFO common.c: Flash written and verified! jolly good!

我沒有將 NRST 信號連接到編程器,因此無法使用 -reset 選項,必須按下復(fù)位按鈕才能運行程序。

Go語言在小硬件上的運用

Interfaces

看來,st-flash 與此板配合使用有點不可靠(通常需要復(fù)位 ST-LINK 加密狗)。此外,當(dāng)前版本不會通過 SWD 發(fā)出復(fù)位命令(僅使用 NRST 信號)。軟件復(fù)位是不現(xiàn)實的,但是它通常是有效的,缺少它會將會帶來不便。對于板卡程序員board-programmer 來說 OpenOCD 工作得更好。

UART

UART(通用異步收發(fā)傳輸器Universal Aynchronous Receiver-Transmitter)仍然是當(dāng)今微控制器最重要的外設(shè)之一。它的優(yōu)點是以下屬性的獨特組合:

  • 相對較高的速度,

  • 僅兩條信號線(在 

    半雙工half-duplex

     通信的情況下甚至一條),

  • 角色對稱,

  • 關(guān)于新數(shù)據(jù)的 

    同步帶內(nèi)信令synchronous in-band signaling

    (起始位),

  • 在傳輸 

    words

     內(nèi)的精確計時。

這使得最初用于傳輸由 7-9 位的字組成的異步消息的 UART,也被用于有效地實現(xiàn)各種其他物理協(xié)議,例如被 WS28xx LEDs 或 1-wire 設(shè)備使用的協(xié)議。

但是,我們將以其通常的角色使用 UART:從程序中打印文本消息。

package main import (    "io"    "rtos"     "stm32/hal/dma"    "stm32/hal/gpio"    "stm32/hal/irq"    "stm32/hal/system"    "stm32/hal/system/timer/systick"    "stm32/hal/usart") var tts *usart.Driver func init() {    system.SetupPLL(8, 1, 48/8)    systick.Setup(2e6)     gpio.A.EnableClock(true)    tx := gpio.A.Pin(9)     tx.Setup(&gpio.Config{Mode: gpio.Alt})    tx.SetAltFunc(gpio.USART1_AF1)    d := dma.DMA1    d.EnableClock(true)    tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)    tts.Periph().EnableClock(true)    tts.Periph().SetBaudRate(115200)    tts.Periph().Enable()    tts.EnableTx()     rtos.IRQ(irq.USART1).Enable()    rtos.IRQ(irq.DMA1_Channel2_3).Enable()} func main() {    io.WriteString(tts, "Hello, World!\r\n")} func ttsISR() {    tts.ISR()} func ttsDMAISR() {    tts.TxDMAISR()} //c:__attribute__((section(".ISRs")))var ISRs = [...]func(){    irq.USART1:          ttsISR,    irq.DMA1_Channel2_3: ttsDMAISR,}

你會發(fā)現(xiàn)此代碼可能有些復(fù)雜,但目前 STM32 HAL 中沒有更簡單的 UART 驅(qū)動程序(在某些情況下,簡單的輪詢驅(qū)動程序可能會很有用)。 usart.Driver 是使用 DMA 和中斷來減輕 CPU 負(fù)擔(dān)的高效驅(qū)動程序。

STM32 USART 外設(shè)提供傳統(tǒng)的 UART 及其同步版本。要將其用作輸出,我們必須將其 Tx 信號連接到正確的 GPIO 引腳:

tx.Setup(&gpio.Config{Mode: gpio.Alt})tx.SetAltFunc(gpio.USART1_AF1)

在 Tx-only 模式下配置 usart.Driver (rxdma 和 rxbuf 設(shè)置為 nil):

tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)

我們使用它的 WriteString 方法來打印這句名言。讓我們清理所有內(nèi)容并編譯該程序:

$ cd $HOME/emgo$ ./clean.sh$ cd $HOME/firstemgo$ egc$ arm-none-eabi-size cortexm0.elf  text       data        bss        dec        hex    filename  12728        236        176      13140       3354    cortexm0.elf

要查看某些內(nèi)容,你需要在 PC 中使用 UART 外設(shè)。

請勿使用 RS232 端口或 USB 轉(zhuǎn) RS232 轉(zhuǎn)換器!

STM32 系列使用 3.3V 邏輯,但是 RS232 可以產(chǎn)生 -15 V ~ +15 V 的電壓,這可能會損壞你的 MCU。你需要使用 3.3V 邏輯的 USB 轉(zhuǎn) UART 轉(zhuǎn)換器。流行的轉(zhuǎn)換器基于 FT232 或 CP2102 芯片。

Go語言在小硬件上的運用

UART

你還需要一些終端仿真程序(我更喜歡 picocom)。刷新新圖像,運行終端仿真器,然后按幾次復(fù)位按鈕:

$ 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: ThreadxPSR: 0xc1000000 pc: 0x080016f4 msp: 0x20000a20adapter speed: 4000 kHz** Programming Started **auto erase enabledtarget halted due to breakpoint, current mode: ThreadxPSR: 0x61000000 pc: 0x2000003a msp: 0x20000a20wrote 13312 bytes from file cortexm0.elf in 1.020185s (12.743 KiB/s)** Programming Finished **adapter speed: 950 kHz$$ picocom -b 115200 /dev/ttyUSB0picocom v3.1 port is        : /dev/ttyUSB0flowcontrol    : nonebaudrate is    : 115200parity is      : nonedatabits are   : 8stopbits are   : 1escape is      : C-alocal echo is  : nonoinit is      : nonoreset is     : nohangup is      : nonolock is      : nosend_cmd is    : sz -vvreceive_cmd is : rz -vv -Eimap is        :omap is        :emap is        : crcrlf,delbs,logfile is     : noneinitstring     : noneexit_after is  : not setexit is        : no Type [C-a] [C-h] to see available commandsTerminal readyHello, World!Hello, World!Hello, World!

每次按下復(fù)位按鈕都會產(chǎn)生新的 “Hello,World!”行。一切都在按預(yù)期進(jìn)行。

要查看此 MCU 的 雙向bi-directional UART 代碼,請查看 此示例。

io.Writer 接口

io.Writer 接口可能是 Go 中第二種最常用的接口類型,僅次于 error 接口。其定義如下所示:

type Writer interface {    Write(p []byte) (n int, err error)}

usart.Driver 實現(xiàn)了 io.Writer,因此我們可以替換:

tts.WriteString("Hello, World!\r\n")

io.WriteString(tts, "Hello, World!\r\n")

此外,你需要將 io 包添加到 import 部分。

io.WriteString 函數(shù)的聲明如下所示:

func WriteString(w Writer, s string) (n int, err error)

如你所見,io.WriteString 允許使用實現(xiàn)了 io.Writer 接口的任何類型來編寫字符串。在內(nèi)部,它檢查基礎(chǔ)類型是否具有 WriteString 方法,并使用該方法代替 Write(如果可用)。

讓我們編譯修改后的程序:

$ egc$ arm-none-eabi-size cortexm0.elf   text    data     bss     dec     hex filename  15456     320     248   16024    3e98 cortexm0.elf

如你所見,io.WriteString 導(dǎo)致二進(jìn)制文件的大小顯著增加:15776-12964 = 2812 字節(jié)。 Flash 上沒有太多空間了。是什么引起了這么大規(guī)模的增長?

使用這個命令:

arm-none-eabi-nm --print-size --size-sort --radix=d cortexm0.elf

我們可以打印兩種情況下按其大小排序的所有符號。通過過濾和分析獲得的數(shù)據(jù)(awk,diff),我們可以找到大約 80 個新符號。最大的十個如下所示:

> 00000062 T stm32$hal$usart$Driver$DisableRx> 00000072 T stm32$hal$usart$Driver$RxDMAISR> 00000076 T internal$Type$Implements> 00000080 T stm32$hal$usart$Driver$EnableRx> 00000084 t errors$New> 00000096 R $8$stm32$hal$usart$Driver$$> 00000100 T stm32$hal$usart$Error$Error> 00000360 T io$WriteString> 00000660 T stm32$hal$usart$Driver$Read

因此,即使我們不使用 usart.Driver.Read 方法,但它被編譯進(jìn)來了,與 DisableRx、RxDMAISR、EnableRx 以及上面未提及的其他方法一樣。不幸的是,如果你為接口賦值了一些內(nèi)容,就需要它的完整方法集(包含所有依賴項)。對于使用大多數(shù)方法的大型程序來說,這不是問題。但是對于我們這種極簡的情況而言,這是一個巨大的負(fù)擔(dān)。

我們已經(jīng)接近 MCU 的極限,但讓我們嘗試打印一些數(shù)字(你需要在 import 部分中用 strconv 替換 io 包):

func main() {    a := 12    b := -123     tts.WriteString("a = ")    strconv.WriteInt(tts, a, 10, 0, 0)    tts.WriteString("\r\n")    tts.WriteString("b = ")    strconv.WriteInt(tts, b, 10, 0, 0)    tts.WriteString("\r\n")     tts.WriteString("hex(a) = ")    strconv.WriteInt(tts, a, 16, 0, 0)    tts.WriteString("\r\n")    tts.WriteString("hex(b) = ")    strconv.WriteInt(tts, b, 16, 0, 0)    tts.WriteString("\r\n")}

與使用 io.WriteString 函數(shù)的情況一樣,strconv.WriteInt 的第一個參數(shù)的類型為 io.Writer。

$ egc/usr/local/arm/bin/arm-none-eabi-ld: /home/michal/firstemgo/cortexm0.elf section `.rodata' will not fit in region `Flash'/usr/local/arm/bin/arm-none-eabi-ld: region `Flash' overflowed by 692 bytesexit status 1

這一次我們的空間超出的不多。讓我們試著精簡一下有關(guān)類型的信息:

$ cd $HOME/emgo$ ./clean.sh$ cd $HOME/firstemgo$ egc -nf -nt$ arm-none-eabi-size cortexm0.elf   text    data     bss     dec     hex filename  15876     316     320   16512    4080 cortexm0.elf

很接近,但很合適。讓我們加載并運行此代碼:

a = 12b = -123hex(a) = chex(b) = -7b

Emgo 中的 strconv 包與 Go 中的原型有很大的不同。它旨在直接用于寫入格式化的數(shù)字,并且在許多情況下可以替換沉重的 fmt 包。 這就是為什么函數(shù)名稱以 Write 而不是 Format 開頭,并具有額外的兩個參數(shù)的原因。 以下是其用法示例:

func main() {    b := -123    strconv.WriteInt(tts, b, 10, 0, 0)    tts.WriteString("\r\n")    strconv.WriteInt(tts, b, 10, 6, ' ')    tts.WriteString("\r\n")    strconv.WriteInt(tts, b, 10, 6, '0')    tts.WriteString("\r\n")    strconv.WriteInt(tts, b, 10, 6, '.')    tts.WriteString("\r\n")    strconv.WriteInt(tts, b, 10, -6, ' ')    tts.WriteString("\r\n")    strconv.WriteInt(tts, b, 10, -6, '0')    tts.WriteString("\r\n")    strconv.WriteInt(tts, b, 10, -6, '.')    tts.WriteString("\r\n")}

下面是它的輸出:

-123  -123-00123..-123-123-123-123..

Unix 流 和 莫爾斯電碼Morse code

由于大多數(shù)寫入的函數(shù)都使用 io.Writer 而不是具體類型(例如 C 中的 FILE ),因此我們獲得了類似于 Unix stream 的功能。在 Unix 中,我們可以輕松地組合簡單的命令來執(zhí)行更大的任務(wù)。例如,我們可以通過以下方式將文本寫入文件:

echo "Hello, World!" > file.txt

> 操作符將前面命令的輸出流寫入文件。還有 | 操作符,用于連接相鄰命令的輸出流和輸入流。

多虧了流,我們可以輕松地轉(zhuǎn)換/過濾任何命令的輸出。例如,要將所有字母轉(zhuǎn)換為大寫,我們可以通過 tr 命令過濾 echo 的輸出:

echo "Hello, World!" | tr a-z A-Z > file.txt

為了顯示 io.Writer 和 Unix 流之間的類比,讓我們編寫以下代碼:

io.WriteString(tts, "Hello, World!\r\n")

采用以下偽 unix 形式:

io.WriteString "Hello, World!" | usart.Driver usart.USART1

下一個示例將顯示如何執(zhí)行此操作:

io.WriteString "Hello, World!" | MorseWriter | usart.Driver usart.USART1

讓我們來創(chuàng)建一個簡單的編碼器,它使用莫爾斯電碼對寫入的文本進(jìn)行編碼:

type MorseWriter struct {    W io.Writer} func (w *MorseWriter) Write(s []byte) (int, error) {    var buf [8]byte    for n, c := range s {        switch {        case c == '\n':            c = ' ' // Replace new lines with spaces.        case 'a' <= c && c <= 'z':            c -= 'a' - 'A' // Convert to upper case.        }        if c < ' ' || 'Z' < c {            continue // c is outside ASCII [' ', 'Z']        }        var symbol morseSymbol        if c == ' ' {            symbol.length = 1            buf[0] = ' '        } else {            symbol = morseSymbols[c-'!']            for i := uint(0); i < uint(symbol.length); i++ {                if (symbol.code>>i)&1 != 0 {                    buf[i] = '-'                } else {                    buf[i] = '.'                }            }        }        buf[symbol.length] = ' '        if _, err := w.W.Write(buf[:symbol.length+1]); err != nil {            return n, err        }    }    return len(s), nil} type morseSymbol struct {    code, length byte} //emgo:constvar morseSymbols = [...]morseSymbol{    {1<<0 | 1<<1 | 1<<2, 4}, // ! ---.    {1<<1 | 1<<4, 6},        // " .-..-.    {},                      // #    {1<<3 | 1<<6, 7},        // $ ...-..-     // Some code omitted...     {1<<0 | 1<<3, 4},        // X -..-    {1<<0 | 1<<2 | 1<<3, 4}, // Y -.--    {1<<0 | 1<<1, 4},        // Z --..}

你可以在 這里 找到完整的 morseSymbols 數(shù)組。 //emgo:const 指令確保 morseSymbols 數(shù)組不會被復(fù)制到 RAM 中。

現(xiàn)在我們可以通過兩種方式打印句子:

func main() {    s := "Hello, World!\r\n"    mw := &MorseWriter{tts}     io.WriteString(tts, s)    io.WriteString(mw, s)}

我們使用指向 MorseWriter &MorseWriter{tts} 的指針而不是簡單的 MorseWriter{tts} 值,因為 MorseWriter 太大,不適合接口變量。

與 Go 不同,Emgo 不會為存儲在接口變量中的值動態(tài)分配內(nèi)存。接口類型的大小受限制,相當(dāng)于三個指針(適合 slice )或兩個 float64(適合 complex128)的大小,以較大者為準(zhǔn)。它可以直接存儲所有基本類型和小型 “結(jié)構(gòu)體/數(shù)組” 的值,但是對于較大的值,你必須使用指針。

讓我們編譯此代碼并查看其輸出:

$ egc$ arm-none-eabi-size cortexm0.elf   text    data     bss     dec     hex filename  15152     324     248   15724    3d6c cortexm0.elf
Hello, World!.... . .-.. .-.. --- --..--   .-- --- .-. .-.. -.. ---.

終極閃爍

Blinky 是等效于 “Hello,World!” 程序的硬件。一旦有了摩爾斯編碼器,我們就可以輕松地將兩者結(jié)合起來以獲得終極閃爍程序:

package main import (    "delay"    "io"     "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, Speed: gpio.Low}    led.Setup(&cfg)} type Telegraph struct {    Pin   gpio.Pin    Dotms int // Dot length [ms]} func (t Telegraph) Write(s []byte) (int, error) {    for _, c := range s {        switch c {        case '.':            t.Pin.Clear()            delay.Millisec(t.Dotms)            t.Pin.Set()            delay.Millisec(t.Dotms)        case '-':            t.Pin.Clear()            delay.Millisec(3 * t.Dotms)            t.Pin.Set()            delay.Millisec(t.Dotms)        case ' ':            delay.Millisec(3 * t.Dotms)        }    }    return len(s), nil} func main() {    telegraph := &MorseWriter{Telegraph{led, 100}}    for {        io.WriteString(telegraph, "Hello, World! ")    }} // Some code omitted...

在上面的示例中,我省略了 MorseWriter 類型的定義,因為它已在前面展示過。完整版可通過 這里 獲取。讓我們編譯它并運行:

$ egc$ arm-none-eabi-size cortexm0.elf   text    data     bss     dec     hex filename  11772     244     244   12260    2fe4 cortexm0.elf

Go語言在小硬件上的運用

Ultimate Blinky

反射

是的,Emgo 支持 反射。reflect 包尚未完成,但是已完成的部分足以實現(xiàn) fmt.Print 函數(shù)族了。來看看我們可以在小型 MCU 上做什么。

為了減少內(nèi)存使用,我們將使用 半主機semihosting 作為標(biāo)準(zhǔn)輸出。為了方便起見,我們還編寫了簡單的 println 函數(shù),它在某種程度上類似于 fmt.Println。

package main import (    "debug/semihosting"    "reflect"    "strconv"     "stm32/hal/system"    "stm32/hal/system/timer/systick") var stdout semihosting.File func init() {    system.SetupPLL(8, 1, 48/8)    systick.Setup(2e6)     var err error    stdout, err = semihosting.OpenFile(":tt", semihosting.W)    for err != nil {    }} type stringer interface {    String() string} func println(args ...interface{}) {    for i, a := range args {        if i > 0 {            stdout.WriteString(" ")        }        switch v := a.(type) {        case string:            stdout.WriteString(v)        case int:            strconv.WriteInt(stdout, v, 10, 0, 0)        case bool:            strconv.WriteBool(stdout, v, 't', 0, 0)        case stringer:            stdout.WriteString(v.String())        default:            stdout.WriteString("%unknown")        }    }    stdout.WriteString("\r\n")} type S struct {    A int    B bool} func main() {    p := &S{-123, true}     v := reflect.ValueOf(p)     println("kind(p) =", v.Kind())    println("kind(*p) =", v.Elem().Kind())    println("type(*p) =", v.Elem().Type())     v = v.Elem()     println("*p = {")    for i := 0; i < v.NumField(); i++ {        ft := v.Type().Field(i)        fv := v.Field(i)        println("  ", ft.Name(), ":", fv.Interface())    }    println("}")}

semihosting.OpenFile 函數(shù)允許在主機端打開/創(chuàng)建文件。特殊路徑 :tt 對應(yīng)于主機的標(biāo)準(zhǔn)輸出。

println 函數(shù)接受任意數(shù)量的參數(shù),每個參數(shù)的類型都是任意的:

func println(args ...interface{})

可能是因為任何類型都實現(xiàn)了空接口 interface{}。 println 使用 類型開關(guān) 打印字符串,整數(shù)和布爾值:

switch v := a.(type) {case string:    stdout.WriteString(v)case int:    strconv.WriteInt(stdout, v, 10, 0, 0)case bool:    strconv.WriteBool(stdout, v, 't', 0, 0)case stringer:    stdout.WriteString(v.String())default:    stdout.WriteString("%unknown")}

此外,它還支持任何實現(xiàn)了 stringer 接口的類型,即任何具有 String() 方法的類型。在任何 case 子句中,v 變量具有正確的類型,與 case 關(guān)鍵字后列出的類型相同。

reflect.ValueOf(p) 函數(shù)通過允許以編程的方式分析其類型和內(nèi)容的形式返回 p。如你所見,我們甚至可以使用 v.Elem() 取消引用指針,并打印所有結(jié)構(gòu)體及其名稱。

讓我們嘗試編譯這段代碼?,F(xiàn)在讓我們看看如果編譯時沒有類型和字段名,會有什么結(jié)果:

$ egc -nt -nf$ arm-none-eabi-size cortexm0.elf   text    data     bss     dec     hex filename  16028     216     312   16556    40ac cortexm0.elf

閃存上只剩下 140 個可用字節(jié)。讓我們使用啟用了半主機的 OpenOCD 加載它:

$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; arm semihosting enable; reset run'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: ThreadxPSR: 0xc1000000 pc: 0x08002338 msp: 0x20000a20adapter speed: 4000 kHz** Programming Started **auto erase enabledtarget halted due to breakpoint, current mode: ThreadxPSR: 0x61000000 pc: 0x2000003a msp: 0x20000a20wrote 16384 bytes from file cortexm0.elf in 0.700133s (22.853 KiB/s)** Programming Finished **semihosting is enabledadapter speed: 950 kHzkind(p) = ptrkind(*p) = structtype(*p) =*p = {   X. : -123   X. : true}

如果你實際運行此代碼,則會注意到半主機運行緩慢,尤其是在逐字節(jié)寫入時(緩沖很有用)。

如你所見,*p 沒有類型名稱,并且所有結(jié)構(gòu)字段都具有相同的 X. 名稱。讓我們再次編譯該程序,這次不帶 -nt -nf 選項:

$ egc$ arm-none-eabi-size cortexm0.elf   text    data     bss     dec     hex filename  16052     216     312   16580    40c4 cortexm0.elf

現(xiàn)在已經(jīng)包括了類型和字段名稱,但僅在 main.go 文件中 main 包中定義了它們。該程序的輸出如下所示:

kind(p) = ptrkind(*p) = structtype(*p) = S*p = {   A : -123   B : true}

反射是任何易于使用的序列化庫的關(guān)鍵部分,而像 JSON 這樣的序列化 算法 在物聯(lián)網(wǎng)IoT時代也越來越重要。

這些就是我完成的本文的第二部分。我認(rèn)為有機會進(jìn)行第三部分,更具娛樂性的部分,在那里我們將各種有趣的設(shè)備連接到這塊板上。如果這塊板裝不下,我們就換一塊大一點的。

感謝各位的閱讀,以上就是“Go語言在小硬件上的運用”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Go語言在小硬件上的運用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

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

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI