Emgo: Bare metal Go (language for programming embedded systems)

Emgo

Emgo consist of a compiler and the set of packages that allows you to run Go programs on small 32-bit microcontrollers. The compiler generates C as an intermediate code and uses C compiler to produce loadable binaries.

There is Embedded Go project that evolved from Emgo which is based on the reference Go compiler. It has much higher hardware requirements but it is also 100% compatible with the Go language specification. The main development process has moved to Embeadded Go but I still use Emgo for small MCUs so some things can be backported here.

How to start

First of all, to try Emgo you need the Go compiler installed. The current Emgo compiler and whole process described below requires also some kind of Unix-like operating system. There is a chance that Windows with Cygwin can be used but this was not tested.

You can probably use go get to install Emgo but the preferred way is to clone this repository using the git command:

git clone https://github.com/ziutek/emgo.git

Next you need to build and install egc (Emgo compiler):

cd emgo/egc
go install

For now, Emgo supports only ARM Cortex-M based MCUs. To build code for Cortex-M architecture, you need to install ARM embedded toolchain. You have two options: install a package included in your OS distribution (in case of Debian/Ubuntu Linux):

apt-get install gcc-arm-none-eabi

or better go to the GNU ARM Embedded Toolchain website and download most recent toolchain. This is preferred version of toolchain, try use it before report any bug with compilation.

Installed toolchain contains set of arm-none-eabi-* binaries. Find their location and set required enviroment 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=path_to_egroot_directory # eg. $HOME/emgo/egroot
export EGPATH=path_to_egpath_directory # eg. $HOME/emgo/egpath

Load/debug helper scripts use also some other tools from the ARM toolchain (eg. arm-none-eabi-objcopy). If you downloaded the toolchain manually, you probably need also to add its bin directory to the PATH enviroment variable:

export PATH=$PATH:path_to_arm_bin_dir  # eg. /usr/local/arm/bin

Now you are ready to compile some example code. There are two directories that contain examples:

$EGPATH/src/stm32/examples

$EGPATH/src/nrf5/examples

Use one that contains example for your MCU/devboard.

For example, to build blinky for STM32 NUCLEO-F411RE board:

cd $EGPATH/src/stm32/examples/nucleo-f411re/blinky
../build.sh

The first compilation may take some time because egc must process all required libraries and runtime. If everything went well you obtain cortexm4f.elf binary file.

Compilation can produce two kind of binaries: binaries that should be loaded to RAM or loaded to Flash of your MCU.

Loading to RAM is useful in case of small programs, during working on the code and debuging. Loading to RAM is faster, allows unlimited number of breakpoints, allows to modify constants and even the code from debuger and saves your Flash, which has big but limited number of erase cycles.

To run program loaded to RAM you must change the MCU boot behavior. In case of most STM32 MCUs you simply need to set high BOOT0 and BOOT1 pins. Newer STM32 MCUs do not provide BOOT1 pin, insdead they require to program some persistant bits that change the default booting behavior when BOOT0 is set high.

However, eventually your program should be loaded to Flash. Sometimes you have no other alternative: your program is too big to fit into RAM or your MCU does not provide easy way to run program from RAM (eg. nRF51). Some bugs may only appear when the program runs from Flash or from RAM, so it is advisable to perform the final test of any new piece of code in both ways.

You need tools to load compiled binary to your MCU's RAM/Flash and allow to debug it. Such tools usually have a hardware part and a software part. In case of STM32 Nucleo or Discovery development boards the hardware part (ST-LINK programmer) is integrated with the board, so you only need the software part, which can be OpenOCD or Texane's stlink. You must install one of them or both before next steps (ensure that openocd and/or st-util binaries are on your PATH).

There is a set of scripts for any board in example directory that simplifies loding and debuging process. The load-oocd.sh and swo-oocd.sh scripts can handle SWO output from ITM (Instrumentation Trace Macrocell) but needs itmsplit to convert binary stream to readable messages. SWO is very useful for debuging and fmt.Print* functions by default use ITM trace port as standard output. Install itmsplit with the command:

go get github.com/ziutek/itmsplit

and ensure that produced binary is in your PATH.

You need the rights to the USB device that corresponds to your programming hardware. This can be done through adding appropriate udev rules. See example rules in Texane's stlink repository, in etc directory.

To program your MCU using Texane's stlink run:

../load-stutil.sh

If you want to use OpenOCD, run:

../load-oocd.sh

Some examples by default are configured to run from RAM. If you have problem to setup your board to run from RAM, edit script.ld file and change the line:

INCLUDE stm32/loadram

to

INCLUDE stm32/loadflash

More editing is need for STM32F1xx series: you additionally have to comment two lines:

bootOffset = 0x1E0;
ENTRY(bootRAM)

You can also load your program during debug session in gdb. Run ../debug-stutil.sh or ../debug-oocd.sh and next invoke load command.

There are also scripts for Black Magic Probe: load-bmp.sh, debug-bmp.sh.

If you played with examples and enjoyed Emgo you probably want to write your first program from scratch, compile it and load without all these magical scripts. This blog post presents how to do it.

You may encounter a problem where st-util or openocd can not connect to your board. This is often the case for boards without integrated programmer/debuger, when the external programmer has no connection to the reset pin. If you can not make such connection, simply press and hold the reset button on your board, next run load/debug script and after the script finishes printing anything, release the reset.

Documentation

Standard library

Libraries for STM32, nRF5 and other

Other resources

Blog

YouTube

Forum

Owner
Comments
  • Goroutines: undefined reference to `internal$NewTask'

    Goroutines: undefined reference to `internal$NewTask'

    Michal, I am trying to compile a code with go routine - a very similar to the one which you have presented on your blog.

    package main
    
    import (
    	"delay"
    	"fmt"
    
    	"stm32/hal/gpio"
    	"stm32/hal/system"
    	"stm32/hal/system/timer/systick"
    )
    
    var led, led2 gpio.Pin
    
    func init() {
    	system.SetupPLL(-48, 6, 20, 0, 0, 2) // 80 MHz (max. for voltage Range 1).
    	//system.Setup80(0, 0) // Short form of above configuration.
    	//system.SetupPLL(-4, 1, 26, 0, 0, 4) // 26 MHz (max. for voltage Range 2).
    	system.SetupMSI(100) // Lowest possible frequency (100 kHz).
    	systick.Setup(2e7) // Typical 2e6 ns is to low for 100 kHz SysClk.
    
    	gpio.A.EnableClock(false)
    	led = gpio.A.Pin(5)
    	led2 = gpio.A.Pin(6)
    
    	cfg := gpio.Config{Mode: gpio.Out, Speed: gpio.Low}
    	led.Setup(&cfg)
    	// cfg2 := gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain}
    	led2.Setup(&cfg)
    }
    
    func blinky(led gpio.Pin, period int) {
    	for {
    		led.Clear()
    		delay.Millisec(100)
    		led.Set()
    		delay.Millisec(period - 100)
    	}
    }
    
    func main() {
    	delay.Millisec(500)
    	buses := []system.Bus{
    		system.Core,
    		system.AHB,
    		system.APB1,
    		system.APB2,
    	}
    	fmt.Printf("\r\n")
    	for _, bus := range buses {
    		fmt.Printf("%4s: %9d Hz\r\n", bus, bus.Clock())
    	}
    	go blinky(led, 500)
    	blinky(led2, 1000)
    }
    

    script.ld:

    ISRStack = 2048;
    MainStack = 8192;
    TaskStack = 1024;
    MaxTasks = 4;
    
    INCLUDE stm32/l476xg
    INCLUDE stm32/loadflash
    INCLUDE noos-cortexm
    
    

    Unfortunately, I am getting compilation errors all the time (even for the example code from: "f030-demo-board/demo").

    /tmp/eg-build977705888/main/__noos_cortexm4f_l476xx.o: In function `main$main':
    emgo/egpath/src/stm32/examples/nucleo-l476rg/blinky/__noos_cortexm4f_l476xx.c:64: undefined reference to `internal$NewTask'
    
    /tmp/eg-build490269123/main/__noos_cortexm0_f030x6.o: In function `main$main':
    emgo/egpath/src/stm32/examples/f030-demo-board/demo/__noos_cortexm0_f030x6.c:88: undefined reference to `internal$NewTask'
    emgo/egpath/src/stm32/examples/f030-demo-board/demo/__noos_cortexm0_f030x6.c:96: undefined reference to `internal$NewTask'
    

    I am using the newest gcc-arm-none-eabi toolchain: gcc-arm-none-eabi-7-2017-q4-major and go version 1.9.5.

    Am I doing something wrong or is there something missing in the repository?

  • Docs about differences between Emgo and Go?

    Docs about differences between Emgo and Go?

    I just noticed this project, it looks cool, but documentation is a little bit sparse.

    Is there one page summary about differences between Emgo and Go?

  • adding dsi peripheral support

    adding dsi peripheral support

    I've got an stm32f469 discovery board, I've added preliminary support for the chip and can successfully run blinky.

    I'm trying to write a driver for the dsi display peripheral, and stm32xgen doesn't seem to handle its registers properly, along with perhaps a lot of other registers. The common factor seems to be registers which expose individual bits I think? like this:

     7591 #define DSI_GPDR_DATA1_Pos            (0U)
     7592 #define DSI_GPDR_DATA1_Msk            (0xFFU << DSI_GPDR_DATA1_Pos)            /*!< 0x000000FF */
     7593 #define DSI_GPDR_DATA1                DSI_GPDR_DATA1_Msk                       /*!< Payload Byte 1 */
     7594 #define DSI_GPDR_DATA1_0              (0x01U << DSI_GPDR_DATA1_Pos)            /*!< 0x00000001 */
     7595 #define DSI_GPDR_DATA1_1              (0x02U << DSI_GPDR_DATA1_Pos)            /*!< 0x00000002 */
     7596 #define DSI_GPDR_DATA1_2              (0x04U << DSI_GPDR_DATA1_Pos)            /*!< 0x00000004 */
     7597 #define DSI_GPDR_DATA1_3              (0x08U << DSI_GPDR_DATA1_Pos)            /*!< 0x00000008 */
     7598 #define DSI_GPDR_DATA1_4              (0x10U << DSI_GPDR_DATA1_Pos)            /*!< 0x00000010 */
     7599 #define DSI_GPDR_DATA1_5              (0x20U << DSI_GPDR_DATA1_Pos)            /*!< 0x00000020 */
     7600 #define DSI_GPDR_DATA1_6              (0x40U << DSI_GPDR_DATA1_Pos)            /*!< 0x00000040 */
     7601 #define DSI_GPDR_DATA1_7              (0x80U << DSI_GPDR_DATA1_Pos)            /*!< 0x00000080 */
    
    Bad bitmask 0x01U << DSI_GPDR_DATA1_Pos : strconv.ParseUint: parsing "0x01U << DSI_GPDR_DATA1_Pos": invalid syntax
    Bad bitmask 0x02U << DSI_GPDR_DATA1_Pos : strconv.ParseUint: parsing "0x02U << DSI_GPDR_DATA1_Pos": invalid syntax
    Bad bitmask 0x04U << DSI_GPDR_DATA1_Pos : strconv.ParseUint: parsing "0x04U << DSI_GPDR_DATA1_Pos": invalid syntax
    Bad bitmask 0x08U << DSI_GPDR_DATA1_Pos : strconv.ParseUint: parsing "0x08U << DSI_GPDR_DATA1_Pos": invalid syntax
    Bad bitmask 0x10U << DSI_GPDR_DATA1_Pos : strconv.ParseUint: parsing "0x10U << DSI_GPDR_DATA1_Pos": invalid syntax
    Bad bitmask 0x20U << DSI_GPDR_DATA1_Pos : strconv.ParseUint: parsing "0x20U << DSI_GPDR_DATA1_Pos": invalid syntax
    Bad bitmask 0x40U << DSI_GPDR_DATA1_Pos : strconv.ParseUint: parsing "0x40U << DSI_GPDR_DATA1_Pos": invalid syntax
    Bad bitmask 0x80U << DSI_GPDR_DATA1_Pos : strconv.ParseUint: parsing "0x80U << DSI_GPDR_DATA1_Pos": invalid syntax
    

    The generated dsi.go has a bunch of constants with the same name, and I get a ton of errors:

    src/stm32/hal/raw/dsi/f469xx--dsi.go:110:2: 	other declaration of VCID
    src/github.com/ianmcmahon/emgo/egpath/src/stm32/hal/raw/dsi/f469xx--dsi.go:192:2: VCID0 redeclared in this block
    src/github.com/ianmcmahon/emgo/egpath/src/stm32/hal/raw/dsi/f469xx--dsi.go:111:2: 	other declaration of VCID0
    src/github.com/ianmcmahon/emgo/egpath/src/stm32/hal/raw/dsi/f469xx--dsi.go:193:2: VCID1 redeclared in this block
    src/github.com/ianmcmahon/emgo/egpath/src/stm32/hal/raw/dsi/f469xx--dsi.go:112:2: 	other declaration of VCID1
    src/github.com/ianmcmahon/emgo/egpath/src/stm32/hal/raw/dsi/f469xx--dsi.go:197:2: VCIDn redeclared in this block
    src/github.com/ianmcmahon/emgo/egpath/src/stm32/hal/raw/dsi/f469xx--dsi.go:116:2: 	other declaration of VCIDn
    src/github.com/ianmcmahon/emgo/egpath/src/stm32/hal/raw/dsi/f469xx--dsi.go:509:2: VCID redeclared in this block
    src/github.com/ianmcmahon/emgo/egpath/src/stm32/hal/raw/dsi/f469xx--dsi.go:110:2: 	other declaration of VCID
    src/github.com/ianmcmahon/emgo/egpath/src/stm32/hal/raw/dsi/f469xx--dsi.go:510:2: VCID0 redeclared in this block
    

    etc

    I'm looking at stm32xgen's parser and trying to understand how to correct this, but it's a bit hard to follow. Any suggestions?

  • [Question] Steps to capture SWO

    [Question] Steps to capture SWO

    Hello Michal,

    In README.MD you wrote:

    There is a set of scripts for any board in example directory that simplifies loding and debuging process. The load-oocd.sh and swo-oocd.sh scripts can handle SWO output from ITM (Instrumentation Trace Macrocell) but needs itmsplit to convert binary stream to readable messages. SWO is very useful for debuging and fmt.Print* functions by default use ITM trace port as standard output.

    Could you write in steps how to get that output? I'm using usart example with Nucleo-L476RG but after some time my board stops working so I'm trying to debug it. Unfortunately, I haven't figured out how to get SWO working - I have tried your scripts but it seems that I am not able to get it working.

    I would really appreciate if you could write steps starting from flashing a microcontroller (maybe I am just missing something).

  • Apply LICENSE properly

    Apply LICENSE properly

    When LICENSE is properly applied to the repo, GitHub shows the name of the license of the top right of repo homepage. For this repo this is not the case. It is forcing me to google the sentences from license to see what it is and compare it word-by-word to see if anything else is modified from the original license.

  • Is it possible to link in C libs, like the STM32F4xxx HAL drivers?

    Is it possible to link in C libs, like the STM32F4xxx HAL drivers?

    I need the rcc_ex and dsi/lcd drivers that exist in the STM32F4xx_HAL_Driver package from ST. They're simple in that they only set configuration registers, but complicated as hell with regards to porting to go. I started porting it over, but I feel like this is the wrong way to go. Is it possible to include those C files in what egc builds?

  • Add tweaks section for DSI peripherals

    Add tweaks section for DSI peripherals

    DSI reuses bit names across many registers; any duplicate names are prefixed with the register name.

    registers CR and MCR conflict when xgen generates RMCR for CR and RMCR for MCR. Renamed to M_CR to avoid conflict.

  • windows build linker line too long error

    windows build linker line too long error

    Hi, Just would like to let you know the thanks for the great work bringing the golang to the bare metal world! Really appreciated your work!

    I'm building it on windows. Though, the note say it is run under cygwin, really it can just use the raw windows cmd window. Because both the cross-toolchain and the go-sdk installed are for raw windows. The notes is here: https://github.com/minghuadev/emgo/blob/minghuadev-notes/localnotes-win-cygwin.md

    In either case, building the stm32/examples/f4-explorer/blinky sample will hit a linker line too long error. The reason is that all the lists of dependents for every package are concatenated together such that some packages appeared too many times on the command line. And the windows clearly cannot cope with it.

    I'm using this patch to get it work around:

    --- a/egc/buildtools.go
    +++ b/egc/buildtools.go
    @@ -240,6 +240,20 @@ func (bt *BuildTools) Link(e string, imports []string, o ...string) error {
            if err != nil {
                    return err
            }
    +       // eliminate duplicate elements.
    +       a = func(b []string) ([]string) {
    +               rv := make([]string, 0)
    +               regm := make(map[string]int)
    +               for _,x := range b {
    +                       if _,ok := regm[x]; !ok {
    +                               rv = append(rv, x)
    +                               regm[x] = 1
    +                       }
    +               }
    +               return rv
    +       }(a)
    +       // then supply the list twice for linker to find earlier dependants
    +       args = append(args, a...)
            args = append(args, a...)
    
            if bt.LDlibgcc != "" {
    

    Since I'm not very good at golang, this may not be the best fix. Hope a better patch can be applied.

    If you wonder, I'm thinking of porting it to picorv32 or myblaze. A long way ahead.

    Thanks.

  • Question: Whad do you thing about tinygo?

    Question: Whad do you thing about tinygo?

    There is other project to bring GO to microcontrollers: https://github.com/aykevl/tinygo. Have you seen it? What is your opinion about it? I am just curious because you try to do the same thing but from different approach. Maybe you can work together for more productive results?

  • wasm targets

    wasm targets

    Since the main Go doesn't compile to native wasm, only a variant that runs in a web browser. I was wondering if this project had any plans of supporting native wasm target

  • how can i use TCP stack?

    how can i use TCP stack?

    Hello, i have board STM32 Nucleo f767zi. I want to make http-client on this board. In C/C++, i can use LWIP library, but i'm not found similar features in Emgo. How can i make http-client by Emgo?

  • how to use bluetooth protocol stack

    how to use bluetooth protocol stack

    in nrf5 example i find two example ble-advert and ble-connect,i cant understand how to work.

    in my mind,i think it use bluetooth protocol stack in nrf5 flash. but it seem it use your own bluetooth protocol stack.

    my problem is how can i use bluetooth protocol stack in nrf5 flash like nrf5 sdk.

    if it is not a good way how can i use bluetooth. for example set Advertising interval ,Advertising_Type。

Related tags
Atomic Arbitrage - A base example of a bare implementation of an arbitrage bot

Atomic Arbitrage Atomic Arbitrage is a base example of a bare implementation of

Nov 23, 2022
Advent of Code is an Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

Advent of Code 2021 Advent of Code is an Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved

Dec 2, 2021
Jan 4, 2022
Embedded, self-hosted swagger-ui for go servers

swaggerui Embedded, self-hosted Swagger Ui for go servers This module provides swaggerui.Handler, which you can use to serve an embedded copy of Swagg

Dec 31, 2022
A BPMN engine, meant to be embedded in Go applications with minim hurdles, and a pleasant developer experience using it.

A BPMN engine, meant to be embedded in Go applications with minim hurdles, and a pleasant developer experience using it. This approach can increase transparency for non-developers.

Dec 29, 2022
Embedded javascript server-side renderer for Golang

v8ssr Embedded javascript server-side renderer for Golang. Useful for static server-side rendering. This does not attempt to polyfill node or browser

Aug 27, 2022
Unit tests generator for Go programming language
Unit tests generator for Go programming language

GoUnit GoUnit is a commandline tool that generates tests stubs based on source function or method signature. There are plugins for Vim Emacs Atom Subl

Jan 1, 2023
FreeSWITCH Event Socket library for the Go programming language.

eventsocket FreeSWITCH Event Socket library for the Go programming language. It supports both inbound and outbound event socket connections, acting ei

Dec 11, 2022
Simple interface to libmagic for Go Programming Language

File Magic in Go Introduction Provides simple interface to libmagic for Go Programming Language. Table of Contents Contributing Versioning Author Copy

Dec 22, 2021
The Gorilla Programming Language
The Gorilla Programming Language

Gorilla Programming Language Gorilla is a tiny, dynamically typed, multi-engine programming language It has flexible syntax, a compiler, as well as an

Apr 16, 2022
Elastic is an Elasticsearch client for the Go programming language.

Elastic is an Elasticsearch client for the Go programming language.

Jan 9, 2023
👩🏼‍💻A simple compiled programming language
👩🏼‍💻A simple compiled programming language

The language is written in Go and the target language is C. The built-in library is written in C too

Nov 29, 2022
Lithia is an experimental functional programming language with an implicit but strong and dynamic type system.

Lithia is an experimental functional programming language with an implicit but strong and dynamic type system. Lithia is designed around a few core concepts in mind all language features contribute to.

Dec 24, 2022
accessor methods generator for Go programming language

accessory accessory is an accessor generator for Go programming language. What is accessory? Accessory is a tool that generates accessor methods from

Nov 15, 2022
Http web frame with Go Programming Language

Http web frame with Go Programming Language

Oct 17, 2021
A modern programming language written in Golang.

MangoScript A modern programming language written in Golang. Here is what I want MangoScript to look like: struct Rectangle { width: number he

Nov 12, 2021
A stack oriented esoteric programming language inspired by poetry and forth

paperStack A stack oriented esoteric programming language inspired by poetry and forth What is paperStack A stack oriented language An esoteric progra

Nov 14, 2021
Oak is an expressive, dynamically typed programming language

Oak ?? Oak is an expressive, dynamically typed programming language. It takes the best parts of my experience with Ink, and adds what I missed and rem

Dec 30, 2022
Besten programming language

Besten What holds this repository? Besten Lexer Besten Parser Besten Interpreter Besten Module Loader Besten Lexer Located in ./internal/lexer A set o

Jun 13, 2022