|
| 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