Go 语言在极小硬件上的运用（一）
Go
![](/data/attachment/album/201909/24/210256yihkuy8kcigugr2h.png)


Go 语言，能在多低下的配置上运行并发挥作用呢？


我最近购买了一个特别便宜的开发板：


![STM32F030F4P6](/data/attachment/album/201909/24/210325sk2snn6u6hs82tu7.jpg)


我购买它的理由有三个。首先，我（作为程序员）从未接触过 STM320 系列的开发板。其次，STM32F10x 系列使用也有点少了。STM320 系列的 MCU 很便宜，有更新一些的外设，对系列产品进行了改进，问题修复也做得更好了。最后，为了这篇文章，我选用了这一系列中最低配置的开发板，整件事情就变得有趣起来了。


### 硬件部分


[STM32F030F4P6](http://www.st.com/content/st_com/en/products/microcontrollers/stm32-32-bit-arm-cortex-mcus/stm32-mainstream-mcus/stm32f0-series/stm32f0x0-value-line/stm32f030f4.html) 给人留下了很深的印象：


* CPU: [Cortex M0](https://en.wikipedia.org/wiki/ARM_Cortex-M#Cortex-M0) 48 MHz（最低配置，只有 12000 个逻辑门电路）
* RAM: 4 KB，
* Flash: 16 KB，
* ADC、SPI、I2C、USART 和几个定时器


以上这些采用了 TSSOP20 封装。正如你所见，这是一个很小的 32 位系统。


### 软件部分


如果你想知道如何在这块开发板上使用 [Go](https://golang.org/) 编程，你需要反复阅读硬件规范手册。你必须面对这样的真实情况：在 Go 编译器中给 Cortex-M0 提供支持的可能性很小。而且，这还仅仅只是第一个要解决的问题。


我会使用 [Emgo](https://github.com/ziutek/emgo)，但别担心，之后你会看到，它如何让 Go 在如此小的系统上尽可能发挥作用。


在我拿到这块开发板之前，对 [stm32/hal](https://github.com/ziutek/emgo/tree/master/egpath/src/stm32/hal) 系列下的 F0 MCU 没有任何支持。在简单研究[参考手册](http://www.st.com/resource/en/reference_manual/dm00091010.pdf)后，我发现 STM32F0 系列是 STM32F3 削减版，这让在新端口上开发的工作变得容易了一些。


如果你想接着本文的步骤做下去，需要先安装 Emgo



```
cd $HOME
git clone https://github.com/ziutek/emgo/
cd emgo/egc
go install
```

然后设置一下环境变量



```
export EGCC=path_to_arm_gcc      # eg. /usr/local/arm/bin/arm-none-eabi-gcc
export EGLD=path_to_arm_linker   # eg. /usr/local/arm/bin/arm-none-eabi-ld
export EGAR=path_to_arm_archiver # eg. /usr/local/arm/bin/arm-none-eabi-ar

export EGROOT=$HOME/emgo/egroot
export EGPATH=$HOME/emgo/egpath

export EGARCH=cortexm0
export EGOS=noos
export EGTARGET=f030x6
```

更详细的说明可以在 [Emgo](https://github.com/ziutek/emgo) 官网上找到。


要确保 `egc` 在你的 `PATH` 中。 你可以使用 `go build` 来代替 `go install`，然后把 `egc` 复制到你的 `$HOME/bin` 或 `/usr/local/bin` 中。


现在，为你的第一个 Emgo 程序创建一个新文件夹，随后把示例中链接器脚本复制过来：



```
mkdir $HOME/firstemgo
cd $HOME/firstemgo
cp $EGPATH/src/stm32/examples/f030-demo-board/blinky/script.ld .
```

### 最基本程序


在 `main.go` 文件中创建一个最基本的程序：



```
package main

func main() {
}
```

文件编译没有出现任何问题：



```
$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
   7452     172     104    7728    1e30 cortexm0.elf
```

第一次编译可能会花点时间。编译后产生的二进制占用了 7624 个字节的 Flash 空间（文本 + 数据）。对于一个什么都没做的程序来说，占用的空间有些大。还剩下 8760 字节，可以用来做些有用的事。


不妨试试传统的 “Hello, World!” 程序：



```
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
```

不幸的是，这次结果有些糟糕：



```
$ egc
/usr/local/arm/bin/arm-none-eabi-ld: /home/michal/P/go/src/github.com/ziutek/emgo/egpath/src/stm32/examples/f030-demo-board/blog/cortexm0.elf section `.text' will not fit in region `Flash'
/usr/local/arm/bin/arm-none-eabi-ld: region `Flash' overflowed by 10880 bytes
exit status 1
```

“Hello, World!” 需要 STM32F030x6 上至少 32KB 的 Flash 空间。


`fmt` 包强制包含整个 `strconv` 和 `reflect` 包。这三个包，即使在精简版本中的 Emgo 中，占用空间也很大。我们不能使用这个例子了。有很多的应用不需要好看的文本输出。通常，一个或多个 LED，或者七段数码管显示就足够了。不过，在第二部分，我会尝试使用 `strconv` 包来格式化，并在 UART 上显示一些数字和文本。


### 闪烁


我们的开发板上有一个与 PA4 引脚和 VCC 相连的 LED。这次我们的代码稍稍长了一些：



```
package main

import (
    "delay"

    "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}
    led.Setup(cfg)
}

func main() {
    for {
        led.Clear()
        delay.Millisec(100)
        led.Set()
        delay.Millisec(900)
    }
}
```

按照惯例，`init` 函数用来初始化和配置外设。


`system.SetupPLL(8, 1, 48/8)` 用来配置 RCC，将外部的 8 MHz 振荡器的 PLL 作为系统时钟源。PLL 分频器设置为 1，倍频数设置为 48/8 =6，这样系统时钟频率为 48MHz。


`systick.Setup(2e6)` 将 Cortex-M SYSTICK 时钟作为系统时钟，每隔 2e6 次纳秒运行一次（每秒钟 500 次）。


`gpio.A.EnableClock(false)` 开启了 GPIO A 口的时钟。`False` 意味着这一时钟在低功耗模式下会被禁用，但在 STM32F0 系列中并未实现这一功能。


`led.Setup(cfg)` 设置 PA4 引脚为开漏输出。


`led.Clear()` 将 PA4 引脚设为低，在开漏设置中，打开 LED。


`led.Set()` 将 PA4 设为高电平状态，关掉LED。


编译这个代码：



```
$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
   9772     172     168   10112    2780 cortexm0.elf
```

正如你所看到的，这个闪烁程序占用了 2320 字节，比最基本程序占用空间要大。还有 6440 字节的剩余空间。


看看代码是否能运行：



```
$ 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 v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
debug_level: 0
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
adapter speed: 950 kHz
target halted due to debug-request, current mode: Thread 
xPSR: 0xc1000000 pc: 0x0800119c msp: 0x20000da0
adapter speed: 4000 kHz
** Programming Started **
auto erase enabled
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000da0
wrote 10240 bytes from file cortexm0.elf in 0.817425s (12.234 KiB/s)
** Programming Finished **
adapter speed: 950 kHz
```

在这篇文章中，这是我第一次，将一个短视频转换成[动画 PNG](https://en.wikipedia.org/wiki/APNG)。我对此印象很深，再见了 YouTube。 对于 IE 用户，我很抱歉，更多信息请看 [apngasm](http://apngasm.sourceforge.net/)。我本应该学习 HTML5，但现在，APNG 是我最喜欢的，用来播放循环短视频的方法了。


![STM32F030F4P6](/data/attachment/album/201909/24/210408vdq9h6qh6cwxtu0z.png)


### 更多的 Go 语言编程


如果你不是一个 Go 程序员，但你已经听说过一些关于 Go 语言的事情，你可能会说：“Go 语法很好，但跟 C 比起来，并没有明显的提升。让我看看 Go 语言的通道和协程！”


接下来我会一一展示:



```
import (
    "delay"

    "stm32/hal/gpio"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
)

var led1, led2 gpio.Pin

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(false)
    led1 = gpio.A.Pin(4)
    led2 = gpio.A.Pin(5)

    cfg := &gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain}
    led1.Setup(cfg)
    led2.Setup(cfg)
}

func blinky(led gpio.Pin, period int) {
    for {
        led.Clear()
        delay.Millisec(100)
        led.Set()
        delay.Millisec(period - 100)
    }
}

func main() {
    go blinky(led1, 500)
    blinky(led2, 1000)
}

```

代码改动很小: 添加了第二个 LED，上一个例子中的 `main` 函数被重命名为 `blinky` 并且需要提供两个参数。 `main` 在新的协程中先调用 `blinky`，所以两个 LED 灯在并行使用。值得一提的是，`gpio.Pin` 可以同时访问同一 GPIO 口的不同引脚。


Emgo 还有很多不足。其中之一就是你需要提前规定 `goroutines(tasks)` 的最大执行数量。是时候修改 `script.ld` 了:



```
ISRStack = 1024;
MainStack = 1024;
TaskStack = 1024;
MaxTasks = 2;

INCLUDE stm32/f030x4
INCLUDE stm32/loadflash
INCLUDE noos-cortexm
```

栈的大小需要靠猜，现在还不用关心这一点。



```
$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  10020     172     172   10364    287c cortexm0.elf
```

另一个 LED 和协程一共占用了 248 字节的 Flash 空间。


![STM32F030F4P6](/data/attachment/album/201909/24/210519jkopyikil0z2al7r.png)


### 通道


通道是 Go 语言中协程之间相互通信的一种[推荐方式](https://blog.golang.org/share-memory-by-communicating)。Emgo 甚至能允许通过*中断处理*来使用缓冲通道。下一个例子就展示了这种情况。



```
package main

import (
    "delay"
    "rtos"

    "stm32/hal/gpio"
    "stm32/hal/irq"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
    "stm32/hal/tim"
)

var (
    leds  [3]gpio.Pin
    timer *tim.Periph
    ch    = make(chan int, 1)
)

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(false)
    leds[0] = gpio.A.Pin(4)
    leds[1] = gpio.A.Pin(5)
    leds[2] = gpio.A.Pin(9)

    cfg := &gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain}
    for _, led := range leds {
        led.Set()
        led.Setup(cfg)
    }

    timer = tim.TIM3
    pclk := timer.Bus().Clock()
    if pclk < system.AHB.Clock() {
        pclk *= 2
    }
    freq := uint(1e3) // Hz
    timer.EnableClock(true)
    timer.PSC.Store(tim.PSC(pclk/freq - 1))
    timer.ARR.Store(700) // ms
    timer.DIER.Store(tim.UIE)
    timer.CR1.Store(tim.CEN)

    rtos.IRQ(irq.TIM3).Enable()
}

func blinky(led gpio.Pin, period int) {
    for range ch {
        led.Clear()
        delay.Millisec(100)
        led.Set()
        delay.Millisec(period - 100)
    }
}

func main() {
    go blinky(leds[1], 500)
    blinky(leds[2], 500)
}

func timerISR() {
    timer.SR.Store(0)
    leds[0].Set()
    select {
    case ch <- 0:
        // Success
    default:
        leds[0].Clear()
    }
}

//c:__attribute__((section(".ISRs")))
var ISRs = [...]func(){
    irq.TIM3: timerISR,
}
```

与之前例子相比较下的不同:


1. 添加了第三个 LED，并连接到 PA9 引脚（UART 头的 TXD 引脚）。
2. 时钟（`TIM3`）作为中断源。
3. 新函数 `timerISR` 用来处理 `irq.TIM3` 的中断。
4. 新增容量为 1 的缓冲通道是为了 `timerISR` 和 `blinky` 协程之间的通信。
5. `ISRs` 数组作为*中断向量表*，是更大的*异常向量表*的一部分。
6. `blinky` 中的 `for` 语句被替换成 `range` 语句。


为了方便起见，所有的 LED，或者说它们的引脚，都被放在 `leds` 这个数组里。另外，所有引脚在被配置为输出之前，都设置为一种已知的初始状态（高电平状态）。


在这个例子里，我们想让时钟以 1 kHz 的频率运行。为了配置 TIM3 预分频器，我们需要知道它的输入时钟频率。通过参考手册我们知道，输入时钟频率在 `APBCLK = AHBCLK` 时，与 `APBCLK` 相同，反之等于 2 倍的 `APBCLK`。


如果 CNT 寄存器增加 1 kHz，那么 ARR 寄存器的值等于*更新事件*（重载事件）在毫秒中的计数周期。 为了让更新事件产生中断，必须要设置 DIER 寄存器中的 UIE 位。CEN 位能启动时钟。


时钟外设在低功耗模式下必须启用，为了自身能在 CPU 处于休眠时保持运行: `timer.EnableClock(true)`。这在 STM32F0 中无关紧要，但对代码可移植性却十分重要。


`timerISR` 函数处理 `irq.TIM3` 的中断请求。`timer.SR.Store(0)` 会清除 SR 寄存器里的所有事件标志，无效化向 [NVIC](http://infocenter.arm.com/help/topic/com.arm.doc.ddi0432c/Cihbecee.html) 发出的所有中断请求。凭借经验，由于中断请求无效的延时性，需要在程序一开始马上清除所有的中断标志。这避免了无意间再次调用处理。为了确保万无一失，需要先清除标志，再读取，但是在我们的例子中，清除标志就已经足够了。


下面的这几行代码：



```
select {
case ch <- 0:
    // Success
default:
    leds[0].Clear()
}
```

是 Go 语言中，如何在通道上非阻塞地发送消息的方法。中断处理程序无法一直等待通道中的空余空间。如果通道已满，则执行 `default`，开发板上的LED就会开启，直到下一次中断。


`ISRs` 数组包含了中断向量表。`//c:__attribute__((section(".ISRs")))` 会导致链接器将数组插入到 `.ISRs` 节中。


`blinky` 的 `for` 循环的新写法：



```
for range ch {
    led.Clear()
    delay.Millisec(100)
    led.Set()
    delay.Millisec(period - 100)
}
```

等价于：



```
for {
    _, ok := <-ch
    if !ok {
        break // Channel closed.
    }
    led.Clear()
    delay.Millisec(100)
    led.Set()
    delay.Millisec(period - 100)
}
```

注意，在这个例子中，我们不在意通道中收到的值，我们只对其接受到的消息感兴趣。我们可以在声明时，将通道元素类型中的 `int` 用空结构体 `struct{}` 来代替，发送消息时，用 `struct{}{}` 结构体的值代替 0，但这部分对新手来说可能会有些陌生。


让我们来编译一下代码:



```
$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  11096     228     188   11512    2cf8 cortexm0.elf
```

新的例子占用了 11324 字节的 Flash 空间，比上一个例子多占用了 1132 字节。


采用现在的时序，两个闪烁协程从通道中获取数据的速度，比 `timerISR` 发送数据的速度要快。所以它们在同时等待新数据，你还能观察到 `select` 的随机性，这也是 [Go 规范](https://golang.org/ref/spec#Select_statements)所要求的。


![STM32F030F4P6](/data/attachment/album/201909/24/210528f1226mspt6m61642.png)


开发板上的 LED 一直没有亮起，说明通道从未出现过溢出。


我们可以加快消息发送的速度，将 `timer.ARR.Store(700)` 改为 `timer.ARR.Store(200)`。 现在 `timerISR` 每秒钟发送 5 条消息，但是两个接收者加起来，每秒也只能接受 4 条消息。


![STM32F030F4P6](/data/attachment/album/201909/24/210533bwf6oxaokxwtiwk6.png)


正如你所看到的，`timerISR` 开启黄色 LED 灯，意味着通道上已经没有剩余空间了。


第一部分到这里就结束了。你应该知道，这一部分并未展示 Go 中最重要的部分，接口。


协程和通道只是一些方便好用的语法。你可以用自己的代码来替换它们，这并不容易，但也可以实现。接口是Go 语言的基础。这是文章中 [第二部分](https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html)所要提到的.


在 Flash 上我们还有些剩余空间。




---


via: <https://ziutek.github.io/2018/03/30/go_on_very_small_hardware.html>


作者：[Michał Derkacz](https://ziutek.github.io/) 译者：[wenwensnow](https://github.com/wenwensnow) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
# Go on very small hardware (Part 1)

How low we can *Go* and still do something useful?

I recently bought this ridiculously cheap board:

I bought it for three reasons. First, I have never dealt (as a programmer) with STM32F0 series. Second, the STM32F10x series is getting old. MCUs belonging to the STM32F0 family are just as cheap if not cheaper and has newer peripherals, with many improvements and bugs fixed. Thirdly, I chose the smallest member of the family for the purpose of this article, to make the whole thing a little more intriguing.

## The Hardware

The [STM32F030F4P6](http://www.st.com/content/st_com/en/products/microcontrollers/stm32-32-bit-arm-cortex-mcus/stm32-mainstream-mcus/stm32f0-series/stm32f0x0-value-line/stm32f030f4.html) is impresive piece of hardware:

- CPU:
[Cortex M0](https://en.wikipedia.org/wiki/ARM_Cortex-M#Cortex-M0)48 MHz (only 12000 logic gates, in minimal configuration), - RAM: 4 KB,
- Flash: 16 KB,
- ADC, SPI, I2C, USART and a couple of timers,

all enclosed in TSSOP20 package. As you can see, it is very small 32-bit system.

## The software

If you hoped to see how to use [genuine Go](https://golang.org/) to program this board, you need to read the hardware specification one more time. You must face the truth: there is a negligible chance that someone will ever add support for Cortex-M0 to the Go compiler and this is just the beginning of work.

I’ll use [Emgo](https://github.com/ziutek/emgo), but don’t worry, you will see that it gives you as much Go as it can on such small system.

There was no support for any F0 MCU in [stm32/hal](https://github.com/ziutek/emgo/tree/master/egpath/src/stm32/hal) before this board arrived to me. After brief study of [RM](http://www.st.com/resource/en/reference_manual/dm00091010.pdf), the STM32F0 series appeared to be striped down STM32F3 series, which made work on new port easier.

If you want to follow subsequent steps of this post, you need to install Emgo

```
cd $HOME
git clone https://github.com/ziutek/emgo/
cd emgo/egc
go install
```


and set a couple environment variables

```
export EGCC=path_to_arm_gcc # eg. /usr/local/arm/bin/arm-none-eabi-gcc
export EGLD=path_to_arm_linker # eg. /usr/local/arm/bin/arm-none-eabi-ld
export EGAR=path_to_arm_archiver # eg. /usr/local/arm/bin/arm-none-eabi-ar
export EGROOT=$HOME/emgo/egroot
export EGPATH=$HOME/emgo/egpath
export EGARCH=cortexm0
export EGOS=noos
export EGTARGET=f030x6
```


A more detailed description can be found on the [Emgo website](https://github.com/ziutek/emgo).

Ensure that egc is on your PATH. You can use `go build`

instead of `go install`

and copy egc to your *$HOME/bin* or */usr/local/bin*.

Now create new directory for your first Emgo program and copy example linker script there:

```
mkdir $HOME/firstemgo
cd $HOME/firstemgo
cp $EGPATH/src/stm32/examples/f030-demo-board/blinky/script.ld .
```


## Minimal program

Lets create minimal program in *main.go* file:

```
package main
func main() {
}
```


It’s actually minimal and compiles witout any problem:

```
$ egc
$ arm-none-eabi-size cortexm0.elf
text data bss dec hex filename
7452 172 104 7728 1e30 cortexm0.elf
```


The first compilation can take some time. The resulting binary takes 7624 bytes of Flash (text+data), quite a lot for a program that does nothing. There are 8760 free bytes left to do something useful.

What about traditional *Hello, World!* code:

```
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
```


Unfortunately, this time it went worse:

```
$ egc
/usr/local/arm/bin/arm-none-eabi-ld: /home/michal/P/go/src/github.com/ziutek/emgo/egpath/src/stm32/examples/f030-demo-board/blog/cortexm0.elf section `.text' will not fit in region `Flash'
/usr/local/arm/bin/arm-none-eabi-ld: region `Flash' overflowed by 10880 bytes
exit status 1
```


*Hello, World!* requires at last STM32F030x6, with its 32 KB of Flash.

The *fmt* package forces to include whole *strconv* and *reflect* packages. All three are pretty big, even a slimmed-down versions in Emgo. We must forget about it. There are many applications that don’t require fancy formatted text output. Often one or more LEDs or seven segment display are enough. However, in Part 2, I’ll try to use *strconv* package to format and print some numbers and text over UART.

## Blinky

Our board has one LED connected between PA4 pin and VCC. This time we need a bit more code:

```
package main
import (
"delay"
"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}
led.Setup(cfg)
}
func main() {
for {
led.Clear()
delay.Millisec(100)
led.Set()
delay.Millisec(900)
}
}
```


By convention, the *init* function is used to initialize the basic things and configure peripherals.

`system.SetupPLL(8, 1, 48/8)`

configures RCC to use PLL with external 8 MHz oscilator as system clock source. PLL divider is set to 1, multipler to 48/8 = 6 which gives 48 MHz system clock.

`systick.Setup(2e6)`

setups Cortex-M SYSTICK timer as system timer, which runs the scheduler every 2e6 nanoseconds (500 times per second).

`gpio.A.EnableClock(false)`

enables clock for GPIO port A. *False* means that this clock should be disabled in low-power mode, but this is not implemented int STM32F0 series.

`led.Setup(cfg)`

setups PA4 pin as open-drain output.

`led.Clear()`

sets PA4 pin low, which in open-drain configuration turns the LED on.

`led.Set()`

sets PA4 to high-impedance state, which turns the LED off.

Lets compile this code:

```
$ egc
$ arm-none-eabi-size cortexm0.elf
text data bss dec hex filename
9772 172 168 10112 2780 cortexm0.elf
```


As you can see, blinky takes 2320 bytes more than minimal program. There are still 6440 bytes left for more code.

Let’s see if it works:

```
$ 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 v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
debug_level: 0
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
adapter speed: 950 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x0800119c msp: 0x20000da0
adapter speed: 4000 kHz
** Programming Started **
auto erase enabled
target halted due to breakpoint, current mode: Thread
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000da0
wrote 10240 bytes from file cortexm0.elf in 0.817425s (12.234 KiB/s)
** Programming Finished **
adapter speed: 950 kHz
```


For this article, the first time in my life, I converted short video to [animated PNG](https://en.wikipedia.org/wiki/APNG) sequence. I’m impressed, goodbye YouTube and sorry IE users. See [apngasm](http://apngasm.sourceforge.net/) for more info. I should study HTML5 based alternative, but for now, APNG is my preffered way for short looped videos.

## More Go

If you aren’t a Go programmer but you’ve heard something about Go language, you can say: “This syntax is nice, but not a significant improvement over C. Show me *Go language*, give mi *channels* and *goroutines!*”.

Here you are:

```
import (
"delay"
"stm32/hal/gpio"
"stm32/hal/system"
"stm32/hal/system/timer/systick"
)
var led1, led2 gpio.Pin
func init() {
system.SetupPLL(8, 1, 48/8)
systick.Setup(2e6)
gpio.A.EnableClock(false)
led1 = gpio.A.Pin(4)
led2 = gpio.A.Pin(5)
cfg := &gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain}
led1.Setup(cfg)
led2.Setup(cfg)
}
func blinky(led gpio.Pin, period int) {
for {
led.Clear()
delay.Millisec(100)
led.Set()
delay.Millisec(period - 100)
}
}
func main() {
go blinky(led1, 500)
blinky(led2, 1000)
}
```


Code changes are minor: the second LED was added and the previous *main* function was renamed to *blinky* and now requires two parameters. *Main* starts first *blinky* in new goroutine, so both LEDs are handled *concurrently*. It is worth mentioning that *gpio.Pin* type supports concurrent access to different pins of the same GPIO port.

Emgo still has several shortcomings. One of them is that you have to specify a maximum number of goroutines (tasks) in advance. It’s time to edit *script.ld*:

```
ISRStack = 1024;
MainStack = 1024;
TaskStack = 1024;
MaxTasks = 2;
INCLUDE stm32/f030x4
INCLUDE stm32/loadflash
INCLUDE noos-cortexm
```


The size of the stacks are set by guess, and we’ll not care about them at the moment.

```
$ egc
$ arm-none-eabi-size cortexm0.elf
text data bss dec hex filename
10020 172 172 10364 287c cortexm0.elf
```


Another LED and goroutine costs 248 bytes of Flash.

## Channels

Channels are the [preffered way](https://blog.golang.org/share-memory-by-communicating) in Go to communicate between goroutines. Emgo goes even further and allows to use *buffered* channels by *interrupt handlers*. The next example actually shows such case.

```
package main
import (
"delay"
"rtos"
"stm32/hal/gpio"
"stm32/hal/irq"
"stm32/hal/system"
"stm32/hal/system/timer/systick"
"stm32/hal/tim"
)
var (
leds [3]gpio.Pin
timer *tim.Periph
ch = make(chan int, 1)
)
func init() {
system.SetupPLL(8, 1, 48/8)
systick.Setup(2e6)
gpio.A.EnableClock(false)
leds[0] = gpio.A.Pin(4)
leds[1] = gpio.A.Pin(5)
leds[2] = gpio.A.Pin(9)
cfg := &gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain}
for _, led := range leds {
led.Set()
led.Setup(cfg)
}
timer = tim.TIM3
pclk := timer.Bus().Clock()
if pclk < system.AHB.Clock() {
pclk *= 2
}
freq := uint(1e3) // Hz
timer.EnableClock(true)
timer.PSC.Store(tim.PSC(pclk/freq - 1))
timer.ARR.Store(700) // ms
timer.DIER.Store(tim.UIE)
timer.CR1.Store(tim.CEN)
rtos.IRQ(irq.TIM3).Enable()
}
func blinky(led gpio.Pin, period int) {
for range ch {
led.Clear()
delay.Millisec(100)
led.Set()
delay.Millisec(period - 100)
}
}
func main() {
go blinky(leds[1], 500)
blinky(leds[2], 500)
}
func timerISR() {
timer.SR.Store(0)
leds[0].Set()
select {
case ch <- 0:
// Success
default:
leds[0].Clear()
}
}
//c:__attribute__((section(".ISRs")))
var ISRs = [...]func(){
irq.TIM3: timerISR,
}
```


Changes compared to the previous example:

-
Thrid LED was added and connected to PA9 pin (TXD pin on UART header).

-
The timer (TIM3) has been introduced as a source of interrupts.

-
The new

*timerISR*function handles*irq.TIM3*interrupt. -
The new buffered channel with capacity 1 is intended for communication between

*timerISR*and*blinky*goroutines. -
The

*ISRs*array acts as*interrupt vector table*, a part of bigger*exception vector table*. -
The

*blinky’s for statement*was replaced with a*range statement*.

For convenience, all LEDs, or rather their pins, have been collected in the *leds* array. Additionally, all pins have been set to a known initial state (high), just before they were configured as outputs.

In this case, we want the timer to tick at 1 kHz. To configure TIM3 prescaler, we need to known its input clock frequency. According to RM the input clock frequency is equal to APBCLK when APBCLK = AHBCLK, otherwise it is equal to 2 x APBCLK.

If the CNT register is incremented at 1 kHz, then the value of ARR register corresponds to the period of counter *update event* (reload event) expressed in milliseconds. To make update event to generate interrupts, the UIE bit in DIER register must be set. The CEN bit enables the timer.

Timer peripheral should stay enabled in low-power mode, to keep ticking when the CPU is put to sleep: `timer.EnableClock(true)`

. It doesn’t matter in case of STM32F0 but it’s important for code portability.

The *timerISR* function handles *irq.TIM3* interrupt requests. `timer.SR.Store(0)`

clears all event flags in SR register to deassert the IRQ to [NVIC](http://infocenter.arm.com/help/topic/com.arm.doc.ddi0432c/Cihbecee.html). The rule of thumb is to clear the interrupt flags immedaitely at begining of their handler, because of the IRQ deassert latency. This prevents unjustified re-call the handler again. For absolute certainty, the clear-read sequence should be performed, but in our case, just clearing is enough.

The following code:

```
select {
case ch <- 0:
// Success
default:
leds[0].Clear()
}
```


is a Go way to non-blocking sending on a channel. No one interrupt handler can afford to wait for a free space in the channel. If the channel is full, the default case is taken, and the onboard LED is set on, until the next interrupt.

The *ISRs* array contains interrupt vectors. The `//c:__attribute__((section(".ISRs")))`

causes that the linker will inserted it into .ISRs section.

The new form of *blinky’s for* loop:

```
for range ch {
led.Clear()
delay.Millisec(100)
led.Set()
delay.Millisec(period - 100)
}
```


is the equivalent of:

```
for {
_, ok := <-ch
if !ok {
break // Channel closed.
}
led.Clear()
delay.Millisec(100)
led.Set()
delay.Millisec(period - 100)
}
```


Note that in this case we aren’t interested in the value received from the channel. We’re interested only in the fact that there is something to receive. We can give it expression by declaring the channel’s element type as empty struct `struct{}`

instead of *int* and send `struct{}{}`

values instead of 0, but it can be strange for newcomer’s eyes.

Lets compile this code:

```
$ egc
$ arm-none-eabi-size cortexm0.elf
text data bss dec hex filename
11096 228 188 11512 2cf8 cortexm0.elf
```


This new example takes 11324 bytes of Flash, 1132 bytes more than the previous one.

With the current timings, both *blinky* goroutines consume from the channel much faster than the *timerISR* sends to it. So they both wait for new data simultaneously and you can observe the randomness of *select*, required by the [Go specification](https://golang.org/ref/spec#Select_statements).

The onboard LED is always off, so the channel overrun never occurs.

Let’s speed up sending, by changing `timer.ARR.Store(700)`

to `timer.ARR.Store(200)`

. Now the *timerISR* sends 5 messages per second but both recipients together can receive only 4 messages per second.

As you can see, the *timerISR* lights the yellow LED which means there is no space in the channel.

This is where I finish the first part of this article. You should know that this part didn’t show you the most important thing in Go language, *interfaces*.

Goroutines and channels are only nice and convenient syntax. You can replace them with your own code - not easy but feasible. Interfaces are the essence of Go,
and that’s what I will start with in the [second part](/2018/04/14/go_on_very_small_hardware2.html) of this article.

We still have some free space on Flash.