Compare commits

..

4 Commits

Author SHA1 Message Date
sago35 396135da7b samd5x spi: change to use drivers.SPI 2020-10-26 18:44:23 +09:00
sago35 d9700779f7 samd5x spi: use standard spi driver 2020-10-26 18:35:55 +09:00
deadprogram 06ab27300c spi: incorporate change from GH issue feedback
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-10-18 21:22:56 +02:00
deadprogram 39e9e209ec spi: remove machine.SPI and replace with drivers.SPI interface for almost all SPI drivers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-10-18 18:03:12 +02:00
15 changed files with 135 additions and 266 deletions
-3
View File
@@ -12,9 +12,6 @@ jobs:
- run:
name: "Enforce Go Formatted Code"
command: make fmt-check
- run:
name: "Run unit tests"
command: make unit-test
- run:
name: "Run build and smoke tests"
command: make smoke-test
+1 -12
View File
@@ -132,8 +132,6 @@ smoke-test:
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
@md5sum ./build/test.hex
ifneq ($(AVR), 0)
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
@md5sum ./build/test.hex
endif
@@ -160,13 +158,4 @@ endif
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go
@md5sum ./build/test.hex
DRIVERS = $(wildcard */)
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
unit-test:
@go test -v $(addprefix ./,$(TESTS))
test: clean fmt-check unit-test smoke-test
test: clean fmt-check smoke-test
+2 -2
View File
@@ -42,8 +42,8 @@ func (d *Device) SetTime(t time.Time) error {
return err
}
// ReadTime returns the date and time
func (d *Device) ReadTime() (time.Time, error) {
// Time returns the time and date
func (d *Device) Time() (time.Time, error) {
data := make([]byte, 8)
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
if err != nil {
+1 -1
View File
@@ -13,7 +13,7 @@ func main() {
rtc.SetTime(time.Date(2019, 5, 15, 20, 34, 12, 0, time.UTC))
for {
t, err := rtc.ReadTime()
t, err := rtc.Time()
if err != nil {
println("Error reading date:", err)
break
-9
View File
@@ -1,9 +0,0 @@
// +build arduino
package main
import "machine"
// Replace neo in the code below to match the pin
// that you are using if different.
var neo = machine.D2
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !digispark,!arduino
// +build !digispark
package main
+6 -6
View File
@@ -135,12 +135,12 @@ func (dev *Device) Configure(config *DeviceConfig) (err error) {
// Speed up to max device frequency
// I propose a check here for max frequency, but not put that functionality directly into the driver.
// Either that or we have to change the signature of the SPI interface in the machine package itself.
if dev.attrs.MaxClockSpeedMHz > 0 {
err := dev.trans.setClockSpeed(uint32(dev.attrs.MaxClockSpeedMHz) * 1e6)
if err != nil {
return err
}
}
// if dev.attrs.MaxClockSpeedMHz > 0 {
// err := dev.trans.setClockSpeed(uint32(dev.attrs.MaxClockSpeedMHz) * 1e6)
// if err != nil {
// return err
// }
// }
// Enable Quad Mode if available
if dev.trans.supportQuadMode() && dev.attrs.QuadEnableBitMask > 0 {
+4 -23
View File
@@ -2,12 +2,13 @@ package flash
import (
"machine"
"tinygo.org/x/drivers"
)
type transport interface {
configure(config *DeviceConfig)
supportQuadMode() bool
setClockSpeed(hz uint32) (err error)
runCommand(cmd byte) (err error)
readCommand(cmd byte, rsp []byte) (err error)
writeCommand(cmd byte, data []byte) (err error)
@@ -18,7 +19,7 @@ type transport interface {
// NewSPI returns a pointer to a flash device that uses a SPI peripheral to
// communicate with a serial memory chip.
func NewSPI(spi *machine.SPI, sdo, sdi, sck, cs machine.Pin) *Device {
func NewSPI(spi drivers.SPI, sdo, sdi, sck, cs machine.Pin) *Device {
return &Device{
trans: &spiTransport{
spi: spi,
@@ -31,7 +32,7 @@ func NewSPI(spi *machine.SPI, sdo, sdi, sck, cs machine.Pin) *Device {
}
type spiTransport struct {
spi *machine.SPI
spi drivers.SPI
sdo machine.Pin
sdi machine.Pin
sck machine.Pin
@@ -39,31 +40,11 @@ type spiTransport struct {
}
func (tr *spiTransport) configure(config *DeviceConfig) {
// Configure spi bus
tr.setClockSpeed(5000000)
// Configure chip select pin
tr.ss.Configure(machine.PinConfig{Mode: machine.PinOutput})
tr.ss.High()
}
func (tr *spiTransport) setClockSpeed(hz uint32) error {
// TODO: un-hardcode this max speed; it is probably a sensible
// default maximum for atsamd and nrf at least
if hz > 24*1e6 {
hz = 24 * 1e6
}
tr.spi.Configure(machine.SPIConfig{
Frequency: hz,
SDI: tr.sdi,
SDO: tr.sdo,
SCK: tr.sck,
LSBFirst: false,
Mode: 0,
})
return nil
}
func (tr *spiTransport) supportQuadMode() bool {
return false
}
+3 -1
View File
@@ -300,7 +300,9 @@ func (d *Device) sendCommand(cmd byte, data []byte) {
d.dc.Low()
d.driver.write8(cmd)
d.dc.High()
d.driver.write8sl(data)
if data != nil && len(data) > 0 {
d.driver.write8sl(data)
}
d.endWrite()
}
+15 -71
View File
@@ -3,15 +3,18 @@
package ili9341
import (
"device/sam"
"machine"
"tinygo.org/x/drivers"
)
type spiDriver struct {
bus machine.SPI
bus drivers.SPI
}
func NewSPI(bus machine.SPI, dc, cs, rst machine.Pin) *Device {
var txBuf [2]uint8
func NewSPI(bus drivers.SPI, dc, cs, rst machine.Pin) *Device {
return &Device{
dc: dc,
cs: cs,
@@ -27,90 +30,31 @@ func (pd *spiDriver) configure(config *Config) {
}
func (pd *spiDriver) write8(b byte) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b))
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
pd.bus.Transfer(b)
}
func (pd *spiDriver) write8n(b byte, n int) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i, c := 0, n; i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
panic("not impl")
}
func (pd *spiDriver) write8sl(b []byte) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i, c := 0, len(b); i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b[i]))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
pd.bus.Tx(b, nil)
}
func (pd *spiDriver) write16(data uint16) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data)))
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
txBuf[0] = uint8(data >> 8)
txBuf[1] = uint8(data)
pd.bus.Tx(txBuf[:], nil)
}
func (pd *spiDriver) write16n(data uint16, n int) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i := 0; i < n; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data)))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
for i, c := 0, n; i < c; i++ {
pd.write16(data)
}
}
func (pd *spiDriver) write16sl(data []uint16) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i, c := 0, len(data); i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data[i] >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data[i])))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
pd.write16(data[i])
}
}
-79
View File
@@ -2,15 +2,10 @@
package ws2812 // import "tinygo.org/x/drivers/ws2812"
import (
"errors"
"image/color"
"machine"
"tinygo.org/x/drivers"
)
var errUnknownClockSpeed = errors.New("ws2812: unknown CPU clock speed")
// Device wraps a pin object for an easy driver interface.
type Device struct {
Pin machine.Pin
@@ -40,77 +35,3 @@ func (d Device) WriteColors(buf []color.RGBA) error {
}
return nil
}
// DeviceSPI wraps a SPI object for driving a string of WS2812 LEDs.
type DeviceSPI struct {
Bus drivers.SPI
// Use a buffer embedded in the device struct so that at most one allocation
// happens at NewSPI and no allocation during transmission.
buf []byte
}
// NewSPI returns a WS2812 driver using a SPI bus. This SPI bus must already be
// configured at exactly 4MHz otherwise WS2812 won't work properly with it.
//
// The advantage of using a SPI bus over bitbanging is that it doesn't require
// custom assembly for each new platform and that it may avoid needing to
// disable interrupts while sending color data if the SPI peripheral uses DMA.
// The disadvantage is of course that it is limited in which pins can be used
// for WS2812 output.
func NewSPI(bus drivers.SPI) *DeviceSPI {
return &DeviceSPI{
Bus: bus,
}
}
// WriteColors wries the given color slice out using the WS2812 protocol.
// Colors are sent out in the usual GRB format.
func (d *DeviceSPI) WriteColors(buf []color.RGBA) error {
// Each color needs 15 bytes: 5 SPI bits per WS2812 bit with 3*8 WS2812 bits
// per color means 120 SPI bits. In addition to that, an extra 0 byte seems
// to be necessary on nRF5x chips to avoid having the SDO line pulled high
// at the end of the transfer.
if len(d.buf) < len(buf)*15+1 {
d.buf = make([]byte, len(buf)*15+1)
}
for i, color := range buf {
bitBuf := makeSPIBits(color.G)
copy(d.buf[i*15+0:], bitBuf[:])
bitBuf = makeSPIBits(color.R)
copy(d.buf[i*15+5:], bitBuf[:])
bitBuf = makeSPIBits(color.B)
copy(d.buf[i*15+10:], bitBuf[:])
}
return d.Bus.Tx(d.buf, nil)
}
func makeSPIBits(b byte) [5]byte {
// Create a 40 bit bitstring from this one byte.
var bitstring uint64
for i := 0; i < 8; i++ {
bitstring <<= 5
if b&0x80 != 0 {
// 0b11100 means the output is high for 750ns (three high bits at
// 4MHz) and low for 500ns (two low bits). This outputs a 1 bit in
// the custom WS2812 protocol.
bitstring |= 0b11100 // T1H (0b111) + TLD (0b00)
} else {
// 0b10000 means the output is high for 250ns (one high bit at 4MHz)
// and low for 1000ns (four low bits at 4MHz). This outputs a 0 bit
// in the custom WS2812 protocol.
bitstring |= 0b10000 // T0H (0b100) + TLD (0b00)
}
b <<= 1
}
// Create a 5 byte array from this bitstring.
bitstring <<= 7
var buf [5]byte
for i := 0; i < 5; i++ {
buf[i] = byte(bitstring >> 40)
bitstring <<= 8
}
return buf
}
-58
View File
@@ -1,58 +0,0 @@
// +build avr
package ws2812
// This file implements the WS2812 protocol for AVR microcontrollers.
import (
"device/avr"
"machine"
)
// Send a single byte using the WS2812 protocol.
func (d Device) WriteByte(c byte) error {
// On AVR, the port is always the same for setting and clearing a register
// so use only one. This avoids the following error:
// error: inline assembly requires more registers than available
// Probably this is about pointer registers, which are very limited on AVR.
port, maskSet := d.Pin.PortMaskSet()
_, maskClear := d.Pin.PortMaskClear()
switch machine.CPUFrequency() {
case 16e6: // 16MHz
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 4 cycles or 250ns
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
// T1H: 9 cycles or 562ns
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
avr.AsmFull(`
send_bit:
st {portSet}, {maskSet} ; [2] set output high
lsl {value} ; [1] shift off the next bit, store it in C
brcs skip_store ; [1/2] branch if this bit is high (long pulse)
st {portClear}, {maskClear} ; [2] set output low (short pulse)
skip_store:
nop ; [4] wait before changing the output again
nop
nop
nop
st {portClear}, {maskClear} ; [2] set output low (end of pulse)
nop ; [3]
nop
nop
subi {i}, 1 ; [1] subtract one (for the loop)
brne send_bit ; [1/2] send the next bit, if not at the end of the loop
`, map[string]interface{}{
"value": c,
"i": byte(8),
"maskSet": maskSet,
"portSet": port,
"maskClear": maskClear,
"portClear": port,
})
return nil
default:
return errUnknownClockSpeed
}
}
+49
View File
@@ -0,0 +1,49 @@
// +build atmega328p
package ws2812
// This file implements the WS2812 protocol for 16MHz AVR microcontrollers.
import (
"device/avr"
)
// Send a single byte using the WS2812 protocol.
func (d Device) WriteByte(c byte) error {
// For the AVR at 16MHz
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 4 cycles or 250ns
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
// T1H: 9 cycles or 562ns
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
avr.AsmFull(`
send_bit:
st {portSet}, {maskSet} ; [2] set output high
lsl {value} ; [1] shift off the next bit, store it in C
brcs skip_store ; [1/2] branch if this bit is high (long pulse)
st {portClear}, {maskClear} ; [2] set output low (short pulse)
skip_store:
nop ; [4] wait before changing the output again
nop
nop
nop
st {portClear}, {maskClear} ; [2] set output low (end of pulse)
nop ; [3]
nop
nop
subi {i}, 1 ; [1] subtract one (for the loop)
brne send_bit ; [1/2] send the next bit, if not at the end of the loop
`, map[string]interface{}{
"value": c,
"i": 8,
"maskSet": maskSet,
"portSet": portSet,
"maskClear": maskClear,
"portClear": portClear,
})
return nil
}
+50
View File
@@ -0,0 +1,50 @@
// +build digispark
package ws2812
// This file implements the WS2812 protocol for 16.5MHz Digispark AVR microcontrollers.
// This is a slightly different implementation than the one for the atmega to work around a compiler bug.
import (
"device/avr"
)
// Send a single byte using the WS2812 protocol.
func (d Device) WriteByte(c byte) error {
// For the AVR at 16MHz
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 4 cycles or 250ns
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
// T1H: 9 cycles or 562ns
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
avr.AsmFull(`
send_bit:
st {portSet}, {maskSet} ; [2] set output high
lsl {value} ; [1] shift off the next bit, store it in C
brcs skip_store ; [1/2] branch if this bit is high (long pulse)
st {portClear}, {maskClear} ; [2] set output low (short pulse)
skip_store:
nop ; [4] wait before changing the output again
nop
nop
nop
st {portClear}, {maskClear} ; [2] set output low (end of pulse)
nop ; [3]
nop
nop
subi {i}, 1 ; [1] subtract one (for the loop)
brne send_bit ; [1/2] send the next bit, if not at the end of the loop
`, map[string]interface{}{
"value": c,
"i": byte(8),
"maskSet": maskSet,
"portSet": portSet,
"maskClear": maskClear,
"portClear": portClear,
})
return nil
}
+3
View File
@@ -4,10 +4,13 @@ package ws2812
import (
"device"
"errors"
"machine"
"unsafe"
)
var errUnknownClockSpeed = errors.New("ws2812: unknown CPU clock speed")
func (d Device) WriteByte(c byte) error {
portSet, maskSet := d.Pin.PortMaskSet()
portClear, maskClear := d.Pin.PortMaskClear()