Merge pull request #19918 from wxy/20180414-Go-on-very-small-hardware-Part-2

PRF&PUB:20180414 Go on very small hardware Part 2
This commit is contained in:
Xingyu.Wang 2020-10-24 09:43:37 +08:00 committed by GitHub
commit b15a0508c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,8 +1,8 @@
[#]: collector: (oska874)
[#]: translator: (gxlct008)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: reviewer: (wxy)
[#]: publisher: (wxy)
[#]: url: (https://linux.cn/article-12747-1.html)
[#]: subject: (Go on very small hardware Part 2)
[#]: via: (https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html)
[#]: author: (Michał Derkacz https://ziutek.github.io/)
@ -10,17 +10,17 @@
Go 语言在极小硬件上的运用(二)
============================================================
![](https://img.linux.net.cn/data/attachment/album/201909/24/210256yihkuy8kcigugr2h.png)
[![STM32F030F4P6](https://ziutek.github.io/images/mcu/f030-demo-board/board.jpg)][1]
在本文的 [第一部分][2] 的结尾,我承诺要写关于接口的内容。我不想在这里写有关接口或完整或简短的讲义。相反,我将展示一个简单的示例,来说明如何定义和使用接口,以及如何利用无处不在的 `io.Writer` 接口。还有一些关于<ruby>反射<rt>reflection</rt></ruby><ruby>半主机<rt>semihosting</rt></ruby>的内容。
![STM32F030F4P6](https://ziutek.github.io/images/mcu/f030-demo-board/board.jpg)
在本文的 [第一部分][2] 的结尾,我承诺要写关于 _interfaces_ 的内容。我不想在这里写有关接口的完整甚至简短的讲义。相反,我将展示一个简单的示例,来说明如何定义和使用接口,以及如何利用无处不在的 _io.Writer_ 接口。还有一些关于 _reflection__semihosting_ 的内容。
接口是 Go 语言的重要组成部分。如果您想了解更多有关它们的信息,我建议您阅读 [Effective Go][3] 和 [Russ Cox 的文章][4]。
接口是 Go 语言的重要组成部分。如果你想了解更多有关它们的信息,我建议你阅读《[高效的 Go 编程][3]》 和 [Russ Cox 的文章][4]。
### 并发 Blinky 回顾
您阅读前面示例的代码时,您可能会注意到一个违反直觉的方式来打开或关闭 LED。 _Set_ 方法用于关闭 LED_Clear_ 方法用于打开 LED。这是由于在 <ruby>漏极开路配置<rt>open-drain configuration</rt></ruby> 下驱动了 LED。我们可以做些什么来减少代码的混乱 让我们用 _On__Off_ 方法来定义 _LED_ 类型:
你阅读前面示例的代码时,你可能会注意到一中打开或关闭 LED 的反直觉方式。 `Set` 方法用于关闭 LED`Clear` 方法用于打开 LED。这是由于在 <ruby>漏极开路配置<rt>open-drain configuration</rt></ruby> 下驱动了 LED。我们可以做些什么来减少代码的混乱让我们用 `On``Off` 方法来定义 `LED` 类型:
```
type LED struct {
@ -34,15 +34,13 @@ func (led LED) On() {
func (led LED) Off() {
led.pin.Set()
}
```
现在我们可以简单地调用 `led.On()``led.Off()`,这不会再引起任何疑惑了。
在前面的所有示例中,我都尝试使用相同的 <ruby>漏极开路配置<rt>open-drain configuration</rt></ruby>来避免代码复杂化。但是在最后一个示例中,对于我来说,将第三个 LED 连接到 GND 和 PA3 引脚之间并将 PA3 配置为<ruby>推挽模式<rt>push-pull mode</rt></ruby>会更容易。下一个示例将使用以此方式连接的 LED。
在前面的所有示例中,我都尝试使用相同的 <ruby>漏极开路配置<rt>open-drain configuration</rt></ruby>来 避免代码复杂化。但是在最后一个示例中,对于我来说,将第三个 LED 连接到 GND 和 PA3 引脚之间并将 PA3 配置为<ruby>推挽模式<rt>push-pull mode</rt></ruby>会更容易。下一个示例将使用以此方式连接的 LED。
但是我们的新 _LED_ 类型不支持推挽配置。实际上,我们应该将其称为 _OpenDrainLED_,并定义另一个类型 _PushPullLED_
但是我们的新 `LED` 类型不支持推挽配置,实际上,我们应该将其称为 `OpenDrainLED`,并定义另一个类型 `PushPullLED`
```
type PushPullLED struct {
@ -56,10 +54,9 @@ func (led PushPullLED) On() {
func (led PushPullLED) Off() {
led.pin.Clear()
}
```
请注意,这两种类型都具有相同的方法,它们的工作方式也相同。如果在 LED 上运行的代码可以同时使用这两种类型,而不必注意当前使用的是哪种类型,那就太好了。 _interface type_ 可以提供帮助:
请注意,这两种类型都具有相同的方法,它们的工作方式也相同。如果在 LED 上运行的代码可以同时使用这两种类型,而不必注意当前使用的是哪种类型,那就太好了。 接口类型可以提供帮助:
```
package main
@ -134,21 +131,20 @@ func main() {
```
我们定义了 _LED_ 接口,它有两个方法: _On__Off__PushPullLED__OpenDrainLED_ 类型代表两种驱动 LED 的方式。我们还定义了两个用作构造函数的 _Make_ _*LED_ 函数。这两种类型都实现了 _LED_ 接口,因此可以将这些类型的值赋给 _LED_ 类型的变量:
我们定义了 `LED` 接口,它有两个方法: `On``Off``PushPullLED``OpenDrainLED` 类型代表两种驱动 LED 的方式。我们还定义了两个用作构造函数的 `Make*LED` 函数。这两种类型都实现了 `LED` 接口,因此可以将这些类型的值赋给 `LED` 类型的变量:
```
led1 = MakeOpenDrainLED(gpio.A.Pin(4))
led2 = MakePushPullLED(gpio.A.Pin(3))
```
在这种情况下,可赋值性在编译时检查。赋值后_led1_ 变量包含一个 `OpenDrainLED{gpio.A.Pin(4)}`,以及一个指向 _OpenDainLED_ 类型的方法集的指针。 `led1.On()` 调用大致对应于以下 C 代码:
在这种情况下,<ruby>可赋值性<rt>assignability</rt></ruby>在编译时检查。赋值后,`led1` 变量包含一个 `OpenDrainLED{gpio.A.Pin(4)}`,以及一个指向 `OpenDrainLED` 类型的方法集的指针。 `led1.On()` 调用大致对应于以下 C 代码:
```
led1.methods->On(led1.value)
```
如您所见,如果仅考虑函数调用的开销,这是相当便宜的抽象。
如你所见,如果仅考虑函数调用的开销,这是相当廉价的抽象。
但是,对接口的任何赋值都会导致包含有关已赋值类型的大量信息。对于由许多其他类型组成的复杂类型,可能会有很多信息:
@ -168,7 +164,7 @@ $ arm-none-eabi-size cortexm0.elf
10312 196 212 10720 29e0 cortexm0.elf
```
生成的二进制文件仍然包含一些有关类型的必要信息和关于所有导出方法(带有名称)的完整信息。在运行时,主要是当将存储在接口变量中的一个值赋值给任何其他变量时,需要此信息来检查可赋值性。
生成的二进制文件仍然包含一些有关类型的必要信息和关于所有导出方法(带有名称)的完整信息。在运行时,主要是当将存储在接口变量中的一个值赋值给任何其他变量时,需要此信息来检查可赋值性。
我们还可以通过重新编译所导入的包来删除它们的类型和字段名称:
@ -203,28 +199,23 @@ Flash page at addr: 0x08002800 erased
```
我没有将 NRST 信号连接到编程器,因此无法使用 _-reset_ 选项,必须按下 reset 按钮才能运行程序。
我没有将 NRST 信号连接到编程器,因此无法使用 `-reset` 选项,必须按下复位按钮才能运行程序。
![Interfaces](https://ziutek.github.io/images/mcu/f030-demo-board/interfaces.png)
看来,_st-flash_ 与此板配合使用有点不可靠 (通常需要重置 ST-LINK 加密狗)。此外,当前版本不会通过 SWD 发出 reset 命令 (仅使用 NRST 信号)。 软件重置是不现实的,但是它通常是有效的,缺少它会将会带来不便。对于<ruby>电路板-程序员<rt>board-programmer</rt></ruby> 组合 _OpenOCD_ 工作得更好。
看来,`st-flash` 与此板配合使用有点不可靠(通常需要复位 ST-LINK 加密狗)。此外,当前版本不会通过 SWD 发出复位命令(仅使用 NRST 信号)。软件复位是不现实的,但是它通常是有效的,缺少它会将会带来不便。对于<ruby>板卡程序员<rt>board-programmer</rt></ruby> 来说 OpenOCD 工作得更好。
### UART
UART<ruby>通用异步收发传输器<rt>Universal Aynchronous Receiver-Transmitter</rt></ruby>)仍然是当今微控制器最重要的外设之一。它的优点是以下属性的独特组合:
* 相对较高的速度,
* 仅两条信号线(在 <ruby>半双工<rt>half-duplex</rt></ruby> 通信的情况下甚至一条),
* 角色对称,
* 关于新数据的 <ruby>同步带内信令<rt>synchronous in-band signaling</rt></ruby>(起始位),
* 在传输 <ruby><rt>words</rt></ruby> 内的精确计时。
这使得最初用于传输由 7-9 位 words 组成的异步消息的 UART也被用于有效地实现各种其他物理协议例如被 [WS28xx LEDs][7] 或 [1-wire][8] 设备使用的协议。
这使得最初用于传输由 7-9 位的字组成的异步消息的 UART也被用于有效地实现各种其他物理协议例如被 [WS28xx LEDs][7] 或 [1-wire][8] 设备使用的协议。
但是,我们将以其通常的角色使用 UART从程序中打印文本消息。
@ -286,7 +277,7 @@ var ISRs = [...]func(){
```
会发现此代码可能有些复杂,但目前 STM32 HAL 中没有更简单的 UART 驱动程序(在某些情况下,简单的轮询驱动程序可能会很有用)。 _usart.Driver_ 是使用 DMA 和中断来卸载 CPU 的高效驱动程序。
会发现此代码可能有些复杂,但目前 STM32 HAL 中没有更简单的 UART 驱动程序(在某些情况下,简单的轮询驱动程序可能会很有用)。 `usart.Driver` 是使用 DMA 和中断来减轻 CPU 负担的高效驱动程序。
STM32 USART 外设提供传统的 UART 及其同步版本。要将其用作输出,我们必须将其 Tx 信号连接到正确的 GPIO 引脚:
@ -295,13 +286,13 @@ tx.Setup(&gpio.Config{Mode: gpio.Alt})
tx.SetAltFunc(gpio.USART1_AF1)
```
在 Tx-only 模式下配置 _usart.Driver_ rxdma 和 rxbuf 设置为 nil
在 Tx-only 模式下配置 `usart.Driver` rxdma 和 rxbuf 设置为 nil
```
tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)
```
我们使用它的 _WriteString_ 方法来打印这句名句。让我们清理所有内容并编译该程序:
我们使用它的 `WriteString` 方法来打印这句名言。让我们清理所有内容并编译该程序:
```
$ cd $HOME/emgo
@ -313,15 +304,15 @@ $ arm-none-eabi-size cortexm0.elf
12728 236 176 13140 3354 cortexm0.elf
```
要查看某些内容,需要在 PC 中使用 UART 外设。
要查看某些内容,需要在 PC 中使用 UART 外设。
**请勿使用 RS232 端口或 USB 转 RS232 转换器!**
STM32 系列使用 3.3V 逻辑,但是 RS232 可以产生 -15 V ~ +15 V 的电压,这可能会损坏您的 MCU。您需要使用 3.3 V 逻辑的 USB 转 UART 转换器。流行的转换器基于 FT232 或 CP2102 芯片。
STM32 系列使用 3.3V 逻辑,但是 RS232 可以产生 -15 V ~ +15 V 的电压,这可能会损坏你的 MCU。你需要使用 3.3V 逻辑的 USB 转 UART 转换器。流行的转换器基于 FT232 或 CP2102 芯片。
![UART](https://ziutek.github.io/images/mcu/f030-demo-board/uart.jpg)
您还需要一些终端仿真程序 (我更喜欢 [picocom][9])。刷新新图像,运行终端仿真器,然后按几次 reset 按钮:
你还需要一些终端仿真程序(我更喜欢 [picocom][9])。刷新新图像,运行终端仿真器,然后按几次复位按钮:
```
$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; reset run; exit'
@ -377,13 +368,13 @@ Hello, World!
Hello, World!
```
每次按下 reset 按钮都会产生新的 “HelloWorld”行。一切都在按预期进行。
每次按下复位按钮都会产生新的 “HelloWorld”行。一切都在按预期进行。
要查看此 MCU 的 <ruby>双向<rt>bi-directional</rt></ruby> UART 代码,请查看 [此示例][10]。
### io.Writer 接口
_io.Writer_ 接口可能是 Go 中第二种最常用的接口类型,紧接在 _error_ 接口之后。其定义如下所示:
`io.Writer` 接口可能是 Go 中第二种最常用的接口类型,仅次于 `error` 接口。其定义如下所示:
```
type Writer interface {
@ -391,7 +382,7 @@ type Writer interface {
}
```
_usart.Driver_ 实现了 _io.Writer_ ,因此我们可以替换:
`usart.Driver` 实现了 `io.Writer`,因此我们可以替换:
```
tts.WriteString("Hello, World!\r\n")
@ -403,15 +394,15 @@ tts.WriteString("Hello, World!\r\n")
io.WriteString(tts, "Hello, World!\r\n")
```
此外,您需要将 _io_ 包添加到 _import_ 部分。
此外,你需要将 `io` 包添加到 `import` 部分。
_io.WriteString_ 函数的声明如下所示:
`io.WriteString` 函数的声明如下所示:
```
func WriteString(w Writer, s string) (n int, err error)
```
您所见_io.WriteString_ 允许使用实现了 _io.Writer_ 接口的任何类型来编写字符串。在内部,它检查基础类型是否具有 _WriteString_ 方法,并使用该方法代替 _Write_ (如果可用)
你所见,`io.WriteString` 允许使用实现了 `io.Writer` 接口的任何类型来编写字符串。在内部,它检查基础类型是否具有 `WriteString` 方法,并使用该方法代替 `Write`(如果可用)
让我们编译修改后的程序:
@ -422,7 +413,7 @@ $ arm-none-eabi-size cortexm0.elf
15456 320 248 16024 3e98 cortexm0.elf
```
您所见_io.WriteString_ 导致二进制文件的大小显着增加15776-12964 = 2812字节。 Flash 上没有太多空间了。是什么引起了这么大规模的增长?
你所见,`io.WriteString` 导致二进制文件的大小显着增加15776-12964 = 2812 字节。 Flash 上没有太多空间了。是什么引起了这么大规模的增长?
使用这个命令:
@ -430,7 +421,7 @@ $ arm-none-eabi-size cortexm0.elf
arm-none-eabi-nm --print-size --size-sort --radix=d cortexm0.elf
```
我们可以打印两种情况下按其大小排序的所有符号。通过过滤和分析获得的数据awkdiff我们可以找到大约 80 个新符号。最大的十个如下所示:
我们可以打印两种情况下按其大小排序的所有符号。通过过滤和分析获得的数据(`awk``diff`),我们可以找到大约 80 个新符号。最大的十个如下所示:
```
> 00000062 T stm32$hal$usart$Driver$DisableRx
@ -444,9 +435,9 @@ arm-none-eabi-nm --print-size --size-sort --radix=d cortexm0.elf
> 00000660 T stm32$hal$usart$Driver$Read
```
因此,即使我们不使用 _usart.Driver.Read_ 方法进行编译,也与 _DisableRx_、_RxDMAISR_、_EnableRx_ 以及上面未提及的其他方法相同。不幸的是,如果您为接口赋值了一些内容,那么它的完整方法集是必需的(包含所有依赖项)。对于使用大多数方法的大型程序来说,这不是问题。但是对于我们这种极简的情况而言,这是一个巨大的负担。
因此,即使我们不使用 `usart.Driver.Read` 方法,但它被编译进来了,与 `DisableRx`、`RxDMAISR`、`EnableRx` 以及上面未提及的其他方法一样。不幸的是,如果你为接口赋值了一些内容,就需要它的完整方法集(包含所有依赖项)。对于使用大多数方法的大型程序来说,这不是问题。但是对于我们这种极简的情况而言,这是一个巨大的负担。
我们已经接近 MCU 的极限,但让我们尝试打印一些数字(您需要在 _import_ 部分中用 _strconv_ 替换 _io_ 包):
我们已经接近 MCU 的极限,但让我们尝试打印一些数字(你需要在 `import` 部分中用 `strconv` 替换 `io` 包):
```
func main() {
@ -469,8 +460,7 @@ func main() {
}
```
与使用 _io.WriteString_ 函数的情况一样_strconv.WriteInt_ 的第一个参数的类型为 _io.Writer_
与使用 `io.WriteString` 函数的情况一样,`strconv.WriteInt` 的第一个参数的类型为 `io.Writer`
```
$ egc
@ -479,7 +469,7 @@ $ egc
exit status 1
```
这一次我们的空间用完了。让我们试着精简一下有关类型的信息:
这一次我们的空间超出的不多。让我们试着精简一下有关类型的信息:
```
$ cd $HOME/emgo
@ -500,7 +490,7 @@ hex(a) = c
hex(b) = -7b
```
Emgo 中的 _strconv_ 包与 Go 中的原型有很大的不同。 它旨在直接用于写入格式化的数字,并且在许多情况下可以替换繁重的 _fmt_ 包。 这就是为什么函数名称以 _Write_ 而不是 _Format_ 开头,并具有额外的两个参数的原因。 以下是其用法示例:
Emgo 中的 `strconv` 包与 Go 中的原型有很大的不同。它旨在直接用于写入格式化的数字,并且在许多情况下可以替换沉重的 `fmt` 包。 这就是为什么函数名称以 `Write` 而不是 `Format` 开头,并具有额外的两个参数的原因。 以下是其用法示例:
```
func main() {
@ -536,7 +526,7 @@ func main() {
### Unix 流 和 <ruby>莫尔斯电码<rt>Morse code</rt></ruby>
得益于事实上大多数写入功能的函数都使用 _io.Writer_ 而不是具体类型(例如 C 中的 _FILE_ ),因此我们获得了类似于 _Unix stream_ 的功能。在 Unix 中,我们可以轻松地组合简单的命令来执行更大的任务。例如,我们可以通过以下方式将文本写入文件:
由于大多数写入的函数都使用 `io.Writer` 而不是具体类型(例如 C 中的 `FILE` ),因此我们获得了类似于 Unix <ruby><rt>stream</rt></ruby> 的功能。在 Unix 中,我们可以轻松地组合简单的命令来执行更大的任务。例如,我们可以通过以下方式将文本写入文件:
```
echo "Hello, World!" > file.txt
@ -544,13 +534,13 @@ echo "Hello, World!" > file.txt
`>` 操作符将前面命令的输出流写入文件。还有 `|` 操作符,用于连接相邻命令的输出流和输入流。
多亏了流,我们可以轻松地转换/过滤任何命令的输出。例如,要将所有字母转换为大写,我们可以通过 `tr` 命令过滤 `echo` 的输出:
多亏了流,我们可以轻松地转换/过滤任何命令的输出。例如,要将所有字母转换为大写,我们可以通过 _tr_ 命令过滤 echo 的输出:
```
echo "Hello, World!" | tr a-z A-Z > file.txt
```
为了显示 _io.Writer_ 和 Unix 流之间的类比,让我们编写以下代码:
为了显示 `io.Writer` 和 Unix 流之间的类比,让我们编写以下代码:
```
io.WriteString(tts, "Hello, World!\r\n")
@ -628,7 +618,7 @@ var morseSymbols = [...]morseSymbol{
}
```
您可以在 [这里][11] 找到完整的 _morseSymbols_ 数组。 `//emgo:const` 指令确保 _morseSymbols_ 数组不会被复制到 RAM 中。
你可以在 [这里][11] 找到完整的 `morseSymbols` 数组。 `//emgo:const` 指令确保 `morseSymbols` 数组不会被复制到 RAM 中。
现在我们可以通过两种方式打印句子:
@ -642,10 +632,9 @@ func main() {
}
```
我们使用指向 _MorseWriter_ `&MorseWriter{tts}` 的指针而不是简单的 `MorseWriter{tts}` 值,因为 _MorseWriter_ 太大,不适合接口变量。
我们使用指向 `MorseWriter` `&MorseWriter{tts}` 的指针而不是简单的 `MorseWriter{tts}` 值,因为 `MorseWriter` 太大,不适合接口变量。
与 Go 不同Emgo 不会为存储在接口变量中的值动态分配内存。接口类型的大小受限制,等于三个指针(适合 _slice_ )或两个 _float64_(适合 _complex128_ )的大小,以较大者为准。它可以直接存储所有基本类型和小型 “结构体/数组” 的值,但是对于较大的值,您必须使用指针。
与 Go 不同Emgo 不会为存储在接口变量中的值动态分配内存。接口类型的大小受限制,相当于三个指针(适合 `slice` )或两个 `float64`(适合 `complex128`)的大小,以较大者为准。它可以直接存储所有基本类型和小型 “结构体/数组” 的值,但是对于较大的值,你必须使用指针。
让我们编译此代码并查看其输出:
@ -661,9 +650,9 @@ Hello, World!
.... . .-.. .-.. --- --..-- .-- --- .-. .-.. -.. ---.
```
### 终极 Blinky
### 终极闪烁
_Blinky_ 等效于 _HelloWorld_ 程序的硬件。一旦有了 Morse 编码器,我们就可以轻松地将两者结合起来以获得 _Ultimate Blinky_ 程序:
Blinky 是等效于 “HelloWorld” 程序的硬件。一旦有了摩尔斯编码器,我们就可以轻松地将两者结合起来以获得终极闪烁程序:
```
package main
@ -726,7 +715,7 @@ func main() {
```
在上面的示例中,我省略了 _MorseWriter_ 类型的定义,因为它已在前面展示过。完整版可通过 [这里][12] 获取。让我们编译它并运行:
在上面的示例中,我省略了 `MorseWriter` 类型的定义,因为它已在前面展示过。完整版可通过 [这里][12] 获取。让我们编译它并运行:
```
$ egc
@ -739,9 +728,9 @@ $ arm-none-eabi-size cortexm0.elf
### 反射
是的Emgo 支持 [反射][13]。 _reflect_ 包尚未完成,但是已完成的部分足以实现 _fmt.Print_ 函数族了。来看看我们可以在小型 MCU 上做什么。
是的Emgo 支持 [反射][13]。`reflect` 包尚未完成,但是已完成的部分足以实现 `fmt.Print` 函数族了。来看看我们可以在小型 MCU 上做什么。
为了减少内存使用,我们将使用 [semihosting][14] 作为标准输出。为了方便起见,我们还编写了简单的 _println_ 函数,它在某种程度上类似于 _fmt.Println_
为了减少内存使用,我们将使用 <ruby>[半主机][14]<rt>semihosting</rt></ruby> 作为标准输出。为了方便起见,我们还编写了简单的 `println` 函数,它在某种程度上类似于 `fmt.Println`
```
package main
@ -819,15 +808,15 @@ func main() {
```
_semihosting.OpenFile_ 函数允许在主机端 打开/创建 文件。特殊路径 _:tt_ 对应于主机的标准输出。
`semihosting.OpenFile` 函数允许在主机端打开/创建文件。特殊路径 `:tt` 对应于主机的标准输出。
_println_ 函数接受任意数量的参数,每个参数的类型都是任意的:
`println` 函数接受任意数量的参数,每个参数的类型都是任意的:
```
func println(args ...interface{})
```
可能是因为任何类型都实现了空接口 _interface{}__println_ 使用 [类型开关][15] 打印字符串,整数和布尔值:
可能是因为任何类型都实现了空接口 `interface{}``println` 使用 [类型开关][15] 打印字符串,整数和布尔值:
```
switch v := a.(type) {
@ -844,12 +833,11 @@ default:
}
```
此外,它还支持任何实现了 _stringer_ 接口的类型,即任何具有 _String()_ 方法的类型。在任何 _case_ 子句中_v_ 变量具有正确的类型,与 _case_ 关键字后列出的类型相同。
此外,它还支持任何实现了 `stringer` 接口的类型,即任何具有 `String()` 方法的类型。在任何 `case` 子句中,`v` 变量具有正确的类型,与 `case` 关键字后列出的类型相同。
`reflect.ValueOf(p)` 函数通过允许以编程的方式分析其类型和内容的形式返回 `p`。如你所见,我们甚至可以使用 `v.Elem()` 取消引用指针,并打印所有结构体及其名称。
reflect.ValueOf(p) 函数以允许以编程方式分析其类型和内容的形式返回 _p_。如您所见,我们甚至可以使用 `v.Elem()` 取消引用指针,并打印所有结构体及其名称。
让我们尝试编译这段代码。现在,让我们看看如果不使用类型和字段名进行编译会产生什么结果:
让我们尝试编译这段代码。现在让我们看看如果编译时没有类型和字段名,会有什么结果:
```
$ egc -nt -nf
@ -858,7 +846,7 @@ $ arm-none-eabi-size cortexm0.elf
16028 216 312 16556 40ac cortexm0.elf
```
闪存上只剩下 140 个可用字节。让我们使用启用了 semihosting 的 OpenOCD 加载它:
闪存上只剩下 140 个可用字节。让我们使用启用了半主机的 OpenOCD 加载它:
```
$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; arm semihosting enable; reset run'
@ -891,9 +879,9 @@ type(*p) =
}
```
如果您实际运行过此代码,则会注意到 semihosting 运行缓慢,尤其是在逐字节写入时(缓冲很有用)。
如果你实际运行此代码,则会注意到半主机运行缓慢,尤其是在逐字节写入时(缓冲很有用)。
您所见,`*p` 没有类型名称,并且所有结构字段都具有相同的 _X._ 名称。让我们再次编译该程序,这次不带 _-nt -nf_ 选项:
你所见,`*p` 没有类型名称,并且所有结构字段都具有相同的 `X.` 名称。让我们再次编译该程序,这次不带 `-nt -nf` 选项:
```
$ egc
@ -902,7 +890,7 @@ $ arm-none-eabi-size cortexm0.elf
16052 216 312 16580 40c4 cortexm0.elf
```
现在已经包括了类型和字段名称,但仅在 ~~_main.go_ 文件中~~ _main_ 包中定义了它们。该程序的输出如下所示:
现在已经包括了类型和字段名称,但仅在 ~~_main.go_ 文件中~~ `main` 包中定义了它们。该程序的输出如下所示:
```
kind(p) = ptr
@ -922,15 +910,15 @@ type(*p) = S
via: https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html
作者:[Michał Derkacz ][a]
作者:[Michał Derkacz][a]
译者:[gxlct008](https://github.com/gxlct008)
校对:[校对者ID](https://github.com/校对者ID)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:https://ziutek.github.io/
[1]:https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html
[2]:https://ziutek.github.io/2018/03/30/go_on_very_small_hardware.html
[2]:https://linux.cn/article-11383-1.html
[3]:https://golang.org/doc/effective_go.html#interfaces
[4]:https://research.swtch.com/interfaces
[5]:https://blog.golang.org/laws-of-reflection