mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
+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
|
||||
|
||||
+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
|
||||
}
|
||||
@@ -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,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)
|
||||
}
|
||||
@@ -1,20 +1,25 @@
|
||||
module tinygo.org/x/drivers
|
||||
|
||||
go 1.18
|
||||
|
||||
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 (
|
||||
|
||||
+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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+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
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package seesaw
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var errInvalidEncoderNumber = errors.New("invalid encoder choice, 0-15 are supported")
|
||||
|
||||
// GetEncoderPosition returns the absolute position (or delta since the previous call) of the specified rotary encoder.
|
||||
func (d *Device) GetEncoderPosition(encoder uint, asDelta bool) (int32, error) {
|
||||
if encoder >= 16 {
|
||||
return 0, errInvalidEncoderNumber
|
||||
}
|
||||
|
||||
// The function address' upper nibble is the function, the lower nibble selects which encoder to communicate with
|
||||
fnAddr := FunctionAddress(encoder)
|
||||
if asDelta {
|
||||
fnAddr |= FunctionEncoderDelta
|
||||
} else {
|
||||
fnAddr |= FunctionEncoderPosition
|
||||
}
|
||||
|
||||
var buf [4]byte
|
||||
err := d.Read(ModuleEncoderBase, fnAddr, buf[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int32(buf[0])<<24 | int32(buf[1])<<16 | int32(buf[2])<<8 | int32(buf[3]), nil
|
||||
}
|
||||
|
||||
// SetEncoderPosition calibrate's the encoder's current absolute position to be whatever the provided position is.
|
||||
func (d *Device) SetEncoderPosition(encoder uint, position int32) error {
|
||||
if encoder >= 16 {
|
||||
return errInvalidEncoderNumber
|
||||
}
|
||||
|
||||
// The function address' upper nibble is the function, the lower nibble selects which encoder to communicate with
|
||||
fnAddr := FunctionEncoderPosition | FunctionAddress(encoder)
|
||||
|
||||
buf := [4]byte{
|
||||
byte(position >> 24),
|
||||
byte(position >> 16),
|
||||
byte(position >> 8),
|
||||
byte(position),
|
||||
}
|
||||
|
||||
return d.Write(ModuleEncoderBase, fnAddr, buf[:])
|
||||
}
|
||||
@@ -98,3 +98,13 @@ const (
|
||||
FunctionKeypadCount FunctionAddress = 0x04
|
||||
FunctionKeypadFifo FunctionAddress = 0x10
|
||||
)
|
||||
|
||||
// encoder module function address registers
|
||||
// these are the defaults for encoder 0, change the lower nibble to address other encoders
|
||||
// see the Device.GetEncoderPosition and SetEncoderPosition methods for examples.
|
||||
const (
|
||||
FunctionEncoderIntenset FunctionAddress = 0x10
|
||||
FunctionEncoderIntenclr FunctionAddress = 0x20
|
||||
FunctionEncoderPosition FunctionAddress = 0x30
|
||||
FunctionEncoderDelta FunctionAddress = 0x40
|
||||
)
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
package sharpmem
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
const (
|
||||
bitWriteCmd uint8 = 0b00000001
|
||||
bitVcom uint8 = 0b00000010
|
||||
bitClear uint8 = 0b00000100
|
||||
)
|
||||
|
||||
var (
|
||||
ConfigLS010B7DH04 = Config{Width: 128, Height: 128}
|
||||
ConfigLS011B7DH03 = Config{Width: 160, Height: 68}
|
||||
ConfigLS012B7DD01 = Config{Width: 184, Height: 38}
|
||||
ConfigLS013B7DH03 = ConfigLS010B7DH04
|
||||
ConfigLS013B7DH05 = Config{Width: 144, Height: 168}
|
||||
ConfigLS018B7DH02 = Config{Width: 230, Height: 303}
|
||||
ConfigLS027B7DH01 = Config{Width: 400, Height: 240}
|
||||
ConfigLS027B7DH01A = ConfigLS027B7DH01
|
||||
ConfigLS032B7DD02 = Config{Width: 336, Height: 536}
|
||||
ConfigLS044Q7DH01 = Config{Width: 320, Height: 240}
|
||||
)
|
||||
|
||||
type Pin interface {
|
||||
High()
|
||||
Low()
|
||||
}
|
||||
|
||||
// Device represents a Sharp Memory Display device. This driver implementation
|
||||
// concerns the 1-bit color versions only (black and white memory displays).
|
||||
//
|
||||
// Supported SKUs include:
|
||||
// LS010B7DH04, LS011B7DH03, LS012B7DD01, LS013B7DH03, LS013B7DH05,
|
||||
// LS018B7DH02, LS027B7DH01, LS027B7DH01A, LS032B7DD02, LS044Q7DH01
|
||||
//
|
||||
// Note: Only SKU LS011B7DH03 (160x68) has been tested as of writing.
|
||||
//
|
||||
// The driver includes optimizations (frame and per-line invalidation) that
|
||||
// only transmit the changed lines to the display. These optimizations are on
|
||||
// by default, and they can be disabled with the respective config option.
|
||||
type Device struct {
|
||||
bus drivers.SPI
|
||||
csPin Pin
|
||||
buffer []byte
|
||||
txBuf []byte
|
||||
lineDiff []byte
|
||||
width int16
|
||||
height int16
|
||||
bufferSize int16
|
||||
bytesPerLine int16
|
||||
vcom uint8
|
||||
diffing bool
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Width int16
|
||||
Height int16
|
||||
|
||||
// DisableOptimizations disables frame and line invalidation optimizations.
|
||||
// Useful if constant frame times are desired.
|
||||
DisableOptimizations bool
|
||||
}
|
||||
|
||||
// New creates a new device connection.
|
||||
// The SPI bus must have already been configured.
|
||||
func New(bus drivers.SPI, csPin Pin) Device {
|
||||
d := Device{
|
||||
bus: bus,
|
||||
csPin: csPin,
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Configure initializes the display with specified configuration. It can be
|
||||
// called multiple times on the same display, resetting its internal state.
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
if cfg.Width == 0 {
|
||||
cfg.Width = 160
|
||||
}
|
||||
if cfg.Height == 0 {
|
||||
cfg.Height = 68
|
||||
}
|
||||
|
||||
d.width = cfg.Width
|
||||
d.height = cfg.Height
|
||||
d.diffing = !cfg.DisableOptimizations
|
||||
|
||||
d.initialize()
|
||||
}
|
||||
|
||||
// initialize properly initializes the display and the in-memory image buffers.
|
||||
func (d *Device) initialize() {
|
||||
d.csPin.Low()
|
||||
|
||||
// initialize VCOM as high
|
||||
d.vcom = bitVcom
|
||||
|
||||
// bytesPerLine has to be 16-bit aligned, as some resolutions require
|
||||
// padding to the nearest 2nd byte.
|
||||
d.bytesPerLine = ceilDiv(d.width, 16) * 2
|
||||
|
||||
// preallocate a contiguous byte buffer for all lines, including
|
||||
// protocol-required padding for each line apriori (easier to transfer).
|
||||
d.bufferSize = d.bytesPerLine * d.height
|
||||
d.buffer = make([]byte, d.bufferSize)
|
||||
// A bit being 1 is white (reflective), 0 is black (less reflective).
|
||||
for i := range d.buffer {
|
||||
d.buffer[i] = 0xff
|
||||
}
|
||||
|
||||
// auxiliary buffer for SPI transfers to avoid dynamic allocations
|
||||
d.txBuf = make([]byte, 2)
|
||||
|
||||
if d.diffing {
|
||||
// buffer to store the changed lines. First bit is whether any line has
|
||||
// changed at all (i.e. the frame is invalid), followed by N bits,
|
||||
// one for each line.
|
||||
d.lineDiff = make([]byte, bitfieldBufLen(1+d.height))
|
||||
}
|
||||
}
|
||||
|
||||
// SetPixel enables or disables a pixel in the buffer.
|
||||
// color.RGBA{0, 0, 0, 255} is considered transparent (reflective, white),
|
||||
// anything else will enable a pixel on the screen (make it appear less
|
||||
// reflective, black).
|
||||
func (d *Device) SetPixel(x, y int16, c color.RGBA) {
|
||||
if d.width == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// bounds check
|
||||
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
||||
return
|
||||
}
|
||||
|
||||
offset := y * d.bytesPerLine
|
||||
|
||||
div := offset + x/8
|
||||
mod := uint8(x % 8)
|
||||
|
||||
prev := hasBit(d.buffer[div], mod)
|
||||
curr := c.R == 0 && c.G == 0 && c.B == 0 && c.A == 255
|
||||
|
||||
if prev == curr {
|
||||
return
|
||||
}
|
||||
|
||||
if curr {
|
||||
d.buffer[div] = setBit(d.buffer[div], mod)
|
||||
} else {
|
||||
d.buffer[div] = unsetBit(d.buffer[div], mod)
|
||||
}
|
||||
|
||||
if d.diffing {
|
||||
d.invalidateLine(y)
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (x, y int16) {
|
||||
return d.width, d.height
|
||||
}
|
||||
|
||||
// Display renders the buffer to the screen. It only transmits changed lines if
|
||||
// optimizations are enabled. It should be called at >=1hz, even if the
|
||||
// buffer hasn't been modified.
|
||||
func (d *Device) Display() error {
|
||||
if d.width == 0 {
|
||||
return errors.New("display not configured")
|
||||
}
|
||||
|
||||
if d.diffing {
|
||||
if !hasBit(d.lineDiff[0], 0) {
|
||||
// no pixels have been modified, simply toggle VCOM
|
||||
return d.holdDisplay()
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for i := 0; i < len(d.lineDiff); i++ {
|
||||
d.lineDiff[i] = 0x00
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
cmd := bitWriteCmd | d.vcom
|
||||
|
||||
d.toggleVcom()
|
||||
|
||||
// Padding to use for high bits of line numbers that overflow 8 bits.
|
||||
var hiPad = uint8(0)
|
||||
if d.height >= 512 {
|
||||
hiPad = 3 + 3 // 3 mode bits + 3 low bits
|
||||
} else if d.height >= 256 {
|
||||
hiPad = 3 + 4 // 3 mode bits + 4 low bits
|
||||
}
|
||||
|
||||
// start transfer
|
||||
d.csPin.High()
|
||||
|
||||
for i := int16(0); i < d.height; i++ {
|
||||
if d.diffing {
|
||||
// Skip rendering lines that haven't changed.
|
||||
linediv := (i + 1) / 8
|
||||
linemod := uint8((i + 1) % 8)
|
||||
if !hasBit(d.lineDiff[linediv], linemod) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// The first 5 bits are either dummy or part of the current line
|
||||
// (1-indexed) if it overflows 8-bits.
|
||||
// The last 3 bits are the command for the first line and dummy bits
|
||||
// for subsequent lines (set as command for simplicity)
|
||||
hi := uint8((i + 1) >> 8)
|
||||
hi = hi << hiPad
|
||||
d.txBuf[0] = cmd | hi
|
||||
|
||||
// The second byte is the low bits of the current line (1-indexed).
|
||||
// for <8 bits cases, the high bits are dummy, so we leave them as 0.
|
||||
d.txBuf[1] = uint8(i + 1)
|
||||
|
||||
// send the first two bytes
|
||||
err := d.bus.Tx(d.txBuf, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// send the line data
|
||||
err = d.bus.Tx(d.buffer[i*d.bytesPerLine:(i+1)*d.bytesPerLine], nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Trailer 16 bits (low)
|
||||
d.txBuf[0] = 0x00
|
||||
d.txBuf[1] = 0x00
|
||||
err := d.bus.Tx(d.txBuf, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// end transfer
|
||||
d.csPin.Low()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// holdDisplay simply toggles VCOM without updating any lines.
|
||||
func (d *Device) holdDisplay() error {
|
||||
d.txBuf[0] = d.vcom
|
||||
d.txBuf[1] = 0x00
|
||||
|
||||
d.toggleVcom()
|
||||
|
||||
// begin transaction
|
||||
d.csPin.High()
|
||||
|
||||
err := d.bus.Tx(d.txBuf, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// end transaction
|
||||
d.csPin.Low()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear clears both the in-memory buffer and the display.
|
||||
func (d *Device) Clear() error {
|
||||
if d.width == 0 {
|
||||
return errors.New("display not configured")
|
||||
}
|
||||
|
||||
d.ClearBuffer()
|
||||
return d.ClearDisplay()
|
||||
}
|
||||
|
||||
// ClearBuffer clears the in-memory buffer. The display is not updated.
|
||||
func (d *Device) ClearBuffer() {
|
||||
if d.width == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if d.diffing {
|
||||
// detect what rows need to be reset on the next render
|
||||
d.invalidateModifiedLines()
|
||||
}
|
||||
|
||||
// reset the in-memory buffer
|
||||
for i := 0; i < len(d.buffer); i++ {
|
||||
d.buffer[i] = 0xff
|
||||
}
|
||||
}
|
||||
|
||||
// invalidateModifiedLines marks any line that has at least a single black pixel
|
||||
// as invalidated. Padding bits, if any, are always 1.
|
||||
func (d *Device) invalidateModifiedLines() {
|
||||
for y := int16(0); y < d.height; y++ {
|
||||
offset := y * d.bytesPerLine
|
||||
|
||||
updateLine := false
|
||||
for x := int16(0); x < d.width; x++ {
|
||||
div := offset + x/8
|
||||
mod := uint8(x % 8)
|
||||
|
||||
if !hasBit(d.buffer[div], mod) {
|
||||
updateLine = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if updateLine {
|
||||
d.invalidateLine(y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ClearDisplay clears the display. The in-memory buffer is not updated. A
|
||||
// subsequent call to Display() will re-render the content as it was before
|
||||
// clearing.
|
||||
func (d *Device) ClearDisplay() error {
|
||||
if d.width == 0 {
|
||||
return errors.New("display not configured")
|
||||
}
|
||||
|
||||
d.txBuf[0] = d.vcom | bitClear
|
||||
d.txBuf[1] = 0x00
|
||||
|
||||
d.toggleVcom()
|
||||
|
||||
// begin transaction
|
||||
d.csPin.High()
|
||||
|
||||
err := d.bus.Tx(d.txBuf, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// end transaction
|
||||
d.csPin.Low()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// invalidateLine marks a line and the frame itself as invalidated.
|
||||
func (d *Device) invalidateLine(line int16) {
|
||||
// mark the frame as invalidated
|
||||
d.lineDiff[0] = setBit(d.lineDiff[0], 0)
|
||||
|
||||
// mark the line as invalidated
|
||||
linediv := (line + 1) / 8
|
||||
linemod := uint8((line + 1) % 8)
|
||||
d.lineDiff[linediv] = setBit(d.lineDiff[linediv], linemod)
|
||||
}
|
||||
|
||||
// toggleVcom toggles the VCOM, as is instructed by the datasheet.
|
||||
// Toggling VCOM can help maintain the display's longevity. It should ideally
|
||||
// be called at least once per second, preferably at 4-100 Hz.
|
||||
// Toggling VCOM causes a tiny bit of flicker, but without it the pixels can
|
||||
// be permanently damaged by the DC bias accumulating over time.
|
||||
func (d *Device) toggleVcom() {
|
||||
if d.vcom != 0 {
|
||||
d.vcom = 0x00
|
||||
} else {
|
||||
d.vcom = bitVcom
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package sharpmem
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math/rand/v2"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func Test_setBit(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for i := uint8(0); i < 8; i++ {
|
||||
v := uint8(1) << i
|
||||
|
||||
c.Assert(setBit(0x00, i), qt.Equals, v)
|
||||
c.Assert(setBit(0x00, (i+1)%8), qt.Not(qt.Equals), v)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_unsetBit(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for i := uint8(0); i < 8; i++ {
|
||||
v := uint8(1) << i
|
||||
|
||||
c.Assert(unsetBit(v, i), qt.Equals, uint8(0x00))
|
||||
c.Assert(unsetBit(v, (i+1)%8), qt.Not(qt.Equals), uint8(0x00))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_hasBit(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for i := uint8(0); i < 8; i++ {
|
||||
v := uint8(1) << i
|
||||
|
||||
c.Assert(hasBit(v, i), qt.Equals, true)
|
||||
c.Assert(hasBit(v, (i+1)%8), qt.Equals, false)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_bitfieldBufLen(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for i := int16(1); i < 536; i++ {
|
||||
requiredBufferSize := i / 8
|
||||
wouldOverflow := i % 8
|
||||
|
||||
if wouldOverflow > 0 {
|
||||
requiredBufferSize += 1
|
||||
}
|
||||
|
||||
c.Assert(bitfieldBufLen(i), qt.Equals, requiredBufferSize)
|
||||
}
|
||||
}
|
||||
|
||||
type mockBus struct {
|
||||
b []byte
|
||||
}
|
||||
|
||||
func (m *mockBus) Tx(w, _ []byte) error {
|
||||
m.b = append(m.b, w...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockBus) Transfer(b byte) (byte, error) {
|
||||
m.b = append(m.b, b)
|
||||
return 0x00, nil
|
||||
}
|
||||
|
||||
type mockPin struct{}
|
||||
|
||||
func (m mockPin) High() {
|
||||
}
|
||||
|
||||
func (m mockPin) Low() {
|
||||
}
|
||||
|
||||
func Test_Device(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
cfgs := []Config{
|
||||
ConfigLS010B7DH04,
|
||||
ConfigLS011B7DH03,
|
||||
ConfigLS012B7DD01,
|
||||
ConfigLS013B7DH03,
|
||||
ConfigLS013B7DH05,
|
||||
ConfigLS018B7DH02,
|
||||
ConfigLS027B7DH01,
|
||||
ConfigLS027B7DH01A,
|
||||
ConfigLS032B7DD02,
|
||||
ConfigLS044Q7DH01,
|
||||
}
|
||||
|
||||
cfgLen := len(cfgs)
|
||||
for i := 0; i < cfgLen; i++ {
|
||||
cfgs = append(cfgs, Config{
|
||||
Width: cfgs[i].Width,
|
||||
Height: cfgs[i].Height,
|
||||
DisableOptimizations: true,
|
||||
})
|
||||
}
|
||||
|
||||
spi := &mockBus{}
|
||||
pin := mockPin{}
|
||||
display := New(spi, pin)
|
||||
|
||||
for _, cfg := range cfgs {
|
||||
display.Configure(cfg)
|
||||
|
||||
x, y := display.Size()
|
||||
c.Assert(x, qt.Equals, cfg.Width)
|
||||
c.Assert(y, qt.Equals, cfg.Height)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
x := int16(rand.IntN(int(cfg.Width)))
|
||||
y := int16(rand.IntN(int(cfg.Height)))
|
||||
display.SetPixel(x, y, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
x := int16(rand.IntN(int(cfg.Width)))
|
||||
y := int16(rand.IntN(int(cfg.Height)))
|
||||
display.SetPixel(x, y, color.RGBA{R: 0, G: 0, B: 0, A: 255})
|
||||
}
|
||||
|
||||
err := display.Display()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
|
||||
err = display.ClearDisplay()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
|
||||
display.ClearBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HiPad(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
spi := &mockBus{}
|
||||
pin := mockPin{}
|
||||
display := New(spi, pin)
|
||||
|
||||
t.Run("LS011B7DH03, 8-bit address", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
spi.b = nil
|
||||
})
|
||||
|
||||
display.Configure(ConfigLS011B7DH03)
|
||||
|
||||
display.SetPixel(0, display.height-1, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||
err := display.Display()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
|
||||
// 160 perfectly divisible by 16, so 20 bytes of pixel data
|
||||
c.Assert(spi.b, qt.HasLen, 2+20+2)
|
||||
|
||||
// line is 1-indexed on the wire (67+1)
|
||||
// 68 in binary
|
||||
// 0b01000100
|
||||
|
||||
// DDDDDMMM
|
||||
c.Assert(spi.b[0], qt.Equals, uint8(0b00000011)) // mode 1, vcom is high on first run
|
||||
|
||||
c.Assert(spi.b[1], qt.Equals, uint8(0b01000100)) // the actual address
|
||||
})
|
||||
|
||||
t.Run("LS018B7DH02, 9-bit address", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
spi.b = nil
|
||||
})
|
||||
|
||||
display.Configure(ConfigLS018B7DH02)
|
||||
|
||||
display.SetPixel(0, display.height-1, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||
err := display.Display()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
|
||||
// 2 first bytes command+address
|
||||
// 230 bits are not divisible by 16, 240 is (15*16), so 30 bytes for line data
|
||||
// 2 trailing bytes
|
||||
c.Assert(spi.b, qt.HasLen, 2+30+2)
|
||||
|
||||
// line is 1-indexed on the wire (302+1)
|
||||
// 303 in binary (split in 2 bytes)
|
||||
// R
|
||||
// 0b00000001 0b00101111
|
||||
// ^
|
||||
|
||||
// RDDDDMMM
|
||||
c.Assert(spi.b[0], qt.Equals, uint8(0b10000011)) // mode 1, vcom is high on first run
|
||||
// ^
|
||||
|
||||
c.Assert(spi.b[1], qt.Equals, uint8(0b00101111)) // rest of the address (low 8 bits)
|
||||
})
|
||||
|
||||
t.Run("LS032B7DD02, 10-bit address", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
spi.b = nil
|
||||
})
|
||||
|
||||
display.Configure(ConfigLS032B7DD02)
|
||||
|
||||
display.SetPixel(0, display.height-1, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||
err := display.Display()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
|
||||
c.Assert(spi.b, qt.HasLen, 2+336/8+2) // 2 command+address, width / 2, 2 trailing bytes
|
||||
|
||||
// line is 1-indexed on the wire (535+1)
|
||||
// 536 in binary (split in 2 bytes)
|
||||
// RR
|
||||
// 0b00000010 0b00011000
|
||||
// ^^
|
||||
|
||||
// RRDDDMMM
|
||||
c.Assert(spi.b[0], qt.Equals, uint8(0b10000011)) // mode 1, vcom is high on first run
|
||||
// ^^
|
||||
|
||||
c.Assert(spi.b[1], qt.Equals, uint8(0b00011000)) // rest of the address (low 8 bits)
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package sharpmem
|
||||
|
||||
// setBit sets the bit at pos in n to 1 and returns the updated number.
|
||||
func setBit(n uint8, pos uint8) uint8 {
|
||||
n |= 1 << pos
|
||||
return n
|
||||
}
|
||||
|
||||
// unsetBit sets the bit at pos in n to 0 and returns the updated number.
|
||||
func unsetBit(n uint8, pos uint8) uint8 {
|
||||
n &^= 1 << pos
|
||||
return n
|
||||
}
|
||||
|
||||
// hasBit returns whether the bit at pos in n is 1.
|
||||
func hasBit(n uint8, pos uint8) bool {
|
||||
n = n & (1 << pos)
|
||||
return n > 0
|
||||
}
|
||||
|
||||
// bitfieldBufLen returns the required buffer size for keeping track of
|
||||
// changed lines.
|
||||
func bitfieldBufLen(bits int16) int16 {
|
||||
return 1 + (bits-1)/8
|
||||
}
|
||||
|
||||
// ceilDiv divides a with b, but it uses the ceiling if modulo is not 0.
|
||||
func ceilDiv(a, b int16) int16 {
|
||||
return 1 + (a-1)/b
|
||||
}
|
||||
+14
-3
@@ -44,6 +44,7 @@ tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/lps22hb/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/lsm303agr/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/lsm303dlhc/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/lsm6ds3/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mag3110/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017/main.go
|
||||
@@ -58,15 +59,17 @@ tinygo build -size short -o ./build/test.hex -target=p1am-100 ./examples/p1am/ma
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/pca9685/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setbuffer/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/seesaw
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/seesaw/soil-sensor
|
||||
tinygo build -size short -o ./build/test.hex -target=qtpy-rp2040 ./examples/seesaw/rotary-encoder
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/servo
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/sgp30
|
||||
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht4x/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/shtc3/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/i2c_128x32/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/spi_128x64/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao-ble ./examples/ssd1306/
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao-rp2040 ./examples/ssd1306/
|
||||
tinygo build -size short -o ./build/test.hex -target=thumby ./examples/ssd1306/
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1331/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7735/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/main.go
|
||||
@@ -136,6 +139,11 @@ tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mpu9150/mai
|
||||
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/sh1106/macropad_spi
|
||||
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/encoders/quadrature-interrupt
|
||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mcp9808/main.go
|
||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/tmc2209/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/tmc5160/main.go
|
||||
tinygo build -size short -o ./build/test.uf2 -target=nicenano ./examples/sharpmem/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/max6675/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
|
||||
# network examples (espat)
|
||||
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
||||
# network examples (wifinina)
|
||||
@@ -150,3 +158,6 @@ tinygo build -size short -o ./build/test.hex -target=nano-rp2040 -stack-size 8kb
|
||||
tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb ./examples/net/webclient/
|
||||
tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb ./examples/net/webserver/
|
||||
tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb ./examples/net/mqttclient/paho/
|
||||
# network examples (comboat)
|
||||
tinygo build -size short -o ./build/test.hex -target=elecrow-rp2040 -stack-size 8kb ./examples/net/tlsclient/
|
||||
tinygo build -size short -o ./build/test.hex -target=elecrow-rp2350 -stack-size 8kb ./examples/net/ntpclient/
|
||||
|
||||
+57
-139
@@ -6,11 +6,9 @@ package ssd1306 // import "tinygo.org/x/drivers/ssd1306"
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/pixel"
|
||||
)
|
||||
|
||||
@@ -23,16 +21,15 @@ type ResetValue [2]byte
|
||||
|
||||
// Device wraps I2C or SPI connection.
|
||||
type Device struct {
|
||||
bus Buser
|
||||
buffer []byte
|
||||
width int16
|
||||
height int16
|
||||
bufferSize int16
|
||||
vccState VccMode
|
||||
canReset bool
|
||||
resetCol ResetValue
|
||||
resetPage ResetValue
|
||||
rotation drivers.Rotation
|
||||
bus Buser
|
||||
buffer []byte
|
||||
width int16
|
||||
height int16
|
||||
vccState VccMode
|
||||
canReset bool
|
||||
resetCol ResetValue
|
||||
resetPage ResetValue
|
||||
rotation drivers.Rotation
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
@@ -51,51 +48,15 @@ type Config struct {
|
||||
Rotation drivers.Rotation
|
||||
}
|
||||
|
||||
type I2CBus struct {
|
||||
wire drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
type SPIBus struct {
|
||||
wire drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
}
|
||||
|
||||
type Buser interface {
|
||||
configure() error
|
||||
tx(data []byte, isCommand bool) error
|
||||
setAddress(address uint16) error
|
||||
configure(address uint16, size int16) []byte // configure the bus and return the image buffer to use
|
||||
command(cmd uint8) error // send a command to the display
|
||||
flush() error // send the image to the display, faster than "tx()" in i2c case since avoids slice copy
|
||||
tx(data []byte, isCommand bool) error // generic transmit function
|
||||
}
|
||||
|
||||
type VccMode uint8
|
||||
|
||||
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
|
||||
func NewI2C(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: &I2CBus{
|
||||
wire: bus,
|
||||
Address: Address,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
|
||||
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
return Device{
|
||||
bus: &SPIBus{
|
||||
wire: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Configure initializes the display with default configuration
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
var zeroReset ResetValue
|
||||
@@ -109,9 +70,6 @@ func (d *Device) Configure(cfg Config) {
|
||||
} else {
|
||||
d.height = 64
|
||||
}
|
||||
if cfg.Address != 0 {
|
||||
d.bus.setAddress(cfg.Address)
|
||||
}
|
||||
if cfg.VccState != 0 {
|
||||
d.vccState = cfg.VccState
|
||||
} else {
|
||||
@@ -127,11 +85,9 @@ func (d *Device) Configure(cfg Config) {
|
||||
} else {
|
||||
d.resetPage = ResetValue{0, uint8(d.height/8) - 1}
|
||||
}
|
||||
d.bufferSize = d.width * d.height / 8
|
||||
d.buffer = make([]byte, d.bufferSize)
|
||||
d.canReset = cfg.Address != 0 || d.width != 128 || d.height != 64 // I2C or not 128x64
|
||||
|
||||
d.bus.configure()
|
||||
d.buffer = d.bus.configure(cfg.Address, d.width*d.height/8)
|
||||
|
||||
time.Sleep(100 * time.Nanosecond)
|
||||
d.Command(DISPLAYOFF)
|
||||
@@ -193,11 +149,22 @@ func (d *Device) Configure(cfg Config) {
|
||||
d.Command(NORMALDISPLAY)
|
||||
d.Command(DEACTIVATE_SCROLL)
|
||||
d.Command(DISPLAYON)
|
||||
|
||||
}
|
||||
|
||||
// Command sends a command to the display
|
||||
func (d *Device) Command(command uint8) {
|
||||
d.bus.command(command)
|
||||
}
|
||||
|
||||
// Tx sends data to the display; if isCommand is false, this also updates the image buffer.
|
||||
func (d *Device) Tx(data []byte, isCommand bool) error {
|
||||
return d.bus.tx(data, isCommand)
|
||||
}
|
||||
|
||||
// ClearBuffer clears the image buffer
|
||||
func (d *Device) ClearBuffer() {
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
for i := 0; i < len(d.buffer); i++ {
|
||||
d.buffer[i] = 0
|
||||
}
|
||||
}
|
||||
@@ -223,7 +190,7 @@ func (d *Device) Display() error {
|
||||
d.Command(d.resetPage[1])
|
||||
}
|
||||
|
||||
return d.Tx(d.buffer, false)
|
||||
return d.bus.flush()
|
||||
}
|
||||
|
||||
// SetPixel enables or disables a pixel in the buffer
|
||||
@@ -252,12 +219,10 @@ func (d *Device) GetPixel(x int16, y int16) bool {
|
||||
|
||||
// SetBuffer changes the whole buffer at once
|
||||
func (d *Device) SetBuffer(buffer []byte) error {
|
||||
if int16(len(buffer)) != d.bufferSize {
|
||||
if len(buffer) != len(d.buffer) {
|
||||
return errBufferSize
|
||||
}
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
d.buffer[i] = buffer[i]
|
||||
}
|
||||
copy(d.buffer, buffer)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -266,81 +231,6 @@ func (d *Device) GetBuffer() []byte {
|
||||
return d.buffer
|
||||
}
|
||||
|
||||
// Command sends a command to the display
|
||||
func (d *Device) Command(command uint8) {
|
||||
d.bus.tx([]byte{command}, true)
|
||||
}
|
||||
|
||||
// setAddress sets the address to the I2C bus
|
||||
func (b *I2CBus) setAddress(address uint16) error {
|
||||
b.Address = address
|
||||
return nil
|
||||
}
|
||||
|
||||
// setAddress does nothing, but it's required to avoid reflection
|
||||
func (b *SPIBus) setAddress(address uint16) error {
|
||||
// do nothing
|
||||
println("trying to Configure an address on a SPI device")
|
||||
return nil
|
||||
}
|
||||
|
||||
// configure does nothing, but it's required to avoid reflection
|
||||
func (b *I2CBus) configure() error { return nil }
|
||||
|
||||
// configure configures some pins with the SPI bus
|
||||
func (b *SPIBus) configure() error {
|
||||
b.csPin.Low()
|
||||
b.dcPin.Low()
|
||||
b.resetPin.Low()
|
||||
|
||||
b.resetPin.High()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
b.resetPin.Low()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
b.resetPin.High()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tx sends data to the display
|
||||
func (d *Device) Tx(data []byte, isCommand bool) error {
|
||||
return d.bus.tx(data, isCommand)
|
||||
}
|
||||
|
||||
// tx sends data to the display (I2CBus implementation)
|
||||
func (b *I2CBus) tx(data []byte, isCommand bool) error {
|
||||
if isCommand {
|
||||
return legacy.WriteRegister(b.wire, uint8(b.Address), 0x00, data)
|
||||
} else {
|
||||
return legacy.WriteRegister(b.wire, uint8(b.Address), 0x40, data)
|
||||
}
|
||||
}
|
||||
|
||||
// tx sends data to the display (SPIBus implementation)
|
||||
func (b *SPIBus) tx(data []byte, isCommand bool) error {
|
||||
var err error
|
||||
|
||||
if isCommand {
|
||||
b.csPin.High()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
b.dcPin.Low()
|
||||
b.csPin.Low()
|
||||
|
||||
err = b.wire.Tx(data, nil)
|
||||
b.csPin.High()
|
||||
} else {
|
||||
b.csPin.High()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
b.dcPin.High()
|
||||
b.csPin.Low()
|
||||
|
||||
err = b.wire.Tx(data, nil)
|
||||
b.csPin.High()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
return d.width, d.height
|
||||
@@ -396,3 +286,31 @@ func (d *Device) Sleep(sleepEnabled bool) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a given coordinates with a color
|
||||
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||
dw, dh := d.Size()
|
||||
|
||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||
x >= d.width || (x+width) > dw || y >= dh || (y+height) > dh {
|
||||
return errOutOfRange
|
||||
}
|
||||
|
||||
if x+width == dw && y+height == dh && c.R == 0 && c.G == 0 && c.B == 0 {
|
||||
d.ClearDisplay()
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := x; i < x+width; i++ {
|
||||
for j := y; j < y+height; j++ {
|
||||
d.SetPixel(i, j, c)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetScroll sets the vertical scrolling for the display, which is a NOP for this display.
|
||||
func (d *Device) SetScroll(line int16) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package ssd1306
|
||||
|
||||
import (
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type I2CBus struct {
|
||||
wire drivers.I2C
|
||||
address uint16
|
||||
buffer []byte // buffer to avoid heap allocations
|
||||
}
|
||||
|
||||
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
|
||||
func NewI2C(bus drivers.I2C) *Device {
|
||||
return &Device{
|
||||
bus: &I2CBus{
|
||||
wire: bus,
|
||||
address: Address,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// configure address for the I2C bus and allocate the buffer
|
||||
func (b *I2CBus) configure(address uint16, size int16) []byte {
|
||||
if address != 0 {
|
||||
b.address = address
|
||||
}
|
||||
b.buffer = make([]byte, size+2) // +1 for the mode and +1 for a command
|
||||
return b.buffer[2:] // return the image buffer
|
||||
}
|
||||
|
||||
// command sends a command to the display
|
||||
func (b *I2CBus) command(cmd uint8) error {
|
||||
b.buffer[0] = 0x00 // Command mode
|
||||
b.buffer[1] = cmd
|
||||
return b.wire.Tx(b.address, b.buffer[:2], nil)
|
||||
}
|
||||
|
||||
// flush sends the image to the display
|
||||
func (b *I2CBus) flush() error {
|
||||
b.buffer[1] = 0x40 // Data mode
|
||||
return b.wire.Tx(b.address, b.buffer[1:], nil)
|
||||
}
|
||||
|
||||
// tx sends data to the display
|
||||
func (b *I2CBus) tx(data []byte, isCommand bool) error {
|
||||
if isCommand {
|
||||
return b.command(data[0])
|
||||
}
|
||||
copy(b.buffer[2:], data)
|
||||
return b.flush()
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package ssd1306
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type SPIBus struct {
|
||||
wire drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
buffer []byte // buffer to avoid heap allocations
|
||||
}
|
||||
|
||||
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
|
||||
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) *Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
return &Device{
|
||||
bus: &SPIBus{
|
||||
wire: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// configure pins with the SPI bus and allocate the buffer
|
||||
func (b *SPIBus) configure(address uint16, size int16) []byte {
|
||||
b.csPin.Low()
|
||||
b.dcPin.Low()
|
||||
b.resetPin.Low()
|
||||
|
||||
b.resetPin.High()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
b.resetPin.Low()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
b.resetPin.High()
|
||||
|
||||
b.buffer = make([]byte, size+1) // +1 for a command
|
||||
return b.buffer[1:] // return the image buffer
|
||||
}
|
||||
|
||||
// command sends a command to the display
|
||||
func (b *SPIBus) command(cmd uint8) error {
|
||||
b.buffer[0] = cmd
|
||||
return b.tx(b.buffer[:1], true)
|
||||
}
|
||||
|
||||
// flush sends the image to the display
|
||||
func (b *SPIBus) flush() error {
|
||||
return b.tx(b.buffer[1:], false)
|
||||
}
|
||||
|
||||
// tx sends data to the display
|
||||
func (b *SPIBus) tx(data []byte, isCommand bool) error {
|
||||
b.csPin.High()
|
||||
b.dcPin.Set(!isCommand)
|
||||
b.csPin.Low()
|
||||
err := b.wire.Tx(data, nil)
|
||||
b.csPin.High()
|
||||
return err
|
||||
}
|
||||
+1
-1
@@ -43,7 +43,7 @@ func (d *Device) GetRadioEventChan() chan lora.RadioEvent {
|
||||
}
|
||||
|
||||
// New creates a new SX127x connection. The SPI bus must already be configured.
|
||||
func New(spi machine.SPI, rstPin machine.Pin) *Device {
|
||||
func New(spi drivers.SPI, rstPin machine.Pin) *Device {
|
||||
k := Device{
|
||||
spi: spi,
|
||||
rstPin: rstPin,
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
# TMC2209 Go Package
|
||||
|
||||
This package provides a lightweight interface to communicate with the TMC2209 stepper motor driver via UART. It is designed to be used with microcontrollers and supports both default UART communication and custom interface implementations for flexibility.
|
||||
Currently this package only implements the communications with TMC2209. Functions to handle specific operations such as EnableStealthChop() etc. will need to be implemented by the user.
|
||||
## Features
|
||||
- **UART Communication:** Standard communication through UART for controlling the TMC2209 driver.
|
||||
- **Custom Communication Interfaces:** Allows custom implementations of communication interfaces (e.g., USB, SPI, etc.) via the `RegisterComm` interface. The usecase for this is if you have host system that can "talk" to the microcontroller connected to the TMC2209
|
||||
- **Error Handling:** Lightweight error handling for TinyGo compatibility.
|
||||
- **Register Access:** Provides methods to read and write registers on the TMC2209.
|
||||
|
||||
## Setup
|
||||
|
||||
### Prerequisites
|
||||
- **TinyGo**: This package is optimized for TinyGo, which is suitable for running Go on microcontrollers and embedded systems.
|
||||
- **UART Communication**: For microcontrollers with UART support, such as ESP32, STM32, and Raspberry Pi Pico.
|
||||
|
||||
|
||||
## Usage
|
||||
### Using with Microcontrollers (Default UART)
|
||||
|
||||
To use the package with a microcontroller, you need to initialize the UART communication and configure the TMC2209 driver.
|
||||
Example:
|
||||
|
||||
```aiignore
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourusername/tmc2209"
|
||||
"machine"
|
||||
)
|
||||
|
||||
func main() {
|
||||
uart := machine.UART0
|
||||
|
||||
// Create an instance of the TMC2209 with UART communication
|
||||
tmc := tmc2209.NewTMC2209(uart, 0x00) // Replace 0x00 with the appropriate address
|
||||
|
||||
// Set up the TMC2209 driver
|
||||
err := tmc.Setup()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to set up TMC2209: %v", 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 {
|
||||
log.Fatalf("Failed to write register: %v", err)
|
||||
}
|
||||
|
||||
// Read from a register (example: reading a register value)
|
||||
value, err := tmc.ReadRegister(0x10)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read register: %v", err)
|
||||
}
|
||||
|
||||
// Output the read value
|
||||
fmt.Printf("Register value: 0x%X\n", value)
|
||||
}
|
||||
|
||||
```
|
||||
## Microcontroller Notes
|
||||
|
||||
- **TinyGo Support:** This code is optimized for use with TinyGo on supported microcontrollers.
|
||||
- **UART Configuration:** Ensure the UART instance is configured correctly for your microcontroller.
|
||||
- The machine.UART0 in the example is for the default UART on TinyGo-compatible devices like the Raspberry Pi Pico. Check your microcontroller's documentation for the correct UART instance and pin configuration.
|
||||
|
||||
## 2. Custom Interface Implementation
|
||||
|
||||
If you want to use a custom communication interface (e.g., USB, SPI, etc.), you can implement the RegisterComm interface.
|
||||
Custom Interface Example (e.g., USB Communication):
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourusername/tmc2209"
|
||||
"yourcustompackage" // Custom package for communication
|
||||
)
|
||||
|
||||
type CustomComm struct {
|
||||
// Implement your custom communication method here
|
||||
}
|
||||
|
||||
func (c *CustomComm) ReadRegister(register uint8, driverIndex uint8) (uint32, error) {
|
||||
// Implement the register read logic using your custom interface (USB, SPI, etc.)
|
||||
// For example, send the register read command over USB and read the response
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (c *CustomComm) WriteRegister(register uint8, value uint32, driverIndex uint8) error {
|
||||
// Implement the register write logic using your custom interface (USB, SPI, etc.)
|
||||
// For example, send the register write command over USB
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create an instance of the TMC2209 with your custom communication interface
|
||||
customComm := &CustomComm{}
|
||||
tmc := tmc2209.NewTMC2209(customComm, 0x00) // Replace 0x00 with the appropriate address
|
||||
|
||||
// Set up the TMC2209 driver
|
||||
err := tmc.Setup()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to set up TMC2209: %v", 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 {
|
||||
log.Fatalf("Failed to write register: %v", err)
|
||||
}
|
||||
|
||||
// Read from a register (example: reading a register value)
|
||||
value, err := tmc.ReadRegister(0x10)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read register: %v", err)
|
||||
}
|
||||
|
||||
// Output the read value
|
||||
fmt.Printf("Register value: 0x%X\n", value)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Custom Interface Notes:
|
||||
|
||||
- RegisterComm Interface: Your custom communication interface (e.g., USB, SPI) should implement the RegisterComm interface. This ensures that the TMC2209 driver can interact with your custom interface for reading and writing registers.
|
||||
- Error Handling: Implement proper error handling in your custom interface methods (ReadRegister and WriteRegister).
|
||||
|
||||
#### Functions
|
||||
```
|
||||
NewTMC2209(comm RegisterComm, address uint8) *TMC2209
|
||||
```
|
||||
Creates a new TMC2209 instance with the provided communication interface (comm) and driver address (address).
|
||||
|
||||
```Setup() error```
|
||||
|
||||
Initializes the communication interface. This is required before interacting with the TMC2209 driver. The method checks if the communication interface is UART and sets it up accordingly.
|
||||
|
||||
```WriteRegister(register uint8, value uint32) error```
|
||||
|
||||
Writes a value to a specific register on the TMC2209 driver.
|
||||
|
||||
```ReadRegister(register uint8) (uint32, error)```
|
||||
|
||||
Reads a value from a specific register on the TMC2209 driver.
|
||||
|
||||
### Error Handling
|
||||
|
||||
This package uses a lightweight custom error type (CustomError) to ensure compatibility with TinyGo and reduce external dependencies.
|
||||
Example Error:
|
||||
|
||||
```CustomError("communication interface not set")```
|
||||
|
||||
|
||||
Created by Amken3d
|
||||
info@amken3d.us
|
||||
|
||||
|
||||
+1409
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
package tmc2209
|
||||
|
||||
func Constrain(value, low, high uint32) uint32 {
|
||||
if value < low {
|
||||
return low
|
||||
}
|
||||
if value > high {
|
||||
return high
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func SetRunCurrent(percent uint8) {
|
||||
_ = PercentToCurrentSetting(percent)
|
||||
|
||||
// Set the run current register to runCurrent value
|
||||
}
|
||||
|
||||
func SetHoldCurrent(percent uint8) {
|
||||
_ = PercentToCurrentSetting(percent)
|
||||
// Set the hold current register to holdCurrent value
|
||||
}
|
||||
func PercentToCurrentSetting(percent uint8) uint8 {
|
||||
constrainedPercent := Constrain(uint32(percent), 0, 100)
|
||||
return uint8(Map(constrainedPercent, 0, 100, 0, 255))
|
||||
}
|
||||
|
||||
func CurrentSettingToPercent(currentSetting uint8) uint8 {
|
||||
return uint8(Map(uint32(currentSetting), 0, 255, 0, 100))
|
||||
}
|
||||
|
||||
func PercentToHoldDelaySetting(percent uint8) uint8 {
|
||||
constrainedPercent := Constrain(uint32(percent), 0, 100)
|
||||
return uint8(Map(constrainedPercent, 0, 100, 0, 255))
|
||||
}
|
||||
|
||||
func HoldDelaySettingToPercent(holdDelaySetting uint8) uint8 {
|
||||
return uint8(Map(uint32(holdDelaySetting), 0, 255, 0, 100))
|
||||
}
|
||||
func Map(value, fromLow, fromHigh, toLow, toHigh uint32) uint32 {
|
||||
return (value-fromLow)*(toHigh-toLow)/(fromHigh-fromLow) + toLow
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package tmc2209
|
||||
|
||||
func SetMicrostepsPerStep(microsteps uint16) uint8 {
|
||||
exponent := uint8(0)
|
||||
microstepsShifted := microsteps >> 1
|
||||
|
||||
for microstepsShifted > 0 {
|
||||
microstepsShifted = microstepsShifted >> 1
|
||||
exponent++
|
||||
}
|
||||
|
||||
SetMicrostepsPerStepPowerOfTwo(exponent)
|
||||
return exponent
|
||||
}
|
||||
|
||||
func SetMicrostepsPerStepPowerOfTwo(exponent uint8) {
|
||||
switch exponent {
|
||||
case 0:
|
||||
// Set MRES_001
|
||||
case 1:
|
||||
// Set MRES_002
|
||||
case 2:
|
||||
// Set MRES_004
|
||||
case 3:
|
||||
// Set MRES_008
|
||||
case 4:
|
||||
// Set MRES_016
|
||||
case 5:
|
||||
// Set MRES_032
|
||||
case 6:
|
||||
// Set MRES_064
|
||||
case 7:
|
||||
// Set MRES_128
|
||||
default:
|
||||
// Set MRES_256
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package tmc2209
|
||||
|
||||
func EnableStealthChop() {
|
||||
// Set StealthChop enabled in the global config register
|
||||
}
|
||||
|
||||
func DisableStealthChop() {
|
||||
// Set StealthChop disabled in the global config register
|
||||
}
|
||||
|
||||
func EnableCoolStep(lowerThreshold, upperThreshold uint8) {
|
||||
// Enable CoolStep with specified thresholds
|
||||
}
|
||||
|
||||
func DisableCoolStep() {
|
||||
// Disable CoolStep feature
|
||||
}
|
||||
|
||||
func EnableAutomaticCurrentScaling() {
|
||||
// Enable Automatic Current Scaling
|
||||
}
|
||||
|
||||
func DisableAutomaticCurrentScaling() {
|
||||
// Disable Automatic Current Scaling
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//go:build tinygo
|
||||
|
||||
package tmc2209
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// TMC2209 represents a single TMC2209 stepper motor driver on the communication line.
|
||||
type TMC2209 struct {
|
||||
comm RegisterComm
|
||||
address uint8
|
||||
}
|
||||
|
||||
// NewTMC2209 creates a new instance of the TMC2209 driver for a specific address.
|
||||
func NewTMC2209(comm RegisterComm, address uint8) *TMC2209 {
|
||||
return &TMC2209{
|
||||
comm: comm,
|
||||
address: address,
|
||||
}
|
||||
}
|
||||
|
||||
// Setup initializes the communication interface with the TMC2209.
|
||||
func (driver *TMC2209) Setup() error {
|
||||
// Check if comm is of type *UARTComm
|
||||
if uartComm, ok := driver.comm.(*UARTComm); ok {
|
||||
// Call Setup only if comm is a *UARTComm
|
||||
err := uartComm.Setup()
|
||||
if err != nil {
|
||||
return CustomError("Failed to setup UART communication: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
// If it's not a UARTComm, log that it's using a different communication method
|
||||
log.Println("Using a non-UART communication method")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRegister sends a register write command to the TMC2209.
|
||||
func (driver *TMC2209) WriteRegister(reg uint8, value uint32) error {
|
||||
if driver.comm == nil {
|
||||
return CustomError("communication interface not set")
|
||||
}
|
||||
// Use the communication interface (RegisterComm) to write the register
|
||||
return driver.comm.WriteRegister(reg, value, driver.address)
|
||||
}
|
||||
|
||||
// ReadRegister sends a register read command to the TMC2209 and returns the read value.
|
||||
func (driver *TMC2209) ReadRegister(reg uint8) (uint32, error) {
|
||||
if driver.comm == nil {
|
||||
return 0, CustomError("communication interface not set")
|
||||
}
|
||||
// Use the communication interface (RegisterComm) to read the register
|
||||
return driver.comm.ReadRegister(reg, driver.address)
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
//go:build tinygo
|
||||
|
||||
package tmc2209
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CustomError is a lightweight error type used for TinyGo compatibility.
|
||||
type CustomError string
|
||||
|
||||
func (e CustomError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// UARTComm implements RegisterComm for UART-based communication
|
||||
type UARTComm struct {
|
||||
uart machine.UART
|
||||
address uint8
|
||||
}
|
||||
|
||||
// NewUARTComm creates a new UARTComm instance.
|
||||
func NewUARTComm(uart machine.UART, address uint8) *UARTComm {
|
||||
return &UARTComm{
|
||||
uart: uart,
|
||||
address: address,
|
||||
}
|
||||
}
|
||||
|
||||
// Setup initializes the UART communication with the TMC2209.
|
||||
func (comm *UARTComm) Setup() error {
|
||||
// Check if UART is initialized
|
||||
if comm.uart == (machine.UART{}) {
|
||||
return CustomError("UART not initialized")
|
||||
}
|
||||
|
||||
// Configure the UART interface with the desired baud rate and settings
|
||||
err := comm.uart.Configure(machine.UARTConfig{
|
||||
BaudRate: 115200,
|
||||
})
|
||||
if err != nil {
|
||||
return CustomError("Failed to configure UART")
|
||||
}
|
||||
|
||||
// No built-in timeout in TinyGo, so timeout will be handled in the read/write methods
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRegister sends a register write command to the TMC2209 with a timeout.
|
||||
func (comm *UARTComm) WriteRegister(register uint8, value uint32, driverIndex uint8) error {
|
||||
buffer := []byte{
|
||||
0x05, // Sync byte
|
||||
comm.address, // Slave address
|
||||
register | 0x80, // Write command (set MSB to 1 for write)
|
||||
byte((value >> 24) & 0xFF), // MSB of value
|
||||
byte((value >> 16) & 0xFF), // Middle byte
|
||||
byte((value >> 8) & 0xFF), // Next byte
|
||||
byte(value & 0xFF), // LSB of value
|
||||
0, // CRC
|
||||
}
|
||||
|
||||
buffer[7] = CalculateCRC(buffer[:7])
|
||||
|
||||
// Write the data to the TMC2209
|
||||
done := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
comm.uart.Write(buffer)
|
||||
done <- nil
|
||||
}()
|
||||
|
||||
// Implementing timeout using a 100ms timer
|
||||
select {
|
||||
case err := <-done:
|
||||
return err
|
||||
case <-time.After(100 * time.Millisecond): // Timeout after 100ms
|
||||
return CustomError("write timeout")
|
||||
}
|
||||
}
|
||||
|
||||
// ReadRegister sends a register read command to the TMC2209 with a timeout.
|
||||
func (comm *UARTComm) ReadRegister(register uint8, driverIndex uint8) (uint32, error) {
|
||||
var writeBuffer [4]byte
|
||||
writeBuffer[0] = 0x05 // Sync byte
|
||||
writeBuffer[1] = 0x00 // Slave address
|
||||
writeBuffer[2] = register & 0x7F // Read command (MSB clear for read)
|
||||
writeBuffer[3] = CalculateCRC(writeBuffer[:3])
|
||||
|
||||
// Send the read command
|
||||
done := make(chan []byte, 1)
|
||||
go func() {
|
||||
comm.uart.Write(writeBuffer[:])
|
||||
readBuffer := make([]byte, 8)
|
||||
comm.uart.Read(readBuffer)
|
||||
done <- readBuffer
|
||||
}()
|
||||
|
||||
// Implementing timeout using a 100ms timer
|
||||
select {
|
||||
case readBuffer := <-done:
|
||||
checksum := CalculateCRC(readBuffer[:7])
|
||||
if checksum != readBuffer[7] {
|
||||
return 0, CustomError("checksum error")
|
||||
}
|
||||
|
||||
// Return the value from the register
|
||||
return uint32(readBuffer[3])<<24 | uint32(readBuffer[4])<<16 | uint32(readBuffer[5])<<8 | uint32(readBuffer[6]), nil
|
||||
case <-time.After(100 * time.Millisecond): // Timeout after 100ms
|
||||
return 0, CustomError("read timeout")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package tmc2209
|
||||
|
||||
import "log"
|
||||
|
||||
func CalculateCRC(data []byte) uint8 {
|
||||
crc := uint8(0)
|
||||
for _, byte := range data {
|
||||
for i := 0; i < 8; i++ {
|
||||
if (crc>>7)^(byte&0x01) == 1 {
|
||||
crc = (crc << 1) ^ 0x07
|
||||
} else {
|
||||
crc = crc << 1
|
||||
}
|
||||
byte >>= 1
|
||||
}
|
||||
}
|
||||
return crc
|
||||
}
|
||||
|
||||
// VerifyCommunication checks the communication with the TMC2209 by reading the version register (IOIN).
|
||||
// It returns true if the communication is successful (i.e., the version matches the expected version).
|
||||
// VerifyCommunication verifies the communication with the TMC2209 by reading the version register (IOIN).
|
||||
// It explicitly resets the struct and de-references it after the check to ensure memory is managed manually.
|
||||
func VerifyCommunication(comm RegisterComm, driverIndex uint8) bool {
|
||||
var io *Ioin
|
||||
if io == nil {
|
||||
io = NewIoin() // Initialize the struct if not already initialized
|
||||
} else {
|
||||
*io = Ioin{}
|
||||
}
|
||||
_, err := ReadRegister(comm, driverIndex, io.GetAddress())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if io.Version == expectedVersion {
|
||||
io = nil
|
||||
return true
|
||||
}
|
||||
io = nil
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckErrorStatus verifies the communication and checks for error flags in the TMC2209 driver status.
|
||||
// It explicitly resets the struct and de-references it when done to ensure memory is managed manually.
|
||||
func CheckErrorStatus(comm RegisterComm, driverIndex uint8) bool {
|
||||
var d *DrvStatus
|
||||
if d == nil {
|
||||
d = NewDrvStatus()
|
||||
} else {
|
||||
*d = DrvStatus{}
|
||||
}
|
||||
_, err := d.Read(comm, driverIndex)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
errorFlags := d.Ola | d.S2vsa | d.S2vsb | d.Ot | d.S2ga | d.S2gb | d.Olb
|
||||
if errorFlags != 0 {
|
||||
log.Printf("TMC2209 Error Detected: %X", errorFlags)
|
||||
return false
|
||||
}
|
||||
d = nil
|
||||
return true
|
||||
}
|
||||
|
||||
// GetInterfaceTransmissionCount reads the IFCNT register to check for UART transmission status
|
||||
func GetInterfaceTransmissionCount(comm RegisterComm, driverIndex uint8) (uint32, error) {
|
||||
ifcnt := NewIfcnt()
|
||||
_, err := ReadRegister(comm, driverIndex, ifcnt.GetAddress())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return ifcnt.Bytes, nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package tmc2209
|
||||
|
||||
func MoveAtVelocity(microstepsPerPeriod int32) {
|
||||
// Write velocity to the relevant register
|
||||
}
|
||||
func MoveUsingStepDirInterface() {
|
||||
// Set the interface to use step/direction interface
|
||||
}
|
||||
func GetVelocity() uint32 {
|
||||
// Read the actual velocity register
|
||||
return 0 // Placeholder return value
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
# TMC5160 Driver for Go (TinyGo)
|
||||
|
||||
This repository provides a Go-based driver for the **TMC5160** stepper motor driver, implemented for both **SPI** and **UART** communication modes. The driver allows you to easily interface with the TMC5160 to configure and control stepper motors.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Communication Modes](#communication-modes)
|
||||
- [SPI Mode](#spi-mode)
|
||||
- [UART Mode](#uart-mode)
|
||||
- [Usage Example](#usage-example)
|
||||
- [Setting and Getting Modes](#setting-and-getting-modes)
|
||||
- [Reading and Writing Registers](#reading-and-writing-registers)
|
||||
- [API Reference](#api-reference)
|
||||
- [License](#license)
|
||||
|
||||
## Installation
|
||||
|
||||
To use the TMC5160 driver, you'll need to have **TinyGo** installed. You can install TinyGo by following the [official installation guide](https://tinygo.org/getting-started/).
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **machine**: To interface with hardware on platforms like Raspberry Pi, STM32, etc.
|
||||
- **TinyGo**: A Go compiler for embedded systems.
|
||||
|
||||
Add the module
|
||||
|
||||
```bash
|
||||
import "tinygo.org/x/drivers/tmc5160"
|
||||
```
|
||||
|
||||
### Communication Modes
|
||||
|
||||
The TMC5160 supports two communication modes for controlling the motor:
|
||||
|
||||
**SPI Mode**
|
||||
|
||||
To communicate with the TMC5160 in SPI mode, you'll need to configure the SPI bus and the chip-select (CS) pin. This allows full-speed communication between your microcontroller and the TMC5160.
|
||||
SPI Setup
|
||||
|
||||
In SPI mode, you must configure the SPI interface on your microcontroller. Here's how to set up SPI communication for the TMC5160.
|
||||
|
||||
```go
|
||||
spi := machine.SPI1
|
||||
csPin := machine.GPIO13
|
||||
spi.Configure(machine.SPIConfig{
|
||||
SCK: machine.GPIO10,
|
||||
SDI: machine.GPIO11,
|
||||
SDO: machine.GPIO12,
|
||||
Frequency: 5000000,
|
||||
Mode: 3,
|
||||
LSBFirst: false,
|
||||
})
|
||||
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
```
|
||||
**Sending Commands via SPI**
|
||||
|
||||
The driver supports reading and writing registers using the SPIComm interface, which is initialized with the configured SPI bus and CS pins
|
||||
|
||||
```go
|
||||
comm := tmc5160.NewSPIComm(*spi, csPins)
|
||||
driver := tmc5160.NewTMC5160(comm, driverIndex)
|
||||
driver.WriteRegister(tmc5160.GCONF, value)
|
||||
|
||||
```
|
||||
|
||||
**UART Mode**
|
||||
|
||||
Alternatively, you can use UART mode to communicate with the TMC5160. UART mode is useful for cases where SPI is not available or when the TMC5160 is used in multi-driver configurations with limited SPI pins.
|
||||
UART Setup
|
||||
|
||||
In UART mode, you will need to configure the UART interface with the appropriate baud rate and settings:
|
||||
|
||||
```go
|
||||
uart := machine.UART0
|
||||
uart.Configure(machine.UARTConfig{
|
||||
BaudRate: 115200,
|
||||
})
|
||||
```
|
||||
#### Sending Commands via UART
|
||||
|
||||
The UART communication is handled through the UARTComm struct, which wraps the UART interface.
|
||||
|
||||
```go
|
||||
comm := tmc5160.NewUARTComm(uart, 0x01)
|
||||
driver := tmc5160.NewTMC5160(comm, 0)
|
||||
driver.WriteRegister(tmc5160.GCONF, 0x01)
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
Here’s a simple example of how to use the TMC5160 driver with SPI and UART modes:
|
||||
|
||||
```aiignore
|
||||
// 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{
|
||||
SCK: machine.GPIO10,
|
||||
SDI: machine.GPIO11,
|
||||
SDO: machine.GPIO12,
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
## Reading and Writing Registers
|
||||
|
||||
You can easily read and write registers using the WriteRegister and ReadRegister methods:
|
||||
|
||||
```aiignore
|
||||
// Write a value to a register
|
||||
err := driver.WriteRegister(tmc5160.GCONF, 0x01)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing register:", err)
|
||||
}
|
||||
|
||||
// Read a register
|
||||
value, err := driver.ReadRegister(tmc5160.GCONF)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading register:", err)
|
||||
} else {
|
||||
fmt.Println("Read value from GCONF:", value)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
NewSPIComm(spi *machine.SPI, csPins map[uint8]machine.Pin) *SPIComm
|
||||
|
||||
Creates a new SPI communication interface for the TMC5160.
|
||||
|
||||
NewUARTComm(uart machine.UART, address uint8) *UARTComm
|
||||
|
||||
Creates a new UART communication interface for the TMC5160.
|
||||
|
||||
NewTMC5160(comm RegisterComm, address uint8) *TMC5160
|
||||
|
||||
Creates a new instance of the TMC5160 driver.
|
||||
|
||||
WriteRegister(register uint8, value uint32) error
|
||||
|
||||
Writes a value to the specified register.
|
||||
|
||||
ReadRegister(register uint8) (uint32, error)
|
||||
|
||||
Reads a value from the specified register.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package tmc5160
|
||||
|
||||
// Driver Register addresses
|
||||
const (
|
||||
GCONF uint8 = 0x00 // Global configuration flags
|
||||
GSTAT uint8 = 0x01 // Global status flags
|
||||
IFCNT = 0x02 // UART transmission counter
|
||||
SLAVECONF = 0x03 // UART slave configuration
|
||||
IOIN = 0x04 // Read input / write output pins
|
||||
X_COMPARE = 0x05 // Position comparison register
|
||||
OTP_PROG = 0x06 // OTP programming register
|
||||
OTP_READ = 0x07 // OTP read register
|
||||
FACTORY_CONF = 0x08 // Factory configuration (clock trim)
|
||||
SHORT_CONF = 0x09 // Short detector configuration
|
||||
DRV_CONF = 0x0A // Driver configuration
|
||||
GLOBAL_SCALER = 0x0B // Global scaling of motor current
|
||||
OFFSET_READ = 0x0C // Offset calibration results
|
||||
|
||||
/* Velocity dependent driver feature control registers */
|
||||
IHOLD_IRUN = 0x10 // Driver current control
|
||||
TPOWERDOWN = 0x11 // Delay before power down
|
||||
TSTEP = 0x12 // Actual time between microsteps
|
||||
TPWMTHRS = 0x13 // Upper velocity for stealthChop voltage PWM mode
|
||||
TCOOLTHRS = 0x14 // Lower threshold velocity for switching on smart energy coolStep and stallGuard feature
|
||||
THIGH = 0x15 // Velocity threshold for switching into a different chopper mode and fullstepping
|
||||
|
||||
/* Ramp generator motion control registers */
|
||||
RAMPMODE = 0x20 // Driving mode (Velocity, Positioning, Hold)
|
||||
XACTUAL = 0x21 // Actual motor position
|
||||
VACTUAL = 0x22 // Actual motor velocity from ramp generator
|
||||
VSTART = 0x23 // Motor start velocity
|
||||
A_1 = 0x24 // First acceleration between VSTART and V1
|
||||
V_1 = 0x25 // First acceleration/deceleration phase target velocity
|
||||
AMAX = 0x26 // Second acceleration between V1 and VMAX
|
||||
VMAX = 0x27 // Target velocity in velocity mode
|
||||
DMAX = 0x28 // Deceleration between VMAX and V1
|
||||
D_1 = 0x2A // Deceleration between V1 and VSTOP
|
||||
//Attention: Do not set 0 in positioning mode, even if V1=0!
|
||||
VSTOP = 0x2B // Motor stop velocity
|
||||
//Attention: Set VSTOP > VSTART!
|
||||
//Attention: Do not set 0 in positioning mode, minimum 10 recommend!
|
||||
TZEROWAIT = 0x2C // Waiting time after ramping down to zero velocity before next movement or direction inversion can start.
|
||||
XTARGET = 0x2D // Target position for ramp mode
|
||||
|
||||
/* Ramp generator driver feature control registers */
|
||||
VDCMIN = 0x33 // Velocity threshold for enabling automatic commutation dcStep
|
||||
SW_MODE = 0x34 // Switch mode configuration
|
||||
RAMP_STAT = 0x35 // Ramp status and switch event status
|
||||
XLATCH = 0x36 // Ramp generator latch position upon programmable switch event
|
||||
|
||||
/* Encoder registers */
|
||||
ENCMODE = 0x38 // Encoder configuration and use of N channel
|
||||
X_ENC = 0x39 // Actual encoder position
|
||||
ENC_CONST = 0x3A // Accumulation constant
|
||||
ENC_STATUS = 0x3B // Encoder status information
|
||||
ENC_LATCH = 0x3C // Encoder position latched on N event
|
||||
ENC_DEVIATION = 0x3D // Maximum number of steps deviation between encoder counter and XACTUAL for deviation warning
|
||||
|
||||
/* Motor driver registers */
|
||||
MSLUT0 uint8 = 0x60 // 32 bits
|
||||
MSLUT1 uint8 = 0x61 // 32 bits
|
||||
MSLUT2 uint8 = 0x62 // 32 bits
|
||||
MSLUT3 uint8 = 0x63 // 32 bits
|
||||
MSLUT4 uint8 = 0x64 // 32 bits
|
||||
MSLUT5 uint8 = 0x65 // 32 bits
|
||||
MSLUT6 uint8 = 0x66 // 32 bits
|
||||
MSLUT7 uint8 = 0x67 // 32 bits
|
||||
MSLUTSEL = 0x68 // Look up table segmentation definition
|
||||
MSLUTSTART = 0x69 // Absolute current at microstep table entries 0 and 256
|
||||
MSCNT = 0x6A // Actual position in the microstep table
|
||||
MSCURACT = 0x6B // Actual microstep current
|
||||
CHOPCONF = 0x6C // Chopper and driver configuration
|
||||
COOLCONF = 0x6D // coolStep smart current control register and stallGuard2 configuration
|
||||
DCCTRL = 0x6E // dcStep automatic commutation configuration register
|
||||
DRV_STATUS = 0x6F // stallGuard2 value and driver error flags
|
||||
PWMCONF = 0x70 // stealthChop voltage PWM mode chopper configuration
|
||||
PWM_SCALE = 0x71 // Results of stealthChop amplitude regulator.
|
||||
PWM_AUTO = 0x72 // Automatically determined PWM config values
|
||||
LOST_STEPS = 0x73 // Number of input steps skipped due to dcStep. only with SD_MODE = 1
|
||||
expectedVersion = 0x03
|
||||
DEFAULT_F_CLK = 12000000
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
package tmc5160
|
||||
|
||||
import (
|
||||
"github.com/orsinium-labs/tinymath"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// VelocityToVMAX calculates the VMAX register value from the current stepper velocity which is in microsteps per tRef (i.e 1/clock speed)
|
||||
func (stepper *Stepper) CurrentVelocityToVMAX() uint32 {
|
||||
tref := float32(16777216) / (float32(stepper.Fclk) * 1000000)
|
||||
r := stepper.VelocitySPS * stepper.GearRatio * float32(tref)
|
||||
return constrain(uint32(r), 0, maxVMAX) // VMAX register value cannot exceed maxVMAX
|
||||
}
|
||||
func (stepper *Stepper) DesiredVelocityToVMAX(v float32) uint32 {
|
||||
tref := 16777216 / (float32(stepper.Fclk) * 1000000)
|
||||
r := tinymath.Round(v * stepper.GearRatio * tref)
|
||||
return constrain(uint32(r), 0, maxVMAX) // VMAX register value cannot exceed maxVMAX
|
||||
}
|
||||
|
||||
func (stepper *Stepper) DesiredAccelToAMAX(dacc float32, dVel float32) uint32 {
|
||||
dVelToVMAX := stepper.DesiredVelocityToVMAX(dVel)
|
||||
_a := uint64(dVelToVMAX) * 131072
|
||||
_b := float32(_a) / dacc
|
||||
_c := _b / float32(uint32(stepper.Fclk)*1000000)
|
||||
return uint32(_c)
|
||||
|
||||
}
|
||||
|
||||
// Convert threshold speed (Hz) to internal TSTEP value
|
||||
func (stepper *Stepper) DesiredSpeedToTSTEP(thrsSpeed uint32) uint32 {
|
||||
if thrsSpeed < 0 {
|
||||
return 0
|
||||
}
|
||||
_a := stepper.DesiredVelocityToVMAX(float32(thrsSpeed))
|
||||
_b := float32(16777216 / _a)
|
||||
_c := float32(stepper.MSteps) / float32(256)
|
||||
_d := uint32(_b * _c)
|
||||
return constrain(_d, 0, 1048575)
|
||||
}
|
||||
|
||||
func (stepper *Stepper) VMAXToTSTEP(vmax uint32) uint32 {
|
||||
_b := float32(16777216 / vmax)
|
||||
_c := float32(stepper.MSteps) / float32(256)
|
||||
_d := tinymath.Round(_b * _c)
|
||||
return constrain(uint32(_d), 0, 1048575)
|
||||
}
|
||||
|
||||
// Constrain function to limit values to a specific range (supports multiple types).
|
||||
func constrain[T constraints.Ordered](value, min, max T) T {
|
||||
if value < min {
|
||||
return min
|
||||
} else if value > max {
|
||||
return max
|
||||
}
|
||||
return value
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,134 @@
|
||||
//go:build tinygo
|
||||
|
||||
package tmc5160
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CustomError is a lightweight error type used for TinyGo compatibility.
|
||||
type CustomError string
|
||||
|
||||
func (e CustomError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// SPIComm implements RegisterComm for SPI-based communication
|
||||
type SPIComm struct {
|
||||
spi *machine.SPI
|
||||
CsPins map[uint8]machine.Pin // Map to store CS pin for each Driver by its address
|
||||
}
|
||||
|
||||
// NewSPIComm creates a new SPIComm instance.
|
||||
func NewSPIComm(spi *machine.SPI, csPins map[uint8]machine.Pin) *SPIComm {
|
||||
return &SPIComm{
|
||||
spi: spi,
|
||||
CsPins: csPins,
|
||||
}
|
||||
}
|
||||
|
||||
// Setup initializes the SPI communication with the Driver and configures all CS pins.
|
||||
func (comm *SPIComm) Setup() error {
|
||||
// Check if SPI is initialized
|
||||
if comm.spi == nil {
|
||||
return CustomError("SPI not initialized")
|
||||
}
|
||||
|
||||
// Configure all CS pins (make them output and set them high)
|
||||
for _, csPin := range comm.CsPins {
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.High() // Set all CS pins high initially
|
||||
}
|
||||
|
||||
// Configure the SPI interface with the desired settings
|
||||
err := comm.spi.Configure(machine.SPIConfig{
|
||||
LSBFirst: false,
|
||||
Mode: 3,
|
||||
})
|
||||
if err != nil {
|
||||
return CustomError("Failed to configure SPI")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRegister sends a register write command to the TMC5160.
|
||||
func (comm *SPIComm) WriteRegister(register uint8, value uint32, driverAddress uint8) error {
|
||||
// Assert the chip select pin (set CS low to start communication)
|
||||
csPin, exists := comm.CsPins[driverAddress]
|
||||
if !exists {
|
||||
return CustomError("Invalid driver address")
|
||||
}
|
||||
csPin.Low()
|
||||
|
||||
// Set the register address with WRITE_ACCESS (0x80)
|
||||
addressWithWriteAccess := register | 0x80
|
||||
|
||||
// Send the address and the data to write (split into 4 bytes)
|
||||
_, err := spiTransfer40(comm.spi, addressWithWriteAccess, value)
|
||||
if err != nil {
|
||||
csPin.High()
|
||||
return CustomError("Failed to write register")
|
||||
}
|
||||
|
||||
// Deassert the chip select pin (set CS high to end communication)
|
||||
csPin.High()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadRegister sends a register read command to the TMC5160.
|
||||
func (comm *SPIComm) ReadRegister(register uint8, driverAddress uint8) (uint32, error) {
|
||||
// Assert the chip select pin (set CS low to start communication)
|
||||
csPin, exists := comm.CsPins[driverAddress]
|
||||
if !exists {
|
||||
return 0, CustomError("Invalid driver address")
|
||||
}
|
||||
csPin.Low()
|
||||
|
||||
// Step 1: Send a dummy write operation to begin the read sequence
|
||||
_, err := spiTransfer40(comm.spi, register, 0x00) // Send dummy data
|
||||
if err != nil {
|
||||
csPin.High()
|
||||
return 0, CustomError("Failed to send dummy write")
|
||||
}
|
||||
csPin.High()
|
||||
time.Sleep(176 * time.Nanosecond)
|
||||
csPin.Low()
|
||||
// Step 2: Send the register read request again to get the actual value
|
||||
response, err := spiTransfer40(comm.spi, register, 0x00) // Send again to get actual register data
|
||||
if err != nil {
|
||||
csPin.High()
|
||||
return 0, CustomError("Failed to read register")
|
||||
}
|
||||
|
||||
// Deassert the chip select pin (set CS high to end communication)
|
||||
csPin.High()
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func spiTransfer40(spi *machine.SPI, register uint8, txData uint32) (uint32, error) {
|
||||
// Prepare the 5-byte buffer for transmission (1 byte address + 4 bytes data)
|
||||
tx := []byte{
|
||||
register, // Address byte
|
||||
byte(txData >> 24), // Upper 8 bits of data
|
||||
byte(txData >> 16), // Middle 8 bits of data
|
||||
byte(txData >> 8), // Next 8 bits of data
|
||||
byte(txData), // Lower 8 bits of data
|
||||
}
|
||||
|
||||
rx := make([]byte, 5)
|
||||
|
||||
// Perform the SPI transaction
|
||||
err := spi.Tx(tx, rx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Combine the received bytes into a 32-bit response, ignore the address byte
|
||||
rxData := uint32(rx[1])<<24 | uint32(rx[2])<<16 | uint32(rx[3])<<8 | uint32(rx[4])
|
||||
|
||||
return rxData, nil
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package tmc5160
|
||||
|
||||
const maxVMAX = 8388096
|
||||
|
||||
// PowerStageParameters represents the power stage parameters
|
||||
type PowerStageParameters struct {
|
||||
drvStrength uint8
|
||||
bbmTime uint8
|
||||
bbmClks uint8
|
||||
}
|
||||
|
||||
// MotorParameters represents the motor parameters
|
||||
type MotorParameters struct {
|
||||
globalScaler uint16
|
||||
ihold uint8
|
||||
irun uint8
|
||||
iholddelay uint8
|
||||
pwmGradInitial uint16
|
||||
pwmOfsInitial uint16
|
||||
freewheeling uint8
|
||||
}
|
||||
|
||||
// MotorDirection defines motor direction constants
|
||||
type MotorDirection uint8
|
||||
|
||||
const (
|
||||
Clockwise MotorDirection = iota
|
||||
CounterClockwise
|
||||
)
|
||||
|
||||
const (
|
||||
// Common stepper motor angles
|
||||
StepAngle_1_8 = 1.8
|
||||
StepAngle_0_9 = 0.9
|
||||
StepAngle_0_72 = 0.72
|
||||
StepAngle_1_2 = 1.2
|
||||
StepAngle_0_48 = 0.48
|
||||
|
||||
// Common microstepping options
|
||||
Step_1 uint8 = 1
|
||||
Step_2 uint8 = 2
|
||||
Step_4 uint8 = 4
|
||||
Step_8 uint8 = 8
|
||||
Step_16 uint8 = 16
|
||||
Step_32 uint8 = 32
|
||||
Step_64 uint8 = 64
|
||||
Step_128 uint8 = 128
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultAngle float32 = StepAngle_1_8
|
||||
DefaultGearRatio float32 = 1.0
|
||||
DefaultVSupply float32 = 12.0
|
||||
DefaultRCoil float32 = 1.2
|
||||
DefaultLCoil float32 = 0.005
|
||||
DefaultIPeak float32 = 2.0
|
||||
DefaultRSense float32 = 0.1
|
||||
DefaultFclk uint8 = 12
|
||||
DefaultStep_256 = 256
|
||||
)
|
||||
|
||||
type Stepper struct {
|
||||
Angle float32
|
||||
GearRatio float32
|
||||
VelocitySPS float32 // Velocity in Steps per sec
|
||||
VSupply float32
|
||||
RCoil float32
|
||||
LCoil float32
|
||||
IPeak float32
|
||||
RSense float32
|
||||
MSteps uint8
|
||||
Fclk uint8 //Clock in Mhz
|
||||
|
||||
}
|
||||
|
||||
// NewStepper function initializes a Stepper with default values used for testing
|
||||
func NewDefaultStepper() Stepper {
|
||||
return Stepper{
|
||||
Angle: StepAngle_1_8, // Default to 1.8 degrees
|
||||
GearRatio: 1.0, // Default to no reduction (1:1)
|
||||
VSupply: 12.0, // Default 12V supply
|
||||
RCoil: 1.2, // Default coil resistance (1.2 ohms)
|
||||
LCoil: 0.005, // Default coil inductance (5 mH)
|
||||
IPeak: 2.0, // Default peak current (2A)
|
||||
RSense: 0.1, // Default sense resistance (0.1 ohms)
|
||||
MSteps: Step_16, // Default 16 Microsteps
|
||||
Fclk: DefaultFclk,
|
||||
}
|
||||
}
|
||||
|
||||
// NewStepper initializes a Stepper with user-defined values
|
||||
func NewStepper(angle float32, gearRatio, vSupply, rCoil, lCoil, iPeak, rSense float32, mSteps uint8, fclk uint8) Stepper {
|
||||
return Stepper{
|
||||
Angle: angle, // User-defined stepper angle (e.g., StepAngle_1_8)
|
||||
GearRatio: gearRatio, // User-defined gear ratio
|
||||
VSupply: vSupply, // User-defined supply voltage
|
||||
RCoil: rCoil, // User-defined coil resistance
|
||||
LCoil: lCoil, // User-defined coil inductance
|
||||
IPeak: iPeak, // User-defined peak current
|
||||
RSense: rSense, // User-defined sense resistance
|
||||
MSteps: mSteps, // User-defined microstepping setting
|
||||
Fclk: fclk, // User-defined clock frequency in MHz
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
//go:build tinygo
|
||||
|
||||
package tmc5160
|
||||
|
||||
import (
|
||||
"github.com/orsinium-labs/tinymath"
|
||||
"machine"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
comm RegisterComm
|
||||
address uint8
|
||||
enablePin machine.Pin
|
||||
stepper Stepper
|
||||
}
|
||||
|
||||
func NewDriver(comm RegisterComm, address uint8, enablePin machine.Pin, stepper Stepper) *Driver {
|
||||
return &Driver{
|
||||
comm: comm,
|
||||
address: address,
|
||||
enablePin: enablePin,
|
||||
stepper: stepper,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteRegister sends a register write command to the Driver.
|
||||
func (driver *Driver) WriteRegister(reg uint8, value uint32) error {
|
||||
if driver.comm == nil {
|
||||
return CustomError("communication interface not set")
|
||||
}
|
||||
// Use the communication interface (RegisterComm) to write the register
|
||||
return driver.comm.WriteRegister(reg, value, driver.address)
|
||||
}
|
||||
|
||||
// ReadRegister sends a register read command to the Driver and returns the read value.
|
||||
func (driver *Driver) ReadRegister(reg uint8) (uint32, error) {
|
||||
if driver.comm == nil {
|
||||
return 0, CustomError("communication interface not set")
|
||||
}
|
||||
// Use the communication interface (RegisterComm) to read the register
|
||||
return driver.comm.ReadRegister(reg, driver.address)
|
||||
}
|
||||
|
||||
// Begin initializes the Driver driver with power and motor parameters
|
||||
func (driver *Driver) Begin(powerParams PowerStageParameters, motorParams MotorParameters, stepperDirection MotorDirection) bool {
|
||||
// Clear the reset and charge pump undervoltage flags
|
||||
gstat := NewGSTAT()
|
||||
gstat.Reset = true
|
||||
gstat.UvCp = true
|
||||
err := driver.WriteRegister(GSTAT, gstat.Pack())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Configure driver settings
|
||||
drvConf := NewDRV_CONF()
|
||||
drvConf.DrvStrength = constrain(powerParams.drvStrength, 0, 3)
|
||||
drvConf.BBMTime = constrain(powerParams.bbmTime, 0, 24)
|
||||
drvConf.BBMClks = constrain(powerParams.bbmClks, 0, 15)
|
||||
err = driver.WriteRegister(DRV_CONF, drvConf.Pack())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Set global scaler
|
||||
err = driver.WriteRegister(GLOBAL_SCALER, uint32(constrain(motorParams.globalScaler, 32, 256)))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Set initial currents and delay
|
||||
iholdrun := NewIHOLD_IRUN()
|
||||
iholdrun.Ihold = constrain(motorParams.ihold, 0, 31)
|
||||
iholdrun.Ihold = constrain(motorParams.irun, 0, 31)
|
||||
iholdrun.IholdDelay = 7
|
||||
err = driver.WriteRegister(IHOLD_IRUN, iholdrun.Pack())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Set PWM configuration values
|
||||
pwmconf := NewPWMCONF()
|
||||
err = driver.WriteRegister(PWMCONF, 0xC40C001E)
|
||||
if err != nil {
|
||||
return false
|
||||
} // Reset default value pwm_ofs = 196,pwm_grad = 12,pwm_freq = 0, pwm_autoscale = false, pwm_autograd = false,freewheel = 3
|
||||
pwmconf.PwmAutoscale = false // Temporarily set to false for setting OFS and GRAD values
|
||||
_fclk := int(driver.stepper.Fclk) * 1000000
|
||||
if _fclk > DEFAULT_F_CLK {
|
||||
pwmconf.PwmFreq = 0
|
||||
} else {
|
||||
pwmconf.PwmFreq = 0b01 // Recommended: 35kHz with internal 12MHz clock
|
||||
}
|
||||
pwmconf.PwmGrad = uint8(motorParams.pwmGradInitial)
|
||||
pwmconf.PwmOfs = uint8(motorParams.pwmOfsInitial)
|
||||
pwmconf.Freewheel = motorParams.freewheeling
|
||||
err = driver.WriteRegister(PWMCONF, pwmconf.Pack())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Enable PWM auto-scaling and gradient adjustment
|
||||
pwmconf.PwmAutoscale = true
|
||||
pwmconf.PwmAutograd = true
|
||||
err = driver.WriteRegister(PWMCONF, pwmconf.Pack())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Recommended chop configuration settings
|
||||
_chopConf := NewCHOPCONF()
|
||||
_chopConf.Toff = 5
|
||||
_chopConf.Tbl = 2
|
||||
_chopConf.HstrtTfd = 4
|
||||
_chopConf.HendOffset = 0
|
||||
err = driver.WriteRegister(CHOPCONF, _chopConf.Pack())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
rampMode := NewRAMPMODE(driver.comm, driver.address)
|
||||
rampMode.SetMode(PositioningMode)
|
||||
gconf := NewGCONF()
|
||||
gconf.EnPwmMode = true // Enable stealthChop PWM mode
|
||||
gconf.Shaft = stepperDirection == Clockwise
|
||||
err = driver.WriteRegister(GCONF, gconf.Pack())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Set default start, stop, threshold speeds
|
||||
driver.setRampSpeeds(0.0, 0.1, 0.0) // Start, stop, threshold speeds
|
||||
|
||||
// Set default D1 (must not be = 0 in positioning mode even with V1=0)
|
||||
err = driver.WriteRegister(D_1, 100)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
func (driver *Driver) setRampSpeeds(startSpeed float32, stopSpeed float32, transitionSpeed float32) {
|
||||
str := driver.stepper.DesiredSpeedToTSTEP(uint32(startSpeed))
|
||||
stp := driver.stepper.DesiredSpeedToTSTEP(uint32(stopSpeed))
|
||||
ts := driver.stepper.DesiredSpeedToTSTEP(uint32(transitionSpeed))
|
||||
driver.WriteRegister(VSTART, uint32(tinymath.Min(0x3FFFF, float32(str))))
|
||||
driver.WriteRegister(VSTOP, uint32(tinymath.Min(0x3FFFF, float32(stp))))
|
||||
driver.WriteRegister(V_1, uint32(tinymath.Min(0xFFFFF, float32(ts))))
|
||||
println("Ramp set to: startSpeed:", startSpeed, "stopSpeed:", stopSpeed, "transitionSpeed:", transitionSpeed)
|
||||
}
|
||||
|
||||
// setMaxSpeed sets the maximum speed to 0 (placeholder function)
|
||||
func setMaxSpeed(speed uint32) {
|
||||
// This is a placeholder function that sets the speed
|
||||
// Implement the actual logic to set the maximum speed register value
|
||||
println("Max Speed set to:", speed)
|
||||
}
|
||||
|
||||
// Dump_TMC reads multiple registers from the Driver and logs their values with their names.
|
||||
func (driver *Driver) Dump_TMC() error {
|
||||
registers := []uint8{
|
||||
GCONF, CHOPCONF, GSTAT, DRV_STATUS, FACTORY_CONF, IOIN, LOST_STEPS, MSCNT,
|
||||
MSCURACT, OTP_READ, PWM_SCALE, PWM_AUTO, TSTEP,
|
||||
}
|
||||
registerNames := map[uint8]string{
|
||||
GCONF: "GCONF",
|
||||
CHOPCONF: "CHOPCONF",
|
||||
GSTAT: "GSTAT",
|
||||
DRV_STATUS: "DRV_STATUS",
|
||||
FACTORY_CONF: "FACTORY_CONF",
|
||||
IOIN: "IOIN",
|
||||
LOST_STEPS: "LOST_STEPS",
|
||||
MSCNT: "MSCNT",
|
||||
MSCURACT: "MSCURACT",
|
||||
OTP_READ: "OTP_READ",
|
||||
PWM_SCALE: "PWM_SCALE",
|
||||
PWM_AUTO: "PWM_AUTO",
|
||||
TSTEP: "TSTEP",
|
||||
}
|
||||
for _, reg := range registers {
|
||||
// Fetch the register name from the map
|
||||
regName, exists := registerNames[reg]
|
||||
if !exists {
|
||||
regName = "Unknown Register"
|
||||
}
|
||||
val, err := driver.ReadRegister(reg)
|
||||
if err != nil {
|
||||
println("Error reading register", regName, err)
|
||||
return err
|
||||
}
|
||||
println("Register", regName, "Value:", val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
//go:build uart
|
||||
|
||||
package tmc5160
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UARTComm implements RegisterComm for UART-based communication with Driver.
|
||||
type UARTComm struct {
|
||||
uart machine.UART
|
||||
address uint8
|
||||
}
|
||||
|
||||
// NewUARTComm creates a new UARTComm instance.
|
||||
func NewUARTComm(uart machine.UART, address uint8) *UARTComm {
|
||||
return &UARTComm{
|
||||
uart: uart,
|
||||
address: address,
|
||||
}
|
||||
}
|
||||
|
||||
// Setup initializes the UART communication with the Driver.
|
||||
func (comm *UARTComm) Setup() error {
|
||||
if comm.uart == (machine.UART{}) {
|
||||
return CustomError("UART not initialized")
|
||||
}
|
||||
err := comm.uart.Configure(machine.UARTConfig{
|
||||
BaudRate: 115200,
|
||||
})
|
||||
if err != nil {
|
||||
return CustomError("Failed to configure UART")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRegister sends a register write command to the Driver.
|
||||
// Prepare the data packet (sync byte + address + register + data + checksum)
|
||||
func (comm *UARTComm) WriteRegister(register uint8, value uint32, driverIndex uint8) error {
|
||||
|
||||
buffer := []byte{
|
||||
0x05, // Sync byte
|
||||
comm.address, // Slave address
|
||||
register | 0x80, // Write command (MSB set to 1 for write)
|
||||
byte((value >> 24) & 0xFF), // MSB of value
|
||||
byte((value >> 16) & 0xFF), // Middle byte
|
||||
byte((value >> 8) & 0xFF), // Next byte
|
||||
byte(value & 0xFF), // LSB of value
|
||||
}
|
||||
checksum := byte(0)
|
||||
for _, b := range buffer[:7] {
|
||||
checksum ^= b
|
||||
}
|
||||
buffer[7] = checksum // Set checksum byte
|
||||
|
||||
// Write the data to the Driver
|
||||
done := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
comm.uart.Write(buffer)
|
||||
done <- nil
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
return err
|
||||
case <-time.After(100 * time.Millisecond): // Timeout after 100ms
|
||||
return CustomError("write timeout")
|
||||
}
|
||||
}
|
||||
|
||||
// ReadRegister sends a register read command to the Driver.
|
||||
func (comm *UARTComm) ReadRegister(register uint8, driverIndex uint8) (uint32, error) {
|
||||
// Prepare the read command (sync byte + address + register + checksum)
|
||||
var writeBuffer [4]byte
|
||||
writeBuffer[0] = 0x05 // Sync byte
|
||||
writeBuffer[1] = comm.address // Slave address
|
||||
writeBuffer[2] = register & 0x7F // Read command (MSB clear for read)
|
||||
writeBuffer[3] = writeBuffer[0] ^ writeBuffer[1] ^ writeBuffer[2] // Checksum
|
||||
done := make(chan []byte, 1)
|
||||
go func() {
|
||||
comm.uart.Write(writeBuffer[:])
|
||||
readBuffer := make([]byte, 8) // Prepare the buffer to read 8 bytes
|
||||
comm.uart.Read(readBuffer)
|
||||
done <- readBuffer
|
||||
}()
|
||||
select {
|
||||
case readBuffer := <-done:
|
||||
checksum := byte(0)
|
||||
for i := 0; i < 7; i++ {
|
||||
checksum ^= readBuffer[i]
|
||||
}
|
||||
if checksum != readBuffer[7] {
|
||||
return 0, CustomError("checksum error")
|
||||
}
|
||||
return uint32(readBuffer[3])<<24 | uint32(readBuffer[4])<<16 | uint32(readBuffer[5])<<8 | uint32(readBuffer[6]), nil
|
||||
case <-time.After(100 * time.Millisecond): // Timeout after 100ms
|
||||
return 0, CustomError("read timeout")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package tmc5160
|
||||
|
||||
func ToHex(value uint32) string {
|
||||
hexChars := "0123456789ABCDEF"
|
||||
result := ""
|
||||
|
||||
for i := 0; i < 8; i++ { // 8 nibbles for a 32-bit number
|
||||
nibble := (value >> (28 - i*4)) & 0xF
|
||||
result += string(hexChars[nibble])
|
||||
}
|
||||
|
||||
return "0x" + result
|
||||
}
|
||||
@@ -478,3 +478,31 @@ func (d *Device) SetLUT(speed Speed, flickerFree bool) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a given coordinates with a color
|
||||
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||
dw, dh := d.Size()
|
||||
|
||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||
x >= d.width || (x+width) > dw || y >= dh || (y+height) > dh {
|
||||
return errOutOfRange
|
||||
}
|
||||
|
||||
if x+width == dw && y+height == dh && c.R == 0 && c.G == 0 && c.B == 0 {
|
||||
d.ClearDisplay()
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := x; i < x+width; i++ {
|
||||
for j := y; j < y+height; j++ {
|
||||
d.SetPixel(i, j, c)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetScroll sets the vertical scrolling for the display, which is a NOP for this display.
|
||||
func (d *Device) SetScroll(line int16) {
|
||||
return
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user