Skip to content

Commit 35f8233

Browse files
bxparksdeadprogram
authored andcommitted
docs/tutorial: Add serial monitor tutorial
In 4 subsections: 1) Writing to the serial port using fmt.Print(), fmt.Println(), and the built-in functions print() and println(). Explain flash memory consumption of `fmt` package. 2) Running `tinygo monitor` to see the output on the host computer. 3) Reading from the serial port. 4) Alternative serial monitors (Arduino IDE, pyserial, picocom).
1 parent c8d5cfd commit 35f8233

File tree

1 file changed

+294
-0
lines changed

1 file changed

+294
-0
lines changed
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
---
2+
title: "Serial Monitor"
3+
weight: 3
4+
description: |
5+
How to write to and read from the serial port.
6+
---
7+
8+
When you run a Go program on a desktop computer, you can use the `fmt.Print()`,
9+
`fmt.Println()`, and `fmt.Printf()` functions from the Go standard [fmt
10+
package](https://pkg.go.dev/fmt) to print strings and numbers to the terminal
11+
program on the desktop computer. The Go language also supports the low-level
12+
[print() and println()](https://go.dev/ref/spec#Bootstrapping) built-in
13+
functions to print to the terminal.
14+
15+
A TinyGo program running on a microcontroller can use those same functions to
16+
print strings and numbers to its serial monitor port and have them appear on the
17+
terminal program on the host computer. By default, the `fmt` functions and the
18+
`print()/println()` functions are configured to send to the `machine.Serial`
19+
object of the microcontroller.
20+
21+
On some microcontrollers, the `machine.Serial` object is configured to send to
22+
the
23+
[UART](https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter)
24+
chip, which is often wired to a [USB-to-serial
25+
adapter](https://en.wikipedia.org/wiki/USB-to-serial_adapter) chip on the dev
26+
board. The adapter chip converts the serial bits into USB packets to the host
27+
computer. On other microcontrollers, the USB controller is built directly into
28+
the [microcontroller SoC](https://en.wikipedia.org/wiki/System_on_a_chip). The
29+
`machine.Serial` object on these microcontrollers is configured to send to the
30+
USB bus directly, instead of going through a USB-to-serial adapter.
31+
32+
In the context of this tutorial, it does not matter whether the microcontroller
33+
uses a UART controller or a USB controller. In both cases, the microcontroller
34+
will appear as a serial device on the host computer which can communicate via
35+
applications that read from and write to the serial port on the host computer.
36+
37+
## Serial Output
38+
39+
### Using `fmt.Print()` and `fmt.Println()`
40+
41+
Here is a sample program that writes a line every second to the `machine.Serial`
42+
port:
43+
44+
```go
45+
package main
46+
47+
import (
48+
"time"
49+
"fmt"
50+
)
51+
52+
func main() {
53+
count := 0
54+
for {
55+
fmt.Println(count, ": Hello, World")
56+
time.Sleep(time.Millisecond * 1000)
57+
count++
58+
}
59+
}
60+
```
61+
62+
This can be flashed to the microcontroller using the `tinygo flash` command
63+
described in the [Blinky tutorial]({{<ref "blinky">}}).
64+
65+
### Using `print()` and `println()`
66+
67+
One problem with the above program is that `fmt` is a large package that
68+
consumes substantial amount of flash memory on a microcontroller. The built-in
69+
functions `print()` and `println()` consume far less resources. The above
70+
program can be written like this:
71+
72+
```go
73+
package main
74+
75+
import (
76+
"time"
77+
)
78+
79+
func main() {
80+
count := 0
81+
for {
82+
println(count, ": Hello, World")
83+
time.Sleep(time.Millisecond * 1000)
84+
count++
85+
}
86+
}
87+
```
88+
89+
An estimate of the flash memory consumption can be printed by the TinyGo
90+
compiler using the [-size]({{<ref "../reference/usage/misc-options">}}) flag.
91+
Here is a table that shows the 2 versions of the program above for some
92+
microcontrollers that I have readily available:
93+
94+
```
95+
+-----------------+---------------+-----------+
96+
| Board Type | fmt.Println() | println() |
97+
+-----------------+---------------+-----------+
98+
| Arduino Zero | 43532 | 7328 |
99+
| Seeeduino Xiao | 43532 | 7388 |
100+
| STM32 BluePill | 41756 | 6296 |
101+
| ESP8266 D1 Mini | 44961 | 3588 |
102+
| ESP32 | 42410 | 3335 |
103+
+-----------------+---------------+-----------+
104+
```
105+
106+
The `fmt` package increases flash memory consumption by 35 kB to 40 kB. On some
107+
microcontrollers with limited amount of flash memory, (e.g. the STM32 Blue Pill
108+
with 64 kB of flash, the Arduino Zero or Seeeduino Xiao both with 256 kB of
109+
flash), it may be worth avoiding the overhead of the `fmt` package by using the
110+
built-in `print()` and `println()` instead.
111+
112+
## Serial Monitor on Host Computer
113+
114+
To see the output of the serial port from the microcontroller, we need to run a
115+
serial monitor application on the host computer. There are many ways to do this,
116+
but the easiest is probably the `tinygo monitor` subcommand which is built
117+
directly into the `tinygo` program itself.
118+
119+
After flashing the program above, run the `tinygo monitor` program to see the
120+
output every second from the microcontroller:
121+
122+
```
123+
$ tinygo monitor
124+
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
125+
4 : Hello, World
126+
5 : Hello, World
127+
6 : Hello, World
128+
[...]
129+
```
130+
131+
In this example, the serial monitor missed the first 4 lines of "Hello, World"
132+
(0 to 3) because the program started to print those lines immediately after
133+
flashing, but before the serial monitor was connected.
134+
135+
## Serial Input
136+
137+
Occasionally it is useful to send characters from the host computer to the
138+
microcontroller. The following program reads a single byte from the
139+
`machine.Serial` object and prints the character back to the host computer.
140+
141+
The caveat is that the `Serial.ReadByte()` feature is *not* currently
142+
implemented on every microcontrollers supported by TinyGo. For example, the
143+
following program does not work on the ESP32 or the ESP8266.
144+
145+
```go
146+
package main
147+
148+
import (
149+
"machine"
150+
"time"
151+
)
152+
153+
func main() {
154+
time.Sleep(time.Millisecond * 2000)
155+
println("Reading from the serial port...")
156+
157+
for {
158+
c, err := machine.Serial.ReadByte()
159+
if err == nil {
160+
if c < 32 {
161+
// Convert nonprintable control characters to
162+
// ^A, ^B, etc.
163+
machine.Serial.WriteByte('^')
164+
machine.Serial.WriteByte(c + '@')
165+
} else if c >= 127 {
166+
// Anything equal or above ASCII 127, print ^?.
167+
machine.Serial.WriteByte('^')
168+
machine.Serial.WriteByte('?')
169+
} else {
170+
// Echo the printable character back to the
171+
// host computer.
172+
machine.Serial.WriteByte(c)
173+
}
174+
}
175+
176+
// This assumes that the input is coming from a keyboard
177+
// so checking 120 per second is sufficient. But if the
178+
// data comes from another processor, the port can
179+
// theoretically receive as much as 11000 bytes/second
180+
// (115200 baud). This delay can be removed and the
181+
// Serial.Read() method can be used to retrieve
182+
// multiple bytes from the receive buffer for each
183+
// iteration.
184+
time.Sleep(time.Millisecond * 8)
185+
}
186+
}
187+
```
188+
189+
You can flash this program to the microcontroller (in this example, a SAMD21 M0+
190+
clone that emulates an Arduino Zero), and fire up the monitor like this:
191+
192+
```
193+
$ tinygo flash -target=arduino-zero
194+
$ tinygo monitor
195+
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
196+
Reading from the serial port...
197+
abcdef^A^B^D^E^F^G^H^I^J^K^L^M^N^O^P^R^T^U^V^W^X^Y^^^[^]^_^?
198+
```
199+
200+
Type a few characters in the `tinygo monitor`, for example "abcdef". You should
201+
see the characters echoed back by the microcontroller, as shown above. If you
202+
type a [nonprintable control
203+
characters](https://en.wikipedia.org/wiki/C0_and_C1_control_codes), these are
204+
echoed back as 2 characters: the caret character `^` and a letter representing
205+
the control character. For example, typing Control-P prints `^P`.
206+
207+
A number of control characters are intercepted by the `tinygo monitor` itself
208+
instead of being sent to the microcontroller:
209+
210+
* Control-C: terminates the `tinygo monitor`
211+
* Control-Z: suspends the `tinygo monitor` and drops back into shell
212+
* Control-\\: terminates the `tinygo monitor` with a stack trace
213+
* Control-S: flow-control, suspends output to the console
214+
* Control-Q: flow-control, resumes output to the console
215+
* Control-@: thrown away by `tinygo monitor`
216+
217+
## Alternative Serial Monitors
218+
219+
There are many alternative serial monitor programs that can be used instead of
220+
`tinygo monitor`. The setup is slightly more complicated because you need to
221+
know the serial port on the host computer that the microcontroller is mapped to.
222+
223+
On Linux machines, the microcontrollers will be assigned a serial port that has
224+
a `USB` prefix or an `ACM` prefix like this:
225+
226+
* `/dev/ttyUSB0`
227+
* `/dev/ttyACM0`
228+
229+
On MacOS machines, the serial port will look like this:
230+
231+
* `/dev/cu.usbserial-1420`
232+
* `/dev/cu.usbmodem6D8733AC53571`
233+
234+
[TODO: No idea on Windows.]
235+
236+
### Arduino IDE
237+
238+
The [Arduino IDE](https://www.arduino.cc/en/software) contains its own serial
239+
monitor. You may choose to use that instead. You need to set the serial port
240+
(something like `/dev/ttyUSB0` on Linux, or `/dev/cu.usbserial-1420` on MacOS),
241+
and set the baud rate to 115200.
242+
243+
### pyserial
244+
245+
The [pyserial](https://pyserial.readthedocs.io/en/latest/pyserial.html) is a
246+
Python library that comes with its own serial monitor. Setting up python3
247+
environment is a complex topic that is beyond the scope of this document. But if
248+
you are able to install `python3` and `pip3`, you can install `pyserial` and use
249+
its built-in `miniterm` tool roughly like this:
250+
251+
```
252+
$ python3 -m pip install --user pyserial
253+
$ python3 -m serial.tools.miniterm /dev/ttyUSB0 115200
254+
```
255+
256+
Another useful feature of `pyserial` is the `list_ports` command:
257+
258+
```
259+
$ python3 -m serial.tools.list_ports
260+
/dev/ttyACM0
261+
/dev/ttyS0
262+
2 ports found
263+
```
264+
265+
This is useful when you plug in a random microcontroller to the USB port, and
266+
you cannot remember which serial port it is mapped to.
267+
268+
### picocom
269+
270+
The [picocom](https://github.com/npat-efault/picocom) terminal emulator can be
271+
installed on both Linux and MacOS. If you are using an Ubuntu flavored Linux,
272+
the installation is something like:
273+
274+
```
275+
$ sudo apt install picocom
276+
```
277+
278+
On MacOS, most people use [Homebrew](https://brew.sh/), and it can be installed
279+
like this:
280+
281+
```
282+
$ brew install picocom
283+
```
284+
285+
It can be invoked like this:
286+
287+
```
288+
$ picocom -b 115200 /dev/ttyACM0
289+
port is : /dev/ttyACM0
290+
picocom v3.1
291+
[...]
292+
Type [C-a] [C-h] to see available commands
293+
Terminal ready
294+
```

0 commit comments

Comments
 (0)