我将展示一个大略的示例,来解释如何定义和利用接口,以及如何利用无处不在的 io.Writer 接口。
• 来源:linux.cn • 作者:Michał Derkacz • 译者:XianLei Gao •

(本笔墨数:21486,阅读时终年夜约:24 分钟)
在本文的 第一部分 的结尾,我承诺要写关于接口的内容。我不想在这里写有关接口或完全或简短的讲义。相反,我将展示一个大略的示例,来解释如何定义和利用接口,以及如何利用无处不在的 io.Writer 接口。还有一些关于 反射(reflection)和 半主机(semihosting)的内容。
]
STM32F030F4P6
接口是 Go 措辞的主要组成部分。如果你想理解更多有关它们的信息,我建议你阅读《 高效的 Go 编程 》 和 Russ Cox 的文章 。
并发 Blinky – 回顾当你阅读前面示例的代码时,你可能会把稳到一中打开或关闭 LED 的反直觉办法。 Set 方法用于关闭 LED,Clear 方法用于打开 LED。这是由于在 漏极开路配置(open-drain configuration) 下驱动了 LED。我们可以做些什么来减少代码的混乱?让我们用 On 和 Off 方法来定义 LED 类型:
type LED struct {pin gpio.Pin}func (led LED) On() {led.pin.Clear()}func (led LED) Off() {led.pin.Set()}
现在我们可以大略地调用 led.On() 和 led.Off(),这不会再引起任何迷惑了。
在前面的所有示例中,我都考试测验利用相同的 漏极开路配置(open-drain configuration)来避免代码繁芜化。但是在末了一个示例中,对付我来说,将第三个 LED 连接到 GND 和 PA3 引脚之间并将 PA3 配置为 推挽模式(push-pull mode)会更随意马虎。下一个示例将利用以此办法连接的 LED。
但是我们的新 LED 类型不支持推挽配置,实际上,我们该当将其称为 OpenDrainLED,并定义另一个类型 PushPullLED:
type PushPullLED struct {pin gpio.Pin}func (led PushPullLED) On() {led.pin.Set()}func (led PushPullLED) Off() {led.pin.Clear()}
请把稳,这两种类型都具有相同的方法,它们的事情办法也相同。如果在 LED 上运行的代码可以同时利用这两种类型,而不必把稳当前利用的是哪种类型,那就太好了。 接口类型可以供应帮助:
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 接口,它有两个方法: On 和 Off。 PushPullLED 和 OpenDrainLED 类型代表两种驱动 LED 的办法。我们还定义了两个用作布局函数的 MakeLED 函数。这两种类型都实现了 LED 接口,因此可以将这些类型的值赋给 LED 类型的变量:
led1 = MakeOpenDrainLED(gpio.A.Pin(4))led2 = MakePushPullLED(gpio.A.Pin(3))
在这种情形下, 可赋值性(assignability)在编译时检讨。赋值后,led1 变量包含一个 OpenDrainLED{gpio.A.Pin(4)},以及一个指向 OpenDrainLED 类型的方法集的指针。 led1.On() 调用大致对应于以下 C 代码:
led1.methods->On(led1.value)
如你所见,如果仅考虑函数调用的开销,这是相称廉价的抽象。
但是,对接口的任何赋值都会导致包含有关已赋值类型的大量信息。对付由许多其他类型组成的繁芜类型,可能会有很多信息:
$ egc$ arm-none-eabi-size cortexm0.elf textdata bss dec hex filename10356 196 212 107642a0c cortexm0.elf
如果我们不该用 反射 ,可以通过避免包含类型和构造字段的名称来节省一些字节:
$ egc -nf -nt$ arm-none-eabi-size cortexm0.elf textdata bss dec hex filename10312 196 212 1072029e0 cortexm0.elf
天生的二进制文件仍旧包含一些有关类型的必要信息和关于所有导出方法(带有名称)的完全信息。在运行时,紧张是当你将存储在接口变量中的一个值赋值给任何其他变量时,须要此信息来检讨可赋值性。
我们还可以通过重新编译所导入的包来删除它们的类型和字段名称:
$ cd $HOME/emgo$ ./clean.sh$ cd $HOME/firstemgo$ egc -nf -nt$ arm-none-eabi-size cortexm0.elf textdata bss dec hex filename10272 196 212 1068029b8 cortexm0.elf
让我们加载这个程序,看看它是否按预期事情。这一次我们将利用 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 选项,必须按下复位按钮才能运行程序。
Interfaces
看来,st-flash 与此板合营利用有点不可靠(常日须要复位 ST-LINK 加密狗)。此外,当前版本不会通过 SWD 发出复位命令(仅利用 NRST 旗子暗记)。软件复位是不现实的,但是它常日是有效的,短缺它会将会带来不便。对付 板卡程序员(board-programmer) 来说 OpenOCD 事情得更好。
UARTUART( 通用异步收发传输器(Universal Aynchronous Receiver-Transmitter))仍旧是当今微掌握器最主要的外设之一。它的优点因此下属性的独特组合:
相对较高的速率,仅两条旗子暗记线(在 半双工(half-duplex) 通信的情形下乃至一条),角色对称,关于新数据的 同步带内信令(synchronous in-band signaling)(起始位),在传输 字(words) 内的精确计时。这使得最初用于传输由 7-9 位的字组成的异步的 UART,也被用于有效地实现各种其他物理协议,例如被 WS28xx LEDs 或 1-wire 设备利用的协议。
但是,我们将以其常日的角色利用 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,}
你会创造此代码可能有些繁芜,但目前 STM32 HAL 中没有更大略的 UART 驱动程序(在某些情形下,大略的轮询驱动程序可能会很有用)。 usart.Driver 是利用 DMA 和中断来减轻 CPU 包袱的高效驱动程序。
STM32 USART 外设供应传统的 UART 及其同步版本。要将其用作输出,我们必须将其 Tx 旗子暗记连接到精确的 GPIO 引脚:
tx.Setup(&gpio.Config{Mode: gpio.Alt})tx.SetAltFunc(gpio.USART1_AF1)
在 Tx-only 模式下配置 usart.Driver (rxdma 和 rxbuf 设置为 nil):
tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)
我们利用它的 WriteString 方法来打印这句名言。让我们清理所有内容并编译该程序:
$ cd $HOME/emgo$ ./clean.sh$ cd $HOME/firstemgo$ egc$ arm-none-eabi-size cortexm0.elftext databssdechexfilename1272823617613140 3354cortexm0.elf
要查看某些内容,你须要在 PC 中利用 UART 外设。
请勿利用 RS232 端口或 USB 转 RS232 转换器!
STM32 系列利用 3.3V 逻辑,但是 RS232 可以产生 -15 V ~ +15 V 的电压,这可能会破坏你的 MCU。你须要利用 3.3V 逻辑的 USB 转 UART 转换器。盛行的转换器基于 FT232 或 CP2102 芯片。
UART
你还须要一些终端仿真程序(我更喜好 picocom )。刷新新图像,运行终端仿真器,然后按几次复位按钮:
$ 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!
每次按下复位按钮都会产生新的 “Hello,World!
”行。统统都在按预期进行。
要查看此 MCU 的 双向(bi-directional) UART 代码,请查看 此示例 。
io.Writer 接口io.Writer 接口可能是 Go 中第二种最常用的接口类型,仅次于 error 接口。其定义如下所示:
type Writer interface {Write(p []byte) (n int, err error)}
usart.Driver 实现了 io.Writer,因此我们可以更换:
tts.WriteString("Hello, World!\r\n")
为
io.WriteString(tts, "Hello, World!\r\n")
此外,你须要将 io 包添加到 import 部分。
io.WriteString 函数的声明如下所示:
func WriteString(w Writer, s string) (n int, err error)
如你所见,io.WriteString 许可利用实现了 io.Writer 接口的任何类型来编写字符串。在内部,它检讨根本类型是否具有 WriteString 方法,并利用该方法代替 Write(如果可用)。
让我们编译修正后的程序:
$ egc$ arm-none-eabi-size cortexm0.elf textdata bss dec hex filename15456 320 248 160243e98 cortexm0.elf
如你所见,io.WriteString 导致二进制文件的大小显著增加:15776-12964 = 2812 字节。 Flash 上没有太多空间了。是什么引起了这么大规模的增长?
利用这个命令:
arm-none-eabi-nm --print-size --size-sort --radix=d cortexm0.elf
我们可以打印两种情形下按其大小排序的所有符号。通过过滤和剖析得到的数据(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 方法,但它被编译进来了,与 DisableRx、RxDMAISR、EnableRx 以及上面未提及的其他方法一样。不幸的是,如果你为接口赋值了一些内容,就须要它的完全方法集(包含所有依赖项)。对付利用大多数方法的大型程序来说,这不是问题。但是对付我们这种极简的情形而言,这是一个巨大的包袱。
我们已经靠近 MCU 的极限,但让我们考试测验打印一些数字(你须要在 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 函数的情形一样,strconv.WriteInt 的第一个参数的类型为 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
这一次我们的空间超出的不多。让我们试着精简一下有关类型的信息:
$ cd $HOME/emgo$ ./clean.sh$ cd $HOME/firstemgo$ egc -nf -nt$ arm-none-eabi-size cortexm0.elf textdata bss dec hex filename15876 316 320 165124080 cortexm0.elf
很靠近,但很得当。让我们加载并运行此代码:
a = 12b = -123hex(a) = chex(b) = -7b
Emgo 中的 strconv 包与 Go 中的原型有很大的不同。它旨在直接用于写入格式化的数字,并且在许多情形下可以更换沉重的 fmt 包。 这便是为什么函数名称以 Write 而不是 Format 开头,并具有额外的两个参数的缘故原由。 以下是其用法示例:
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)
由于大多数写入的函数都利用 io.Writer 而不是详细类型(例如 C 中的 FILE ),因此我们得到了类似于 Unix 流(stream) 的功能。在 Unix 中,我们可以轻松地组合大略的命令来实行更大的任务。例如,我们可以通过以下办法将文本写入文件:
echo "Hello, World!" > file.txt
> 操作符将前面命令的输出流写入文件。还有 | 操作符,用于连接相邻命令的输出流和输入流。
多亏了流,我们可以轻松地转换/过滤任何命令的输出。例如,要将所有字母转换为大写,我们可以通过 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
下一个示例将显示如何实行此操作:
io.WriteString "Hello, World!" | MorseWriter | usart.Driver usart.USART1
让我们来创建一个大略的编码器,它利用莫尔斯电码对写入的文本进行编码:
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 --..}
你可以在 这里 找到完全的 morseSymbols 数组。 //emgo:const 指令确保 morseSymbols 数组不会被复制到 RAM 中。
现在我们可以通过两种办法打印句子:
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 不会为存储在接口变量中的值动态分配内存。接口类型的大小受限定,相称于三个指针(适宜 slice )或两个 float64(适宜 complex128)的大小,以较大者为准。它可以直接存储所有基本类型和小型 “构造体/数组” 的值,但是对付较大的值,你必须利用指针。
让我们编译此代码并查看其输出:
$ egc$ arm-none-eabi-size cortexm0.elf textdata bss dec hex filename15152 324 248 157243d6c cortexm0.elf
Hello, World!.... . .-.. .-.. --- --..-- .-- --- .-. .-.. -.. ---.终极闪烁
Blinky 是等效于 “Hello,World!
” 程序的硬件。一旦有了摩尔斯编码器,我们就可以轻松地将两者结合起来以得到终极闪烁程序:
package mainimport ("delay""io""stm32/hal/gpio""stm32/hal/system""stm32/hal/system/timer/systick")var led gpio.Pinfunc 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.PinDotms 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 textdata bss dec hex filename11772 244 244 122602fe4 cortexm0.elf
Ultimate Blinky
反射是的,Emgo 支持 反射 。reflect 包尚未完成,但是已完成的部分足以实现 fmt.Print 函数族了。来看看我们可以在小型 MCU 上做什么。
为了减少内存利用,我们将利用 半主机 (semihosting) 作为标准输出。为了方便起见,我们还编写了大略的 println 函数,它在某种程度上类似于 fmt.Println。
package mainimport ("debug/semihosting""reflect""strconv""stm32/hal/system""stm32/hal/system/timer/systick")var stdout semihosting.Filefunc init() {system.SetupPLL(8, 1, 48/8)systick.Setup(2e6)var err errorstdout, 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 intB 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 函数许可在主机端打开/创建文件。分外路径 :tt 对应于主机的标准输出。
println 函数接管任意数量的参数,每个参数的类型都是任意的:
func println(args ...interface{})
可能是由于任何类型都实现了空接口 interface{}。 println 利用 类型开关 打印字符串,整数和布尔值:
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")}
此外,它还支持任何实现了 stringer 接口的类型,即任何具有 String() 方法的类型。在任何 case 子句中,v 变量具有精确的类型,与 case 关键字后列出的类型相同。
reflect.ValueOf(p) 函数通过许可以编程的办法剖析其类型和内容的形式返回 p。如你所见,我们乃至可以利用 v.Elem() 取消引用指针,并打印所有构造体及其名称。
让我们考试测验编译这段代码。现在让我们看看如果编译时没有类型和字段名,会有什么结果:
$ egc -nt -nf$ arm-none-eabi-size cortexm0.elf textdata bss dec hex filename16028 216 312 1655640ac cortexm0.elf
闪存上只剩下 140 个可用字节。让我们利用启用了半主机的 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, 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: 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}
如果你实际运行此代码,则会把稳到半主机运行缓慢,尤其是在逐字节写入时(缓冲很有用)。
如你所见,p 没有类型名称,并且所有构造字段都具有相同的 X. 名称。让我们再次编译该程序,这次不带 -nt -nf 选项:
$ egc$ arm-none-eabi-size cortexm0.elf textdata bss dec hex filename16052 216 312 1658040c4 cortexm0.elf
现在已经包括了类型和字段名称,但仅在 main.go 文件中 main 包中定义了它们。该程序的输出如下所示:
kind(p) = ptrkind(p) = structtype(p) = Sp = { A : -123 B : true}
反射是任何易于利用的序列化库的关键部分,而像 JSON 这样的序列化 算法 在 物联网(IoT)时期也越来越主要。
这些便是我完成的本文的第二部分。我认为有机会进行第三部分,更具娱乐性的部分,在那里我们将各种有趣的设备连接到这块板上。如果这块板装不下,我们就换一块大一点的。
via: ziutek.github.io
作者: Michał Derkacz 译者: gxlct008 校正: wxy
本文由 LCTT 原创编译, Linux中国 名誉推出
点击“理解更多”可访问文内链接






