Compare commits

...

26 Commits

Author SHA1 Message Date
deadprogram dbff576c9e all: release 0.20.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2022-04-28 17:03:38 +02:00
Ayke van Laethem f0a260be66 ws2812: convert AVR assembly to C inline assembly
See https://github.com/tinygo-org/drivers/pull/401 for details.
I haven't converted it to autogenerated assembly because AVR is
different from many other architectures (8-bit, among others) and it
didn't seem worth the effort as many chips run at 16MHz anyway.

I ran the two AVR smoke tests for the ws2812 driver and the resulting
binary is exactly the same.
2022-04-21 11:10:18 +02:00
Ayke van Laethem 06e298c514 ws2812: write inline assembly using C instead of Go
This is better for various reasons:

  * I don't like the inline assembly implementation in TinyGo. It kinda
    works, but it's hard to use correctly.
  * The Go version had a subtle bug: the i and value variables weren't
    marked as modified. The compiler produced correct code by chance. In
    GCC-style inline assembly, it's possible to correctly mark it as an
    input+output operand.
  * LLVM 14 has some changes to inline assembly that change how memory
    operands can be created. Instead of supporting that, I'd like to get
    rid of memory operands entirely (and possibly inline assembly in
    general).

I did some light testing: the ARM assembly changed a bit but not in any
way that should have a practical effect, and the RISC-V assembly didn't
change at all.
2022-04-21 11:10:18 +02:00
Kuiki f2f470973d fix esp8266 ConnectToAccessPoint timeout args 2022-04-21 10:23:21 +02:00
Oliver Stäbler 448598cbf8 vl53l1x: Add functions for setting 'region of interest'
Implement functions to get and set 'region of interest' similar to its
reference implementation in C:
https://github.com/pololu/vl53l1x-arduino/commit/a6de3966784aa07680bb35a1f9ac369959efe19f

Signed-off-by: Oliver Stäbler <oliver.staebler@bytesatwork.ch>
2022-04-21 08:03:22 +02:00
Oliver Stäbler ec98f2dc2c vl53l1x: Fix switch-case semantics
The source code has been ported from C. It seems, the different
semantics of switch-case in Go has not been ported properly.
To fix this, remove 'break' statements and introduce 'fallthrough' where
necessary.

Signed-off-by: Oliver Stäbler <oliver.staebler@bytesatwork.ch>
2022-04-20 20:14:36 +02:00
Patricio Whittingslow 2476cd7bd8 pca9685: fix on=0 bug
Setting on to zero caused PWM signal to be set to high.
2022-04-16 19:14:01 +02:00
Neil Davis 01fed475e1 Add 'irremote' package as a basic infra-red driver
Currently contains an IR Receiver device only.
An IR Sender device will be added shortly

Supports receiving only NEC (extended address) protocol including repeat codes so far
2022-04-11 22:05:57 +02:00
soypat edfdd4e09e add l3gd20 gyro driver 2022-04-08 15:41:46 +02:00
soypat 5e54f08605 pca9685: add buffered one shot write 2022-04-04 17:12:39 +02:00
sago35 880b958d64 ili9341: added Feather board support to InitDisplay() 2022-03-12 08:42:26 +01:00
sago35 24db8b4e2c ili9341: add support for atsame5x 2022-03-11 07:53:30 +01:00
sago35 6df9247f92 ili9341: avoid heap allocations 2022-03-10 20:28:20 +01:00
Elfo404 56fb346438 dht: fix error check in example 2022-03-10 19:10:50 +01:00
Neil Davis 3d279d4d25 Add a SetIntensity() function to max7xx driver and example.
Also remove redundant byte() casts from SetScanLimit()
2022-03-10 18:05:34 +01:00
sago35 4a336f674c image: fix interface 2022-03-10 12:53:44 +01:00
Ayke van Laethem 2d32995f6a ws2812: support high-MHz ARMv6M chips like the RP2040
The possible branch distance is a lot shorter on ARMv6M (Cortex-M and
Cortex-M0+) for conditional branches. Therefore, convert this long
conditional branch into an unconditional branch.

This probably makes the code a little bit slower but because it is in
the low period of the WS2812 signal it shouldn't matter for the
protocol. And it avoids difficult workarounds specifically for the
RP2040.
2022-03-10 10:50:12 +01:00
Anton Fisher 9a98d1be55 drivers: add driver for IS31FL3731 matrix LED driver (#370)
IS31FL3731: add driver for IS31FL3731 matrix LED driver
2022-03-09 13:54:07 +01:00
Donia Chaiehloudj 02e4548966 dht: fix humidity and temperature extraction for DHT22 (#358) 2022-03-08 12:42:28 +01:00
Yurii Soldak 45dce188f5 lsmXXX: unified, error handling, memory management 2022-02-18 06:10:27 +01:00
Steven Pearson b3c0315a09 Driver for SSD1289 LCD 2022-02-18 06:01:55 +01:00
BCG 0ced12683c Revert "Added support for 125MHz to WS2812 for RP2040"
This reverts commit b4eb406a43 (erroneous
push).
2022-02-14 01:06:23 -05:00
BCG b4eb406a43 Added support for 125MHz to WS2812 for RP2040 2022-02-14 00:48:33 -05:00
sago35 10bd48c39d ws2812: add support for m5stamp-c3 2022-02-11 23:47:55 +01:00
Yurii Soldak ad3ef92cfe lps22hb: pin rename, sync with main repo 2022-01-29 23:02:42 +01:00
deadprogram f7dce6ae22 wifinina: correct sendParamStr to handle empty strings, such as when connecting to an unsecured access point.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2022-01-26 23:05:50 +01:00
58 changed files with 4323 additions and 2456 deletions
+42
View File
@@ -1,3 +1,45 @@
0.20.0
---
- **new devices**
- irremote: Add basic infra-red driver
- IS31FL3731: add driver for IS31FL3731 matrix LED driver (#370)
- l3gd20: add gyro driver
- SSD1289: Driver for SSD1289 LCD
- **enhancements**
- **ili9341**
- add support for atsame5x
- added Feather board support to InitDisplay()
- avoid heap allocations
- **lps22hb**
- pin rename, sync with main repo
- **lsmXXX**
- unified, error handling, memory management
- **max7xx**
- Add a SetIntensity() function to max7xx driver and example
- **vl53l1x**
- Add functions for setting 'region of interest'
- Fix switch-case semantics
- **ws2812**
- add support for m5stamp-c3
- convert AVR assembly to C inline assembly
- support high-MHz ARMv6M chips like the RP2040
- write inline assembly using C instead of Go
- **bugfixes**
- **dht**
- fix error check in example
- fix humidity and temperature extraction for DHT22 (#358)
- **esp8266**
- fix ConnectToAccessPoint timeout args
- **image**
- fix interface
- **pca9685**
- add buffered one shot write
- fix on=0 bug
- **wifinina**
- correct sendParamStr to handle empty strings, such as when connecting to an unsecured access point
0.19.0
---
- **new devices**
+13 -3
View File
@@ -107,6 +107,8 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=p1am-100 ./examples/p1am/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pico ./examples/pca9685/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setbuffer/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
@@ -157,6 +159,10 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examples/ws2812
@md5sum ./build/test.bin
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
@md5sum ./build/test.hex
ifneq ($(AVR), 0)
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
@md5sum ./build/test.hex
@@ -225,16 +231,20 @@ endif
@md5sum ./build/test.elf
tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/sx126x/lora_rxtx/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/ssd1289/main.go
@md5sum ./build/test.uf2
tinygo build -size short -o ./build/test.hex -target=pico ./examples/irremote/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 dht keypad4x4 max72xx p1am tone tm1637 \
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x keypad4x4 max72xx p1am tone tm1637 \
pcf8563 mcp2515 servo sdcard rtl8720dn image cmd i2csoft hts221 lps22hb apds9960 axp192 xpt2046 \
ft6336 sx126x
ft6336 sx126x ssd1289 irremote
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
unit-test:
@go test -v $(addprefix ./,$(TESTS))
@go test -v $(addprefix ./,$(TESTS))
test: clean fmt-check unit-test smoke-test
+3
View File
@@ -87,6 +87,8 @@ The following 78 devices are supported.
| [software I2C driver](https://www.ti.com/lit/an/slva704/slva704.pdf) | GPIO |
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
| [INA260 Volt/Amp/Power meter](https://www.ti.com/lit/ds/symlink/ina260.pdf) | I2C |
| [Infrared remote control](https://en.wikipedia.org/wiki/Consumer_IR) | GPIO |
| [IS31FL3731 matrix LED driver](https://www.lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf) | I2C |
| [4x4 Membrane Keypad](https://cdn.sparkfun.com/assets/f/f/a/5/0/DS-16038.pdf) | GPIO |
| [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM |
| [L9110x motor driver](https://www.elecrow.com/download/datasheet-l9110.pdf) | GPIO/PWM |
@@ -135,6 +137,7 @@ The following 78 devices are supported.
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
| [XPT2046 touch controller](http://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf) | GPIO |
| [Semtech SX126x Lora](https://www.semtech.com/products/wireless-rf/lora-transceiv-ers/sx1261) | SPI |
| [SSD1289 TFT color display](http://aitendo3.sakura.ne.jp/aitendo_data/product_img/lcd/tft2/M032C1289TP/3.2-SSD1289.pdf) | GPIO |
## Contributing
+3 -27
View File
@@ -1,3 +1,6 @@
//go:build tinygo
// +build tinygo
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
@@ -7,34 +10,10 @@
package dht // import "tinygo.org/x/drivers/dht"
import (
"encoding/binary"
"machine"
"time"
)
// enum type for device type
type DeviceType uint8
// DeviceType specific parsing of information received from the sensor
func (d DeviceType) extractData(buf []byte) (temp int16, hum uint16) {
if d == DHT11 {
temp = int16(buf[2])
if buf[3]&0x80 > 0 {
temp = -1 - temp
}
temp *= 10
temp += int16(buf[3] & 0x0f)
hum = 10*uint16(buf[0]) + uint16(buf[1])
} else {
hum = binary.LittleEndian.Uint16(buf[0:2])
temp = int16(buf[3])<<8 + int16(buf[2]&0x7f)
if buf[2]&0x80 > 0 {
temp = -temp
}
}
return
}
// Celsius and Fahrenheit temperature scales
type TemperatureScale uint8
@@ -54,9 +33,6 @@ const (
startTimeout = time.Millisecond * 200
startingLow = time.Millisecond * 20
DHT11 DeviceType = iota
DHT22
C TemperatureScale = iota
F
+44
View File
@@ -0,0 +1,44 @@
package dht
import (
"encoding/binary"
)
// DeviceType is the enum type for device type
type DeviceType uint8
const (
DHT11 DeviceType = iota
DHT22
)
// extractData parses information received from the sensor.
// The 2 first buffers are for the humidity and
// the 2 following corresponds to the temperature.
func (d DeviceType) extractData(buf []byte) (temp int16, hum uint16) {
switch d {
case DHT11:
hum = 10*uint16(buf[0]) + uint16(buf[1])
temp = int16(buf[2])
if buf[3]&0x80 > 0 {
temp = -1 - temp
}
temp *= 10
temp += int16(buf[3] & 0x0f)
case DHT22:
hum = binary.BigEndian.Uint16(buf[0:2])
temp = int16(buf[2]&0x7f)<<8 + int16(buf[3])
// the first bit corresponds to the sign bit
if buf[2]&0x80 > 0 {
temp = -temp
}
default:
// keeping this for retro-compatibility but not tested
hum = binary.LittleEndian.Uint16(buf[0:2])
temp = int16(buf[3])<<8 + int16(buf[2]&0x7f)
if buf[2]&0x80 > 0 {
temp = -temp
}
}
return
}
+46
View File
@@ -0,0 +1,46 @@
package dht
import (
"testing"
)
func TestDeviceType_extractData(t *testing.T) {
bitStr := "0000001010001100000000010101111111101110"
buf := bitStringToBytes(bitStr)
tt := []struct {
name string
d DeviceType
buf []byte
wantTemp int16
wantHum uint16
}{
{
// temp = 35.1C hum = 65.2%
name: "DHT22", d: DHT22, buf: buf, wantTemp: 351, wantHum: 652,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
gotTemp, gotHum := tc.d.extractData(tc.buf)
if gotTemp != tc.wantTemp {
t.Errorf("extractData() gotTemp = %v, want %v", gotTemp, tc.wantTemp)
}
if gotHum != tc.wantHum {
t.Errorf("extractData() gotHum = %v, want %v", gotHum, tc.wantHum)
}
})
}
}
func bitStringToBytes(s string) []byte {
b := make([]byte, (len(s)+(8-1))/8)
for i, r := range s {
if r < '0' || r > '1' {
panic("not in range")
}
b[i>>3] |= byte(r-'0') << uint(7-i&7)
}
return b
}
+3
View File
@@ -1,3 +1,6 @@
//go:build tinygo
// +build tinygo
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
+3
View File
@@ -1,3 +1,6 @@
//go:build tinygo
// +build tinygo
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
+3
View File
@@ -1,3 +1,6 @@
//go:build tinygo
// +build tinygo
package dht // import "tinygo.org/x/drivers/dht"
import (
+1 -1
View File
@@ -12,7 +12,7 @@ func (d *Device) ConnectToAccessPoint(ssid, pass string, timeout time.Duration)
}
d.SetWifiMode(WifiModeClient)
return d.ConnectToAP(ssid, pass, 10)
return d.ConnectToAP(ssid, pass, int(timeout.Seconds()))
}
func (d *Device) Disconnect() error {
+1 -1
View File
@@ -12,7 +12,7 @@ func main() {
dhtSensor := dht.New(pin, dht.DHT11)
for {
temp, hum, err := dhtSensor.Measurements()
if err != nil {
if err == nil {
fmt.Printf("Temperature: %02d.%d°C, Humidity: %02d.%d%%\n", temp/10, temp%10, hum/10, hum%10)
} else {
fmt.Printf("Could not take measurements from the sensor: %s\n", err.Error())
+42
View File
@@ -0,0 +1,42 @@
//go:build feather_m0 || feather_m4 || feather_m4_can || feather_nrf52840 || feather_nrf52840_sense || feather_stm32f405 || feather_rp2040
// +build feather_m0 feather_m4 feather_m4_can feather_nrf52840 feather_nrf52840_sense feather_stm32f405 feather_rp2040
package initdisplay
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
func InitDisplay() *ili9341.Device {
machine.D5.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.D6.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.SPI0.Configure(machine.SPIConfig{
SCK: machine.SPI0_SCK_PIN,
SDO: machine.SPI0_SDO_PIN,
SDI: machine.SPI0_SDI_PIN,
Frequency: 40000000,
})
// configure backlight
backlight := machine.D9
backlight.Configure(machine.PinConfig{machine.PinOutput})
display := ili9341.NewSPI(
machine.SPI0,
machine.D10, // LCD_DC,
machine.D11, // LCD_SS_PIN,
machine.D12, // LCD_RESET,
)
// configure display
display.Configure(ili9341.Config{})
backlight.High()
display.SetRotation(ili9341.Rotation270)
return display
}
+4 -2
View File
@@ -72,7 +72,8 @@ func drawPng(display *ili9341.Device) error {
}
})
return png.Decode(p)
_, err := png.Decode(p)
return err
}
func drawJpeg(display *ili9341.Device) error {
@@ -84,7 +85,8 @@ func drawJpeg(display *ili9341.Device) error {
}
})
return jpeg.Decode(p)
_, err := jpeg.Decode(p)
return err
}
func errorMessage(err error) {
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/irremote"
)
var irCmdButtons = map[uint16]string{
0xA2: "POWER",
0xE2: "FUNC/STOP",
0x62: "VOL+",
0x22: "FAST BACK",
0x02: "PAUSE",
0xC2: "FAST FORWARD",
0xE0: "DOWN",
0xA8: "VOL-",
0x90: "UP",
0x98: "EQ",
0xB0: "ST/REPT",
0x68: "0",
0x30: "1",
0x18: "2",
0x7A: "3",
0x10: "4",
0x38: "5",
0x5A: "6",
0x42: "7",
0x4A: "8",
0x52: "9",
}
var (
pinIRIn = machine.GP26
ir irremote.ReceiverDevice
)
func setupPins() {
ir = irremote.NewReceiver(pinIRIn)
ir.Configure()
}
func irCallback(data irremote.Data) {
msg := "Command: " + irCmdButtons[data.Command]
if data.Flags&irremote.DataFlagIsRepeat != 0 {
msg = msg + " (REPEAT)"
}
println(msg)
}
func main() {
setupPins()
ir.SetCommandHandler(irCallback)
for {
time.Sleep(time.Millisecond * 10)
}
}
+51
View File
@@ -0,0 +1,51 @@
package main
import (
"time"
"machine"
"tinygo.org/x/drivers/is31fl3731"
)
// I2CAddress -- address of led matrix
var I2CAddress uint8 = is31fl3731.I2C_ADDRESS_74
func main() {
bus := machine.I2C0
err := bus.Configure(machine.I2CConfig{})
if err != nil {
println("could not configure I2C:", err)
return
}
// Create driver for Adafruit 15x7 CharliePlex LED Matrix FeatherWing
// (CharlieWing): https://www.adafruit.com/product/3163
ledMatrix := is31fl3731.NewAdafruitCharlieWing15x7(bus, I2CAddress)
err = ledMatrix.Configure()
if err != nil {
println("could not configure is31fl3731 driver:", err)
return
}
// Fill the whole matrix on the frame #0 (visible by default)
ledMatrix.Fill(is31fl3731.FRAME_0, uint8(3))
// Draw couple pixels on the frame #1 (not visible yet)
ledMatrix.DrawPixelXY(is31fl3731.FRAME_1, uint8(0), uint8(0), uint8(10))
ledMatrix.DrawPixelXY(is31fl3731.FRAME_1, uint8(14), uint8(6), uint8(10))
// There are 8 frames available, it's a good idea to draw on an invisible
// frame and then switch to that frame to reduce flickering. Switch between
// frame #0 and #1 in a loop to show animation:
for {
println("show frame #0...")
ledMatrix.SetActiveFrame(is31fl3731.FRAME_0)
time.Sleep(time.Second * 3)
println("show frame #1...")
ledMatrix.SetActiveFrame(is31fl3731.FRAME_1)
time.Sleep(time.Second * 3)
}
}
+39
View File
@@ -0,0 +1,39 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/l3gd20"
)
func main() {
const (
// Default address on most breakout boards.
pcaAddr = 0x40
)
bus := machine.I2C0
err := bus.Configure(machine.I2CConfig{})
if err != nil {
panic(err.Error())
}
gyro := l3gd20.NewI2C(bus, 105)
err = gyro.Configure(l3gd20.Config{Range: l3gd20.Range_250})
if err != nil {
println(err.Error())
}
var x, y, z int32
for {
err = gyro.Update()
if err != nil {
println(err.Error())
}
x, y, z = gyro.AngularVelocity()
println(x, y, z)
time.Sleep(500 * time.Millisecond)
}
}
+16 -8
View File
@@ -13,7 +13,13 @@ func main() {
machine.I2C0.Configure(machine.I2CConfig{})
sensor := lsm303agr.New(machine.I2C0)
sensor.Configure(lsm303agr.Configuration{}) //default settings
err := sensor.Configure(lsm303agr.Configuration{}) //default settings
if err != nil {
for {
println("Failed to configure", err.Error())
time.Sleep(time.Second)
}
}
// you can specify the following options to adjust accuracy, sensor range or save power.
// see https://github.com/tinygo-org/drivers/blob/release/lsm303agr/registers.go for details:
@@ -28,22 +34,24 @@ func main() {
})
*/
if !sensor.Connected() {
println("LSM303AGR/MAG not connected!")
return
}
for {
if !sensor.Connected() {
println("LSM303AGR/MAG not connected!")
time.Sleep(time.Second)
continue
}
// accel_x, accel_y, accel_z := sensor.ReadAcceleration()
// println("ACCEL_X:", accel_x/100000, " ACCEL_Y:", accel_y/100000, " ACCEL_Z:", accel_z/100000)
// mag_x, mag_y, mag_z := sensor.ReadMagneticField()
// println("MAG_X:", mag_x/100000, " MAG_Y:", mag_y/100000, " MAG_Z:", mag_z/100000)
pitch, roll := sensor.ReadPitchRoll()
pitch, roll, _ := sensor.ReadPitchRoll()
println("Pitch:", float32(pitch)/100000, " Roll:", float32(roll)/100000)
heading := sensor.ReadCompass()
heading, _ := sensor.ReadCompass()
println("Heading:", float32(heading)/100000, "degrees")
temp, _ := sensor.ReadTemperature()
+13 -6
View File
@@ -12,16 +12,23 @@ func main() {
machine.I2C0.Configure(machine.I2CConfig{})
accel := lsm6ds3.New(machine.I2C0)
accel.Configure(lsm6ds3.Configuration{})
if !accel.Connected() {
println("LSM6DS3 not connected")
return
err := accel.Configure(lsm6ds3.Configuration{})
if err != nil {
for {
println("Failed to configure", err.Error())
time.Sleep(time.Second)
}
}
for {
x, y, z := accel.ReadAcceleration()
if !accel.Connected() {
println("LSM6DS3 not connected")
time.Sleep(time.Second)
continue
}
x, y, z, _ := accel.ReadAcceleration()
println("Acceleration:", float32(x)/1000000, float32(y)/1000000, float32(z)/1000000)
x, y, z = accel.ReadRotation()
x, y, z, _ = accel.ReadRotation()
println("Gyroscope:", float32(x)/1000000, float32(y)/1000000, float32(z)/1000000)
x, _ = accel.ReadTemperature()
println("Degrees C", float32(x)/1000, "\n\n")
+10 -4
View File
@@ -26,12 +26,18 @@ func main() {
machine.I2C0.Configure(machine.I2CConfig{})
device := lsm6dsox.New(machine.I2C0)
device.Configure(lsm6dsox.Configuration{
err := device.Configure(lsm6dsox.Configuration{
AccelRange: lsm6dsox.ACCEL_2G,
AccelSampleRate: lsm6dsox.ACCEL_SR_104,
GyroRange: lsm6dsox.GYRO_250DPS,
GyroSampleRate: lsm6dsox.GYRO_SR_104,
})
if err != nil {
for {
println("Failed to configure", err.Error())
time.Sleep(time.Second)
}
}
for {
@@ -46,8 +52,8 @@ func main() {
calibrateGyro(device)
}
ax, ay, az := device.ReadAcceleration()
gx, gy, gz := device.ReadRotation()
ax, ay, az, _ := device.ReadAcceleration()
gx, gy, gz, _ := device.ReadRotation()
t, _ := device.ReadTemperature()
if PLOTTER {
@@ -64,7 +70,7 @@ func main() {
func calibrateGyro(device *lsm6dsox.Device) {
for i := 0; i < 100; i++ {
gx, gy, gz := device.ReadRotation()
gx, gy, gz, _ := device.ReadRotation()
cal[0] += float32(gx) / 1000000
cal[1] += float32(gy) / 1000000
cal[2] += float32(gz) / 1000000
+1 -1
View File
@@ -41,7 +41,7 @@ func main() {
for {
if con, err := device.Connected(); !con || err != nil {
if !device.Connected() {
println("LSM9DS1 not connected")
time.Sleep(time.Second)
continue
+1
View File
@@ -29,6 +29,7 @@ func main() {
driver.StopDisplayTest()
driver.SetDecodeMode(4)
driver.SetScanLimit(4)
driver.SetIntensity(8)
driver.StopShutdownMode()
for i := 1; i < int(digitNumber); i++ {
+68
View File
@@ -0,0 +1,68 @@
package main
import (
"image/color"
"machine"
"math/rand"
"tinygo.org/x/drivers/ssd1289"
)
func main() {
//The SSD1289 is configured in 16 bit parallel mode and requires 16 GPIOs
//The Pin bus is the most flexible but ineffecient method it switches
//individual pins on and off. If you are able to use consecutive pins
//consider creating a more efficient bus implementation that uses
//your microcontrollers built in "ports"
//see rp2040bus.go for an example for the rapsberry pi pico
bus := ssd1289.NewPinBus([16]machine.Pin{
machine.GP4, //DB0
machine.GP5, //DB1
machine.GP6, //DB2
machine.GP7, //DB3
machine.GP8, //DB4
machine.GP9, //DB5
machine.GP10, //DB6
machine.GP11, //DB7
machine.GP12, //DB8
machine.GP13, //DB9
machine.GP14, //DB10
machine.GP15, //DB11
machine.GP16, //DB12
machine.GP17, //DB13
machine.GP18, //DB14
machine.GP19, //DB15
})
//Control pins for the SSD1289
rs := machine.GP0
wr := machine.GP1
cs := machine.GP2
rst := machine.GP3
display := ssd1289.New(rs, wr, cs, rst, bus)
display.Configure() //Sends intialization sequence to SSD1289.
//!! After configure the display will contain random data and needs to be cleared
background := color.RGBA{0, 0, 0, 255} //Black
display.FillDisplay(background) //Clears the display to the given color
for {
//Draw random filled coloured rectangles
x := int16(rand.Intn(120))
w := int16(rand.Intn(120))
y := int16(rand.Intn(160))
h := int16(rand.Intn(160))
r := uint8(rand.Intn(255))
g := uint8(rand.Intn(255))
b := uint8(rand.Intn(255))
c := color.RGBA{r, g, b, 255}
display.FillRect(x, y, w, h, c) //Fills the given rectangle the rest of the display is unaffected.
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
//go:build !digispark && !arduino && !qtpy
// +build !digispark,!arduino,!qtpy
//go:build !digispark && !arduino && !qtpy && !m5stamp_c3
// +build !digispark,!arduino,!qtpy,!m5stamp_c3
package main
+11
View File
@@ -0,0 +1,11 @@
//go:build qtpy || m5stamp_c3
// +build qtpy m5stamp_c3
package main
import "machine"
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.WS2812
var led = machine.NoPin
-5
View File
@@ -5,11 +5,6 @@ package main
import "machine"
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.NEOPIXELS
var led = machine.NoPin
func init() {
pwr := machine.NEOPIXELS_POWER
pwr.Configure(machine.PinConfig{Mode: machine.PinOutput})
+52 -44
View File
@@ -29,6 +29,33 @@ type Device struct {
rd machine.Pin
}
var cmdBuf [6]byte
var initCmd = []byte{
0xEF, 3, 0x03, 0x80, 0x02,
0xCF, 3, 0x00, 0xC1, 0x30,
0xED, 4, 0x64, 0x03, 0x12, 0x81,
0xE8, 3, 0x85, 0x00, 0x78,
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
0xF7, 1, 0x20,
0xEA, 2, 0x00, 0x00,
PWCTR1, 1, 0x23, // Power control VRH[5:0]
PWCTR2, 1, 0x10, // Power control SAP[2:0];BT[3:0]
VMCTR1, 2, 0x3e, 0x28, // VCM control
VMCTR2, 1, 0x86, // VCM control2
MADCTL, 1, 0x48, // Memory Access Control
VSCRSADD, 1, 0x00, // Vertical scroll zero
PIXFMT, 1, 0x55,
FRMCTR1, 2, 0x00, 0x18,
DFUNCTR, 3, 0x08, 0x82, 0x27, // Display Function Control
0xF2, 1, 0x00, // 3Gamma Function Disable
GAMMASET, 1, 0x01, // Gamma curve selected
GMCTRP1, 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00,
GMCTRN1, 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F,
}
// Configure prepares display for use
func (d *Device) Configure(config Config) {
@@ -81,42 +108,15 @@ func (d *Device) Configure(config Config) {
delay(150)
}
initCmd := []byte{
0xEF, 3, 0x03, 0x80, 0x02,
0xCF, 3, 0x00, 0xC1, 0x30,
0xED, 4, 0x64, 0x03, 0x12, 0x81,
0xE8, 3, 0x85, 0x00, 0x78,
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
0xF7, 1, 0x20,
0xEA, 2, 0x00, 0x00,
PWCTR1, 1, 0x23, // Power control VRH[5:0]
PWCTR2, 1, 0x10, // Power control SAP[2:0];BT[3:0]
VMCTR1, 2, 0x3e, 0x28, // VCM control
VMCTR2, 1, 0x86, // VCM control2
MADCTL, 1, 0x48, // Memory Access Control
VSCRSADD, 1, 0x00, // Vertical scroll zero
PIXFMT, 1, 0x55,
FRMCTR1, 2, 0x00, 0x18,
DFUNCTR, 3, 0x08, 0x82, 0x27, // Display Function Control
0xF2, 1, 0x00, // 3Gamma Function Disable
GAMMASET, 1, 0x01, // Gamma curve selected
GMCTRP1, 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00,
GMCTRN1, 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F,
}
if config.DisplayInversion {
initCmd = append(initCmd, []byte{
INVON, 0x80,
}...)
initCmd = append(initCmd, INVON, 0x80)
}
initCmd = append(initCmd, []byte{
initCmd = append(initCmd,
SLPOUT, 0x80, // Exit Sleep
DISPON, 0x80, // Display on
0x00, // End of list
}...)
)
for i, c := 0, len(initCmd); i < c; {
cmd := initCmd[i]
if cmd == 0x00 {
@@ -267,24 +267,28 @@ func (d *Device) SetRotation(rotation Rotation) {
case Rotation270Mirror:
madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR
}
d.sendCommand(MADCTL, []uint8{madctl})
cmdBuf[0] = madctl
d.sendCommand(MADCTL, cmdBuf[:1])
d.rotation = rotation
}
// SetScrollArea sets an area to scroll with fixed top/bottom or left/right parts of the display
// Rotation affects scroll direction
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
d.sendCommand(VSCRDEF, []uint8{
uint8(topFixedArea >> 8), uint8(topFixedArea),
uint8(d.height - topFixedArea - bottomFixedArea>>8),
uint8(d.height - topFixedArea - bottomFixedArea),
uint8(bottomFixedArea >> 8), uint8(bottomFixedArea),
})
cmdBuf[0] = uint8(topFixedArea >> 8)
cmdBuf[1] = uint8(topFixedArea)
cmdBuf[2] = uint8(d.height - topFixedArea - bottomFixedArea>>8)
cmdBuf[3] = uint8(d.height - topFixedArea - bottomFixedArea)
cmdBuf[4] = uint8(bottomFixedArea >> 8)
cmdBuf[5] = uint8(bottomFixedArea)
d.sendCommand(VSCRDEF, cmdBuf[:6])
}
// SetScroll sets the vertical scroll address of the display.
func (d *Device) SetScroll(line int16) {
d.sendCommand(VSCRSADD, []uint8{uint8(line >> 8), uint8(line)})
cmdBuf[0] = uint8(line >> 8)
cmdBuf[1] = uint8(line)
d.sendCommand(VSCRSADD, cmdBuf[:2])
}
// StopScroll returns the display to its normal state
@@ -298,16 +302,20 @@ func (d *Device) setWindow(x, y, w, h int16) {
//y += d.rowOffset
x1 := x + w - 1
if x != d.x0 || x1 != d.x1 {
d.sendCommand(CASET, []uint8{
uint8(x >> 8), uint8(x), uint8(x1 >> 8), uint8(x1),
})
cmdBuf[0] = uint8(x >> 8)
cmdBuf[1] = uint8(x)
cmdBuf[2] = uint8(x1 >> 8)
cmdBuf[3] = uint8(x1)
d.sendCommand(CASET, cmdBuf[:4])
d.x0, d.x1 = x, x1
}
y1 := y + h - 1
if y != d.y0 || y1 != d.y1 {
d.sendCommand(PASET, []uint8{
uint8(y >> 8), uint8(y), uint8(y1 >> 8), uint8(y1),
})
cmdBuf[0] = uint8(y >> 8)
cmdBuf[1] = uint8(y)
cmdBuf[2] = uint8(y1 >> 8)
cmdBuf[3] = uint8(y1)
d.sendCommand(PASET, cmdBuf[:4])
d.y0, d.y1 = y, y1
}
d.sendCommand(RAMWR, nil)
+2 -2
View File
@@ -1,5 +1,5 @@
//go:build !atsamd51 && !atsamd21
// +build !atsamd51,!atsamd21
//go:build !atsamd51 && !atsame5x && !atsamd21
// +build !atsamd51,!atsame5x,!atsamd21
package ili9341
+2 -2
View File
@@ -1,5 +1,5 @@
//go:build atsamd51
// +build atsamd51
//go:build atsamd51 || atsame5x
// +build atsamd51 atsame5x
package ili9341
+2 -2
View File
@@ -773,10 +773,10 @@ func (d *decoder) convertToRGB() (image.Image, error) {
// Decode reads a JPEG image from r. Different from the standard package, the
// decoded result will be received by the callback set by SetCallback().
func Decode(r io.Reader) error {
func Decode(r io.Reader) (image.Image, error) {
var d decoder
_, err := d.decode(r, false)
return err
return nil, err
}
// DecodeConfig returns the color model and dimensions of a JPEG image without
+4 -4
View File
@@ -965,7 +965,7 @@ func (d *decoder) checkHeader() error {
// Decode reads a PNG image from r. Different from the standard package, the
// decoded result will be received by the callback set by SetCallback().
func Decode(r io.Reader) error {
func Decode(r io.Reader) (image.Image, error) {
d := &decoder{
r: r,
crc: crc32.NewIEEE(),
@@ -974,17 +974,17 @@ func Decode(r io.Reader) error {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return err
return nil, err
}
for d.stage != dsSeenIEND {
if err := d.parseChunk(); err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return err
return nil, err
}
}
return nil
return nil, nil
}
// DecodeConfig returns the color model and dimensions of a PNG image without
+225
View File
@@ -0,0 +1,225 @@
package irremote // import "tinygo.org/x/drivers/irremote"
import (
"machine"
"time"
)
// NEC protocol references
// https://www.sbprojects.net/knowledge/ir/nec.php
// https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol
// https://simple-circuit.com/arduino-nec-remote-control-decoder/
// Data encapsulates the data received by the ReceiverDevice.
type Data struct {
// Code is the raw IR data received.
Code uint32
// Address is the decoded address from the IR data received.
Address uint16
// Command is the decoded command from the IR data recieved
Command uint16
// Flags provides additional information about the IR data received. See DataFlags
Flags DataFlags
}
// DataFlags provides bitwise flags representing various information about recieved IR data.
type DataFlags uint16
// Valid values for DataFlags
const (
// DataFlagIsRepeat set indicates that the IR data is a repeat commmand
DataFlagIsRepeat DataFlags = 1 << iota
)
// CommandHandler defines the callback function used to provide IR data received by the ReceiverDevice.
type CommandHandler func(data Data)
// nec_ir_state represents the various internal states used to decode the NEC IR protocol commands
type nec_ir_state uint8
// Valid values for nec_ir_state
const (
lead_pulse_start nec_ir_state = iota // Start receiving IR data, beginning of 9ms lead pulse
lead_space_start // End of 9ms lead pulse, start of 4.5ms space
lead_space_end // End of 4.5ms space, start of 562µs pulse
bit_read_start // End of 562µs pulse, start of 562µs or 1687µs space
bit_read_end // End of 562µs or 1687µs space
trail_pulse_end // End of 562µs trailing pulse
)
// ReceiverDevice is the device for receiving IR commands
type ReceiverDevice struct {
pin machine.Pin // IR input pin.
ch CommandHandler // client callback function
necState nec_ir_state // internal state machine
data Data // decoded data for client
lastTime time.Time // used to track states
bitIndex int // tracks which bit (0-31) of necCode is being read
}
// NewReceiver returns a new IR receiver device
func NewReceiver(pin machine.Pin) ReceiverDevice {
return ReceiverDevice{pin: pin}
}
// Configure configures the input pin for the IR receiver device
func (ir *ReceiverDevice) Configure() {
// The IR receiver sends logic HIGH when NOT receiving IR, and logic LOW when receiving IR
ir.pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
}
// SetCommandHandler is used to start or stop receiving IR commands via a callback function (pass nil to stop)
func (ir *ReceiverDevice) SetCommandHandler(ch CommandHandler) {
ir.ch = ch
ir.resetStateMachine()
if ch != nil {
// Start monitoring IR output pin for changes
ir.pin.SetInterrupt(machine.PinFalling|machine.PinRising, ir.pinChange)
} else {
// Stop monitoring IR output pin for changes
ir.pin.SetInterrupt(0, nil)
}
}
// Internal helper function to reset state machine on protocol failure
func (ir *ReceiverDevice) resetStateMachine() {
ir.data = Data{}
ir.bitIndex = 0
ir.necState = lead_pulse_start
}
// Internal pin rising/falling edge interrupt handler
func (ir *ReceiverDevice) pinChange(pin machine.Pin) {
/* Currently TinyGo is sending machine.NoPin (0xff) for all pins, at least on RP2040
if pin != ir.pin {
return // This is not the pin you're looking for
}
*/
now := time.Now()
duration := now.Sub(ir.lastTime)
ir.lastTime = now
switch ir.necState {
case lead_pulse_start:
if !ir.pin.Get() {
// IR is 'on' (pin is pulled high and sent low when IR is received)
ir.necState = lead_space_start // move to next state
}
case lead_space_start:
if duration > time.Microsecond*9500 || duration < time.Microsecond*8500 {
// Invalid interval for 9ms lead pulse. Reset
ir.resetStateMachine()
} else {
// 9ms lead pulse detected, move to next state
ir.necState = lead_space_end
}
case lead_space_end:
if duration > time.Microsecond*5000 || duration < time.Microsecond*1750 {
// Invalid interval for 4.5ms lead space OR 2.25ms repeat space. Reset
ir.resetStateMachine()
} else {
// 4.5ms lead space OR 2.25ms repeat space detected
if duration > time.Microsecond*3000 {
// 4.5ms lead space detected, new code incoming, move to next state
ir.resetStateMachine()
ir.necState = bit_read_start
} else {
// 2.25ms repeat space detected.
if ir.data.Code != 0 {
// Valid repeat code. Invoke client callback with repeat flag set
ir.data.Flags |= DataFlagIsRepeat
if ir.ch != nil {
ir.ch(ir.data)
}
ir.necState = lead_pulse_start
} else {
// ir.data is not in a valid state for a repeat. Reset
ir.resetStateMachine()
}
}
}
case bit_read_start:
if duration > time.Microsecond*700 || duration < time.Microsecond*400 {
// Invalid interval for 562.5µs pulse. Reset
ir.resetStateMachine()
} else {
// 562.5µs pulse detected, move to next state
ir.necState = bit_read_end
}
case bit_read_end:
if duration > time.Microsecond*1800 || duration < time.Microsecond*400 {
// Invalid interval for 562.5µs space OR 1687.5µs space. Reset
ir.resetStateMachine()
} else {
// 562.5µs OR 1687.5µs space detected
mask := uint32((1 << (31 - ir.bitIndex)))
if duration > time.Microsecond*1000 {
// 1687.5µs space detected (logic 1) - Set bit
ir.data.Code |= mask
} else {
// 562.5µs space detected (logic 0) - Clear bit
ir.data.Code &^= mask
}
ir.bitIndex++
if ir.bitIndex > 31 {
// We've read all bits for this code, move to next state
ir.necState = trail_pulse_end
} else {
// Read next bit
ir.necState = bit_read_start
}
}
case trail_pulse_end:
if duration > time.Microsecond*700 || duration < time.Microsecond*400 {
// Invalid interval for trailing 562.5µs pulse. Reset
ir.resetStateMachine()
} else {
// 562.5µs trailing pulse detected. Decode & validate data
err := ir.decode()
if err == irDecodeErrorNone {
// Valid data, invoke client callback
if ir.ch != nil {
ir.ch(ir.data)
}
// around we go again. Note: we don't resetStateMachine() since repeat codes are now possible
ir.necState = lead_pulse_start
} else {
ir.resetStateMachine()
}
}
}
}
// Error type for NEC format decoding
type irDecodeError int
// Valid values for irDecodeError
const (
irDecodeErrorNone irDecodeError = iota // no error occurred
irDecodeErrorInverseCheckFail // validation of inverse cmd does not match cmd
)
func (ir *ReceiverDevice) decode() irDecodeError {
// Decode cmd and inverse cmd and perform validation check
cmd := uint8((ir.data.Code & 0xff00) >> 8)
invCmd := uint8(ir.data.Code & 0xff)
if cmd != ^invCmd {
// Validation failure. cmd and inverse cmd do not match
return irDecodeErrorInverseCheckFail
}
// cmd validation pass, decode address
ir.data.Command = uint16(cmd)
addrLow := uint8((ir.data.Code & 0xff000000) >> 24)
addrHigh := uint8((ir.data.Code & 0x00ff0000) >> 16)
if addrHigh == ^addrLow {
// addrHigh is inverse of addrLow. This is not a valid 16-bit address in extended NEC coding
// since it is indistinguishable from 8-bit address with inverse validation. Use the 8-bit address
ir.data.Address = uint16(addrLow)
} else {
// 16-bit extended NEC address
ir.data.Address = (uint16(addrHigh) << 8) | uint16(addrLow)
}
// Clear repeat flag
ir.data.Flags &^= DataFlagIsRepeat
return irDecodeErrorNone
}
+209
View File
@@ -0,0 +1,209 @@
// Package is31fl3731 provides a driver for the Lumissil IS31FL3731 matrix LED
// driver.
//
// Driver supports following layouts:
// - any custom LED matrix layout
// - Adafruit 15x7 CharliePlex LED Matrix FeatherWing (CharlieWing)
// https://www.adafruit.com/product/3163
//
// Datasheet:
// https://www.lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf
//
// This driver inspired by Adafruit Python driver:
// https://github.com/adafruit/Adafruit_CircuitPython_IS31FL3731
//
package is31fl3731
import (
"fmt"
"time"
"tinygo.org/x/drivers"
)
// Device implements TinyGo driver for Lumissil IS31FL3731 matrix LED driver
type Device struct {
Address uint8
bus drivers.I2C
// Currently selected command register (one of the frame registers or the
// function register)
selectedCommand uint8
}
// Configure chip for operating as a LED matrix display
func (d *Device) Configure() (err error) {
// Shutdown software
err = d.writeFunctionRegister(SET_SHUTDOWN, []byte{SOFTWARE_OFF})
if err != nil {
return fmt.Errorf("failed to shutdown: %w", err)
}
time.Sleep(time.Millisecond * 10)
// Wake up software
err = d.writeFunctionRegister(SET_SHUTDOWN, []byte{SOFTWARE_ON})
if err != nil {
return fmt.Errorf("failed to wake up: %w", err)
}
// Set display to a picture mode ("auto frame play mode" and "audio frame play
// mode" are not supported in this version of the driver)
err = d.writeFunctionRegister(SET_DISPLAY_MODE, []byte{DISPLAY_MODE_PICTURE})
if err != nil {
return fmt.Errorf("failed to switch to a picture move: %w", err)
}
// Enable LEDs that are present (soldered) on the board. From the datasheet:
// LEDs which are no connected must be off by LED Control Register (Frame
// Registers) or it will affect other LEDs
err = d.enableLEDs()
if err != nil {
return fmt.Errorf("failed to enable LEDs: %w", err)
}
// Disable audiosync
err = d.writeFunctionRegister(SET_AUDIOSYNC, []byte{AUDIOSYNC_OFF})
if err != nil {
return fmt.Errorf("failed to disable audiosync: %w", err)
}
// Clear all frames
for frame := FRAME_0; frame <= FRAME_7; frame++ {
err = d.Clear(frame)
if err != nil {
return fmt.Errorf("failed to clear frame %d: %w", frame, err)
}
}
// 1st frame is displayed by default
err = d.SetActiveFrame(FRAME_0)
if err != nil {
return fmt.Errorf("failed to set active frame: %w", err)
}
return nil
}
// selectCommand selects command register, can be:
// - frame registers 0-7
// - function register
func (d *Device) selectCommand(command uint8) (err error) {
if command != d.selectedCommand {
d.selectedCommand = command
return d.bus.WriteRegister(d.Address, COMMAND, []byte{command})
}
return nil
}
// writeFunctionRegister selects the function register and writes data into it
func (d *Device) writeFunctionRegister(operation uint8, data []byte) (err error) {
err = d.selectCommand(FUNCTION)
if err != nil {
return err
}
return d.bus.WriteRegister(d.Address, operation, data)
}
// enableLEDs enables only LEDs that are soldered on the set board. Enabled
// all 16x9 LEDs by default
func (d *Device) enableLEDs() (err error) {
for frame := FRAME_0; frame <= FRAME_7; frame++ {
err = d.selectCommand(frame)
if err != nil {
return err
}
// Enable every LED (16 columns x 9 rows)
for i := uint8(0); i < 16; i++ {
err = d.bus.WriteRegister(d.Address, i, []byte{0xFF})
if err != nil {
return err
}
}
}
return nil
}
// setPixelPWD sets individual pixel's PWM value [0-255] on the selected frame
func (d *Device) setPixelPWD(frame, n, value uint8) (err error) {
err = d.selectCommand(frame)
if err != nil {
return err
}
return d.bus.WriteRegister(d.Address, LED_PWM_OFFSET+n, []byte{value})
}
// SetActiveFrame sets frame to display with LEDs
func (d *Device) SetActiveFrame(frame uint8) (err error) {
if frame > FRAME_7 {
return fmt.Errorf("frame %d is out of valid range [0-7]", frame)
}
return d.writeFunctionRegister(SET_ACTIVE_FRAME, []byte{frame})
}
// Fill the whole frame with provided PWM value [0-255]
func (d *Device) Fill(frame, value uint8) (err error) {
if frame > FRAME_7 {
return fmt.Errorf("frame %d is out of valid range [0-7]", frame)
}
err = d.selectCommand(frame)
if err != nil {
return err
}
data := make([]byte, 24)
for i := range data {
data[i] = value
}
for i := uint8(0); i < 6; i++ {
err = d.bus.WriteRegister(d.Address, LED_PWM_OFFSET+i*24, data)
if err != nil {
return err
}
}
return nil
}
// Clear the whole frame
func (d *Device) Clear(frame uint8) (err error) {
return d.Fill(frame, 0x00)
}
// DrawPixelIndex draws a single pixel on the selected frame by its index with
// provided PWM value [0-255]
func (d *Device) DrawPixelIndex(frame, index, value uint8) (err error) {
if frame > FRAME_7 {
return fmt.Errorf("frame %d is out of valid range [0-7]", frame)
}
return d.setPixelPWD(frame, index, value)
}
// DrawPixelXY draws a single pixel on the selected frame by its XY coordinates
// with provided PWM value [0-255]. Raw LEDs layout assumed to be a 16x9 matrix,
// and can be used with any custom board that has IS31FL3731 driver.
func (d *Device) DrawPixelXY(frame, x, y, value uint8) (err error) {
return d.setPixelPWD(frame, 16*x+y, value)
}
// New creates a raw driver w/o any preset board layout.
// Addresses:
// - 0x74 (AD pin connected to GND)
// - 0x75 (AD pin connected to SCL)
// - 0x76 (AD pin connected to SDA)
// - 0x77 (AD pin connected to VCC)
func New(bus drivers.I2C, address uint8) Device {
return Device{
Address: address,
bus: bus,
}
}
+106
View File
@@ -0,0 +1,106 @@
package is31fl3731
import (
"fmt"
"tinygo.org/x/drivers"
)
// DeviceAdafruitCharlieWing15x7 implements TinyGo driver for Lumissil
// IS31FL3731 matrix LED driver on Adafruit 15x7 CharliePlex LED Matrix
// FeatherWing (CharlieWing) board: https://www.adafruit.com/product/3163
type DeviceAdafruitCharlieWing15x7 struct {
Device
}
// enableLEDs enables only LEDs that are soldered on the Adafruit CharlieWing
// board. The board has following LEDs matrix layout:
//
// "o" - connected (soldered) LEDs
// "x" - not connected LEDs
//
// + - - - - - - - - - - - - - - +
// | + - - - - - - - - - - - - + |
// | | | |
// | | v v
// +---------------------------------+
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | x x x x x x x x x x x x x x x x |
// +---------------------------------+
// ^ ^ | |
// | | ... - - + |
// | + - - - - - - - - - - - - - +
// |
// start (address 0x00)
//
func (d *DeviceAdafruitCharlieWing15x7) enableLEDs() (err error) {
for frame := FRAME_0; frame <= FRAME_7; frame++ {
err = d.selectCommand(frame)
if err != nil {
return err
}
// Enable left half
for i := uint8(0); i < 16; i += 2 {
err = d.bus.WriteRegister(d.Address, i, []byte{0b11111110})
if err != nil {
return err
}
}
// Enable right half
for i := uint8(3); i < 16; i += 2 {
err = d.bus.WriteRegister(d.Address, i, []byte{0b01111111})
if err != nil {
return err
}
}
// Disable invisible column on the right side
err = d.bus.WriteRegister(d.Address, 1, []byte{0b00000000})
if err != nil {
return err
}
}
return nil
}
// DrawPixelXY draws a single pixel on the selected frame by its XY coordinates
// with provided PWM value [0-255]
func (d *DeviceAdafruitCharlieWing15x7) DrawPixelXY(frame, x, y, value uint8) (err error) {
var index uint8
if x >= 15 {
return fmt.Errorf("invalid value: X is out of range [0, 15]")
} else if y >= 7 {
return fmt.Errorf("invalid value: Y is out of range [0, 7]")
}
// Board is one pixel shorter (7 vs 8 supported pixels)
if x < 8 {
index = 16*x + y + 1
} else {
index = 16*(16-x) - y - 1 - 1
}
return d.setPixelPWD(frame, index, value)
}
// NewAdafruitCharlieWing15x7 creates a new driver with Adafruit 15x7
// CharliePlex LED Matrix FeatherWing (CharlieWing) layout.
// Available addresses:
// - 0x74 (default)
// - 0x77 (when the address jumper soldered)
func NewAdafruitCharlieWing15x7(bus drivers.I2C, address uint8) DeviceAdafruitCharlieWing15x7 {
return DeviceAdafruitCharlieWing15x7{
Device: Device{
Address: address,
bus: bus,
},
}
}
+52
View File
@@ -0,0 +1,52 @@
package is31fl3731
// Registers. Names taken from the datasheet:
// https://www.lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf
const (
// AD pin connected to GND
I2C_ADDRESS_74 uint8 = 0x74
// AD pin connected to SCL
I2C_ADDRESS_75 uint8 = 0x75
// AD pin connected to SDA
I2C_ADDRESS_76 uint8 = 0x76
// AD pin connected to VCC
I2C_ADDRESS_77 uint8 = 0x77
// Main command register
COMMAND uint8 = 0xFD
// Commands for each of 8 frames
FRAME_0 uint8 = 0x00
FRAME_1 uint8 = 0x01
FRAME_2 uint8 = 0x02
FRAME_3 uint8 = 0x03
FRAME_4 uint8 = 0x04
FRAME_5 uint8 = 0x05
FRAME_6 uint8 = 0x06
FRAME_7 uint8 = 0x07
// Command to set configuration
FUNCTION uint8 = 0x0B
// Configuration:
SET_DISPLAY_MODE uint8 = 0x00
SET_ACTIVE_FRAME uint8 = 0x01
SET_AUDIOSYNC uint8 = 0x06
SET_SHUTDOWN uint8 = 0x0A
// Configuration: display mode
DISPLAY_MODE_PICTURE uint8 = 0x00
// Configuration: audiosync (enable audio signal to modulate the intensity of
// the matrix)
AUDIOSYNC_OFF uint8 = 0x00
AUDIOSYNC_ON uint8 = 0x01
// Configuration: software shutdown
SOFTWARE_OFF uint8 = 0x00
SOFTWARE_ON uint8 = 0x01
// Frame LEDs
LED_CONTROL_OFFSET uint8 = 0x00 // to on/off each LED
LED_PWM_OFFSET uint8 = 0x24 // to set PWM (0-255) for each LED
)
+141
View File
@@ -0,0 +1,141 @@
package l3gd20
import (
"encoding/binary"
"time"
"tinygo.org/x/drivers"
)
const (
fifoLen = 32 * 3 * 2
)
type DevI2C struct {
addr uint8
// sensitivity or range.
mul int32
bus drivers.I2C
buf [1]byte
// gyro databuf.
databuf [6]byte
data [3]int32
}
func NewI2C(bus drivers.I2C, addr uint8) *DevI2C {
return &DevI2C{
addr: addr,
bus: bus,
mul: sensMul250,
}
}
// Initializes and configures the device.
func (d *DevI2C) Configure(cfg Config) error {
err := cfg.validate()
if err != nil {
return err
}
err = d.Reboot()
if err != nil {
return err
}
// Reset then switch to normal mode and enable all three channels.
err = d.write8(CTRL_REG1, 0)
if err != nil {
return err
}
err = d.write8(CTRL_REG1, reg1NormalBits)
if err != nil {
return err
}
// Reset REG2 values to default
err = d.write8(CTRL_REG3, 0)
if err != nil {
return err
}
// Set sensitivity
switch cfg.Range {
case 1: // debugging range
d.mul = 1
cfg.Range = Range_2000
case Range_250:
d.mul = sensMul250
case Range_500:
d.mul = sensMul500
case Range_2000:
d.mul = sensMul2000
default:
return ErrBadRange
}
err = d.write8(CTRL_REG4, cfg.Range)
if err != nil {
return err
}
// Finally verify whomai register and return error if
// board is not who it says it is. Some counterfeit boards
// have incorrect whomai but can still be used.
whoami, err := d.read8(WHOAMI)
if err != nil {
return err
}
if whoami != expectedWHOAMI && whoami != expectedWHOAMI_H {
return ErrBadIdentity
}
return nil
}
func (d *DevI2C) Update() error {
err := d.bus.ReadRegister(d.addr, OUT_X_L, d.databuf[:2])
if err != nil {
return err
}
err = d.bus.ReadRegister(d.addr, OUT_Y_L, d.databuf[2:4])
if err != nil {
return err
}
err = d.bus.ReadRegister(d.addr, OUT_Z_L, d.databuf[4:6])
if err != nil {
return err
}
x := int16(binary.LittleEndian.Uint16(d.databuf[0:]))
y := int16(binary.LittleEndian.Uint16(d.databuf[2:]))
z := int16(binary.LittleEndian.Uint16(d.databuf[4:]))
d.data[0] = d.mul * int32(x)
d.data[1] = d.mul * int32(y)
d.data[2] = d.mul * int32(z)
return nil
}
// Reboot sets reboot bit in CTRL_REG5 to true and unsets it.
func (d *DevI2C) Reboot() error {
reg5, err := d.read8(CTRL_REG5)
if err != nil {
return err
}
// Write reboot bit and then unset it.
err = d.write8(CTRL_REG5, reg5|reg5RebootBit)
if err != nil {
return err
}
time.Sleep(50 * time.Microsecond)
return d.write8(CTRL_REG5, reg5&^reg5RebootBit)
}
// AngularVelocity returns result in microradians per second.
func (d *DevI2C) AngularVelocity() (x, y, z int32) {
return d.data[0], d.data[1], d.data[2]
}
// func (d DevI2C) Update(measurement)
func (d DevI2C) read8(reg uint8) (byte, error) {
err := d.bus.ReadRegister(d.addr, reg, d.buf[:1])
return d.buf[0], err
}
func (d DevI2C) write8(reg uint8, val byte) error {
d.buf[0] = val
return d.bus.WriteRegister(d.addr, reg, d.buf[:1])
}
+49
View File
@@ -0,0 +1,49 @@
package l3gd20
import "errors"
var (
ErrBadIdentity = errors.New("got unexpected identity from WHOMAI")
ErrBadRange = errors.New("bad range configuration value")
)
// Sensitivity factors
const (
// range at bits 4-5
// 00 = 250 dps
// 01 = 500 dps
// 10 = 2000 dps
// 11 = 2000 dps
rangePos = 4
Range_250 = 0b00 << rangePos // 8.75 mdps/digit
Range_500 = 0b01 << rangePos // 17.5 mdps/digit
Range_2000 = 0b11 << rangePos // 70 mdps/digit
rangebits = Range_250 | Range_500 | Range_2000
// Sensitivities for degrees
sensDiv250dps = 800
sensDiv500dps = 400
sensDiv2000dps = 100
sens_250 = 7. / sensDiv250dps // Sensitivity at 250 dps
sens_500 = 7. / sensDiv500dps // Sensitivity at 500 dps
sens_2000 = 7. / sensDiv2000dps // Sensitivity at 500 dp
// 1e6*Pi/180. = 17453.292519943298 (constant for Degree to micro radians conversion)
// sensitivities for radians
sensMul250 = 7 * 1745329 / 100 / sensDiv250dps
sensMul500 = 7 * 1745329 / 100 / sensDiv500dps
sensMul2000 = 7 * 1745329 / 100 / sensDiv2000dps
)
type Config struct {
Range uint8
}
// validate scans config for invalid data and returns non-nil
// error indicating what data must be modified.
func (cfg *Config) validate() error {
if cfg.Range&^rangebits != 0 && cfg.Range != 1 {
return ErrBadRange
}
return nil
}
+99
View File
@@ -0,0 +1,99 @@
package l3gd20
// Expected identification number for L3GD20.
const (
// For L3GD20
expectedWHOAMI = 0xD4
// For L3GD20H
expectedWHOAMI_H = 0xD7
)
// Register bits masks
const (
reg5RebootBit = 1 << 7
reg5FIFOEnableBit = 1 << 6
reg1NormalBits = 0b1111
)
// Register addresses. Comments from https://github.com/adafruit/Adafruit_L3GD20_U/blob/master/Adafruit_L3GD20_U.cpp
const (
// The Slave ADdress (SAD) associated with the L3GD20 is 110101xb
I2CAddr = 0b1101011
// The SDO pin can be used to modify the less significant bit of the device address.
I2CAddrSDOLow = 0b1101010
WHOAMI uint8 = 0x0F
// CTRL_REG1 (0x20)
// ====================================================================
// BIT Symbol Description Default
// --- ------ --------------------------------------------- -------
// 7-6 DR1/0 Output data rate 00
// 5-4 BW1/0 Bandwidth selection 00
// 3 PD 0 = Power-down mode, 1 = normal/sleep mode 0
// 2 ZEN Z-axis enable (0 = disabled, 1 = enabled) 1
// 1 YEN Y-axis enable (0 = disabled, 1 = enabled) 1
// 0 XEN X-axis enable (0 = disabled, 1 = enabled) 1
CTRL_REG1 uint8 = 0x20
// Set CTRL_REG2 (0x21)
// ====================================================================
// BIT Symbol Description Default
// --- ------ --------------------------------------------- -------
// 5-4 HPM1/0 High-pass filter mode selection 00
// 3-0 HPCF3..0 High-pass filter cutoff frequency selection 0000
CTRL_REG2 uint8 = 0x21
// CTRL_REG3 (0x22)
// ====================================================================
// BIT Symbol Description Default
// --- ------ --------------------------------------------- -------
// 7 I1_Int1 Interrupt enable on INT1 (0=disable,1=enable) 0
// 6 I1_Boot Boot status on INT1 (0=disable,1=enable) 0
// 5 H-Lactive Interrupt active config on INT1 (0=high,1=low) 0
// 4 PP_OD Push-Pull/Open-Drain (0=PP, 1=OD) 0
// 3 I2_DRDY Data ready on DRDY/INT2 (0=disable,1=enable) 0
// 2 I2_WTM FIFO wtrmrk int on DRDY/INT2 (0=dsbl,1=enbl) 0
// 1 I2_ORun FIFO overrun int on DRDY/INT2 (0=dsbl,1=enbl) 0
// 0 I2_Empty FIFI empty int on DRDY/INT2 (0=dsbl,1=enbl) 0
CTRL_REG3 uint8 = 0x22
// CTRL_REG4 (0x23)
// ====================================================================
// BIT Symbol Description Default
// --- ------ --------------------------------------------- -------
// 7 BDU Block Data Update (0=continuous, 1=LSB/MSB) 0
// 6 BLE Big/Little-Endian (0=Data LSB, 1=Data MSB) 0
// 5-4 FS1/0 Full scale selection 00
// 00 = 250 dps
// 01 = 500 dps
// 10 = 2000 dps
// 11 = 2000 dps
// 0 SIM SPI Mode (0=4-wire, 1=3-wire) 0
CTRL_REG4 uint8 = 0x23
// CTRL_REG5 (0x24)
// ====================================================================
// BIT Symbol Description Default
// --- ------ --------------------------------------------- -------
// 7 BOOT Reboot memory content (0=normal, 1=reboot) 0
// 6 FIFO_EN FIFO enable (0=FIFO disable, 1=enable) 0
// 4 HPen High-pass filter enable (0=disable,1=enable) 0
// 3-2 INT1_SEL INT1 Selection config 00
// 1-0 OUT_SEL Out selection config 00
CTRL_REG5 uint8 = 0x24
REFERENCE uint8 = 0x25
OUT_TEMP uint8 = 0x26
STATUS_REG uint8 = 0x27
OUT_X_L uint8 = 0x28
OUT_X_H uint8 = 0x29
OUT_Y_L uint8 = 0x2A
OUT_Y_H uint8 = 0x2B
OUT_Z_L uint8 = 0x2C
OUT_Z_H uint8 = 0x2D
FIFO_CTRL_REG uint8 = 0x2E
FIFO_SRC_REG uint8 = 0x2F
INT1_CFG uint8 = 0x30
INT1_SRC uint8 = 0x31
INT1_TSH_XH uint8 = 0x32
INT1_TSH_XL uint8 = 0x33
INT1_TSH_YH uint8 = 0x34
INT1_TSH_YL uint8 = 0x35
INT1_TSH_ZH uint8 = 0x36
INT1_TSH_ZL uint8 = 0x37
INT1_DURATION uint8 = 0x38
)
+2 -2
View File
@@ -11,8 +11,8 @@ import (
// Configure sets up the LPS22HB device for communication.
func (d *Device) Configure() {
// Following lines are Nano 33 BLE specific, they have nothing to do with sensor per se
machine.LSP_PWR.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.LSP_PWR.High()
machine.LPS_PWR.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.LPS_PWR.High()
machine.I2C_PULLUP.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.I2C_PULLUP.High()
// Wait a moment
+77 -50
View File
@@ -6,6 +6,7 @@
package lsm303agr // import "tinygo.org/x/drivers/lsm303agr"
import (
"errors"
"math"
"tinygo.org/x/drivers"
@@ -22,6 +23,7 @@ type Device struct {
MagPowerMode uint8
MagSystemMode uint8
MagDataRate uint8
buf [6]uint8
}
// Configuration for LSM303AGR device.
@@ -34,12 +36,17 @@ type Configuration struct {
MagDataRate uint8
}
// New creates a new LSM303AGR connection. The I2C bus must already be
// configured.
var errNotConnected = errors.New("lsm303agr: failed to communicate with either acel or magnet sensor")
// New creates a new LSM303AGR connection. The I2C bus must already be configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) Device {
return Device{bus: bus, AccelAddress: ACCEL_ADDRESS, MagAddress: MAG_ADDRESS}
func New(bus drivers.I2C) *Device {
return &Device{
bus: bus,
AccelAddress: ACCEL_ADDRESS,
MagAddress: MAG_ADDRESS,
}
}
// Connected returns whether both sensor on LSM303AGR has been found.
@@ -52,7 +59,12 @@ func (d *Device) Connected() bool {
}
// Configure sets up the LSM303AGR device for communication.
func (d *Device) Configure(cfg Configuration) {
func (d *Device) Configure(cfg Configuration) (err error) {
// Verify unit communication
if !d.Connected() {
return errNotConnected
}
if cfg.AccelDataRate != 0 {
d.AccelDataRate = cfg.AccelDataRate
@@ -90,36 +102,46 @@ func (d *Device) Configure(cfg Configuration) {
d.MagSystemMode = MAG_SYSTEM_CONTINUOUS
}
cmd := []byte{0}
data := d.buf[:1]
cmd[0] = byte(d.AccelDataRate<<4 | d.AccelPowerMode | 0x07)
d.bus.WriteRegister(uint8(d.AccelAddress), ACCEL_CTRL_REG1_A, cmd)
data[0] = byte(d.AccelDataRate<<4 | d.AccelPowerMode | 0x07)
err = d.bus.WriteRegister(uint8(d.AccelAddress), ACCEL_CTRL_REG1_A, data)
if err != nil {
return
}
cmd[0] = byte(0x80 | d.AccelRange<<4)
d.bus.WriteRegister(uint8(d.AccelAddress), ACCEL_CTRL_REG4_A, cmd)
data[0] = byte(0x80 | d.AccelRange<<4)
err = d.bus.WriteRegister(uint8(d.AccelAddress), ACCEL_CTRL_REG4_A, data)
if err != nil {
return
}
cmd[0] = byte(0xC0)
d.bus.WriteRegister(uint8(d.AccelAddress), TEMP_CFG_REG_A, cmd)
data[0] = byte(0xC0)
err = d.bus.WriteRegister(uint8(d.AccelAddress), TEMP_CFG_REG_A, data)
if err != nil {
return
}
// Temperature compensation is on for magnetic sensor
cmd[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
d.bus.WriteRegister(uint8(d.MagAddress), MAG_MR_REG_M, cmd)
data[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
err = d.bus.WriteRegister(uint8(d.MagAddress), MAG_MR_REG_M, data)
if err != nil {
return
}
return nil
}
// ReadAcceleration reads the current acceleration from the device and returns
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x int32, y int32, z int32) {
data1, data2, data3, data4, data5, data6 := []byte{0}, []byte{0}, []byte{0}, []byte{0}, []byte{0}, []byte{0}
d.bus.ReadRegister(uint8(d.AccelAddress), ACCEL_OUT_X_H_A, data1)
d.bus.ReadRegister(uint8(d.AccelAddress), ACCEL_OUT_X_L_A, data2)
d.bus.ReadRegister(uint8(d.AccelAddress), ACCEL_OUT_Y_H_A, data3)
d.bus.ReadRegister(uint8(d.AccelAddress), ACCEL_OUT_Y_L_A, data4)
d.bus.ReadRegister(uint8(d.AccelAddress), ACCEL_OUT_Z_H_A, data5)
d.bus.ReadRegister(uint8(d.AccelAddress), ACCEL_OUT_Z_L_A, data6)
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
data := d.buf[:6]
err = d.bus.ReadRegister(uint8(d.AccelAddress), ACCEL_OUT_X_L_A, data)
if err != nil {
return
}
rangeFactor := int16(0)
switch d.AccelRange {
@@ -133,18 +155,21 @@ func (d *Device) ReadAcceleration() (x int32, y int32, z int32) {
rangeFactor = 12 // the readings in 16G are a bit lower
}
x = int32(int32(int16((uint16(data1[0])<<8|uint16(data2[0])))>>4*rangeFactor) * 1000000 / 1024)
y = int32(int32(int16((uint16(data3[0])<<8|uint16(data4[0])))>>4*rangeFactor) * 1000000 / 1024)
z = int32(int32(int16((uint16(data5[0])<<8|uint16(data6[0])))>>4*rangeFactor) * 1000000 / 1024)
x = int32(int32(int16((uint16(data[1])<<8|uint16(data[0])))>>4*rangeFactor) * 1000000 / 1024)
y = int32(int32(int16((uint16(data[3])<<8|uint16(data[2])))>>4*rangeFactor) * 1000000 / 1024)
z = int32(int32(int16((uint16(data[5])<<8|uint16(data[4])))>>4*rangeFactor) * 1000000 / 1024)
return
}
// ReadPitchRoll reads the current pitch and roll angles from the device and
// returns it in micro-degrees. When the z axis is pointing straight to Earth
// the returned values of pitch and roll would be zero.
func (d *Device) ReadPitchRoll() (pitch int32, roll int32) {
func (d *Device) ReadPitchRoll() (pitch, roll int32, err error) {
x, y, z := d.ReadAcceleration()
x, y, z, err := d.ReadAcceleration()
if err != nil {
return
}
xf, yf, zf := float64(x), float64(y), float64(z)
pitch = int32((math.Round(math.Atan2(yf, math.Sqrt(math.Pow(xf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000)
roll = int32((math.Round(math.Atan2(xf, math.Sqrt(math.Pow(yf, 2)+math.Pow(zf, 2)))*(180/math.Pi)*100) / 100) * 1000000)
@@ -154,25 +179,23 @@ func (d *Device) ReadPitchRoll() (pitch int32, roll int32) {
// ReadMagneticField reads the current magnetic field from the device and returns
// it in mG (milligauss). 1 mG = 0.1 µT (microtesla).
func (d *Device) ReadMagneticField() (x int32, y int32, z int32) {
func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
if d.MagSystemMode == MAG_SYSTEM_SINGLE {
cmd := []byte{0}
cmd := d.buf[:1]
cmd[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
d.bus.WriteRegister(uint8(d.MagAddress), MAG_MR_REG_M, cmd)
err = d.bus.WriteRegister(uint8(d.MagAddress), MAG_MR_REG_M, cmd)
if err != nil {
return
}
}
data1, data2, data3, data4, data5, data6 := []byte{0}, []byte{0}, []byte{0}, []byte{0}, []byte{0}, []byte{0}
d.bus.ReadRegister(uint8(d.MagAddress), MAG_OUT_X_H_M, data1)
d.bus.ReadRegister(uint8(d.MagAddress), MAG_OUT_X_L_M, data2)
d.bus.ReadRegister(uint8(d.MagAddress), MAG_OUT_Y_H_M, data3)
d.bus.ReadRegister(uint8(d.MagAddress), MAG_OUT_Y_L_M, data4)
d.bus.ReadRegister(uint8(d.MagAddress), MAG_OUT_Z_H_M, data5)
d.bus.ReadRegister(uint8(d.MagAddress), MAG_OUT_Z_L_M, data6)
data := d.buf[0:6]
d.bus.ReadRegister(uint8(d.MagAddress), MAG_OUT_X_L_M, data)
x = int32(int16((uint16(data1[0])<<8 | uint16(data2[0]))))
y = int32(int16((uint16(data3[0])<<8 | uint16(data4[0]))))
z = int32(int16((uint16(data5[0])<<8 | uint16(data6[0]))))
x = int32(int16((uint16(data[1])<<8 | uint16(data[0]))))
y = int32(int16((uint16(data[3])<<8 | uint16(data[2]))))
z = int32(int16((uint16(data[5])<<8 | uint16(data[4]))))
return
}
@@ -182,23 +205,27 @@ func (d *Device) ReadMagneticField() (x int32, y int32, z int32) {
//
// However, the heading may be off due to electronic compasses would be effected
// by strong magnetic fields and require constant calibration.
func (d *Device) ReadCompass() (h int32) {
func (d *Device) ReadCompass() (h int32, err error) {
x, y, _ := d.ReadMagneticField()
x, y, _, err := d.ReadMagneticField()
if err != nil {
return
}
xf, yf := float64(x), float64(y)
h = int32(float32((180/math.Pi)*math.Atan2(yf, xf)) * 1000000)
return
}
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (c int32, e error) {
func (d *Device) ReadTemperature() (t int32, err error) {
data1, data2 := []byte{0}, []byte{0}
d.bus.ReadRegister(uint8(d.AccelAddress), OUT_TEMP_H_A, data1)
d.bus.ReadRegister(uint8(d.AccelAddress), OUT_TEMP_L_A, data2)
data := d.buf[:2]
err = d.bus.ReadRegister(uint8(d.AccelAddress), OUT_TEMP_L_A, data)
if err != nil {
return
}
t := int16((uint16(data1[0])<<8 | uint16(data2[0]))) >> 4 // temperature offsef from 25 °C
c = int32((float32(25) + float32(t)/8) * 1000)
e = nil
r := int16((uint16(data[1])<<8 | uint16(data[0]))) >> 4 // temperature offset from 25 °C
t = 25000 + int32((float32(r)/8)*1000)
return
}
+91 -39
View File
@@ -5,7 +5,11 @@
//
package lsm6ds3 // import "tinygo.org/x/drivers/lsm6ds3"
import "tinygo.org/x/drivers"
import (
"errors"
"tinygo.org/x/drivers"
)
type AccelRange uint8
type AccelSampleRate uint8
@@ -23,8 +27,7 @@ type Device struct {
accelBandWidth AccelBandwidth
gyroRange GyroRange
gyroSampleRate GyroSampleRate
dataBufferSix []uint8
dataBufferTwo []uint8
buf [6]uint8
}
// Configuration for LSM6DS3 device.
@@ -38,16 +41,26 @@ type Configuration struct {
ResetStepCounter bool
}
// New creates a new LSM6DS3 connection. The I2C bus must already be
// configured.
var errNotConnected = errors.New("lsm6ds3: failed to communicate with acel/gyro sensor")
// New creates a new LSM6DS3 connection. The I2C bus must already be configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) Device {
return Device{bus: bus, Address: Address}
func New(bus drivers.I2C) *Device {
return &Device{
bus: bus,
Address: Address,
}
}
// Configure sets up the device for communication.
func (d *Device) Configure(cfg Configuration) {
func (d *Device) Configure(cfg Configuration) (err error) {
// Verify unit communication
if !d.Connected() {
return errNotConnected
}
if cfg.AccelRange != 0 {
d.accelRange = cfg.AccelRange
} else {
@@ -78,44 +91,67 @@ func (d *Device) Configure(cfg Configuration) {
d.gyroSampleRate = GYRO_SR_104
}
d.dataBufferSix = make([]uint8, 6)
d.dataBufferTwo = make([]uint8, 2)
data := d.buf[:1]
if cfg.IsPedometer { // CONFIGURE AS PEDOMETER
// Configure accelerometer: 2G + 26Hz
d.bus.WriteRegister(uint8(d.Address), CTRL1_XL, []byte{uint8(ACCEL_2G) | uint8(ACCEL_SR_26)})
data[0] = uint8(ACCEL_2G) | uint8(ACCEL_SR_26)
err = d.bus.WriteRegister(uint8(d.Address), CTRL1_XL, data)
if err != nil {
return
}
// Configure Zen_G, Yen_G, Xen_G, reset steps
data[0] = 0x3C
if cfg.ResetStepCounter {
d.bus.WriteRegister(uint8(d.Address), CTRL10_C, []byte{0x3E})
} else {
d.bus.WriteRegister(uint8(d.Address), CTRL10_C, []byte{0x3C})
data[0] |= 0x02
}
err = d.bus.WriteRegister(uint8(d.Address), CTRL10_C, data)
if err != nil {
return
}
// Enable pedometer
d.bus.WriteRegister(uint8(d.Address), TAP_CFG, []byte{0x40})
data[0] = 0x40
err = d.bus.WriteRegister(uint8(d.Address), TAP_CFG, data)
if err != nil {
return
}
} else { // NORMAL USE
// Configure accelerometer
data := make([]uint8, 1)
data[0] = uint8(d.accelRange) | uint8(d.accelSampleRate) | uint8(d.accelBandWidth)
d.bus.WriteRegister(uint8(d.Address), CTRL1_XL, data)
err = d.bus.WriteRegister(uint8(d.Address), CTRL1_XL, data)
if err != nil {
return
}
// Set ODR bit
d.bus.ReadRegister(uint8(d.Address), CTRL4_C, data)
err = d.bus.ReadRegister(uint8(d.Address), CTRL4_C, data)
if err != nil {
return
}
data[0] = data[0] &^ BW_SCAL_ODR_ENABLED
data[0] |= BW_SCAL_ODR_ENABLED
d.bus.WriteRegister(uint8(d.Address), CTRL4_C, data)
err = d.bus.WriteRegister(uint8(d.Address), CTRL4_C, data)
if err != nil {
return
}
// Configure gyroscope
data[0] = uint8(d.gyroRange) | uint8(d.gyroSampleRate)
d.bus.WriteRegister(uint8(d.Address), CTRL2_G, data)
err = d.bus.WriteRegister(uint8(d.Address), CTRL2_G, data)
if err != nil {
return
}
}
return nil
}
// Connected returns whether a LSM6DS3 has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
data := d.buf[:1]
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
return data[0] == 0x69
}
@@ -124,8 +160,12 @@ func (d *Device) Connected() bool {
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x int32, y int32, z int32) {
d.bus.ReadRegister(uint8(d.Address), OUTX_L_XL, d.dataBufferSix)
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
data := d.buf[:6]
err = d.bus.ReadRegister(uint8(d.Address), OUTX_L_XL, data)
if err != nil {
return
}
// k comes from "Table 3. Mechanical characteristics" 3 of the datasheet * 1000
k := int32(61) // 2G
if d.accelRange == ACCEL_4G {
@@ -135,9 +175,9 @@ func (d *Device) ReadAcceleration() (x int32, y int32, z int32) {
} else if d.accelRange == ACCEL_16G {
k = 488
}
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * k
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * k
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * k
x = int32(int16((uint16(data[1])<<8)|uint16(data[0]))) * k
y = int32(int16((uint16(data[3])<<8)|uint16(data[2]))) * k
z = int32(int16((uint16(data[5])<<8)|uint16(data[4]))) * k
return
}
@@ -145,8 +185,12 @@ func (d *Device) ReadAcceleration() (x int32, y int32, z int32) {
// µ°/s (micro-degrees/sec). This means that if you were to do a complete
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *Device) ReadRotation() (x int32, y int32, z int32) {
d.bus.ReadRegister(uint8(d.Address), OUTX_L_G, d.dataBufferSix)
func (d *Device) ReadRotation() (x, y, z int32, err error) {
data := d.buf[:6]
err = d.bus.ReadRegister(uint8(d.Address), OUTX_L_G, data)
if err != nil {
return
}
// k comes from "Table 3. Mechanical characteristics" 3 of the datasheet * 1000
k := int32(4375) // 125DPS
if d.gyroRange == GYRO_250DPS {
@@ -158,24 +202,32 @@ func (d *Device) ReadRotation() (x int32, y int32, z int32) {
} else if d.gyroRange == GYRO_2000DPS {
k = 70000
}
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * k
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * k
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * k
x = int32(int16((uint16(data[1])<<8)|uint16(data[0]))) * k
y = int32(int16((uint16(data[3])<<8)|uint16(data[2]))) * k
z = int32(int16((uint16(data[5])<<8)|uint16(data[4]))) * k
return
}
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (int32, error) {
d.bus.ReadRegister(uint8(d.Address), OUT_TEMP_L, d.dataBufferTwo)
func (d *Device) ReadTemperature() (t int32, err error) {
data := d.buf[:2]
err = d.bus.ReadRegister(uint8(d.Address), OUT_TEMP_L, data)
if err != nil {
return
}
// From "Table 5. Temperature sensor characteristics"
// temp = value/16 + 25
t := 25000 + (int32(int16((int16(d.dataBufferTwo[1])<<8)|int16(d.dataBufferTwo[0])))*125)/2
return t, nil
t = 25000 + (int32(int16((int16(data[1])<<8)|int16(data[0])))*125)/2
return
}
// ReadSteps returns the steps of the pedometer
func (d *Device) ReadSteps() int32 {
d.bus.ReadRegister(uint8(d.Address), STEP_COUNTER_L, d.dataBufferTwo)
return int32(int16((uint16(d.dataBufferTwo[1]) << 8) | uint16(d.dataBufferTwo[0])))
func (d *Device) ReadSteps() (s int32, err error) {
data := d.buf[:2]
err = d.bus.ReadRegister(uint8(d.Address), STEP_COUNTER_L, data)
if err != nil {
return
}
s = int32(int16((uint16(data[1]) << 8) | uint16(data[0])))
return
}
+54 -27
View File
@@ -5,7 +5,11 @@
//
package lsm6dsox // import "tinygo.org/x/drivers/lsm6dsox"
import "tinygo.org/x/drivers"
import (
"errors"
"tinygo.org/x/drivers"
)
type AccelRange uint8
type AccelSampleRate uint8
@@ -17,10 +21,9 @@ type GyroSampleRate uint8
type Device struct {
bus drivers.I2C
Address uint16
dataBufferSix []uint8
dataBufferTwo []uint8
accelMultiplier int32
gyroMultiplier int32
buf [6]uint8
}
// Configuration for LSM6DSOX device.
@@ -31,20 +34,25 @@ type Configuration struct {
GyroSampleRate GyroSampleRate
}
var errNotConnected = errors.New("lsm6dsox: failed to communicate with acel/gyro sensor")
// New creates a new LSM6DSOX connection. The I2C bus must already be configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) *Device {
return &Device{
bus: bus,
Address: Address,
dataBufferSix: make([]uint8, 6),
dataBufferTwo: make([]uint8, 2),
bus: bus,
Address: Address,
}
}
// Configure sets up the device for communication.
func (d *Device) Configure(cfg Configuration) {
func (d *Device) Configure(cfg Configuration) (err error) {
// Verify unit communication
if !d.Connected() {
return errNotConnected
}
// Multipliers come from "Table 2. Mechanical characteristics" of the datasheet * 1000
switch cfg.AccelRange {
@@ -68,19 +76,27 @@ func (d *Device) Configure(cfg Configuration) {
d.gyroMultiplier = 70000
}
data := make([]uint8, 1)
data := d.buf[:1]
// Configure accelerometer
data[0] = uint8(cfg.AccelRange) | uint8(cfg.AccelSampleRate)
d.bus.WriteRegister(uint8(d.Address), CTRL1_XL, data)
err = d.bus.WriteRegister(uint8(d.Address), CTRL1_XL, data)
if err != nil {
return
}
// Configure gyroscope
data[0] = uint8(cfg.GyroRange) | uint8(cfg.GyroSampleRate)
d.bus.WriteRegister(uint8(d.Address), CTRL2_G, data)
err = d.bus.WriteRegister(uint8(d.Address), CTRL2_G, data)
if err != nil {
return
}
return nil
}
// Connected returns whether a LSM6DSOX has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
data := d.buf[:1]
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
return data[0] == 0x6C
}
@@ -89,11 +105,15 @@ func (d *Device) Connected() bool {
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x int32, y int32, z int32) {
d.bus.ReadRegister(uint8(d.Address), OUTX_L_A, d.dataBufferSix)
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * d.accelMultiplier
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * d.accelMultiplier
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * d.accelMultiplier
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
data := d.buf[:6]
err = d.bus.ReadRegister(uint8(d.Address), OUTX_L_A, data)
if err != nil {
return
}
x = int32(int16((uint16(data[1])<<8)|uint16(data[0]))) * d.accelMultiplier
y = int32(int16((uint16(data[3])<<8)|uint16(data[2]))) * d.accelMultiplier
z = int32(int16((uint16(data[5])<<8)|uint16(data[4]))) * d.accelMultiplier
return
}
@@ -101,20 +121,27 @@ func (d *Device) ReadAcceleration() (x int32, y int32, z int32) {
// µ°/s (micro-degrees/sec). This means that if you were to do a complete
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *Device) ReadRotation() (x int32, y int32, z int32) {
d.bus.ReadRegister(uint8(d.Address), OUTX_L_G, d.dataBufferSix)
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * d.gyroMultiplier
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * d.gyroMultiplier
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * d.gyroMultiplier
func (d *Device) ReadRotation() (x, y, z int32, err error) {
data := d.buf[:6]
err = d.bus.ReadRegister(uint8(d.Address), OUTX_L_G, data)
if err != nil {
return
}
x = int32(int16((uint16(data[1])<<8)|uint16(data[0]))) * d.gyroMultiplier
y = int32(int16((uint16(data[3])<<8)|uint16(data[2]))) * d.gyroMultiplier
z = int32(int16((uint16(data[5])<<8)|uint16(data[4]))) * d.gyroMultiplier
return
}
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (int32, error) {
d.bus.ReadRegister(uint8(d.Address), OUT_TEMP_L, d.dataBufferTwo)
func (d *Device) ReadTemperature() (t int32, err error) {
data := d.buf[:2]
err = d.bus.ReadRegister(uint8(d.Address), OUT_TEMP_L, data)
if err != nil {
return
}
// From "Table 4. Temperature sensor characteristics"
// temp = value/256 + 25
t := 25000 + (int32(int16((int16(d.dataBufferTwo[1])<<8)|int16(d.dataBufferTwo[0])))*125)/32
return t, nil
t = 25000 + (int32(int16((int16(data[1])<<8)|int16(data[0])))*125)/32
return
}
+29 -34
View File
@@ -28,8 +28,7 @@ type Device struct {
accelMultiplier int32
gyroMultiplier int32
magMultiplier int32
dataBufferSix []uint8
dataBufferTwo []uint8
buf [6]uint8
}
// Configuration for LSM9DS1 device.
@@ -50,11 +49,9 @@ var errNotConnected = errors.New("lsm9ds1: failed to communicate with either ace
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) *Device {
return &Device{
bus: bus,
AccelAddress: ACCEL_ADDRESS,
MagAddress: MAG_ADDRESS,
dataBufferSix: make([]uint8, 6),
dataBufferTwo: make([]uint8, 2),
bus: bus,
AccelAddress: ACCEL_ADDRESS,
MagAddress: MAG_ADDRESS,
}
}
@@ -63,17 +60,11 @@ func New(bus drivers.I2C) *Device {
// In a rare case of an I2C bus issue, it can also return an error.
// Case of boolean false and error nil means I2C is up,
// but "who am I" responses have unexpected values.
func (d *Device) Connected() (connected bool, err error) {
data1, data2 := []byte{0}, []byte{0}
err = d.bus.ReadRegister(d.AccelAddress, WHO_AM_I, data1)
if err != nil {
return false, err
}
err = d.bus.ReadRegister(d.MagAddress, WHO_AM_I_M, data2)
if err != nil {
return false, err
}
return data1[0] == 0x68 && data2[0] == 0x3D, nil
func (d *Device) Connected() bool {
data1, data2 := d.buf[:1], d.buf[1:2]
d.bus.ReadRegister(d.AccelAddress, WHO_AM_I, data1)
d.bus.ReadRegister(d.MagAddress, WHO_AM_I_M, data2)
return data1[0] == 0x68 && data2[0] == 0x3D
}
// ReadAcceleration reads the current acceleration from the device and returns
@@ -81,13 +72,14 @@ func (d *Device) Connected() (connected bool, err error) {
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
err = d.bus.ReadRegister(uint8(d.AccelAddress), OUT_X_L_XL, d.dataBufferSix)
data := d.buf[:6]
err = d.bus.ReadRegister(uint8(d.AccelAddress), OUT_X_L_XL, data)
if err != nil {
return
}
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * d.accelMultiplier
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * d.accelMultiplier
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * d.accelMultiplier
x = int32(int16((uint16(data[1])<<8)|uint16(data[0]))) * d.accelMultiplier
y = int32(int16((uint16(data[3])<<8)|uint16(data[2]))) * d.accelMultiplier
z = int32(int16((uint16(data[5])<<8)|uint16(data[4]))) * d.accelMultiplier
return
}
@@ -96,38 +88,41 @@ func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *Device) ReadRotation() (x, y, z int32, err error) {
err = d.bus.ReadRegister(uint8(d.AccelAddress), OUT_X_L_G, d.dataBufferSix)
data := d.buf[:6]
err = d.bus.ReadRegister(uint8(d.AccelAddress), OUT_X_L_G, data)
if err != nil {
return
}
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * d.gyroMultiplier
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * d.gyroMultiplier
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * d.gyroMultiplier
x = int32(int16((uint16(data[1])<<8)|uint16(data[0]))) * d.gyroMultiplier
y = int32(int16((uint16(data[3])<<8)|uint16(data[2]))) * d.gyroMultiplier
z = int32(int16((uint16(data[5])<<8)|uint16(data[4]))) * d.gyroMultiplier
return
}
// ReadMagneticField reads the current magnetic field from the device and returns
// it in nT (nanotesla). 1 G (gauss) = 100_000 nT (nanotesla).
func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
err = d.bus.ReadRegister(uint8(d.MagAddress), OUT_X_L_M, d.dataBufferSix)
data := d.buf[:6]
err = d.bus.ReadRegister(uint8(d.MagAddress), OUT_X_L_M, data)
if err != nil {
return
}
x = int32(int16((int16(d.dataBufferSix[1])<<8)|int16(d.dataBufferSix[0]))) * d.magMultiplier
y = int32(int16((int16(d.dataBufferSix[3])<<8)|int16(d.dataBufferSix[2]))) * d.magMultiplier
z = int32(int16((int16(d.dataBufferSix[5])<<8)|int16(d.dataBufferSix[4]))) * d.magMultiplier
x = int32(int16((int16(data[1])<<8)|int16(data[0]))) * d.magMultiplier
y = int32(int16((int16(data[3])<<8)|int16(data[2]))) * d.magMultiplier
z = int32(int16((int16(data[5])<<8)|int16(data[4]))) * d.magMultiplier
return
}
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (t int32, err error) {
err = d.bus.ReadRegister(uint8(d.AccelAddress), OUT_TEMP_L, d.dataBufferTwo)
data := d.buf[:2]
err = d.bus.ReadRegister(uint8(d.AccelAddress), OUT_TEMP_L, data)
if err != nil {
return
}
// From "Table 5. Temperature sensor characteristics"
// temp = value/16 + 25
t = 25000 + (int32(int16((int16(d.dataBufferTwo[1])<<8)|int16(d.dataBufferTwo[0])))*125)/2
t = 25000 + (int32(int16((int16(data[1])<<8)|int16(data[0])))*125)/2
return
}
@@ -138,7 +133,7 @@ func (d *Device) ReadTemperature() (t int32, err error) {
func (d *Device) doConfigure(cfg Configuration) (err error) {
// Verify unit communication
if con, err := d.Connected(); !con || err != nil {
if !d.Connected() {
return errNotConnected
}
@@ -172,7 +167,7 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
d.magMultiplier = 58
}
data := make([]byte, 1)
data := d.buf[:1]
// Configure accelerometer
// Sample rate & measurement range
+1 -1
View File
@@ -19,7 +19,7 @@ func (d *Device) Configure(cfg Configuration) error {
machine.I2C_PULLUP.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.I2C_PULLUP.High()
// Wait a moment
time.Sleep(10 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
// Common initialisation code
return d.doConfigure(cfg)
}
+10 -1
View File
@@ -31,7 +31,16 @@ func (driver *Device) Configure() {
// SetScanLimit sets the scan limit. Maximum is 8.
// Example: a 4 digit 7SegmentDisplay has a scan limit of 4
func (driver *Device) SetScanLimit(digitNumber uint8) {
driver.WriteCommand(byte(REG_SCANLIMIT), byte(digitNumber-1))
driver.WriteCommand(REG_SCANLIMIT, digitNumber-1)
}
// SetIntensity sets the intensity of the diplays.
// There are 16 possible intensity levels. The valid range is 0x00-0x0F
func (driver *Device) SetIntensity(intensity uint8) {
if intensity > 0x0F {
intensity = 0x0F
}
driver.WriteCommand(REG_INTENSITY, intensity)
}
// SetDecodeMode sets the decode mode for 7 segment displays.
+3 -7
View File
@@ -89,18 +89,14 @@ func (d Dev) Top() uint32 {
}
// Set sets the `on` value of a PWM channel in the range [0..15].
// Max value `on` can take is 4095.
// Example:
// d.Set(1, d.Top()/4)
// sets the dutycycle of second (LED1) channel to 25%.
func (d Dev) Set(channel uint8, on uint32) {
switch {
case on > maxtop:
panic("pca9685: value must be in range 0..4096")
case on == 0:
d.SetPhased(channel, 0, maxtop)
return
if on > maxtop {
panic("pca9685: value must be in range 0..4095")
}
d.SetPhased(channel, on, 0)
}
+53
View File
@@ -0,0 +1,53 @@
package pca9685
import (
"encoding/binary"
"tinygo.org/x/drivers"
)
// 16 PWM channels, 2 2 byte values each (on, off 16bits)
const buffLen = 16 * 2 * 2
// DevBuffered provides a way of performing one-shot writes
// on all PWM signals. This is useful when working with systems
// which require as little as possible I/O overhead.
type DevBuffered struct {
Dev
// LED buffer, first value is address, following values correspond to LED registers.
// [0]: LEDSTART register address
// [1:5]: LED0 corresponding to PWM channel 0
// [5:9]: LED1 PWM channel 1
// ...
// [1 + N*4 : 1 + N*4 + 4] : channnel N up to channel 15
ledBuf [buffLen + 1]byte
}
// New creates a new instance of a PCA9685 device. It performs
// no IO on the i2c bus.
func NewBuffered(bus drivers.I2C, addr uint8) *DevBuffered {
db := &DevBuffered{
Dev: New(bus, addr),
}
db.ledBuf[0] = LEDSTART
return db
}
// PrepSet prepares a value to be written to the
// channel's PWM register on Update() call.
func (b *DevBuffered) PrepSet(channel uint8, on uint32) {
b.PrepPhasedSet(channel, on, 0)
}
// PrepPhasedSet prepares a phased PWM value to be written to the
// channel's register on Update() call.
func (b *DevBuffered) PrepPhasedSet(channel uint8, on, off uint32) {
onLReg := 1 + channel*4
binary.LittleEndian.PutUint16(b.ledBuf[onLReg:], uint16(on)&maxtop)
binary.LittleEndian.PutUint16(b.ledBuf[onLReg+2:], uint16(off)&maxtop)
}
// Update writes the prepared values to the PWM device registers in one shot.
func (b *DevBuffered) Update() error {
return b.bus.Tx(uint16(b.addr), b.ledBuf[:], nil)
}
+37
View File
@@ -0,0 +1,37 @@
package ssd1289
import "machine"
type pinBus struct {
pins [16]machine.Pin
}
func NewPinBus(pins [16]machine.Pin) pinBus {
for i := 0; i < 16; i++ {
pins[i].Configure(machine.PinConfig{Mode: machine.PinOutput})
}
return pinBus{
pins: pins,
}
}
func (b pinBus) Set(data uint16) {
b.pins[15].Set((data & (1 << 15)) != 0)
b.pins[14].Set((data & (1 << 14)) != 0)
b.pins[13].Set((data & (1 << 13)) != 0)
b.pins[12].Set((data & (1 << 12)) != 0)
b.pins[11].Set((data & (1 << 11)) != 0)
b.pins[10].Set((data & (1 << 10)) != 0)
b.pins[9].Set((data & (1 << 9)) != 0)
b.pins[8].Set((data & (1 << 8)) != 0)
b.pins[7].Set((data & (1 << 7)) != 0)
b.pins[6].Set((data & (1 << 6)) != 0)
b.pins[5].Set((data & (1 << 5)) != 0)
b.pins[4].Set((data & (1 << 4)) != 0)
b.pins[3].Set((data & (1 << 3)) != 0)
b.pins[2].Set((data & (1 << 2)) != 0)
b.pins[1].Set((data & (1 << 1)) != 0)
b.pins[0].Set((data & (1 << 0)) != 0)
}
+23
View File
@@ -0,0 +1,23 @@
package ssd1289
type Command byte
const (
OSCILLATIONSTART Command = 0x00
DRIVEROUTPUTCONTROL = 0x01
POWERCONTROL1 = 0x03
POWERCONTROL2 = 0x0C
POWERCONTROL3 = 0x0D
POWERCONTROL4 = 0x0E
POWERCONTROL5 = 0x1E
DISPLAYCONTROL = 0x07
SLEEPMODE = 0x10
ENTRYMODE = 0x11
LCDDRIVEACCONTROL = 0x02
HORIZONTALRAMADDRESSPOSITION = 0x44
VERTICALRAMADDRESSSTARTPOSITION = 0x45
VERTICALRAMADDRESSENDPOSITION = 0x46
SETGDDRAMYADDRESSCOUNTER = 0x4F
SETGDDRAMXADDRESSCOUNTER = 0x4E
RAMDATAREADWRITE = 0x22
)
+32
View File
@@ -0,0 +1,32 @@
//go:build rp2040
// +build rp2040
package ssd1289
import (
"device/rp"
"machine"
)
type rp2040Bus struct {
firstPin machine.Pin
}
func NewRP2040Bus(firstPin machine.Pin) rp2040Bus {
for i := uint8(0); i < 16; i++ {
pin := machine.Pin(i + uint8(firstPin))
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
return rp2040Bus{
firstPin: firstPin,
}
}
func (b rp2040Bus) Set(data uint16) {
data32 := uint32(data)
rp.SIO.GPIO_OUT_CLR.Set(0xFFFF << b.firstPin)
rp.SIO.GPIO_OUT_SET.Set(data32 << b.firstPin)
}
+200
View File
@@ -0,0 +1,200 @@
// Package ssd1289 implements a driver for the SSD1289 led matrix controller as packaged on the TFT_320QVT board
//
// Datasheet: http://aitendo3.sakura.ne.jp/aitendo_data/product_img/lcd/tft2/M032C1289TP/3.2-SSD1289.pdf
//
package ssd1289
import (
"image/color"
"machine"
"time"
)
type Bus interface {
Set(data uint16)
}
type Device struct {
rs machine.Pin
wr machine.Pin
cs machine.Pin
rst machine.Pin
bus Bus
}
const width = int16(240)
const height = int16(320)
func New(rs machine.Pin, wr machine.Pin, cs machine.Pin, rst machine.Pin, bus Bus) Device {
d := Device{
rs: rs,
wr: wr,
cs: cs,
rst: rst,
bus: bus,
}
rs.Configure(machine.PinConfig{Mode: machine.PinOutput})
wr.Configure(machine.PinConfig{Mode: machine.PinOutput})
cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
cs.High()
rst.High()
wr.High()
return d
}
func (d *Device) lcdWriteCom(cmd Command) {
d.rs.Low()
d.lcdWriteBusInt(uint16(cmd))
}
func (d *Device) lcdWriteDataInt(data uint16) {
d.rs.High()
d.lcdWriteBusInt(data)
}
func (d *Device) lcdWriteComData(cmd Command, data uint16) {
d.lcdWriteCom(cmd)
d.lcdWriteDataInt(data)
}
func (d *Device) tx() {
d.wr.Low()
d.wr.High()
}
func (d *Device) lcdWriteBusInt(data uint16) {
d.bus.Set(data)
d.tx()
}
func (d *Device) Configure() {
d.rst.High()
time.Sleep(time.Millisecond * 5)
d.rst.Low()
time.Sleep(time.Millisecond * 15)
d.rst.High()
time.Sleep(time.Millisecond * 15)
d.cs.Low()
//Power supply setting
d.lcdWriteComData(POWERCONTROL1, 0xA8A4)
d.lcdWriteComData(POWERCONTROL2, 0x0000)
d.lcdWriteComData(POWERCONTROL3, 0x080C)
d.lcdWriteComData(POWERCONTROL4, 0x2B00)
d.lcdWriteComData(POWERCONTROL5, 0x00B7)
//Set R07h at 0021h
d.lcdWriteComData(DISPLAYCONTROL, 0x021)
//Set R00h at 0001h
d.lcdWriteComData(OSCILLATIONSTART, 0x0001)
//Set R07h at 0021h
d.lcdWriteComData(DISPLAYCONTROL, 0x023)
//Set R10h at 0000h, Exit sleep mode
d.lcdWriteComData(SLEEPMODE, 0x0000)
//Wait 30ms
time.Sleep(time.Millisecond * 30)
//Set R07h at 0033h
d.lcdWriteComData(DISPLAYCONTROL, 0x033)
//Entry Mode setting (R11h)
//DFM 11 --> 65k
//TRANS 0
//OEDEF 0
//WMODE 0 --> Normal data bus
//DMODE 00 --> Ram
//TY 01 --> 262k Type A not used as we are in 65k mode.
//ID 11 --> Horizontal & Vertical increment
//AM 0 --> Horizontal
//LG 000 --> No compare register usage
d.lcdWriteComData(ENTRYMODE, 0x6030)
//LCD Driver AC Setting
//I couldn't make sense of the documentation fortunately 0 seems to
//FLD 0 --> Normal driving
//ENWS 0 --> POR mode
//BC 1 --> Less flicker
//EOR 1 --> Less stripey
//WSMD 0 --> not used in POR mode
//NW 0 --> Least flicker
d.lcdWriteComData(LCDDRIVEACCONTROL, 0x0600)
//End of documented init
//RL 0 --> Output shift direction
//REV 1 --> Reverse colors
//CAD 0 --> Cs on common
//BGR 0 --> use RGB color assignment
//SM 0 --> standard gate scan sequence
//TB 1 --> Display is mirrored with 0
//MUX 319 --> Number of lines in display
d.lcdWriteComData(DRIVEROUTPUTCONTROL, 0x233F)
d.cs.High()
}
func (d *Device) setXY(x1 uint16, y1 uint16, x2 uint16, y2 uint16) {
d.lcdWriteComData(HORIZONTALRAMADDRESSPOSITION, (x2<<8)+x1)
d.lcdWriteComData(VERTICALRAMADDRESSSTARTPOSITION, y1)
d.lcdWriteComData(VERTICALRAMADDRESSENDPOSITION, y2)
d.lcdWriteComData(SETGDDRAMXADDRESSCOUNTER, x1)
d.lcdWriteComData(SETGDDRAMYADDRESSCOUNTER, y1)
d.lcdWriteCom(RAMDATAREADWRITE)
}
func (d *Device) ClearDisplay() {
d.FillDisplay(color.RGBA{0, 0, 0, 255})
}
func (d *Device) FillDisplay(c color.RGBA) {
d.FillRect(0, 0, width, height, c)
}
func encodeColor(c color.RGBA) uint16 {
encoded := (uint16(c.B)&248)<<8 | (uint16(c.G)&252)<<3 | (uint16(c.R)&248)>>3
return encoded
}
func (d *Device) SetPixel(x, y int16, c color.RGBA) {
encoded := encodeColor(c)
d.cs.Low()
d.setXY(uint16(x), uint16(y), uint16(x), uint16(y))
d.rs.High()
d.lcdWriteBusInt(encoded)
d.cs.High()
}
func (d *Device) FillRect(x, y, w, h int16, c color.RGBA) {
encoded := encodeColor(c)
d.cs.Low()
d.setXY(uint16(x), uint16(y), uint16(x+(w-1)), uint16(y+(h-1)))
d.rs.High()
d.bus.Set(encoded)
for i := int64(0); i < int64(w)*int64(h); i++ {
d.tx()
}
d.cs.High()
d.rs.Low()
}
func (d *Device) Display() error {
//Not enough memory to store an entire screen on most microcontrollers
return nil
}
func (d *Device) Size() (x, y int16) {
return width, height
}
+1 -1
View File
@@ -2,4 +2,4 @@ package drivers
// Version returns a user-readable string showing the version of the drivers package for support purposes.
// Update this value before release of new version of software.
const Version = "0.19.0"
const Version = "0.20.0"
+2
View File
@@ -46,6 +46,8 @@ const (
SD_CONFIG_INITIAL_PHASE_SD1 = 0x007B
SYSTEM_GROUPED_PARAMETER_HOLD_1 = 0x007C
SD_CONFIG_QUANTIFIER = 0x007E
ROI_CONFIG_USER_ROI_CENTRE_SPAD = 0x007F
ROI_CONFIG_USER_ROI_REQUESTED_GLOBAL_XY_SIZE = 0x0080
SYSTEM_SEQUENCE_CONFIG = 0x0081
SYSTEM_GROUPED_PARAMETER_HOLD = 0x0082
SYSTEM_INTERRUPT_CLEAR = 0x0086
+37 -17
View File
@@ -10,6 +10,7 @@
package vl53l1x // import "tinygo.org/x/drivers/vl53l1x"
import (
"errors"
"time"
"tinygo.org/x/drivers"
@@ -157,7 +158,6 @@ func (d *Device) SetDistanceMode(mode DistanceMode) bool {
d.writeReg(SD_CONFIG_WOI_SD1, 0x05)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD0, 6)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD1, 6)
break
case MEDIUM:
// timing config
d.writeReg(RANGE_CONFIG_VCSEL_PERIOD_A, 0x0B)
@@ -169,7 +169,6 @@ func (d *Device) SetDistanceMode(mode DistanceMode) bool {
d.writeReg(SD_CONFIG_WOI_SD1, 0x09)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD0, 10)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD1, 10)
break
case LONG:
// timing config
d.writeReg(RANGE_CONFIG_VCSEL_PERIOD_A, 0x0F)
@@ -181,7 +180,6 @@ func (d *Device) SetDistanceMode(mode DistanceMode) bool {
d.writeReg(SD_CONFIG_WOI_SD1, 0x0D)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD0, 14)
d.writeReg(SD_CONFIG_INITIAL_PHASE_SD1, 14)
break
default:
return false
}
@@ -333,44 +331,35 @@ func (d *Device) AmbientRate() int32 {
func (d *Device) getRangingData() {
d.rangingData.mm = uint16((uint32(d.results.mmCrosstalkSD0)*2011 + 0x0400) / 0x0800)
switch d.results.status {
case 17: // MULTCLIPFAIL
case 2: // VCSELWATCHDOGTESTFAILURE
case 1: // VCSELCONTINUITYTESTFAILURE
case 3: // NOVHVVALUEFOUND
case 1, // VCSELCONTINUITYTESTFAILURE
2, // VCSELWATCHDOGTESTFAILURE
3, // NOVHVVALUEFOUND
17: // MULTCLIPFAIL
d.rangingData.status = HardwareFail
break
case 13: // USERROICLIP
d.rangingData.status = MinRangeFail
break
case 18: // GPHSTREAMCOUNT0READY
d.rangingData.status = SynchronizationInt
break
case 5: // RANGEPHASECHECK
d.rangingData.status = OutOfBoundsFail
break
case 4: // MSRCNOTARGET
d.rangingData.status = SignalFail
break
case 6: // SIGMATHRESHOLDCHECK
d.rangingData.status = SignalFail
break
case 7: // PHASECONSISTENCY
d.rangingData.status = WrapTargetFail
break
case 12: // RANGEIGNORETHRESHOLD
d.rangingData.status = XtalkSignalFail
break
case 8: // MINCLIP
d.rangingData.status = RangeValidMinRangeClipped
break
case 9: // RANGECOMPLETE
if d.results.streamCount == 0 {
@@ -378,7 +367,6 @@ func (d *Device) getRangingData() {
} else {
d.rangingData.status = RangeValid
}
break
default:
d.rangingData.status = None
@@ -430,6 +418,38 @@ func (d *Device) StopContinuous() {
d.writeReg(PHASECAL_CONFIG_OVERRIDE, 0x00)
}
// SetROI sets the 'region of interest' for x and y coordinates. Valid ranges are from 4/4 to 16/16.
func (d *Device) SetROI(x, y uint8) error {
if !validROIRange(x, y) {
return errors.New("ROI value out of range")
}
if x > 10 || y > 10 {
d.writeReg(ROI_CONFIG_USER_ROI_CENTRE_SPAD, 199)
}
d.writeReg(ROI_CONFIG_USER_ROI_REQUESTED_GLOBAL_XY_SIZE, (y-1)<<4|(x-1))
return nil
}
// GetROI returns the currently configured 'region of interest' for x and y coordinates.
func (d *Device) GetROI() (x, y uint8, err error) {
reg := d.readReg(ROI_CONFIG_USER_ROI_REQUESTED_GLOBAL_XY_SIZE)
x = (reg & 0x0f) + 1
y = ((reg & 0xf0) >> 4) + 1
if !validROIRange(x, y) {
err = errors.New("ROI value out of range")
}
return
}
func validROIRange(x, y uint8) bool {
return x >= 4 && x <= 16 && y >= 4 && y <= 16
}
// writeReg sends a single byte to the specified register address
func (d *Device) writeReg(reg uint16, value uint8) {
msb := byte((reg >> 8) & 0xFF)
+3 -1
View File
@@ -969,7 +969,9 @@ func (d *Device) sendParamBuf(p []byte, isLastParam bool) (l int) {
func (d *Device) sendParamStr(p string, isLastParam bool) (l int) {
l = len(p)
d.SPI.Transfer(uint8(l))
d.SPI.Tx([]byte(p), nil)
if l > 0 {
d.SPI.Tx([]byte(p), nil)
}
if isLastParam {
d.SPI.Transfer(CmdEnd)
l += 1
+70 -37
View File
@@ -59,21 +59,23 @@ var architectures = map[string]architectureImpl{
maxBaseCyclesT0H: 1 + 3 + 2, // shift + branch (not taken) + store
minBaseCyclesT1H: 1 + 1 + 2, // shift + branch (taken) + store
maxBaseCyclesT1H: 1 + 3 + 2, // shift + branch (taken) + store
minBaseCyclesTLD: 1 + 1 + 2, // subtraction + branch + store (in next cycle)
valueTemplate: "uint32(c) << 24",
minBaseCyclesTLD: 1 + 2 + 2, // subtraction + branch x2 + store (in next cycle)
valueTemplate: "(uint32_t)c << 24",
template: `
1: @ send_bit
str {maskSet}, {portSet} @ [2] T0H and T0L start here
str %[maskSet], %[portSet] @ [2] T0H and T0L start here
@DELAY1
lsls {value}, #1 @ [1]
bcs.n 2f @ [1/3] skip_store
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
lsls %[value], #1 @ [1]
bcs.n 2f @ [1/3] skip_store
str %[maskClear], %[portClear] @ [2] T0H -> T0L transition
2: @ skip_store
@DELAY2
str {maskClear}, {portClear} @ [2] T1H -> T1L transition
str %[maskClear], %[portClear] @ [2] T1H -> T1L transition
@DELAY3
subs {i}, #1 @ [1]
bne.n 1b @ [1/3] send_bit
subs %[i], #1 @ [1]
beq.n 3f @ [1/3] end
b 1b @ [1/3] send_bit
3: @ end
`,
},
"tinygoriscv": {
@@ -88,25 +90,25 @@ var architectures = map[string]architectureImpl{
minBaseCyclesT1H: 1 + 1 + 1, // shift + branch (taken) + store
maxBaseCyclesT1H: 1 + 3 + 1, // shift + branch (taken) + store
minBaseCyclesTLD: 1 + 1 + 1, // subtraction + branch + store (in next cycle)
valueTemplate: "uint32(c) << 23",
valueTemplate: "(uint32_t)c << 23",
template: `
1: // send_bit
sw {maskSet}, {portSet} // [1] T0H and T0L start here
sw %[maskSet], %[portSet] // [1] T0H and T0L start here
@DELAY1
slli {value}, {value}, 1 // [1] shift value left by 1
bltz {value}, 2f // [1/3] skip_store
sw {maskClear}, {portClear} // [1] T0H -> T0L transition
slli %[value], %[value], 1 // [1] shift value left by 1
bltz %[value], 2f // [1/3] skip_store
sw %[maskClear], %[portClear] // [1] T0H -> T0L transition
2: // skip_store
@DELAY2
sw {maskClear}, {portClear} // [1] T1H -> T1L transition
sw %[maskClear], %[portClear] // [1] T1H -> T1L transition
@DELAY3
addi {i}, {i}, -1 // [1]
bnez {i}, 1b // [1/3] send_bit
addi %[i], %[i], -1 // [1]
bnez %[i], 1b // [1/3] send_bit
`,
},
}
func writeImplementation(f *os.File, arch string, megahertz int) error {
func writeCAssembly(f *os.File, arch string, megahertz int) error {
cycleTimeNS := 1 / float64(megahertz)
// These timings are taken from the table "Updated simplified timing
// constraints for NeoPixel strings" at:
@@ -207,30 +209,51 @@ func writeImplementation(f *os.File, arch string, megahertz int) error {
// ignore I/O errors.
buf := &bytes.Buffer{}
fmt.Fprintf(buf, "\n")
fmt.Fprintf(buf, "func (d Device) writeByte%d(c byte) {\n", megahertz)
fmt.Fprintf(buf, " portSet, maskSet := d.Pin.PortMaskSet()\n")
fmt.Fprintf(buf, " portClear, maskClear := d.Pin.PortMaskClear()\n")
fmt.Fprintf(buf, "\n")
fmt.Fprintf(buf, "__attribute__((always_inline))\nvoid ws2812_writeByte%d(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {\n", megahertz)
fmt.Fprintf(buf, " // Timings:\n")
fmt.Fprintf(buf, " // T0H: %2d - %2d cycles or %.1fns - %.1fns\n", actualMinCyclesT0H, actualMaxCyclesT0H, actualMinNanosecondsT0H, actualMaxNanosecondsT0H)
fmt.Fprintf(buf, " // T1H: %2d - %2d cycles or %.1fns - %.1fns\n", actualMinCyclesT1H, actualMaxCyclesT1H, actualMinNanosecondsT1H, actualMaxNanosecondsT1H)
fmt.Fprintf(buf, " // TLD: %2d - cycles or %.1fns -\n", actualMinCyclesTLD, actualMinNanosecondsTLD)
fmt.Fprintf(buf, " mask := interrupt.Disable()\n")
fmt.Fprintf(buf, " value := %s\n", archImpl.valueTemplate)
fmt.Fprintf(buf, " uint32_t value = %s;\n", archImpl.valueTemplate)
asm := archImpl.template
asm = strings.TrimSpace(asm)
asm = strings.ReplaceAll(asm, " @DELAY1\n", strings.Repeat(" nop\n", delay1))
asm = strings.ReplaceAll(asm, " @DELAY2\n", strings.Repeat(" nop\n", delay2))
asm = strings.ReplaceAll(asm, " @DELAY3\n", strings.Repeat(" nop\n", delay3))
asm = strings.ReplaceAll(asm, "\n", "\n\t")
fmt.Fprintf(buf, " device.AsmFull(`%s`, map[string]interface{}{", asm)
fmt.Fprintf(buf, " char i = 8;\n")
fmt.Fprintf(buf, " __asm__ __volatile__(\n")
for _, line := range strings.Split(asm, "\n") {
fmt.Fprintf(buf, "\t\t%#v\n", line+"\n")
}
// Note: [value] and [i] must be input+output operands because they modify
// the value.
fmt.Fprintf(buf, ` : [value]"+r"(value),
[i]"+r"(i)
: [maskSet]"r"(maskSet),
[portSet]"m"(*portSet),
[maskClear]"r"(maskClear),
[portClear]"m"(*portClear));
}
`)
// Now write the buffer contents (with the assembly function) to a file.
_, err := f.Write(buf.Bytes())
return err
}
func writeGoWrapper(f *os.File, arch string, megahertz int) error {
// Create the Go function in a buffer. Using a buffer here to be able to
// ignore I/O errors.
buf := &bytes.Buffer{}
fmt.Fprintf(buf, "\n")
fmt.Fprintf(buf, "func (d Device) writeByte%d(c byte) {\n", megahertz)
fmt.Fprintf(buf, " portSet, maskSet := d.Pin.PortMaskSet()\n")
fmt.Fprintf(buf, " portClear, maskClear := d.Pin.PortMaskClear()\n")
fmt.Fprintf(buf, "\n")
fmt.Fprintf(buf, " mask := interrupt.Disable()\n")
fmt.Fprintf(buf, " C.ws2812_writeByte%d(C.char(c), portSet, portClear, maskSet, maskClear)\n", megahertz)
buf.WriteString(`
"value": value,
"i": 8,
"maskSet": maskSet,
"portSet": portSet,
"maskClear": maskClear,
"portClear": portClear,
})
interrupt.Restore(mask)
}
`)
@@ -269,16 +292,26 @@ package ws2812
// Warning: autogenerated file. Instead of modifying this file, change
// gen-ws2812.go and run "go generate".
import (
"device"
"runtime/interrupt"
)
import "runtime/interrupt"
/*
#include <stdint.h>
`)
for _, megahertz := range clockFrequencies {
err := writeImplementation(f, *arch, megahertz)
err := writeCAssembly(f, *arch, megahertz)
if err != nil {
fmt.Fprintf(os.Stderr, "could not generate WS2812 assembly code for %s and %dMHz: %s\n", *arch, megahertz, err)
os.Exit(1)
}
}
f.WriteString(`*/
import "C"
`)
for _, megahertz := range clockFrequencies {
err := writeGoWrapper(f, *arch, megahertz)
if err != nil {
fmt.Fprintf(os.Stderr, "could not generate Go wrapper: %w\n", err)
os.Exit(1)
}
}
}
+1031 -999
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+40 -32
View File
@@ -6,11 +6,49 @@ package ws2812
// This file implements the WS2812 protocol for AVR microcontrollers.
import (
"device/avr"
"machine"
"runtime/interrupt"
"unsafe"
)
/*
#include <stdint.h>
__attribute__((always_inline))
void ws2812_writeByte16(char c, uint8_t *port, uint8_t maskSet, uint8_t maskClear) {
// 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
char i = 8;
__asm__ __volatile__(
"1:\n"
"\t st %[port], %[maskSet] ; [2] set output high\n"
"\t lsl %[value] ; [1] shift off the next bit, store it in C\n"
"\t brcs 2f ; [1/2] branch if this bit is high (long pulse)\n"
"\t st %[port], %[maskClear] ; [2] set output low (short pulse)\n"
"\t2:\n"
"\t nop ; [4] wait before changing the output again\n"
"\t nop\n"
"\t nop\n"
"\t nop\n"
"\t st %[port], %[maskClear] ; [2] set output low (end of pulse)\n"
"\t nop ; [3]\n"
"\t nop\n"
"\t nop\n"
"\t subi %[i], 1 ; [1] subtract one (for the loop)\n"
"\t brne 1b ; [1/2] send the next bit, if not at the end of the loop\n"
: [value]"+r"(c),
[i]"+r"(i)
: [maskSet]"r"(maskSet),
[maskClear]"r"(maskClear),
[port]"m"(*port));
}
*/
import "C"
// 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
@@ -23,37 +61,7 @@ func (d Device) WriteByte(c byte) error {
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,
})
C.ws2812_writeByte16(C.char(c), (*uint8)(unsafe.Pointer(port)), maskSet, maskClear)
interrupt.Restore(mask)
return nil
default: