mitxela.com forum
Welcome. Please log in or register.

golang on STMs
whiskeywhiskey Posted: 31 Oct 2019, 06:10 PM
Avatar


Member
Posts: 6
Joined: 31-October 19
I read the writeup on the FLASH synth, and this resonated with me a lot:

QUOTE

The abstraction level has hit a hazy middle ground which manages to encompass the worst of everything. As an example – say you wanted to enable a GPIO pin as output and set it high. Nothing could be conceptually simpler. But if you use the HAL library, and specify that a pin should be an output and its value is high, nothing will happen, because the pins will only function if the RCC clock source to that GPIO block has been enabled. Unbelievable! If you need register-level knowledge of how the chip works in order to use it, it's not much of an abstraction library. Couple that with the fact that the syntax is verbose and ugly, and it's hard to think of a worse way of implementing this thing.

I spent some time a couple months ago writing an fm synth engine in go, and my plan was to run it on an stm32f469 discovery board, with a nice UI on its high-res LCD.

There's a project called emgo which transpiles go into C for the stm. It's a little goofy, but it works. It deals with the HAL craziness in a fairly interesting way, there's basically drivers written in go that are selectively compiled based on build flags for the chip you're using, and gives a uniform go api which does the right thing in terms of register configuration etc.

Unfortunately, I got stuck because the emgo project seems to focus more on the smaller chips without fancy peripherals like DSI and LTDC, so I had to dive into emgo and write drivers for the peripherals I needed to get the display going. I did enough of the work to expect it to be functional, but never managed to get anything to display, and I had nobody to discuss with to figure out where to focus my troubleshooting, so I got discouraged and cycled off the project.

I still really like the idea of working with STMs using go, it's my language of choice by a wide margin, and aside from a few pretty major limitations (like lack of dynamic memory), emgo is a neat way to do it.

If this sounds intriguing to anyone, I'd love to discuss it; I'll have to dig back into my past work in order to explain enough to get someone up to speed, but that kind of sounding board is what I need to make it happen.

-------------
[top]
DAVID Posted: 31 Oct 2019, 11:10 PM
Avatar
avrs + midi = best diy synths

Member
Posts: 211
Joined: 10-September 17
I have never heard about the Go programming language before, So I wonder how did you implement sound in there. Did you use some kind of buffer that will get loaded in to a sound card? Maybe Timers? I´m interested

-------------
[top]
DAVID Posted: 31 Oct 2019, 11:10 PM
Avatar
avrs + midi = best diy synths

Member
Posts: 211
Joined: 10-September 17
Also one thing that i hate about the STM mcus is that registers for all boards are not consistent like in the AVR architecture; It took me a whole month to figure out how to use the timers registers on the STM32F103CB. And I kind of dislike using the HAL libraries since it´s really hard to set them up by hand, so then you are stuck with IDEs

-------------
[top]
whiskeywhiskey Posted: 1 Nov 2019, 03:11 AM
Avatar


Member
Posts: 6
Joined: 31-October 19
QUOTE (DAVID)
I have never heard about the Go programming language before, So I wonder how did you implement sound in there. Did you use some kind of buffer that will get loaded in to a sound card? Maybe Timers? I´m interested

Go is a really great language, I've been using it professionally for over seven years now, and I try to avoid using any other language anymore.

I wrote the fm engine first on my laptop, and it uses libportaudio as an abstraction for CoreAudio in osx. Portaudio will call a callback when it's ready for more data; I render all my audio in 128-sample buffers and throw those buffers into a channel until the channel blocks. When portaudio calls the callback, it reads a bufferful from the channel and returns it to portaudio. Works quite well on the laptop, I'm not sure what challenges I'll have working with the audio peripheral on the STM, but I think it should work fine.

Here's the code for the engine, it's not anything like complete:

https://github.com/ianmcmahon/fmsynth/blob/master/audio/engine.go

It's designed around the concept of components that know how to render themselves a buffer at a time. The engine has an input, connected to that input is a Mixer component, it's got inputs that are connected to Voice components. The Voices have an Algorithm, the Algorithm has operators and envelopes and a mixer, and each of those components carry their setting and state data around with them. There's a mechanism for mapping a CC directly to a component, so midi messages get routed around properly.



-------------
[top]
whiskeywhiskey Posted: 1 Nov 2019, 03:11 AM
Avatar


Member
Posts: 6
Joined: 31-October 19
I'm digging back into where I got stuck with the stm code; I believe I was having issues initializing the otm8009a lcd driver; it's done by sending a bunch of magic words over the DSI host's secondary configuration channel, and that's done by a bunch of writes to a register that pushes into an internal FIFO, and I recall having issues with code that is supposed to check the FIFO status flags and spin until it reports empty, and it would hang there indefinitely.

https://github.com/ianmcmahon/emgo/blob/fc23788f151771e43b147b2a339c9e20efa5602e/egpath/src/stm32/hal/dsi/driver.go#L514


-------------
[top]
whiskeywhiskey Posted: 1 Nov 2019, 03:11 AM
Avatar


Member
Posts: 6
Joined: 31-October 19
I got hung up on the LCD because I really wanted a UI for the thing, because I was having a hard time finding sensible ranges for the various operator and envelope parameters, and I wanted some visualization of parameters while tweaking.

My grand vision was to make a hardware groovebox/synth similar to the elektron digitone, but with my own spin on the UI and sequencer.

-------------
[top]
DAVID Posted: 1 Nov 2019, 05:11 AM
Avatar
avrs + midi = best diy synths

Member
Posts: 211
Joined: 10-September 17
QUOTE (whiskeywhiskey)
I got hung up on the LCD because I really wanted a UI for the thing
I don´t think I can help with that, I haven´t played with TFTs before; but with the audio i think it would be somewhat "easy" to do, of all synth that I have made so far, none of them were made using a buffer, so I´ll just do some proof of concept code on the atmega328 and then try to port that into the HAL library and using something like I2S(I think it would be really hard to try using that golang converter for doing this).

-------------
[top]
mit Posted: 1 Nov 2019, 02:11 PM
Avatar
yeah whatever

Admin
Posts: 209
Joined: 4-May 16
Hey. This is interesting - I have never used Go but just yesterday someone emailed me about using Go on microcontrollers with a project called TinyGo.

It's funny how bad the stm32 abstraction libraries are, I only really realized when I used the raspberry pi's WiringPi library. The Pi is a much more complicated device, but the GPIO pins can be configured and used in a single line of code.

Not sure if I'll play around with emgo but I'll take a look into it, even if just to see how they tackle the abstraction.

-------------
[top]
whiskeywhiskey Posted: 1 Nov 2019, 04:11 PM
Avatar


Member
Posts: 6
Joined: 31-October 19
Here's a sample of what emgo code looks like for a blinky demo. I am about to jump on a meeting, but when I'm done I'll dig through the library and piece together how the abstraction works and post relevant bits



package main

import (
"delay"
"fmt"

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

var green, orange, red, blue gpio.Pin

func init() {
system.Setup168(8)
systick.Setup(2e6)

gpio.D.EnableClock(false)
gpio.G.EnableClock(false)
gpio.K.EnableClock(false)
green = gpio.G.Pin(6)
orange = gpio.D.Pin(4)
red = gpio.D.Pin(5)
blue = gpio.K.Pin(3)

cfg := gpio.Config{Mode: gpio.Out, Speed: gpio.Low}

green.Setup(&cfg)
orange.Setup(&cfg)
red.Setup(&cfg)
blue.Setup(&cfg)
}

func wait() {
delay.Millisec(500)
}

func main() {
for {
blue.Clear()
green.Clear()
orange.Set()
red.Set()

wait()
red.Clear()
blue.Set()
orange.Clear()
green.Set()
wait()
}
}



-------------
[top]
whiskeywhiskey Posted: 2 Nov 2019, 01:11 AM
Avatar


Member
Posts: 6
Joined: 31-October 19
emgo uses build tags to conditionally compile implementation code depending on the chip.
Here's where the instances of a gpio port are declared for f469:


// +build f40_41xxx f469xx f746xx

package gpio

import (
"unsafe"

"stm32/hal/raw/mmap"
)

//emgo:const
var (
A = (*Port)(unsafe.Pointer(mmap.GPIOA_BASE))
B = (*Port)(unsafe.Pointer(mmap.GPIOB_BASE))
C = (*Port)(unsafe.Pointer(mmap.GPIOC_BASE))
D = (*Port)(unsafe.Pointer(mmap.GPIOD_BASE))
E = (*Port)(unsafe.Pointer(mmap.GPIOE_BASE))
F = (*Port)(unsafe.Pointer(mmap.GPIOF_BASE))
G = (*Port)(unsafe.Pointer(mmap.GPIOG_BASE))
H = (*Port)(unsafe.Pointer(mmap.GPIOH_BASE))
I = (*Port)(unsafe.Pointer(mmap.GPIOI_BASE))
J = (*Port)(unsafe.Pointer(mmap.GPIOJ_BASE))
K = (*Port)(unsafe.Pointer(mmap.GPIOK_BASE))
)


Here is the generic code for a Port


package gpio

import (
"unsafe"

"stm32/hal/internal"
"stm32/hal/system"
)

// Port represents GPIO port.
type Port struct {
registers
}

// Bus returns a bus to which p is connected.
func (p *Port) Bus() system.Bus {
return internal.Bus(unsafe.Pointer(p))
}

// Num returns port number: A.Num() == 0, B.Num() = 1, ...
func (p *Port) Num() int {
return int(portnum(p))
}

// EnableClock enables clock for port p.
// lp determines whether the clock remains on in low power (sleep) mode.
func (p *Port) EnableClock(lp bool) {
enableClock(p, lp)
}

// DisableClock disables clock for port p.
func (p *Port) DisableClock() {
disableClock(p)
}

// Reset resets port p.
func (p *Port) Reset() {
reset(p)
}

// Pins is a bitmask which represents the pins of GPIO port.
type Pins uint16

const (
Pin0 Pins = 1 << iota
Pin1
Pin2
Pin3
Pin4
Pin5
Pin6
Pin7
Pin8
Pin9
Pin10
Pin11
Pin12
Pin13
Pin14
Pin15
)

func (p *Port) Pin(id int) Pin {
ptr := uintptr(unsafe.Pointer(p))
return Pin{ptr | uintptr(id&0xf)}
}


EnableClock is implemented in here, this implementation is common to everything but f1:


// +build f030x6 f030x8 f303xe f40_41xxx f411xe f469xx f746xx l1xx_md l1xx_mdp l1xx_hd l1xx_xl l476xx

package gpio

import (
"mmio"

"stm32/hal/raw/rcc"
)

type registers struct {
moder mmio.U32
otyper mmio.U32
ospeedr mmio.U32
pupdr mmio.U32
idr mmio.U16
_ uint16
odr mmio.U16
_ uint16
bsrr mmio.U32
lckr mmio.U32
afr [2]mmio.U32
}

const (
// Mode
out = 1
alt = 2
altIn = 2
ana = 3

// Pull
pullUp = 1
pullDown = 2

// Driver
openDrain = 1
)

func enableClock(p *Port, lp bool) {
pnum := portnum(p)
enreg().U32.AtomicSetBits(uint32(rcc.GPIOAEN << pnum))
if lp {
lpenaclk(pnum)
} else {
lpdisclk(pnum)
}
enreg().Load() // RCC delay (workaround for silicon bugs).
}

func disableClock(p *Port) {
enreg().U32.AtomicClearBits(uint32(rcc.GPIOAEN << portnum(p)))
}

func reset(p *Port) {
pnum := portnum(p)
rstreg().U32.AtomicSetBits(uint32(rcc.GPIOARST << pnum))
rstreg().U32.AtomicClearBits(uint32(rcc.GPIOARST << pnum))
}

func setup(p *Port, n int, cfg *Config) {
pos := uint(n * 2)
p.otyper.StoreBit(n, int(cfg.Driver))
p.ospeedr.StoreBits(3<<pos, uint32(int(cfg.Speed)-veryLow)<<pos)
p.pupdr.StoreBits(3<<pos, uint32(cfg.Pull)<<pos)
p.moder.StoreBits(3<<pos, uint32(cfg.Mode)<<pos)
}

type AltFunc byte

const (
AF0 AltFunc = iota
AF1
AF2
AF3
AF4
AF5
AF6
AF7
AF8
AF9
AF10
AF11
AF12
AF13
AF14
AF15

System = AF0 // System function.
EventOut = AF15 // Pulse generated by SEV instruction.
)

// SetPinAltFunc sets alternate function for n-th pin.
func (p *Port) SetPinAltFunc(n int, af AltFunc) {
m := n >> 3 & 1
o := uint(n) & 7 * 4
p.afr[m].StoreBits(0xf<<o, uint32(af)<<o)
}

// SetAltFunc sets alternate function for pins.
func (p *Port) SetAltFunc(pins Pins, af AltFunc) {
for n := 0; n < 16; n++ {
if pins&(1<<uint(n)) != 0 {
p.SetPinAltFunc(n, af)
}
}
}

// SetAltFunc sets alternate function for pin.
func (p Pin) SetAltFunc(af AltFunc) {
p.Port().SetPinAltFunc(p.Index(), af)
}


Here's implementation code specific to the f4 chips:


// +build f40_41xxx f411xe f469xx

package gpio

import (
"stm32/hal/raw/rcc"
)

const (
MCO = AF0
RTC_50HZ = AF0
TRACE = AF0

TIM1 = AF1
TIM2 = AF1

TIM3 = AF2
TIM4 = AF2
TIM5 = AF2

TIM8 = AF3
TIM9 = AF3
TIM10 = AF3
TIM11 = AF3

I2C1 = AF4
I2C2 = AF4
I2C3 = AF4

SPI1 = AF5
SPI2 = AF5
SPI4 = AF5
SPI5 = AF5
SPI6 = AF5

SPI3 = AF6
SAI1 = AF6

USART1 = AF7
USART2 = AF7
USART3 = AF7

UART4 = AF8
UART5 = AF8
USART6 = AF8
UART7 = AF8
UART8 = AF8

CAN1 = AF9
CAN2 = AF9
TIM12 = AF9
TIM13 = AF9
TIM14 = AF9

OTGFS = AF10
OTGHS = AF10

ETH = AF11

FSMC = AF12
FMC = AF12
SDIO = AF12
OTGHSFS = AF12

DCMI = AF13

LTDC = AF14
)

const (
veryLow = -1 // Not supported.
low = -1 // 2 MHz (CL = 50 pF, VDD > 2.7 V)
_ = 0 // 25 MHz (CL = 50 pF, VDD > 2.7 V)
high = 1 // 50 MHz (CL = 40 pF, VDD > 2.7 V)
veryHigh = 2 // 100 MHz (CL = 30 pF, VDD > 2.7 V)
)

func enreg() *rcc.RAHB1ENR { return &rcc.RCC.AHB1ENR }
func rstreg() *rcc.RAHB1RSTR { return &rcc.RCC.AHB1RSTR }

func lpenaclk(pnum uint) {
rcc.RCC.AHB1LPENR.AtomicSetBits(rcc.GPIOALPEN << pnum)
}
func lpdisclk(pnum uint) {
rcc.RCC.AHB1LPENR.AtomicClearBits(rcc.GPIOALPEN << pnum)
}



There's a code generator called xgen in emgo that uses the chip definition headers from st's hal library and generates go code for each chip describing all the registers and pins.

The generated constants for the f469 are here:
https://github.com/ianmcmahon/emgo/tree/dsi-driver/egpath/src/stm32/o/f469xx/gpio

and here are generated structs that wrap the registers for convenience:
https://github.com/ianmcmahon/emgo/blob/dsi-driver/egpath/src/stm32/o/f469xx/gpio/xgen_gpio.go


Emgo then includes a compiler that transpiles the go into C, and the resulting C is built using the standard arm-gcc toolchain

-------------
[top]
mit Posted: 6 Nov 2019, 12:11 PM
Avatar
yeah whatever

Admin
Posts: 209
Joined: 4-May 16
Thanks for sharing. It looks a lot like they've just re-implemented or ported the existing peripheral libraries. Which is fine. I just wonder why the "enable GPIO" command can't include compile-time or run-time code that detects whether the clock source to that block is already enabled.

-------------
[top]

Sign in to post a reply.