Skip to content

Adding a new board

Sam Smith edited this page Dec 13, 2020 · 8 revisions

Adding a board can be easy or difficult, depending on how much is already implemented. For example, it may be that the microcontroller is already supported in which case the main thing that needs to be done is mapping internal pin names (such as PB5) to names as they appear on the silkscreen (such as D13, A5). If this is the case, skip to "Adding a new board" below.

Below you will find all the steps needed, of which you can probably skip the first few.

Adding a new baremetal architecture

Adding a new baremetal architecture is not easy, and requires some low-level understand of it. Luckily, many common architectures are already supported so you won't have to do this. Adding support for a new GOARCH for a common GOOS (such as Linux) is different and usually much easier.

Adding a new architecture involves the following things:

  • Checking backend support from LLVM. This is a hard requirement. If the backend is not yet supported, you might consider adding it to LLVM, but note that this is absolutely not trivial.
  • Adding support to the compiler. Sometimes the TinyGo compiler needs a few small modifications to work with a new architecture.
  • Creating a linker script. There are some conventions used in TinyGo which may be useful to follow, and also some conventions used by the architecture which are also useful to follow. One place where TinyGo deviates from the common memory layout is that it places the stack at lower memory addresses and makes it a fixed size, at least for Cortex-M device to support zero-cost stack overflow checking. You can look at existing linker scripts to copy/modify to your needs.

Supported architectures as of July 2020:

  • Most desktop systems (386, amd64, arm, arm64)
  • Cortex-M
  • RISC-V
  • WebAssembly
  • AVR (as used in the Arduino Uno).

Adding a new chip family or vendor

Adding support for a new chip or silicon vendor mainly involves getting a SVD file and setting up autogenerating register access code for it.

Make sure a SVD file for the chip you want to target is included in the cmsis-svd repository. If it is not there yet, you may need to hunt around to find it. It may be included with a vendor provided tool (especially debugging tools), so look for downloads related to the chip and look for SVD files in them. Once you've found it, make a PR to the cmsis-svd repository to add it.

SVD files are used in particular for Cortex-M chips but sometimes they are also written for other chips. Sometimes they are community written, especially by the Rust Embedded community, as they (like TinyGo) depend on SVD files for register access. Examples of community written SVD files include support for the RISC-V fe310 and the Xtensa ESP32.

Once the SVD file is included, you can use it from TinyGo by updating the Makefile to generate register access code. Take a look at the gen-device-stm32 target for example. Please note that it's quite common for new vendors to break the existing SVD conversion scripts, as vendors like to interpret the SVD specification in their own ways, which means that supporting a previously unsupported vendor may involve editing the generation script in tools/gen-device-svd.

Adding a new chip

A new chip needs support for the following things (in addition to board support):

  • Some support in the machine package. Create a new file src/machine/machine_<chip>.go file and implement the things needed to support this chip.
  • Some runtime support for initializing the chip and for console output and sleeping. See the existing runtime support in src/runtime/runtime_<chip>.go files. Check how much of this is actually chip specific, often basic peripherals like UART are shared by chips in the same chip family and can thus be implemented for the whole family.
  • A new linker script fragment to define flash and RAM layout of this chip. Take a look at the existing linker scripts (targets/*.ld) to see how they're structured. Except for very few exceptions, it's best to simply define flash and RAM areas and include the main linker script for the architecture. As an example, take a look at targets/atsamd21.ld.

Adding a new board

Finally, we're getting to the board specific part. This is often all you need to add support a new board.

Adding a new board is relatively easy. You will need:

  • A new target description in .json format. It is easiest to copy an existing description from a board with the same chip and adjust it to your needs. The things that will need to be modified are at least the build tags, and likely the flash property as well if the board is flashed in a different way.
  • Some definitions in src/machine/board_<boardname>.go. These are the silkscreen pin names (such as D13) and common definitions such as LED. You also need to define standard pins, such as the standard I2C or UART pins. See other board definitions for examples. If some pins do not exist on a board (for example, the board doesn't have a standard UART), use NoPin for these.

Making a board PR

Before (or after) making a PR for a board, please check that it is as small as possible while still implementing enough to prove that it works, in particular if you add support for a new chip at the same time. For example, provide just enough support to blink an LED. Larger PRs take longer to review and require more rounds to review. After initial support has been added, you can make more PRs for specific functionality (such as UART, SPI, I2C, other boards, etc).

Build tags

You may have noticed that TinyGo uses a lot more build tags than is common for Go. For example, Arduino Uno uses the following build tags: arduino, atmega328p, atmega, avr5, avr, baremetal, arm, linux. Why so many? And what does linux/arm have to do with it? It's mostly to support the large variation in microcontrollers. Let's break them down:

  • arduino: this is the board name. Actually, it's the Arduino Uno so arduino is a slightly wrong name because there are other Arduino boards like the Arduino Zero with a completely different architecture. But when talking about Arduino most people actually refer to the Arduino Uno so that's why it is this name.
  • atmega328p is the chip used in the Arduino Uno, the common ATmega328p.
  • atmega is the chip family. There is also the attiny, for example, but it isn't (yet) supported by TinyGo.
  • avr5 is the AVR sub-family of many families.
  • avr is the base instruction set architecture, which is a particular 8-bit Harvard-style architecture.
  • linux, arm are the oddballs here: the AVR is not Linux nor ARM. These build tags are added because some supported GOOS/GOARCH needs to be used otherwise many Go standard library packages will fail to compile. Any OS/ARCH could be picked, but Linux/ARM seems like a good choice. Because the syscall package is replaced on baremetal targets, most Linux-specific things are replaced already in the standard library.

In general, build tags are sorted from more specific to less specific in the .json target descriptions.