您好,登錄后才能下訂單哦!
這篇文章主要講解了“Go語言在小硬件上的運用”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Go語言在小硬件上的運用”吧!
在本文的 第一部分 的結(jié)尾,我承諾要寫關(guān)于接口的內(nèi)容。我不想在這里寫有關(guān)接口或完整或簡短的講義。相反,我將展示一個簡單的示例,來說明如何定義和使用接口,以及如何利用無處不在的 io.Writer
接口。還有一些關(guān)于反射和半主機的內(nèi)容。
STM32F030F4P6
接口是 Go 語言的重要組成部分。如果你想了解更多有關(guān)它們的信息,我建議你閱讀《高效的 Go 編程》 和 Russ Cox 的文章。
當(dāng)你閱讀前面示例的代碼時,你可能會注意到一中打開或關(guān)閉 LED 的反直覺方式。 Set
方法用于關(guān)閉 LED,Clear
方法用于打開 LED。這是由于在 漏極開路配置 下驅(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()
,這不會再引起任何疑惑了。
在前面的所有示例中,我都嘗試使用相同的 漏極開路配置來避免代碼復(fù)雜化。但是在最后一個示例中,對于我來說,將第三個 LED 連接到 GND 和 PA3 引腳之間并將 PA3 配置為推挽模式會更容易。下一個示例將使用以此方式連接的 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))
在這種情況下,可賦值性在編譯時檢查。賦值后,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ù)位按鈕才能運行程序。
Interfaces
看來,st-flash
與此板配合使用有點不可靠(通常需要復(fù)位 ST-LINK 加密狗)。此外,當(dāng)前版本不會通過 SWD 發(fā)出復(fù)位命令(僅使用 NRST 信號)。軟件復(fù)位是不現(xiàn)實的,但是它通常是有效的,缺少它會將會帶來不便。對于板卡程序員 來說 OpenOCD 工作得更好。
UART(通用異步收發(fā)傳輸器)仍然是當(dāng)今微控制器最重要的外設(shè)之一。它的優(yōu)點是以下屬性的獨特組合:
相對較高的速度,
僅兩條信號線(在
半雙工通信的情況下甚至一條),
角色對稱,
關(guān)于新數(shù)據(jù)的
同步帶內(nèi)信令(起始位),
在傳輸
字內(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 芯片。
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 的 雙向 UART 代碼,請查看 此示例。
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..
由于大多數(shù)寫入的函數(shù)都使用 io.Writer
而不是具體類型(例如 C 中的 FILE
),因此我們獲得了類似于 Unix 流 的功能。在 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
Ultimate Blinky
是的,Emgo 支持 反射。reflect
包尚未完成,但是已完成的部分足以實現(xiàn) fmt.Print
函數(shù)族了。來看看我們可以在小型 MCU 上做什么。
為了減少內(nèi)存使用,我們將使用 半主機 作為標(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)時代也越來越重要。
這些就是我完成的本文的第二部分。我認(rèn)為有機會進(jìn)行第三部分,更具娛樂性的部分,在那里我們將各種有趣的設(shè)備連接到這塊板上。如果這塊板裝不下,我們就換一塊大一點的。
感謝各位的閱讀,以上就是“Go語言在小硬件上的運用”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Go語言在小硬件上的運用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
免責(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)容。