Compare commits

..

53 Commits

Author SHA1 Message Date
deadprogram 019bbbe5fb Prepare for release 0.29
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-30 17:02:40 +00:00
deadprogram d04e68bef8 bugfix: correct casting of return value from adc as it was always overflowing the 8 bit result
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-29 07:28:29 +01:00
Ayke van Laethem 76a4276b5d pixels: do more extensive testing for roundtrip bugs between Set and Get 2024-10-28 09:07:15 +00:00
Ayke van Laethem 7d7efe25e7 pixel: fix Monochrome setPixel
Set/setPixel and Get weren't using the same indices. I've taken the ones
used in Get and applied them to setPixel too.
This fixes testImageNoise for the monochrome image.
2024-10-28 09:07:15 +00:00
deadprogram 0186d0905d pixel: remove monochrome image from random noise test
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-28 05:58:53 +00:00
deadprogram 2f3b5ca59a pixel: correct and clarify code for monochrome get/set pixels
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-28 05:58:53 +00:00
deadprogram ecae5e28ad pixel: add NewImageFromBytes() function to allow creating image from existing slice
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-28 05:58:53 +00:00
Daniel Esteban bcf3b84654 Revert "pixel: add NewImageFromBytes function (#713)" (#714)
This reverts commit 4fb5d7a0e5.
2024-10-28 01:11:52 +01:00
Ron Evans 4fb5d7a0e5 pixel: add NewImageFromBytes function (#713)
* pixel: add NewImageFromBytes() function to allow creating image from existing slice

Signed-off-by: deadprogram <ron@hybridgroup.com>

* pixel: correct and clarify code for monochrome get/set pixels

Signed-off-by: deadprogram <ron@hybridgroup.com>

---------

Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-28 00:59:39 +01:00
deadprogram f12454d4f7 uc8151: add FillRectangle() and SetScroll() functions to satisfy tinyterm.Displayer interface
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-27 23:23:31 +00:00
deadprogram 829ae09651 ssd1306: add FillRectangle() and SetScroll() functions to satisfy tinyterm.Displayer interface
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-28 00:09:01 +01:00
Ayke van Laethem 30f540c29f touch: add capacitive touch sensing on normal GPIO pins
Tested on the following chips/boards:

  * RP2040 (Raspberry Pi Pico)
  * ATSAMD21 (Adafruit PyBadge)
  * NRF52840 (PCA10056 developer board)
  * ESP8266 (NodeMCU)
  * ATmega328p (Arduino Uno)
  * ESP32C3 (WaveShare ESP-C3-32S-Kit)
  * FE310 (SiFive HiFive1 rev B)

The sensitivity threshold in the example may need to be adjusted per
board though, the default value of 100 typically recognizes when a cable
is being touched but the RP2040 for example is capable of doing much
more precise measurements if the power supply is sufficiently
noise-free.
2024-10-25 06:28:03 +01:00
Warren Guy 6d431e0726 Add I2C INA219 driver (#705)
INA219: add INA219 driver
2024-10-24 07:45:33 +02:00
Daniel Esteban f308f8fce0 Add sponsor button to key repositories 2024-10-23 16:22:42 +01:00
PWND0U 1c2b802f47 servo: Add function SetAngleWithMicroseconds (#695)
servo: Add function `SetAngleWithMicroseconds`,Adjust the angle by customizing the control pulse width
2024-10-23 15:56:41 +02:00
Marcin Białoń ce80e7582f epd1in54: add Waveshare 1.54inch B/W e-Paper display (#704)
epd1in54: add Waveshare 1.54inch B/W e-Paper display
2024-10-23 13:43:30 +02:00
HattoriHanzo031 109cab4f3b onewire improvements 2024-10-23 12:32:32 +01:00
Greg Herlein 07216d3051 expanded README to discuss need to change variables in examples 2024-08-25 20:31:26 +02:00
たぬき d688fa3f3f ssd1306: Add function SetFlip and GetFlip (#702)
* ssd1306: Add SetRotation function
2024-08-19 01:45:59 +02:00
Greg Herlein dd44f9220b removed what seems like an accidental home directory replace in go.mod 2024-08-14 20:00:38 +02:00
Ron Evans ee3842f639 pcf8591: add ADC only implementation for I2C ADC/DAC (#690)
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-07-01 22:06:25 +09:00
deadprogram 1bf1a11067 all: prepare release v0.28.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-06-18 11:38:02 +02:00
leongross 848f5def03 nit: increase verbosity of espat NetNotify error (#684)
espat: increase verbosity of espat NetNotify error
2024-06-14 17:36:07 +02:00
Thomas 4a0bcba87c epd2in66b: Waveshare 2.66inch E-Paper Display Module (B) for Raspberry Pi Pico (#673)
epd2in66b: add working driver

Co-authored-by: Thomas Richner <thomas.richner@oviva.com>
2024-06-10 13:16:45 +02:00
Ayke van Laethem 7d983647ad pixel: fix Image[Monochrome].Set for larger images
For bigger images, the pixel index might not fit in a int16. Therefore,
int is needed during the calculation.

While fixing this bug, I've added a few tests that verify the Image
implementation by creating images, filling them with random data, and
then checking whether they still contain the same data. This test failed
before the patch.
2024-05-25 17:09:48 +02:00
deadprogram 831982ad33 servo: add function SetAngle() to simplify API for most common use case
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-05-25 08:34:52 +02:00
deadprogram 9063f46313 uc8151: improvements to speed and also add flicker-free mode based on @antirez code example
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-05-13 22:45:37 +02:00
gmsec 2a621dc3b1 Add driver for MCP9808 i2c temperature sensor (#676)
mcp9808: add support for temperature sensor
2024-05-12 10:42:03 +02:00
deadprogram 7dbca2a543 uc8151: correct DrawBitmap() also refactor SendCommand() and SendData() for clarity
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-05-09 15:37:57 +02:00
deadprogram 50b6f18cc2 uc8151: update to support all functions needed by tinygl and board package Displayer interface
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-05-08 16:35:21 +02:00
sago35 bb0e6b8ba8 Fix typo and move initialization of neo to init() 2024-05-06 11:19:19 +02:00
sago35 5eeba138a4 Simplify examples/ws2812 2024-05-06 11:19:19 +02:00
deadprogram 1095629f62 ssd1306: add Sleep() function for Displayer interface
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-24 13:00:20 +02:00
deadprogram bf0b383176 ssd1306: add rotation functions for Displayer interface
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-24 13:00:20 +02:00
Elias Naur f206f6f56f ft6336: ignore bogus touch events
At least one ft6336 device reports all 255 values from the first read
after reset or poweron, even after waiting the specified 300ms reset
delay.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-04-21 12:00:03 +02:00
deadprogram a0293643b3 ssd1306: add DrawBitmap() function to complete Displayer interface
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-21 11:52:21 +02:00
deadprogram 60801ba852 pixel: add support for Monochrome types such as the SSD1306 display
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-13 15:50:30 +02:00
deadprogram a74770b2e6 rtl8720dn: implement ConnectModeAP
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-01 07:40:46 +02:00
deadprogram 3ed09d3fe1 wifinina: implement ConnectModeAP
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-01 07:40:46 +02:00
sago35 22581dfd58 encoders: add atsamd21, atsamd51, atsame5x 2024-02-29 15:29:41 +01:00
deadprogram 31f09d93b8 all: update for relase v0.27
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-02-26 19:55:23 +01:00
deadprogram 0ea969f25a docs: update LICENSE year
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-02-26 19:55:23 +01:00
Niccolò Maggioni 8665d44e0f Use int16 for ADXL345 readings (#656)
ADXL345: fix: use int16 for ADXL345 readings
2024-02-24 19:16:06 +01:00
deadprogram c344e5d879 ssd1306: improvements needed for Thumby SPI display
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-02-23 20:07:40 -05:00
BCG 308763f500 Adding driver for rotary encoder support 2024-02-15 16:55:54 +01:00
deadprogram c41f2e472d rtl8720dn, wifinina: use drivers package version as the driver version
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-01-27 16:43:48 +01:00
Scott Feldman aa7bd47c68 examples: add classic network snake test example
Adding this example to test multiple concurrent socket connections.
2024-01-24 08:25:20 +01:00
Scott Feldman e7931c6a22 wifinina: fix concurrency issues with multiple sockets
wifinina driver was not handling concurrent socket connections
correctly.  When trying to make multiple socket connections, the sockfd
returned by the first Socket() call would be the same sockfd returned by
subsequent Socket() calls.  The problem is the sockfd returned by
Socket() isn't used until Connect() (or Appect()).  This would result in
Connect() trying to use the same sockfd for multiple connections,
causing wifinina fw to lock up.

The solution in this PR is to create a new sockfd space managed by the
driver that gives the app a unique, safe sockfd for each connection.
The real underlying sock fd returned by fw is set on Connect() (or
Accept()), and mapped back to the apps sockfd using a map:

    sockets map[int]*Socket // keyed by sockfd

Where Socket has a reference to the fw sock:

    type Socket struct {
            protocol        int
            clientConnected bool
            laddr           netip.AddrPort // Set in Bind()
            raddr           netip.AddrPort // Set in Connect()
            sock                           // Device socket, as returned from w.getSocket()
    }
2024-01-22 13:32:57 -08:00
deadprogram 3c5e17423a netlink, examples: use 'ninafw' tag instead of individual board tags to simplify maintenence. Also see PR #4085 in the main TinyGo repo
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-01-17 08:47:00 +01:00
deadprogram 0262122ccd rtl8720dn: allow connecting to open wifi access points
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-01-12 10:45:53 +01:00
Scott Feldman 7d24c3c285 wioterminal: fix check for bad Wifi connect
rpc_wifi_connect() will return 0 on successful connection, so check for
that.  Also, check that if passphrase is given, that it's the minimum 8
chars per WPA Wifi.
2024-01-11 12:43:50 +01:00
Scott Feldman 5c0f0480bc fix wifinina UDP send
Fix an error I introduced in porting wifinina to netdev.  The driver was
starting a client on the socket once, during Connect.  The first UDP
send on the socket would succeed, any subsequent sends would fail.  The
fix is to start the client on the socket for each UDP send.

I think I see the logic in this design, so the fix makes sense.  If the
device was sending to many UDP clients, it could use a single socket,
but change the dst addr for each send.  The pkt data would be queued to
hw just once, and then sent from hw to each client dst addr.  This would
be a real efficient way to multicast to many clients.
2024-01-10 17:40:25 +01:00
Scott Feldman 8642886f73 correct netdever Accept() prototype
According to man page accept(2), accept returns new client sockfd and
remote peer ip:port.  This patch corrects the Accept() prototype in the
netdever interface to not take in an ip:port arg, but rather return an
ip:port for remote peer.

Tested with examples/net/tcpecho on wioterminal and nano-rp2040.  Here's
a run with wioterminal:

SERVER
============
sfeldma@nuc:~/work/drivers$ tinygo flash -monitor -target wioterminal -size short -stack-size=8kb ./examples/net/tcpecho
code    data     bss |   flash     ram
110876    2552   11212 |  113428   13764
Connected to /dev/ttyACM2. Press Ctrl-C to exit.

Realtek rtl8720dn Wifi network device driver (rtl8720dn)

Driver version           : 0.0.1
RTL8720 firmware version : 2.1.2
MAC address              : 2c:f7:f1:1c:9b:2f

Connecting to Wifi SSID 'test'...CONNECTED

DHCP-assigned IP         : 10.0.0.140
DHCP-assigned subnet     : 255.255.255.0
DHCP-assigned gateway    : 10.0.0.1

Starting TCP server listening on :8080
Client 10.0.0.190:50000 connected
Client 10.0.0.190:50000 closed

CLIENT
=============
nc -p 50000 10.0.0.140 8080
2023-12-19 09:33:54 +01:00
80 changed files with 4153 additions and 918 deletions
+2
View File
@@ -0,0 +1,2 @@
# These are supported funding model platforms
open_collective: tinygo
+162
View File
@@ -1,3 +1,165 @@
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**
- **epd2in66b**
- Waveshare 2.66inch E-Paper Display Module (B) for Raspberry Pi Pico (#673)
- **mcp9808**
- Add driver for MCP9808 i2c temperature sensor (#676)
- **enhancements**
- **encoders**
- add atsamd21, atsamd51, atsame5x
- **pixel**
- add support for Monochrome types such as the SSD1306 display
- **rtl8720dn**
- implement ConnectModeAP
- **servo**
- add function SetAngle() to simplify API for most common use case
- **ssd1306**
- add DrawBitmap() function to complete Displayer interface
- add rotation functions for Displayer interface
- add Sleep() function for Displayer interface
- **uc8151**
- improvements to speed and also add flicker-free mode based on @antirez code example
- update to support all functions needed by tinygl and board package Displayer interface
- **wifinina**
- implement ConnectModeAP
- **bugfixes**
- **ft6336**
- ignore bogus touch events
- **pixel**
- fix Image[Monochrome].Set for larger images
- **uc8151**
- correct DrawBitmap() also refactor SendCommand() and SendData() for clarity
- **ws2812**
- Fix typo and move initialization of neo to init()
- **examples**
- **ws2812**
- Simplify examples/ws2812
0.27.0
---
- **core**
- prepare for CGo changes in TinyGo
- **new devices**
- **adafruit4650**
- support for Adafruit 4650 feather OLED
- **net**
- new networking support based on tinygo net package
- **pixel**
- add package for efficiently working with raw pixel buffers
- **rotary**
- Adding driver for rotary encoder support
- **seesaw**
- Adding support for Adafruit Seesaw platform
- **sgp30**
- add SGP30 air quality sensor
- **sk6812**
- added support for SK6812 to WS2812 device (#610)
- **enhancements**
- **epd2in13**
- add Sleep method like other displays
- unify rotation configuration with other displays
- use better black/white approximation
- **ili9341**
- add DrawBitmap method
- **lora/lorawan**
- LoRa WAN US915 Support
- LoRa WAN add setter functions
- refactor shared functionality for channels/regions
- **mcp2515**
- Add more line speeds to mcp2515.go (#626)
- **rtl8720dn**
- use drivers package version as the driver version
- **ssd1306**
- improvements needed for Thumby SPI display
- **st7735**
- make the display generic over RGB565 and RGB444
- **st7789**
- add DrawBitmap method
- make the display generic over RGB565 and RGB444
- **wifinina**
- add ResetIsHigh cfg switch for MKR 1010 (copied from #561)
- maintenence. Also see PR #4085 in the main TinyGo repo
- use drivers package version as the driver version
- **bugfixes**
- **adxl345**
- Use int16 for ADXL345 readings (#656)
- **at24cx**
- fixed the description of the device struct
- **rtl8720dn**
- allow connecting to open wifi access points
- fix check for bad Wifi connect
- **sh1106**
- fix I2C interface and add smoketest
- fixed the description of the device struct
- **wifinina**
- add 'unknown failure' reason code for AP connect
- fix concurrency issues with multiple sockets
- fix wifinina UDP send
- **examples**
- **ds3231**
- fix the description in the example
- **lorawan**
- add missing functions for simulated interface
- modify atcmd and basic demo to support choosing any one of the supported regions at compile time by using ldflags
- **net**
- all networking examples now using netdev and netlink.
- **build**
- **all**
- fix broken testrunner
- migrated legacy I2C
- add natiu package for tests
- **smoketest**
- add stack-size param for net tests.
- allow stack-size flag as it is needed for net examples
0.26.0
---
- **core**
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved.
Copyright (c) 2018-2024 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
+1 -1
View File
@@ -19,7 +19,7 @@ rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst
# Recursively find all *_test.go files from cwd & reduce to unique dir names
HAS_TESTS = $(sort $(dir $(call rwildcard,,*_test.go)))
# Exclude anything we explicitly don't want to test for whatever reason
EXCLUDE_TESTS = image
EXCLUDE_TESTS = image waveshare-epd/epd2in66b
TESTS = $(filter-out $(addsuffix /%,$(EXCLUDE_TESTS)),$(HAS_TESTS))
unit-test:
+24 -2
View File
@@ -3,7 +3,7 @@
[![PkgGoDev](https://pkg.go.dev/badge/tinygo.org/x/drivers)](https://pkg.go.dev/tinygo.org/x/drivers) [![Build](https://github.com/tinygo-org/drivers/actions/workflows/build.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/drivers/actions/workflows/build.yml)
This package provides a collection of 101 different hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
This package provides a collection of over 100 different hardware drivers for devices such as sensors, displays, wireless adaptors, and actuators, that can be used together with [TinyGo](https://tinygo.org).
For the complete list, please see:
https://tinygo.org/docs/reference/devices/
@@ -16,7 +16,7 @@ go get tinygo.org/x/drivers
## How to use
Here is an example in TinyGo that uses the BMP180 digital barometer:
Here is an example in TinyGo that uses the BMP180 digital barometer. This example should work on any board that supports I2C:
```go
package main
@@ -53,6 +53,28 @@ func main() {
}
```
## Examples Using GPIO or SPI
If compiling these examples directly you are likely to need to make minor changes to the defined variables to map the pins for the board you are using. For example, this block in main.go:
```golang
var (
spi = machine.SPI0
csPin = machine.D5
)
```
It might not be obvious, but you need to change these to match how you wired your specific board. Constants are [defined for each supported microcontroller](https://tinygo.org/docs/reference/microcontrollers/).
For example, to change the definitions for use on a Raspberry Pi Pico using typical wiring, you might need to do this:
```golang
var (
spi = machine.SPI0
csPin = machine.GP17
)
```
## Contributing
Your contributions are welcome!
+7 -7
View File
@@ -95,16 +95,16 @@ func (d *Device) Restart() {
func (d *Device) ReadAcceleration() (x int32, y int32, z int32, err error) {
rx, ry, rz := d.ReadRawAcceleration()
x = d.dataFormat.convertToIS(rx)
y = d.dataFormat.convertToIS(ry)
z = d.dataFormat.convertToIS(rz)
x = int32(d.dataFormat.convertToIS(rx))
y = int32(d.dataFormat.convertToIS(ry))
z = int32(d.dataFormat.convertToIS(rz))
return
}
// ReadRawAcceleration reads the sensor values and returns the raw x, y and z axis
// from the adxl345.
func (d *Device) ReadRawAcceleration() (x int32, y int32, z int32) {
func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
data := []byte{0, 0, 0, 0, 0, 0}
legacy.ReadRegister(d.bus, uint8(d.Address), REG_DATAX0, data)
@@ -140,7 +140,7 @@ func (d *Device) SetRange(sensorRange Range) bool {
}
// convertToIS adjusts the raw values from the adxl345 with the range configuration
func (d *dataFormat) convertToIS(rawValue int32) int32 {
func (d *dataFormat) convertToIS(rawValue int16) int16 {
switch d.sensorRange {
case RANGE_2G:
return rawValue * 4 // rawValue * 2 * 1000 / 512
@@ -190,6 +190,6 @@ func (b *bwRate) toByte() (bits uint8) {
}
// readInt converts two bytes to int16
func readIntLE(msb byte, lsb byte) int32 {
return int32(uint16(msb) | uint16(lsb)<<8)
func readIntLE(msb byte, lsb byte) int16 {
return int16(uint16(msb) | uint16(lsb)<<8)
}
+2 -2
View File
@@ -19,7 +19,7 @@ type OneWireDevice interface {
Write(uint8)
Read() uint8
Select([]uint8) error
Сrc8([]uint8, int) uint8
Сrc8([]uint8) uint8
}
// Device wraps a connection to an 1-Wire devices.
@@ -69,7 +69,7 @@ func (d Device) ReadTemperatureRaw(romid []uint8) ([]uint8, error) {
for i := 0; i < 9; i++ {
spb[i] = d.owd.Read()
}
if d.owd.Сrc8(spb, 8) != spb[8] {
if d.owd.Сrc8(spb) != 0 {
return nil, errReadTemperature
}
return spb[:2:2], nil
+34
View File
@@ -0,0 +1,34 @@
package encoders
type QuadratureDevice struct {
cfg QuadratureConfig
impl quadratureImpl
}
type QuadratureConfig struct {
Precision int
}
type quadratureImpl interface {
configure(cfg QuadratureConfig) error
readValue() int
writeValue(int)
}
func (enc *QuadratureDevice) Configure(cfg QuadratureConfig) error {
if cfg.Precision < 1 {
cfg.Precision = 4
}
enc.cfg = cfg
return enc.impl.configure(cfg)
}
// Position returns the stored int value for the encoder
func (enc *QuadratureDevice) Position() int {
return enc.impl.readValue() / enc.cfg.Precision
}
// SetPosition overwrites the currently stored value with the specified int value
func (enc *QuadratureDevice) SetPosition(v int) {
enc.impl.writeValue(v * enc.cfg.Precision)
}
+69
View File
@@ -0,0 +1,69 @@
//go:build tinygo && (rp2040 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
// Implementation based on:
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
// Note: build constraints in this file list targets that define machine.PinToggle.
// If this is supported for additional targets in the future, they can be added above.
package encoders
import (
"machine"
"runtime/volatile"
)
var (
states = []int8{0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}
)
// NewQuadratureViaInterrupt returns a rotary encoder device that uses GPIO
// interrupts and a lookup table to keep track of quadrature state changes.
//
// This constructur is only available for TinyGo targets for which machine.PinToggle
// is defined as a valid interrupt type.
func NewQuadratureViaInterrupt(pinA, pinB machine.Pin) *QuadratureDevice {
return &QuadratureDevice{impl: &quadInterruptImpl{pinA: pinA, pinB: pinB, oldAB: 0b00000011}}
}
type quadInterruptImpl struct {
pinA machine.Pin
pinB machine.Pin
// precision int
oldAB int
value volatile.Register32
}
func (enc *quadInterruptImpl) configure(cfg QuadratureConfig) error {
enc.pinA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
enc.pinA.SetInterrupt(machine.PinToggle, enc.interrupt)
enc.pinB.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
enc.pinB.SetInterrupt(machine.PinToggle, enc.interrupt)
return nil
}
func (enc *quadInterruptImpl) interrupt(pin machine.Pin) {
aHigh, bHigh := enc.pinA.Get(), enc.pinB.Get()
enc.oldAB <<= 2
if aHigh {
enc.oldAB |= 1 << 1
}
if bHigh {
enc.oldAB |= 1
}
enc.writeValue(enc.readValue() + int(states[enc.oldAB&0x0f]))
}
// readValue gets the value using volatile operations and returns it as an int
func (enc *quadInterruptImpl) readValue() int {
return int(enc.value.Get())
}
// writeValue set the value to the specified int using volatile operations
func (enc *quadInterruptImpl) writeValue(v int) {
enc.value.Set(uint32(v))
}
+3 -4
View File
@@ -122,7 +122,7 @@ func (d *Device) NetDisconnect() {
}
func (d *Device) NetNotify(cb func(netlink.Event)) {
// Not supported
fmt.Printf("\r\n%s\r\n", netlink.ErrNotSupported)
}
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
@@ -218,8 +218,8 @@ func (d *Device) Listen(sockfd int, backlog int) error {
return nil
}
func (d *Device) Accept(sockfd int, ip netip.AddrPort) (int, error) {
return -1, netdev.ErrNotSupported
func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error) {
return -1, netip.AddrPort{}, netdev.ErrNotSupported
}
func (d *Device) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
@@ -473,7 +473,6 @@ func (d *Device) parseIPD(end int) error {
}
// load up the socket data
//d.data = append(d.data, d.response[e+1:end]...)
d.data = append(d.data, d.response[e+1:e+1+v]...)
return nil
}
@@ -0,0 +1,28 @@
//go:build macropad_rp2040
package main
import (
"machine"
"tinygo.org/x/drivers/encoders"
)
var (
enc = encoders.NewQuadratureViaInterrupt(machine.ROT_A, machine.ROT_B)
)
func main() {
enc.Configure(encoders.QuadratureConfig{
Precision: 4,
})
for oldValue := 0; ; {
if newValue := enc.Position(); newValue != oldValue {
println("value: ", newValue)
oldValue = newValue
}
}
}
+29
View File
@@ -0,0 +1,29 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/ina219"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
dev := ina219.New(machine.I2C0)
dev.Configure()
for {
busVoltage, shuntVoltage, current, power, err := dev.Measurements()
if err != nil {
println("Error reading measurements", err)
}
println("Bus Voltage:", busVoltage, "V")
println("Shunt Voltage:", shuntVoltage/100, "mV")
println("Current:", current, "mA")
println("Power:", power, "mW")
time.Sleep(10 * time.Millisecond)
}
}
+48
View File
@@ -0,0 +1,48 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/mcp9808"
)
func main() {
//tinygo monitor
time.Sleep(time.Millisecond * 5000)
//Configure I2C (in this case, I2C0 on RPI Pico), and wire the module accordingly
machine.I2C0.Configure(machine.I2CConfig{
SCL: machine.GP1,
SDA: machine.GP0,
})
//Create sensor
sensor := mcp9808.New(machine.I2C0)
if !sensor.Connected() {
println("MCP9808 not found")
return
} else {
println("MCP9808 found")
}
time.Sleep(time.Millisecond * 1000)
//Set resolution
sensor.SetResolution(mcp9808.Maximum)
time.Sleep(time.Millisecond * 1000)
//Read temp.
temp, err := sensor.ReadTemperature()
if err != nil {
println("MCP9808 error reading temperature")
println(err.Error())
return
} else {
fmt.Printf("Temperature: %.2f \n", temp)
}
return
}
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
+1 -1
View File
@@ -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 pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
//go:build ninafw || wioterminal || challenger_rp2040
package main
+1 -1
View File
@@ -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 pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
//go:build ninafw || wioterminal || challenger_rp2040
package main
+1 -1
View File
@@ -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 pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
//go:build ninafw || wioterminal || challenger_rp2040
package main
+30
View File
@@ -0,0 +1,30 @@
//go:build ninafw || wioterminal
package main
import (
"log"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
)
func init() {
time.Sleep(2 * time.Second)
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
}
+120
View File
@@ -0,0 +1,120 @@
// This example is the classic snake network test. The snake is feed a steady
// diet of pkts and the pkts work themselves thru the snake segments and exit
// the tail. Each snake segment is a TCP socket connection to a server. The
// server echos pkts received back to the snake, and serves each segment on a
// different port. (See server/main.go for server).
//
// snake | server
// |
// head ----->---|-->--+
// seg a | |
// +---<-|--<--+
// | |
// +-->--|-->--+
// seg b | |
// +---<-|--<--+
// | |
// +-->--|-->--+
// seg c | |
// +---<-|--<--+
// | |
// +-->--|-->--+
// ... | |
// +---<-|--<--+
// | |
// +-->--|-->--+
// seg n | |
// tail -------<-|--<--+
// |
// The snake segments are linked by channels and each segment is run as a go
// 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
package main
import (
_ "embed"
"fmt"
"log"
"net"
"strings"
"time"
)
//go:embed main.go
var code string
var (
server string = "10.0.0.100:8080"
)
func segment(in chan []byte, out chan []byte) {
var buf [512]byte
for {
c, err := net.Dial("tcp", server)
for ; err != nil; c, err = net.Dial("tcp", server) {
println(err.Error())
time.Sleep(5 * time.Second)
}
for {
select {
case msg := <-in:
_, err := c.Write(msg)
if err != nil {
log.Fatal(err.Error())
}
time.Sleep(100 * time.Millisecond)
n, err := c.Read(buf[:])
if err != nil {
log.Fatal(err.Error())
}
out <- buf[:n]
}
}
}
}
func feedit(head chan []byte) {
for i := 0; i < 100; i++ {
head <- []byte(fmt.Sprintf("\n---%d---\n", i))
for _, line := range strings.Split(code, "\n") {
if len(line) == 0 {
line = " "
}
head <- []byte(line)
}
}
}
var head = make(chan []byte)
var a = make(chan []byte)
var b = make(chan []byte)
var c = make(chan []byte)
var d = make(chan []byte)
var e = make(chan []byte)
var f = make(chan []byte)
var tail = make(chan []byte)
func main() {
// The snake
go segment(head, a)
go segment(a, b)
go segment(b, c)
go segment(c, d)
go segment(d, e)
go segment(e, f)
go segment(f, tail)
go feedit(head)
for {
select {
case msg := <-tail:
println(string(msg))
}
}
}
+34
View File
@@ -0,0 +1,34 @@
package main
import (
"io"
"log"
"net"
)
func main() {
// Listen for connections
l, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err.Error())
}
defer l.Close()
println("Listening on port", ":8080")
for {
// Wait for a connection
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
println("Accepted connection from", conn.RemoteAddr().String())
// Service the new connection in a goroutine.
// The loop then returns to accepting, so that
// multiple connections may be served concurrently
go func(c net.Conn) {
// Echo all incoming data
io.Copy(c, c)
// Shut down the connection
c.Close()
}(conn)
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
//
// nc -lk 8080
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
//go:build ninafw || wioterminal || challenger_rp2040
package main
+1 -1
View File
@@ -5,7 +5,7 @@
//
// nc -lk 8080
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040 || pico
//go:build ninafw || wioterminal || challenger_rp2040 || pico
package main
+5 -2
View File
@@ -7,7 +7,7 @@
//
// $ nc 10.0.0.2 8080 <file >copy ; cmp file copy
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
@@ -30,16 +30,18 @@ var (
var buf [1024]byte
func echo(conn net.Conn) {
println("Client", conn.RemoteAddr(), "connected")
defer conn.Close()
_, err := io.CopyBuffer(conn, conn, buf[:])
if err != nil && err != io.EOF {
log.Fatal(err.Error())
}
println("Client", conn.RemoteAddr(), "closed")
}
func main() {
time.Sleep(time.Second)
time.Sleep(2 * time.Second)
link, _ := probe.Probe()
@@ -51,6 +53,7 @@ func main() {
log.Fatal(err)
}
println("Starting TCP server listening on", port)
l, err := net.Listen("tcp", port)
if err != nil {
log.Fatal(err.Error())
+1 -1
View File
@@ -5,7 +5,7 @@
//
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
+1 -1
View File
@@ -17,7 +17,7 @@
// }
// ---------------------------------------------------------------------------
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
+1 -1
View File
@@ -6,7 +6,7 @@
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
+1 -1
View File
@@ -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 pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
+1 -1
View File
@@ -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 pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
+1 -1
View File
@@ -3,7 +3,7 @@
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
//go:build ninafw || wioterminal
package main
+28
View File
@@ -0,0 +1,28 @@
// Connects to a pcf8591 ADC via I2C.
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/pcf8591"
)
var (
i2c = machine.I2C0
)
func main() {
i2c.Configure(machine.I2CConfig{})
adc := pcf8591.New(i2c)
adc.Configure()
// get "CH0" aka "machine.ADC" interface to channel 0 from ADC.
p := adc.CH0
for {
val := p.Get()
println(val)
time.Sleep(50 * time.Millisecond)
}
}
+19 -13
View File
@@ -25,19 +25,25 @@ func main() {
return
}
println("setting to 0°")
s.SetMicroseconds(1000)
time.Sleep(3 * time.Second)
println("setting to 45°")
s.SetMicroseconds(1500)
time.Sleep(3 * time.Second)
println("setting to 90°")
s.SetMicroseconds(2000)
time.Sleep(3 * time.Second)
for {
time.Sleep(time.Second)
println("setting to 0°")
s.SetAngle(0)
time.Sleep(3 * time.Second)
println("setting to 45°")
s.SetAngle(45)
time.Sleep(3 * time.Second)
println("setting to 90°")
s.SetAngle(90)
time.Sleep(3 * time.Second)
println("setting to 135°")
s.SetAngle(135)
time.Sleep(3 * time.Second)
println("setting to 180°")
s.SetAngle(180)
time.Sleep(3 * time.Second)
}
}
+50
View File
@@ -0,0 +1,50 @@
// 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)
}
}
+69
View File
@@ -0,0 +1,69 @@
// Capacitive touch sensing example.
//
// This capacitive touch sensor works by charging a normal GPIO pin, then slowly
// discharging it through a 1MΩ resistor and seeing how long it takes to go from
// high to low.
//
// Use as follows:
// - Change touchPin below as needed.
// - Connect this pin to some metal surface, like a piece of aluminimum foil.
// Make sure this surface is covered (using paper, Scotch tape, etc).
// - Also connect this same pin to ground through a 1MΩ resistor.
//
// This sensor is very sensitive to noise on the power source, so you should
// probably try to limit it by running from a battery for example. Especially
// phone chargers can produce a lot of noise.
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/touch/capacitive"
)
const touchPin = machine.GP16 // Raspberry Pi Pico
func main() {
time.Sleep(time.Second * 2)
println("start")
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led.Low()
// Configure the array of GPIO pins used for capacitive touch sensing.
// We're using only one pin.
array := capacitive.NewArray([]machine.Pin{touchPin})
// Use a dynamic threshold, meaning the GPIO pin is automatically calibrated
// and re-calibrated to adjust for varying environments (e.g. changing
// humidity).
array.SetDynamicThreshold(100)
wasTouching := false
for i := uint32(0); ; i++ {
// Update the GPIO pin. This must be called very often.
array.Update()
touching := array.Touching(0)
// Indicate whether the pin is touched via the LED.
led.Set(touching)
// Print something when the touch state changed.
if wasTouching != touching {
wasTouching = touching
if touching {
println(" touch!")
} else {
println(" release!")
}
}
// Print the current value, as a debugging aid. It's not really meant to
// be used directly.
if i%128 == 32 {
println("touch value:", array.RawValue(0))
}
}
}
+40 -12
View File
@@ -3,7 +3,9 @@ package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/uc8151"
)
@@ -14,30 +16,56 @@ func main() {
led = machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 12000000,
Frequency: 12 * machine.MHz,
SCK: machine.EPD_SCK_PIN,
SDO: machine.EPD_SDO_PIN,
})
display = uc8151.New(machine.SPI0, machine.EPD_CS_PIN, machine.EPD_DC_PIN, machine.EPD_RESET_PIN, machine.EPD_BUSY_PIN)
display.Configure(uc8151.Config{
Rotation: uc8151.ROTATION_270,
Speed: uc8151.MEDIUM,
Blocking: true,
Rotation: drivers.Rotation270,
Speed: uc8151.TURBO,
FlickerFree: true,
Blocking: false,
})
black := color.RGBA{1, 1, 1, 255}
display.ClearBuffer()
display.Display()
for i := int16(0); i < 37; i++ {
for j := int16(0); j < 16; j++ {
if (i+j)%2 == 0 {
showRect(i*8, j*8, 8, 8, black)
display.ClearDisplay()
mod := int16(1)
for {
// checkerboard
for i := int16(0); i < 11; i++ {
if mod == 1 {
mod = 0
} else {
mod = 1
}
display.ClearBuffer()
for i := int16(0); i < 37; i++ {
for j := int16(0); j < 16; j++ {
if (i+j)%2 == mod {
showRect(i*8, j*8, 8, 8, black)
}
}
}
display.Display()
time.Sleep(500 * time.Millisecond)
}
// moving line
for i := int16(16); i < 21; i++ {
display.ClearBuffer()
for j := int16(0); j < 16; j++ {
if (i+j)%2 == 0 {
showRect(i*8, j*8, 8, 8, black)
}
display.Display()
time.Sleep(250 * time.Millisecond)
}
}
}
display.Display()
}
func showRect(x int16, y int16, w int16, h int16, c color.RGBA) {
+36
View File
@@ -0,0 +1,36 @@
package main
import (
"image/color"
"machine"
"tinygo.org/x/drivers/waveshare-epd/epd1in54"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/gophers"
)
var (
spi0 = machine.SPI0
cs = machine.D10
dc = machine.D9
rst = machine.D6
busy = machine.D5
black = color.RGBA{R: 1, G: 1, B: 1, A: 255}
)
func main() {
display := epd1in54.New(spi0, cs, dc, rst, busy)
display.LDirInit(epd1in54.Config{})
display.Clear()
display.ClearBuffer()
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 150, 0, "A B C", black, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 100, 0, "D E F", black, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 50, 0, "G H I", black, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 0, 0, "J K L", black, tinyfont.ROTATION_90)
display.Display()
display.Sleep()
}
+84
View File
@@ -0,0 +1,84 @@
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/waveshare-epd/epd2in66b"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)
var (
black = color.RGBA{0, 0, 0, 0xff}
white = color.RGBA{0xff, 0xff, 0xff, 0xff}
red = color.RGBA{0xff, 0, 0, 0xff}
)
func main() {
machine.Serial.Configure(machine.UARTConfig{})
time.Sleep(2 * time.Second)
machine.SPI1.Configure(machine.SPIConfig{
Frequency: epd2in66b.Baudrate,
})
println("started")
// in case you have a Pico module, you can directly use
// dev, err := epd2in66b.NewPicoModule()
display := epd2in66b.New(machine.SPI1)
cfg := epd2in66b.Config{
DataPin: machine.GP8,
ChipSelectPin: machine.GP9,
ResetPin: machine.GP12,
BusyPin: machine.GP13,
}
err := display.Configure(cfg)
if err != nil {
panic(err)
}
err = display.Reset()
if err != nil {
panic(err)
}
println("draw checkerboard")
drawCheckerBoard(&display)
println("draw 'hello'")
tinyfont.WriteLineRotated(&display, &freemono.Bold24pt7b, 40, 10, "Hello!", white, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &freemono.Bold12pt7b, 10, 10, "tinygo rocks", white, tinyfont.ROTATION_90)
err = display.Display()
if err != nil {
panic(err)
}
}
func drawCheckerBoard(display drivers.Displayer) {
s := 8
width, height := display.Size()
for x := 0; x <= int(width)-s; x += s {
for y := 0; y <= int(height)-s; y += s {
c := red
if (x/s)%2 == (y/s)%2 {
c = black
}
showRect(display, x, y, s, s, c)
}
}
}
func showRect(display drivers.Displayer, x int, y int, w int, h int, c color.RGBA) {
for i := x; i < x+w; i++ {
for j := y; j < y+h; j++ {
display.SetPixel(int16(i), int16(j), c)
}
}
}
+5 -4
View File
@@ -4,7 +4,8 @@ package main
import "machine"
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo = machine.D2
var led = machine.LED
func init() {
// Replace neo in the code below to match the pin
// that you are using if different.
neo = machine.D2
}
+6 -5
View File
@@ -4,8 +4,9 @@ package main
import "machine"
// This is the pin assignment for the Digispark only.
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = 0
var led = machine.LED
func init() {
// This is the pin assignment for the Digispark only.
// Replace neo and led in the code below to match the pin
// that you are using if different.
neo = machine.Pin(0)
}
+4 -4
View File
@@ -12,11 +12,12 @@ import (
"tinygo.org/x/drivers/ws2812"
)
var leds [10]color.RGBA
var (
neo machine.Pin
leds [10]color.RGBA
)
func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
ws := ws2812.NewWS2812(neo)
@@ -35,7 +36,6 @@ func main() {
}
ws.WriteColors(leds[:])
led.Set(rg)
time.Sleep(100 * time.Millisecond)
}
}
+6 -5
View File
@@ -1,10 +1,11 @@
//go:build !digispark && !arduino && !qtpy && !m5stamp_c3 && !thingplus_rp2040
//go:build !digispark && !arduino
package main
import "machine"
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.WS2812
var led = machine.LED
func init() {
// Replace neo in the code below to match the pin
// that you are using if different.
neo = machine.WS2812
}
-10
View File
@@ -1,10 +0,0 @@
//go:build qtpy || m5stamp_c3
package main
import "machine"
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.WS2812
var led = machine.NoPin
-12
View File
@@ -1,12 +0,0 @@
//go:build thingplus_rp2040
package main
import "machine"
// This is the pin assignment for the internal neopixel of the
// Sparkfun thingplus rp2040.
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.GPIO8
var led = machine.LED
+2 -1
View File
@@ -80,7 +80,8 @@ func (d *Device) Read() []byte {
func (d *Device) ReadTouchPoint() touch.Point {
d.Read()
z := 0xFFFFF
if d.buf[0] == 0 {
switch d.buf[0] {
case 0, 255:
z = 0
}
+183
View File
@@ -0,0 +1,183 @@
package ina219
type Config struct {
// BusVoltageRange sets the bus voltage range.
BusVoltageRange BusVoltageRange
// PGA sets the programmable gain amplifier.
PGA PGA
// BusADC sets the bus ADC resolution.
BusADC BusADC
// ShuntADC sets the shunt ADC resolution.
ShuntADC ShuntADC
// Mode sets the operating mode.
Mode Mode
// Calibration sets the calibration value for the expected
// voltage and current values.
Calibration Calibration
// 1000 / uA per bit
CurrentDivider float32
// 1mW per bit
PowerMultiplier float32
}
// RegisterValue returns the register value of the configuration.
func (c *Config) RegisterValue() uint16 {
return c.BusVoltageRange.RegisterValue() |
c.PGA.RegisterValue() |
c.BusADC.RegisterValue() |
c.ShuntADC.RegisterValue() |
c.Mode.RegisterValue()
}
// Generate a new configuration from a register value.
func NewConfig(config int16, calibration int16) Config {
return Config{
BusVoltageRange: BusVoltageRange(config >> 13 & 0x1),
PGA: PGA(config >> 11 & 0x3),
BusADC: BusADC(config >> 7 & 0xF),
ShuntADC: ShuntADC(config >> 3 & 0xF),
Mode: Mode(config & 0x7),
Calibration: Calibration(calibration),
}
}
// Configurations from
// https://github.com/adafruit/Adafruit_INA219/blob/master/Adafruit_INA219.cpp
var (
// Config32V2A is a configuration for a 32V 2A range.
Config32V2A = Config{
BusVoltageRange: Range32V,
PGA: PGA8,
BusADC: ADC12,
ShuntADC: SADC12,
Mode: ModeContShuntBus,
Calibration: Calibration16V400mA,
CurrentDivider: 10.0,
PowerMultiplier: 2.0,
}
// Config32V1A is a configuration for a 32V 1A range.
Config32V1A = Config{
BusVoltageRange: Range32V,
PGA: PGA8,
BusADC: ADC12,
ShuntADC: SADC12,
Mode: ModeContShuntBus,
Calibration: Calibration32V1A,
CurrentDivider: 25.0,
PowerMultiplier: 0.8,
}
// Config16V400mA is a configuration for a 16V 400mA range.
Config16V400mA = Config{
BusVoltageRange: Range16V,
PGA: PGA1,
BusADC: ADC12,
ShuntADC: SADC12,
Mode: ModeContShuntBus,
Calibration: Calibration16V400mA,
CurrentDivider: 20.0,
PowerMultiplier: 1.0,
}
)
// BusVoltageRange is the bus voltage range.
type BusVoltageRange int8
const (
Range16V BusVoltageRange = 0 // 0-16V
Range32V BusVoltageRange = 1 // 0-32V
)
func (r BusVoltageRange) RegisterValue() uint16 {
return uint16(r) << 13
}
// PGA is the programmable gain amplifier.
type PGA int8
const (
PGA1 PGA = 0 // 40mV
PGA2 PGA = 1 // 80mV
PGA4 PGA = 2 // 160mV
PGA8 PGA = 3 // 320mV
)
func (p PGA) RegisterValue() uint16 {
return uint16(p) << 11
}
// BusADC is the bus ADC resolution.
type BusADC int8
const (
ADC9 BusADC = 0 // 9-bit
ADC10 BusADC = 1 // 10-bit
ADC11 BusADC = 2 // 11-bit
ADC12 BusADC = 3 // 12-bit
)
func (b BusADC) RegisterValue() uint16 {
return uint16(b) << 7
}
// ShuntADC is the shunt ADC resolution.
type ShuntADC int8
const (
SADC9 ShuntADC = 0 // 9-bit
SADC10 ShuntADC = 1 // 10-bit
SADC11 ShuntADC = 2 // 11-bit
SADC12 ShuntADC = 3 // 12-bit
)
func (s ShuntADC) RegisterValue() uint16 {
return uint16(s) << 3
}
// Mode is the operating mode.
type Mode int8
const (
ModePowerDown Mode = 0 // power-down
ModeTrigShunt Mode = 1 // triggered shunt voltage
ModeTrigBus Mode = 2 // triggered bus voltage
ModeTrigShuntBus Mode = 3 // triggered shunt and bus voltage
ModeADCOff Mode = 4 // ADC off
ModeContShunt Mode = 5 // continuous shunt voltage
ModeContBus Mode = 6 // continuous bus voltage
ModeContShuntBus Mode = 7 // continuous shunt and bus voltage
)
// ModeTriggered is a mask for triggered modes.
const ModeTriggeredMask Mode = 0x4
// ModeTriggered returns true if the mode is a triggered mode.
func ModeTriggered(m Mode) bool {
return m != ModePowerDown && m&ModeTriggeredMask == 0
}
func (m Mode) RegisterValue() uint16 {
return uint16(m)
}
// Calibration is the calibration register for the INA219. Values from:
// https://github.com/adafruit/Adafruit_INA219/blob/master/Adafruit_INA219.cpp
type Calibration uint16
const (
Calibration32V2A Calibration = 4096
Calibration32V1A Calibration = 10240
Calibration16V400mA Calibration = 8192
)
func (c Calibration) RegisterValue() uint16 {
return uint16(c)
}
+13
View File
@@ -0,0 +1,13 @@
package ina219
type ErrOverflow struct{}
func (e ErrOverflow) Error() string { return "overflow" }
type ErrNotReady struct{}
func (e ErrNotReady) Error() string { return "not ready" }
type ErrConfigMismatch struct{}
func (e ErrConfigMismatch) Error() string { return "config mismatch" }
+202
View File
@@ -0,0 +1,202 @@
package ina219
import (
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// An INA219 device.
type Device struct {
bus drivers.I2C
Address uint16
config Config
}
// Create a new INA219 device with the default configuration
// and the given I2C bus at the default address.
//
// Set Address after New to change the address.
//
// Call Configure after New to write the configuration to the
// device. If you don't call Configure, the device may have a
// different configuration and the power divider and current
// multiplier are probably wrong.
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
config: Config32V2A,
}
}
// Set the configuration for the device. This only changes the
// configuration in memory, not on the device. Call Configure
// to write the configuration to the device.
func (d *Device) SetConfig(config Config) {
d.config = config
}
// Write the current configuration to the device.
func (d *Device) Configure() (err error) {
if err = d.WriteRegister(
RegConfig,
d.config.RegisterValue(),
); err != nil {
return
}
if err = d.WriteRegister(
RegCalibration,
d.config.Calibration.RegisterValue(),
); err != nil {
return
}
var readConfig Config
// make sure the configuration is read back correctly
if readConfig, err = d.ReadConfig(); err != nil {
return
} else if readConfig.RegisterValue() != d.config.RegisterValue() {
err = ErrConfigMismatch{}
} else if readConfig.Calibration.RegisterValue() != d.config.Calibration.RegisterValue() {
err = ErrConfigMismatch{}
}
return
}
// Trigger a conversion. This is only necessary if the device is in
// trigger mode. In continuous mode (the default), the device will
// automatically trigger conversions and this has no effect. See
// config.go.
//
// Triggering a conversion or reading the "power" register resets
// the conversion ready bit.
func (d *Device) Trigger() (err error) {
// Only trigger if the mode is one of the triggered modes.
if ModeTriggered(d.config.Mode) {
err = d.WriteRegister(RegConfig, d.config.RegisterValue())
}
return
}
// Measurements reads the bus voltage, shunt voltage, current, and power
// from the device.
func (d *Device) Measurements() (
busVoltage int16,
shuntVoltage int16,
current float32,
power float32,
err error,
) {
// Attempt to read bus voltage first, so we can check for overflow
// or conversion not ready.
if busVoltage, err = d.BusVoltage(); err != nil {
return
}
// Read the rest of the values, reading Power last, which resets
// the conversion ready bit (relevant for triggered modes).
if shuntVoltage, err = d.ShuntVoltage(); err != nil {
return
}
if current, err = d.Current(); err != nil {
return
}
if power, err = d.Power(); err != nil {
return
}
return
}
// BusVoltage reads the "bus" voltage in millivolts.
//
// It returns an error if the value is invalid due to overflow
// or if the conversion is not ready yet. In a continuous mode
// there should always be a measurement available after the
// device is ready. See above notes on Trigger.
func (d *Device) BusVoltage() (voltage int16, err error) {
val, err := d.ReadRegister(RegBusVoltage)
if err != nil {
return
}
// The overflow bit is set, so the values are invalid.
if val&(1<<0) != 0 {
err = ErrOverflow{}
return
}
// The conversion is not ready yet.
if ModeTriggered(d.config.Mode) && val&(1<<1) != 0 {
err = ErrNotReady{}
return
}
voltage = (int16(val) >> 3) * 4
return
}
// ShuntVoltage reads the "shunt" voltage in 100ths of a millivolt.
func (d *Device) ShuntVoltage() (voltage int16, err error) {
return d.ReadRegister(RegShuntVoltage)
}
// Current reads the current in milliamps.
func (d *Device) Current() (current float32, err error) {
val, err := d.ReadRegister(RegCurrent)
if err != nil {
return
}
current = float32(val) / d.config.CurrentDivider
return
}
// Power reads the power in milliwatts.
func (d *Device) Power() (power float32, err error) {
val, err := d.ReadRegister(RegPower)
if err != nil {
return
}
power = float32(val) * d.config.PowerMultiplier
return
}
// Read the configuration from the device.
func (d *Device) ReadConfig() (config Config, err error) {
var cfg, cal int16
if cfg, err = d.ReadRegister(RegConfig); err != nil {
return
}
if cal, err = d.ReadRegister(RegCalibration); err != nil {
return
}
config = NewConfig(cfg, cal)
return
}
// Read a register from the device.
func (d *Device) ReadRegister(reg uint8) (val int16, err error) {
buf := make([]byte, 2)
err = legacy.ReadRegister(d.bus, uint8(d.Address), reg, buf)
if err != nil {
return
}
val = int16(buf[0])<<8 | int16(buf[1]&0xff)
return
}
// Write to a register on the device.
func (d *Device) WriteRegister(reg uint8, val uint16) error {
buf := []byte{byte(val >> 8), byte(val & 0xff)}
return legacy.WriteRegister(d.bus, uint8(d.Address), reg, buf)
}
+232
View File
@@ -0,0 +1,232 @@
package ina219
import (
"fmt"
"testing"
qt "github.com/frankban/quicktest"
"tinygo.org/x/drivers/tester"
)
func TestDefaultAddress(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
dev := New(bus)
c.Assert(dev.Address, qt.Equals, uint16(Address))
}
func TestBusVoltage(t *testing.T) {
t.Run("valid", func(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice16(c, Address)
fake.Registers = map[uint8]uint16{
RegBusVoltage: (4200 << 3) / 4, // 4.2V
}
bus.AddDevice(fake)
dev := New(bus)
voltage, err := dev.BusVoltage()
c.Assert(err, qt.IsNil)
c.Assert(voltage, qt.Equals, int16(4200))
})
t.Run("overflow", func(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice16(c, Address)
fake.Registers = map[uint8]uint16{
RegBusVoltage: (1 >> 0), // overflow
}
bus.AddDevice(fake)
dev := New(bus)
_, err := dev.BusVoltage()
c.Assert(err, qt.Not(qt.IsNil))
c.Assert(err, qt.ErrorMatches, ErrOverflow{}.Error())
})
t.Run("not ready", func(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice16(c, Address)
fake.Registers = map[uint8]uint16{
RegBusVoltage: ((4200 << 3) / 4) | (1 << 1), // not ready
}
bus.AddDevice(fake)
dev := New(bus)
dev.config.Mode = ModeTrigBus
_, err := dev.BusVoltage()
c.Assert(err, qt.Not(qt.IsNil))
c.Assert(err, qt.ErrorMatches, ErrNotReady{}.Error())
})
}
func TestShuntVoltage(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice16(c, Address)
fake.Registers = map[uint8]uint16{
RegShuntVoltage: 0x1234,
}
bus.AddDevice(fake)
dev := New(bus)
voltage, err := dev.ShuntVoltage()
c.Assert(err, qt.IsNil)
c.Assert(voltage, qt.Equals, int16(0x1234))
}
func TestCurrent(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice16(c, Address)
fake.Registers = map[uint8]uint16{
RegCurrent: 420 * 6.9, // 420mA
}
bus.AddDevice(fake)
dev := New(bus)
dev.config.CurrentDivider = 6.9
current, err := dev.Current()
c.Assert(err, qt.IsNil)
c.Assert(current, qt.Equals, float32(420))
}
func TestPower(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice16(c, Address)
fake.Registers = map[uint8]uint16{
RegPower: 420 / 0.8, // 420mW
}
bus.AddDevice(fake)
dev := New(bus)
dev.config.PowerMultiplier = 0.8
power, err := dev.Power()
c.Assert(err, qt.IsNil)
c.Assert(power, qt.Equals, float32(420))
}
func TestReadConfig(t *testing.T) {
// use the default configurations
for _, tc := range []Config{
Config16V400mA,
Config32V2A,
Config32V1A,
} {
n := fmt.Sprintf("%x/%x", tc.RegisterValue(), tc.Calibration.RegisterValue())
t.Run(n, func(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice16(c, Address)
fake.Registers = map[uint8]uint16{
RegConfig: tc.RegisterValue(),
RegCalibration: tc.Calibration.RegisterValue(),
}
bus.AddDevice(fake)
dev := New(bus)
config, err := dev.ReadConfig()
c.Assert(err, qt.IsNil)
c.Assert(config.BusADC, qt.Equals, tc.BusADC)
c.Assert(config.BusVoltageRange, qt.Equals, tc.BusVoltageRange)
c.Assert(config.Calibration, qt.Equals, tc.Calibration)
c.Assert(config.Mode, qt.Equals, tc.Mode)
c.Assert(config.PGA, qt.Equals, tc.PGA)
c.Assert(config.ShuntADC, qt.Equals, tc.ShuntADC)
})
}
}
func TestWriteConfig(t *testing.T) {
// use the default configurations
for _, tc := range []Config{
Config16V400mA,
Config32V2A,
Config32V1A,
} {
n := fmt.Sprintf("%x/%x", tc.RegisterValue(), tc.Calibration.RegisterValue())
t.Run(n, func(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice16(c, Address)
bus.AddDevice(fake)
fake.Registers = map[uint8]uint16{
RegConfig: 0,
RegCalibration: 0,
}
dev := New(bus)
dev.config = tc
err := dev.Configure()
c.Assert(err, qt.IsNil)
c.Assert(fake.Registers[RegConfig], qt.Equals, tc.RegisterValue())
c.Assert(fake.Registers[RegCalibration], qt.Equals, tc.Calibration.RegisterValue())
})
}
}
func TestSetConfig(t *testing.T) {
for _, tc := range []Config{
Config16V400mA,
Config32V2A,
Config32V1A,
} {
n := fmt.Sprintf("%x/%x", tc.RegisterValue(), tc.Calibration.RegisterValue())
t.Run(n, func(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
dev := New(bus)
dev.SetConfig(tc)
c.Assert(dev.config, qt.Equals, tc)
})
}
}
func TestTrigger(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice16(c, Address)
bus.AddDevice(fake)
fake.Registers = map[uint8]uint16{
RegConfig: Config32V2A.RegisterValue(),
}
dev := New(bus)
dev.config = Config32V2A
dev.config.Mode = ModeTrigBus
err := dev.Trigger()
c.Assert(err, qt.IsNil)
c.Assert(fake.Registers[RegConfig], qt.Equals, dev.config.RegisterValue())
}
func TestMeasurements(t *testing.T) {
bvVal := int16(4200)
svVal := int16(1234)
iVal := float32(420)
pVal := float32(420)
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice16(c, Address)
bus.AddDevice(fake)
fake.Registers = map[uint8]uint16{
RegBusVoltage: uint16(((4200 << 3) / 4) | (1 << 1)),
RegShuntVoltage: uint16(svVal),
RegCurrent: uint16(iVal * Config16V400mA.CurrentDivider),
RegPower: uint16(pVal / Config16V400mA.PowerMultiplier),
}
dev := New(bus)
dev.config = Config16V400mA
bv, sv, i, p, err := dev.Measurements()
c.Assert(err, qt.IsNil)
c.Assert(bv, qt.Equals, bvVal)
c.Assert(sv, qt.Equals, svVal)
c.Assert(i, qt.Equals, iVal)
c.Assert(p, qt.Equals, pVal)
}
+13
View File
@@ -0,0 +1,13 @@
package ina219
// The default I2C address for this device.
const Address = 0x40
const (
RegConfig uint8 = 0x0
RegShuntVoltage uint8 = 0x1
RegBusVoltage uint8 = 0x2
RegPower uint8 = 0x3
RegCurrent uint8 = 0x4
RegCalibration uint8 = 0x5
)
+98
View File
@@ -0,0 +1,98 @@
// Package mcp9808 implements a driver for the MCP9808 High Accuracy I2C Temperature Sensor
//
// Datasheet: https://cdn-shop.adafruit.com/datasheets/MCP9808.pdf
// Module: https://www.adafruit.com/product/1782
// Only implemented: temperature reading, resolution read & set
package mcp9808
import (
"encoding/binary"
"errors"
"tinygo.org/x/drivers"
)
type Device struct {
bus drivers.I2C
Address uint16
}
func New(bus drivers.I2C) Device {
return Device{bus, MCP9808_I2CADDR_DEFAULT}
}
func (d *Device) Connected() bool {
data := make([]byte, 2)
d.Read(MCP9808_REG_DEVICE_ID, &data)
return binary.BigEndian.Uint16(data) == MCP9808_DEVICE_ID
}
func (d *Device) ReadTemperature() (float64, error) {
data := make([]byte, 2)
var temp float64
if err := d.Read(MCP9808_REG_AMBIENT_TEMP, &data); err != nil {
return 0, err
}
data[0] = data[0] & 0x1F
if data[0]&0x10 == 0x10 {
data[0] = data[0] & 0x0F
temp = float64(data[0])*16 + float64(data[1])/16.0 - 256
}
temp = float64(data[0])*16 + float64(data[1])/16.0
return temp, nil
}
func (d *Device) ReadResolution() (resolution, error) {
data := make([]byte, 2)
err := d.Read(MCP9808_REG_RESOLUTION, &data)
if err != nil {
return 0, err
}
switch data[0] {
case 0:
return Low, nil
case 1:
return Medium, nil
case 2:
return High, nil
case 3:
return Maximum, nil
default:
return 0, errors.New("unknown resolution")
}
}
func (d *Device) SetResolution(r resolution) error {
switch r {
case Low:
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x00}); err != nil {
return err
}
case Medium:
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x01}); err != nil {
return err
}
case High:
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x02}); err != nil {
return err
}
case Maximum:
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x03}); err != nil {
return err
}
default:
return nil
}
return nil
}
func (d *Device) Write(register byte, data []byte) error {
buf := append([]byte{register}, data...)
return d.bus.Tx(d.Address, buf, nil)
}
func (d *Device) Read(register byte, data *[]byte) error {
return d.bus.Tx(d.Address, []byte{register}, *data)
}
+53
View File
@@ -0,0 +1,53 @@
// Package mcp9808 implements a driver for the MCP9808 High Accuracy I2C Temperature Sensor
//
// Datasheet: https://cdn-shop.adafruit.com/datasheets/MCP9808.pdf
// Module: https://www.adafruit.com/product/1782
package mcp9808
// Constants/addresses used for I2C.
const (
MCP9808_DEVICE_ID = 0x0400
MCP9808_MANUF_ID = 0x0054
MCP9808_I2CADDR_DEFAULT = 0x18 //default I2C address
MCP9808_REG_CONFIG = 0x01 //MCP9808 config register
MCP9808_REG_CONFIG_SHUTDOWN = 0x0100 //shutdown config
MCP9808_REG_CONFIG_CRITLOCKED = 0x0080 //critical trip lock
MCP9808_REG_CONFIG_WINLOCKED = 0x0040 //alarm window lock
MCP9808_REG_CONFIG_INTCLR = 0x0020 //interrupt clear
MCP9808_REG_CONFIG_ALERTSTAT = 0x0010 //alert output status
MCP9808_REG_CONFIG_ALERTCTRL = 0x0008 //alert output control
MCP9808_REG_CONFIG_ALERTSEL = 0x0004 //alert output select
MCP9808_REG_CONFIG_ALERTPOL = 0x0002 //alert output polarity
MCP9808_REG_CONFIG_ALERTMODE = 0x0001 //alert output mode
MCP9808_REG_UPPER_TEMP = 0x02 //upper alert boundary
MCP9808_REG_LOWER_TEMP = 0x03 //lower alert boundery
MCP9808_REG_CRIT_TEMP = 0x04 //critical temperature
MCP9808_REG_AMBIENT_TEMP = 0x05 //ambient temperature
MCP9808_REG_MANUF_ID = 0x06 //manufacturer ID
MCP9808_REG_DEVICE_ID = 0x07 //device ID
MCP9808_REG_RESOLUTION = 0x08 //resolution
)
/*
======= ============ ==============
value resolution reading Time
======= ============ ==============
0 0.5°C 30 ms
1 0.25°C 65 ms
2 0.125°C 130 ms
3 0.0625°C 250 ms
======= ============ ==============
*/
type resolution uint8
const (
Low resolution = iota
Medium
High
Maximum
)
+3 -1
View File
@@ -36,9 +36,11 @@ 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")
ErrNoMoreSockets = errors.New("No more sockets")
ErrClosingSocket = errors.New("Error closing socket")
ErrNotSupported = errors.New("Not supported")
ErrInvalidSocketFd = errors.New("Invalid socket fd")
)
// Duplicate of non-exported net.errTimeout
@@ -82,7 +84,7 @@ type Netdever interface {
Bind(sockfd int, ip netip.AddrPort) error
Connect(sockfd int, host string, ip netip.AddrPort) error
Listen(sockfd int, backlog int) error
Accept(sockfd int, ip netip.AddrPort) (int, error)
Accept(sockfd int) (int, netip.AddrPort, error)
Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)
Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)
Close(sockfd int) error
-97
View File
@@ -1,97 +0,0 @@
package netif
import (
"net"
"time"
)
func AutoProbe(timeout time.Duration) (Stack, error) {
// This function automatically gets the first available network device
// and returns a stack for it.
// It is intended to be used by the user as a convenience function.
// Will be guarded by build tags and specific for whether the device
// is a StackWifi, InterfaceEthPollWifi or InterfaceEthPoller.
return nil, nil
}
func ProbeStackWifi() StackWifi {
return nil
}
func ProbeEthPollWifi() InterfaceEthPollWifi {
return nil
}
func ProbeEthPoller() InterfaceEthPoller {
return nil
}
func StackForEthPoll(dev InterfaceEthPoller) Stack {
// Use seqs to generate a stack.
return nil
}
// OSI layer 4 enabled WIFI chip. i.e.: ESP32
func ExampleProbeStackWifi() {
dev := ProbeStackWifi()
wifiparams := WifiParams{
SSID: "myssid",
ConnectMode: ConnectModeSTA,
Passphrase: "mypassphrase",
Auth: AuthTypeWPA2,
CountryCode: "US",
}
err := StartWifiAutoconnect(dev, WifiAutoconnectParams{
WifiParams: wifiparams,
})
if err != nil {
panic(err)
}
for dev.NetFlags()&net.FlagRunning == 0 {
time.Sleep(100 * time.Millisecond)
}
UseStack(dev)
net.Dial("tcp", "192.168.1.1:33")
}
// Simplest case, OSI layer 2 enabled WIFI chip, i.e: CYW43439
func ExampleProbeEthPollWifi() {
dev := ProbeEthPollWifi()
wifiparams := WifiParams{
SSID: "myssid",
ConnectMode: ConnectModeSTA,
Passphrase: "mypassphrase",
Auth: AuthTypeWPA2,
CountryCode: "US",
}
err := StartWifiAutoconnect(dev, WifiAutoconnectParams{
WifiParams: wifiparams,
})
if err != nil {
panic(err)
}
for dev.NetFlags()&net.FlagRunning == 0 {
time.Sleep(100 * time.Millisecond)
}
stack := StackForEthPoll(dev)
UseStack(stack)
net.Dial("tcp", "192.168.1.1:33")
}
// OSI level 2 chip with wired connection, i.e. ENC28J60.
func ExampleProbeEthPoller() {
dev := ProbeEthPoller()
if dev.NetFlags()&net.FlagRunning == 0 {
panic("ethernet not connected")
}
stack := StackForEthPoll(dev)
UseStack(stack)
net.Dial("tcp", "192.168.1.1:33")
}
func ExampleAutoProbe() {
stack, err := AutoProbe(10 * time.Second)
if err != nil {
panic(err)
}
UseStack(stack)
net.Dial("tcp", "192.168.1.1:33")
}
-211
View File
@@ -1,211 +0,0 @@
package netif
import (
"context"
"errors"
"net"
"net/netip"
"time"
_ "unsafe"
)
//go:linkname UseStack net.useNetdev
func UseStack(stack Stack)
// Socket errors.
var (
ErrProtocolNotSupported = errors.New("socket protocol/type not supported")
)
// Wifi errors.
var (
ErrAuthFailure = errors.New("wifi authentication failure")
ErrAuthUnsupported = errors.New("wifi authorization type not supported")
)
const (
_AF_INET = 0x2
_SOCK_STREAM = 0x1
_SOCK_DGRAM = 0x2
_SOL_SOCKET = 0x1
_SO_KEEPALIVE = 0x9
_SOL_TCP = 0x6
_TCP_KEEPINTVL = 0x5
_IPPROTO_TCP = 0x6
_IPPROTO_UDP = 0x11
// Made up, not a real IP protocol number. This is used to create a
// TLS socket on the device, assuming the device supports mbed TLS.
_IPPROTO_TLS = 0xFE
_F_SETFL = 0x4
)
// Interface is the minimum interface that need be implemented by any network
// device driver and is based on [net.Interface].
type Interface interface {
// HardwareAddr6 returns the device's 6-byte [MAC address].
//
// [MAC address]: https://en.wikipedia.org/wiki/MAC_address
HardwareAddr6() ([6]byte, error)
// NetFlags returns the net.Flag values for the interface. It includes state of connection.
NetFlags() net.Flags
// MTU returns the maximum transmission unit size.
MTU() int
// Notify to register callback for network events. May not be supported for certain devices.
NetNotify(cb func(Event)) error
}
// InterfaceEthPoller is implemented by devices that send/receive ethernet packets.
type InterfaceEthPoller interface {
Interface
// SendEth sends an Ethernet packet
SendEth(pkt []byte) error
// RecvEthHandle sets recieve Ethernet packet callback function
RecvEthHandle(func(pkt []byte) error)
// PollOne tries to receive one Ethernet packet and returns true if one was
PollOne() (bool, error)
}
// InterfaceWifi is implemented by a interface device that has the capacity
// to connect to wifi networks.
type InterfaceWifi interface {
Interface
// Connect device to network
NetConnect(params WifiParams) error
// Disconnect device from network
NetDisconnect()
}
// InterfaceEthPollWifi is implemented by devices that connect to wifi networks
// and send/receive ethernet packets (OSI level 2).
type InterfaceEthPollWifi interface {
InterfaceWifi
InterfaceEthPoller
}
type Stack interface {
// GetHostByName returns the IP address of either a hostname or IPv4
// address in standard dot notation
// GetHostByName(name string) (netip.Addr, error)
// Addr returns IP address assigned to the interface, either by
// DHCP or statically
Addr() (netip.Addr, error)
// Berkely Sockets-like interface, Go-ified. See man page for socket(2), etc.
Socket(domain int, stype int, protocol int) (int, error)
Bind(sockfd int, ip netip.AddrPort) error
Connect(sockfd int, host string, ip netip.AddrPort) error
Listen(sockfd int, backlog int) error
Accept(sockfd int) (int, netip.AddrPort, error)
Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)
Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)
Close(sockfd int) error
SetSockOpt(sockfd int, level int, opt int, value interface{}) error
}
// Resolver is implemented by DNS resolvers, notably Go's [net.DefaultResolver].
type Resolver interface {
// LookupNetIP looks up host using the local resolver.
// It returns a slice of that host's IP addresses of the type specified by
// network.
// The network must be one of "ip", "ip4" or "ip6".
LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
}
// StackWifi is returned by `Probe` function for devices that communicate
// on the OSI level 4 (transport) layer.
type StackWifi interface {
InterfaceWifi
Stack
}
type WifiParams struct {
// Connect mode
ConnectMode ConnectMode
// SSID of Wifi AP
SSID string
// Passphrase of Wifi AP
Passphrase string
// Wifi authorization type
Auth AuthType
// Wifi country code as two-char string. E.g. "XX" for world-wide,
// "US" for USA, etc.
CountryCode string
}
type Event uint8
// Network events
const (
// The device's network connection is now UP
EventNetUp Event = iota
// The device's network connection is now DOWN
EventNetDown
)
type ConnectMode uint8
// Connect modes
const (
ConnectModeSTA = iota // Connect as Wifi station (default)
ConnectModeAP // Connect as Wifi Access Point
)
type AuthType uint8
// Wifi authorization types. Used when setting up an access point, or
// connecting to an access point
const (
AuthTypeWPA2 = iota // WPA2 authorization (default)
AuthTypeOpen // No authorization required (open)
AuthTypeWPA // WPA authorization
AuthTypeWPA2Mixed // WPA2/WPA mixed authorization
)
type WifiAutoconnectParams struct {
WifiParams
// Retries is how many attempts to connect before returning with a
// "Connect failed" error. Zero means infinite retries.
// Retries int // Probably should be implemented as a function
// Timeout duration for each connection attempt. The default zero
// value means 10sec.
ConnectTimeout time.Duration
// Watchdog ticker duration. On tick, the watchdog will check for
// downed connection or hardware fault and try to recover the
// connection. Set to zero to disable watchodog.
WatchdogTimeout time.Duration
}
func StartWifiAutoconnect(dev InterfaceWifi, cfg WifiAutoconnectParams) error {
if dev == nil {
return errors.New("nil device")
}
go func() {
// Wifi autoconnect algorithm in one place,
// no need to implement for every single netdever.
RECONNECT:
for i := 0; i < 4; i++ {
err := dev.NetConnect(cfg.WifiParams)
if err != nil {
time.Sleep(cfg.ConnectTimeout)
goto RECONNECT
}
// Once connected reset the retry counter.
i = 0
for cfg.WatchdogTimeout != 0 {
time.Sleep(cfg.WatchdogTimeout)
if dev.NetFlags()&net.FlagRunning == 0 {
goto RECONNECT
}
}
}
}()
return nil
}
+1
View File
@@ -13,6 +13,7 @@ var (
ErrConnectFailed = errors.New("Connect failed")
ErrConnectTimeout = errors.New("Connect timed out")
ErrMissingSSID = errors.New("Missing WiFi SSID")
ErrShortPassphrase = errors.New("Invalid Wifi Passphrase < 8 chars")
ErrAuthFailure = errors.New("Wifi authentication failure")
ErrAuthTypeNoGood = errors.New("Wifi authorization type not supported")
ErrConnectModeNoGood = errors.New("Connect mode not supported")
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || matrixportal_m4
//go:build ninafw && !arduino_mkrwifi1010
package probe
+17 -9
View File
@@ -27,8 +27,9 @@ type Config struct{}
// Errors list
var (
errNoPresence = errors.New("Error: OneWire. No devices on the bus.")
errReadAddress = errors.New("Error: OneWire. Read address error: CRC mismatch.")
errNoPresence = errors.New("Error: OneWire. No devices on the bus.")
errTooManyDevices = errors.New("Error: OneWire. Too many devices on the bus.")
errReadAddress = errors.New("Error: OneWire. Read address error: CRC mismatch.")
)
// New creates a new GPIO 1-Wire connection.
@@ -46,7 +47,7 @@ func (d *Device) Configure(config Config) {}
func (d Device) Reset() error {
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
time.Sleep(480 * time.Microsecond)
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
time.Sleep(70 * time.Microsecond)
precence := d.p.Get()
time.Sleep(410 * time.Microsecond)
@@ -61,11 +62,11 @@ func (d Device) WriteBit(data uint8) {
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
if data&1 == 1 { // Send '1'
time.Sleep(5 * time.Microsecond)
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
time.Sleep(60 * time.Microsecond)
} else { // Send '0'
time.Sleep(60 * time.Microsecond)
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
time.Sleep(5 * time.Microsecond)
}
}
@@ -82,7 +83,7 @@ func (d Device) Write(data uint8) {
func (d Device) ReadBit() (data uint8) {
d.p.Configure(machine.PinConfig{Mode: machine.PinOutput})
time.Sleep(3 * time.Microsecond)
d.p.Configure(machine.PinConfig{Mode: machine.PinInput})
d.p.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
time.Sleep(8 * time.Microsecond)
if d.p.Get() {
data = 1
@@ -111,7 +112,7 @@ func (d Device) ReadAddress() ([]uint8, error) {
for i := 0; i < 8; i++ {
romid[i] = d.Read()
}
if d.Сrc8(romid, 7) != romid[7] {
if d.Сrc8(romid) != 0 {
return nil, errReadAddress
}
return romid, nil
@@ -187,15 +188,22 @@ func (d Device) Search(cmd uint8) ([][]uint8, error) {
}
d.WriteBit(bit)
}
if d.Сrc8(lastAddress) != 0 {
continue
}
lastFork = lastZero
copy(romIDs[romIndex], lastAddress)
romIndex++
if romIndex >= 32 {
return romIDs, errTooManyDevices
}
}
return romIDs[:romIndex:romIndex], nil
}
// Crc8 compute a Dallas Semiconductor 8 bit CRC.
func (d Device) Сrc8(buffer []uint8, size int) (crc uint8) {
func (_ Device) Сrc8(buffer []uint8) (crc uint8) {
// Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0
// Tiny 2x16 entry CRC table created by Arjen Lentz
// See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table
@@ -205,7 +213,7 @@ func (d Device) Сrc8(buffer []uint8, size int) (crc uint8) {
0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8,
0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74,
}
for i := 0; i < size; i++ {
for i := 0; i < len(buffer); i++ {
crc = buffer[i] ^ crc // just re-using crc as intermediate
crc = crc8_table[crc&0x0f] ^ crc8_table[16+((crc>>4)&0x0f)]
}
+84
View File
@@ -0,0 +1,84 @@
// Package pcf8591 implements a driver for the PCF8591 Analog to Digital/Digital to Analog Converter.
//
// Datasheet: https://www.nxp.com/docs/en/data-sheet/PCF8591.pdf
package pcf8591 // import "tinygo.org/x/drivers/pcf8591"
import (
"machine"
"errors"
"tinygo.org/x/drivers"
)
// Device wraps PCF8591 ADC functions.
type Device struct {
bus drivers.I2C
Address uint16
CH0 ADCPin
CH1 ADCPin
CH2 ADCPin
CH3 ADCPin
}
// ADCPin is the implementation of the ADConverter interface.
type ADCPin struct {
machine.Pin
d *Device
}
// New returns a new PCF8591 driver. Pass in a fully configured I2C bus.
func New(b drivers.I2C) *Device {
d := &Device{
bus: b,
Address: defaultAddress,
}
// setup all channels
d.CH0 = d.GetADC(0)
d.CH1 = d.GetADC(1)
d.CH2 = d.GetADC(2)
d.CH3 = d.GetADC(3)
return d
}
// Configure here just for interface compatibility.
func (d *Device) Configure() {
}
// Read analog data from channel
func (d *Device) Read(ch int) (uint16, error) {
if ch < 0 || ch > 3 {
return 0, errors.New("invalid channel for pcf8591 Read")
}
return d.GetADC(ch).Get(), nil
}
// GetADC returns an ADC for a specific channel.
func (d *Device) GetADC(ch int) ADCPin {
return ADCPin{machine.Pin(ch), d}
}
// Get the current reading for a specific ADCPin.
func (p ADCPin) Get() uint16 {
// TODO: also implement DAC
tx := make([]byte, 2)
tx[0] = byte(p.Pin)
rx := make([]byte, 2)
// The result from the measurement triggered by the first write,
// however, the second write is required to get the result.
// See section 8.4 "A/D Conversion" in the datasheet for more info
p.d.bus.Tx(p.d.Address, tx, rx)
p.d.bus.Tx(p.d.Address, tx, rx)
// scale result to 16bit value like other ADCs
return uint16(rx[1]) << 8
}
// Configure here just for interface compatibility.
func (p ADCPin) Configure() {
}
+7
View File
@@ -0,0 +1,7 @@
package pcf8591
// PCF8591 Default Address
const defaultAddress = 0x48
// control bit for DAC
const PCF8591_ENABLE_DAC = 0x40
+85 -8
View File
@@ -22,12 +22,13 @@ func NewImage[T Color](width, height int) Image[T] {
}
var zeroColor T
var data unsafe.Pointer
if zeroColor.BitsPerPixel()%8 == 0 {
switch {
case zeroColor.BitsPerPixel()%8 == 0:
// Typical formats like RGB888 and RGB565.
// Each color starts at a whole byte offset from the start.
buf := make([]T, width*height)
data = unsafe.Pointer(&buf[0])
} else {
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()
@@ -42,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
@@ -80,10 +115,11 @@ func (img Image[T]) Len() int {
func (img Image[T]) RawBuffer() []uint8 {
var zeroColor T
var numBytes int
if zeroColor.BitsPerPixel()%8 == 0 {
switch {
case zeroColor.BitsPerPixel()%8 == 0:
// Each color starts at a whole byte offset.
numBytes = int(unsafe.Sizeof(zeroColor)) * int(img.width) * int(img.height)
} else {
default:
// Formats like RGB444 that aren't a whole number of bytes.
numBits := zeroColor.BitsPerPixel() * int(img.width) * int(img.height)
numBytes = (numBits + 7) / 8 // round up (see NewImage)
@@ -99,7 +135,21 @@ func (img Image[T]) Size() (int, int) {
func (img Image[T]) setPixel(index int, c T) {
var zeroColor T
if zeroColor.BitsPerPixel()%8 == 0 {
switch {
case zeroColor.BitsPerPixel() == 1:
// Monochrome.
offset := index / 8
bits := index % 8
ptr := (*byte)(unsafe.Add(img.data, offset))
if c != zeroColor {
*((*byte)(ptr)) |= (1 << (7 - uint8(bits)))
} else {
*((*byte)(ptr)) &^= (1 << (7 - uint8(bits)))
}
return
case zeroColor.BitsPerPixel()%8 == 0:
// Each color starts at a whole byte offset.
// This is the easy case.
offset := index * int(unsafe.Sizeof(zeroColor))
@@ -147,7 +197,16 @@ func (img Image[T]) Get(x, y int) T {
var zeroColor T
index := y*int(img.width) + x // index into img.data
if zeroColor.BitsPerPixel()%8 == 0 {
switch {
case zeroColor.BitsPerPixel() == 1:
// Monochrome.
var c Monochrome
offset := index / 8
bits := index % 8
ptr := (*byte)(unsafe.Add(img.data, offset))
c = ((*ptr >> (7 - uint8(bits))) & 0x1) > 0
return any(c).(T)
case zeroColor.BitsPerPixel()%8 == 0:
// Colors like RGB565, RGB888, etc.
offset := index * int(unsafe.Sizeof(zeroColor))
ptr := unsafe.Add(img.data, offset)
@@ -181,8 +240,26 @@ func (img Image[T]) Get(x, y int) T {
func (img Image[T]) FillSolidColor(color T) {
var zeroColor T
// Fast pass for colors of 8, 16, 24, etc bytes in size.
if zeroColor.BitsPerPixel()%8 == 0 {
switch {
case zeroColor.BitsPerPixel() == 1:
// Monochrome.
var colorByte uint8
if color != zeroColor {
colorByte = 0xff
}
numBytes := int(img.width) * int(img.height) / 8
for i := 0; i < numBytes; i++ {
// TODO: this can be optimized a lot.
// - The store can be done as a 32-bit integer, after checking for
// alignment.
// - Perhaps the loop can be unrolled to improve copy performance.
ptr := (*byte)(unsafe.Add(img.data, i))
*((*byte)(ptr)) = colorByte
}
return
case zeroColor.BitsPerPixel()%8 == 0:
// Fast pass for colors of 8, 16, 24, etc bytes in size.
ptr := img.data
for i := 0; i < img.Len(); i++ {
// TODO: this can be optimized a lot.
+190
View File
@@ -1,7 +1,9 @@
package pixel_test
import (
goimage "image"
"image/color"
"math/rand"
"testing"
"tinygo.org/x/drivers/pixel"
@@ -62,3 +64,191 @@ func TestImageRGB444BE(t *testing.T) {
}
}
}
func TestImageMonochrome(t *testing.T) {
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},
{G: 0xff},
{R: 0xff, G: 0xff},
{G: 0xff, B: 0xff},
{R: 0x00},
{G: 0x00, A: 0xff},
{B: 0x00, A: 0xff},
} {
encoded := pixel.NewColor[pixel.Monochrome](expected.R, expected.G, expected.B)
image.Set(5, 3, encoded)
actual := image.Get(5, 3).RGBA()
switch {
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)
}
}
}
// Test pixel formats by filling them with noise and checking whether they
// contain the same data afterwards.
func TestImageNoise(t *testing.T) {
t.Run("RGB888", func(t *testing.T) {
testImageNoiseN[pixel.RGB888](t)
})
t.Run("RGB565BE", func(t *testing.T) {
testImageNoiseN[pixel.RGB565BE](t)
})
t.Run("RGB555", func(t *testing.T) {
testImageNoiseN[pixel.RGB555](t)
})
t.Run("RGB444BE", func(t *testing.T) {
testImageNoiseN[pixel.RGB444BE](t)
})
t.Run("Monochrome", func(t *testing.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
height := rand.Int()%500 + 10
t.Log("image size:", width, height)
// Create two images: the to-be-tested image object and a reference image.
img := pixel.NewImage[T](width, height)
ref := goimage.NewRGBA(goimage.Rect(0, 0, width, height))
// Fill the two images with noise.
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
// Set a random color in both images.
c := pixel.NewColor[T](uint8(rand.Uint32()), uint8(rand.Uint32()), uint8(rand.Uint32()))
img.Set(x, y, c)
ref.Set(x, y, c.RGBA())
}
}
// Compare the two images. They should match.
mismatch := 0
firstX := 0
firstY := 0
var firstExpected, firstActual color.RGBA
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
c := img.Get(x, y).RGBA()
r2, g2, b2, _ := ref.At(x, y).RGBA()
c2 := color.RGBA{R: uint8(r2 >> 8), G: uint8(g2 >> 8), B: uint8(b2 >> 8), A: 255}
if c != c2 {
mismatch++
if mismatch == 1 {
firstX = x
firstY = y
firstExpected = c
firstActual = c2
}
}
}
}
if mismatch != 0 {
t.Errorf("mismatch found: %d pixels are different (first diff at (%d, %d), expected %v, actual %v)", mismatch, firstX, firstY, firstExpected, firstActual)
}
}
+33 -2
View File
@@ -14,9 +14,9 @@ import (
// Pixel with a particular color, matching the underlying hardware of a
// particular display. Each pixel is at least 1 byte in size.
// The color format is sRGB (or close to it) in all cases.
// The color format is sRGB (or close to it) in all cases except for 1-bit.
type Color interface {
RGB888 | RGB565BE | RGB555 | RGB444BE
RGB888 | RGB565BE | RGB555 | RGB444BE | Monochrome
BaseColor
}
@@ -50,6 +50,8 @@ func NewColor[T Color](r, g, b uint8) T {
return any(NewRGB555(r, g, b)).(T)
case RGB444BE:
return any(NewRGB444BE(r, g, b)).(T)
case Monochrome:
return any(NewMonochrome(r, g, b)).(T)
default:
panic("unknown color format")
}
@@ -202,6 +204,35 @@ func (c RGB444BE) RGBA() color.RGBA {
return color
}
type Monochrome bool
func NewMonochrome(r, g, b uint8) Monochrome {
// Very simple black/white split.
// This isn't very accurate (especially for sRGB colors) but is close enough.
if int(r)+int(g)+int(b) > 128*3 { // light, convert to white
return Monochrome(true)
}
// dark, convert to black
return Monochrome(false)
}
func (c Monochrome) BitsPerPixel() int {
return 1
}
func (c Monochrome) RGBA() color.RGBA {
value := uint8(0)
if c {
value = 255
}
return color.RGBA{
R: value,
G: value,
B: value,
A: 255,
}
}
// Gamma brightness lookup table:
// https://victornpb.github.io/gamma-table-generator
// gamma = 0.45 steps = 256 range = 0-255
+98 -17
View File
@@ -17,6 +17,7 @@ import (
"sync"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/netlink"
)
@@ -27,13 +28,13 @@ var _debug debug = debugBasic
//var _debug debug = debugBasic | debugNetdev | debugRpc
var (
version = "0.0.1"
driverName = "Realtek rtl8720dn Wifi network device driver (rtl8720dn)"
)
const (
O_NONBLOCK = 1 // note: different value than syscall.O_NONBLOCK (0x800)
RTW_MODE_STA = 0x00000001
O_NONBLOCK = 1 // note: different value than syscall.O_NONBLOCK (0x800)
RTW_MODE_STA = 0x00000001
defaultChannel = 6
)
type sock int32
@@ -102,14 +103,22 @@ func (r *rtl8720dn) connectToAP() error {
return netlink.ErrMissingSSID
}
if len(r.params.Passphrase) != 0 && len(r.params.Passphrase) < 8 {
return netlink.ErrShortPassphrase
}
if debugging(debugBasic) {
fmt.Printf("Connecting to Wifi SSID '%s'...", r.params.Ssid)
}
// Start the connection process
securityType := uint32(0x00400004)
securityType := uint32(0) // RTW_SECURITY_OPEN
if len(r.params.Passphrase) != 0 {
securityType = 0x00400004 // RTW_SECURITY_WPA2_AES_PSK
}
result := r.rpc_wifi_connect(r.params.Ssid, r.params.Passphrase, securityType, -1, 0)
if result == -1 {
if result != 0 {
if debugging(debugBasic) {
fmt.Printf("FAILED\r\n")
}
@@ -127,6 +136,51 @@ func (r *rtl8720dn) connectToAP() error {
return r.startDhcpc()
}
func (r *rtl8720dn) startDhcps() error {
if result := r.rpc_tcpip_adapter_dhcps_start(0); result == -1 {
return netdev.ErrStartingDHCPServer
}
return nil
}
func (r *rtl8720dn) startAP() error {
if len(r.params.Ssid) == 0 {
return netlink.ErrMissingSSID
}
if len(r.params.Passphrase) != 0 && len(r.params.Passphrase) < 8 {
return netlink.ErrShortPassphrase
}
if debugging(debugBasic) {
fmt.Printf("Starting Wifi AP as SSID '%s'...", r.params.Ssid)
}
// Start the connection process
securityType := uint32(0) // RTW_SECURITY_OPEN
if len(r.params.Passphrase) != 0 {
securityType = 0x00400004 // RTW_SECURITY_WPA2_AES_PSK
}
result := r.rpc_wifi_start_ap(r.params.Ssid, r.params.Passphrase, securityType, defaultChannel)
if result != 0 {
if debugging(debugBasic) {
fmt.Printf("FAILED\r\n")
}
return netlink.ErrConnectFailed
}
if debugging(debugBasic) {
fmt.Printf("LISTENING\r\n")
}
if r.notifyCb != nil {
r.notifyCb(netlink.EventNetUp)
}
return r.startDhcps()
}
func (r *rtl8720dn) showDriver() {
if r.driverShown {
return
@@ -134,7 +188,7 @@ func (r *rtl8720dn) showDriver() {
if debugging(debugBasic) {
fmt.Printf("\r\n")
fmt.Printf("%s\r\n\r\n", driverName)
fmt.Printf("Driver version : %s\r\n", version)
fmt.Printf("Driver version : %s\r\n", drivers.Version)
}
r.driverShown = true
}
@@ -238,14 +292,27 @@ func (r *rtl8720dn) netConnect(reset bool) error {
}
r.showDevice()
retry:
for i := 0; r.params.Retries == 0 || i < r.params.Retries; i++ {
if err := r.connectToAP(); err != nil {
if err == netlink.ErrConnectFailed {
continue
switch r.params.ConnectMode {
case netlink.ConnectModeAP:
if err := r.startAP(); err != nil {
if err == netlink.ErrConnectFailed {
continue
}
return err
}
return err
break retry
default:
if err := r.connectToAP(); err != nil {
if err == netlink.ErrConnectFailed {
continue
}
return err
}
break retry
}
break
}
if r.networkDown() {
@@ -437,6 +504,12 @@ func ipToName(ip netip.AddrPort) []byte {
return name
}
func nameToIp(name []byte) netip.AddrPort {
port := uint16(name[2])<<8 | uint16(name[3])
addr, _ := netip.AddrFromSlice(name[4:8])
return netip.AddrPortFrom(addr, port)
}
func (r *rtl8720dn) Bind(sockfd int, ip netip.AddrPort) error {
if debugging(debugNetdev) {
@@ -534,10 +607,10 @@ func (r *rtl8720dn) Listen(sockfd int, backlog int) error {
return nil
}
func (r *rtl8720dn) Accept(sockfd int, ip netip.AddrPort) (int, error) {
func (r *rtl8720dn) Accept(sockfd int) (int, netip.AddrPort, error) {
if debugging(debugNetdev) {
fmt.Printf("[Accept] sockfd: %d, peer: %s\r\n", sockfd, ip)
fmt.Printf("[Accept] sockfd: %d\r\n", sockfd)
}
r.mu.Lock()
@@ -546,12 +619,12 @@ func (r *rtl8720dn) Accept(sockfd int, ip netip.AddrPort) (int, error) {
var newSock int32
var lsock = sock(sockfd)
var socket = r.sockets[lsock]
var name = ipToName(ip)
var name = ipToName(netip.AddrPort{})
switch socket.protocol {
case netdev.IPPROTO_TCP:
default:
return -1, netdev.ErrProtocolNotSupported
return -1, netip.AddrPort{}, netdev.ErrProtocolNotSupported
}
for {
@@ -570,6 +643,14 @@ func (r *rtl8720dn) Accept(sockfd int, ip netip.AddrPort) (int, error) {
continue
}
// Get remote peer ip:port
namelen = uint32(len(name))
result := r.rpc_lwip_getpeername(int32(newSock), name, &namelen)
if result == -1 {
return -1, netip.AddrPort{}, fmt.Errorf("Getpeername failed")
}
raddr := nameToIp(name)
// If we've already seen this socket, we can re-use
// the socket and return it. But, only if the socket
// is closed. If it's not closed, we'll just come back
@@ -582,12 +663,12 @@ func (r *rtl8720dn) Accept(sockfd int, ip netip.AddrPort) (int, error) {
continue
}
// Reuse client socket
return int(newSock), nil
return int(newSock), raddr, nil
}
// Create new socket for client and return fd
r.sockets[sock(newSock)] = newSocket(socket.protocol)
return int(newSock), nil
return int(newSock), raddr, nil
}
}
+36 -1
View File
@@ -1,6 +1,12 @@
package servo
import "machine"
import (
"machine"
"errors"
)
var ErrInvalidAngle = errors.New("servo: invalid angle")
// PWM is the interface necessary for controlling typical servo motors.
type PWM interface {
@@ -80,3 +86,32 @@ func (s Servo) SetMicroseconds(microseconds int16) {
value := uint64(s.pwm.Top()) * uint64(microseconds) / (pwmPeriod / 1000)
s.pwm.Set(s.channel, uint32(value))
}
// SetAngle sets the angle of the servo in degrees. The angle should be between
// 0 and 180, where 0 is the minimum angle and 180 is the maximum angle.
// This function should work for most servos, but if it doesn't work for yours
// you can use SetMicroseconds directly instead.
func (s Servo) SetAngle(angle int) error {
if angle < 0 || angle > 180 {
return ErrInvalidAngle
}
// 0° is 1000µs, 180° is 2000µs. See explanation in SetMicroseconds.
microseconds := angle*1000/180 + 1000
s.SetMicroseconds(int16(microseconds))
return nil
}
// SetAngleWithMicroseconds sets the angle of the servo in degrees. The angle should be between
// 0 and 180, where 0 is the minimum angle and 180 is the maximum angle.
// The high duration can be customized
// 0° is lowMicroseconds(us), 180° is highMicroseconds(us)
func (s Servo) SetAngleWithMicroseconds(angle int, lowMicroseconds, highMicroseconds int) error {
if angle < 0 || angle > 180 {
return ErrInvalidAngle
}
microseconds := lowMicroseconds + (highMicroseconds-lowMicroseconds)*angle/180
s.SetMicroseconds(int16(microseconds))
return nil
}
+7
View File
@@ -73,13 +73,16 @@ tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/thermistor/main.go
tinygo build -size short -o ./build/test.hex -target=circuitplay-bluefruit ./examples/tone
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/tm1637/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/touch/capacitive
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/fourwire/main.go
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/pyportal_touchpaint/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl53l1x/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl6180x/main.go
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840-sense ./examples/waveshare-epd/epd1in54/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd4in2/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/waveshare-epd/epd2in66b/main.go
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examples/ws2812
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
@@ -105,7 +108,9 @@ tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/clk
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/time/
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/timer/
tinygo build -size short -o ./build/test.hex -target=pico ./examples/qmi8658c/main.go
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/pcf8591/
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina260/main.go
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina219/main.go
tinygo build -size short -o ./build/test.hex -target=nucleo-l432kc ./examples/aht20/main.go
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/console/
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/i2csoft/adt7410/
@@ -129,6 +134,8 @@ tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ndir/ma
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ndir/main_ndir.go
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mpu9150/main.go
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
# network examples (espat)
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
# network examples (wifinina)
+5
View File
@@ -1,5 +1,7 @@
package ssd1306
import "tinygo.org/x/drivers"
// Registers
const (
Address = 0x3D
@@ -38,4 +40,7 @@ const (
EXTERNALVCC VccMode = 0x1
SWITCHCAPVCC VccMode = 0x2
NO_ROTATION = drivers.Rotation0
ROTATION_180 = drivers.Rotation180
)
+116 -8
View File
@@ -11,8 +11,16 @@ import (
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/pixel"
)
var (
errBufferSize = errors.New("invalid size buffer")
errOutOfRange = errors.New("out of screen range")
)
type ResetValue [2]byte
// Device wraps I2C or SPI connection.
type Device struct {
bus Buser
@@ -22,6 +30,9 @@ type Device struct {
bufferSize int16
vccState VccMode
canReset bool
resetCol ResetValue
resetPage ResetValue
rotation drivers.Rotation
}
// Config is the configuration for the display
@@ -30,6 +41,14 @@ type Config struct {
Height int16
VccState VccMode
Address uint16
// ResetCol and ResetPage are used to reset the screen to 0x0
// This is useful for some screens that have a different size than 128x64
// For example, the Thumby's screen is 72x40
// The default values are normally set automatically based on the size.
// If you're using a different size, you might need to set these values manually.
ResetCol ResetValue
ResetPage ResetValue
Rotation drivers.Rotation
}
type I2CBus struct {
@@ -79,6 +98,7 @@ func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
var zeroReset ResetValue
if cfg.Width != 0 {
d.width = cfg.Width
} else {
@@ -97,6 +117,16 @@ func (d *Device) Configure(cfg Config) {
} else {
d.vccState = SWITCHCAPVCC
}
if cfg.ResetCol != zeroReset {
d.resetCol = cfg.ResetCol
} else {
d.resetCol = ResetValue{0, uint8(d.width - 1)}
}
if cfg.ResetPage != zeroReset {
d.resetPage = cfg.ResetPage
} 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
@@ -120,8 +150,8 @@ func (d *Device) Configure(cfg Config) {
}
d.Command(MEMORYMODE)
d.Command(0x00)
d.Command(SEGREMAP | 0x1)
d.Command(COMSCANDEC)
d.SetRotation(cfg.Rotation)
if (d.width == 128 && d.height == 64) || (d.width == 64 && d.height == 48) { // 128x64 or 64x48
d.Command(SETCOMPINS)
@@ -186,11 +216,11 @@ func (d *Device) Display() error {
// Since we're printing the whole buffer, avoid resetting it in this case
if d.canReset {
d.Command(COLUMNADDR)
d.Command(0)
d.Command(uint8(d.width - 1))
d.Command(d.resetCol[0])
d.Command(d.resetCol[1])
d.Command(PAGEADDR)
d.Command(0)
d.Command(uint8(d.height/8) - 1)
d.Command(d.resetPage[0])
d.Command(d.resetPage[1])
}
return d.Tx(d.buffer, false)
@@ -223,8 +253,7 @@ 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 {
//return ErrBuffer
return errors.New("wrong size buffer")
return errBufferSize
}
for i := int16(0); i < d.bufferSize; i++ {
d.buffer[i] = buffer[i]
@@ -316,3 +345,82 @@ func (b *SPIBus) tx(data []byte, isCommand bool) error {
func (d *Device) Size() (w, h int16) {
return d.width, d.height
}
// DrawBitmap copies the bitmap to the screen at the given coordinates.
func (d *Device) DrawBitmap(x, y int16, bitmap pixel.Image[pixel.Monochrome]) error {
width, height := bitmap.Size()
if x < 0 || x+int16(width) > d.width || y < 0 || y+int16(height) > d.height {
return errOutOfRange
}
for i := 0; i < width; i++ {
for j := 0; j < height; j++ {
d.SetPixel(x+int16(i), y+int16(j), bitmap.Get(i, j).RGBA())
}
}
return nil
}
// Rotation returns the currently configured rotation.
func (d *Device) Rotation() drivers.Rotation {
return d.rotation
}
// SetRotation changes the rotation of the device (clock-wise).
func (d *Device) SetRotation(rotation drivers.Rotation) error {
d.rotation = rotation
switch d.rotation {
case drivers.Rotation0:
d.Command(SEGREMAP | 0x1) // Reverse horizontal mapping
d.Command(COMSCANDEC) // Reverse vertical mapping
case drivers.Rotation180:
d.Command(SEGREMAP) // Normal horizontal mapping
d.Command(COMSCANINC) // Normal vertical mapping
// nothing to do
default:
d.Command(SEGREMAP | 0x1) // Reverse horizontal mapping
d.Command(COMSCANDEC) // Reverse vertical mapping
}
return nil
}
// Set the sleep mode for this display. When sleeping, the panel uses a lot
// less power. The display won't show an image anymore, but the memory contents
// should be kept.
func (d *Device) Sleep(sleepEnabled bool) error {
if sleepEnabled {
d.Command(DISPLAYOFF)
} else {
d.Command(DISPLAYON)
}
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
}
+313
View File
@@ -0,0 +1,313 @@
package capacitive
import (
"machine"
"runtime/interrupt"
"time"
)
const (
// How often to measure.
// The Update function will wait until this amount of time has passed.
measurementFrequency = 200
minTimeBetweenMeasurements = time.Second / measurementFrequency
// How much to multiply values before averaging. A value higher than 1 will
// help to avoid integer rounding errors and may improve accuracy slightly.
oversampling = 8
// How many samples to use for the moving average.
movingAverageWindow = 16
// After how many samples should the touch sensor be recalibrated?
// This should be a power of two (for efficient division) and be a multiple
// of movingAverageWindow. Ideally it should cause a recalibration every 5s
// or so.
recalibrationSamples = 1024
)
type Array struct {
// Time when the last update finished. This is used to make sure we call
// Update() the expected number of times per second.
lastUpdate time.Time
// List of pins to measure each time.
pins []machine.Pin
// Raw values (non-smoothed) from the last read.
values []uint16
hasFirstMeasurement bool
// Static threshold. Zero if using a dynamic threshold.
staticThreshold uint16
// How long to measure.
measureCycles uint16
// Sensitivity (in promille) for the dynamic threshold.
sensitivity uint16
// Capacitance trackers for dynamic capacitance measurement.
trackers []capacitanceTracker
}
// Create a new array of pins to be used as touch sensors.
// The pins do not need to be initialized. The array is immediately ready to
// use.
//
// By default, NewArray configures a static threshold that is not very
// sensitive. If you want the touch inputs to be more sensitive, use
// SetDynamicThreshold.
func NewArray(pins []machine.Pin) *Array {
for _, pin := range pins {
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
pin.High()
}
array := &Array{
pins: pins,
values: make([]uint16, len(pins)),
measureCycles: uint16(machine.CPUFrequency() / 125000), // 1000 on the RP2040 (which is 125MHz)
lastUpdate: time.Now(),
}
// A threshold of 500 works well on the RP2040. Scale this number to
// something similar on other chips.
array.SetStaticThreshold(int(machine.CPUFrequency() / 250000))
return array
}
// Use a static threshold. This works well on simple touch surfaces where you'll
// directly touch the metal.
func (a *Array) SetStaticThreshold(threshold int) {
if threshold > 0xffff {
threshold = 0xffff
}
a.staticThreshold = uint16(threshold)
a.trackers = nil
}
// Use a dynamic threshold (as promille), that will calibrate automatically.
// This is needed when you want to be able to detect touches through a
// non-conducting surface for example. Something like 100‰ (10%) will probably
// work in many cases, though you may need to try different value to reliably
// detect touches.
func (a *Array) SetDynamicThreshold(sensitivity int) {
a.sensitivity = uint16(sensitivity)
a.staticThreshold = 0
a.trackers = make([]capacitanceTracker, len(a.pins))
}
// Measure all GPIO pins. This function must be called very often, ideally about
// 100-200 times per second (it will delay a bit when called more than 200 times
// per second).
func (a *Array) Update() {
// Wait until enough time has passed to charge all pins.
now := time.Now()
timeSinceLastUpdate := now.Sub(a.lastUpdate)
sleepTime := minTimeBetweenMeasurements - timeSinceLastUpdate
time.Sleep(sleepTime)
a.lastUpdate = now.Add(sleepTime) // should be ~equivalent to time.Now()
// Measure each pin in turn.
for i, pin := range a.pins {
// Interrupts must be disabled during measuring for accurate results.
mask := interrupt.Disable()
// Switch to input. This will stop the charging, and let it discharge
// through the resistor.
pin.Configure(machine.PinConfig{Mode: machine.PinInput})
// Wait for the pin to go low again.
// A longer duration means more capacitance, which means something is
// touching it (finger, banana, etc).
count := uint32(i)
for i := 0; i < int(a.measureCycles); i++ {
if !pin.Get() {
break
}
count++
}
interrupt.Restore(mask)
a.values[i] = uint16(count)
// Set the pin to high, to charge it for the next measurement.
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
pin.High()
}
// The first measurement tends to be slightly off (too low value) so ignore
// that one.
if !a.hasFirstMeasurement {
a.hasFirstMeasurement = true
return
}
for i := 0; i < len(a.trackers); i++ {
a.trackers[i].addValue(int(a.values[i]), int(a.sensitivity))
}
}
// Return the raw value of the given pin index of the most recent call to
// Update. This value is not smoothed in any way.
func (a *Array) RawValue(index int) int {
return int(a.values[index])
}
// Return the value from the moving average. This value is only available when a
// dynamic threshold has been set, it will panic otherwise.
func (a *Array) SmoothedValue(index int) int {
return int(a.trackers[index].avg) / oversampling
}
// Return whether the given pin index is currently being touched.
func (a *Array) Touching(index int) bool {
if a.staticThreshold != 0 {
// Using a static threshold.
return a.values[index] > a.staticThreshold
}
return a.trackers[index].touching
}
// Separate object to store calibration data and track capacitance over time.
type capacitanceTracker struct {
recentValues [movingAverageWindow]uint16
sum uint32
avg uint16
baseline uint16
noise uint16
valueCount uint8
touching bool
recalibrationCount uint8
recalibrationPrevAvg uint16
recalibrationNoiseSum int32
recalibrationSum uint32
}
func (ct *capacitanceTracker) addValue(value int, sensitivity int) {
// Maybe increase the resolution slightly by oversampling. This should
// increase the resolution a little bit after averaging and should reduce
// rounding errors.
// Typical input values on the RP2040 are 100-200 (or up to 1000 or so when
// touching the metal) so multiplying by 4-8 should be fine. Other chips
// generally have much lower values.
value *= oversampling
if value > 0xffff {
value = 0xffff // unlikely, but make sure we don't overflow
}
// This does a number of things at the same time:
// * Add the new value to the recentValues array.
// * Calculate the moving sum (and average) of recentValues using a
// recursive moving average algorithm:
// https://www.dspguide.com/ch15/5.htm
ptr := &ct.recentValues[ct.valueCount%movingAverageWindow]
ct.sum -= uint32(*ptr)
ct.sum += uint32(value)
ct.avg = uint16(ct.sum / movingAverageWindow)
*ptr = uint16(value)
ct.valueCount++
// Do an initial calibration once the first values have been read.
if ct.baseline == 0 && ct.valueCount == movingAverageWindow {
ct.baseline = ct.avg
// Calculate initial noise as an average absolute deviation:
// https://en.wikipedia.org/wiki/Average_absolute_deviation
// This is a quick and imprecise way to find the noise, better noise
// detection happens during recalibration.
var diffSum uint32
for _, sample := range ct.recentValues {
diff := int(ct.avg) - int(sample)
if diff < 0 {
diff = -diff
}
diffSum += uint32(diff)
}
ct.noise = uint16(diffSum / (movingAverageWindow / 2))
}
// Now determine whether the touch pad is being touched.
if ct.baseline == 0 {
// Not yet calibrated.
ct.touching = false
return
}
// Calculate the threshold.
// Divide by 65536 (instead of 65500) to avoid a potentially expensive
// division while still being close enough.
threshold := (uint32(ct.baseline) * uint32(sensitivity+1000) * 65) / 65536
// Add noise to the threshold, to avoid toggling quickly. This mainly
// filters out mains noise.
threshold += uint32(ct.noise)
// Implement some hysteresis: if the touch pad was previously touched, lower
// the threshold a little to avoid bouncing effects.
// TODO: let this hysteresis depend on the amount of noise.
if ct.touching {
threshold = (threshold*3 + uint32(ct.baseline)) / 4 // lower the threshold by 25%
}
// Is the pad being touched?
ct.touching = uint32(ct.avg) > threshold
// Do a recalibration after the sensor hasn't been touched for ~5s, to
// account for drift over time (humidity etc).
if ct.touching {
// Reset calibration (start from zero).
ct.recalibrationCount = 0
ct.recalibrationSum = 0
ct.recalibrationNoiseSum = 0
} else {
// Add the last batch of samples to the sum.
if ct.valueCount%movingAverageWindow == 0 {
ct.recalibrationCount++
// Wait a few cycles before starting data collection for
// calibration.
cycle := int(ct.recalibrationCount) - 3
if cycle < 0 {
// Store the previous average, to calculate the noise value.
ct.recalibrationPrevAvg = ct.avg
} else if cycle >= 0 {
// Collect data for recalibration.
ct.recalibrationSum += ct.sum
// Add difference between two (averaged) samples as a measure of
// the noise.
diff := int32(ct.recalibrationPrevAvg) - int32(ct.avg)
if diff < 0 {
diff = -diff
}
ct.recalibrationNoiseSum += diff
ct.recalibrationPrevAvg = ct.avg
}
// Do the recalibration after enough samples have been collected.
// Note: the noise is basically the average of absolute differences
// between two averaging windows. I don't know whether this
// algorithm has a name, but it seems to work here to detect the
// amount of noise.
const totalRecalibrationCount = recalibrationSamples / movingAverageWindow
if cycle == totalRecalibrationCount {
ct.baseline = uint16(ct.recalibrationSum / recalibrationSamples)
ct.noise = uint16(ct.recalibrationNoiseSum / (totalRecalibrationCount / 2))
ct.recalibrationCount = 0
ct.recalibrationSum = 0
ct.recalibrationNoiseSum = 0
}
}
}
}
+30
View File
@@ -0,0 +1,30 @@
package uc8151
// LUTType is the look-up table for the display
type LUTType [42]uint8
type LUTSet struct {
VCOM LUTType
WW LUTType
BW LUTType
WB LUTType
BB LUTType
}
func (lut *LUTType) Clear() {
for i := range lut {
lut[i] = 0
}
}
func (lut *LUTType) SetRow(row int, pat uint8, dur [4]uint8, rep uint8) error {
index := row * 6
lut[index] = pat
lut[index+1] = dur[0]
lut[index+2] = dur[1]
lut[index+3] = dur[2]
lut[index+4] = dur[3]
lut[index+5] = rep
return nil
}
+12 -7
View File
@@ -1,5 +1,7 @@
package uc8151
import "tinygo.org/x/drivers"
// Registers
const (
// Display resolution
@@ -141,13 +143,16 @@ const (
HZ_100 = 0b00111010
HZ_200 = 0b00111001
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
ROTATION_180 Rotation = 2
ROTATION_270 Rotation = 3
// deprecated constants, just here for backward compatibility.
NO_ROTATION = drivers.Rotation0
ROTATION_90 = drivers.Rotation90
ROTATION_180 = drivers.Rotation180
ROTATION_270 = drivers.Rotation270
DEFAULT Speed = 0
MEDIUM Speed = 1
FAST Speed = 2
TURBO Speed = 3
SLOW Speed = 1
MEDIUM Speed = 2
FAST Speed = 3
FASTER Speed = 4
TURBO Speed = 5
)
+224 -349
View File
@@ -1,6 +1,7 @@
// Package uc8151 implements a driver for e-ink displays controlled by UC8151
//
// Inspired by https://github.com/pimoroni/pimoroni-pico/blob/main/drivers/uc8151/uc8151.cpp
// Additional inspiration from https://github.com/antirez/uc8151_micropython
// Datasheet: https://www.buydisplay.com/download/ic/UC8151C.pdf
package uc8151 // import "tinygo.org/x/drivers/uc8151"
@@ -11,35 +12,43 @@ import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/pixel"
)
var (
errOutOfRange = errors.New("out of screen range")
)
type Config struct {
Width int16
Height int16
Rotation Rotation // Rotation is clock-wise
Speed Speed // Value from DEFAULT, MEDIUM, FAST, TURBO
Blocking bool
Width int16
Height int16
Rotation drivers.Rotation // Rotation is clock-wise
Speed Speed // Value from DEFAULT, SLOW, MEDIUM, FAST, FASTER, TURBO
Blocking bool // block on calls to display or return immediately
FlickerFree bool // if we should avoid flickering
UpdateAfter int // if we are using flicker-free mode, how often we should update the screen
}
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
width int16
height int16
buffer []uint8
bufferLength uint32
rotation Rotation
speed Speed
blocking bool
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
width int16
height int16
buffer []uint8
bufferLength uint32
rotation drivers.Rotation
speed Speed
blocking bool
flickerFree bool
updateCount, updateAfter int
}
type Rotation uint8
type Speed uint8
// New returns a new epd2in13x driver. Pass in a fully configured SPI bus.
// New returns a new uc8151 driver. Pass in a fully configured SPI bus.
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
@@ -69,6 +78,8 @@ func (d *Device) Configure(cfg Config) {
d.rotation = cfg.Rotation
d.speed = cfg.Speed
d.blocking = cfg.Blocking
d.flickerFree = cfg.FlickerFree
d.updateAfter = cfg.UpdateAfter
d.bufferLength = (uint32(d.width) * uint32(d.height)) / 8
d.buffer = make([]uint8, d.bufferLength)
for i := uint32(0); i < d.bufferLength; i++ {
@@ -84,14 +95,14 @@ func (d *Device) Configure(cfg Config) {
d.SendData(RES_128x296 | LUT_REG | FORMAT_BW | SHIFT_RIGHT | BOOSTER_ON | RESET_NONE | SCAN_UP)
}
d.SetLUT(d.speed)
d.SetLUT(d.speed, d.flickerFree)
d.SendCommand(PWR)
d.SendData(VDS_INTERNAL | VDG_INTERNAL)
d.SendData(VCOM_VG | VGHL_16V)
d.SendData(0x2B)
d.SendData(0x2B)
d.SendData(0x2B)
d.SendData(VCOM_VD | VGHL_16V)
d.SendData(0b100110) // +10v VDH
d.SendData(0b100110) // -10v VDL
d.SendData(0b000011) // VDHR default (For red pixels, not used here)
d.SendCommand(PON)
d.WaitUntilIdle()
@@ -102,7 +113,7 @@ func (d *Device) Configure(cfg Config) {
d.SendData(START_10MS | STRENGTH_3 | OFF_6_58US)
d.SendCommand(PFS)
d.SendData(FRAMES_1)
d.SendData(FRAMES_4)
d.SendCommand(TSE)
d.SendData(TEMP_INTERNAL | OFFSET_0)
@@ -111,14 +122,13 @@ func (d *Device) Configure(cfg Config) {
d.SendData(0x22)
d.SendCommand(CDI)
d.SendData(0x4C) // 4C //5C
d.SendData(0b11_00_1100)
d.SendCommand(PLL)
d.SendData(HZ_100)
d.SendCommand(POF)
d.WaitUntilIdle()
}
// Reset resets the device
@@ -135,31 +145,30 @@ func (d *Device) PowerOff() {
d.SendCommand(POF)
}
// PowerOn power on the device
func (d *Device) PowerOn() {
d.SendCommand(PON)
}
// SendCommand sends a command to the display
func (d *Device) SendCommand(command uint8) {
d.sendDataCommand(true, command)
d.dc.Low()
d.cs.Low()
d.bus.Transfer(command)
d.cs.High()
}
// SendData sends a data byte to the display
func (d *Device) SendData(data uint8) {
d.sendDataCommand(false, data)
}
// sendDataCommand sends image data or a command to the screen
func (d *Device) sendDataCommand(isCommand bool, data uint8) {
if isCommand {
d.dc.Low()
} else {
d.dc.High()
}
func (d *Device) SendData(data ...uint8) {
d.dc.High()
d.cs.Low()
d.bus.Transfer(data)
d.bus.Tx(data, nil)
d.cs.High()
}
// SetPixel modifies the internal buffer in a single pixel.
// The display have 2 colors: black and white
// We use RGBA(0,0,0, 255) as white (transparent)
// We use RGBA(0, 0, 0) as white (transparent)
// Anything else as black
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
x, y = d.xy(x, y)
@@ -168,31 +177,60 @@ func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
return
}
byteIndex := x/8 + y*(d.width/8)
if c.R == 0 && c.G == 0 && c.B == 0 { // TRANSPARENT / WHITE
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
} else { // WHITE / EMPTY
if c.R != 0 || c.G != 0 || c.B != 0 {
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
} else {
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
}
}
// DrawBitmap copies the bitmap to the screen at the given coordinates.
func (d *Device) DrawBitmap(x, y int16, bitmap pixel.Image[pixel.Monochrome]) error {
dw, dh := d.Size()
bw, bh := bitmap.Size()
if x < 0 || x+int16(bw) > dw || y < 0 || y+int16(bh) > dh {
return errOutOfRange
}
for i := 0; i < bw; i++ {
for j := 0; j < bh; j++ {
d.SetPixel(x+int16(i), y+int16(j), bitmap.Get(i, j).RGBA())
}
}
return nil
}
// Display sends the buffer to the screen.
func (d *Device) Display() error {
if d.blocking {
d.WaitUntilIdle()
}
d.SendCommand(PON)
if d.flickerFree && d.updateAfter != 0 && d.updateCount%d.updateAfter == 0 {
// we need full refresh here
d.SetLUT(MEDIUM, false)
} else {
d.SetLUT(d.speed, d.flickerFree)
}
d.updateCount++
d.PowerOn()
d.SendCommand(PTOU)
d.SendCommand(DTM2)
for i := uint32(0); i < d.bufferLength; i++ {
d.SendData(d.buffer[i])
}
d.SendData(d.buffer...)
d.SendCommand(DSP)
d.SendCommand(DRF)
d.SetLUT(d.speed, d.flickerFree)
if d.blocking {
d.WaitUntilIdle()
d.PowerOff()
}
return nil
}
@@ -208,13 +246,14 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
if x < 0 || y < 0 || x >= d.width || y >= d.height || width < 0 || height < 0 {
return errors.New("wrong rectangle")
}
if d.rotation == ROTATION_90 {
switch d.rotation {
case drivers.Rotation0:
width, height = height, width
x -= width
} else if d.rotation == ROTATION_180 {
case drivers.Rotation90:
x -= width - 1
y -= height - 1
} else if d.rotation == ROTATION_270 {
case drivers.Rotation180:
width, height = height, width
y -= height
}
@@ -262,6 +301,12 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
// ClearDisplay erases the device SRAM
func (d *Device) ClearDisplay() {
ff := d.flickerFree
d.flickerFree = false
defer func() {
d.flickerFree = ff
}()
d.ClearBuffer()
d.Display()
}
@@ -269,7 +314,7 @@ func (d *Device) ClearDisplay() {
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for !d.busy.Get() {
time.Sleep(100 * time.Millisecond)
time.Sleep(10 * time.Millisecond)
}
}
@@ -287,15 +332,32 @@ func (d *Device) ClearBuffer() {
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
if d.rotation == ROTATION_90 || d.rotation == ROTATION_270 {
if d.rotation == drivers.Rotation90 || d.rotation == drivers.Rotation270 {
return d.height, d.width
}
return d.width, d.height
}
// Rotation returns the currently configured rotation.
func (d *Device) Rotation() drivers.Rotation {
return d.rotation
}
// SetRotation changes the rotation (clock-wise) of the device
func (d *Device) SetRotation(rotation Rotation) {
func (d *Device) SetRotation(rotation drivers.Rotation) error {
d.rotation = rotation
return nil
}
// Set the sleep mode for this display.
func (d *Device) Sleep(sleepEnabled bool) error {
if sleepEnabled {
d.PowerOff()
return nil
}
d.PowerOn()
return nil
}
// SetBlocking changes the blocking flag of the device
@@ -303,16 +365,16 @@ func (d *Device) SetBlocking(blocking bool) {
d.blocking = blocking
}
// xy chages the coordinates according to the rotation
// xy changes the coordinates according to the rotation
func (d *Device) xy(x, y int16) (int16, int16) {
switch d.rotation {
case NO_ROTATION:
case drivers.Rotation0:
return x, y
case ROTATION_90:
case drivers.Rotation90:
return d.width - y - 1, x
case ROTATION_180:
case drivers.Rotation180:
return d.width - x - 1, d.height - y - 1
case ROTATION_270:
case drivers.Rotation270:
return y, d.height - x - 1
}
return x, y
@@ -338,296 +400,109 @@ func (d *Device) Invert(invert bool) {
}
}
// SetLUT sets the look up tables for full or partial updates
func (d *Device) SetLUT(speed Speed) {
switch speed {
case MEDIUM:
var lut = [44]uint8{
0x00, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x23, 0x23, 0x00, 0x00, 0x02,
0x00, 0x16, 0x16, 0x0d, 0x00, 0x01,
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,
}
d.SendCommand(LUT_VCOM)
for i := 0; i < 44; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
// SetLUT sets the look up tables for full or partial updates based on
// the speed and flicker-free mode.
// Based on code from https://github.com/antirez/uc8151_micropython
func (d *Device) SetLUT(speed Speed, flickerFree bool) error {
var lut LUTSet
lut = [44]uint8{
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
break
case FAST:
var lut = [44]uint8{
0x00, 0x04, 0x04, 0x07, 0x00, 0x01,
0x00, 0x0c, 0x0c, 0x00, 0x00, 0x02,
0x00, 0x04, 0x04, 0x07, 0x00, 0x02,
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,
}
d.SendCommand(LUT_VCOM)
for i := 0; i < 44; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x04, 0x04, 0x07, 0x00, 0x01,
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
0xa8, 0x04, 0x04, 0x07, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x04, 0x04, 0x07, 0x00, 0x01,
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
0xa8, 0x04, 0x04, 0x07, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x04, 0x04, 0x07, 0x00, 0x01,
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
0x54, 0x04, 0x04, 0x07, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x04, 0x04, 0x07, 0x00, 0x01,
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
0x54, 0x04, 0x04, 0x07, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
break
case TURBO:
var lut = [44]uint8{
0x00, 0x01, 0x01, 0x02, 0x00, 0x01,
0x00, 0x02, 0x02, 0x00, 0x00, 0x02,
0x00, 0x02, 0x02, 0x03, 0x00, 0x02,
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,
}
d.SendCommand(LUT_VCOM)
for i := 0; i < 44; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x01, 0x01, 0x02, 0x00, 0x01,
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
0xa8, 0x02, 0x02, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x01, 0x01, 0x02, 0x00, 0x01,
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
0xa8, 0x02, 0x02, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x01, 0x01, 0x02, 0x00, 0x01,
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
0x54, 0x02, 0x02, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x01, 0x01, 0x02, 0x00, 0x01,
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
0x54, 0x02, 0x02, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
break
default:
var lut = [44]uint8{
0x00, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0x00, 0x64, 0x64, 0x37, 0x00, 0x01,
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,
}
d.SendCommand(LUT_VCOM)
for i := 0; i < 44; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
break
// Num. of frames for single direction change.
period := 64
p := uint8(period / (2 ^ (int(speed) - 1)))
if p < 1 {
p = 1
}
// Num. of frames for back-and-forth change.
hperiod := period % 2
hp := uint8(hperiod / (2 ^ (int(speed) - 1)))
if hp < 1 {
hp = 1
}
if speed < FAST && !flickerFree {
// For low speed everything is charge-neutral, even WB/BW.
// Phase 1: long go-inverted-color.
lut.VCOM.SetRow(0, 0x00, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
lut.BW.SetRow(0, 0b01_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
lut.WB.SetRow(0, 0b10_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
// Phase 2: short ping/pong.
lut.VCOM.SetRow(1, 0x00, [4]uint8{hp, hp, 0x00, 0x00}, 0x02)
lut.BW.SetRow(1, 0b10_01_0000, [4]uint8{hp, hp, 0x00, 0x00}, 0x01)
lut.WB.SetRow(1, 0b01_10_0000, [4]uint8{hp, hp, 0x00, 0x00}, 0x01)
// Phase 3: long go-target-color.
lut.VCOM.SetRow(2, 0x00, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
lut.BW.SetRow(2, 0b10_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
lut.WB.SetRow(2, 0b01_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
// For this speed, we use the same LUTs for WW/BB as well.
copy(lut.WW[:], lut.BW[:])
copy(lut.BB[:], lut.WB[:])
} else {
// Speed >= FAST
// For greater than 3 we use non charge-neutral LUTs for WB/BW
// since the inpulse is short and it gets reversed when the
// pixel changes color, so that's not a problem for the display,
// however we still need to use charge-neutral LUTs for WW/BB.
lut.VCOM.SetRow(0, 0x00, [4]uint8{p, p, p, p}, 0x01)
lut.BW.SetRow(0, 0b10_00_00_00, [4]uint8{p * 4, 0x00, 0x00, 0x00}, 0x01)
lut.WB.SetRow(0, 0b01_00_00_00, [4]uint8{p * 4, 0x00, 0x00, 0x00}, 0x01)
lut.WW.SetRow(0, 0b01_10_00_00, [4]uint8{p * 2, p * 2, 0x00, 0x00}, 0x01)
lut.BB.SetRow(0, 0b10_01_00_00, [4]uint8{p * 2, p * 2, 0x00, 0x00}, 0x01)
}
if flickerFree {
// If no flickering mode is enabled, we use an empty
// waveform BB and WW. The screen will need to be periodically fully refreshed.
lut.WW.Clear()
lut.BB.Clear()
}
d.SendCommand(LUT_VCOM)
d.SendData(append(lut.VCOM[:], []uint8{0, 0}...)...)
d.SendCommand(LUT_BW)
d.SendData(lut.BW[:]...)
d.SendCommand(LUT_WB)
d.SendData(lut.WB[:]...)
d.SendCommand(LUT_WW)
d.SendData(lut.WW[:]...)
d.SendCommand(LUT_BB)
d.SendData(lut.BB[:]...)
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
}
+1 -1
View File
@@ -2,4 +2,4 @@ package drivers
// Version returns a user-readable string showing the version of the drivers package for support purposes.
// Update this value before release of new version of software.
const Version = "0.26.0"
const Version = "0.29.0"
+424
View File
@@ -0,0 +1,424 @@
// Package epd1in54 implements a driver for Waveshare 1.54in black and white e-paper device.
//
// Derived from:
//
// https://github.com/tinygo-org/drivers/tree/master/waveshare-epd
// https://github.com/waveshare/e-Paper/blob/master/Arduino/epd1in54_V2/epd1in54_V2.cpp
//
// Datasheet: https://www.waveshare.com/w/upload/e/e5/1.54inch_e-paper_V2_Datasheet.pdf
package epd1in54
import (
"image/color"
"machine"
"time"
)
type Config struct {
Width int16
Height int16
LogicalWidth int16
Rotation Rotation
}
type Device struct {
bus machine.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
buffer []uint8
rotation Rotation
}
type Rotation uint8
var fullRefresh = [159]uint8{
0x80, 0x48, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x40, 0x48, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x80, 0x48, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x40, 0x48, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x8, 0x1, 0x0, 0x8, 0x1, 0x0, 0x2,
0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
0x22, 0x17, 0x41, 0x0, 0x32, 0x20,
}
var partialRefresh = [159]uint8{
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
0x02, 0x17, 0x41, 0xB0, 0x32, 0x28,
}
// New returns a new epd1in54 driver. Pass in a fully configured SPI bus.
func New(bus machine.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
return Device{
buffer: make([]uint8, (uint32(Width)*uint32(Height))/8),
bus: bus,
cs: csPin,
dc: dcPin,
rst: rstPin,
busy: busyPin,
}
}
func (d *Device) LDirInit(cfg Config) {
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
d.bus.Configure(machine.SPIConfig{
Frequency: 2000000,
Mode: 0,
LSBFirst: false,
})
d.Reset()
d.WaitUntilIdle()
d.SendCommand(0x12)
d.WaitUntilIdle()
d.SendCommand(0x01)
d.SendData(0xC7)
d.SendData(0x00)
d.SendData(0x00)
d.SendCommand(0x11)
d.SendData(0x03)
d.SendCommand(0x44)
/* x point must be the multiple of 8 or the last 3 bits will be ignored */
d.SendData((0 >> 3) & 0xFF)
d.SendData((199 >> 3) & 0xFF)
d.SendCommand(0x45)
d.SendData(0 & 0xFF)
d.SendData((0 >> 8) & 0xFF)
d.SendData(199 & 0xFF)
d.SendData((199 >> 8) & 0xFF)
d.SendCommand(0x3C)
d.SendData(0x01)
d.SendCommand(0x18)
d.SendData(0x80)
d.SendCommand(0x22)
d.SendData(0xB1)
d.SendCommand(0x20)
d.SendCommand(0x4E)
d.SendData(0x00)
d.SendCommand(0x4F)
d.SendData(0xC7)
d.SendData(0x00)
d.WaitUntilIdle()
d.setLUT(fullRefresh)
}
func (d *Device) HDirInit(cfg Config) {
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
d.bus.Configure(machine.SPIConfig{
Frequency: 2000000,
Mode: 0,
LSBFirst: false,
})
d.Reset()
d.WaitUntilIdle()
d.SendCommand(0x12)
d.WaitUntilIdle()
d.SendCommand(0x01)
d.SendData(0xC7)
d.SendData(0x00)
d.SendData(0x01)
d.SendCommand(0x11)
d.SendData(0x01)
d.SendCommand(0x44)
d.SendData(0x00)
d.SendData(0x18)
d.SendCommand(0x45)
d.SendData(0xC7)
d.SendData(0x00)
d.SendData(0x00)
d.SendData(0x00)
d.SendCommand(0x3C)
d.SendData(0x01)
d.SendCommand(0x18)
d.SendData(0x80)
d.SendCommand(0x22)
d.SendData(0xB1)
d.SendCommand(0x20)
d.SendCommand(0x4E)
d.SendData(0x00)
d.SendCommand(0x4F)
d.SendData(0xC7)
d.SendData(0x00)
d.WaitUntilIdle()
d.setLUT(fullRefresh)
}
func (d *Device) setLUT(lut [159]uint8) {
d.SendCommand(0x32)
for i := 0; i < 153; i++ {
d.SendData(lut[i])
}
d.WaitUntilIdle()
d.SendCommand(0x3F)
d.SendData(lut[153])
d.SendCommand(0x03)
d.SendData(lut[154])
d.SendCommand(0x04)
d.SendData(lut[155])
d.SendData(lut[156])
d.SendData(lut[157])
d.SendCommand(0x2C)
d.SendData(lut[158])
}
// Reset resets the display.
func (d *Device) Reset() {
d.rst.High()
time.Sleep(20 * time.Millisecond)
d.rst.Low()
time.Sleep(5 * time.Millisecond)
d.rst.High()
time.Sleep(20 * time.Millisecond)
}
// SendCommand sends a command to the display
func (d *Device) SendCommand(command uint8) {
d.sendDataCommand(true, command)
}
// SendData sends a data byte to the display
func (d *Device) SendData(data uint8) {
d.sendDataCommand(false, data)
}
// sendDataCommand sends image data or a command to the screen
func (d *Device) sendDataCommand(isCommand bool, data uint8) {
if isCommand {
d.dc.Low()
} else {
d.dc.High()
}
d.cs.Low()
d.bus.Transfer(data)
d.cs.High()
}
// SetPixel modifies the internal buffer in a single pixel.
// The display have 2 colors: black and white
// We use RGBA(0,0,0, 255) as white (transparent)
// Anything else as black
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
x, y = d.xy(x, y)
if x < 0 || x >= Width || y < 0 || y >= Height {
return
}
byteIndex := (uint32(x) + uint32(y)*uint32(Width)) / 8
if c.R == 0 && c.G == 0 && c.B == 0 { // TRANSPARENT / WHITE
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
} else { // WHITE / EMPTY
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
}
}
func (d *Device) DisplayImage(image []uint8) {
var w, h int
if Width%8 == 0 {
w = int(Width / 8)
} else {
w = int(Width/8 + 1)
}
h = int(Height)
d.SendCommand(0x24)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
d.SendData(image[i+j*w])
}
}
d.SendCommand(0x26)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
d.SendData(image[i+j*w])
}
}
d.displayFrame()
}
func (d *Device) Display() error {
var w, h int
if Width%8 == 0 {
w = int(Width / 8)
} else {
w = int(Width/8 + 1)
}
h = int(Height)
d.SendCommand(0x24)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
x := i + j*w
d.SendData(d.buffer[x])
}
}
d.SendCommand(0x26)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
x := i + j*w
d.SendData(d.buffer[x])
}
}
d.displayFrame()
return nil
}
func (d *Device) displayFrame() {
d.SendCommand(0x22)
d.SendData(0xC7)
d.SendCommand(0x20)
d.WaitUntilIdle()
}
func (d *Device) Clear() {
var w, h int
if Width%8 == 0 {
w = int(Width / 8)
} else {
w = int(Width/8 + 1)
}
h = int(Height)
d.SendCommand(0x24)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
d.SendData(0xff)
}
}
d.SendCommand(0x26)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
d.SendData(0xff)
}
}
d.displayFrame()
}
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for d.busy.Get() {
time.Sleep(100 * time.Millisecond)
}
time.Sleep(200 * time.Millisecond)
}
// IsBusy returns the busy status of the display
func (d *Device) IsBusy() bool {
return d.busy.Get()
}
// ClearBuffer sets the buffer to 0xFF (white)
func (d *Device) ClearBuffer() {
for i := 0; i < len(d.buffer); i++ {
d.buffer[i] = 0xFF
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
if d.rotation == ROTATION_90 || d.rotation == ROTATION_270 {
return Height, Width
}
return Width, Height
}
// SetRotation changes the rotation (clock-wise) of the device
func (d *Device) SetRotation(rotation Rotation) {
d.rotation = rotation
}
// xy chages the coordinates according to the rotation
func (d *Device) xy(x, y int16) (int16, int16) {
switch d.rotation {
case NO_ROTATION:
return x, y
case ROTATION_90:
return Width - y - 1, x
case ROTATION_180:
return Width - x - 1, Height - y - 1
case ROTATION_270:
return y, Height - x - 1
}
return x, y
}
func (d *Device) Sleep() {
d.SendCommand(0x10)
d.SendData(0x01)
time.Sleep(200 * time.Millisecond)
d.rst.Low()
}
+52
View File
@@ -0,0 +1,52 @@
package epd1in54
// Derived from https://github.com/waveshare/e-Paper/blob/master/Arduino/epd4in2/epd4in2.h
const (
Width = 200
Height = 200
PANEL_SETTING = 0x00
POWER_SETTING = 0x01
POWER_OFF = 0x02
POWER_OFF_SEQUENCE_SETTING = 0x03
POWER_ON = 0x04
POWER_ON_MEASURE = 0x05
BOOSTER_SOFT_START = 0x06
DEEP_SLEEP = 0x07
DATA_START_TRANSMISSION_1 = 0x10
DATA_STOP = 0x11
DISPLAY_REFRESH = 0x12
DATA_START_TRANSMISSION_2 = 0x13
LUT_FOR_VCOM = 0x20
LUT_WHITE_TO_WHITE = 0x21
LUT_BLACK_TO_WHITE = 0x22
LUT_WHITE_TO_BLACK = 0x23
LUT_BLACK_TO_BLACK = 0x24
PLL_CONTROL = 0x30
TEMPERATURE_SENSOR_COMMAND = 0x40
TEMPERATURE_SENSOR_SELECTION = 0x41
TEMPERATURE_SENSOR_WRITE = 0x42
TEMPERATURE_SENSOR_READ = 0x43
VCOM_AND_DATA_INTERVAL_SETTING = 0x50
LOW_POWER_DETECTION = 0x51
TCON_SETTING = 0x60
RESOLUTION_SETTING = 0x61
GSST_SETTING = 0x65
GET_STATUS = 0x71
AUTO_MEASUREMENT_VCOM = 0x80
READ_VCOM_VALUE = 0x81
VCM_DC_SETTING = 0x82
PARTIAL_WINDOW = 0x90
PARTIAL_IN = 0x91
PARTIAL_OUT = 0x92
PROGRAM_MODE = 0xA0
ACTIVE_PROGRAMMING = 0xA1
READ_OTP = 0xA2
POWER_SAVING = 0xE3
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1
ROTATION_180 Rotation = 2
ROTATION_270 Rotation = 3
)
+287
View File
@@ -0,0 +1,287 @@
// Package epd2in66b implements a driver for the Waveshare 2.66inch E-Paper E-Ink Display Module (B)
// for Raspberry Pi Pico, 296×152, Red / Black / White
// Datasheet: https://files.waveshare.com/upload/e/ec/2.66inch-e-paper-b-specification.pdf
package epd2in66b
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
)
const (
displayWidth = 152
displayHeight = 296
)
const Baudrate = 4_000_000 // 4 MHz
type Config struct {
ResetPin machine.Pin
DataPin machine.Pin
ChipSelectPin machine.Pin
BusyPin machine.Pin
}
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
blackBuffer []byte
redBuffer []byte
}
// New allocates a new device.
// The bus is expected to be configured and ready for use.
func New(bus drivers.SPI) Device {
pixelCount := displayWidth * displayHeight
bufLen := pixelCount / 8
return Device{
bus: bus,
blackBuffer: make([]byte, bufLen),
redBuffer: make([]byte, bufLen),
}
}
// Configure configures the device and its pins.
func (d *Device) Configure(c Config) error {
d.cs = c.ChipSelectPin
d.dc = c.DataPin
d.rst = c.ResetPin
d.busy = c.BusyPin
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
return nil
}
func (d *Device) Size() (x, y int16) {
return displayWidth, displayHeight
}
// SetPixel modifies the internal buffer in a single pixel.
// The display has 3 colors: red, black and white
//
// - white = RGBA(255,255,255, 1-255)
// - red = RGBA(1-255,0,0,1-255)
// - Anything else as black
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
if x < 0 || x >= displayWidth || y < 0 || y >= displayHeight {
return
}
bytePos, bitPos := pos(x, y, displayWidth)
if c.R == 0xff && c.G == 0xff && c.B == 0xff && c.A > 0 { // white
set(d.blackBuffer, bytePos, bitPos)
unset(d.redBuffer, bytePos, bitPos)
} else if c.R != 0 && c.G == 0 && c.B == 0 && c.A > 0 { // red-ish
set(d.blackBuffer, bytePos, bitPos)
set(d.redBuffer, bytePos, bitPos)
} else { // black or other
unset(d.blackBuffer, bytePos, bitPos)
unset(d.redBuffer, bytePos, bitPos)
}
}
func set(buf []byte, bytePos, bitPos int) {
buf[bytePos] |= 0x1 << bitPos
}
func unset(buf []byte, bytePos, bitPos int) {
buf[bytePos] &^= 0x1 << bitPos
}
func pos(x, y, stride int16) (bytePos int, bitPos int) {
p := int(x) + int(y)*int(stride)
bytePos = p / 8
// reverse bit position as it is reversed on the device's buffer
bitPos = 7 - p%8
return bytePos, bitPos
}
func (d *Device) Display() error {
// Write RAM (Black White) / RAM 0x24
// 1 == white, 0 == black
if err := d.sendCommandByte(0x24); err != nil {
return err
}
if err := d.sendData(d.blackBuffer); err != nil {
return err
}
// Write RAM (RED) / RAM 0x26)
// 0 == blank, 1 == red
if err := d.sendCommandByte(0x26); err != nil {
return err
}
if err := d.sendData(d.redBuffer); err != nil {
return err
}
return d.turnOnDisplay()
}
func (d *Device) ClearBuffer() {
fill(d.redBuffer, 0x00)
fill(d.blackBuffer, 0xff)
}
func (d *Device) turnOnDisplay() error {
// also documented as 'Master Activation'
if err := d.sendCommandByte(0x20); err != nil {
return err
}
d.WaitUntilIdle()
return nil
}
func (d *Device) Reset() error {
d.hwReset()
d.WaitUntilIdle()
// soft reset & set defaults
if err := d.sendCommandByte(0x12); err != nil {
return err
}
d.WaitUntilIdle()
// data entry mode setting
if err := d.sendCommandSequence([]byte{0x11, 0x03}); err != nil {
return err
}
if err := d.setWindow(0, displayWidth-1, 0, displayHeight-1); err != nil {
return err
}
// display update control 1 - resolution setting
if err := d.sendCommandSequence([]byte{0x21, 0x00, 0x80}); err != nil {
return err
}
if err := d.setCursor(0, 0); err != nil {
return err
}
d.WaitUntilIdle()
return nil
}
func (d *Device) setCursor(x, y uint16) error {
// Set RAM X address counter
if err := d.sendCommandSequence([]byte{0x4e, byte(x & 0x1f)}); err != nil {
return err
}
// Set RAM Y address counter
yLo := byte(y)
yHi := byte(y>>8) & 0x1
if err := d.sendCommandSequence([]byte{0x4f, yLo, yHi}); err != nil {
return err
}
return nil
}
func (d *Device) hwReset() {
d.rst.High()
time.Sleep(50 * time.Millisecond)
d.rst.Low()
time.Sleep(2 * time.Millisecond)
d.rst.High()
time.Sleep(50 * time.Millisecond)
}
func (d *Device) setWindow(xstart, xend, ystart, yend int16) error {
// set RAM X-address start / end position
d1 := byte((xstart >> 3) & 0x1f)
d2 := byte((xend >> 3) & 0x1f)
if err := d.sendCommandSequence([]byte{0x44, d1, d2}); err != nil {
return err
}
// set RAM Y-address start / end position
ystartLo := byte(ystart)
ystartHi := byte(ystart>>8) & 0x1
yendLo := byte(yend)
yendHi := byte(yend>>8) & 0x1
return d.sendCommandSequence([]byte{0x45, ystartLo, ystartHi, yendLo, yendHi})
}
func (d *Device) WaitUntilIdle() {
// give it some time to get busy
time.Sleep(50 * time.Millisecond)
for d.busy.Get() { // high = busy
time.Sleep(10 * time.Millisecond)
}
// give it some extra time
time.Sleep(50 * time.Millisecond)
}
// sendCommandSequence sends the first byte in the buffer as a 'command' and all following bytes as data
func (d *Device) sendCommandSequence(seq []byte) error {
err := d.sendCommandByte(seq[0])
if err != nil {
return err
}
for i := 1; i < len(seq); i++ {
err = d.sendDataByte(seq[i])
if err != nil {
return err
}
}
return nil
}
func (d *Device) sendCommandByte(b byte) error {
d.dc.Low()
d.cs.Low()
_, err := d.bus.Transfer(b)
d.cs.High()
return err
}
func (d *Device) sendDataByte(b byte) error {
d.dc.High()
d.cs.Low()
_, err := d.bus.Transfer(b)
d.cs.High()
return err
}
func (d *Device) sendData(b []byte) error {
d.dc.High()
d.cs.Low()
err := d.bus.Tx(b, nil)
d.cs.High()
return err
}
// fill quickly fills a slice with a given value
func fill(s []byte, b byte) {
s[0] = b
for j := 1; j < len(s); j *= 2 {
copy(s[j:], s[:j])
}
}
+34
View File
@@ -0,0 +1,34 @@
//go:build pico
package epd2in66b
import (
"machine"
)
// DefaultConfig contains the default config for the https://www.waveshare.com/wiki/Pico-ePaper-2.66 module
var DefaultConfig = Config{
DataPin: machine.GP8,
ChipSelectPin: machine.GP9,
ResetPin: machine.GP12,
BusyPin: machine.GP13,
}
// NewPicoModule allocates a new device backed by the https://www.waveshare.com/wiki/Pico-ePaper-2.66 module
// This will also configure the SPI1 bus and configure the device with the DefaultConfig
func NewPicoModule() (Device, error) {
spi := machine.SPI1
if err := spi.Configure(machine.SPIConfig{
Frequency: Baudrate,
}); err != nil {
return Device{}, err
}
dev := New(spi)
if err := dev.Configure(DefaultConfig); err != nil {
return dev, err
}
return dev, nil
}
+92
View File
@@ -0,0 +1,92 @@
package epd2in66b
import (
_ "embed"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"os"
"testing"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)
type mockBus struct{}
func (m *mockBus) Tx(w, r []byte) error {
return nil
}
func (m *mockBus) Transfer(b byte) (byte, error) {
return 0, nil
}
func TestBufferDrawing(t *testing.T) {
dev := New(&mockBus{})
tinyfont.WriteLine(&dev, &freemono.Bold9pt7b, 10, 40, "Hello World!", color.RGBA{0xff, 0xff, 0xff, 0xff})
red := color.RGBA{0xff, 0, 0, 0xff}
black := color.RGBA{0xff, 0xff, 0xff, 0xff}
showRect(&dev, 10, 10, 10, 10, black)
showRect(&dev, 10, 20, 10, 10, red)
img := toImage(&dev)
writeImage(img)
}
func toImage(dev *Device) *image.RGBA {
red := color.RGBA{0xff, 0, 0, 0xff}
xMax, yMax := dev.Size()
r := image.Rect(0, 0, int(xMax), int(yMax))
container := image.NewRGBA(r)
draw.Draw(container, container.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Over)
for x := 0; x < int(xMax); x++ {
for y := 0; y < int(yMax); y++ {
bytePos, bitPos := pos(int16(x), int16(y), displayWidth)
if isSet(dev.redBuffer, bytePos, bitPos) {
container.Set(x, y, red)
} else if isSet(dev.blackBuffer, bytePos, bitPos) {
container.Set(x, y, color.Black)
}
}
}
return container
}
func isSet(buf []byte, bytePos, bitPos int) bool {
return (buf[bytePos])&(0x1<<bitPos) != 0
}
func showRect(display drivers.Displayer, x int16, y int16, w int16, h int16, c color.RGBA) {
for i := x; i < x+w; i++ {
for j := y; j < y+h; j++ {
display.SetPixel(i, j, c)
}
}
}
func writeImage(img image.Image) string {
fn := fmt.Sprintf("%d.png", time.Now().Unix())
f, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0o644)
if err != nil {
panic(err)
}
defer f.Close()
err = png.Encode(f, img)
if err != nil {
panic(err)
}
return fn
}
+244 -106
View File
@@ -33,7 +33,6 @@ var _debug debug = debugBasic
//var _debug debug = debugBasic | debugNetdev | debugCmd | debugDetail
var (
version = "0.0.1"
driverName = "Tinygo ESP32 Wifi network device driver (WiFiNINA)"
)
@@ -48,6 +47,9 @@ const (
statusConnectFailed connectionStatus = 4
statusConnectionLost connectionStatus = 5
statusDisconnected connectionStatus = 6
statusAPListening connectionStatus = 7
statusAPConnected connectionStatus = 8
statusAPFailed connectionStatus = 9
encTypeTKIP encryptionType = 2
encTypeCCMP encryptionType = 4
@@ -163,10 +165,12 @@ type encryptionType uint8
type sock uint8
type hwerr uint8
type socket struct {
protocol int
ip netip.AddrPort
inuse bool
type Socket struct {
protocol int
clientConnected bool
laddr netip.AddrPort // Set in Bind()
raddr netip.AddrPort // Set in Connect()
sock // Device socket, as returned from w.getSocket()
}
type Config struct {
@@ -213,17 +217,13 @@ type wifinina struct {
killWatchdog chan bool
fault error
sockets map[sock]*socket // keyed by sock as returned by getSocket()
}
func newSocket(protocol int) *socket {
return &socket{protocol: protocol, inuse: true}
sockets map[int]*Socket // keyed by sockfd
}
func New(cfg *Config) *wifinina {
w := wifinina{
cfg: cfg,
sockets: make(map[sock]*socket),
sockets: make(map[int]*Socket),
killWatchdog: make(chan bool),
cs: cfg.Cs,
ack: cfg.Ack,
@@ -302,6 +302,61 @@ func (w *wifinina) connectToAP() error {
return netlink.ErrConnectTimeout
}
func (w *wifinina) startAP() error {
timeout := w.params.ConnectTimeout
if timeout == 0 {
timeout = netlink.DefaultConnectTimeout
}
if len(w.params.Ssid) == 0 {
return netlink.ErrMissingSSID
}
if debugging(debugBasic) {
fmt.Printf("Starting Wifi AP as SSID '%s'...", w.params.Ssid)
}
start := time.Now()
// Start the connection process
switch {
case w.params.Passphrase != "":
w.setPassphraseForAP(w.params.Ssid, w.params.Passphrase)
default:
w.setNetworkForAP(w.params.Ssid)
}
// Check if we are listening
for {
status := w.getConnectionStatus()
switch status {
case statusAPListening:
if debugging(debugBasic) {
fmt.Printf("LISTENING\r\n")
}
if w.notifyCb != nil {
w.notifyCb(netlink.EventNetUp)
}
return nil
case statusAPFailed:
if debugging(debugBasic) {
fmt.Printf("FAILED (%s)\r\n", w.reason())
}
return netlink.ErrConnectFailed
}
if time.Since(start) > timeout {
break
}
time.Sleep(1 * time.Second)
}
if debugging(debugBasic) {
fmt.Printf("FAILED (timed out)\r\n")
}
return netlink.ErrConnectTimeout
}
func (w *wifinina) netDisconnect() {
w.disconnect()
}
@@ -313,7 +368,7 @@ func (w *wifinina) showDriver() {
if debugging(debugBasic) {
fmt.Printf("\r\n")
fmt.Printf("%s\r\n\r\n", driverName)
fmt.Printf("Driver version : %s\r\n", version)
fmt.Printf("Driver version : %s\r\n", drivers.Version)
}
w.driverShown = true
}
@@ -383,7 +438,12 @@ func (w *wifinina) showIP() {
}
func (w *wifinina) networkDown() bool {
return w.getConnectionStatus() != statusConnected
switch w.getConnectionStatus() {
case statusConnected, statusAPListening, statusAPConnected:
return false
default:
return true
}
}
func (w *wifinina) watchdog() {
@@ -421,15 +481,28 @@ func (w *wifinina) netConnect(reset bool) error {
}
w.showDevice()
retry:
for i := 0; w.params.Retries == 0 || i < w.params.Retries; i++ {
if err := w.connectToAP(); err != nil {
switch err {
case netlink.ErrConnectTimeout, netlink.ErrConnectFailed:
continue
switch w.params.ConnectMode {
case netlink.ConnectModeAP:
if err := w.startAP(); err != nil {
switch err {
case netlink.ErrConnectTimeout, netlink.ErrConnectFailed:
continue
}
return err
}
return err
break retry
default:
if err := w.connectToAP(); err != nil {
switch err {
case netlink.ErrConnectTimeout, netlink.ErrConnectFailed:
continue
}
return err
}
break retry
}
break
}
if w.networkDown() {
@@ -504,6 +577,12 @@ func (w *wifinina) GetHostByName(name string) (netip.Addr, error) {
fmt.Printf("[GetHostByName] name: %s\r\n", name)
}
// If it's already in dotted-decimal notation, return a copy
// per gethostbyname(3).
if ip, err := netip.ParseAddr(name); err == nil {
return ip, nil
}
w.mu.Lock()
defer w.mu.Unlock()
@@ -546,6 +625,20 @@ func (w *wifinina) Addr() (netip.Addr, error) {
return ip, nil
}
// newSockfd returns the next available sockfd, or -1 if none available
func (w *wifinina) newSockfd() int {
if len(w.sockets) >= maxNetworks {
return -1
}
// Search for the next available sockfd starting at 0
for sockfd := 0; ; sockfd++ {
if _, ok := w.sockets[sockfd]; !ok {
return sockfd
}
}
return -1
}
// See man socket(2) for standard Berkely sockets for Socket, Bind, etc.
// The driver strives to meet the function and semantics of socket(2).
@@ -573,37 +666,49 @@ func (w *wifinina) Socket(domain int, stype int, protocol int) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
sock := w.getSocket()
if sock == noSocketAvail {
sockfd := w.newSockfd()
if sockfd == -1 {
return -1, netdev.ErrNoMoreSockets
}
socket := newSocket(protocol)
w.sockets[sock] = socket
w.sockets[sockfd] = &Socket{
protocol: protocol,
sock: noSocketAvail,
}
return int(sock), nil
if debugging(debugNetdev) {
fmt.Printf("[Socket] <-- sockfd %d\r\n", sockfd)
}
return sockfd, nil
}
func (w *wifinina) Bind(sockfd int, ip netip.AddrPort) error {
if debugging(debugNetdev) {
fmt.Printf("[Bind] sockfd: %d, addr: %s\r\n", sockfd, ip)
fmt.Printf("[Bind] sockfd: %d, addr: %s:%d\r\n", sockfd, ip.Addr(), ip.Port())
}
w.mu.Lock()
defer w.mu.Unlock()
var sock = sock(sockfd)
var socket = w.sockets[sock]
socket, ok := w.sockets[sockfd]
if !ok {
return netdev.ErrInvalidSocketFd
}
switch socket.protocol {
case netdev.IPPROTO_TCP:
case netdev.IPPROTO_TLS:
case netdev.IPPROTO_UDP:
w.startServer(sock, ip.Port(), protoModeUDP)
socket.sock = w.getSocket()
if socket.sock == noSocketAvail {
return netdev.ErrNoMoreSockets
}
w.startServer(socket.sock, ip.Port(), protoModeUDP)
}
socket.ip = ip
socket.laddr = ip
return nil
}
@@ -628,21 +733,40 @@ func (w *wifinina) Connect(sockfd int, host string, ip netip.AddrPort) error {
w.mu.Lock()
defer w.mu.Unlock()
var sock = sock(sockfd)
var socket = w.sockets[sock]
socket, ok := w.sockets[sockfd]
if !ok {
return netdev.ErrInvalidSocketFd
}
// Start the connection
switch socket.protocol {
case netdev.IPPROTO_TCP:
w.startClient(sock, "", toUint32(ip.Addr().As4()), ip.Port(), protoModeTCP)
socket.sock = w.getSocket()
if socket.sock == noSocketAvail {
return netdev.ErrNoMoreSockets
}
w.startClient(socket.sock, "", toUint32(ip.Addr().As4()), ip.Port(), protoModeTCP)
case netdev.IPPROTO_TLS:
w.startClient(sock, host, 0, ip.Port(), protoModeTLS)
socket.sock = w.getSocket()
if socket.sock == noSocketAvail {
return netdev.ErrNoMoreSockets
}
w.startClient(socket.sock, host, 0, ip.Port(), protoModeTLS)
case netdev.IPPROTO_UDP:
w.startClient(sock, "", toUint32(ip.Addr().As4()), ip.Port(), protoModeUDP)
if socket.sock == noSocketAvail {
return fmt.Errorf("Must Bind before Connecting")
}
// See start in sendUDP()
socket.raddr = ip
socket.clientConnected = true
return nil
}
if w.getClientState(sock) == tcpStateEstablished {
if w.getClientState(socket.sock) == tcpStateEstablished {
socket.clientConnected = true
return nil
}
@@ -662,12 +786,18 @@ func (w *wifinina) Listen(sockfd int, backlog int) error {
w.mu.Lock()
defer w.mu.Unlock()
var sock = sock(sockfd)
var socket = w.sockets[sock]
socket, ok := w.sockets[sockfd]
if !ok {
return netdev.ErrInvalidSocketFd
}
switch socket.protocol {
case netdev.IPPROTO_TCP:
w.startServer(sock, socket.ip.Port(), protoModeTCP)
socket.sock = w.getSocket()
if socket.sock == noSocketAvail {
return netdev.ErrNoMoreSockets
}
w.startServer(socket.sock, socket.laddr.Port(), protoModeTCP)
case netdev.IPPROTO_UDP:
default:
return netdev.ErrProtocolNotSupported
@@ -676,27 +806,29 @@ func (w *wifinina) Listen(sockfd int, backlog int) error {
return nil
}
func (w *wifinina) Accept(sockfd int, ip netip.AddrPort) (int, error) {
func (w *wifinina) Accept(sockfd int) (int, netip.AddrPort, error) {
if debugging(debugNetdev) {
fmt.Printf("[Accept] sockfd: %d, peer: %s\r\n", sockfd, ip)
fmt.Printf("[Accept] sockfd: %d\r\n", sockfd)
}
w.mu.Lock()
defer w.mu.Unlock()
var client sock
var sock = sock(sockfd)
var socket = w.sockets[sock]
socket, ok := w.sockets[sockfd]
if !ok {
return -1, netip.AddrPort{}, netdev.ErrInvalidSocketFd
}
switch socket.protocol {
case netdev.IPPROTO_TCP:
default:
return -1, netdev.ErrProtocolNotSupported
return -1, netip.AddrPort{}, netdev.ErrProtocolNotSupported
}
skip:
for {
// Accept() will be sleeping most of the time, checking for a
// Accept() will be sleeping most of the time, checking for
// new clients every 1/10 sec.
w.mu.Unlock()
time.Sleep(100 * time.Millisecond)
@@ -704,49 +836,46 @@ func (w *wifinina) Accept(sockfd int, ip netip.AddrPort) (int, error) {
// Check if we've faulted
if w.fault != nil {
return -1, w.fault
return -1, netip.AddrPort{}, w.fault
}
// TODO: BUG: Currently, a sock that is 100% busy will always be
// TODO: returned by w.accept(sock), starving other socks
// TODO: from begin serviced. Need to figure out how to
// TODO: service socks fairly (round-robin?) so no one sock
// TODO: can dominate.
// Check if a client has data
client = w.accept(sock)
var client sock = w.accept(socket.sock)
if client == noSocketAvail {
// None ready
continue
}
// If we've already seen this socket, we can reuse
// the socket and return it. But, only if the socket
// is closed. If it's not closed, we'll just come back
// later to reuse it.
clientSocket, ok := w.sockets[client]
if ok {
// Wait for client to Close
if clientSocket.inuse {
continue
// If we already have a socket for the client, skip
for _, s := range w.sockets {
if s.sock == client {
continue skip
}
// Reuse client socket
return int(client), nil
}
// Create new socket for client and return fd
w.sockets[client] = newSocket(socket.protocol)
return int(client), nil
// Otherwise, create a new socket
clientfd := w.newSockfd()
if clientfd == -1 {
return -1, netip.AddrPort{}, netdev.ErrNoMoreSockets
}
w.sockets[clientfd] = &Socket{
protocol: netdev.IPPROTO_TCP,
sock: client,
clientConnected: true,
}
raddr := w.getRemoteData(client)
return clientfd, raddr, nil
}
}
func (w *wifinina) sockDown(sock sock) bool {
var socket = w.sockets[sock]
func (w *wifinina) sockDown(socket *Socket) bool {
if socket.protocol == netdev.IPPROTO_UDP {
return false
}
return w.getClientState(sock) != tcpStateEstablished
return w.getClientState(socket.sock) != tcpStateEstablished
}
func (w *wifinina) sendTCP(sock sock, buf []byte, deadline time.Time) (int, error) {
@@ -774,7 +903,7 @@ func (w *wifinina) sendTCP(sock sock, buf []byte, deadline time.Time) (int, erro
}
// Check if socket went down
if w.sockDown(sock) {
if w.getClientState(sock) != tcpStateEstablished {
return -1, io.EOF
}
@@ -792,7 +921,10 @@ func (w *wifinina) sendTCP(sock sock, buf []byte, deadline time.Time) (int, erro
return -1, netdev.ErrTimeout
}
func (w *wifinina) sendUDP(sock sock, buf []byte, deadline time.Time) (int, error) {
func (w *wifinina) sendUDP(sock sock, raddr netip.AddrPort, buf []byte, deadline time.Time) (int, error) {
// Start a client for each send
w.startClient(sock, "", toUint32(raddr.Addr().As4()), raddr.Port(), protoModeUDP)
// Queue it
ok := w.insertDataBuf(sock, buf)
@@ -810,8 +942,10 @@ func (w *wifinina) sendUDP(sock sock, buf []byte, deadline time.Time) (int, erro
}
func (w *wifinina) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
var sock = sock(sockfd)
var socket = w.sockets[sock]
socket, ok := w.sockets[sockfd]
if !ok {
return -1, netdev.ErrInvalidSocketFd
}
// Check if we've timed out
if !deadline.IsZero() {
@@ -822,9 +956,9 @@ func (w *wifinina) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, e
switch socket.protocol {
case netdev.IPPROTO_TCP, netdev.IPPROTO_TLS:
return w.sendTCP(sock, buf, deadline)
return w.sendTCP(socket.sock, buf, deadline)
case netdev.IPPROTO_UDP:
return w.sendUDP(sock, buf, deadline)
return w.sendUDP(socket.sock, socket.raddr, buf, deadline)
}
return -1, netdev.ErrProtocolNotSupported
@@ -869,7 +1003,10 @@ func (w *wifinina) Recv(sockfd int, buf []byte, flags int,
w.mu.Lock()
defer w.mu.Unlock()
var sock = sock(sockfd)
socket, ok := w.sockets[sockfd]
if !ok {
return -1, netdev.ErrInvalidSocketFd
}
// Limit max read size to chunk large read requests
var max = len(buf)
@@ -890,22 +1027,22 @@ func (w *wifinina) Recv(sockfd int, buf []byte, flags int,
// doesn't return unless there is data, even a single byte, or
// on error such as timeout or EOF.
n := int(w.getDataBuf(sock, buf[:max]))
n := int(w.getDataBuf(socket.sock, buf[:max]))
if n > 0 {
if debugging(debugNetdev) {
fmt.Printf("[<--Recv] sockfd: %d, n: %d\r\n",
sock, n)
sockfd, n)
}
return n, nil
}
// Check if socket went down
if w.sockDown(sock) {
if w.sockDown(socket) {
// Get any last bytes
n = int(w.getDataBuf(sock, buf[:max]))
n = int(w.getDataBuf(socket.sock, buf[:max]))
if debugging(debugNetdev) {
fmt.Printf("[<--Recv] sockfd: %d, n: %d, EOF\r\n",
sock, n)
sockfd, n)
}
if n > 0 {
return n, io.EOF
@@ -934,34 +1071,18 @@ func (w *wifinina) Close(sockfd int) error {
w.mu.Lock()
defer w.mu.Unlock()
var sock = sock(sockfd)
var socket = w.sockets[sock]
if !socket.inuse {
return nil
socket, ok := w.sockets[sockfd]
if !ok {
return netdev.ErrInvalidSocketFd
}
w.stopClient(sock)
if socket.protocol == netdev.IPPROTO_UDP {
socket.inuse = false
return nil
if socket.clientConnected {
w.stopClient(socket.sock)
}
start := time.Now()
for time.Since(start) < 5*time.Second {
delete(w.sockets, sockfd)
if w.getClientState(sock) == tcpStateClosed {
socket.inuse = false
return nil
}
w.mu.Unlock()
time.Sleep(100 * time.Millisecond)
w.mu.Lock()
}
return netdev.ErrClosingSocket
return nil
}
func (w *wifinina) SetSockOpt(sockfd int, level int, opt int, value interface{}) error {
@@ -1123,6 +1244,23 @@ func (w *wifinina) accept(s sock) sock {
return newsock
}
func (w *wifinina) getRemoteData(s sock) netip.AddrPort {
if debugging(debugCmd) {
fmt.Printf(" [cmdGetRemoteData] sock: %d\r\n", s)
}
sl := make([]string, 2)
l := w.reqRspStr1(cmdGetRemoteData, uint8(s), sl)
if l != 2 {
w.faultf("getRemoteData wanted l=2, got l=%d", l)
return netip.AddrPort{}
}
ip, _ := netip.AddrFromSlice([]byte(sl[0])[:4])
port := binary.BigEndian.Uint16([]byte(sl[1]))
return netip.AddrPortFrom(ip, port)
}
// insertDataBuf adds data to the buffer used for sending UDP data
func (w *wifinina) insertDataBuf(sock sock, buf []byte) bool {