Compare commits

...

6 Commits

Author SHA1 Message Date
Ron Evans e4f6fbcb52 release: update changelog for v0.2.0
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-04 07:47:44 +02:00
Ron Evans 7487c6b3a2 docs: correct badge and link in README for godocs to use custom domain
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-03 19:45:18 +02:00
Ron Evans d1553458f5 bme280: add package docs, and add bme280 to README
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-03 19:39:30 +02:00
Daniel Esteban 79d3609f76 Support for AT24C32/64 2-wire serial EEPROM 2019-06-03 19:38:04 +02:00
Ayke van Laethem 00a9b9db77 ws2812: better support the nrf52832
This has cost me _hours_ to find. There are many clones of the ws2812
and they have slightly different timing characteristics. Some don't work
so well when you get close to the minimum T1H time: a 1-bit may be
interpreted as a 0-bit.
2019-06-03 19:25:24 +02:00
Martin Treml f68388702d Implementation of the BME280 Sensor (#38)
* Driver for bme280
2019-06-03 19:03:53 +02:00
10 changed files with 634 additions and 5 deletions
+8
View File
@@ -1,3 +1,11 @@
0.2.0
---
- **new devices**
- AT24C32/64 2-wire serial EEPROM
- BME280 humidity/pressure sensor
- **bugfixes**
- ws2812: better support for nrf52832
0.1.0
---
- **first release**
+2
View File
@@ -11,6 +11,7 @@ smoke-test:
@mkdir -p build
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/adxl345/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/apa102/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/at24cx/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/bh1750/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/blinkm/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/bmp180/main.go
@@ -41,5 +42,6 @@ smoke-test:
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/waveshare-epd/epd2in13/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/ws2812/main.go
tinygo build -size short -o ./build/test.elf -target=trinket-m0 ./examples/bme280/main.go
test: clean fmt-check smoke-test
+3 -1
View File
@@ -1,6 +1,6 @@
# TinyGo Drivers
[![GoDoc](https://godoc.org/github.com/tinygo-org/drivers?status.svg)](https://godoc.org/github.com/tinygo-org/drivers) [![CircleCI](https://circleci.com/gh/tinygo-org/drivers/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/drivers/tree/dev)
[![GoDoc](https://godoc.org/tinygo.org/x/drivers?status.svg)](https://godoc.org/tinygo.org/x/drivers) [![CircleCI](https://circleci.com/gh/tinygo-org/drivers/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/drivers/tree/dev)
This package provides a collection of hardware drivers for devices that can be used together with [TinyGo](https://tinygo.org).
@@ -56,8 +56,10 @@ func main() {
|----------|-------------|
| [ADXL345 accelerometer](http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf) | I2C |
| [APA102 RGB LED](https://cdn-shop.adafruit.com/product-files/2343/APA102C.pdf) | SPI |
| [AT24CX 2-wire serial EEPROM](https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf) | I2C |
| [BH1750 ambient light sensor](https://www.mouser.com/ds/2/348/bh1750fvi-e-186247.pdf) | I2C |
| [BlinkM RGB LED](http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf) | I2C |
| [BME280 humidity/pressure sensor](https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf) | I2C |
| [BMP180 barometer](https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf) | I2C |
| [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C |
| [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C |
+171
View File
@@ -0,0 +1,171 @@
// Package at24cx provides a driver for the AT24C32/64/128/256/512 2-wire serial EEPROM
//
// Datasheet:
// https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf
package at24cx // import "tinygo.org/x/drivers/at24cx"
import (
"errors"
"machine"
"time"
)
// Device wraps an I2C connection to a DS3231 device.
type Device struct {
bus machine.I2C
Address uint16
pageSize uint16
currentRAMAddress uint16
startRAMAddress uint16
endRAMAddress uint16
}
type Config struct {
PageSize uint16
StartRAMAddress uint16
EndRAMAddress uint16
}
// New creates a new AT24C32/64 connection. The I2C bus must already be
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2C) Device {
return Device{
bus: bus,
Address: Address,
}
}
// Configure sets up the device for communication
func (d *Device) Configure(cfg Config) {
if cfg.PageSize == 0 {
d.pageSize = 32
} else {
d.pageSize = cfg.PageSize
}
if cfg.EndRAMAddress == 0 {
d.endRAMAddress = 4096
} else {
d.endRAMAddress = cfg.EndRAMAddress
}
d.startRAMAddress = cfg.StartRAMAddress
}
// WriteByte writes a byte at the specified address
func (d *Device) WriteByte(eepromAddress uint16, value uint8) error {
address := []uint8{
uint8((eepromAddress >> 8) & 0xFF),
uint8(eepromAddress & 0xFF),
value,
}
return d.bus.Tx(d.Address, address, nil)
}
// ReadByte reads the byte at the specified address
func (d *Device) ReadByte(eepromAddress uint16) (uint8, error) {
address := []uint8{
uint8(eepromAddress >> 8),
uint8(eepromAddress & 0xFF),
}
data := make([]uint8, 1)
err := d.bus.Tx(d.Address, address, data)
return data[0], err
}
// WriteAt writes a byte array at the specified address
func (d *Device) WriteAt(data []byte, offset int64) (n int, err error) {
return d.writeAt(data, uint16(offset))
}
// writeAt writes a byte array at the specified address
func (d *Device) writeAt(data []byte, offset uint16) (n int, err error) {
values := make([]uint8, 32)
dataLeft := uint16(len(data))
d.currentRAMAddress = offset
offset = 0
var offsetPage uint16
var chunkLength uint16
for dataLeft > 0 {
offsetPage = d.currentRAMAddress % d.pageSize
if dataLeft < 30 { // The 32K/64K EEPROM is capable of 32-byte page writes and we're using 2 for the address
chunkLength = dataLeft
} else {
chunkLength = 30
}
if (d.pageSize - offsetPage) < chunkLength {
chunkLength = d.pageSize - offsetPage
}
for i := uint16(0); i < chunkLength; i++ {
values[2+i] = data[offset+i]
}
values[0] = uint8(d.currentRAMAddress >> 8)
values[1] = uint8(d.currentRAMAddress & 0xFF)
err := d.bus.Tx(d.Address, values[:chunkLength+2], nil)
if err != nil {
return 0, err
}
dataLeft -= chunkLength
offset += chunkLength
if d.endRAMAddress-chunkLength < d.currentRAMAddress {
d.currentRAMAddress = d.startRAMAddress + (d.currentRAMAddress+uint16(len(data)))%d.endRAMAddress
} else {
d.currentRAMAddress += chunkLength
}
time.Sleep(2 * time.Millisecond) // writing again too soon will block the device
}
return len(data), nil
}
// ReadAt reads the bytes at the specified address
func (d *Device) ReadAt(data []byte, offset int64) (n int, err error) {
return d.readAt(data, uint16(offset))
}
// readAt reads the bytes at the specified address
func (d *Device) readAt(data []byte, offset uint16) (n int, err error) {
address := []uint8{
uint8((offset >> 8) & 0xFF),
uint8(offset & 0xFF),
}
err = d.bus.Tx(d.Address, address, data)
if d.endRAMAddress-uint16(len(data)) < offset {
d.currentRAMAddress = d.startRAMAddress + (offset+uint16(len(data)))%d.endRAMAddress
} else {
d.currentRAMAddress = offset + uint16(len(data))
}
return len(data), err
}
// Seek sets the offset for the next Read or Write on SRAM to offset, interpreted
// according to whence: 0 means relative to the origin of the SRAM, 1 means
// relative to the current offset, and 2 means relative to the end.
// returns new offset and error, if any
func (d *Device) Seek(offset int64, whence int) (int64, error) {
w := uint16(0)
switch whence {
case 0:
w = d.startRAMAddress
case 1:
w = d.currentRAMAddress
case 2:
w = d.endRAMAddress
default:
return 0, errors.New("invalid whence")
}
d.currentRAMAddress = w + uint16(offset)
return int64(d.currentRAMAddress), nil
}
// Write writes len(data) bytes to SRAM
// returns number of bytes written and error, if any
func (d *Device) Write(data []byte) (n int, err error) {
return d.writeAt(data, d.currentRAMAddress)
}
// Read reads len(data) from SRAM
// returns number of bytes written and error, if any
func (d *Device) Read(data []uint8) (n int, err error) {
return d.readAt(data, d.currentRAMAddress)
}
+4
View File
@@ -0,0 +1,4 @@
package at24cx
// The I2C address which this device listens to.
const Address = 0x57
+257
View File
@@ -0,0 +1,257 @@
// Package bme280 provides a driver for the BME280 digital combined
// humidity and pressure sensor by Bosch.
//
// Datasheet:
// https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf
//
package bme280
import (
"machine"
"math"
)
// calibrationCoefficients reads at startup and stores the calibration coefficients
type calibrationCoefficients struct {
t1 uint16
t2 int16
t3 int16
p1 uint16
p2 int16
p3 int16
p4 int16
p5 int16
p6 int16
p7 int16
p8 int16
p9 int16
h1 uint8
h2 int16
h3 uint8
h4 int16
h5 int16
h6 int8
}
// Device wraps an I2C connection to a BME280 device.
type Device struct {
bus machine.I2C
Address uint16
calibrationCoefficients calibrationCoefficients
}
// New creates a new BME280 connection. The I2C bus must already be
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2C) Device {
return Device{
bus: bus,
Address: Address,
}
}
// Configure sets up the device for communication and
// read the calibration coefficientes.
func (d *Device) Configure() {
var data [24]byte
err := d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION, data[:])
if err != nil {
return
}
var h1 [1]byte
err = d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION_H1, h1[:])
if err != nil {
return
}
var h2lsb [7]byte
err = d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION_H2LSB, h2lsb[:])
if err != nil {
return
}
d.calibrationCoefficients.t1 = readUintLE(data[0], data[1])
d.calibrationCoefficients.t2 = readIntLE(data[2], data[3])
d.calibrationCoefficients.t3 = readIntLE(data[4], data[5])
d.calibrationCoefficients.p1 = readUintLE(data[6], data[7])
d.calibrationCoefficients.p2 = readIntLE(data[8], data[9])
d.calibrationCoefficients.p3 = readIntLE(data[10], data[11])
d.calibrationCoefficients.p4 = readIntLE(data[12], data[13])
d.calibrationCoefficients.p5 = readIntLE(data[14], data[15])
d.calibrationCoefficients.p6 = readIntLE(data[16], data[17])
d.calibrationCoefficients.p7 = readIntLE(data[18], data[19])
d.calibrationCoefficients.p8 = readIntLE(data[20], data[21])
d.calibrationCoefficients.p9 = readIntLE(data[22], data[23])
d.calibrationCoefficients.h1 = h1[0]
d.calibrationCoefficients.h2 = readIntLE(h2lsb[0], h2lsb[1])
d.calibrationCoefficients.h3 = h2lsb[2]
d.calibrationCoefficients.h6 = int8(h2lsb[6])
d.calibrationCoefficients.h4 = 0 + (int16(h2lsb[3]) << 4) | (int16(h2lsb[4] & 0x0F))
d.calibrationCoefficients.h5 = 0 + (int16(h2lsb[5]) << 4) | (int16(h2lsb[4]) >> 4)
d.bus.WriteRegister(uint8(d.Address), CTRL_HUMIDITY_ADDR, []byte{0x3f})
d.bus.WriteRegister(uint8(d.Address), CTRL_MEAS_ADDR, []byte{0xB7})
d.bus.WriteRegister(uint8(d.Address), CTRL_CONFIG, []byte{0x00})
}
// Connected returns whether a BME280 has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
return data[0] == CHIP_ID
}
// Reset the device
func (d *Device) Reset() {
d.bus.WriteRegister(uint8(d.Address), CMD_RESET, []byte{0xB6})
}
// ReadTemperature returns the temperature in celsius milli degrees (ºC/1000)
func (d *Device) ReadTemperature() (int32, error) {
data, err := d.readData()
if err != nil {
return 0, err
}
temp, _ := d.calculateTemp(data)
return temp, nil
}
// ReadPressure returns the pressure in milli pascals mPa
func (d *Device) ReadPressure() (int32, error) {
data, err := d.readData()
if err != nil {
return 0, err
}
_, tFine := d.calculateTemp(data)
pressure := d.calculatePressure(data, tFine)
return pressure, nil
}
// ReadHumidity returns the relative humidity in hundredths of a percent
func (d *Device) ReadHumidity() (int32, error) {
data, err := d.readData()
if err != nil {
return 0, err
}
_, tFine := d.calculateTemp(data)
humidity := d.calculateHumidity(data, tFine)
return humidity, nil
}
// ReadAltitude returns the current altitude in meters based on the
// current barometric pressure and estimated pressure at sea level.
// Calculation is based on code from Adafruit BME280 library
// https://github.com/adafruit/Adafruit_BME280_Library
func (d *Device) ReadAltitude() (alt int32, err error) {
mPa, _ := d.ReadPressure()
atmP := float32(mPa) / 100000
alt = int32(44330.0 * (1.0 - math.Pow(float64(atmP/SEALEVEL_PRESSURE), 0.1903)))
return
}
// convert2Bytes converts two bytes to int32
func convert2Bytes(msb byte, lsb byte) int32 {
return int32(readUint(msb, lsb))
}
// convert3Bytes converts three bytes to int32
func convert3Bytes(msb byte, b1 byte, lsb byte) int32 {
return int32(((((uint32(msb) << 8) | uint32(b1)) << 8) | uint32(lsb)) >> 4)
}
// readUint converts two bytes to uint16
func readUint(msb byte, lsb byte) uint16 {
return (uint16(msb) << 8) | uint16(lsb)
}
// readUintLE converts two little endian bytes to uint16
func readUintLE(msb byte, lsb byte) uint16 {
temp := readUint(msb, lsb)
return (temp >> 8) | (temp << 8)
}
// readIntLE converts two little endian bytes to int16
func readIntLE(msb byte, lsb byte) int16 {
return int16(readUintLE(msb, lsb))
}
// readData does a burst read from 0xF7 to 0xF0 according to the datasheet
// resulting in an slice with 8 bytes 0-2 = pressure / 3-5 = temperature / 6-7 = humidity
func (d *Device) readData() (data [8]byte, err error) {
err = d.bus.ReadRegister(uint8(d.Address), REG_PRESSURE, data[:])
if err != nil {
println(err)
return
}
return
}
// calculateTemp uses the data slice and applies calibrations values on it to convert the value to milli degrees
// it also calculates the variable tFine which is used by the pressure and humidity calculation
func (d *Device) calculateTemp(data [8]byte) (int32, int32) {
rawTemp := convert3Bytes(data[3], data[4], data[5])
var1 := (((rawTemp >> 3) - (int32(d.calibrationCoefficients.t1) << 1)) * int32(d.calibrationCoefficients.t2)) >> 11
var2 := (((((rawTemp >> 4) - int32(d.calibrationCoefficients.t1)) * ((rawTemp >> 4) - int32(d.calibrationCoefficients.t1))) >> 12) * int32(d.calibrationCoefficients.t3)) >> 14
tFine := var1 + var2
T := (tFine*5 + 128) >> 8
return (10 * T), tFine
}
// calculatePressure uses the data slice and applies calibrations values on it to convert the value to milli pascals mPa
func (d *Device) calculatePressure(data [8]byte, tFine int32) int32 {
rawPressure := convert3Bytes(data[0], data[1], data[2])
var1 := int64(tFine) - 128000
var2 := var1 * var1 * int64(d.calibrationCoefficients.p6)
var2 = var2 + ((var1 * int64(d.calibrationCoefficients.p5)) << 17)
var2 = var2 + (int64(d.calibrationCoefficients.p4) << 35)
var1 = ((var1 * var1 * int64(d.calibrationCoefficients.p3)) >> 8) + ((var1 * int64(d.calibrationCoefficients.p2)) << 12)
var1 = ((int64(1) << 47) + var1) * int64(d.calibrationCoefficients.p1) >> 33
if var1 == 0 {
return 0 // avoid exception caused by division by zero
}
p := int64(1048576 - rawPressure)
p = (((p << 31) - var2) * 3125) / var1
var1 = (int64(d.calibrationCoefficients.p9) * (p >> 13) * (p >> 13)) >> 25
var2 = (int64(d.calibrationCoefficients.p8) * p) >> 19
p = ((p + var1 + var2) >> 8) + (int64(d.calibrationCoefficients.p7) << 4)
p = (p / 256)
return int32(1000 * p)
}
// calculateHumidity uses the data slice and applies calibrations values on it to convert the value to relative humidity in hundredths of a percent
func (d *Device) calculateHumidity(data [8]byte, tFine int32) int32 {
rawHumidity := convert2Bytes(data[6], data[7])
h := float32(tFine) - 76800
if h == 0 {
println("invalid value")
}
var1 := float32(rawHumidity) - (float32(d.calibrationCoefficients.h4)*64.0 +
(float32(d.calibrationCoefficients.h5) / 16384.0 * h))
var2 := float32(d.calibrationCoefficients.h2) / 65536.0 *
(1.0 + float32(d.calibrationCoefficients.h6)/67108864.0*h*
(1.0+float32(d.calibrationCoefficients.h3)/67108864.0*h))
h = var1 * var2
h = h * (1 - float32(d.calibrationCoefficients.h1)*h/524288)
return int32(100 * h)
}
+25
View File
@@ -0,0 +1,25 @@
package bme280
// Constants/addresses used for I2C.
// The I2C address which this device listens to.
const Address = 0x76
// Registers. Names, addresses and comments copied from the datasheet.
const (
CTRL_MEAS_ADDR = 0xF4
CTRL_HUMIDITY_ADDR = 0xF2
CTRL_CONFIG = 0xF5
REG_PRESSURE = 0xF7
REG_CALIBRATION = 0x88
REG_CALIBRATION_H1 = 0xA1
REG_CALIBRATION_H2LSB = 0xE1
CMD_RESET = 0xE0
WHO_AM_I = 0xD0
CHIP_ID = 0x60
)
const (
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
)
+122
View File
@@ -0,0 +1,122 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/at24cx"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
eeprom := at24cx.New(machine.I2C0)
eeprom.Configure(at24cx.Config{})
values := make([]uint8, 100)
for i := uint16(0); i < 100; i++ {
values[i] = uint8(65 + i%26)
}
_, err := eeprom.WriteAt(values, 0)
if err != nil {
println("There was an error in WriteAt:", err)
return
}
for i := uint16(0); i < 26; i++ {
err = eeprom.WriteByte(100+i, uint8(90-i))
if err != nil {
println("There was an error in WriteByte:", i, err)
return
}
time.Sleep(2 * time.Millisecond)
}
println("\n\r\n\rRead 26 bytes one by one from address 0")
println("Expected: ABCDEFGHIJKLMNOPQRSTUVWXYZ")
print("Real: ")
for i := uint16(0); i < 26; i++ {
char, err := eeprom.ReadByte(i)
print(string(char))
if err != nil {
println("There was an error in ReadByte:", i, err)
return
}
}
println("")
println("\n\r\n\rRead 100 bytes from address 26")
println("Expected: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVZYXWVUTSRQPONMLKJIHGFEDCBA")
print("Real: ")
data := make([]byte, 100)
_, err = eeprom.ReadAt(data, 26)
if err != nil {
println("There was an error in ReadAt:", err)
return
}
for i := 0; i < 100; i++ {
print(string(data[i]))
}
println("")
// Move to the beginning of memory
eeprom.Seek(0, 0)
_, err = eeprom.Write([]uint8{88, 88, 88})
if err != nil {
println("There was an error in Write:", err)
return
}
println("\n\r\n\rRead 3 bytes")
println("Expected: DEF")
print("Real: ")
data = make([]byte, 3)
_, err = eeprom.Read(data)
if err != nil {
println("There was an error in Read:", err)
return
}
for _, char := range data {
print(string(char))
}
println("")
println("\n\r\n\rRead another 3 bytes (from the beginning this time)")
eeprom.Seek(-6, 1)
println("Expected: XXX")
print("Real: ")
data = make([]byte, 3)
_, err = eeprom.Read(data)
if err != nil {
println("There was an error in Read:", err)
return
}
for _, char := range data {
print(string(char))
}
println("")
// Move to the end of memory
eeprom.Seek(-4, 2)
_, err = eeprom.Write([]uint8{89, 90, 89, 90})
if err != nil {
println("There was an error in Write:", err)
return
}
println("\n\r\n\rRead the last 4 bytes of the memory and the 3 of the beginning")
eeprom.Seek(-4, 1)
println("Expected: YZYZXXX")
print("Real: ")
data = make([]byte, 7)
_, err = eeprom.Read(data)
if err != nil {
println("There was an error in Read:", err)
return
}
for _, char := range data {
print(string(char))
}
println("")
}
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/bme280"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
sensor := bme280.New(machine.I2C0)
sensor.Configure()
connected := sensor.Connected()
if !connected {
println("BME280 not detected")
}
println("BME280 detected")
for {
temp, _ := sensor.ReadTemperature()
println("Temperature:", strconv.FormatFloat(float64(temp)/1000, 'f', 2, 64), "ºC")
press, _ := sensor.ReadPressure()
println("Pressure:", strconv.FormatFloat(float64(press)/100000, 'f', 2, 64), "hPa")
hum, _ := sensor.ReadHumidity()
println("Humidity:", strconv.FormatFloat(float64(hum)/100, 'f', 2, 64), "%")
alt, _ := sensor.ReadAltitude()
println("Altitude:", alt, "m")
time.Sleep(2 * time.Second)
}
}
+7 -4
View File
@@ -17,12 +17,12 @@ func (d Device) WriteByte(c byte) error {
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 14-16 cycles or 218.75ns - 250.00ns
// T0H: 17-19 cycles or 265.63ns - 296.88ns
// T0L: 54-56 cycles or 843.75ns - 875.00ns
// +: 68-72 cycles or 1062.50ns - 1125.00ns
// T1H: 36-38 cycles or 562.50ns - 593.75ns
// +: 71-75 cycles or 1109.38ns - 1171.88ns
// T1H: 39-41 cycles or 609.38ns - 640.63ns
// T1L: 30-32 cycles or 468.75ns - 500.0ns
// +: 66-70 cycles or 1031.25ns - 1093.75ns
// +: 69-73 cycles or 1078.13ns - 1140.63ns
// A branch is treated here as 1-3 cycles, because apparently it might get
// speculated. This is more of a guess than hard fact, because the only docs
// by ARM that state this are now considered superseded (by what?).
@@ -41,6 +41,9 @@ func (d Device) WriteByte(c byte) error {
nop
nop
nop
nop
nop
nop
bcs.n skip_store @ [1-3]
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
skip_store: