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

目前創(chuàng)新互聯(lián)已為近1000家的企業(yè)提供了網(wǎng)站建設(shè)、域名、虛擬主機(jī)、網(wǎng)站改版維護(hù)、企業(yè)網(wǎng)站設(shè)計(jì)、平樂網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
STM32F030F4P6
接口是 Go 語言的重要組成部分。如果你想了解更多有關(guān)它們的信息,我建議你閱讀《高效的 Go 編程》 和 Russ Cox 的文章。
并發(fā) Blinky – 回顧
當(dāng)你閱讀前面示例的代碼時(shí),你可能會(huì)注意到一中打開或關(guān)閉 LED 的反直覺方式。 Set 方法用于關(guān)閉 LED,Clear 方法用于打開 LED。這是由于在 漏極開路配置open-drain configuration 下驅(qū)動(dòng)了 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(),這不會(huì)再引起任何疑惑了。
在前面的所有示例中,我都嘗試使用相同的 漏極開路配置open-drain configuration來避免代碼復(fù)雜化。但是在最后一個(gè)示例中,對(duì)于我來說,將第三個(gè) LED 連接到 GND 和 PA3 引腳之間并將 PA3 配置為推挽模式push-pull mode會(huì)更容易。下一個(gè)示例將使用以此方式連接的 LED。
但是我們的新 LED 類型不支持推挽配置,實(shí)際上,我們應(yīng)該將其稱為 OpenDrainLED,并定義另一個(gè)類型 PushPullLED:
type PushPullLED struct {pin gpio.Pin}func (led PushPullLED) On() {led.pin.Set()}func (led PushPullLED) Off() {led.pin.Clear()}
請(qǐng)注意,這兩種類型都具有相同的方法,它們的工作方式也相同。如果在 LED 上運(yùn)行的代碼可以同時(shí)使用這兩種類型,而不必注意當(dāng)前使用的是哪種類型,那就太好了。 接口類型可以提供幫助:
package mainimport ("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 LEDfunc 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 接口,它有兩個(gè)方法: On 和 Off。 PushPullLED 和 OpenDrainLED 類型代表兩種驅(qū)動(dòng) LED 的方式。我們還定義了兩個(gè)用作構(gòu)造函數(shù)的 Make*LED 函數(shù)。這兩種類型都實(shí)現(xiàn)了 LED 接口,因此可以將這些類型的值賦給 LED 類型的變量:
led1 = MakeOpenDrainLED(gpio.A.Pin(4))led2 = MakePushPullLED(gpio.A.Pin(3))
在這種情況下,可賦值性assignability在編譯時(shí)檢查。賦值后,led1 變量包含一個(gè) OpenDrainLED{gpio.A.Pin(4)},以及一個(gè)指向 OpenDrainLED 類型的方法集的指針。 led1.On() 調(diào)用大致對(duì)應(yīng)于以下 C 代碼:
led1.methods->On(led1.value)
如你所見,如果僅考慮函數(shù)調(diào)用的開銷,這是相當(dāng)廉價(jià)的抽象。
但是,對(duì)接口的任何賦值都會(huì)導(dǎo)致包含有關(guān)已賦值類型的大量信息。對(duì)于由許多其他類型組成的復(fù)雜類型,可能會(huì)有很多信息:
$ egc$ arm-none-eabi-size cortexm0.elftext data bss dec hex filename10356 196 212 10764 2a0c cortexm0.elf
如果我們不使用 反射,可以通過避免包含類型和結(jié)構(gòu)字段的名稱來節(jié)省一些字節(jié):
$ egc -nf -nt$ arm-none-eabi-size cortexm0.elftext data bss dec hex filename10312 196 212 10720 29e0 cortexm0.elf
生成的二進(jìn)制文件仍然包含一些有關(guān)類型的必要信息和關(guān)于所有導(dǎo)出方法(帶有名稱)的完整信息。在運(yùn)行時(shí),主要是當(dāng)你將存儲(chǔ)在接口變量中的一個(gè)值賦值給任何其他變量時(shí),需要此信息來檢查可賦值性。
我們還可以通過重新編譯所導(dǎo)入的包來刪除它們的類型和字段名稱:
$ cd $HOME/emgo$ ./clean.sh$ cd $HOME/firstemgo$ egc -nf -nt$ arm-none-eabi-size cortexm0.elftext data bss dec hex filename10272 196 212 10680 29b8 cortexm0.elf
讓我們加載這個(gè)程序,看看它是否按預(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 sram11/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 信號(hào)連接到編程器,因此無法使用 -reset 選項(xiàng),必須按下復(fù)位按鈕才能運(yùn)行程序。
Interfaces
看來,st-flash 與此板配合使用有點(diǎn)不可靠(通常需要復(fù)位 ST-LINK 加密狗)。此外,當(dāng)前版本不會(huì)通過 SWD 發(fā)出復(fù)位命令(僅使用 NRST 信號(hào))。軟件復(fù)位是不現(xiàn)實(shí)的,但是它通常是有效的,缺少它會(huì)將會(huì)帶來不便。對(duì)于板卡程序員board-programmer 來說 OpenOCD 工作得更好。
UART
UART(通用異步收發(fā)傳輸器Universal Aynchronous Receiver-Transmitter)仍然是當(dāng)今微控制器最重要的外設(shè)之一。它的優(yōu)點(diǎn)是以下屬性的獨(dú)特組合:
- 相對(duì)較高的速度,
- 僅兩條信號(hào)線(在 半雙工half-duplex 通信的情況下甚至一條),
- 角色對(duì)稱,
- 關(guān)于新數(shù)據(jù)的 同步帶內(nèi)信令synchronous in-band signaling(起始位),
- 在傳輸 字words 內(nèi)的精確計(jì)時(shí)。
這使得最初用于傳輸由 7-9 位的字組成的異步消息的 UART,也被用于有效地實(shí)現(xiàn)各種其他物理協(xié)議,例如被 WS28xx LEDs 或 1-wire 設(shè)備使用的協(xié)議。
但是,我們將以其通常的角色使用 UART:從程序中打印文本消息。
package mainimport ("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.Driverfunc 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.DMA1d.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,}
你會(huì)發(fā)現(xiàn)此代碼可能有些復(fù)雜,但目前 STM32 HAL 中沒有更簡單的 UART 驅(qū)動(dòng)程序(在某些情況下,簡單的輪詢驅(qū)動(dòng)程序可能會(huì)很有用)。 usart.Driver 是使用 DMA 和中斷來減輕 CPU 負(fù)擔(dān)的高效驅(qū)動(dòng)程序。
STM32 USART 外設(shè)提供傳統(tǒng)的 UART 及其同步版本。要將其用作輸出,我們必須將其 Tx 信號(hào)連接到正確的 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.elftext data bss dec hex filename12728 236 176 13140 3354 cortexm0.elf
要查看某些內(nèi)容,你需要在 PC 中使用 UART 外設(shè)。
請(qǐng)勿使用 RS232 端口或 USB 轉(zhuǎn) RS232 轉(zhuǎn)換器!
STM32 系列使用 3.3V 邏輯,但是 RS232 可以產(chǎn)生 -15 V ~ +15 V 的電壓,這可能會(huì)損壞你的 MCU。你需要使用 3.3V 邏輯的 USB 轉(zhuǎn) UART 轉(zhuǎn)換器。流行的轉(zhuǎn)換器基于 FT232 或 CP2102 芯片。
UART
你還需要一些終端仿真程序(我更喜歡 picocom)。刷新新圖像,運(yùn)行終端仿真器,然后按幾次復(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, readhttp://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.1port 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 : noType [C-a] [C-h] to see available commandsTerminal readyHello, World!Hello, World!Hello, World!
每次按下復(fù)位按鈕都會(huì)產(chǎn)生新的 “Hello,World!”行。一切都在按預(yù)期進(jìn)行。
要查看此 MCU 的 雙向bi-directional UART 代碼,請(qǐng)查看 此示例。
io.Writer 接口
io.Writer 接口可能是 Go 中第二種最常用的接口類型,僅次于 error 接口。其定義如下所示:
type Writer interface {Write(p []byte) (n int, err error)}
usart.Driver 實(shí)現(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 允許使用實(shí)現(xiàn)了 io.Writer 接口的任何類型來編寫字符串。在內(nèi)部,它檢查基礎(chǔ)類型是否具有 WriteString 方法,并使用該方法代替 Write(如果可用)。
讓我們編譯修改后的程序:
$ egc$ arm-none-eabi-size cortexm0.elftext data bss dec hex filename15456 320 248 16024 3e98 cortexm0.elf
如你所見,io.WriteString 導(dǎo)致二進(jìn)制文件的大小顯著增加:15776-12964 = 2812 字節(jié)。 Flash 上沒有太多空間了。是什么引起了這么大規(guī)模的增長?
使用這個(gè)命令:
arm-none-eabi-nm --print-size --size-sort --radix=d cortexm0.elf
我們可以打印兩種情況下按其大小排序的所有符號(hào)。通過過濾和分析獲得的數(shù)據(jù)(awk,diff),我們可以找到大約 80 個(gè)新符號(hào)。最大的十個(gè)如下所示:
> 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)容,就需要它的完整方法集(包含所有依賴項(xiàng))。對(duì)于使用大多數(shù)方法的大型程序來說,這不是問題。但是對(duì)于我們這種極簡的情況而言,這是一個(gè)巨大的負(fù)擔(dān)。
我們已經(jīng)接近 MCU 的極限,但讓我們嘗試打印一些數(shù)字(你需要在 import 部分中用 strconv 替換 io 包):
func main() {a := 12b := -123tts.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 的第一個(gè)參數(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
這一次我們的空間超出的不多。讓我們?cè)囍喴幌掠嘘P(guān)類型的信息:
$ cd $HOME/emgo$ ./clean.sh$ cd $HOME/firstemgo$ egc -nf -nt$ arm-none-eabi-size cortexm0.elftext data bss dec hex filename15876 316 320 16512 4080 cortexm0.elf
很接近,但很合適。讓我們加載并運(yùn)行此代碼:
a = 12b = -123hex(a) = chex(b) = -7b
Emgo 中的 strconv 包與 Go 中的原型有很大的不同。它旨在直接用于寫入格式化的數(shù)字,并且在許多情況下可以替換沉重的 fmt 包。 這就是為什么函數(shù)名稱以 Write 而不是 Format 開頭,并具有額外的兩個(gè)參數(shù)的原因。 以下是其用法示例:
func main() {b := -123strconv.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
下一個(gè)示例將顯示如何執(zhí)行此操作:
io.WriteString "Hello, World!" | MorseWriter | usart.Driver usart.USART1
讓我們來創(chuàng)建一個(gè)簡單的編碼器,它使用莫爾斯電碼對(duì)寫入的文本進(jìn)行編碼:
type MorseWriter struct {W io.Writer}func (w *MorseWriter) Write(s []byte) (int, error) {var buf [8]bytefor 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 morseSymbolif c == ' ' {symbol.length = 1buf[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 --..}
你可以在 這里 找到完整的 
分享標(biāo)題:Go語言在極小硬件上的運(yùn)用(二)
文章地址:http://www.dlmjj.cn/article/ccsjgde.html


咨詢
建站咨詢
