mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-28 11:38:41 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec6385c15a | |||
| 7267dc1014 | |||
| 84bfe4a928 | |||
| bd88b70511 | |||
| 34da2d208a | |||
| 5cb360a4bf | |||
| 51b604ce97 | |||
| ec680be784 | |||
| 5fb935001e | |||
| 297ad416d3 | |||
| 3fa08112db | |||
| b639f7b12e | |||
| 28d625abfd | |||
| 228e57cf98 | |||
| 6cf1eb86e5 | |||
| 857ab80ae6 | |||
| a31ba26a6c | |||
| 303ec94529 | |||
| 833990f44d | |||
| 28d87eb0c5 | |||
| ae9e8f915e | |||
| 45fad80c3e | |||
| 0304d30b78 | |||
| 7de0a0814e | |||
| 80356fd9d9 | |||
| c4ff8242a7 | |||
| 82c41dbf14 | |||
| c4168864fd | |||
| dbc9022f6a | |||
| d41bc0b85f | |||
| e7f90166ad | |||
| 156d6e7c9c | |||
| 6e04decf19 | |||
| afb1ea39c2 | |||
| 382e79a748 | |||
| 06dd60fba2 | |||
| 27a7d5031e | |||
| 6281f36662 | |||
| ccfcfa837e | |||
| 87c205fd65 | |||
| f57b5ecee9 | |||
| 486949686c | |||
| 00578a3a81 | |||
| 17f273243d | |||
| 0623bb425c | |||
| 019bbbe5fb | |||
| d04e68bef8 | |||
| 76a4276b5d | |||
| 7d7efe25e7 | |||
| 0186d0905d | |||
| 2f3b5ca59a | |||
| ecae5e28ad | |||
| bcf3b84654 | |||
| 4fb5d7a0e5 | |||
| f12454d4f7 | |||
| 829ae09651 | |||
| 30f540c29f | |||
| 6d431e0726 | |||
| f308f8fce0 | |||
| 1c2b802f47 | |||
| ce80e7582f | |||
| 109cab4f3b | |||
| 07216d3051 | |||
| d688fa3f3f | |||
| dd44f9220b |
@@ -0,0 +1,2 @@
|
||||
# These are supported funding model platforms
|
||||
open_collective: tinygo
|
||||
+118
@@ -1,3 +1,121 @@
|
||||
0.33.0
|
||||
---
|
||||
- **new devices**
|
||||
- **ens160**
|
||||
- Add ens160 i2c driver
|
||||
- **lsm303dlhc**
|
||||
- added support for LSM303DLHC e-Compass; (#783)
|
||||
- **seesaw**
|
||||
- add support for Adafruit Seesaw encoders
|
||||
|
||||
- **enhancements**
|
||||
- **ws2812**
|
||||
- add RP2350 support
|
||||
- **ssd1306**
|
||||
- avoid unnecessary heap allocations (#767)
|
||||
- **gps**
|
||||
- allow gps init with address
|
||||
- **lsm6ds3tr**
|
||||
- avoid unnecessary heap allocations (#766)
|
||||
|
||||
- **bugfixes**
|
||||
- **gps**
|
||||
- Fix gps time calculation (#785)
|
||||
|
||||
|
||||
0.32.0
|
||||
---
|
||||
- **enhancements**
|
||||
- **bmp280**
|
||||
- remove alloc on read sensor data
|
||||
- **ws2812**
|
||||
- add 200MHz support for the Cortex-M0/rp2040
|
||||
|
||||
- **bugfixes**
|
||||
- **ssd1306**
|
||||
- remove time.Sleep from SSD1306 SPI transfer code
|
||||
- **tmc2209**
|
||||
- tmc2209 bug fixes (#755)
|
||||
|
||||
- **docs**
|
||||
- **contributing**
|
||||
- add driver design pointer to CONTRIBUTING.md
|
||||
|
||||
|
||||
0.31.0
|
||||
---
|
||||
---
|
||||
- **enhancements**
|
||||
- **spi**
|
||||
- update all SPI usage to use either *machine.SPI or drivers.SPI
|
||||
|
||||
|
||||
0.30.0
|
||||
---
|
||||
- **new devices**
|
||||
- **comboat**
|
||||
- Add wifi driver comboat for Elecrow W5 rp2040 and rp2350 devices (#741)
|
||||
- **max6675**
|
||||
- Add MAX6675 device
|
||||
- **TMC2209**
|
||||
- Added TMC2209 support (#727)
|
||||
- **TMC5160**
|
||||
- Added TMC5160 support (#725)
|
||||
- **sharpmem**
|
||||
- Add sharpmem (#724)
|
||||
|
||||
- **enhancements**
|
||||
- **net**
|
||||
- move to latest golang.org/x/net v0.33.0 (#732)
|
||||
- **microphone**
|
||||
- update microphone driver to use latest i2s interface
|
||||
|
||||
- **bugfixes**
|
||||
- **net**
|
||||
- fix typo in DHCP error message
|
||||
- **aht20**
|
||||
- Fixed bug in aht20 driver
|
||||
- **hub75**
|
||||
- fix data buffering
|
||||
|
||||
|
||||
0.29.0
|
||||
---
|
||||
- **new devices**
|
||||
- **epd1in54**
|
||||
- Waveshare 1.54inch B/W e-Paper display (#704)
|
||||
- **touch**
|
||||
- add capacitive touch sensing on normal GPIO pins
|
||||
- **INA219**
|
||||
- I2C INA219 driver (#705)
|
||||
- **pcf8591**
|
||||
- add ADC only implementation for I2C ADC/DAC (#690)
|
||||
|
||||
- **enhancements**
|
||||
- **pixel**
|
||||
- add NewImageFromBytes() function to allow creating image from existing slice
|
||||
- **servo**
|
||||
- Add function `SetAngleWithMicroseconds` (#695)
|
||||
- **onewire**
|
||||
- onewire improvements
|
||||
- **ssd1306**
|
||||
- Add function `SetFlip` and `GetFlip` (#702)
|
||||
- **uc8151**
|
||||
- add FillRectangle() and SetScroll() functions to satisfy tinyterm.Displayer interface
|
||||
- **ssd1306**
|
||||
- add FillRectangle() and SetScroll() functions to satisfy tinyterm.Displayer interface
|
||||
|
||||
- **bugfixes**
|
||||
- **pixel**
|
||||
- fix Monochrome setPixel
|
||||
|
||||
- **docs**
|
||||
- **readme**
|
||||
- discuss need to change variables in examples
|
||||
- **sponsor**
|
||||
- Add sponsor button to key repositories
|
||||
|
||||
|
||||
0.28.0
|
||||
---
|
||||
- **new devices**
|
||||
|
||||
@@ -8,6 +8,9 @@ We would like your help to make this project better, so we appreciate any contri
|
||||
|
||||
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
|
||||
|
||||
### Driver design
|
||||
Before porting or writing a driver from scratch please read **[Driver Design for TinyGo](https://tinygo.org/docs/guides/driver-design)**.
|
||||
|
||||
### One of the TinyGo drivers is not working as you expect
|
||||
|
||||
Please open a Github issue with your problem, and we will be happy to assist.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018-2024 The TinyGo Authors. All rights reserved.
|
||||
Copyright (c) 2018-2025 The TinyGo Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
||||
@@ -16,7 +16,7 @@ go get tinygo.org/x/drivers
|
||||
|
||||
## How to use
|
||||
|
||||
Here is an example in TinyGo that uses the BMP180 digital barometer:
|
||||
Here is an example in TinyGo that uses the BMP180 digital barometer. This example should work on any board that supports I2C:
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -53,6 +53,28 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Examples Using GPIO or SPI
|
||||
|
||||
If compiling these examples directly you are likely to need to make minor changes to the defined variables to map the pins for the board you are using. For example, this block in main.go:
|
||||
|
||||
```golang
|
||||
var (
|
||||
spi = machine.SPI0
|
||||
csPin = machine.D5
|
||||
)
|
||||
```
|
||||
|
||||
It might not be obvious, but you need to change these to match how you wired your specific board. Constants are [defined for each supported microcontroller](https://tinygo.org/docs/reference/microcontrollers/).
|
||||
|
||||
For example, to change the definitions for use on a Raspberry Pi Pico using typical wiring, you might need to do this:
|
||||
|
||||
```golang
|
||||
var (
|
||||
spi = machine.SPI0
|
||||
csPin = machine.GP17
|
||||
)
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Your contributions are welcome!
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@ func New(bus drivers.I2C) Device {
|
||||
func (d *Device) Configure() {
|
||||
// Check initialization state
|
||||
status := d.Status()
|
||||
if status&0x08 == 1 {
|
||||
if status&STATUS_CALIBRATED == 1 {
|
||||
// Device is initialized
|
||||
return
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func (d *Device) Read() error {
|
||||
}
|
||||
|
||||
// If measurement complete, store values
|
||||
if data[0]&0x04 != 0 && data[0]&0x80 == 0 {
|
||||
if data[0]&STATUS_CALIBRATED != 0 && data[0]&STATUS_BUSY == 0 {
|
||||
d.humidity = uint32(data[1])<<12 | uint32(data[2])<<4 | uint32(data[3])>>4
|
||||
d.temp = (uint32(data[3])&0xF)<<16 | uint32(data[4])<<8 | uint32(data[5])
|
||||
return nil
|
||||
|
||||
+7
-8
@@ -23,6 +23,7 @@ type Filter uint
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
buf [6]byte
|
||||
cali calibrationCoefficients
|
||||
Temperature Oversampling
|
||||
Pressure Oversampling
|
||||
@@ -134,8 +135,8 @@ func (d *Device) PrintCali() {
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000).
|
||||
func (d *Device) ReadTemperature() (temperature int32, err error) {
|
||||
data, err := d.readData(REG_TEMP, 3)
|
||||
if err != nil {
|
||||
data := d.buf[:3]
|
||||
if err = d.readData(REG_TEMP, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -158,8 +159,8 @@ func (d *Device) ReadTemperature() (temperature int32, err error) {
|
||||
// ReadPressure returns the pressure in milli pascals (mPa).
|
||||
func (d *Device) ReadPressure() (pressure int32, err error) {
|
||||
// First 3 bytes are Pressure, last 3 bytes are Temperature
|
||||
data, err := d.readData(REG_PRES, 6)
|
||||
if err != nil {
|
||||
data := d.buf[:6]
|
||||
if err = d.readData(REG_PRES, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -203,7 +204,7 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
|
||||
}
|
||||
|
||||
// readData reads n number of bytes of the specified register
|
||||
func (d *Device) readData(register int, n int) ([]byte, error) {
|
||||
func (d *Device) readData(register int, data []byte) error {
|
||||
// If not in normal mode, set the mode to FORCED mode, to prevent incorrect measurements
|
||||
// After the measurement in FORCED mode, the sensor will return to SLEEP mode
|
||||
if d.Mode != MODE_NORMAL {
|
||||
@@ -218,9 +219,7 @@ func (d *Device) readData(register int, n int) ([]byte, error) {
|
||||
}
|
||||
|
||||
// Read the requested register
|
||||
data := make([]byte, n)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), uint8(register), data[:])
|
||||
return data, err
|
||||
return legacy.ReadRegister(d.bus, uint8(d.Address), uint8(register), data[:])
|
||||
}
|
||||
|
||||
// convert3Bytes converts three bytes to int32
|
||||
|
||||
@@ -0,0 +1,711 @@
|
||||
// Package comboat implements WiFi driver for the Aithinker-Combo-AT WiFi
|
||||
// device found on the Elecrow W5 rp2040 and rp2350 devices. Ths WiFi device
|
||||
// is a RTL8720d variant. The driver interface is via AT command set over UART
|
||||
// (see reference docs below).
|
||||
//
|
||||
// NOTE: the driver doesn't support UDP/TCP server connections in STA mode,
|
||||
// currently. UDP/TCP/TLS client connections are supported in STA mode.
|
||||
//
|
||||
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/instruction/index.html
|
||||
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/command-set/index.html
|
||||
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/command-examples/index.html
|
||||
|
||||
package comboat // import "tinygo.org/x/drivers/comboat"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"machine"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
"tinygo.org/x/drivers/netlink"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BaudRate uint32
|
||||
Uart *machine.UART
|
||||
Tx machine.Pin
|
||||
Rx machine.Pin
|
||||
}
|
||||
|
||||
type socket struct {
|
||||
protocol int
|
||||
id string
|
||||
rx chan []byte
|
||||
remainder []byte
|
||||
laddr netip.AddrPort // Set in Bind()
|
||||
}
|
||||
|
||||
type device struct {
|
||||
cfg *Config
|
||||
uart *machine.UART
|
||||
uartMu sync.Mutex
|
||||
mac net.HardwareAddr
|
||||
ip netip.Addr
|
||||
gateway netip.Addr
|
||||
buf [1500]byte
|
||||
pos int
|
||||
last []byte
|
||||
ok chan bool
|
||||
txReady chan bool
|
||||
accept chan string
|
||||
err chan error
|
||||
sockets [8]*socket
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewDevice(cfg *Config) *device {
|
||||
return &device{
|
||||
cfg: cfg,
|
||||
ok: make(chan bool),
|
||||
txReady: make(chan bool),
|
||||
accept: make(chan string),
|
||||
err: make(chan error),
|
||||
}
|
||||
}
|
||||
|
||||
func logDebug(msg string) {
|
||||
//println("[DEBUG] " + msg)
|
||||
}
|
||||
|
||||
func logError(msg string) {
|
||||
println("[ERROR] " + msg)
|
||||
}
|
||||
|
||||
func split(resp []byte, part int, del, on string) string {
|
||||
parts := bytes.Split(resp, []byte(del))
|
||||
if part >= len(parts) {
|
||||
return "Split parts error getting " + on
|
||||
}
|
||||
return string(parts[part])
|
||||
}
|
||||
|
||||
func (d *device) getFWVersion() string {
|
||||
return split(d.last, 1, ":", "FW version")
|
||||
}
|
||||
|
||||
func (d *device) saveMAC() {
|
||||
raw := split(d.last, 1, ":", "MAC")
|
||||
if len(raw) > 11 {
|
||||
macStr := fmt.Sprintf("%s:%s:%s:%s:%s:%s",
|
||||
raw[0:2], raw[2:4], raw[4:6],
|
||||
raw[6:8], raw[8:10], raw[10:12])
|
||||
d.mac, _ = net.ParseMAC(macStr)
|
||||
}
|
||||
}
|
||||
|
||||
var countryCodes = map[int]string{
|
||||
1: "JP Japan",
|
||||
2: "American Samoa",
|
||||
3: "CA Canada",
|
||||
4: "US",
|
||||
5: "CN China",
|
||||
6: "Hong Kong, China",
|
||||
7: "Taiwan, China",
|
||||
8: "MO Macau, China",
|
||||
9: "IL Israel",
|
||||
10: "Singapore",
|
||||
11: "KR South Korea",
|
||||
12: "TR Türkiye",
|
||||
13: "AU Australia",
|
||||
14: "ZA South Africa",
|
||||
15: "BR Brazil",
|
||||
}
|
||||
|
||||
func (d *device) getCountry() (code string) {
|
||||
code = split(d.last, 1, ":", "county code")
|
||||
codeNum, err := strconv.Atoi(code)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if val, ok := countryCodes[codeNum]; ok {
|
||||
code = val
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) saveIP() {
|
||||
ipStr := split(d.last, 7, ",", "IP address")
|
||||
gwStr := split(d.last, 8, ",", "gateway address")
|
||||
d.ip, _ = netip.ParseAddr(ipStr)
|
||||
d.gateway, _ = netip.ParseAddr(gwStr)
|
||||
}
|
||||
|
||||
func (d *device) execute(cmd string, timeout int) (err error) {
|
||||
logDebug("EXECUTE " + cmd)
|
||||
|
||||
d.uartMu.Lock()
|
||||
_, err = d.uart.Write([]byte(cmd + "\r\n"))
|
||||
d.uartMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t := time.NewTicker(time.Duration(timeout) * time.Millisecond)
|
||||
defer t.Stop()
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
return errors.New("Timed out")
|
||||
case <-d.ok:
|
||||
return
|
||||
case err = <-d.err:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) send(cmd string, timeout int) (err error) {
|
||||
logDebug("EXECUTE " + cmd)
|
||||
|
||||
d.uartMu.Lock()
|
||||
_, err = d.uart.Write([]byte(cmd + "\r\n"))
|
||||
d.uartMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t := time.NewTicker(time.Duration(timeout) * time.Millisecond)
|
||||
defer t.Stop()
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
return errors.New("Timed out")
|
||||
case <-d.txReady:
|
||||
return
|
||||
case err = <-d.err:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) findSocket(id string) (*socket, error) {
|
||||
for _, s := range d.sockets {
|
||||
if s.id == id {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("Socket not found with id: " + id)
|
||||
}
|
||||
|
||||
func (d *device) getSocket(sockfd int) (*socket, error) {
|
||||
if sockfd < 0 || sockfd+1 > len(d.sockets) {
|
||||
return nil, netdev.ErrInvalidSocketFd
|
||||
}
|
||||
if d.sockets[sockfd] == nil {
|
||||
return nil, netdev.ErrInvalidSocketFd
|
||||
}
|
||||
return d.sockets[sockfd], nil
|
||||
}
|
||||
|
||||
func (d *device) handle(event []byte) {
|
||||
logDebug("GOT EVENT " + string(event))
|
||||
switch {
|
||||
|
||||
// SocketDisconnect,<id>
|
||||
case bytes.HasPrefix(event, []byte("SocketDisconnect")):
|
||||
id := split(event, 1, ",", "SocketDisconnect")
|
||||
s, err := d.findSocket(id)
|
||||
if err == nil {
|
||||
close(s.rx) // Sends io.EOF
|
||||
}
|
||||
|
||||
// SocketSeed,<id>,<server id>
|
||||
case bytes.HasPrefix(event, []byte("SocketSeed,2,1")):
|
||||
//d.uart.Write([]byte("AT+SOCKET?" + "\r\n"))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) processUART() {
|
||||
|
||||
if d.pos == 1 && d.buf[0] == '>' {
|
||||
d.pos = 0
|
||||
logDebug("GOT >")
|
||||
d.txReady <- true
|
||||
}
|
||||
|
||||
sofar := d.buf[:d.pos]
|
||||
|
||||
if !bytes.HasSuffix(sofar, []byte("\r\n")) {
|
||||
return
|
||||
}
|
||||
|
||||
// Strip CR/LF off end
|
||||
sofar = sofar[:len(sofar)-2]
|
||||
|
||||
switch {
|
||||
|
||||
case bytes.HasPrefix(sofar, []byte("+EVENT:SocketDown")):
|
||||
// +EVENT:SocketDown,<id>,<length>,<data>
|
||||
parts := bytes.SplitN(sofar, []byte(","), 4)
|
||||
if len(parts) != 4 {
|
||||
logError("Error parsing +EVENT:SocketDown: " + string(sofar))
|
||||
d.pos = 0
|
||||
return
|
||||
}
|
||||
id := string(parts[1])
|
||||
length, err := strconv.Atoi(string(parts[2]))
|
||||
if err != nil {
|
||||
logError("Error parsing length from: " + string(parts[2]))
|
||||
d.pos = 0
|
||||
return
|
||||
}
|
||||
if length != len(parts[3]) {
|
||||
// This can happen if <data> actually contains a CR/LF.
|
||||
// Return without resetting d.pos to continue reading
|
||||
// in the full <data>.
|
||||
return
|
||||
}
|
||||
s, err := d.findSocket(id)
|
||||
if err != nil {
|
||||
logError(err.Error())
|
||||
d.pos = 0
|
||||
return
|
||||
}
|
||||
logDebug("GOT +EVENT:SocketDown," + id + "," + string(parts[2]))
|
||||
d.pos = 0
|
||||
data := make([]byte, len(parts[3]))
|
||||
copy(data, parts[3])
|
||||
s.rx <- data
|
||||
|
||||
case bytes.HasPrefix(sofar, []byte("OK")):
|
||||
d.pos = 0
|
||||
logDebug("GOT OK")
|
||||
d.ok <- true
|
||||
|
||||
case bytes.HasPrefix(sofar, []byte("ERROR")):
|
||||
d.pos = 0
|
||||
logDebug("GOT ERROR")
|
||||
errStr := getErrStr(d.last)
|
||||
d.err <- errors.New(errStr)
|
||||
|
||||
case bytes.HasPrefix(sofar, []byte("+EVENT:")):
|
||||
d.pos = 0
|
||||
event := sofar[len("+EVENT:"):]
|
||||
d.handle(event)
|
||||
|
||||
default:
|
||||
// Catch everything else and store in d.last
|
||||
d.pos = 0
|
||||
size := len(sofar)
|
||||
if size > 0 {
|
||||
d.last = make([]byte, size)
|
||||
copy(d.last, sofar[:size])
|
||||
logDebug("GOT LINE " + string(d.last))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) serviceUART() {
|
||||
for {
|
||||
d.uartMu.Lock()
|
||||
for d.uart.Buffered() > 0 {
|
||||
if d.pos >= len(d.buf) {
|
||||
println("Trying to write past buffer")
|
||||
d.pos = 0
|
||||
break
|
||||
}
|
||||
var err error
|
||||
d.buf[d.pos], err = d.uart.ReadByte()
|
||||
if err == nil {
|
||||
d.pos++
|
||||
d.processUART()
|
||||
}
|
||||
}
|
||||
d.uartMu.Unlock()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) NetConnect(params *netlink.ConnectParams) error {
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.uart = d.cfg.Uart
|
||||
d.uart.Configure(machine.UARTConfig{
|
||||
BaudRate: d.cfg.BaudRate,
|
||||
TX: d.cfg.Tx,
|
||||
RX: d.cfg.Rx,
|
||||
})
|
||||
|
||||
go d.serviceUART()
|
||||
|
||||
fmt.Printf("\r\n")
|
||||
fmt.Printf("TinyGo Combo-AT WiFi network device driver\r\n")
|
||||
|
||||
fmt.Printf("\r\n")
|
||||
fmt.Printf("Driver version : %s\r\n", drivers.Version)
|
||||
|
||||
if len(params.Ssid) == 0 {
|
||||
return netlink.ErrMissingSSID
|
||||
}
|
||||
|
||||
// AT Test to see if device is alive
|
||||
if err := d.execute("AT", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Disable echo
|
||||
if err := d.execute("ATE0", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get FW version
|
||||
if err := d.execute("AT+GMR", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Combo-AT firmware version : %s\r\n", d.getFWVersion())
|
||||
|
||||
// Get/save MAC addresses
|
||||
if err := d.execute("AT+CIPSTAMAC_DEF?", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
d.saveMAC()
|
||||
fmt.Printf("MAC address : %s\r\n", d.mac.String())
|
||||
|
||||
// Set country code US
|
||||
if err := d.execute("AT+WCOUNTRY=4", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get country code
|
||||
if err := d.execute("AT+WCOUNTRY?", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("WiFi country code : %s\r\n", d.getCountry())
|
||||
|
||||
// Set Wi-Fi working mode to STA and save to flash
|
||||
if err := d.execute("AT+WMODE=1,1", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Connect to Wifi AP (keep trying until connected)
|
||||
fmt.Printf("\r\n")
|
||||
cmd := "AT+WJAP=" + params.Ssid + "," + params.Passphrase
|
||||
|
||||
for {
|
||||
fmt.Printf("Connecting to WiFi SSID '%s'...", params.Ssid)
|
||||
if err := d.execute(cmd, 20000); err != nil {
|
||||
fmt.Printf("FAILED (%s)\r\n", err.Error())
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("CONNECTED\r\n")
|
||||
|
||||
// Automatically reconnect to Wi-Fi after power on
|
||||
if err := d.execute("AT+WAUTOCONN=1", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get/save IP/gateway addresses
|
||||
if err := d.execute("AT+WJAP?", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
d.saveIP()
|
||||
|
||||
fmt.Printf("\r\n")
|
||||
fmt.Printf("DHCP-assigned IP : %s\r\n", d.ip)
|
||||
fmt.Printf("DHCP-assigned gateway : %s\r\n", d.gateway)
|
||||
fmt.Printf("\r\n")
|
||||
|
||||
// Set socket receiving mode to active
|
||||
if err := d.execute("AT+SOCKETRECVCFG=1", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *device) NetDisconnect() {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
// Disconnect from WiFi AP
|
||||
d.execute("AT+WDISCONNECT", 1000)
|
||||
}
|
||||
|
||||
func (d *device) NetNotify(cb func(netlink.Event)) {
|
||||
fmt.Printf("\r\n%s\r\n", netlink.ErrNotSupported)
|
||||
}
|
||||
|
||||
func (d *device) GetHardwareAddr() (net.HardwareAddr, error) {
|
||||
return d.mac, nil
|
||||
}
|
||||
|
||||
func (d *device) _getHostByName(name string) (ip netip.Addr, err error) {
|
||||
if err = d.execute("AT+WDOMAIN="+name, 1000); err != nil {
|
||||
return
|
||||
}
|
||||
ipStr := split(d.last, 1, ":", "host by name")
|
||||
return netip.ParseAddr(ipStr)
|
||||
}
|
||||
|
||||
func (d *device) GetHostByName(name string) (ip netip.Addr, err error) {
|
||||
|
||||
// If it's already a dotted-network address, and not a host name,
|
||||
// return it
|
||||
ip, err = netip.ParseAddr(name)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
return d._getHostByName(name)
|
||||
}
|
||||
|
||||
func (d *device) Addr() (netip.Addr, error) {
|
||||
return d.ip, nil
|
||||
}
|
||||
|
||||
func (d *device) Socket(domain, stype, protocol int) (int, error) {
|
||||
|
||||
switch domain {
|
||||
case netdev.AF_INET:
|
||||
default:
|
||||
return -1, netdev.ErrFamilyNotSupported
|
||||
}
|
||||
|
||||
switch {
|
||||
case protocol == netdev.IPPROTO_TCP && stype == netdev.SOCK_STREAM:
|
||||
case protocol == netdev.IPPROTO_TLS && stype == netdev.SOCK_STREAM:
|
||||
case protocol == netdev.IPPROTO_UDP && stype == netdev.SOCK_DGRAM:
|
||||
default:
|
||||
return -1, netdev.ErrProtocolNotSupported
|
||||
}
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
// Search for empty slot in sockets array
|
||||
for fd, s := range d.sockets {
|
||||
if s == nil {
|
||||
// Found one
|
||||
d.sockets[fd] = &socket{
|
||||
protocol: protocol,
|
||||
rx: make(chan []byte, 10),
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
}
|
||||
|
||||
return -1, netdev.ErrNoMoreSockets
|
||||
}
|
||||
|
||||
func (d *device) Bind(sockfd int, ip netip.AddrPort) error {
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.laddr = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *device) Connect(sockfd int, host string, ip netip.AddrPort) error {
|
||||
|
||||
var addr string
|
||||
var cmd string
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
addr = ip.Addr().String()
|
||||
} else {
|
||||
ip, err := d._getHostByName(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr = ip.String()
|
||||
}
|
||||
port := strconv.Itoa(int(ip.Port()))
|
||||
|
||||
switch s.protocol {
|
||||
case netdev.IPPROTO_UDP:
|
||||
cmd = "AT+SOCKET=2," + addr + "," + port
|
||||
case netdev.IPPROTO_TCP:
|
||||
cmd = "AT+SOCKET=4," + addr + "," + port
|
||||
case netdev.IPPROTO_TLS:
|
||||
cmd = "AT+SOCKET=7," + addr + "," + port
|
||||
}
|
||||
|
||||
if cmd == "" {
|
||||
return netdev.ErrProtocolNotSupported
|
||||
}
|
||||
|
||||
if err := d.execute(cmd, 20000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.id = split(d.last, 1, "=", "connection ID")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *device) Listen(sockfd, backlog int) error {
|
||||
|
||||
// TODO Creating a TCP server socket isn't working when in STA mode,
|
||||
// TODO returning error "Socket bind error".
|
||||
// TODO The reference example shows a TCP server example in AP mode.
|
||||
|
||||
/*
|
||||
var cmd string
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port := strconv.Itoa(int(s.laddr.Port()))
|
||||
|
||||
switch s.protocol {
|
||||
case netdev.IPPROTO_UDP:
|
||||
cmd = "AT+SOCKET=1," + port
|
||||
case netdev.IPPROTO_TCP:
|
||||
cmd = "AT+SOCKET=3," + port
|
||||
}
|
||||
|
||||
if cmd == "" {
|
||||
return netdev.ErrProtocolNotSupported
|
||||
}
|
||||
|
||||
if err := d.execute(cmd, 20000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.id = split(d.last, 1, "=", "connection ID")
|
||||
*/
|
||||
|
||||
return netdev.ErrNotSupported
|
||||
}
|
||||
|
||||
func (d *device) Accept(sockfd int) (int, netip.AddrPort, error) {
|
||||
return 0, netip.AddrPort{}, netdev.ErrNotSupported
|
||||
}
|
||||
|
||||
func (d *device) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("AT+SOCKETSEND=%s,%d", s.id, len(buf))
|
||||
|
||||
if err := d.send(cmd, 1000); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// AT+SOCKETSEND will sub-packet send data into 1024-byte chunks,
|
||||
// automatically, so send the full buffer in one shot, even if it's
|
||||
// bigger than 1024 bytes.
|
||||
|
||||
d.uartMu.Lock()
|
||||
n, err := d.uart.Write(buf)
|
||||
d.uartMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Expecting "OK" after good send, or "ERROR"
|
||||
|
||||
t := time.NewTicker(time.Duration(1000) * time.Millisecond)
|
||||
defer t.Stop()
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
return 0, errors.New("Timed out")
|
||||
case <-d.ok:
|
||||
return n, nil
|
||||
case err = <-d.err:
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 1. Use leftover data first
|
||||
if len(s.remainder) > 0 {
|
||||
n := copy(buf, s.remainder)
|
||||
s.remainder = s.remainder[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// 2. Get new data from the channel
|
||||
data, ok := <-s.rx
|
||||
if !ok {
|
||||
// Socket closed, return EOF
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// 3. Copy data, handle leftovers
|
||||
n := copy(buf, data)
|
||||
if n < len(data) {
|
||||
s.remainder = data[n:]
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (d *device) Close(sockfd int) error {
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete socket only if connection was successful (s.id is set)
|
||||
if s.id != "" {
|
||||
cmd := fmt.Sprintf("AT+SOCKETDEL=%s", s.id)
|
||||
if err = d.execute(cmd, 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.sockets[sockfd] = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *device) SetSockOpt(sockfd, level, opt int, value interface{}) error {
|
||||
return netdev.ErrNotSupported
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package comboat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var errStrings = map[int]string{
|
||||
|
||||
// System framework related error codes
|
||||
|
||||
0: "success",
|
||||
1: "The command is not supported (the combo framework contains the command but the current platform has not transplanted or adapted to support it)",
|
||||
2: "The command parameters contain unsupported operations (the current platform only supports some operations for this command)",
|
||||
3: "The instruction format is incorrect (this refers to the wrong number of parameters, for example, two parameters are required, but only one parameter is entered)",
|
||||
4: "Parameter error (the content of the parameter is wrong, for example, a number between 0 and 9 is required, but 10 or xyz is passed in, which is a parameter error)",
|
||||
5: "Parameter length error (command length exceeds the maximum supported length)",
|
||||
31: "The current command has not ended and needs to report the status asynchronously. This value is used by the state machine to determine the use of the command and no message is returned.",
|
||||
32: "Unknown error (or unhandled error type)",
|
||||
|
||||
// Common error codes
|
||||
|
||||
33: "malloc error",
|
||||
34: "Failed to read buf",
|
||||
35: "Failed to write buf",
|
||||
36: "Configuration error (configuration error loaded from memory, for example, we set port -1 for OTA upgrade, and check port error when executing AT+OTA, then configuration error will be reported)",
|
||||
37: "Failed to create task",
|
||||
38: "Flash read and write failure",
|
||||
39: "Serial port configuration error, unsupported baud rate",
|
||||
40: "Serial port configuration error, unsupported data bits",
|
||||
41: "Serial port configuration error, unsupported stop bit",
|
||||
42: "Serial port configuration error, unsupported parity bit",
|
||||
43: "Serial port configuration error, unsupported flow control",
|
||||
44: "Serial port configuration failed",
|
||||
45: "Wrong username/password",
|
||||
46: "Low power mode error or unsupported low power mode",
|
||||
47: "Uninitialized configuration data error (including io mapping data)",
|
||||
63: "General error code (without other information)",
|
||||
|
||||
// Wi-Fi related error codes
|
||||
|
||||
64: "Wi-Fi not initialized or initialization failed",
|
||||
65: "Wi-Fi mode error (unable to connect to Wi-Fi in single AP mode)",
|
||||
66: "Wi-Fi connection failed",
|
||||
67: "Wi-Fi connection successful, error in obtaining IP (DHCP)",
|
||||
68: "Failed to obtain encryption method",
|
||||
69: "The specified AP was not found.",
|
||||
70: "Wi-Fi scan start failed",
|
||||
71: "Wi-Fi scan timeout",
|
||||
72: "Failed to enable AP hotspot",
|
||||
73: "Failed to obtain the Wi-Fi information of the router or the AP information that you enabled yourself",
|
||||
74: "The network card (STA/AP) is not running",
|
||||
75: "Wi-Fi country code error (unsupported Wi-Fi country code)",
|
||||
76: "The current network configuration mode is wrong.",
|
||||
95: "Wi-Fi connection unknown error",
|
||||
|
||||
// Socket related error codes
|
||||
|
||||
96: "Failed to create socket",
|
||||
97: "Socket connection failed",
|
||||
98: "DNS Failure",
|
||||
99: "The socket status is wrong (for example, TCP is not connected yet)",
|
||||
100: "Socket type error",
|
||||
101: "Socket send failed",
|
||||
102: "Socket receive failed",
|
||||
103: "Socket monitoring thread creation failed",
|
||||
104: "Socket bind error",
|
||||
105: "The current connection cannot be transparently linked (wrong socket type or number)",
|
||||
106: "PING test failed (all packets lost)",
|
||||
107: "Wi-Fi country code error (unsupported Wi-Fi country code)",
|
||||
108: "SSL Config Error",
|
||||
109: "SSL verification error (usually caused by unsupported SSL encryption type or certificate error)",
|
||||
127: "Unknown socket error",
|
||||
}
|
||||
|
||||
func getErrStr(errLine []byte) (errStr string) {
|
||||
errStr = "Can't parse ERROR response"
|
||||
tokens := bytes.Split(errLine, []byte(":"))
|
||||
if len(tokens) > 1 {
|
||||
errCode, err := strconv.Atoi(string(tokens[1]))
|
||||
if err == nil {
|
||||
errStr = errStrings[errCode]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
+2
-2
@@ -19,7 +19,7 @@ type OneWireDevice interface {
|
||||
Write(uint8)
|
||||
Read() uint8
|
||||
Select([]uint8) error
|
||||
Сrc8([]uint8, int) uint8
|
||||
Сrc8([]uint8) uint8
|
||||
}
|
||||
|
||||
// Device wraps a connection to an 1-Wire devices.
|
||||
@@ -69,7 +69,7 @@ func (d Device) ReadTemperatureRaw(romid []uint8) ([]uint8, error) {
|
||||
for i := 0; i < 9; i++ {
|
||||
spb[i] = d.owd.Read()
|
||||
}
|
||||
if d.owd.Сrc8(spb, 8) != spb[8] {
|
||||
if d.owd.Сrc8(spb) != 0 {
|
||||
return nil, errReadTemperature
|
||||
}
|
||||
return spb[:2:2], nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build tinygo && (rp2040 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
|
||||
//go:build tinygo && (rp2040 || rp2350 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
|
||||
|
||||
// Implementation based on:
|
||||
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
// Package ens160 provides a driver for the ScioSense ENS160 digital gas sensor.
|
||||
//
|
||||
// Datasheet: https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
|
||||
package ens160
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 30 * time.Millisecond
|
||||
shortTimeout = 1 * time.Millisecond
|
||||
)
|
||||
|
||||
// Conversion constants for environment data compensation.
|
||||
const (
|
||||
kelvinOffsetMilli = 273150 // 273.15 K in milli-units
|
||||
tempRawFactor = 64 // As per datasheet for TEMP_IN
|
||||
humRawFactor = 512 // As per datasheet for RH_IN
|
||||
milliFactor = 1000 // For converting from milli-units
|
||||
roundingTerm = milliFactor / 2 // For rounding before integer division
|
||||
)
|
||||
|
||||
// validityStrings provides human-readable descriptions for validity flags.
|
||||
var validityStrings = [...]string{
|
||||
ValidityNormalOperation: "normal operation",
|
||||
ValidityWarmUpPhase: "warm-up phase, wait ~3 minutes for valid data",
|
||||
ValidityInitialStartUpPhase: "initial start-up phase, wait ~1 hour for valid data",
|
||||
ValidityInvalidOutput: "invalid output",
|
||||
}
|
||||
|
||||
// Device wraps an I2C connection to an ENS160 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C // I²C implementation
|
||||
addr uint16 // 7‑bit bus address, promoted to uint16 per drivers.I2C
|
||||
|
||||
// shadow registers / last measurements
|
||||
lastTvocPPB uint16
|
||||
lastEco2PPM uint16
|
||||
lastAqiUBA uint8
|
||||
lastValidity uint8 // Store the latest validity status
|
||||
|
||||
// pre‑allocated buffers
|
||||
wbuf [5]byte // longest write: reg + 4 bytes (TEMP+RH)
|
||||
rbuf [5]byte // longest read: DATA burst (5 bytes)
|
||||
}
|
||||
|
||||
// New returns a new ENS160 driver.
|
||||
func New(bus drivers.I2C, addr uint16) *Device {
|
||||
if addr == 0 {
|
||||
addr = DefaultAddress
|
||||
}
|
||||
return &Device{
|
||||
bus: bus,
|
||||
addr: addr,
|
||||
lastValidity: ValidityInvalidOutput,
|
||||
}
|
||||
}
|
||||
|
||||
// Connected returns whether a ENS160 has been found.
|
||||
func (d *Device) Connected() bool {
|
||||
d.wbuf[0] = regPartID
|
||||
err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:2])
|
||||
return err == nil && d.rbuf[0] == LowPartID && d.rbuf[1] == HighPartID
|
||||
}
|
||||
|
||||
// Configure sets up the device for reading.
|
||||
func (d *Device) Configure() error {
|
||||
// 1. Soft-reset. The device will automatically enter IDLE mode.
|
||||
if err := d.write1(regOpMode, ModeReset); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(defaultTimeout)
|
||||
|
||||
// 2. Clear GPR registers, then go to STANDARD mode.
|
||||
if err := d.write1(regCommand, cmdClrGPR); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(defaultTimeout)
|
||||
|
||||
if err := d.write1(regOpMode, ModeStandard); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(defaultTimeout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// calculateTempRaw converts temperature from milli-degrees Celsius to the sensor's raw format.
|
||||
func calculateTempRaw(tempMilliC int32) uint16 {
|
||||
// Clip temperature
|
||||
const (
|
||||
minC = -40 * 1000
|
||||
maxC = 85 * 1000
|
||||
)
|
||||
if tempMilliC < minC {
|
||||
tempMilliC = minC
|
||||
} else if tempMilliC > maxC {
|
||||
tempMilliC = maxC
|
||||
}
|
||||
|
||||
// Integer fixed-point conversion to format required by the sensor.
|
||||
// Formula from datasheet: T_IN = (T_ambient_C + 273.15) * 64
|
||||
return uint16((((tempMilliC + kelvinOffsetMilli) * tempRawFactor) + roundingTerm) / milliFactor)
|
||||
}
|
||||
|
||||
// calculateHumRaw converts relative humidity from milli-percent to the sensor's raw format.
|
||||
func calculateHumRaw(rhMilliPct int32) uint16 {
|
||||
// Clip humidity
|
||||
if rhMilliPct < 0 {
|
||||
rhMilliPct = 0
|
||||
} else if rhMilliPct > 100*1000 {
|
||||
rhMilliPct = 100 * 1000
|
||||
}
|
||||
|
||||
// Integer fixed-point conversion to format required by the sensor.
|
||||
// Formula from datasheet: RH_IN = (RH_ambient_% * 512)
|
||||
return uint16(((rhMilliPct * humRawFactor) + roundingTerm) / milliFactor)
|
||||
}
|
||||
|
||||
// SetEnvDataMilli sets the ambient temperature and humidity for compensation.
|
||||
//
|
||||
// tempMilliC is the temperature in milli-degrees Celsius.
|
||||
// rhMilliPct is the relative humidity in milli-percent.
|
||||
func (d *Device) SetEnvDataMilli(tempMilliC, rhMilliPct int32) error {
|
||||
tempRaw := calculateTempRaw(tempMilliC)
|
||||
humRaw := calculateHumRaw(rhMilliPct)
|
||||
|
||||
d.wbuf[0] = regTempIn // start address (auto‑increment)
|
||||
binary.LittleEndian.PutUint16(d.wbuf[1:3], tempRaw)
|
||||
binary.LittleEndian.PutUint16(d.wbuf[3:5], humRaw)
|
||||
|
||||
return d.bus.Tx(d.addr, d.wbuf[:5], nil)
|
||||
}
|
||||
|
||||
// Update refreshes the concentration measurements.
|
||||
func (d *Device) Update(which drivers.Measurement) error {
|
||||
if which&drivers.Concentration == 0 {
|
||||
return nil // nothing requested
|
||||
}
|
||||
|
||||
const maxTries = 1000
|
||||
var (
|
||||
status uint8
|
||||
validity uint8
|
||||
)
|
||||
var gotData bool
|
||||
|
||||
// Poll DEVICE_STATUS until NEWDAT or timeout
|
||||
for range maxTries {
|
||||
var err error
|
||||
status, err = d.read1(regStatus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status&statusSTATER != 0 {
|
||||
return errors.New("ENS160: error (STATER set)")
|
||||
}
|
||||
validity = (status & statusValidityMask) >> statusValidityShift
|
||||
|
||||
if status&statusNEWDAT != 0 {
|
||||
gotData = true
|
||||
break // Always break when data available
|
||||
}
|
||||
time.Sleep(shortTimeout)
|
||||
}
|
||||
if !gotData {
|
||||
return errors.New("ENS160: timeout waiting for NEWDAT")
|
||||
}
|
||||
|
||||
// Burst-read data regardless of validity state
|
||||
d.wbuf[0] = regAQI
|
||||
if err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:5]); err != nil {
|
||||
return errors.New("ENS160: burst read failed")
|
||||
}
|
||||
|
||||
d.lastAqiUBA = d.rbuf[0]
|
||||
d.lastTvocPPB = binary.LittleEndian.Uint16(d.rbuf[1:3])
|
||||
d.lastEco2PPM = binary.LittleEndian.Uint16(d.rbuf[3:5])
|
||||
d.lastValidity = validity // Store the validity status
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TVOC returns the last total‑VOC concentration in parts‑per‑billion.
|
||||
func (d *Device) TVOC() uint16 { return d.lastTvocPPB }
|
||||
|
||||
// ECO2 returns the last equivalent CO₂ concentration in parts‑per‑million.
|
||||
func (d *Device) ECO2() uint16 { return d.lastEco2PPM }
|
||||
|
||||
// AQI returns the last Air‑Quality Index according to UBA (1–5).
|
||||
func (d *Device) AQI() uint8 { return d.lastAqiUBA }
|
||||
|
||||
// Validity returns the current operating state of the sensor.
|
||||
func (d *Device) Validity() uint8 {
|
||||
return d.lastValidity
|
||||
}
|
||||
|
||||
// ValidityString returns a human-readable string describing the current validity status.
|
||||
func (d *Device) ValidityString() string {
|
||||
if int(d.lastValidity) < len(validityStrings) {
|
||||
return validityStrings[d.lastValidity]
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// write1 writes a single byte to a register.
|
||||
func (d *Device) write1(reg, val uint8) error {
|
||||
d.wbuf[0] = reg
|
||||
d.wbuf[1] = val
|
||||
return d.bus.Tx(d.addr, d.wbuf[:2], nil)
|
||||
}
|
||||
|
||||
// read1 reads a single byte from a register.
|
||||
func (d *Device) read1(reg uint8) (uint8, error) {
|
||||
d.wbuf[0] = reg
|
||||
if err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:1]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.rbuf[0], nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package ens160
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCalculateTempRaw(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
tempMilliC int32
|
||||
expectedRaw uint16
|
||||
}{
|
||||
{"25°C", 25000, 19082},
|
||||
{"-10.5°C", -10500, 16810},
|
||||
{"Min temp", -40000, 14922},
|
||||
{"Below min", -50000, 14922},
|
||||
{"Max temp", 85000, 22922},
|
||||
{"Above max", 90000, 22922},
|
||||
{"Zero", 0, 17482},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
raw := calculateTempRaw(tc.tempMilliC)
|
||||
if raw != tc.expectedRaw {
|
||||
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateHumRaw(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
rhMilliPct int32
|
||||
expectedRaw uint16
|
||||
}{
|
||||
{"50%", 50000, 25600},
|
||||
{"0%", 0, 0},
|
||||
{"100%", 100000, 51200},
|
||||
{"Below 0%", -10000, 0},
|
||||
{"Above 100%", 110000, 51200},
|
||||
{"33.3%", 33300, 17050},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
raw := calculateHumRaw(tc.rhMilliPct)
|
||||
if raw != tc.expectedRaw {
|
||||
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package ens160
|
||||
|
||||
// DefaultAddress is the default I2C address for the ENS160 when the ADDR pin is
|
||||
// connected to high (3.3V). When connected to low (GND), the address is 0x52.
|
||||
const DefaultAddress = 0x53
|
||||
|
||||
// Registers
|
||||
const (
|
||||
regPartID = 0x00
|
||||
regOpMode = 0x10
|
||||
regConfig = 0x11
|
||||
regCommand = 0x12
|
||||
regTempIn = 0x13
|
||||
regRhIn = 0x15
|
||||
regStatus = 0x20
|
||||
regAQI = 0x21
|
||||
regTVOC = 0x22
|
||||
regECO2 = 0x24
|
||||
regDataT = 0x30
|
||||
regDataRH = 0x32
|
||||
regMISR = 0x38
|
||||
regGPRWrite = 0x40
|
||||
regGPRRead = 0x48
|
||||
)
|
||||
|
||||
// Operating modes
|
||||
const (
|
||||
ModeDeepSleep = 0x00
|
||||
ModeIdle = 0x01
|
||||
ModeStandard = 0x02
|
||||
ModeReset = 0xF0
|
||||
)
|
||||
|
||||
// Status register bits
|
||||
const (
|
||||
statusSTATAS = 1 << 7
|
||||
statusSTATER = 1 << 6
|
||||
|
||||
statusValidityMask = 0x0C
|
||||
statusValidityShift = 2
|
||||
|
||||
statusNEWDAT = 1 << 1
|
||||
statusNEWGPR = 1 << 0
|
||||
)
|
||||
|
||||
// Validity flags
|
||||
const (
|
||||
ValidityNormalOperation = 0x00
|
||||
ValidityWarmUpPhase = 0x01 // need ~3 minutes until valid data
|
||||
ValidityInitialStartUpPhase = 0x02 // need ~1 hour until valid data
|
||||
ValidityInvalidOutput = 0x03
|
||||
)
|
||||
|
||||
// Commands
|
||||
const (
|
||||
cmdNOP = 0x00
|
||||
cmdGetAppVer = 0x0E
|
||||
cmdClrGPR = 0xCC
|
||||
)
|
||||
|
||||
// Part IDs
|
||||
const (
|
||||
LowPartID = 0x60
|
||||
HighPartID = 0x01
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
// This example demonstrates ENS160 usage.
|
||||
//
|
||||
// Wiring:
|
||||
// - VCC to 3.3V, GND to ground
|
||||
// - SDA to board SDA, SCL to board SCL
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/ens160"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: 400 * machine.KHz,
|
||||
})
|
||||
if err != nil {
|
||||
println("Failed to configure I2C:", err)
|
||||
}
|
||||
|
||||
dev := ens160.New(machine.I2C0, ens160.DefaultAddress)
|
||||
|
||||
connected := dev.Connected()
|
||||
if !connected {
|
||||
println("ENS160 not detected")
|
||||
return
|
||||
}
|
||||
println("ENS160 detected")
|
||||
|
||||
if err := dev.Configure(); err != nil {
|
||||
println("Failed to configure ENS160:", err)
|
||||
}
|
||||
|
||||
for {
|
||||
err := dev.Update(drivers.Concentration)
|
||||
if err != nil {
|
||||
println("Error reading ENS160: %v\n", err)
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
println(
|
||||
"AQI:", dev.AQI(),
|
||||
"TVOC:", dev.TVOC(),
|
||||
"eCO2:", dev.ECO2(),
|
||||
"Validity:", dev.ValidityString(),
|
||||
)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
func main() {
|
||||
console_example.RunFor(
|
||||
flash.NewSPI(
|
||||
&machine.SPI1,
|
||||
machine.SPI1,
|
||||
machine.SPI1_SDO_PIN,
|
||||
machine.SPI1_SDI_PIN,
|
||||
machine.SPI1_SCK_PIN,
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
func main() {
|
||||
println("GPS I2C Example")
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
ublox := gps.NewI2C(machine.I2C0)
|
||||
ublox := gps.NewI2CWithAddress(machine.I2C0, gps.UBLOX_I2C_ADDRESS)
|
||||
parser := gps.NewParser()
|
||||
var fix gps.Fix
|
||||
for {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/honeyhsc"
|
||||
)
|
||||
|
||||
// Data taken from https://github.com/rodan/honeywell_hsc_ssc_i2c/blob/master/hsc_ssc_i2c.cpp
|
||||
// these defaults are valid for the HSCMRNN030PA2A3 chip
|
||||
const (
|
||||
i2cAddress = 0x28
|
||||
// 10%
|
||||
outputMinimum = 0x666
|
||||
// 90% of 2^14 - 1
|
||||
outputMax = 0x399A
|
||||
// min is 0 for sensors that give absolute values
|
||||
pressureMin = 0
|
||||
// 30psi (and we want results in millipascals)
|
||||
// pressureMax = 206842.7
|
||||
pressureMax = 206843 * 1000
|
||||
)
|
||||
|
||||
func main() {
|
||||
bus := machine.I2C0
|
||||
err := bus.Configure(machine.I2CConfig{
|
||||
Frequency: 400_000, // 100kHz minimum and 400kHz I2C maximum clock. 50 to 800 for SPI.
|
||||
SDA: machine.I2C0_SDA_PIN,
|
||||
SCL: machine.I2C0_SCL_PIN,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
sensor := honeyhsc.NewDevI2C(bus, i2cAddress, outputMinimum, outputMax, pressureMin, pressureMax)
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
const measuremask = drivers.Pressure | drivers.Temperature
|
||||
err := sensor.Update(measuremask)
|
||||
if err != nil {
|
||||
println("error updating measurements:", err.Error())
|
||||
continue
|
||||
}
|
||||
P := sensor.Pressure()
|
||||
T := sensor.Temperature()
|
||||
println("pressure:", P, "temperature:", T)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ina219"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
dev := ina219.New(machine.I2C0)
|
||||
dev.Configure()
|
||||
|
||||
for {
|
||||
busVoltage, shuntVoltage, current, power, err := dev.Measurements()
|
||||
if err != nil {
|
||||
println("Error reading measurements", err)
|
||||
}
|
||||
|
||||
println("Bus Voltage:", busVoltage, "V")
|
||||
println("Shunt Voltage:", shuntVoltage/100, "mV")
|
||||
println("Current:", current, "mA")
|
||||
println("Power:", power, "mW")
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
+12
-3
@@ -14,9 +14,18 @@ func main() {
|
||||
i2c.Configure(machine.I2CConfig{SCL: machine.SCL1_PIN, SDA: machine.SDA1_PIN})
|
||||
|
||||
accel := lis3dh.New(i2c)
|
||||
accel.Address = lis3dh.Address1 // address on the Circuit Playground Express
|
||||
accel.Configure()
|
||||
accel.SetRange(lis3dh.RANGE_2_G)
|
||||
err := accel.Configure(lis3dh.Config{
|
||||
Address: lis3dh.Address1, // address on the Circuit Playground Express
|
||||
})
|
||||
for err != nil {
|
||||
println("could not configure LIS3DH:", err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
err = accel.SetRange(lis3dh.RANGE_2_G)
|
||||
for err != nil {
|
||||
println("could not set acceleration range:", err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
println(accel.Connected())
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/lsm303dlhc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// LSM303DLHC is connected to the I2C0 bus on Adafruit Feather M4 via pins: 20(SDA) and 21(SCL).
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
sensor := lsm303dlhc.New(machine.I2C0)
|
||||
//default settings
|
||||
err := sensor.Configure(lsm303dlhc.Configuration{
|
||||
AccelPowerMode: lsm303dlhc.ACCEL_POWER_NORMAL,
|
||||
AccelRange: lsm303dlhc.ACCEL_RANGE_2G,
|
||||
AccelDataRate: lsm303dlhc.ACCEL_DATARATE_100HZ,
|
||||
MagPowerMode: lsm303dlhc.MAG_POWER_NORMAL,
|
||||
MagSystemMode: lsm303dlhc.MAG_SYSTEM_CONTINUOUS,
|
||||
MagDataRate: lsm303dlhc.MAG_DATARATE_10HZ,
|
||||
})
|
||||
if err != nil {
|
||||
for {
|
||||
println("Failed to configure", err.Error())
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
accel_x, accel_y, accel_z, err := sensor.ReadAcceleration()
|
||||
if err != nil {
|
||||
println("Failed to read accel", err.Error())
|
||||
}
|
||||
println("ACCEL_X:", accel_x, " ACCEL_Y:", accel_y, " ACCEL_Z:", accel_z)
|
||||
|
||||
mag_x, mag_y, mag_z, err := sensor.ReadMagneticField()
|
||||
if err != nil {
|
||||
println("Failed to read mag", err.Error())
|
||||
}
|
||||
println("MAG_X:", mag_x, " MAG_Y:", mag_y, " MAG_Z:", mag_z)
|
||||
|
||||
pitch, roll, _ := sensor.ReadPitchRoll()
|
||||
println("Pitch:", float32(pitch), " Roll:", float32(roll))
|
||||
|
||||
heading, _ := sensor.ReadCompass()
|
||||
println("Heading:", float32(heading), "degrees")
|
||||
|
||||
temp, _ := sensor.ReadTemperature()
|
||||
println("Temperature:", float32(temp)/1000, "*C")
|
||||
|
||||
println("\n")
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/max6675"
|
||||
)
|
||||
|
||||
// example for reading temperature from a thermocouple
|
||||
func main() {
|
||||
// Pins are for an Adafruit Feather nRF52840 Express
|
||||
machine.D5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
machine.D5.High()
|
||||
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 1_000_000,
|
||||
SCK: machine.SPI0_SCK_PIN,
|
||||
SDI: machine.SPI0_SDI_PIN,
|
||||
})
|
||||
|
||||
thermocouple := max6675.NewDevice(machine.SPI0, machine.D5)
|
||||
|
||||
for {
|
||||
temp, err := thermocouple.Read()
|
||||
if err != nil {
|
||||
println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%0.02f C : %0.02f F\n", temp, (temp*9/5)+32)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
// examples/net/webclient (for HTTP)
|
||||
// examples/net/tlsclient (for HTTPS)
|
||||
|
||||
//go:build ninafw || wioterminal
|
||||
//go:build ninafw || wioterminal || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// examples/net/webclient (for HTTP)
|
||||
// examples/net/tlsclient (for HTTPS)
|
||||
|
||||
//go:build ninafw || wioterminal
|
||||
//go:build ninafw || wioterminal || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// examples/net/webclient (for HTTP)
|
||||
// examples/net/tlsclient (for HTTPS)
|
||||
|
||||
//go:build ninafw || wioterminal
|
||||
//go:build ninafw || wioterminal || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// examples/net/webclient (for HTTP)
|
||||
// examples/net/tlsclient (for HTTPS)
|
||||
|
||||
//go:build ninafw || wioterminal
|
||||
//go:build ninafw || wioterminal || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Note: It may be necessary to increase the stack size when using
|
||||
// paho.mqtt.golang. Use the -stack-size=4KB command line option.
|
||||
|
||||
//go:build ninafw || wioterminal || challenger_rp2040
|
||||
//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Note: It may be necessary to increase the stack size when using
|
||||
// paho.mqtt.golang. Use the -stack-size=4KB command line option.
|
||||
|
||||
//go:build ninafw || wioterminal || challenger_rp2040
|
||||
//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// It creates a UDP connection to request the current time and parse the
|
||||
// response from a NTP server. The system time is set to NTP time.
|
||||
|
||||
//go:build ninafw || wioterminal || challenger_rp2040
|
||||
//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build ninafw || wioterminal
|
||||
//go:build ninafw || wioterminal || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
// func. This forces segments to connect and run concurrently, which is a good
|
||||
// test of the underlying driver's ability to handle concurrent connections.
|
||||
|
||||
//go:build ninafw || wioterminal
|
||||
//go:build ninafw || wioterminal || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//
|
||||
// nc -lk 8080
|
||||
|
||||
//go:build ninafw || wioterminal || challenger_rp2040
|
||||
//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//
|
||||
// nc -lk 8080
|
||||
|
||||
//go:build ninafw || wioterminal || challenger_rp2040 || pico
|
||||
//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
|
||||
|
||||
//go:build ninafw || wioterminal
|
||||
//go:build ninafw || wioterminal || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// }
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
//go:build ninafw || wioterminal
|
||||
//go:build ninafw || wioterminal || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// Note: It may be necessary to increase the stack size when using
|
||||
// "golang.org/x/net/websocket". Use the -stack-size=4KB command line option.
|
||||
|
||||
//go:build ninafw || wioterminal
|
||||
//go:build ninafw || wioterminal || comboat_fw
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
spi = &machine.SPI0
|
||||
spi = machine.SPI0
|
||||
sckPin = machine.SPI0_SCK_PIN
|
||||
sdoPin = machine.SPI0_SDO_PIN
|
||||
sdiPin = machine.SPI0_SDI_PIN
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
spi = &machine.SPI1
|
||||
spi = machine.SPI1
|
||||
sckPin = machine.SDCARD_SCK_PIN
|
||||
sdoPin = machine.SDCARD_SDO_PIN
|
||||
sdiPin = machine.SDCARD_SDI_PIN
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
spi = &machine.SPI0
|
||||
spi = machine.SPI0
|
||||
sckPin = machine.SPI0_SCK_PIN
|
||||
sdoPin = machine.SPI0_SDO_PIN
|
||||
sdiPin = machine.SPI0_SDI_PIN
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
spi = &machine.SDCARD_SPI
|
||||
spi = machine.SDCARD_SPI
|
||||
sckPin = machine.SDCARD_SCK_PIN
|
||||
sdoPin = machine.SDCARD_SDO_PIN
|
||||
sdiPin = machine.SDCARD_SDI_PIN
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
spi = &machine.SPI0
|
||||
spi = machine.SPI0
|
||||
sckPin = machine.SPI0_SCK_PIN
|
||||
sdoPin = machine.SPI0_SDO_PIN
|
||||
sdiPin = machine.SPI0_SDI_PIN
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
spi = &machine.SPI0
|
||||
spi = machine.SPI0
|
||||
sckPin = machine.SPI0_SCK_PIN
|
||||
sdoPin = machine.SPI0_SDO_PIN
|
||||
sdiPin = machine.SPI0_SDI_PIN
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
spi = &machine.SPI2
|
||||
spi = machine.SPI2
|
||||
sckPin = machine.SCK2
|
||||
sdoPin = machine.SDO2
|
||||
sdiPin = machine.SDI2
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/seesaw"
|
||||
)
|
||||
|
||||
// example reading the position of a rotary encoder (4991) powered by a seesaw
|
||||
// https://learn.adafruit.com/adafruit-i2c-qt-rotary-encoder/arduino
|
||||
func main() {
|
||||
// This assumes you are using an Adafruit QT Py RP2040 for its Stemma QT connector
|
||||
// https://www.adafruit.com/product/4900
|
||||
i2c := machine.I2C1
|
||||
i2c.Configure(machine.I2CConfig{
|
||||
SCL: machine.I2C1_QT_SCL_PIN,
|
||||
SDA: machine.I2C1_QT_SDA_PIN,
|
||||
})
|
||||
|
||||
dev := seesaw.New(i2c)
|
||||
dev.Address = 0x36
|
||||
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
pos, err := dev.GetEncoderPosition(0, false)
|
||||
if err != nil {
|
||||
println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
println(pos)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"math/rand/v2"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/sharpmem"
|
||||
)
|
||||
|
||||
var (
|
||||
// example wiring using a nice!view and nice!nano:
|
||||
// (view) (nano)
|
||||
// MOSI --> P0.24
|
||||
// SCK ---> P0.22
|
||||
// GND ---> GND
|
||||
// VCC ---> 3.3V
|
||||
// CS ----> P0.06
|
||||
|
||||
spi = machine.SPI0
|
||||
|
||||
sckPin = machine.SPI0_SCK_PIN // SCK
|
||||
sdoPin = machine.SPI0_SDO_PIN // MOSI
|
||||
sdiPin = machine.SPI0_SDI_PIN // (any pin)
|
||||
|
||||
csPin = machine.P0_06 // CS
|
||||
)
|
||||
|
||||
func main() {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err := spi.Configure(machine.SPIConfig{
|
||||
Frequency: 2000000,
|
||||
SCK: sckPin,
|
||||
SDO: sdoPin,
|
||||
SDI: sdiPin,
|
||||
Mode: 0,
|
||||
LSBFirst: true,
|
||||
})
|
||||
if err != nil {
|
||||
println("spi.Configure() failed, error:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
display := sharpmem.New(spi, csPin)
|
||||
|
||||
cfg := sharpmem.ConfigLS011B7DH03
|
||||
display.Configure(cfg)
|
||||
|
||||
// clear the display before first use
|
||||
err = display.Clear()
|
||||
if err != nil {
|
||||
println("display.Clear() failed, error:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// random boxes pop into and out of existence
|
||||
for {
|
||||
x0 := int16(rand.IntN(int(cfg.Width - 7)))
|
||||
y0 := int16(rand.IntN(int(cfg.Height - 7)))
|
||||
|
||||
for x2 := int16(0); x2 < 16; x2++ {
|
||||
x2 := x2
|
||||
c := color.RGBA{R: 255, G: 255, B: 255, A: 255}
|
||||
|
||||
if x2 >= 8 {
|
||||
// effectively erases the box after it showed up
|
||||
x2 = x2 - 8
|
||||
c = color.RGBA{R: 0, G: 0, B: 0, A: 255}
|
||||
}
|
||||
|
||||
for x := int16(0); x < x2; x++ {
|
||||
for y := int16(0); y < 8; y++ {
|
||||
display.SetPixel(x0+x, y0+y, c)
|
||||
}
|
||||
}
|
||||
|
||||
err = display.Display()
|
||||
if err != nil {
|
||||
println("display.Display() failed, error:", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
time.Sleep(33 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
|
||||
display := ssd1306.NewI2C(machine.I2C0)
|
||||
display.Configure(ssd1306.Config{
|
||||
Address: ssd1306.Address_128_32,
|
||||
Width: 128,
|
||||
Height: 32,
|
||||
})
|
||||
|
||||
display.ClearDisplay()
|
||||
|
||||
x := int16(0)
|
||||
y := int16(0)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == 127 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == 31 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// This example shows how to use 128x64 display over I2C
|
||||
// Tested on Seeeduino XIAO Expansion Board https://wiki.seeedstudio.com/Seeeduino-XIAO-Expansion-Board/
|
||||
//
|
||||
// According to manual, I2C address of the display is 0x78, but that's 8-bit address.
|
||||
// TinyGo operates on 7-bit addresses and respective 7-bit address would be 0x3C, which we use below.
|
||||
//
|
||||
// To learn more about different types of I2C addresses, please see following page
|
||||
// https://www.totalphase.com/support/articles/200349176-7-bit-8-bit-and-10-bit-I2C-Slave-Addressing
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
|
||||
display := ssd1306.NewI2C(machine.I2C0)
|
||||
display.Configure(ssd1306.Config{
|
||||
Address: 0x3C,
|
||||
Width: 128,
|
||||
Height: 64,
|
||||
})
|
||||
|
||||
display.ClearDisplay()
|
||||
|
||||
x := int16(0)
|
||||
y := int16(0)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == 127 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == 63 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
// This example shows how to use SSD1306 OLED display driver over I2C and SPI.
|
||||
//
|
||||
// Check the `newSSD1306Display()` functions for I2C and SPI initializations.
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"image/color"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
display := newSSD1306Display()
|
||||
display.ClearDisplay()
|
||||
|
||||
w, h := display.Size()
|
||||
x := int16(0)
|
||||
y := int16(0)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
|
||||
traceTime := time.Now().UnixMilli() + 1000
|
||||
frames := 0
|
||||
ms := runtime.MemStats{}
|
||||
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == w-1 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == h-1 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
|
||||
frames++
|
||||
now := time.Now().UnixMilli()
|
||||
if now >= traceTime {
|
||||
runtime.ReadMemStats(&ms)
|
||||
println("TS", now, "| FPS", frames, "| HeapInuse", ms.HeapInuse)
|
||||
traceTime = now + 1000
|
||||
frames = 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//go:build xiao_ble
|
||||
|
||||
// This initializes SSD1306 OLED display driver over I2C.
|
||||
//
|
||||
// Seeed XIAO BLE board + SSD1306 128x32 I2C OLED display.
|
||||
//
|
||||
// Wiring:
|
||||
// - XIAO GND -> OLED GND
|
||||
// - XIAO 3v3 -> OLED VCC
|
||||
// - XIAO D4 (SDA) -> OLED SDA
|
||||
// - XIAO D5 (SCL) -> OLED SCK
|
||||
//
|
||||
// For your case:
|
||||
// - Connect the display to I2C pins on your board.
|
||||
// - Adjust I2C address and display size as needed.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func newSSD1306Display() *ssd1306.Device {
|
||||
machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: 400 * machine.KHz,
|
||||
SDA: machine.SDA0_PIN,
|
||||
SCL: machine.SCL0_PIN,
|
||||
})
|
||||
display := ssd1306.NewI2C(machine.I2C0)
|
||||
display.Configure(ssd1306.Config{
|
||||
Address: ssd1306.Address_128_32, // or ssd1306.Address
|
||||
Width: 128,
|
||||
Height: 32, // or 64
|
||||
})
|
||||
return display
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//go:build thumby
|
||||
|
||||
// This initializes SSD1306 OLED display driver over SPI.
|
||||
//
|
||||
// Thumby board has a tiny built-in 72x40 display.
|
||||
//
|
||||
// As the display is built-in, no wiring is needed.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func newSSD1306Display() *ssd1306.Device {
|
||||
machine.SPI0.Configure(machine.SPIConfig{})
|
||||
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
|
||||
display.Configure(ssd1306.Config{
|
||||
Width: 72,
|
||||
Height: 40,
|
||||
ResetCol: ssd1306.ResetValue{28, 99},
|
||||
ResetPage: ssd1306.ResetValue{0, 5},
|
||||
})
|
||||
return display
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//go:build xiao_rp2040
|
||||
|
||||
// This initializes SSD1306 OLED display driver over SPI.
|
||||
//
|
||||
// Seeed XIAO RP2040 board + SSD1306 128x64 SPI OLED display.
|
||||
//
|
||||
// Wiring:
|
||||
// - XIAO GND -> OLED GND
|
||||
// - XIAO 3v3 -> OLED VCC
|
||||
// - XIAO D8 (SCK) -> OLED D0
|
||||
// - XIAO D10 (SDO) -> OLED D1
|
||||
// - XIAO D4 -> OLED RES
|
||||
// - XIAO D5 -> OLED DC
|
||||
// - XIAO D6 -> OLED CS
|
||||
//
|
||||
// For your case:
|
||||
// - Connect the display to SPI pins on your board.
|
||||
// - Adjust RES, DC and CS pins as needed.
|
||||
// - Adjust SPI frequency as needed.
|
||||
// - Adjust display size as needed.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func newSSD1306Display() *ssd1306.Device {
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 50 * machine.MHz,
|
||||
})
|
||||
display := ssd1306.NewSPI(machine.SPI0, machine.D5, machine.D4, machine.D6)
|
||||
display.Configure(ssd1306.Config{
|
||||
Width: 128,
|
||||
Height: 64,
|
||||
})
|
||||
return display
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 8000000,
|
||||
})
|
||||
display := ssd1306.NewSPI(machine.SPI0, machine.P8, machine.P7, machine.P9)
|
||||
display.Configure(ssd1306.Config{
|
||||
Width: 128,
|
||||
Height: 64,
|
||||
})
|
||||
|
||||
display.ClearDisplay()
|
||||
|
||||
x := int16(64)
|
||||
y := int16(32)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == 127 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == 63 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// This example using the SSD1306 OLED display over SPI on the Thumby board
|
||||
// A very tiny 72x40 display.
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.SPI0.Configure(machine.SPIConfig{})
|
||||
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
|
||||
display.Configure(ssd1306.Config{
|
||||
Width: 72,
|
||||
Height: 40,
|
||||
ResetCol: ssd1306.ResetValue{28, 99},
|
||||
ResetPage: ssd1306.ResetValue{0, 5},
|
||||
})
|
||||
|
||||
display.ClearDisplay()
|
||||
|
||||
x := int16(36)
|
||||
y := int16(20)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == 71 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == 39 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/tmc2209"
|
||||
)
|
||||
|
||||
func main() {
|
||||
uart := machine.UART0
|
||||
comm := tmc2209.NewUARTComm(*uart, 0)
|
||||
// Create an instance of the TMC2209 with UART communication
|
||||
tmc := tmc2209.NewTMC2209(comm, 0x00) // Replace 0x00 with the appropriate address
|
||||
|
||||
// Set up the TMC2209 driver
|
||||
err := tmc.Setup()
|
||||
if err != nil {
|
||||
println("Failed to set up TMC2209: ", err)
|
||||
}
|
||||
|
||||
// Write to a register (example: setting a register value)
|
||||
err = tmc.WriteRegister(0x10, 0x12345678) // Replace 0x10 with the register address and 0x12345678 with the value
|
||||
if err != nil {
|
||||
println("Failed to write register:", err)
|
||||
}
|
||||
|
||||
// Read from a register (example: reading a register value)
|
||||
value, err := tmc.ReadRegister(0x10)
|
||||
if err != nil {
|
||||
println("Failed to read register: ", err)
|
||||
}
|
||||
|
||||
// Output the read value
|
||||
println("Register value: ", value)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Connects to SPI1 on a RP2040 (Pico)
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/tmc5160"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Step 1. Setup your protocol. SPI setup shown below
|
||||
spi := machine.SPI1
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 12000000, // Upto 12 MHZ is pretty stable. Reduce to 5 or 6 Mhz if you are experiencing issues
|
||||
Mode: 3,
|
||||
LSBFirst: false,
|
||||
})
|
||||
// Step 2. Set up all associated Pins
|
||||
csPin0 := machine.GPIO13
|
||||
csPin0.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
enn0 := machine.GPIO18
|
||||
enn0.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
// csPins is a map of all chip select pins in a multi driver setup.
|
||||
//Only one pin csPin0 mapped to "0"is shown in this example, but add more mappings as required
|
||||
csPins := map[uint8]machine.Pin{0: csPin0}
|
||||
//bind csPin to driverAdddress
|
||||
driverAddress := uint8(0) // Let's assume we are working with driver at address 0x01
|
||||
// Step 3. Bind the communication interface to the protocol
|
||||
comm := tmc5160.NewSPIComm(spi, csPins)
|
||||
// Step 4. Define your stepper like this below
|
||||
//stepper := tmc5160.NewStepper(angle , gearRatio vSupply rCoil , lCoil , iPeak , rSense , mSteps, fclk )
|
||||
stepper := tmc5160.NewDefaultStepper() // Default Stepper should be used only for testing.
|
||||
// Step 5. Instantiate your driver
|
||||
driver := tmc5160.NewDriver(
|
||||
comm,
|
||||
driverAddress,
|
||||
enn0,
|
||||
stepper)
|
||||
|
||||
// Setting and getting mode
|
||||
rampMode := tmc5160.NewRAMPMODE(comm, driverAddress)
|
||||
err := rampMode.SetMode(tmc5160.PositioningMode)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mode, err := rampMode.GetMode()
|
||||
if err != nil {
|
||||
println("Error getting mode:", err)
|
||||
} else {
|
||||
println("Current Mode:", mode)
|
||||
}
|
||||
|
||||
// Read GCONF register
|
||||
GCONF := tmc5160.NewGCONF()
|
||||
gconfVal, err := driver.ReadRegister(tmc5160.GCONF)
|
||||
// Uppack the register to get all the bits and bytes of the register
|
||||
GCONF.Unpack(gconfVal)
|
||||
//E.g. MultiStepFlit is retrieved from the GCONF register
|
||||
println("GCONF:MultiStepFlit:", GCONF.MultistepFilt)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Capacitive touch sensing example.
|
||||
//
|
||||
// This capacitive touch sensor works by charging a normal GPIO pin, then slowly
|
||||
// discharging it through a 1MΩ resistor and seeing how long it takes to go from
|
||||
// high to low.
|
||||
//
|
||||
// Use as follows:
|
||||
// - Change touchPin below as needed.
|
||||
// - Connect this pin to some metal surface, like a piece of aluminimum foil.
|
||||
// Make sure this surface is covered (using paper, Scotch tape, etc).
|
||||
// - Also connect this same pin to ground through a 1MΩ resistor.
|
||||
//
|
||||
// This sensor is very sensitive to noise on the power source, so you should
|
||||
// probably try to limit it by running from a battery for example. Especially
|
||||
// phone chargers can produce a lot of noise.
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/touch/capacitive"
|
||||
)
|
||||
|
||||
const touchPin = machine.GP16 // Raspberry Pi Pico
|
||||
|
||||
func main() {
|
||||
time.Sleep(time.Second * 2)
|
||||
println("start")
|
||||
|
||||
led := machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
led.Low()
|
||||
|
||||
// Configure the array of GPIO pins used for capacitive touch sensing.
|
||||
// We're using only one pin.
|
||||
array := capacitive.NewArray([]machine.Pin{touchPin})
|
||||
|
||||
// Use a dynamic threshold, meaning the GPIO pin is automatically calibrated
|
||||
// and re-calibrated to adjust for varying environments (e.g. changing
|
||||
// humidity).
|
||||
array.SetDynamicThreshold(100)
|
||||
|
||||
wasTouching := false
|
||||
for i := uint32(0); ; i++ {
|
||||
// Update the GPIO pin. This must be called very often.
|
||||
array.Update()
|
||||
touching := array.Touching(0)
|
||||
|
||||
// Indicate whether the pin is touched via the LED.
|
||||
led.Set(touching)
|
||||
|
||||
// Print something when the touch state changed.
|
||||
if wasTouching != touching {
|
||||
wasTouching = touching
|
||||
if touching {
|
||||
println(" touch!")
|
||||
} else {
|
||||
println(" release!")
|
||||
}
|
||||
}
|
||||
|
||||
// Print the current value, as a debugging aid. It's not really meant to
|
||||
// be used directly.
|
||||
if i%128 == 32 {
|
||||
println("touch value:", array.RawValue(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/waveshare-epd/epd1in54"
|
||||
"tinygo.org/x/tinyfont"
|
||||
"tinygo.org/x/tinyfont/gophers"
|
||||
)
|
||||
|
||||
var (
|
||||
spi0 = machine.SPI0
|
||||
cs = machine.D10
|
||||
dc = machine.D9
|
||||
rst = machine.D6
|
||||
busy = machine.D5
|
||||
|
||||
black = color.RGBA{R: 1, G: 1, B: 1, A: 255}
|
||||
)
|
||||
|
||||
func main() {
|
||||
display := epd1in54.New(spi0, cs, dc, rst, busy)
|
||||
|
||||
display.LDirInit(epd1in54.Config{})
|
||||
display.Clear()
|
||||
display.ClearBuffer()
|
||||
|
||||
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 150, 0, "A B C", black, tinyfont.ROTATION_90)
|
||||
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 100, 0, "D E F", black, tinyfont.ROTATION_90)
|
||||
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 50, 0, "G H I", black, tinyfont.ROTATION_90)
|
||||
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 0, 0, "J K L", black, tinyfont.ROTATION_90)
|
||||
|
||||
display.Display()
|
||||
display.Sleep()
|
||||
}
|
||||
@@ -46,6 +46,10 @@ var DefaultDeviceIdentifier = DeviceIdentifierFunc(func(id JedecID) Attrs {
|
||||
return GD25Q16C()
|
||||
case 0xC84017:
|
||||
return GD25Q64C()
|
||||
case 0x856015:
|
||||
return P25Q16H()
|
||||
case 0xEF4014:
|
||||
return W25Q80DV()
|
||||
case 0xEF4015:
|
||||
return W25Q16JVIQ()
|
||||
case 0xEF4016:
|
||||
@@ -239,6 +243,24 @@ func GD25Q64C() Attrs {
|
||||
}
|
||||
}
|
||||
|
||||
// Settings for the Puya P25Q16H 2MiB SPI flash.
|
||||
// Datasheet: https://files.seeedstudio.com/wiki/github_weiruanexample/Flash_P25Q16H-UXH-IR_Datasheet.pdf
|
||||
func P25Q16H() Attrs {
|
||||
return Attrs{
|
||||
TotalSize: 1 << 21, // 2 MiB
|
||||
StartUp: 5000 * time.Microsecond,
|
||||
JedecID: JedecID{0x85, 0x60, 0x15},
|
||||
MaxClockSpeedMHz: 55,
|
||||
QuadEnableBitMask: 0x02,
|
||||
HasSectorProtection: true,
|
||||
SupportsFastRead: true,
|
||||
SupportsQSPI: true,
|
||||
SupportsQSPIWrites: true,
|
||||
WriteStatusSplit: true,
|
||||
SingleStatusByte: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Settings for the Winbond W25Q16JV-IQ 2MiB SPI flash. Note that JV-IM has a
|
||||
// different .memory_type (0x70) Datasheet:
|
||||
// https://www.winbond.com/resource-files/w25q16jv%20spi%20revf%2005092017.pdf
|
||||
@@ -380,6 +402,25 @@ func W25Q80DL() Attrs {
|
||||
TotalSize: 1 << 20, // 1 MiB
|
||||
StartUp: 5000 * time.Microsecond,
|
||||
JedecID: JedecID{0xEF, 0x60, 0x14},
|
||||
MaxClockSpeedMHz: 80,
|
||||
QuadEnableBitMask: 0x02,
|
||||
HasSectorProtection: false,
|
||||
SupportsFastRead: true,
|
||||
SupportsQSPI: true,
|
||||
SupportsQSPIWrites: false,
|
||||
WriteStatusSplit: false,
|
||||
SingleStatusByte: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Settings for the Winbond W25Q80DV 2MiB SPI flash.
|
||||
// Datasheet:
|
||||
// https://www.winbond.com/resource-files/w25q80dv%20dl_revh_10022015.pdf
|
||||
func W25Q80DV() Attrs {
|
||||
return Attrs{
|
||||
TotalSize: 1 << 21, // 2 MiB
|
||||
StartUp: 5000 * time.Microsecond,
|
||||
JedecID: JedecID{0xEF, 0x40, 0x14},
|
||||
MaxClockSpeedMHz: 104,
|
||||
QuadEnableBitMask: 0x02,
|
||||
HasSectorProtection: false,
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
module tinygo.org/x/drivers
|
||||
|
||||
go 1.18
|
||||
|
||||
replace tinygo.org/x/drivers/mcp9808 => /home/kasterby/Documents/drivers/mcp9808
|
||||
go 1.22.1
|
||||
|
||||
toolchain go1.23.1
|
||||
|
||||
|
||||
require (
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0
|
||||
github.com/frankban/quicktest v1.10.2
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/orsinium-labs/tinymath v1.1.0
|
||||
github.com/soypat/natiu-mqtt v0.5.1
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
|
||||
golang.org/x/net v0.33.0
|
||||
tinygo.org/x/tinyfont v0.3.0
|
||||
tinygo.org/x/tinyterm v0.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.5.2 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/kr/text v0.1.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
|
||||
)
|
||||
|
||||
@@ -3,8 +3,9 @@ github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
||||
github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk=
|
||||
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
@@ -12,12 +13,15 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/orsinium-labs/tinymath v1.1.0 h1:KomdsyLHB7vE3f1nRAJF2dyf1m/gnM2HxfTeV1vS5UA=
|
||||
github.com/orsinium-labs/tinymath v1.1.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A=
|
||||
github.com/soypat/natiu-mqtt v0.5.1 h1:rwaDmlvjzD2+3MCOjMZc4QEkDkNwDzbct2TJbpz+TPc=
|
||||
github.com/soypat/natiu-mqtt v0.5.1/go.mod h1:xEta+cwop9izVCW7xOx2W+ct9PRMqr0gNVkvBPnQTc4=
|
||||
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
||||
tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
|
||||
|
||||
+7
-1
@@ -69,10 +69,16 @@ func NewUART(uart drivers.UART) Device {
|
||||
}
|
||||
|
||||
// NewI2C creates a new I2C GPS connection.
|
||||
// Uses the default i2c address (0x42) for backward compatibility reasons.
|
||||
func NewI2C(bus drivers.I2C) Device {
|
||||
return NewI2CWithAddress(bus, I2C_ADDRESS)
|
||||
}
|
||||
|
||||
// NewI2CWithAddress creates a new I2C GPS connection on the provided address
|
||||
func NewI2CWithAddress(bus drivers.I2C, i2cAddress uint16) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
address: I2C_ADDRESS,
|
||||
address: i2cAddress,
|
||||
buffer: make([]byte, bufferSize),
|
||||
bufIdx: bufferSize,
|
||||
sentence: strings.Builder{},
|
||||
|
||||
+4
-1
@@ -96,7 +96,10 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
|
||||
fix.Speed = findSpeed(fields[7])
|
||||
fix.Heading = findHeading(fields[8])
|
||||
date := findDate(fields[9])
|
||||
fix.Time = fix.Time.AddDate(date.Year(), int(date.Month()), date.Day())
|
||||
fix.Time = date.Add(time.Duration(fix.Time.Hour())*time.Hour +
|
||||
time.Duration(fix.Time.Minute())*time.Minute +
|
||||
time.Duration(fix.Time.Second())*time.Second +
|
||||
time.Duration(fix.Time.Nanosecond())*time.Nanosecond)
|
||||
|
||||
return fix, nil
|
||||
}
|
||||
|
||||
@@ -70,15 +70,15 @@ func TestParseRMC(t *testing.T) {
|
||||
t.Error("should have errInvalidRMCSentence error")
|
||||
}
|
||||
|
||||
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,130522,0.0,E,D*2B"
|
||||
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,010622,0.0,E,D*2B"
|
||||
fix, err := p.Parse(val)
|
||||
if err != nil {
|
||||
t.Error("should have parsed")
|
||||
}
|
||||
|
||||
c.Assert(fix.Time.Year(), qt.Equals, 2022)
|
||||
c.Assert(fix.Time.Month(), qt.Equals, time.May)
|
||||
c.Assert(fix.Time.Day(), qt.Equals, 13)
|
||||
c.Assert(fix.Time.Month(), qt.Equals, time.June)
|
||||
c.Assert(fix.Time.Day(), qt.Equals, 1)
|
||||
c.Assert(fix.Time.Hour(), qt.Equals, 20)
|
||||
c.Assert(fix.Time.Minute(), qt.Equals, 35)
|
||||
c.Assert(fix.Time.Second(), qt.Equals, 22)
|
||||
|
||||
+5
-1
@@ -4,7 +4,11 @@ package gps
|
||||
|
||||
// The I2C address which this device listens to.
|
||||
const (
|
||||
I2C_ADDRESS = 0x42
|
||||
// To ensure backward compatibility
|
||||
I2C_ADDRESS = UBLOX_I2C_ADDRESS
|
||||
|
||||
UBLOX_I2C_ADDRESS = 0x42
|
||||
PA1010D_I2C_ADDRESS = 0x10
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
package honeyhsc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
errSensorMissing = errors.New("hsc: not connected")
|
||||
errDiagnostic = errors.New("hsc: diagnostic error")
|
||||
)
|
||||
|
||||
const (
|
||||
measuremask = drivers.Pressure | drivers.Temperature
|
||||
statusMask = 0b1100_0000
|
||||
statusOffset = 6
|
||||
)
|
||||
|
||||
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
|
||||
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
|
||||
type DevI2C struct {
|
||||
bus drivers.I2C
|
||||
dev
|
||||
addr uint8
|
||||
buf [6]byte
|
||||
}
|
||||
|
||||
// NewDevI2C creates and returns a new DevI2C that communicates with an HSC device over the provided I2C bus.
|
||||
// Parameters:
|
||||
// - bus: the I2C bus to use.
|
||||
// - addr: the 7-bit I2C address of the sensor.
|
||||
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
|
||||
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
|
||||
//
|
||||
// The returned DevI2C will use these calibration parameters to convert raw bridge counts to pressure.
|
||||
func NewDevI2C(bus drivers.I2C, addr, outMin, outMax uint16, pMin, pMax int32) *DevI2C {
|
||||
h := &DevI2C{
|
||||
bus: bus,
|
||||
addr: uint8(addr),
|
||||
dev: dev{
|
||||
cmin: outMin,
|
||||
cmax: outMax,
|
||||
pmin: pMin,
|
||||
pmax: pMax,
|
||||
},
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the I2C-attached HSC device.
|
||||
// It performs an Update internally to get the latest temperature value.
|
||||
func (h *DevI2C) ReadTemperature() (int32, error) {
|
||||
err := h.Update(drivers.Temperature)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return h.Temperature(), nil
|
||||
}
|
||||
|
||||
// Update reads both temperature and pressure data from the I2C-attached HSC device when
|
||||
// the requested measurement mask includes pressure or temperature.
|
||||
// If neither pressure nor temperature is requested, Update is a no-op.
|
||||
func (d *DevI2C) Update(which drivers.Measurement) error {
|
||||
// Update performs an I2C transaction to read 4 bytes, parses the status bits, 14-bit bridge data and
|
||||
// temperature bits, and forwards them to the internal update routine. Any I2C transport error is returned,
|
||||
// as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
|
||||
if which&measuremask == 0 {
|
||||
return nil
|
||||
}
|
||||
rbuf := d.buf[:4]
|
||||
wbuf := d.buf[4:6]
|
||||
const reg = 0
|
||||
value := (d.addr << 1) | 1
|
||||
wbuf[0] = reg
|
||||
wbuf[1] = value
|
||||
err := d.bus.Tx(uint16(d.addr), wbuf, rbuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status := (rbuf[0] & statusMask) >> statusOffset
|
||||
bridgeData := (uint16(rbuf[0]&^statusMask) << 8) | uint16(rbuf[1])
|
||||
tempData := uint16(rbuf[2])<<8 | uint16(rbuf[3]&0xe0)>>5
|
||||
return d.dev.update(status, bridgeData, tempData)
|
||||
}
|
||||
|
||||
type pinout func(level bool)
|
||||
|
||||
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
|
||||
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
|
||||
type DevSPI struct {
|
||||
spi drivers.SPI
|
||||
cs pinout
|
||||
dev
|
||||
buf [4]byte
|
||||
}
|
||||
|
||||
// NewDevSPI creates and returns a new DevSPI that communicates with an HSC device over SPI.
|
||||
// Parameters:
|
||||
// - conn: the SPI connection to use.
|
||||
// - cs: a chip-select function that drives the device select line low/high.
|
||||
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
|
||||
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
|
||||
//
|
||||
// The function returns the constructed DevSPI and an error value (currently always nil).
|
||||
func NewDevSPI(conn drivers.SPI, cs pinout, outMin, outMax uint16, pMin, pMax int32) (*DevSPI, error) {
|
||||
h := &DevSPI{
|
||||
spi: conn,
|
||||
cs: cs,
|
||||
dev: dev{
|
||||
cmin: outMin,
|
||||
cmax: outMax,
|
||||
pmin: pMin,
|
||||
pmax: pMax,
|
||||
},
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the SPI-attached HSC device.
|
||||
// It performs an Update internally to get the latest temperature value.
|
||||
func (h *DevSPI) ReadTemperature() (int32, error) {
|
||||
err := h.Update(drivers.Temperature)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return h.Temperature(), nil
|
||||
}
|
||||
|
||||
// Update reads pressure and temperature data from the SPI-attached HSC device when the requested measurement mask includes
|
||||
// pressure or temperature. If neither pressure nor temperature is requested, Update is a no-op.
|
||||
func (h *DevSPI) Update(which drivers.Measurement) error {
|
||||
// It toggles the provided chip-select, performs an SPI transfer to read 4 bytes, parses the status bits,
|
||||
// 14-bit bridge data and temperature bits, and forwards them to the internal update routine. Any SPI
|
||||
// transport error is returned, as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
|
||||
if which&measuremask == 0 {
|
||||
return nil
|
||||
}
|
||||
buf := &h.buf
|
||||
h.cs(false)
|
||||
err := h.spi.Tx(nil, buf[:4])
|
||||
h.cs(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// First two bits are status bits.
|
||||
status := (buf[0] & statusMask) >> statusOffset
|
||||
bridgeData := (uint16(buf[0]&^statusMask) << 8) | uint16(buf[1])
|
||||
|
||||
tempData := uint16(buf[2])<<8 | uint16(buf[3]&0xe0)>>5
|
||||
return h.dev.update(status, bridgeData, tempData)
|
||||
}
|
||||
|
||||
type dev struct {
|
||||
pressure int32
|
||||
temp int32
|
||||
cmin, cmax uint16
|
||||
pmin, pmax int32
|
||||
}
|
||||
|
||||
// Pressure returns the most recently computed pressure value in millipascals (mPa).
|
||||
// The value is taken from the last successful Update.
|
||||
func (d *dev) Pressure() int32 {
|
||||
return d.pressure
|
||||
}
|
||||
|
||||
// Temperature returns the most recently read temperature value in milliKelvin (mC).
|
||||
// The value is taken from the last successful Update.
|
||||
func (d *dev) Temperature() int32 {
|
||||
return d.temp + 273_150
|
||||
}
|
||||
|
||||
// update interprets raw sensor fields (status, bridgeData, tempData) and updates the dev's stored
|
||||
// pressure and temperature. It returns errSensorMissing when the temperature raw value indicates no sensor
|
||||
// (tempData == math.MaxUint16), errDiagnostic when the status indicates a device diagnostic condition
|
||||
// (status == 3), or nil on success. Pressure is computed with integer arithmetic using the configured
|
||||
// cmin/cmax -> pmin/pmax linear mapping in order to avoid overflows.
|
||||
func (d *dev) update(status uint8, bridgeData, tempData uint16) error {
|
||||
if tempData == math.MaxUint16 {
|
||||
return errSensorMissing
|
||||
} else if status == 3 {
|
||||
return errDiagnostic
|
||||
}
|
||||
|
||||
// Take care not to overflow here.
|
||||
p := (int32(bridgeData)-int32(d.cmin))*(d.pmax-d.pmin)/int32(d.cmax-d.cmin) + d.pmin
|
||||
d.temp = int32(tempData)
|
||||
d.pressure = p
|
||||
return nil
|
||||
}
|
||||
+1
-1
@@ -167,7 +167,7 @@ func (d *Device) fillMatrixBuffer(x int16, y int16, r uint8, g uint8, b uint8) {
|
||||
if r > colorTresh {
|
||||
d.buffer[c][offsetR] |= 1 << bitSelect
|
||||
} else {
|
||||
d.buffer[c][offsetR] = d.buffer[c][offsetR] &^ 1 << bitSelect
|
||||
d.buffer[c][offsetR] &^= 1 << bitSelect
|
||||
}
|
||||
if g > colorTresh {
|
||||
d.buffer[(c+d.colorThirdStep)%d.colorDepth][offsetG] |= 1 << bitSelect
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
)
|
||||
|
||||
type spiDriver struct {
|
||||
bus machine.SPI
|
||||
bus *machine.SPI
|
||||
}
|
||||
|
||||
func NewSPI(bus machine.SPI, dc, cs, rst machine.Pin) *Device {
|
||||
func NewSPI(bus *machine.SPI, dc, cs, rst machine.Pin) *Device {
|
||||
return &Device{
|
||||
dc: dc,
|
||||
cs: cs,
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
)
|
||||
|
||||
type spiDriver struct {
|
||||
bus machine.SPI
|
||||
bus *machine.SPI
|
||||
}
|
||||
|
||||
func NewSPI(bus machine.SPI, dc, cs, rst machine.Pin) *Device {
|
||||
func NewSPI(bus *machine.SPI, dc, cs, rst machine.Pin) *Device {
|
||||
return &Device{
|
||||
dc: dc,
|
||||
cs: cs,
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package ina219
|
||||
|
||||
type Config struct {
|
||||
// BusVoltageRange sets the bus voltage range.
|
||||
BusVoltageRange BusVoltageRange
|
||||
|
||||
// PGA sets the programmable gain amplifier.
|
||||
PGA PGA
|
||||
|
||||
// BusADC sets the bus ADC resolution.
|
||||
BusADC BusADC
|
||||
|
||||
// ShuntADC sets the shunt ADC resolution.
|
||||
ShuntADC ShuntADC
|
||||
|
||||
// Mode sets the operating mode.
|
||||
Mode Mode
|
||||
|
||||
// Calibration sets the calibration value for the expected
|
||||
// voltage and current values.
|
||||
Calibration Calibration
|
||||
|
||||
// 1000 / uA per bit
|
||||
CurrentDivider float32
|
||||
|
||||
// 1mW per bit
|
||||
PowerMultiplier float32
|
||||
}
|
||||
|
||||
// RegisterValue returns the register value of the configuration.
|
||||
func (c *Config) RegisterValue() uint16 {
|
||||
return c.BusVoltageRange.RegisterValue() |
|
||||
c.PGA.RegisterValue() |
|
||||
c.BusADC.RegisterValue() |
|
||||
c.ShuntADC.RegisterValue() |
|
||||
c.Mode.RegisterValue()
|
||||
}
|
||||
|
||||
// Generate a new configuration from a register value.
|
||||
func NewConfig(config int16, calibration int16) Config {
|
||||
return Config{
|
||||
BusVoltageRange: BusVoltageRange(config >> 13 & 0x1),
|
||||
PGA: PGA(config >> 11 & 0x3),
|
||||
BusADC: BusADC(config >> 7 & 0xF),
|
||||
ShuntADC: ShuntADC(config >> 3 & 0xF),
|
||||
Mode: Mode(config & 0x7),
|
||||
Calibration: Calibration(calibration),
|
||||
}
|
||||
}
|
||||
|
||||
// Configurations from
|
||||
// https://github.com/adafruit/Adafruit_INA219/blob/master/Adafruit_INA219.cpp
|
||||
var (
|
||||
// Config32V2A is a configuration for a 32V 2A range.
|
||||
Config32V2A = Config{
|
||||
BusVoltageRange: Range32V,
|
||||
PGA: PGA8,
|
||||
BusADC: ADC12,
|
||||
ShuntADC: SADC12,
|
||||
Mode: ModeContShuntBus,
|
||||
Calibration: Calibration16V400mA,
|
||||
CurrentDivider: 10.0,
|
||||
PowerMultiplier: 2.0,
|
||||
}
|
||||
|
||||
// Config32V1A is a configuration for a 32V 1A range.
|
||||
Config32V1A = Config{
|
||||
BusVoltageRange: Range32V,
|
||||
PGA: PGA8,
|
||||
BusADC: ADC12,
|
||||
ShuntADC: SADC12,
|
||||
Mode: ModeContShuntBus,
|
||||
Calibration: Calibration32V1A,
|
||||
CurrentDivider: 25.0,
|
||||
PowerMultiplier: 0.8,
|
||||
}
|
||||
|
||||
// Config16V400mA is a configuration for a 16V 400mA range.
|
||||
Config16V400mA = Config{
|
||||
BusVoltageRange: Range16V,
|
||||
PGA: PGA1,
|
||||
BusADC: ADC12,
|
||||
ShuntADC: SADC12,
|
||||
Mode: ModeContShuntBus,
|
||||
Calibration: Calibration16V400mA,
|
||||
CurrentDivider: 20.0,
|
||||
PowerMultiplier: 1.0,
|
||||
}
|
||||
)
|
||||
|
||||
// BusVoltageRange is the bus voltage range.
|
||||
type BusVoltageRange int8
|
||||
|
||||
const (
|
||||
Range16V BusVoltageRange = 0 // 0-16V
|
||||
Range32V BusVoltageRange = 1 // 0-32V
|
||||
)
|
||||
|
||||
func (r BusVoltageRange) RegisterValue() uint16 {
|
||||
return uint16(r) << 13
|
||||
}
|
||||
|
||||
// PGA is the programmable gain amplifier.
|
||||
type PGA int8
|
||||
|
||||
const (
|
||||
PGA1 PGA = 0 // 40mV
|
||||
PGA2 PGA = 1 // 80mV
|
||||
PGA4 PGA = 2 // 160mV
|
||||
PGA8 PGA = 3 // 320mV
|
||||
)
|
||||
|
||||
func (p PGA) RegisterValue() uint16 {
|
||||
return uint16(p) << 11
|
||||
}
|
||||
|
||||
// BusADC is the bus ADC resolution.
|
||||
type BusADC int8
|
||||
|
||||
const (
|
||||
ADC9 BusADC = 0 // 9-bit
|
||||
ADC10 BusADC = 1 // 10-bit
|
||||
ADC11 BusADC = 2 // 11-bit
|
||||
ADC12 BusADC = 3 // 12-bit
|
||||
)
|
||||
|
||||
func (b BusADC) RegisterValue() uint16 {
|
||||
return uint16(b) << 7
|
||||
}
|
||||
|
||||
// ShuntADC is the shunt ADC resolution.
|
||||
type ShuntADC int8
|
||||
|
||||
const (
|
||||
SADC9 ShuntADC = 0 // 9-bit
|
||||
SADC10 ShuntADC = 1 // 10-bit
|
||||
SADC11 ShuntADC = 2 // 11-bit
|
||||
SADC12 ShuntADC = 3 // 12-bit
|
||||
)
|
||||
|
||||
func (s ShuntADC) RegisterValue() uint16 {
|
||||
return uint16(s) << 3
|
||||
}
|
||||
|
||||
// Mode is the operating mode.
|
||||
type Mode int8
|
||||
|
||||
const (
|
||||
ModePowerDown Mode = 0 // power-down
|
||||
ModeTrigShunt Mode = 1 // triggered shunt voltage
|
||||
ModeTrigBus Mode = 2 // triggered bus voltage
|
||||
ModeTrigShuntBus Mode = 3 // triggered shunt and bus voltage
|
||||
ModeADCOff Mode = 4 // ADC off
|
||||
ModeContShunt Mode = 5 // continuous shunt voltage
|
||||
ModeContBus Mode = 6 // continuous bus voltage
|
||||
ModeContShuntBus Mode = 7 // continuous shunt and bus voltage
|
||||
)
|
||||
|
||||
// ModeTriggered is a mask for triggered modes.
|
||||
const ModeTriggeredMask Mode = 0x4
|
||||
|
||||
// ModeTriggered returns true if the mode is a triggered mode.
|
||||
func ModeTriggered(m Mode) bool {
|
||||
return m != ModePowerDown && m&ModeTriggeredMask == 0
|
||||
}
|
||||
|
||||
func (m Mode) RegisterValue() uint16 {
|
||||
return uint16(m)
|
||||
}
|
||||
|
||||
// Calibration is the calibration register for the INA219. Values from:
|
||||
// https://github.com/adafruit/Adafruit_INA219/blob/master/Adafruit_INA219.cpp
|
||||
type Calibration uint16
|
||||
|
||||
const (
|
||||
Calibration32V2A Calibration = 4096
|
||||
Calibration32V1A Calibration = 10240
|
||||
Calibration16V400mA Calibration = 8192
|
||||
)
|
||||
|
||||
func (c Calibration) RegisterValue() uint16 {
|
||||
return uint16(c)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ina219
|
||||
|
||||
type ErrOverflow struct{}
|
||||
|
||||
func (e ErrOverflow) Error() string { return "overflow" }
|
||||
|
||||
type ErrNotReady struct{}
|
||||
|
||||
func (e ErrNotReady) Error() string { return "not ready" }
|
||||
|
||||
type ErrConfigMismatch struct{}
|
||||
|
||||
func (e ErrConfigMismatch) Error() string { return "config mismatch" }
|
||||
@@ -0,0 +1,202 @@
|
||||
package ina219
|
||||
|
||||
import (
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// An INA219 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
config Config
|
||||
}
|
||||
|
||||
// Create a new INA219 device with the default configuration
|
||||
// and the given I2C bus at the default address.
|
||||
//
|
||||
// Set Address after New to change the address.
|
||||
//
|
||||
// Call Configure after New to write the configuration to the
|
||||
// device. If you don't call Configure, the device may have a
|
||||
// different configuration and the power divider and current
|
||||
// multiplier are probably wrong.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
config: Config32V2A,
|
||||
}
|
||||
}
|
||||
|
||||
// Set the configuration for the device. This only changes the
|
||||
// configuration in memory, not on the device. Call Configure
|
||||
// to write the configuration to the device.
|
||||
func (d *Device) SetConfig(config Config) {
|
||||
d.config = config
|
||||
}
|
||||
|
||||
// Write the current configuration to the device.
|
||||
func (d *Device) Configure() (err error) {
|
||||
if err = d.WriteRegister(
|
||||
RegConfig,
|
||||
d.config.RegisterValue(),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = d.WriteRegister(
|
||||
RegCalibration,
|
||||
d.config.Calibration.RegisterValue(),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var readConfig Config
|
||||
// make sure the configuration is read back correctly
|
||||
if readConfig, err = d.ReadConfig(); err != nil {
|
||||
return
|
||||
} else if readConfig.RegisterValue() != d.config.RegisterValue() {
|
||||
err = ErrConfigMismatch{}
|
||||
} else if readConfig.Calibration.RegisterValue() != d.config.Calibration.RegisterValue() {
|
||||
err = ErrConfigMismatch{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Trigger a conversion. This is only necessary if the device is in
|
||||
// trigger mode. In continuous mode (the default), the device will
|
||||
// automatically trigger conversions and this has no effect. See
|
||||
// config.go.
|
||||
//
|
||||
// Triggering a conversion or reading the "power" register resets
|
||||
// the conversion ready bit.
|
||||
func (d *Device) Trigger() (err error) {
|
||||
// Only trigger if the mode is one of the triggered modes.
|
||||
if ModeTriggered(d.config.Mode) {
|
||||
err = d.WriteRegister(RegConfig, d.config.RegisterValue())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Measurements reads the bus voltage, shunt voltage, current, and power
|
||||
// from the device.
|
||||
func (d *Device) Measurements() (
|
||||
busVoltage int16,
|
||||
shuntVoltage int16,
|
||||
current float32,
|
||||
power float32,
|
||||
err error,
|
||||
) {
|
||||
// Attempt to read bus voltage first, so we can check for overflow
|
||||
// or conversion not ready.
|
||||
if busVoltage, err = d.BusVoltage(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Read the rest of the values, reading Power last, which resets
|
||||
// the conversion ready bit (relevant for triggered modes).
|
||||
if shuntVoltage, err = d.ShuntVoltage(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if current, err = d.Current(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if power, err = d.Power(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// BusVoltage reads the "bus" voltage in millivolts.
|
||||
//
|
||||
// It returns an error if the value is invalid due to overflow
|
||||
// or if the conversion is not ready yet. In a continuous mode
|
||||
// there should always be a measurement available after the
|
||||
// device is ready. See above notes on Trigger.
|
||||
func (d *Device) BusVoltage() (voltage int16, err error) {
|
||||
val, err := d.ReadRegister(RegBusVoltage)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// The overflow bit is set, so the values are invalid.
|
||||
if val&(1<<0) != 0 {
|
||||
err = ErrOverflow{}
|
||||
return
|
||||
}
|
||||
|
||||
// The conversion is not ready yet.
|
||||
if ModeTriggered(d.config.Mode) && val&(1<<1) != 0 {
|
||||
err = ErrNotReady{}
|
||||
return
|
||||
}
|
||||
|
||||
voltage = (int16(val) >> 3) * 4
|
||||
return
|
||||
}
|
||||
|
||||
// ShuntVoltage reads the "shunt" voltage in 100ths of a millivolt.
|
||||
func (d *Device) ShuntVoltage() (voltage int16, err error) {
|
||||
return d.ReadRegister(RegShuntVoltage)
|
||||
}
|
||||
|
||||
// Current reads the current in milliamps.
|
||||
func (d *Device) Current() (current float32, err error) {
|
||||
val, err := d.ReadRegister(RegCurrent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
current = float32(val) / d.config.CurrentDivider
|
||||
return
|
||||
}
|
||||
|
||||
// Power reads the power in milliwatts.
|
||||
func (d *Device) Power() (power float32, err error) {
|
||||
val, err := d.ReadRegister(RegPower)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
power = float32(val) * d.config.PowerMultiplier
|
||||
return
|
||||
}
|
||||
|
||||
// Read the configuration from the device.
|
||||
func (d *Device) ReadConfig() (config Config, err error) {
|
||||
var cfg, cal int16
|
||||
if cfg, err = d.ReadRegister(RegConfig); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cal, err = d.ReadRegister(RegCalibration); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
config = NewConfig(cfg, cal)
|
||||
return
|
||||
}
|
||||
|
||||
// Read a register from the device.
|
||||
func (d *Device) ReadRegister(reg uint8) (val int16, err error) {
|
||||
buf := make([]byte, 2)
|
||||
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), reg, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
val = int16(buf[0])<<8 | int16(buf[1]&0xff)
|
||||
return
|
||||
}
|
||||
|
||||
// Write to a register on the device.
|
||||
func (d *Device) WriteRegister(reg uint8, val uint16) error {
|
||||
buf := []byte{byte(val >> 8), byte(val & 0xff)}
|
||||
return legacy.WriteRegister(d.bus, uint8(d.Address), reg, buf)
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package ina219
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"tinygo.org/x/drivers/tester"
|
||||
)
|
||||
|
||||
func TestDefaultAddress(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Address, qt.Equals, uint16(Address))
|
||||
}
|
||||
|
||||
func TestBusVoltage(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegBusVoltage: (4200 << 3) / 4, // 4.2V
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
voltage, err := dev.BusVoltage()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(voltage, qt.Equals, int16(4200))
|
||||
})
|
||||
|
||||
t.Run("overflow", func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegBusVoltage: (1 >> 0), // overflow
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
_, err := dev.BusVoltage()
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
c.Assert(err, qt.ErrorMatches, ErrOverflow{}.Error())
|
||||
})
|
||||
|
||||
t.Run("not ready", func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegBusVoltage: ((4200 << 3) / 4) | (1 << 1), // not ready
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
dev.config.Mode = ModeTrigBus
|
||||
_, err := dev.BusVoltage()
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
c.Assert(err, qt.ErrorMatches, ErrNotReady{}.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestShuntVoltage(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegShuntVoltage: 0x1234,
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
voltage, err := dev.ShuntVoltage()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(voltage, qt.Equals, int16(0x1234))
|
||||
}
|
||||
|
||||
func TestCurrent(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegCurrent: 420 * 6.9, // 420mA
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
dev.config.CurrentDivider = 6.9
|
||||
current, err := dev.Current()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(current, qt.Equals, float32(420))
|
||||
}
|
||||
|
||||
func TestPower(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegPower: 420 / 0.8, // 420mW
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
dev.config.PowerMultiplier = 0.8
|
||||
power, err := dev.Power()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(power, qt.Equals, float32(420))
|
||||
}
|
||||
|
||||
func TestReadConfig(t *testing.T) {
|
||||
// use the default configurations
|
||||
for _, tc := range []Config{
|
||||
Config16V400mA,
|
||||
Config32V2A,
|
||||
Config32V1A,
|
||||
} {
|
||||
n := fmt.Sprintf("%x/%x", tc.RegisterValue(), tc.Calibration.RegisterValue())
|
||||
t.Run(n, func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegConfig: tc.RegisterValue(),
|
||||
RegCalibration: tc.Calibration.RegisterValue(),
|
||||
}
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
config, err := dev.ReadConfig()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(config.BusADC, qt.Equals, tc.BusADC)
|
||||
c.Assert(config.BusVoltageRange, qt.Equals, tc.BusVoltageRange)
|
||||
c.Assert(config.Calibration, qt.Equals, tc.Calibration)
|
||||
c.Assert(config.Mode, qt.Equals, tc.Mode)
|
||||
c.Assert(config.PGA, qt.Equals, tc.PGA)
|
||||
c.Assert(config.ShuntADC, qt.Equals, tc.ShuntADC)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteConfig(t *testing.T) {
|
||||
// use the default configurations
|
||||
for _, tc := range []Config{
|
||||
Config16V400mA,
|
||||
Config32V2A,
|
||||
Config32V1A,
|
||||
} {
|
||||
n := fmt.Sprintf("%x/%x", tc.RegisterValue(), tc.Calibration.RegisterValue())
|
||||
t.Run(n, func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
bus.AddDevice(fake)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegConfig: 0,
|
||||
RegCalibration: 0,
|
||||
}
|
||||
|
||||
dev := New(bus)
|
||||
dev.config = tc
|
||||
err := dev.Configure()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fake.Registers[RegConfig], qt.Equals, tc.RegisterValue())
|
||||
c.Assert(fake.Registers[RegCalibration], qt.Equals, tc.Calibration.RegisterValue())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetConfig(t *testing.T) {
|
||||
for _, tc := range []Config{
|
||||
Config16V400mA,
|
||||
Config32V2A,
|
||||
Config32V1A,
|
||||
} {
|
||||
n := fmt.Sprintf("%x/%x", tc.RegisterValue(), tc.Calibration.RegisterValue())
|
||||
t.Run(n, func(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
dev := New(bus)
|
||||
dev.SetConfig(tc)
|
||||
c.Assert(dev.config, qt.Equals, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrigger(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
bus.AddDevice(fake)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegConfig: Config32V2A.RegisterValue(),
|
||||
}
|
||||
|
||||
dev := New(bus)
|
||||
dev.config = Config32V2A
|
||||
dev.config.Mode = ModeTrigBus
|
||||
err := dev.Trigger()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fake.Registers[RegConfig], qt.Equals, dev.config.RegisterValue())
|
||||
}
|
||||
|
||||
func TestMeasurements(t *testing.T) {
|
||||
bvVal := int16(4200)
|
||||
svVal := int16(1234)
|
||||
iVal := float32(420)
|
||||
pVal := float32(420)
|
||||
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
bus.AddDevice(fake)
|
||||
fake.Registers = map[uint8]uint16{
|
||||
RegBusVoltage: uint16(((4200 << 3) / 4) | (1 << 1)),
|
||||
RegShuntVoltage: uint16(svVal),
|
||||
RegCurrent: uint16(iVal * Config16V400mA.CurrentDivider),
|
||||
RegPower: uint16(pVal / Config16V400mA.PowerMultiplier),
|
||||
}
|
||||
|
||||
dev := New(bus)
|
||||
dev.config = Config16V400mA
|
||||
bv, sv, i, p, err := dev.Measurements()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(bv, qt.Equals, bvVal)
|
||||
c.Assert(sv, qt.Equals, svVal)
|
||||
c.Assert(i, qt.Equals, iVal)
|
||||
c.Assert(p, qt.Equals, pVal)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ina219
|
||||
|
||||
// The default I2C address for this device.
|
||||
const Address = 0x40
|
||||
|
||||
const (
|
||||
RegConfig uint8 = 0x0
|
||||
RegShuntVoltage uint8 = 0x1
|
||||
RegBusVoltage uint8 = 0x2
|
||||
RegPower uint8 = 0x3
|
||||
RegCurrent uint8 = 0x4
|
||||
RegCalibration uint8 = 0x5
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// The pingconfig group of files serve to abstract away
|
||||
// pin configuration calls on the machine.Pin type.
|
||||
// It was observed this way of developing drivers was
|
||||
// non-portable and unusable on "big" Go projects so
|
||||
// future projects should NOT configure pins in driver code.
|
||||
// Users must configure pins before passing them as arguments
|
||||
// to drivers.
|
||||
|
||||
// ConfigurePinOut is a legacy function used to configure pins as outputs.
|
||||
//
|
||||
// Deprecated: Do not configure pins in drivers.
|
||||
// This is a legacy feature and should only be used by drivers that
|
||||
// previously configured pins in initialization to avoid breaking users.
|
||||
func ConfigurePinOut(po pin.Output) {
|
||||
configurePinOut(po)
|
||||
}
|
||||
|
||||
// ConfigurePinInput is a legacy function used to configure pins as inputs.
|
||||
//
|
||||
// Deprecated: Do not configure pins in drivers.
|
||||
// This is a legacy feature and should only be used by drivers that
|
||||
// previously configured pins in initialization to avoid breaking users.
|
||||
func ConfigurePinInputPulldown(pi pin.Input) {
|
||||
configurePinInputPulldown(pi)
|
||||
}
|
||||
|
||||
// ConfigurePinInput is a legacy function used to configure pins as inputs.
|
||||
//
|
||||
// Deprecated: Do not configure pins in drivers.
|
||||
// This is a legacy feature and should only be used by drivers that
|
||||
// previously configured pins in initialization to avoid breaking users.
|
||||
func ConfigurePinInput(pi pin.Input) {
|
||||
configurePinInput(pi)
|
||||
}
|
||||
|
||||
// ConfigurePinInput is a legacy function used to configure pins as inputs.
|
||||
//
|
||||
// Deprecated: Do not configure pins in drivers.
|
||||
// This is a legacy feature and should only be used by drivers that
|
||||
// previously configured pins in initialization to avoid breaking users.
|
||||
func ConfigurePinInputPullup(pi pin.Input) {
|
||||
configurePinInputPullup(pi)
|
||||
}
|
||||
|
||||
// PinIsNoPin returns true if the argument is a machine.Pin type and is the machine.NoPin predeclared type.
|
||||
//
|
||||
// Deprecated: Drivers do not require pin knowledge from now on.
|
||||
func PinIsNoPin(pin any) bool {
|
||||
return pinIsNoPin(pin)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrConfigBeforeInstantiated = errors.New("device must be instantiated with New before calling Configure method")
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build !tinygo
|
||||
|
||||
package legacy
|
||||
|
||||
import "tinygo.org/x/drivers/internal/pin"
|
||||
|
||||
// This file compiles for non-tinygo builds
|
||||
// for use with "big" or "upstream" Go where
|
||||
// there is no machine package.
|
||||
|
||||
func configurePinOut(p pin.Output) {}
|
||||
func configurePinInput(p pin.Input) {}
|
||||
func configurePinInputPulldown(p pin.Input) {}
|
||||
func configurePinInputPullup(p pin.Input) {}
|
||||
func pinIsNoPin(a any) bool { return false }
|
||||
@@ -0,0 +1,10 @@
|
||||
//go:build baremetal && fe310
|
||||
|
||||
package legacy
|
||||
|
||||
import "machine"
|
||||
|
||||
const (
|
||||
pulldown = machine.PinInput
|
||||
pullup = machine.PinInput
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
//go:build baremetal && !fe310
|
||||
|
||||
package legacy
|
||||
|
||||
import "machine"
|
||||
|
||||
// If you are getting a build error here you then we missed adding
|
||||
// your CPU build tag to the list of CPUs that do not have pulldown/pullups.
|
||||
// Add it above and in pinhal_nopulls! You should also add a smoketest for it :)
|
||||
const (
|
||||
pulldown = machine.PinInputPulldown
|
||||
pullup = machine.PinInputPullup
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
//go:build baremetal
|
||||
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
func configurePinOut(po pin.Output) {
|
||||
configurePin(po, machine.PinOutput)
|
||||
}
|
||||
|
||||
func configurePinInputPulldown(pi pin.Input) {
|
||||
configurePin(pi, pulldown) // some chips do not have pull down, in which case pulldown==machine.PinInput.
|
||||
}
|
||||
|
||||
func configurePinInput(pi pin.Input) {
|
||||
configurePin(pi, machine.PinInput)
|
||||
}
|
||||
|
||||
func configurePinInputPullup(pi pin.Input) {
|
||||
configurePin(pi, pullup) // some chips do not have pull up, in which case pullup==machine.PinInput.
|
||||
}
|
||||
|
||||
func pinIsNoPin(a any) bool {
|
||||
p, ok := a.(machine.Pin)
|
||||
return ok && p == machine.NoPin
|
||||
}
|
||||
|
||||
func configurePin(p any, mode machine.PinMode) {
|
||||
machinePin, ok := p.(machine.Pin)
|
||||
if ok {
|
||||
machinePin.Configure(machine.PinConfig{Mode: mode})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// package pin implements a TinyGo Pin HAL.
|
||||
// It serves to eliminate machine.Pin from driver constructors
|
||||
// so that drivers can be used in "big" Go projects where
|
||||
// there is no machine package.
|
||||
// This file contains both function and interface-style Pin HAL definitions.
|
||||
package pin
|
||||
|
||||
// OutputFunc is hardware abstraction for a pin which outputs a
|
||||
// digital signal (high or low level).
|
||||
//
|
||||
// // Code conversion demo: from machine.Pin to pin.OutputFunc
|
||||
// led := machine.LED
|
||||
// led.Configure(machine.PinConfig{Mode: machine.Output})
|
||||
// var pin pin.OutputFunc = led.Set // Going from a machine.Pin to a pin.OutputFunc
|
||||
//
|
||||
// This is an alternative to [Output] which is an interface type.
|
||||
type OutputFunc func(level bool)
|
||||
|
||||
// High sets the underlying pin's level to high. This is equivalent to calling PinOutput(true).
|
||||
func (setPin OutputFunc) High() {
|
||||
setPin(true)
|
||||
}
|
||||
|
||||
// Low sets the underlying pin's level to low. This is equivalent to calling PinOutput(false).
|
||||
func (setPin OutputFunc) Low() {
|
||||
setPin(false)
|
||||
}
|
||||
|
||||
// InputFunc is hardware abstraction for a pin which receives a
|
||||
// digital signal and reads it (high or low level).
|
||||
//
|
||||
// // Code conversion demo: from machine.Pin to pin.InputFunc
|
||||
// input := machine.LED
|
||||
// input.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) // or use machine.PinInputPullup or machine.Input
|
||||
// var pin pin.InputFunc = input.Get // Going from a machine.Pin to a pin.InputFunc
|
||||
//
|
||||
// This is an alternative to [Input] which is an interface type.
|
||||
type InputFunc func() (level bool)
|
||||
|
||||
// // Below is an example on how to define a input/output pin HAL for a
|
||||
// // pin that must switch between input and output mode:
|
||||
//
|
||||
// var pinIsOutput bool
|
||||
// var po PinOutputFunc = func(b bool) {
|
||||
// if !pinIsOutput {
|
||||
// pin.Configure(outputMode)
|
||||
// pinIsOutput = true
|
||||
// }
|
||||
// pin.Set(b)
|
||||
// }
|
||||
//
|
||||
// var pi PinInputFunc = func() bool {
|
||||
// if pinIsOutput {
|
||||
// pin.Configure(inputMode)
|
||||
// pinIsOutput = false
|
||||
// }
|
||||
// return pin.Get()
|
||||
// }
|
||||
|
||||
// Output interface represents a pin hardware abstraction layer for a pin that can output a digital signal.
|
||||
//
|
||||
// This is an alternative to [OutputFunc] abstraction which is a function type.
|
||||
type Output interface {
|
||||
Set(level bool)
|
||||
}
|
||||
|
||||
// Input interface represents a pin hardware abstraction layer for a pin that can read a digital signal.
|
||||
//
|
||||
// This is an alternative to [InputFunc] abstraction which is a function type.
|
||||
type Input interface {
|
||||
Get() (level bool)
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package regmap
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device8 implements common logic to most 8-bit peripherals with an I2C or SPI bus.
|
||||
// All methods expect the target to support conventional register read and write operations
|
||||
// where the first byte sent is the register address being accessed.
|
||||
//
|
||||
// All methods use an internal buffer and perform no dynamic memory allocation.
|
||||
type Device8 struct {
|
||||
buf [10]byte
|
||||
}
|
||||
|
||||
// clear zeroes Device8's buffers.
|
||||
func (d *Device8) clear() {
|
||||
d.buf = [10]byte{}
|
||||
}
|
||||
|
||||
// I2C methods.
|
||||
|
||||
// Read8I2C reads a single byte from register addr of the device at i2cAddr using the provided I2C bus.
|
||||
func (d *Device8) Read8I2C(bus drivers.I2C, i2cAddr uint16, addr uint8) (byte, error) {
|
||||
d.buf[0] = addr
|
||||
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:2])
|
||||
return d.buf[1], err
|
||||
}
|
||||
|
||||
// Read16I2C reads a 16-bit value from register addr of the device at i2cAddr using the provided I2C bus.
|
||||
// The byte order is specified by order.
|
||||
func (d *Device8) Read16I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, order binary.ByteOrder) (uint16, error) {
|
||||
d.buf[0] = addr
|
||||
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:3])
|
||||
return order.Uint16(d.buf[1:3]), err
|
||||
}
|
||||
|
||||
// Read32I2C reads a 32-bit value from register addr of the device at i2cAddr using the provided I2C bus.
|
||||
// The byte order is specified by order.
|
||||
func (d *Device8) Read32I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, order binary.ByteOrder) (uint32, error) {
|
||||
d.buf[0] = addr
|
||||
err := bus.Tx(i2cAddr, d.buf[0:1], d.buf[1:5])
|
||||
return order.Uint32(d.buf[1:5]), err
|
||||
}
|
||||
|
||||
// ReadDataI2C reads dataLength bytes from register addr of the device at i2cAddr using the provided I2C bus.
|
||||
// The data is stored in dataDestination.
|
||||
func (d *Device8) ReadDataI2C(bus drivers.I2C, i2cAddr uint16, addr uint8, dataDestination []byte) error {
|
||||
d.buf[0] = addr
|
||||
return bus.Tx(i2cAddr, d.buf[:1], dataDestination)
|
||||
}
|
||||
|
||||
// Write8I2C writes a single byte value to register addr of the device at i2cAddr using the provided I2C bus.
|
||||
func (d *Device8) Write8I2C(bus drivers.I2C, i2cAddr uint16, addr, value uint8) error {
|
||||
d.buf[0] = addr
|
||||
d.buf[1] = value
|
||||
return bus.Tx(i2cAddr, d.buf[:2], nil)
|
||||
}
|
||||
|
||||
// Write16I2C writes a 16-bit value to register addr of the device at i2cAddr using the provided I2C bus.
|
||||
// The byte order is specified by order.
|
||||
func (d *Device8) Write16I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, value uint16, order binary.ByteOrder) error {
|
||||
d.buf[0] = addr
|
||||
order.PutUint16(d.buf[1:3], value)
|
||||
return bus.Tx(i2cAddr, d.buf[0:3], nil)
|
||||
}
|
||||
|
||||
// Write32I2C writes a 32-bit value to register addr of the device at i2cAddr using the provided I2C bus.
|
||||
// The byte order is specified by order.
|
||||
func (d *Device8) Write32I2C(bus drivers.I2C, i2cAddr uint16, addr uint8, value uint32, order binary.ByteOrder) error {
|
||||
d.buf[0] = addr
|
||||
order.PutUint32(d.buf[1:5], value)
|
||||
return bus.Tx(i2cAddr, d.buf[0:5], nil)
|
||||
}
|
||||
|
||||
// SPI methods.
|
||||
|
||||
// Read8SPI reads a single byte from register addr using the provided SPI bus.
|
||||
func (d *Device8) Read8SPI(bus drivers.SPI, addr uint8) (byte, error) {
|
||||
d.clear()
|
||||
d.buf[0] = addr
|
||||
err := bus.Tx(d.buf[0:1], d.buf[1:2]) // We suppose data is returned after first byte in SPI.
|
||||
return d.buf[1], err
|
||||
}
|
||||
|
||||
// Read16SPI reads a 16-bit value from register addr using the provided SPI bus. The byte order is specified by order.
|
||||
func (d *Device8) Read16SPI(bus drivers.SPI, addr uint8, order binary.ByteOrder) (uint16, error) {
|
||||
d.clear()
|
||||
d.buf[0] = addr
|
||||
err := bus.Tx(d.buf[0:3], d.buf[3:6]) // We suppose data is returned after first byte in SPI.
|
||||
return order.Uint16(d.buf[4:6]), err
|
||||
}
|
||||
|
||||
// Read32SPI reads a 32-bit value from register addr using the provided SPI bus. The byte order is specified by order.
|
||||
func (d *Device8) Read32SPI(bus drivers.SPI, addr uint8, order binary.ByteOrder) (uint32, error) {
|
||||
d.clear()
|
||||
d.buf[0] = addr
|
||||
err := bus.Tx(d.buf[0:5], d.buf[5:10]) // We suppose data is returned after first byte in SPI.
|
||||
return order.Uint32(d.buf[6:10]), err
|
||||
}
|
||||
|
||||
// ReadDataSPI reads data from a 8bit device address. It assumes data at register address is sent back
|
||||
// from device after first byte is written as address.
|
||||
// It needs the auxiliary buffer length to be large enough to contain both the write and read portions of buffer,
|
||||
// so 2*(dataLength+1) < len(auxiliaryBuf) must hold.
|
||||
func (d *Device8) ReadDataSPI(bus drivers.SPI, addr uint8, dataLength int, auxiliaryBuf []byte) ([]byte, error) {
|
||||
split := len(auxiliaryBuf) / 2
|
||||
if split < dataLength+1 {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
wbuf, rbuf := auxiliaryBuf[:split], auxiliaryBuf[split:]
|
||||
wbuf[0] = addr
|
||||
err := bus.Tx(wbuf, rbuf)
|
||||
return rbuf[1:], err
|
||||
}
|
||||
|
||||
// Write8SPI writes a single byte value to register addr using the provided SPI bus.
|
||||
func (d *Device8) Write8SPI(bus drivers.SPI, addr, value uint8) error {
|
||||
d.clear()
|
||||
d.buf[0] = addr
|
||||
d.buf[1] = value
|
||||
return bus.Tx(d.buf[:2], nil)
|
||||
}
|
||||
|
||||
// Write16SPI writes a 16-bit value to register addr using the provided SPI bus. The byte order is specified by order.
|
||||
func (d *Device8) Write16SPI(bus drivers.SPI, addr uint8, value uint16, order binary.ByteOrder) error {
|
||||
d.clear()
|
||||
d.buf[0] = addr
|
||||
order.PutUint16(d.buf[1:3], value)
|
||||
return bus.Tx(d.buf[:3], nil)
|
||||
}
|
||||
|
||||
// Write32SPI writes a 32-bit value to register addr using the provided SPI bus. The byte order is specified by order.
|
||||
func (d *Device8) Write32SPI(bus drivers.SPI, addr uint8, value uint32, order binary.ByteOrder) error {
|
||||
d.clear()
|
||||
d.buf[0] = addr
|
||||
order.PutUint32(d.buf[1:5], value)
|
||||
return bus.Tx(d.buf[:5], nil)
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Package regmap provides transaction-based interfaces for reading and writing
|
||||
// to device registers over I2C and SPI buses with pre-allocated buffers.
|
||||
package regmap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
// errNotInTx indicates an operation was attempted outside of an active transaction.
|
||||
errNotInTx = errors.New("device not in Tx")
|
||||
|
||||
// errInTx indicates a transaction was started while another is still active.
|
||||
errInTx = errors.New("device already in Tx")
|
||||
|
||||
// errShortWriteBuffer indicates the write buffer is too small for the requested operation.
|
||||
errShortWriteBuffer = errors.New("device write buffer too short")
|
||||
|
||||
// errShortReadBuffer indicates the read buffer is too small for the requested operation.
|
||||
errShortReadBuffer = errors.New("device read buffer too short")
|
||||
)
|
||||
|
||||
// Device8Txer wraps a Device8 to provide buffered transaction support for
|
||||
// I2C and SPI operations. It maintains pre-allocated buffers to avoid heap
|
||||
// allocations during register access operations.
|
||||
//
|
||||
// Users must call SetBuffers to configure the write and read buffers before
|
||||
// initiating transactions.
|
||||
type Device8Txer struct {
|
||||
Device8
|
||||
writeBuf []byte // Pre-allocated buffer for write operations
|
||||
readBuf []byte // Pre-allocated buffer for read operations
|
||||
inTx bool // Tracks whether a transaction is currently active
|
||||
}
|
||||
|
||||
// SetTxBuffers configures the write and read buffers for this device.
|
||||
// These buffers are reused across transactions to avoid heap allocations.
|
||||
//
|
||||
// The writebuf should be large enough to hold the register address plus
|
||||
// all data bytes to be written in a single transaction.
|
||||
func (d *Device8Txer) SetTxBuffers(writebuf, readbuf []byte) {
|
||||
d.readBuf = readbuf
|
||||
d.writeBuf = writebuf
|
||||
}
|
||||
|
||||
// Tx8 represents an active transaction for an 8-bit register device.
|
||||
// It tracks the write buffer and current offset as data is added to the transaction.
|
||||
//
|
||||
// Use AddWriteByte or AddWriteData to add data to the transaction, then call
|
||||
// DoTxI2C or DoTxSPI to execute the transaction over the bus.
|
||||
type Tx8 struct {
|
||||
dw *Device8Txer // Reference to the parent device
|
||||
off int // Current offset in the write buffer
|
||||
}
|
||||
|
||||
// Tx initiates a new transaction for writing to the specified register address.
|
||||
//
|
||||
// Parameters:
|
||||
// - writeAddr: The 8-bit register address to write to
|
||||
//
|
||||
// Returns a Tx8 handle that can be used to add data and execute the transaction.
|
||||
//
|
||||
// Returns an error if:
|
||||
// - A transaction is already active (errInTx)
|
||||
// - The write buffer is too short (errShortWriteBuffer)
|
||||
func (dw *Device8Txer) Tx(writeAddr uint8) (Tx8, error) {
|
||||
if dw.inTx {
|
||||
return Tx8{}, errInTx
|
||||
} else if len(dw.writeBuf) < 1 {
|
||||
return Tx8{}, errShortWriteBuffer
|
||||
}
|
||||
dw.writeBuf[0] = writeAddr
|
||||
return Tx8{dw: dw, off: 1}, nil
|
||||
}
|
||||
|
||||
// AddWriteData appends multiple bytes to the current transaction's write buffer.
|
||||
//
|
||||
// Parameters:
|
||||
// - buf: Variable number of bytes to add to the transaction
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The write buffer doesn't have enough space (errShortWriteBuffer)
|
||||
func (tx *Tx8) AddWriteData(buf ...byte) error {
|
||||
if !tx.dw.inTx {
|
||||
return errNotInTx
|
||||
}
|
||||
avail := tx.dw.writeBuf[tx.off:]
|
||||
if len(avail) < len(buf) {
|
||||
return errShortWriteBuffer
|
||||
}
|
||||
n := copy(avail, buf)
|
||||
tx.off += n
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWriteByte appends a single byte to the current transaction's write buffer.
|
||||
//
|
||||
// Parameters:
|
||||
// - b: The byte to add to the transaction
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The write buffer doesn't have enough space (errShortWriteBuffer)
|
||||
func (tx *Tx8) AddWriteByte(b byte) error {
|
||||
if !tx.dw.inTx {
|
||||
return errNotInTx
|
||||
}
|
||||
avail := tx.dw.writeBuf[tx.off:]
|
||||
if len(avail) < 1 {
|
||||
return errShortWriteBuffer
|
||||
}
|
||||
avail[0] = b
|
||||
tx.off++
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoTxI2C executes the transaction over an I2C bus.
|
||||
//
|
||||
// This performs a combined write-read I2C transaction, first sending the
|
||||
// register address and any data added to the transaction, then reading
|
||||
// the specified number of bytes from the device.
|
||||
//
|
||||
// Parameters:
|
||||
// - bus: The I2C bus to communicate over
|
||||
// - deviceAddr: The I2C address of the target device
|
||||
// - readLength: Number of bytes to read from the device
|
||||
//
|
||||
// Returns the read data as a slice of the internal read buffer, valid until
|
||||
// the next transaction. The transaction is automatically freed after execution.
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The read buffer is too short (errShortReadBuffer)
|
||||
// - The I2C transaction fails
|
||||
func (tx *Tx8) DoTxI2C(bus drivers.I2C, deviceAddr uint16, readLength int) ([]byte, error) {
|
||||
if tx.off == 0 || !tx.dw.inTx {
|
||||
return nil, errNotInTx
|
||||
}
|
||||
defer tx.freeTx()
|
||||
if len(tx.dw.readBuf) < readLength {
|
||||
return nil, errShortReadBuffer
|
||||
}
|
||||
rbuf := tx.dw.readBuf[:readLength]
|
||||
err := bus.Tx(deviceAddr, tx.dw.writeBuf[:tx.off], rbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rbuf, err
|
||||
}
|
||||
|
||||
// DoTxSPI executes the transaction over an SPI bus.
|
||||
//
|
||||
// This performs a full-duplex SPI transaction, simultaneously writing the
|
||||
// register address and data while reading the same number of bytes from the device.
|
||||
//
|
||||
// If no read buffer was configured (readBuf is nil), this performs a write-only
|
||||
// transaction and returns nil without error.
|
||||
//
|
||||
// Parameters:
|
||||
// - bus: The SPI bus to communicate over
|
||||
//
|
||||
// Returns the read data as a slice of the internal read buffer (same length as
|
||||
// the write data), valid until the next transaction. The transaction is
|
||||
// automatically freed after execution.
|
||||
//
|
||||
// Returns an error if:
|
||||
// - No transaction is active (errNotInTx)
|
||||
// - The read buffer is too short (errShortReadBuffer)
|
||||
// - The SPI transaction fails
|
||||
func (tx *Tx8) DoTxSPI(bus drivers.SPI) (readBuf []byte, err error) {
|
||||
if tx.off == 0 || !tx.dw.inTx {
|
||||
return nil, errNotInTx
|
||||
}
|
||||
defer tx.freeTx()
|
||||
if tx.dw.readBuf == nil {
|
||||
err = bus.Tx(tx.dw.writeBuf[:tx.off], nil) // Special case, only use write buffer functionality.
|
||||
return nil, err
|
||||
} else if len(readBuf) < tx.off {
|
||||
return nil, errShortReadBuffer
|
||||
}
|
||||
rbuf := tx.dw.readBuf[:tx.off]
|
||||
err = bus.Tx(tx.dw.writeBuf[:tx.off], rbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rbuf, err
|
||||
}
|
||||
|
||||
// freeTx marks the transaction as complete, allowing a new transaction to be started.
|
||||
// This is called internally by DoTxI2C and DoTxSPI after the transaction completes.
|
||||
func (tx *Tx8) freeTx() {
|
||||
tx.dw.inTx = false
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package regmap
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device8SPI implements common logic to most 8-bit peripherals with an SPI bus.
|
||||
// All methods expect the target to support conventional register read and write operations
|
||||
// where the first byte sent is the register address being accessed.
|
||||
//
|
||||
// All methods use an internal buffer and perform no dynamic memory allocation.
|
||||
type Device8SPI struct {
|
||||
bus drivers.SPI
|
||||
order binary.ByteOrder
|
||||
d Device8
|
||||
}
|
||||
|
||||
// SetBus sets the SPI bus and byte order for the Device8SPI.
|
||||
//
|
||||
// As a hint, most SPI devices use big-endian (MSB) byte order.
|
||||
// - Big endian: A value of 0x1234 is transmitted as 0x12 followed by 0x34.
|
||||
// - Little endian: A value of 0x1234 is transmitted as 0x34 followed by 0x12.
|
||||
func (d *Device8SPI) SetBus(bus drivers.SPI, order binary.ByteOrder) {
|
||||
d.bus = bus
|
||||
d.order = order
|
||||
}
|
||||
|
||||
// Read8 reads a single byte from register addr.
|
||||
func (d *Device8SPI) Read8(addr uint8) (byte, error) {
|
||||
return d.d.Read8SPI(d.bus, addr)
|
||||
}
|
||||
|
||||
// Read16 reads a 16-bit value from register addr.
|
||||
func (d *Device8SPI) Read16(addr uint8) (uint16, error) {
|
||||
return d.d.Read16SPI(d.bus, addr, d.order)
|
||||
}
|
||||
|
||||
// Read32 reads a 32-bit value from register addr.
|
||||
func (d *Device8SPI) Read32(addr uint8) (uint32, error) {
|
||||
return d.d.Read32SPI(d.bus, addr, d.order)
|
||||
}
|
||||
|
||||
// ReadData reads dataLength bytes from register addr. Due to the internal functioning of
|
||||
// SPI, an auxiliary buffer must be provided to perform the operation and avoid memory allocation.
|
||||
// The returned slice is a subslice of auxBuffer containing the read data.
|
||||
func (d *Device8SPI) ReadData(addr uint8, datalength int, auxBuffer []byte) ([]byte, error) {
|
||||
return d.d.ReadDataSPI(d.bus, addr, datalength, auxBuffer)
|
||||
}
|
||||
|
||||
// Write8 writes a single byte value to register addr.
|
||||
func (d *Device8SPI) Write8(addr, value uint8) error {
|
||||
return d.d.Write8SPI(d.bus, addr, value)
|
||||
}
|
||||
|
||||
// Write16 writes a 16-bit value to register addr.
|
||||
func (d *Device8SPI) Write16(addr uint8, value uint16) error {
|
||||
return d.d.Write16SPI(d.bus, addr, value, d.order)
|
||||
}
|
||||
|
||||
// Write32 writes a 32-bit value to register addr.
|
||||
func (d *Device8SPI) Write32(addr uint8, value uint32) error {
|
||||
return d.d.Write32SPI(d.bus, addr, value, d.order)
|
||||
}
|
||||
|
||||
// Device8I2C implements common logic to most 8-bit peripherals with an I2C bus.
|
||||
// All methods expect the target to support conventional register read and write operations
|
||||
// where the first byte sent is the register address being accessed.
|
||||
//
|
||||
// All methods use an internal buffer and perform no dynamic memory allocation.
|
||||
type Device8I2C struct {
|
||||
bus drivers.I2C
|
||||
i2cAddr uint16
|
||||
order binary.ByteOrder
|
||||
d Device8
|
||||
}
|
||||
|
||||
// SetBus sets the I2C bus, device address, and byte order for the Device8I2C.
|
||||
//
|
||||
// As a hint, most I2C devices use big-endian (MSB) byte order.
|
||||
// - Big endian: A value of 0x1234 is transmitted as 0x12 followed by 0x34.
|
||||
// - Little endian: A value of 0x1234 is transmitted as 0x34 followed by 0x12.
|
||||
func (d *Device8I2C) SetBus(bus drivers.I2C, i2cAddr uint16, order binary.ByteOrder) {
|
||||
d.bus = bus
|
||||
d.i2cAddr = i2cAddr
|
||||
d.order = order
|
||||
}
|
||||
|
||||
// Read8 reads a single byte from register addr.
|
||||
func (d *Device8I2C) Read8(addr uint8) (byte, error) {
|
||||
return d.d.Read8I2C(d.bus, d.i2cAddr, addr)
|
||||
}
|
||||
|
||||
// Read16 reads a 16-bit value from register addr.
|
||||
func (d *Device8I2C) Read16(addr uint8) (uint16, error) {
|
||||
return d.d.Read16I2C(d.bus, d.i2cAddr, addr, d.order)
|
||||
}
|
||||
|
||||
// Read32 reads a 32-bit value from register addr.
|
||||
func (d *Device8I2C) Read32(addr uint8) (uint32, error) {
|
||||
return d.d.Read32I2C(d.bus, d.i2cAddr, addr, d.order)
|
||||
}
|
||||
|
||||
// ReadData reads dataLength bytes from register addr.
|
||||
func (d *Device8I2C) ReadData(addr uint8, dataDestination []byte) error {
|
||||
return d.d.ReadDataI2C(d.bus, d.i2cAddr, addr, dataDestination)
|
||||
}
|
||||
|
||||
// Write8 writes a single byte value to register addr.
|
||||
func (d *Device8I2C) Write8(addr, value uint8) error {
|
||||
return d.d.Write8I2C(d.bus, d.i2cAddr, addr, value)
|
||||
}
|
||||
|
||||
// Write16 writes a 16-bit value to register addr.
|
||||
func (d *Device8I2C) Write16(addr uint8, value uint16) error {
|
||||
return d.d.Write16I2C(d.bus, d.i2cAddr, addr, value, d.order)
|
||||
}
|
||||
|
||||
// Write32 writes a 32-bit value to register addr.
|
||||
func (d *Device8I2C) Write32(addr uint8, value uint32) error {
|
||||
return d.d.Write32I2C(d.bus, d.i2cAddr, addr, value, d.order)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package regmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
func ExampleDevice8Txer() {
|
||||
// Initialization.
|
||||
var dtx Device8Txer
|
||||
dtx.SetTxBuffers(make([]byte, 256), make([]byte, 256))
|
||||
|
||||
// Usage.
|
||||
const (
|
||||
defaultAddr = 65
|
||||
REG_WRITE = 0x1f
|
||||
IOCTL_CALL = 0xc0
|
||||
)
|
||||
tx, err := dtx.Tx(REG_WRITE)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = tx.AddWriteData(IOCTL_CALL, 0x80, 0x80)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var bus drivers.I2C
|
||||
readData, err := tx.DoTxI2C(bus, defaultAddr, 20)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(readData)
|
||||
}
|
||||
+115
-36
@@ -11,37 +11,57 @@ import (
|
||||
// Device wraps an I2C connection to a LIS3DH device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
address uint16
|
||||
r Range
|
||||
accel [6]byte // stored acceleration data (from the Update call)
|
||||
}
|
||||
|
||||
// Driver configuration, used for the Configure call. All fields are optional.
|
||||
type Config struct {
|
||||
Address uint16
|
||||
}
|
||||
|
||||
// New creates a new LIS3DH 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: Address0}
|
||||
return Device{bus: bus, address: Address0}
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication
|
||||
func (d *Device) Configure() {
|
||||
func (d *Device) Configure(config Config) error {
|
||||
if config.Address != 0 {
|
||||
d.address = config.Address
|
||||
}
|
||||
|
||||
// enable all axes, normal mode
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL1, []byte{0x07})
|
||||
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, []byte{0x07})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 400Hz rate
|
||||
d.SetDataRate(DATARATE_400_HZ)
|
||||
err = d.SetDataRate(DATARATE_400_HZ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// High res & BDU enabled
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL4, []byte{0x88})
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, []byte{0x88})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get current range
|
||||
d.r = d.ReadRange()
|
||||
d.r, err = d.ReadRange()
|
||||
return err
|
||||
}
|
||||
|
||||
// Connected returns whether a LIS3DH has been found.
|
||||
// It does a "who am I" request and checks the response.
|
||||
func (d *Device) Connected() bool {
|
||||
data := []byte{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.address), WHO_AM_I, data)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -49,46 +69,51 @@ func (d *Device) Connected() bool {
|
||||
}
|
||||
|
||||
// SetDataRate sets the speed of data collected by the LIS3DH.
|
||||
func (d *Device) SetDataRate(rate DataRate) {
|
||||
func (d *Device) SetDataRate(rate DataRate) error {
|
||||
ctl1 := []byte{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL1, ctl1)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return err
|
||||
}
|
||||
// mask off bits
|
||||
ctl1[0] &^= 0xf0
|
||||
ctl1[0] |= (byte(rate) << 4)
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL1, ctl1)
|
||||
return legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL1, ctl1)
|
||||
}
|
||||
|
||||
// SetRange sets the G range for LIS3DH.
|
||||
func (d *Device) SetRange(r Range) {
|
||||
func (d *Device) SetRange(r Range) error {
|
||||
ctl := []byte{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return err
|
||||
}
|
||||
// mask off bits
|
||||
ctl[0] &^= 0x30
|
||||
ctl[0] |= (byte(r) << 4)
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// store the new range
|
||||
d.r = r
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadRange returns the current G range for LIS3DH.
|
||||
func (d *Device) ReadRange() (r Range) {
|
||||
func (d *Device) ReadRange() (r Range, err error) {
|
||||
ctl := []byte{0}
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CTRL4, ctl)
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.address), REG_CTRL4, ctl)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
// mask off bits
|
||||
r = Range(ctl[0] >> 4)
|
||||
r &= 0x03
|
||||
|
||||
return r
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// ReadAcceleration reads the current acceleration from the device and returns
|
||||
@@ -96,28 +121,17 @@ func (d *Device) ReadRange() (r Range) {
|
||||
// and the sensor is not moving the returned value will be around 1000000 or
|
||||
// -1000000.
|
||||
func (d *Device) ReadAcceleration() (int32, int32, int32, error) {
|
||||
x, y, z := d.ReadRawAcceleration()
|
||||
divider := float32(1)
|
||||
switch d.r {
|
||||
case RANGE_16_G:
|
||||
divider = 1365
|
||||
case RANGE_8_G:
|
||||
divider = 4096
|
||||
case RANGE_4_G:
|
||||
divider = 8190
|
||||
case RANGE_2_G:
|
||||
divider = 16380
|
||||
}
|
||||
|
||||
return int32(float32(x) / divider * 1000000), int32(float32(y) / divider * 1000000), int32(float32(z) / divider * 1000000), nil
|
||||
rawX, rawY, rawZ := d.ReadRawAcceleration()
|
||||
x, y, z := normalizeRange(rawX, rawY, rawZ, d.r)
|
||||
return x, y, z, nil
|
||||
}
|
||||
|
||||
// ReadRawAcceleration returns the raw x, y and z axis from the LIS3DH
|
||||
func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_OUT_X_L|0x80, nil)
|
||||
legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
|
||||
|
||||
data := []byte{0, 0, 0, 0, 0, 0}
|
||||
d.bus.Tx(d.Address, nil, data)
|
||||
d.bus.Tx(d.address, nil, data)
|
||||
|
||||
x = int16((uint16(data[1]) << 8) | uint16(data[0]))
|
||||
y = int16((uint16(data[3]) << 8) | uint16(data[2]))
|
||||
@@ -125,3 +139,68 @@ func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Update the sensor values of the 'which' parameter. Only acceleration is
|
||||
// supported at the moment.
|
||||
func (d *Device) Update(which drivers.Measurement) error {
|
||||
if which&drivers.Acceleration != 0 {
|
||||
// Read raw acceleration values and store them in the driver.
|
||||
err := legacy.WriteRegister(d.bus, uint8(d.address), REG_OUT_X_L|0x80, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.bus.Tx(d.address, nil, d.accel[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Acceleration returns the last read acceleration 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) Acceleration() (x, y, z int32) {
|
||||
// Extract the raw 16-bit values.
|
||||
rawX := int16((uint16(d.accel[1]) << 8) | uint16(d.accel[0]))
|
||||
rawY := int16((uint16(d.accel[3]) << 8) | uint16(d.accel[2]))
|
||||
rawZ := int16((uint16(d.accel[5]) << 8) | uint16(d.accel[4]))
|
||||
|
||||
// Normalize these values, to be in µg (micro-gravity).
|
||||
return normalizeRange(rawX, rawY, rawZ, d.r)
|
||||
}
|
||||
|
||||
// Convert raw 16-bit values to normalized 32-bit values while avoiding floats
|
||||
// and divisions.
|
||||
func normalizeRange(rawX, rawY, rawZ int16, r Range) (x, y, z int32) {
|
||||
// We're going to convert the 16-bit raw values to values in the range
|
||||
// -1000_000..1000_000. For now we're going to assume a range of 16G, we'll
|
||||
// adjust that range later.
|
||||
// The formula is derived as follows, and carefully selected to avoid
|
||||
// overflow and integer divisions (the division will be optimized to a
|
||||
// bitshift):
|
||||
// x = x * 1000_000 / 2048
|
||||
// x = x * (1000_000/64) / (2048/64)
|
||||
// x = x * 15625 / 32
|
||||
x = int32(rawX) * 15625 / 32
|
||||
y = int32(rawY) * 15625 / 32
|
||||
z = int32(rawZ) * 15625 / 32
|
||||
|
||||
// Now we need to normalize the three values, since we assumed 16G before.
|
||||
shift := uint32(0)
|
||||
switch r {
|
||||
case RANGE_16_G:
|
||||
shift = 0
|
||||
case RANGE_8_G:
|
||||
shift = 1
|
||||
case RANGE_4_G:
|
||||
shift = 2
|
||||
case RANGE_2_G:
|
||||
shift = 3
|
||||
}
|
||||
x >>= shift
|
||||
y >>= shift
|
||||
z >>= shift
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ type Configuration struct {
|
||||
MagDataRate uint8
|
||||
}
|
||||
|
||||
var errNotConnected = errors.New("lsm303agr: failed to communicate with either acel or magnet sensor")
|
||||
var errNotConnected = errors.New("lsm303agr: failed to communicate with either accel or magnet sensor")
|
||||
|
||||
// New creates a new LSM303AGR connection. The I2C bus must already be configured.
|
||||
//
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
// Package lsm303dlhc implements a driver for the LSM303dlhc,
|
||||
// a 3 axis accelerometer/magnetic sensor typically available on breakout boards.
|
||||
//
|
||||
// Datasheet: https://www.st.com/resource/en/datasheet/lsm303dlhc.pdf
|
||||
|
||||
package lsm303dlhc // import "tinygo.org/x/drivers/lsm303dlhc"
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a LSM303dlhc device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
AccelAddress uint8
|
||||
MagAddress uint8
|
||||
AccelPowerMode uint8
|
||||
AccelRange uint8
|
||||
AccelDataRate uint8
|
||||
MagPowerMode uint8
|
||||
MagSystemMode uint8
|
||||
MagDataRate uint8
|
||||
buf [6]uint8
|
||||
}
|
||||
|
||||
// Configuration for LSM303dlhc device.
|
||||
type Configuration struct {
|
||||
AccelPowerMode uint8
|
||||
AccelRange uint8
|
||||
AccelDataRate uint8
|
||||
MagPowerMode uint8
|
||||
MagSystemMode uint8
|
||||
MagDataRate uint8
|
||||
}
|
||||
|
||||
// New creates a new LSM303DLHC 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,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure sets up the LSM303dlhc device for communication.
|
||||
func (d *Device) Configure(cfg Configuration) (err error) {
|
||||
|
||||
if cfg.AccelDataRate != 0 {
|
||||
d.AccelDataRate = cfg.AccelDataRate
|
||||
} else {
|
||||
d.AccelDataRate = ACCEL_DATARATE_100HZ
|
||||
}
|
||||
|
||||
if cfg.AccelPowerMode != 0 {
|
||||
d.AccelPowerMode = cfg.AccelPowerMode
|
||||
} else {
|
||||
d.AccelPowerMode = ACCEL_POWER_NORMAL
|
||||
}
|
||||
|
||||
if cfg.AccelRange != 0 {
|
||||
d.AccelRange = cfg.AccelRange
|
||||
} else {
|
||||
d.AccelRange = ACCEL_RANGE_2G
|
||||
}
|
||||
|
||||
if cfg.MagPowerMode != 0 {
|
||||
d.MagPowerMode = cfg.MagPowerMode
|
||||
} else {
|
||||
d.MagPowerMode = MAG_POWER_NORMAL
|
||||
}
|
||||
|
||||
if cfg.MagDataRate != 0 {
|
||||
d.MagDataRate = cfg.MagDataRate
|
||||
} else {
|
||||
d.MagDataRate = MAG_DATARATE_10HZ
|
||||
}
|
||||
|
||||
if cfg.MagSystemMode != 0 {
|
||||
d.MagSystemMode = cfg.MagSystemMode
|
||||
} else {
|
||||
d.MagSystemMode = MAG_SYSTEM_CONTINUOUS
|
||||
}
|
||||
|
||||
data := d.buf[:1]
|
||||
|
||||
data[0] = byte(d.AccelDataRate<<4 | d.AccelPowerMode | 0x07)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG1_A, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data[0] = byte(0x80 | d.AccelRange<<4)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), ACCEL_CTRL_REG4_A, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data[0] = byte(0xC0)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.AccelAddress), CRA_REG_M, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Temperature compensation is on for magnetic sensor
|
||||
data[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
|
||||
err = legacy.WriteRegister(d.bus, 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, y, z int32, err error) {
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), ACCEL_OUT_AUTO_INC, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rangeFactor := int16(0)
|
||||
switch d.AccelRange {
|
||||
case ACCEL_RANGE_2G:
|
||||
rangeFactor = 1
|
||||
case ACCEL_RANGE_4G:
|
||||
rangeFactor = 2
|
||||
case ACCEL_RANGE_8G:
|
||||
rangeFactor = 4
|
||||
case ACCEL_RANGE_16G:
|
||||
rangeFactor = 12 // the readings in 16G are a bit lower
|
||||
}
|
||||
|
||||
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, roll int32, err error) {
|
||||
|
||||
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)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// 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, y, z int32, err error) {
|
||||
|
||||
if d.MagSystemMode == MAG_SYSTEM_SINGLE {
|
||||
cmd := d.buf[:1]
|
||||
cmd[0] = byte(0x80 | d.MagPowerMode<<4 | d.MagDataRate<<2 | d.MagSystemMode)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.MagAddress), MAG_MR_REG_M, cmd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data := d.buf[0:6]
|
||||
legacy.ReadRegister(d.bus, uint8(d.MagAddress), MAG_OUT_AUTO_INC, data)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ReadCompass reads the current compass heading from the device and returns
|
||||
// it in micro-degrees. When the z axis is pointing straight to Earth and
|
||||
// the y axis is pointing to North, the heading would be zero.
|
||||
//
|
||||
// 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, err error) {
|
||||
|
||||
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() (t int32, err error) {
|
||||
|
||||
data := d.buf[:2]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.MagAddress), TEMP_OUT_AUTO_INC, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r := int16((uint16(data[1])<<8 | uint16(data[0]))) >> 4 // temperature offset from 25 °C
|
||||
t = 25000 + int32((float32(r)/8)*1000)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package lsm303dlhc
|
||||
|
||||
const (
|
||||
|
||||
// Constants/addresses used for I2C.
|
||||
ACCEL_ADDRESS = 0x19
|
||||
MAG_ADDRESS = 0x1E
|
||||
|
||||
// i2C 8-bit subaddress (SUB): the 7 LSb represent the actual register address
|
||||
// while the MSB enables address auto increment.
|
||||
// If the MSb of the SUB field is 1, the SUB (register address) is
|
||||
// automatically increased to allow multiple data read/writes.
|
||||
ADDR_AUTO_INC_MASK = 0x80
|
||||
|
||||
// accelerometer registers.
|
||||
ACCEL_CTRL_REG1_A = 0x20
|
||||
ACCEL_CTRL_REG4_A = 0x23
|
||||
ACCEL_OUT_X_L_A = 0x28
|
||||
ACCEL_OUT_X_H_A = 0x29
|
||||
ACCEL_OUT_Y_L_A = 0x2A
|
||||
ACCEL_OUT_Y_H_A = 0x2B
|
||||
ACCEL_OUT_Z_L_A = 0x2C
|
||||
ACCEL_OUT_Z_H_A = 0x2D
|
||||
ACCEL_OUT_AUTO_INC = ACCEL_OUT_X_L_A | ADDR_AUTO_INC_MASK
|
||||
|
||||
// magnetic sensor registers.
|
||||
MAG_MR_REG_M = 0x02
|
||||
MAG_OUT_X_L_M = 0x68
|
||||
MAG_OUT_X_H_M = 0x69
|
||||
MAG_OUT_Y_L_M = 0x6A
|
||||
MAG_OUT_Y_H_M = 0x6B
|
||||
MAG_OUT_Z_L_M = 0x6C
|
||||
MAG_OUT_Z_H_M = 0x6D
|
||||
MAG_OUT_AUTO_INC = MAG_OUT_X_L_M | ADDR_AUTO_INC_MASK
|
||||
|
||||
// temperature sensor registers.
|
||||
CRA_REG_M = 0x80
|
||||
TEMP_OUT_L_M = 0x32
|
||||
TEMP_OUT_H_M = 0x31
|
||||
TEMP_OUT_AUTO_INC = TEMP_OUT_L_M | ADDR_AUTO_INC_MASK
|
||||
|
||||
// accelerometer power mode.
|
||||
ACCEL_POWER_NORMAL = 0x00 // default
|
||||
ACCEL_POWER_LOW = 0x08
|
||||
|
||||
// accelerometer range.
|
||||
ACCEL_RANGE_2G = 0x00 // default
|
||||
ACCEL_RANGE_4G = 0x01
|
||||
ACCEL_RANGE_8G = 0x02
|
||||
ACCEL_RANGE_16G = 0x03
|
||||
|
||||
// accelerometer data rate.
|
||||
ACCEL_DATARATE_1HZ = 0x01
|
||||
ACCEL_DATARATE_10HZ = 0x02
|
||||
ACCEL_DATARATE_25HZ = 0x03
|
||||
ACCEL_DATARATE_50HZ = 0x04
|
||||
ACCEL_DATARATE_100HZ = 0x05 // default
|
||||
ACCEL_DATARATE_200HZ = 0x06
|
||||
ACCEL_DATARATE_400HZ = 0x07
|
||||
ACCEL_DATARATE_1344HZ = 0x09 // 5376Hz in low-power mode
|
||||
|
||||
// magnetic sensor power mode.
|
||||
MAG_POWER_NORMAL = 0x00 // default
|
||||
MAG_POWER_LOW = 0x01
|
||||
|
||||
// magnetic sensor operate mode.
|
||||
MAG_SYSTEM_CONTINUOUS = 0x00 // default
|
||||
MAG_SYSTEM_SINGLE = 0x01
|
||||
|
||||
// magnetic sensor data rate
|
||||
MAG_DATARATE_10HZ = 0x00 // default
|
||||
MAG_DATARATE_20HZ = 0x01
|
||||
MAG_DATARATE_50HZ = 0x02
|
||||
MAG_DATARATE_100HZ = 0x03
|
||||
)
|
||||
+35
-24
@@ -8,7 +8,6 @@ import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
type AccelRange uint8
|
||||
@@ -26,7 +25,7 @@ type Device struct {
|
||||
accelSampleRate AccelSampleRate
|
||||
gyroRange GyroRange
|
||||
gyroSampleRate GyroSampleRate
|
||||
buf [6]uint8
|
||||
buf [7]uint8 // up to 6 bytes for read + 1 byte for the register address
|
||||
}
|
||||
|
||||
// Configuration for LSM6DS3TR device.
|
||||
@@ -84,30 +83,20 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
|
||||
d.gyroSampleRate = GYRO_SR_104
|
||||
}
|
||||
|
||||
data := d.buf[:1]
|
||||
|
||||
// Configure accelerometer
|
||||
data[0] = uint8(d.accelRange) | uint8(d.accelSampleRate)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL1_XL, data)
|
||||
err = d.writeByte(CTRL1_XL, uint8(d.accelRange)|uint8(d.accelSampleRate))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Set ODR bit
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), CTRL4_C, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data[0] = data[0] &^ BW_SCAL_ODR_ENABLED
|
||||
data[0] |= BW_SCAL_ODR_ENABLED
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL4_C, data)
|
||||
// Enable ODR scaling
|
||||
err = d.setBits(CTRL4_C, BW_SCAL_ODR_ENABLED)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Configure gyroscope
|
||||
data[0] = uint8(d.gyroRange) | uint8(d.gyroSampleRate)
|
||||
err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL2_G, data)
|
||||
err = d.writeByte(CTRL2_G, uint8(d.gyroRange)|uint8(d.gyroSampleRate))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -118,8 +107,10 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
|
||||
// Connected returns whether a LSM6DS3TR has been found.
|
||||
// It does a "who am I" request and checks the response.
|
||||
func (d *Device) Connected() bool {
|
||||
data := d.buf[:1]
|
||||
legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
|
||||
data, err := d.readBytes(WHO_AM_I, 1)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return data[0] == 0x6A
|
||||
}
|
||||
|
||||
@@ -128,8 +119,7 @@ func (d *Device) Connected() bool {
|
||||
// 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) {
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUTX_L_XL, data)
|
||||
data, err := d.readBytes(OUTX_L_XL, 6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -153,8 +143,7 @@ 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) {
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUTX_L_G, data)
|
||||
data, err := d.readBytes(OUTX_L_G, 6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -177,8 +166,7 @@ func (d *Device) ReadRotation() (x, y, z int32, err error) {
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
|
||||
func (d *Device) ReadTemperature() (t int32, err error) {
|
||||
data := d.buf[:2]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), OUT_TEMP_L, data)
|
||||
data, err := d.readBytes(OUT_TEMP_L, 2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -187,3 +175,26 @@ func (d *Device) ReadTemperature() (t int32, err error) {
|
||||
t = 25000 + (int32(int16((int16(data[1])<<8)|int16(data[0])))*125)/32
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) readBytes(reg, size uint8) ([]byte, error) {
|
||||
d.buf[0] = reg
|
||||
err := d.bus.Tx(d.Address, d.buf[0:1], d.buf[1:size+1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.buf[1 : size+1], nil
|
||||
}
|
||||
|
||||
func (d *Device) writeByte(reg, value uint8) error {
|
||||
d.buf[0] = reg
|
||||
d.buf[1] = value
|
||||
return d.bus.Tx(d.Address, d.buf[0:2], nil)
|
||||
}
|
||||
|
||||
func (d *Device) setBits(reg, bits uint8) error {
|
||||
data, err := d.readBytes(reg, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.writeByte(reg, (data[0]&^bits)|bits)
|
||||
}
|
||||
|
||||
+35
-28
@@ -7,7 +7,6 @@ import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
type AccelRange uint8
|
||||
@@ -28,7 +27,7 @@ type Device struct {
|
||||
accelMultiplier int32
|
||||
gyroMultiplier int32
|
||||
magMultiplier int32
|
||||
buf [6]uint8
|
||||
buf [7]uint8 // up to 6 bytes for read + 1 byte for the register address
|
||||
}
|
||||
|
||||
// Configuration for LSM9DS1 device.
|
||||
@@ -61,10 +60,15 @@ func New(bus drivers.I2C) *Device {
|
||||
// Case of boolean false and error nil means I2C is up,
|
||||
// but "who am I" responses have unexpected values.
|
||||
func (d *Device) Connected() bool {
|
||||
data1, data2 := d.buf[:1], d.buf[1:2]
|
||||
legacy.ReadRegister(d.bus, d.AccelAddress, WHO_AM_I, data1)
|
||||
legacy.ReadRegister(d.bus, d.MagAddress, WHO_AM_I_M, data2)
|
||||
return data1[0] == 0x68 && data2[0] == 0x3D
|
||||
data, err := d.readBytes(d.AccelAddress, WHO_AM_I, 1)
|
||||
if err != nil || data[0] != 0x68 {
|
||||
return false
|
||||
}
|
||||
data, err = d.readBytes(d.MagAddress, WHO_AM_I_M, 1)
|
||||
if err != nil || data[0] != 0x3D {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadAcceleration reads the current acceleration from the device and returns
|
||||
@@ -72,8 +76,7 @@ func (d *Device) Connected() bool {
|
||||
// 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) {
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_X_L_XL, data)
|
||||
data, err := d.readBytes(d.AccelAddress, OUT_X_L_XL, 6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -88,8 +91,7 @@ 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) {
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_X_L_G, data)
|
||||
data, err := d.readBytes(d.AccelAddress, OUT_X_L_G, 6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -102,8 +104,7 @@ func (d *Device) ReadRotation() (x, y, z int32, err error) {
|
||||
// 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) {
|
||||
data := d.buf[:6]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.MagAddress), OUT_X_L_M, data)
|
||||
data, err := d.readBytes(d.MagAddress, OUT_X_L_M, 6)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -115,8 +116,7 @@ func (d *Device) ReadMagneticField() (x, y, z int32, err error) {
|
||||
|
||||
// ReadTemperature returns the temperature in Celsius milli degrees (°C/1000)
|
||||
func (d *Device) ReadTemperature() (t int32, err error) {
|
||||
data := d.buf[:2]
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.AccelAddress), OUT_TEMP_L, data)
|
||||
data, err := d.readBytes(d.AccelAddress, OUT_TEMP_L, 2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -167,20 +167,16 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
|
||||
d.magMultiplier = 58
|
||||
}
|
||||
|
||||
data := d.buf[:1]
|
||||
|
||||
// Configure accelerometer
|
||||
// Sample rate & measurement range
|
||||
data[0] = uint8(cfg.AccelSampleRate)<<5 | uint8(cfg.AccelRange)<<3
|
||||
err = legacy.WriteRegister(d.bus, d.AccelAddress, CTRL_REG6_XL, data)
|
||||
err = d.writeByte(d.AccelAddress, CTRL_REG6_XL, uint8(cfg.AccelSampleRate)<<5|uint8(cfg.AccelRange)<<3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Configure gyroscope
|
||||
// Sample rate & measurement range
|
||||
data[0] = uint8(cfg.GyroSampleRate)<<5 | uint8(cfg.GyroRange)<<3
|
||||
err = legacy.WriteRegister(d.bus, d.AccelAddress, CTRL_REG1_G, data)
|
||||
err = d.writeByte(d.AccelAddress, CTRL_REG1_G, uint8(cfg.GyroSampleRate)<<5|uint8(cfg.GyroRange)<<3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -190,33 +186,44 @@ func (d *Device) doConfigure(cfg Configuration) (err error) {
|
||||
// Temperature compensation enabled
|
||||
// High-performance mode XY axis
|
||||
// Sample rate
|
||||
data[0] = 0b10000000 | 0b01000000 | uint8(cfg.MagSampleRate)<<2
|
||||
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG1_M, data)
|
||||
err = d.writeByte(d.MagAddress, CTRL_REG1_M, 0b10000000|0b01000000|uint8(cfg.MagSampleRate)<<2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Measurement range
|
||||
data[0] = uint8(cfg.MagRange) << 5
|
||||
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG2_M, data)
|
||||
err = d.writeByte(d.MagAddress, CTRL_REG2_M, uint8(cfg.MagRange)<<5)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Continuous-conversion mode
|
||||
// https://electronics.stackexchange.com/questions/237397/continuous-conversion-vs-single-conversion-mode
|
||||
data[0] = 0b00000000
|
||||
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG3_M, data)
|
||||
err = d.writeByte(d.MagAddress, CTRL_REG3_M, 0b00000000)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// High-performance mode Z axis
|
||||
data[0] = 0b00001000
|
||||
err = legacy.WriteRegister(d.bus, d.MagAddress, CTRL_REG4_M, data)
|
||||
err = d.writeByte(d.MagAddress, CTRL_REG4_M, 0b00001000)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) readBytes(addr, reg, size uint8) ([]byte, error) {
|
||||
d.buf[0] = reg
|
||||
err := d.bus.Tx(uint16(addr), d.buf[0:1], d.buf[1:size+1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.buf[1 : size+1], nil
|
||||
}
|
||||
|
||||
func (d *Device) writeByte(addr, reg, value uint8) error {
|
||||
d.buf[0] = reg
|
||||
d.buf[1] = value
|
||||
return d.bus.Tx(uint16(addr), d.buf[0:2], nil)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max6675.pdf
|
||||
package max6675
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// ErrThermocoupleOpen is returned when the thermocouple input is open.
|
||||
// i.e. not attached or faulty
|
||||
var ErrThermocoupleOpen = errors.New("thermocouple input open")
|
||||
|
||||
type Device struct {
|
||||
bus drivers.SPI
|
||||
cs machine.Pin
|
||||
}
|
||||
|
||||
// Create a new Device to read from a MAX6675 thermocouple.
|
||||
// Pins must be configured before use. Frequency for SPI
|
||||
// should be 4.3MHz maximum.
|
||||
func NewDevice(bus drivers.SPI, cs machine.Pin) *Device {
|
||||
return &Device{
|
||||
bus: bus,
|
||||
cs: cs,
|
||||
}
|
||||
}
|
||||
|
||||
// Read and return the temperature in celsius
|
||||
func (d *Device) Read() (float32, error) {
|
||||
var (
|
||||
read []byte = []byte{0, 0}
|
||||
value uint16
|
||||
)
|
||||
|
||||
d.cs.Low()
|
||||
if err := d.bus.Tx([]byte{0, 0}, read); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.cs.High()
|
||||
|
||||
// datasheet: Bit D2 is normally low and goes high if the thermocouple input is open.
|
||||
if read[1]&0x04 == 0x04 {
|
||||
return 0, ErrThermocoupleOpen
|
||||
}
|
||||
|
||||
// data is 12 bits, split across the two bytes
|
||||
// -XXXXXXX XXXXX---
|
||||
value = (uint16(read[0]) << 5) | (uint16(read[1]) >> 3)
|
||||
|
||||
return float32(value) * 0.25, nil
|
||||
}
|
||||
+4
-2
@@ -4,17 +4,19 @@ package max72xx
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
bus drivers.SPI
|
||||
cs machine.Pin
|
||||
}
|
||||
|
||||
// NewDriver creates a new max7219 connection. The SPI wire must already be configured
|
||||
// The SPI frequency must not be higher than 10MHz.
|
||||
// parameter cs: the datasheet also refers to this pin as "load" pin.
|
||||
func NewDevice(bus machine.SPI, cs machine.Pin) *Device {
|
||||
func NewDevice(bus drivers.SPI, cs machine.Pin) *Device {
|
||||
return &Device{
|
||||
bus: bus,
|
||||
cs: cs,
|
||||
|
||||
@@ -64,7 +64,7 @@ func (d *Device) Read(r []int32) (int, error) {
|
||||
count := len(r)
|
||||
|
||||
// get the next group of samples
|
||||
machine.I2S0.Read(d.buf)
|
||||
machine.I2S0.ReadStereo(d.buf)
|
||||
|
||||
if len(r) > len(d.buf) {
|
||||
count = len(d.buf)
|
||||
@@ -83,7 +83,7 @@ func (d *Device) ReadWithFilter(r []int32) (int, error) {
|
||||
for i := 0; i < len(r); i++ {
|
||||
|
||||
// get the next group of samples
|
||||
machine.I2S0.Read(d.buf)
|
||||
machine.I2S0.ReadStereo(d.buf)
|
||||
|
||||
// filter
|
||||
sum = applySincFilter(d.buf)
|
||||
|
||||
+2
-2
@@ -35,8 +35,8 @@ var (
|
||||
var (
|
||||
ErrFamilyNotSupported = errors.New("Address family not supported")
|
||||
ErrProtocolNotSupported = errors.New("Socket protocol/type not supported")
|
||||
ErrStartingDHCPClient = errors.New("Error starting DHPC client")
|
||||
ErrStartingDHCPServer = errors.New("Error starting DHPC server")
|
||||
ErrStartingDHCPClient = errors.New("Error starting DHCP client")
|
||||
ErrStartingDHCPServer = errors.New("Error starting DHCP server")
|
||||
ErrNoMoreSockets = errors.New("No more sockets")
|
||||
ErrClosingSocket = errors.New("Error closing socket")
|
||||
ErrNotSupported = errors.New("Not supported")
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//go:build comboat_fw
|
||||
|
||||
package probe
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/comboat"
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
"tinygo.org/x/drivers/netlink"
|
||||
)
|
||||
|
||||
func Probe() (netlink.Netlinker, netdev.Netdever) {
|
||||
|
||||
cfg := comboat.Config{
|
||||
BaudRate: 115200,
|
||||
Uart: machine.UART1,
|
||||
Tx: machine.UART1_TX_PIN,
|
||||
Rx: machine.UART1_RX_PIN,
|
||||
}
|
||||
|
||||
combo := comboat.NewDevice(&cfg)
|
||||
netdev.UseNetdev(combo)
|
||||
|
||||
return combo, combo
|
||||
}
|
||||
+17
-9
@@ -27,8 +27,9 @@ type Config struct{}
|
||||
|
||||
// Errors list
|
||||
var (
|
||||
errNoPresence = errors.New("Error: OneWire. No devices on the bus.")
|
||||
errReadAddress = errors.New("Error: OneWire. Read address error: CRC mismatch.")
|
||||
errNoPresence = errors.New("Error: OneWire. No devices on the bus.")
|
||||
errTooManyDevices = errors.New("Error: OneWire. Too many devices on the bus.")
|
||||
errReadAddress = errors.New("Error: OneWire. Read address error: CRC mismatch.")
|
||||
)
|
||||
|
||||
// New creates a new GPIO 1-Wire connection.
|
||||
@@ -46,7 +47,7 @@ func (d *Device) Configure(config Config) {}
|
||||
func (d Device) Reset() error {
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
time.Sleep(480 * time.Microsecond)
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
time.Sleep(70 * time.Microsecond)
|
||||
precence := d.p.Get()
|
||||
time.Sleep(410 * time.Microsecond)
|
||||
@@ -61,11 +62,11 @@ func (d Device) WriteBit(data uint8) {
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
if data&1 == 1 { // Send '1'
|
||||
time.Sleep(5 * time.Microsecond)
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
time.Sleep(60 * time.Microsecond)
|
||||
} else { // Send '0'
|
||||
time.Sleep(60 * time.Microsecond)
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
time.Sleep(5 * time.Microsecond)
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,7 @@ func (d Device) Write(data uint8) {
|
||||
func (d Device) ReadBit() (data uint8) {
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
time.Sleep(3 * time.Microsecond)
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
time.Sleep(8 * time.Microsecond)
|
||||
if d.p.Get() {
|
||||
data = 1
|
||||
@@ -111,7 +112,7 @@ func (d Device) ReadAddress() ([]uint8, error) {
|
||||
for i := 0; i < 8; i++ {
|
||||
romid[i] = d.Read()
|
||||
}
|
||||
if d.Сrc8(romid, 7) != romid[7] {
|
||||
if d.Сrc8(romid) != 0 {
|
||||
return nil, errReadAddress
|
||||
}
|
||||
return romid, nil
|
||||
@@ -187,15 +188,22 @@ func (d Device) Search(cmd uint8) ([][]uint8, error) {
|
||||
}
|
||||
d.WriteBit(bit)
|
||||
}
|
||||
if d.Сrc8(lastAddress) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
lastFork = lastZero
|
||||
copy(romIDs[romIndex], lastAddress)
|
||||
romIndex++
|
||||
if romIndex >= 32 {
|
||||
return romIDs, errTooManyDevices
|
||||
}
|
||||
}
|
||||
return romIDs[:romIndex:romIndex], nil
|
||||
}
|
||||
|
||||
// Crc8 compute a Dallas Semiconductor 8 bit CRC.
|
||||
func (d Device) Сrc8(buffer []uint8, size int) (crc uint8) {
|
||||
func (_ Device) Сrc8(buffer []uint8) (crc uint8) {
|
||||
// Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0
|
||||
// Tiny 2x16 entry CRC table created by Arjen Lentz
|
||||
// See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table
|
||||
@@ -205,7 +213,7 @@ func (d Device) Сrc8(buffer []uint8, size int) (crc uint8) {
|
||||
0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8,
|
||||
0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74,
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
for i := 0; i < len(buffer); i++ {
|
||||
crc = buffer[i] ^ crc // just re-using crc as intermediate
|
||||
crc = crc8_table[crc&0x0f] ^ crc8_table[16+((crc>>4)&0x0f)]
|
||||
}
|
||||
|
||||
+2
-1
@@ -14,7 +14,8 @@ import (
|
||||
)
|
||||
|
||||
type P1AM struct {
|
||||
bus machine.SPI
|
||||
bus *machine.SPI
|
||||
|
||||
slaveSelectPin, slaveAckPin, baseEnablePin machine.Pin
|
||||
|
||||
// SkipAutoConfig will skip loading a default configuration into each module.
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ func (p ADCPin) Get() uint16 {
|
||||
p.d.bus.Tx(p.d.Address, tx, rx)
|
||||
|
||||
// scale result to 16bit value like other ADCs
|
||||
return uint16(rx[1] << 8)
|
||||
return uint16(rx[1]) << 8
|
||||
}
|
||||
|
||||
// Configure here just for interface compatibility.
|
||||
|
||||
+43
-7
@@ -43,6 +43,40 @@ func NewImage[T Color](width, height int) Image[T] {
|
||||
}
|
||||
}
|
||||
|
||||
// NewImageFromBytes creates a new image of the given size using an existing data slice of bytes.
|
||||
func NewImageFromBytes[T Color](width, height int, buf []byte) Image[T] {
|
||||
if width < 0 || height < 0 || int(int16(width)) != width || int(int16(height)) != height {
|
||||
// The width/height are stored as 16-bit integers and should never be
|
||||
// negative.
|
||||
panic("NewImageFromBytes: width/height out of bounds")
|
||||
}
|
||||
var zeroColor T
|
||||
var data unsafe.Pointer
|
||||
switch {
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Typical formats like RGB888 and RGB565.
|
||||
// Each color starts at a whole byte offset from the start.
|
||||
if len(buf) != width*height*int(unsafe.Sizeof(zeroColor)) {
|
||||
panic("NewImageFromBytes: data slice size mismatch")
|
||||
}
|
||||
data = unsafe.Pointer(&buf[0])
|
||||
default:
|
||||
// Formats like RGB444 that have 12 bits per pixel.
|
||||
// We access these as bytes, so allocate the buffer as a byte slice.
|
||||
bufBits := width * height * zeroColor.BitsPerPixel()
|
||||
bufBytes := (bufBits + 7) / 8
|
||||
if len(buf) != bufBytes {
|
||||
panic("NewImageFromBytes: data slice size mismatch")
|
||||
}
|
||||
data = unsafe.Pointer(&buf[0])
|
||||
}
|
||||
return Image[T]{
|
||||
width: int16(width),
|
||||
height: int16(height),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Rescale returns a new Image buffer based on the img buffer.
|
||||
// The contents is undefined after the Rescale operation, and any modification
|
||||
// to the returned image will overwrite the underlying image buffer in undefined
|
||||
@@ -104,15 +138,16 @@ func (img Image[T]) setPixel(index int, c T) {
|
||||
switch {
|
||||
case zeroColor.BitsPerPixel() == 1:
|
||||
// Monochrome.
|
||||
x := index % int(img.width)
|
||||
y := index / int(img.width)
|
||||
offset := x + (y/8)*int(img.width)
|
||||
offset := index / 8
|
||||
bits := index % 8
|
||||
|
||||
ptr := (*byte)(unsafe.Add(img.data, offset))
|
||||
if c != zeroColor {
|
||||
*((*byte)(ptr)) |= 1 << uint8(y%8)
|
||||
*((*byte)(ptr)) |= (1 << (7 - uint8(bits)))
|
||||
} else {
|
||||
*((*byte)(ptr)) &^= 1 << uint8(y%8)
|
||||
*((*byte)(ptr)) &^= (1 << (7 - uint8(bits)))
|
||||
}
|
||||
|
||||
return
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Each color starts at a whole byte offset.
|
||||
@@ -166,9 +201,10 @@ func (img Image[T]) Get(x, y int) T {
|
||||
case zeroColor.BitsPerPixel() == 1:
|
||||
// Monochrome.
|
||||
var c Monochrome
|
||||
offset := x + (y/8)*int(img.width)
|
||||
offset := index / 8
|
||||
bits := index % 8
|
||||
ptr := (*byte)(unsafe.Add(img.data, offset))
|
||||
c = (*ptr >> uint8(y%8) & 0x1) == 1
|
||||
c = ((*ptr >> (7 - uint8(bits))) & 0x1) > 0
|
||||
return any(c).(T)
|
||||
case zeroColor.BitsPerPixel()%8 == 0:
|
||||
// Colors like RGB565, RGB888, etc.
|
||||
|
||||
+105
-15
@@ -66,9 +66,9 @@ func TestImageRGB444BE(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestImageMonochrome(t *testing.T) {
|
||||
image := pixel.NewImage[pixel.Monochrome](5, 3)
|
||||
if width, height := image.Size(); width != 5 && height != 3 {
|
||||
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
|
||||
image := pixel.NewImage[pixel.Monochrome](128, 64)
|
||||
if width, height := image.Size(); width != 128 && height != 64 {
|
||||
t.Errorf("image.Size(): expected 128, 64 but got %d, %d", width, height)
|
||||
}
|
||||
for _, expected := range []color.RGBA{
|
||||
{R: 0xff, G: 0xff, B: 0xff},
|
||||
@@ -80,19 +80,101 @@ func TestImageMonochrome(t *testing.T) {
|
||||
{B: 0x00, A: 0xff},
|
||||
} {
|
||||
encoded := pixel.NewColor[pixel.Monochrome](expected.R, expected.G, expected.B)
|
||||
image.Set(4, 2, encoded)
|
||||
actual := image.Get(4, 2).RGBA()
|
||||
image.Set(5, 3, encoded)
|
||||
actual := image.Get(5, 3).RGBA()
|
||||
switch {
|
||||
case expected.R == 0 && expected.G == 0 && expected.B == 0:
|
||||
// should be false eg black
|
||||
if actual.R != 0 || actual.G != 0 || actual.B != 0 {
|
||||
t.Errorf("failed to roundtrip color: expected %v but got %v", expected, actual)
|
||||
}
|
||||
case int(expected.R)+int(expected.G)+int(expected.B) > 128*3:
|
||||
// should be true eg white
|
||||
if actual.R == 0 || actual.G == 0 || actual.B == 0 {
|
||||
t.Errorf("failed to roundtrip color: expected %v but got %v", expected, actual)
|
||||
}
|
||||
default:
|
||||
// should be false eg black
|
||||
if actual.R != 0 || actual.G != 0 || actual.B != 0 {
|
||||
t.Errorf("failed to roundtrip color: expected %v but got %v", expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 128x128
|
||||
var rprofile = []byte{
|
||||
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00,
|
||||
0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00,
|
||||
0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x17, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
|
||||
0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x01, 0xF8, 0x00, 0x5F, 0xFF, 0xFF, 0xFC, 0x00, 0x02, 0x80, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x03, 0xFE, 0x03, 0xFF, 0xFD, 0xBF, 0xFF, 0x80, 0x1F, 0xE0, 0x3F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x03, 0xFE, 0x01, 0xFF, 0xF7, 0x6B, 0xFF, 0x80, 0x1F, 0xC0, 0x1F, 0xFF, 0xFE, 0x02, 0xFF, 0xFC, 0x03, 0xDF, 0x17, 0xFA, 0x00, 0x00, 0x37, 0xF0, 0x3F, 0xE0, 0x1F, 0xFF, 0xD0,
|
||||
0x00, 0x07, 0xFC, 0x07, 0x07, 0xBF, 0x00, 0x00, 0x00, 0x01, 0xFE, 0xF8, 0x78, 0x3F, 0xF8, 0x00, 0x00, 0x01, 0xFC, 0x06, 0x1B, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xEA, 0x78, 0x1F, 0x80, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x03, 0x1B, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xE2, 0x68, 0x1F, 0xC0, 0x00, 0x00, 0x01, 0xFC, 0x07, 0x5B, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x07, 0xC4, 0x38, 0x1F, 0x80, 0x00,
|
||||
0x00, 0x01, 0xF8, 0x03, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x78, 0x3F, 0xC0, 0x00, 0x00, 0x01, 0xFC, 0x03, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x68, 0x1F, 0x80, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x07, 0x9F, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x70, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x03, 0x9E, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3E, 0xE8, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xF8, 0x01, 0xFF, 0x55, 0xF8, 0x00, 0x00, 0x03, 0xEA, 0xFF, 0xC0, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x01, 0xFE, 0xAF, 0xF0, 0x00, 0x00, 0x03, 0xFD, 0xBF, 0xE0, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x01, 0xF8, 0x00, 0x7C, 0x00, 0x00, 0x07, 0x80, 0x03, 0xE0, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x03, 0xC1, 0xE0, 0x3E, 0x00, 0x00, 0x1F, 0x03, 0xC0, 0xF0, 0x1F, 0x80, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x82, 0xF8, 0x0F, 0x00, 0x00, 0x3E, 0x05, 0xE0, 0x7C, 0x1F, 0x80, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x07, 0x01, 0xF8, 0x17, 0x00, 0x00, 0x3C, 0x09, 0xE0, 0x78, 0x1F, 0xC0, 0x00, 0x00, 0x03, 0xFC, 0x0F, 0x06, 0xFC, 0x07, 0x80, 0x00, 0x7C, 0x1D, 0xE0, 0x3C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0xFF, 0xFC, 0x1E, 0x03, 0xFC, 0x03, 0x80, 0x00, 0x78, 0x1F, 0xE0, 0x1E, 0x1F, 0xFE, 0x80, 0x2F, 0xFF, 0xFC, 0x1C, 0x07, 0xF8, 0x01, 0x80, 0x00, 0x60, 0x1F, 0xE0, 0x16, 0x1F, 0xFF, 0xF8,
|
||||
0x1F, 0xFF, 0xF8, 0x3E, 0x07, 0xFC, 0x01, 0xC0, 0x00, 0xE8, 0x1F, 0xE0, 0x1E, 0x1F, 0xFF, 0xEC, 0x3F, 0xFF, 0xFC, 0x3C, 0x03, 0xF8, 0x01, 0xC0, 0x00, 0xE0, 0x17, 0xE0, 0x07, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x38, 0x03, 0xE8, 0x00, 0xC0, 0x00, 0xC0, 0x07, 0xC0, 0x03, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x38, 0x00, 0xC0, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x78, 0x01, 0xE0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x38, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xF8, 0x78, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x68, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x70, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x01, 0x9F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x78, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x68, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0x70, 0x00, 0x00, 0x00, 0xC0, 0x01, 0xC0, 0x00, 0x00, 0x01, 0x9F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x68, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x38, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x9F, 0xFF, 0xFE,
|
||||
0x07, 0xFF, 0xFC, 0x78, 0x00, 0x00, 0x00, 0xC0, 0x80, 0xC0, 0x00, 0x00, 0x03, 0x1F, 0xFF, 0xF8, 0x00, 0x37, 0xF8, 0x38, 0x00, 0x00, 0x01, 0xC7, 0xF0, 0xE0, 0x00, 0x00, 0x03, 0x9F, 0xFE, 0x00,
|
||||
0x00, 0x5F, 0xFC, 0x38, 0x00, 0x00, 0x01, 0xC3, 0xE8, 0xE0, 0x00, 0x00, 0x03, 0x1F, 0xFB, 0x00, 0x00, 0x01, 0xFC, 0x3C, 0x00, 0x00, 0x01, 0xC7, 0xF8, 0xE0, 0x00, 0x00, 0x07, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x3C, 0x00, 0x00, 0x03, 0x9F, 0xFC, 0x70, 0x00, 0x00, 0x07, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xF8, 0x1E, 0x00, 0x00, 0x03, 0xBF, 0xFE, 0x78, 0x00, 0x00, 0x1E, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x1E, 0x00, 0x00, 0x07, 0x9F, 0xFF, 0x78, 0x00, 0x00, 0x16, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x1E, 0x00, 0x00, 0x07, 0x73, 0xC3, 0x3C, 0x00, 0x00, 0x1E, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x1F, 0x00, 0x00, 0x1E, 0x60, 0x01, 0x9E, 0x00, 0x00, 0x3C, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xF8, 0x1F, 0x80, 0x00, 0x7E, 0x40, 0x01, 0x17, 0x80, 0x00, 0x7E, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x1F, 0x80, 0x00, 0x3C, 0x40, 0x01, 0x9F, 0x00, 0x00, 0x7C, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x1F, 0xC0, 0x00, 0xFC, 0x40, 0x01, 0x07, 0xC0, 0x00, 0xFC, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x1F, 0xF8, 0x0B, 0xE8, 0x7B, 0xFF, 0x81, 0xF8, 0x03, 0xFC, 0x1F, 0x80, 0x00, 0x00, 0x03, 0xF8, 0x1C, 0xFF, 0xFF, 0xC0, 0x3F, 0xFE, 0x00, 0xFF, 0xFF, 0xDE, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x05, 0xFC, 0x1C, 0xFF, 0xFF, 0x80, 0x7F, 0xDE, 0x00, 0xFF, 0x7F, 0xDC, 0x1F, 0x80, 0x00, 0x00, 0xFF, 0xFC, 0x1C, 0x3F, 0xFE, 0x00, 0x0E, 0xB8, 0x00, 0x3F, 0xFF, 0x1C, 0x1F, 0xFC, 0x00,
|
||||
0x2F, 0xFF, 0xF8, 0x1C, 0x00, 0x00, 0x00, 0x04, 0x98, 0x00, 0x01, 0x00, 0x1E, 0x1F, 0xFF, 0xF4, 0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x98, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x1C, 0x00, 0x00, 0x00, 0x06, 0x98, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x02, 0xB8, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x1C, 0x00, 0x00, 0x00, 0x03, 0xE8, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE, 0x7F, 0xFF, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE,
|
||||
0x7F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFE,
|
||||
0x7F, 0xFF, 0xFC, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x1F, 0xFF, 0xFE, 0x0B, 0xFF, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xF8,
|
||||
0x00, 0x7F, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFE, 0x00, 0x00, 0x01, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x03, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x38, 0x15, 0xB7, 0x80, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x1C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xF8, 0x3C, 0x16, 0xAB, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x16, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x3C, 0x3F, 0xFB, 0x80, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x1C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x3C, 0x1F, 0xE9, 0x00, 0x00, 0x01, 0xF7, 0x80, 0x00, 0x1E, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xF8, 0x3C, 0x03, 0x82, 0x1D, 0xC6, 0x39, 0xC0, 0x07, 0x00, 0x14, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0x01, 0xFC, 0x3C, 0x03, 0x87, 0x14, 0x85, 0x15, 0xC1, 0x03, 0x80, 0x1E, 0x1F, 0x80, 0x00, 0x00, 0x01, 0xFC, 0x3C, 0x03, 0x87, 0x9F, 0xE6, 0x3D, 0xC0, 0x1F, 0xC0, 0x1C, 0x1F, 0xC0, 0x00,
|
||||
0x00, 0xBF, 0xF8, 0x3C, 0x03, 0x83, 0x9F, 0xE7, 0x3F, 0x8D, 0x1E, 0xC0, 0x16, 0x1F, 0xD4, 0x00, 0x17, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0x9E, 0xE7, 0x79, 0xD7, 0xBC, 0xE0, 0x1C, 0x1F, 0xFF, 0xE8,
|
||||
0x1F, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0x9C, 0xE3, 0x3F, 0x9F, 0xBC, 0xE0, 0x1E, 0x1F, 0xFF, 0xD0, 0x3F, 0xFF, 0xF8, 0x3C, 0x03, 0x83, 0x9E, 0xE7, 0x79, 0xC7, 0xBC, 0xE0, 0x16, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0xBC, 0xE3, 0xF9, 0xC3, 0xBC, 0xE0, 0x1C, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0x9E, 0xEB, 0xE9, 0xE3, 0xBD, 0xE0, 0x1E, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xF8, 0x3C, 0x03, 0x83, 0x9C, 0xE3, 0xF1, 0xC3, 0x9C, 0xC0, 0x1C, 0x1F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0x9E, 0xE9, 0xE0, 0xFF, 0x9F, 0xC0, 0x16, 0x1F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x03, 0x83, 0xBC, 0x61, 0xE0, 0xFF, 0x07, 0xC0, 0x1E, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x1C, 0x3F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xF8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x1E, 0x1F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x00, 0x1C, 0x1F, 0xFF, 0xFC,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x17, 0xC0, 0x00, 0x00, 0x00, 0x16, 0x3F, 0xFF, 0xFE, 0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x3F, 0xFF, 0xFE,
|
||||
0x3F, 0xFF, 0xFC, 0x3C, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x3F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFE, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x7F, 0xFF, 0xFE,
|
||||
0x2F, 0xFF, 0xFF, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x80, 0x5F, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
|
||||
0x01, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
|
||||
0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func TestImageFromBytesMonochrome(t *testing.T) {
|
||||
image := pixel.NewImageFromBytes[pixel.Monochrome](128, 128, rprofile)
|
||||
if width, height := image.Size(); width != 128 && height != 128 {
|
||||
t.Errorf("image.Size(): expected 128, 128 but got %d, %d", width, height)
|
||||
}
|
||||
|
||||
raw := image.RawBuffer()
|
||||
for i, b := range raw {
|
||||
if b != rprofile[i] {
|
||||
t.Fatalf("failed to roundtrip image. expected %v but got %v", rprofile[i], b)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,22 +183,30 @@ func TestImageMonochrome(t *testing.T) {
|
||||
// contain the same data afterwards.
|
||||
func TestImageNoise(t *testing.T) {
|
||||
t.Run("RGB888", func(t *testing.T) {
|
||||
testImageNoise[pixel.RGB888](t)
|
||||
testImageNoiseN[pixel.RGB888](t)
|
||||
})
|
||||
t.Run("RGB565BE", func(t *testing.T) {
|
||||
testImageNoise[pixel.RGB565BE](t)
|
||||
testImageNoiseN[pixel.RGB565BE](t)
|
||||
})
|
||||
t.Run("RGB555", func(t *testing.T) {
|
||||
testImageNoise[pixel.RGB555](t)
|
||||
testImageNoiseN[pixel.RGB555](t)
|
||||
})
|
||||
t.Run("RGB444BE", func(t *testing.T) {
|
||||
testImageNoise[pixel.RGB444BE](t)
|
||||
testImageNoiseN[pixel.RGB444BE](t)
|
||||
})
|
||||
t.Run("Monochrome", func(t *testing.T) {
|
||||
testImageNoise[pixel.Monochrome](t)
|
||||
testImageNoiseN[pixel.Monochrome](t)
|
||||
})
|
||||
}
|
||||
|
||||
// Run the testImageNoise multiple times, because a single test might not catch
|
||||
// all bugs (since the test uses random data).
|
||||
func testImageNoiseN[T pixel.Color](t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
testImageNoise[T](t)
|
||||
}
|
||||
}
|
||||
|
||||
func testImageNoise[T pixel.Color](t *testing.T) {
|
||||
// Create an image of a random width/height for extra testing.
|
||||
width := rand.Int()%500 + 10
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user