mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-30 04:28:40 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce03bebc34 | |||
| dfd652ca1f | |||
| c765ef3970 | |||
| 428db3cd12 | |||
| d7f619ca21 | |||
| d1ace44754 | |||
| 6b58fdc95a | |||
| bd2530abee | |||
| 3bcde1485c | |||
| 351700e48d | |||
| cbcb62af01 | |||
| 19bb773e5b | |||
| 69cc0b22c4 | |||
| 2a48b5c25d | |||
| e6a822f68b | |||
| f65bce6d36 | |||
| 67b8a341a6 | |||
| b869d27170 | |||
| 107932a201 | |||
| d2db0c28e2 | |||
| 761bcfc4db | |||
| 91dadd5535 | |||
| f7dc106fc8 | |||
| df343190c2 | |||
| 6bc466f79b | |||
| 23776bf906 | |||
| e77e9249cd | |||
| 1345bc2161 | |||
| ab795cc186 | |||
| 36213a1cdc | |||
| 963c903d71 | |||
| 2cec480fb8 | |||
| 68da7f437b | |||
| d0632fccfa | |||
| 2c0a74df4d | |||
| c6228a0677 | |||
| 33ec7fc98f | |||
| 75c15d5d0b | |||
| e1deb45aac | |||
| d1cafbc2b2 | |||
| 0b6bfda8cf | |||
| 84408279de | |||
| de1e6a626a | |||
| 7531c9d334 | |||
| ee44900c25 | |||
| b9a70aeb6f | |||
| cad0a320b7 | |||
| a53a39922b | |||
| 41d6a4c3fa | |||
| 60ba0e3b30 | |||
| 2b5f43029c | |||
| a771641339 | |||
| 5741ceb9d1 | |||
| 27ef18930e | |||
| e9a6d96ddd | |||
| 9a7cb1a22f | |||
| d170ec8d81 | |||
| 0fc2d28ca8 | |||
| ef34c13cc1 | |||
| c64d7920dc | |||
| 008157b6c9 | |||
| 0ed9683a52 | |||
| 01acd977f3 | |||
| 231ec57202 | |||
| 5d3ad4ba52 | |||
| 8cb226938b | |||
| ce5e443084 | |||
| edf9ba92be | |||
| c1c05cbef7 | |||
| c338348d2b | |||
| b6aa674b2a | |||
| cc7079b0cd | |||
| 42a907035b | |||
| 46c9ba9595 | |||
| df64ce8f50 |
@@ -12,6 +12,9 @@ jobs:
|
||||
- run:
|
||||
name: "Enforce Go Formatted Code"
|
||||
command: make fmt-check
|
||||
- run:
|
||||
name: "Run unit tests"
|
||||
command: make unit-test
|
||||
- run:
|
||||
name: "Run build and smoke tests"
|
||||
command: make smoke-test
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
build
|
||||
.vscode/
|
||||
|
||||
@@ -1,3 +1,68 @@
|
||||
0.16.0
|
||||
---
|
||||
- **new devices**
|
||||
- aht20: add device
|
||||
- ina260: add new i2c device
|
||||
- keypad: add 4x4 keypad driver (#226)
|
||||
- max7219: add driver support
|
||||
- mcp2515: add support for mcp2515 CAN device
|
||||
- p1am: support the P1AM-100 hardware watchdog
|
||||
- pcf8563: add support for pcf8563 real time clock
|
||||
- servo: add driver using PWM
|
||||
- tm1637: add support for tm1637 7-segment LED
|
||||
- tone: add package for producing tones using the PWM interface
|
||||
- **enhancements**
|
||||
- pwm: update drivers with PWM to use new interface
|
||||
- wifinina: Make TLS work over WiFiNINA Verified on Arduino Nano33 IoT and nina fw v1.4.5
|
||||
- ssd1306: Enable reset screen for SSD1306 via I2C
|
||||
- st7789: add scrolling functions to match st7735
|
||||
- **bugfixes**
|
||||
- wifinina:
|
||||
- fix getMACAddress and getTime
|
||||
- fix println + cleanup
|
||||
- remove debug flag and remove unnecessary padding call
|
||||
- fix padding and implement missing functions
|
||||
- flash: fix EraseBlocks method which is erasing sectors instead
|
||||
- **core**
|
||||
- all: use interfaces for UART objects
|
||||
- all: do not take the pointer of an I2C object
|
||||
- adc: update drivers with ADC to use new config struct
|
||||
- **testing**
|
||||
- tester:
|
||||
- add a mock for command-oriented i2c devices
|
||||
- add 16-bit register mock device
|
||||
|
||||
- **docs**
|
||||
- ssd1306: example of ssd1306 with 128x64 display over I2C
|
||||
- wifinina:
|
||||
- add information about Adafruit boards with ESP32 wifi coprocessors, and modify examples to remove code that was both not being used, and also prevented many Adafruit boards from being able to be targeted by the examples
|
||||
- update docs to simplify the nina-fw update process
|
||||
- example that connects to AP and prints ip addresses, time and mac
|
||||
- p1am: documentation and example program
|
||||
- add missing new drivers added since last release
|
||||
|
||||
0.15.0
|
||||
---
|
||||
- **new devices**
|
||||
- dht: add DHTXX thermometer
|
||||
- mcp23017: new driver for MCP23017 (I2C port expander)
|
||||
- bmp388: Add bmp388 support (#219)
|
||||
- **enhancements**
|
||||
- hd44780: add a mode to work with boards where the RW pin is grounded
|
||||
- st7789: add scrolling functions to match st7735
|
||||
- microbitmatrix: matrix now working on microbit v2
|
||||
- ds1307: Better interface "ReadTime" instead of "Time"
|
||||
- ws2812: make AVR support more robust
|
||||
- **bugfixes**
|
||||
- all: fix main package in examples
|
||||
- **core**
|
||||
- adc: update all drivers with ADC to use new config struct
|
||||
- spi: remove machine.SPI and replace with drivers.SPI interface for almost all SPI drivers
|
||||
- **testing**
|
||||
- test: run unit tests against i2c drivers and any spi drivers without direct gpio
|
||||
- **docs**
|
||||
- st7789: correct errors on various godoc comments
|
||||
|
||||
0.14.0
|
||||
---
|
||||
- **new devices**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018-2020 The TinyGo Authors. All rights reserved.
|
||||
Copyright (c) 2018-2021 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
|
||||
|
||||
@@ -31,6 +31,8 @@ smoke-test:
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp280/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bmp388/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/sram/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/time/main.go
|
||||
@@ -81,18 +83,28 @@ smoke-test:
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mag3110/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017-multiple/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp3008/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp2515/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/microbitmatrix/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mma8653/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mpu6050/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=p1am-100 ./examples/p1am/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setbuffer/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/servo
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
|
||||
@@ -109,6 +121,10 @@ smoke-test:
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/thermistor/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-bluefruit ./examples/tone
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/tm1637/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/fourwire/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/pyportal_touchpaint/main.go
|
||||
@@ -132,6 +148,8 @@ smoke-test:
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
|
||||
@md5sum ./build/test.hex
|
||||
ifneq ($(AVR), 0)
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
|
||||
@md5sum ./build/test.hex
|
||||
endif
|
||||
@@ -157,5 +175,33 @@ endif
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/max72xx/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/keypad4x4/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/alarm/
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/clkout/
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/time/
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/timer/
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina260/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=nucleo-l432kc ./examples/aht20/main.go
|
||||
@md5sum ./build/test.hex
|
||||
|
||||
test: clean fmt-check smoke-test
|
||||
DRIVERS = $(wildcard */)
|
||||
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
|
||||
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
|
||||
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 \
|
||||
pcf8563 mcp2515 servo
|
||||
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
|
||||
|
||||
unit-test:
|
||||
@go test -v $(addprefix ./,$(TESTS))
|
||||
|
||||
test: clean fmt-check unit-test smoke-test
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://pkg.go.dev/tinygo.org/x/drivers) [](https://circleci.com/gh/tinygo-org/drivers/tree/dev)
|
||||
|
||||
|
||||
This package provides a collection of hardware drivers for devices that can be used together with [TinyGo](https://tinygo.org).
|
||||
This package provides a collection of hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
|
||||
|
||||
## Installing
|
||||
|
||||
@@ -52,12 +52,13 @@ func main() {
|
||||
|
||||
## Currently supported devices
|
||||
|
||||
The following 53 devices are supported.
|
||||
The following 65 devices are supported.
|
||||
|
||||
| Device Name | Interface Type |
|
||||
|----------|-------------|
|
||||
| [ADT7410 I2C Temperature Sensor](https://www.analog.com/media/en/technical-documentation/data-sheets/ADT7410.pdf) | I2C |
|
||||
| [ADXL345 accelerometer](http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf) | I2C |
|
||||
| [AHT20 I2C Temperature and Humidity Sensor](http://www.aosong.com/userfiles/files/media/AHT20%20%E8%8B%B1%E6%96%87%E7%89%88%E8%AF%B4%E6%98%8E%E4%B9%A6%20A0%2020201222.pdf) | I2C |
|
||||
| [AMG88xx 8x8 Thermal camera sensor](https://cdn-learn.adafruit.com/assets/assets/000/043/261/original/Grid-EYE_SPECIFICATIONS%28Reference%29.pdf) | I2C |
|
||||
| [APA102 RGB LED](https://cdn-shop.adafruit.com/product-files/2343/APA102C.pdf) | SPI |
|
||||
| [AT24CX 2-wire serial EEPROM](https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf) | I2C |
|
||||
@@ -68,7 +69,9 @@ The following 53 devices are supported.
|
||||
| [BMI160 accelerometer/gyroscope](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi160-ds000.pdf) | SPI |
|
||||
| [BMP180 barometer](https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf) | I2C |
|
||||
| [BMP280 temperature/barometer](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf) | I2C |
|
||||
| [BMP388 pressure sensor](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf) | I2C |
|
||||
| [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | GPIO |
|
||||
| [DHTXX thermometer and humidity sensor](https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf) | GPIO |
|
||||
| [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C |
|
||||
| [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C |
|
||||
| [ESP32 as WiFi Coprocessor with Arduino nina-fw](https://github.com/arduino/nina-fw) | SPI |
|
||||
@@ -78,19 +81,27 @@ The following 53 devices are supported.
|
||||
| [HD44780 LCD controller](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) | GPIO/I2C |
|
||||
| [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI |
|
||||
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
|
||||
| [INA260 Volt/Amp/Power meter](https://www.ti.com/lit/ds/symlink/ina260.pdf) | I2C |
|
||||
| [4x4 Membrane Keypad](https://cdn.sparkfun.com/assets/f/f/a/5/0/DS-16038.pdf) | GPIO |
|
||||
| [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM |
|
||||
| [L9110x motor driver](https://www.elecrow.com/download/datasheet-l9110.pdf) | GPIO/PWM |
|
||||
| [LIS2MDL magnetometer](https://www.st.com/resource/en/datasheet/lis2mdl.pdf) | I2C |
|
||||
| [LIS3DH accelerometer](https://www.st.com/resource/en/datasheet/lis3dh.pdf) | I2C |
|
||||
| [LSM6DS3 accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3.pdf) | I2C |
|
||||
| [MAG3110 magnetometer](https://www.nxp.com/docs/en/data-sheet/MAG3110.pdf) | I2C |
|
||||
| [MAX7219 & MAX7221 display driver](https://datasheets.maximintegrated.com/en/ds/MAX7219-MAX7221.pdf) | SPI |
|
||||
| [MCP23017 port expander](https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf) | I2C |
|
||||
| [MCP3008 analog to digital converter (ADC)](http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf) | SPI |
|
||||
| [MCP2515 Stand-Alone CAN Controller with SPI Interface](https://ww1.microchip.com/downloads/en/DeviceDoc/MCP2515-Family-Data-Sheet-DS20001801K.pdf) | SPI |
|
||||
| [Microphone - PDM](https://cdn-learn.adafruit.com/assets/assets/000/049/977/original/MP34DT01-M.pdf) | I2S/PDM |
|
||||
| [MMA8653 accelerometer](https://www.nxp.com/docs/en/data-sheet/MMA8653FC.pdf) | I2C |
|
||||
| [MPU6050 accelerometer/gyroscope](https://store.invensense.com/datasheets/invensense/MPU-6050_DataSheet_V3%204.pdf) | I2C |
|
||||
| [P1AM-100 Base Controller](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) | SPI |
|
||||
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
|
||||
| [PCF8563 real time clock](https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf) | I2C |
|
||||
| [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO |
|
||||
| [Semihosting](https://wiki.segger.com/Semihosting) | Debug |
|
||||
| [Servo](https://learn.sparkfun.com/tutorials/hobby-servo-tutorial/all) | PWM |
|
||||
| [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO |
|
||||
| [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO |
|
||||
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Humidity/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
|
||||
@@ -102,6 +113,7 @@ The following 53 devices are supported.
|
||||
| [ST7789 TFT color display](https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf) | SPI |
|
||||
| [Stepper motor "Easystepper" controller](https://en.wikipedia.org/wiki/Stepper_motor) | GPIO |
|
||||
| [Thermistor](https://www.farnell.com/datasheets/33552.pdf) | ADC |
|
||||
| [TM1637 7-segment LED display](https://www.mcielectronics.cl/website_MCI/static/documents/Datasheet_TM1637.pdf) | I2C |
|
||||
| [TMP102 I2C Temperature Sensor](https://download.mikroe.com/documents/datasheets/tmp102-data-sheet.pdf) | I2C |
|
||||
| [VEML6070 UV light sensor](https://www.vishay.com/docs/84277/veml6070.pdf) | I2C |
|
||||
| [VL53L1X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl53l1x.pdf) | I2C |
|
||||
|
||||
@@ -18,13 +18,13 @@ func TestWhoAmI(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice(c, Address)
|
||||
fake.SetupRegisters(defaultRegisters())
|
||||
copy(fake.Registers[:], defaultRegisters())
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Connected(), qt.Equals, true)
|
||||
|
||||
fake.SetupRegister(RegID, 0x99)
|
||||
fake.Registers[RegID] = 0x99
|
||||
c.Assert(dev.Connected(), qt.Equals, false)
|
||||
}
|
||||
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package aht20
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to an AHT20 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
humidity uint32
|
||||
temp uint32
|
||||
}
|
||||
|
||||
// New creates a new AHT20 connection. The I2C bus must already be
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the device
|
||||
func (d *Device) Configure() {
|
||||
// Check initialization state
|
||||
status := d.Status()
|
||||
if status&0x08 == 1 {
|
||||
// Device is initialized
|
||||
return
|
||||
}
|
||||
|
||||
// Force initialization
|
||||
d.bus.Tx(d.Address, []byte{CMD_INITIALIZE, 0x08, 0x00}, nil)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Reset the device
|
||||
func (d *Device) Reset() {
|
||||
d.bus.Tx(d.Address, []byte{CMD_SOFTRESET}, nil)
|
||||
}
|
||||
|
||||
// Status of the device
|
||||
func (d *Device) Status() byte {
|
||||
data := []byte{0}
|
||||
|
||||
d.bus.Tx(d.Address, []byte{CMD_STATUS}, data)
|
||||
|
||||
return data[0]
|
||||
}
|
||||
|
||||
// Read the temperature and humidity
|
||||
//
|
||||
// The actual temperature and humidity are stored
|
||||
// and can be accessed using `Temp` and `Humidity`.
|
||||
func (d *Device) Read() error {
|
||||
d.bus.Tx(d.Address, []byte{CMD_TRIGGER, 0x33, 0x00}, nil)
|
||||
|
||||
data := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
for retry := 0; retry < 3; retry++ {
|
||||
time.Sleep(80 * time.Millisecond)
|
||||
err := d.bus.Tx(d.Address, nil, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If measurement complete, store values
|
||||
if data[0]&0x04 != 0 && data[0]&0x80 == 0 {
|
||||
d.humidity = uint32(data[1])<<12 | uint32(data[2])<<4 | uint32(data[3])>>4
|
||||
d.temp = (uint32(data[3])&0xF)<<16 | uint32(data[4])<<8 | uint32(data[5])
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ErrTimeout
|
||||
}
|
||||
|
||||
func (d *Device) RawHumidity() uint32 {
|
||||
return d.humidity
|
||||
}
|
||||
|
||||
func (d *Device) RawTemp() uint32 {
|
||||
return d.temp
|
||||
}
|
||||
|
||||
func (d *Device) RelHumidity() float32 {
|
||||
return (float32(d.humidity) * 100) / 0x100000
|
||||
}
|
||||
|
||||
func (d *Device) DeciRelHumidity() int32 {
|
||||
return (int32(d.humidity) * 1000) / 0x100000
|
||||
}
|
||||
|
||||
// Temperature in degrees celsius
|
||||
func (d *Device) Celsius() float32 {
|
||||
return (float32(d.temp*200.0) / 0x100000) - 50
|
||||
}
|
||||
|
||||
// Temperature in mutiples of one tenth of a degree celsius
|
||||
//
|
||||
// Using this method avoids floating point calculations.
|
||||
func (d *Device) DeciCelsius() int32 {
|
||||
return ((int32(d.temp) * 2000) / 0x100000) - 500
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package aht20
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"tinygo.org/x/drivers/tester"
|
||||
)
|
||||
|
||||
func TestDefaultI2CAddress(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
dev := New(bus)
|
||||
c.Assert(uint8(dev.Address), qt.Equals, uint8(Address))
|
||||
}
|
||||
|
||||
func TestInitialization(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := tester.NewI2CDeviceCmd(c, Address)
|
||||
fdev.Commands = defaultCommands()
|
||||
bus.AddDevice(fdev)
|
||||
|
||||
// Set status to uninitialized to force initialization
|
||||
fdev.Commands[CMD_STATUS].Response[0] = 0x0C
|
||||
|
||||
dev := New(bus)
|
||||
dev.Configure()
|
||||
|
||||
// Check initialization command invoked
|
||||
c.Assert(fdev.Commands[CMD_INITIALIZE].Invocations > 0, qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := tester.NewI2CDeviceCmd(c, Address)
|
||||
fdev.Commands = defaultCommands()
|
||||
bus.AddDevice(fdev)
|
||||
|
||||
dev := New(bus)
|
||||
dev.Read()
|
||||
|
||||
// Should be 25deg (250 decidegrees)
|
||||
c.Assert(dev.DeciCelsius(), qt.Equals, int32(250))
|
||||
|
||||
// Should be 36.3% (363 decipercent)
|
||||
c.Assert(dev.DeciRelHumidity(), qt.Equals, int32(363))
|
||||
}
|
||||
|
||||
func defaultCommands() map[uint8]*tester.Cmd {
|
||||
return map[uint8]*tester.Cmd{
|
||||
CMD_INITIALIZE: {
|
||||
Command: []byte{0xBE},
|
||||
Mask: []byte{0xFF},
|
||||
Response: []byte{},
|
||||
},
|
||||
CMD_TRIGGER: {
|
||||
Command: []byte{0xAC, 0x33, 0x00},
|
||||
Mask: []byte{0xFF, 0xFF, 0xFF},
|
||||
Response: []byte{0x1C, 0x5D, 0x10, 0x66, 0x01, 0xD2, 0x93},
|
||||
},
|
||||
CMD_SOFTRESET: {
|
||||
Command: []byte{0xBA},
|
||||
Mask: []byte{0xFF},
|
||||
Response: []byte{},
|
||||
},
|
||||
CMD_STATUS: {
|
||||
Command: []byte{0x71},
|
||||
Mask: []byte{0xFF},
|
||||
Response: []byte{0x1C},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package aht20
|
||||
|
||||
import "errors"
|
||||
|
||||
const (
|
||||
Address = 0x38
|
||||
|
||||
CMD_INITIALIZE = 0xBE
|
||||
CMD_STATUS = 0x71
|
||||
CMD_TRIGGER = 0xAC
|
||||
CMD_SOFTRESET = 0xBA
|
||||
|
||||
STATUS_BUSY = 0x80
|
||||
STATUS_CALIBRATED = 0x08
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBusy = errors.New("device busy")
|
||||
ErrTimeout = errors.New("timeout")
|
||||
)
|
||||
+4
-10
@@ -6,6 +6,8 @@ package apa102 // import "tinygo.org/x/drivers/apa102"
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,20 +25,12 @@ var startFrame = []byte{0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
// Device wraps APA102 SPI LEDs.
|
||||
type Device struct {
|
||||
bus SPI
|
||||
bus drivers.SPI
|
||||
Order int
|
||||
}
|
||||
|
||||
// The SPI interface specifies the minimum functionality that a bus
|
||||
// implementation needs to provide for use by the APA102 driver. Hardware
|
||||
// SPI from the TinyGo "machine" package implements this already.
|
||||
type SPI interface {
|
||||
Tx(w, r []byte) error
|
||||
Transfer(b byte) (byte, error)
|
||||
}
|
||||
|
||||
// New returns a new APA102 driver. Pass in a fully configured SPI bus.
|
||||
func New(b SPI) Device {
|
||||
func New(b drivers.SPI) Device {
|
||||
return Device{bus: b, Order: BGR}
|
||||
}
|
||||
|
||||
|
||||
+7
-4
@@ -1,8 +1,11 @@
|
||||
package bmi160
|
||||
|
||||
import "machine"
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
import "time"
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is
|
||||
// also an I2C interface, but it is not yet supported.
|
||||
@@ -11,13 +14,13 @@ type DeviceSPI struct {
|
||||
CSB machine.Pin
|
||||
|
||||
// SPI bus (requires chip select to be usable).
|
||||
Bus machine.SPI
|
||||
Bus drivers.SPI
|
||||
}
|
||||
|
||||
// NewSPI returns a new device driver. The pin and SPI interface are not
|
||||
// touched, provide a fully configured SPI object and call Configure to start
|
||||
// using this device.
|
||||
func NewSPI(csb machine.Pin, spi machine.SPI) *DeviceSPI {
|
||||
func NewSPI(csb machine.Pin, spi drivers.SPI) *DeviceSPI {
|
||||
return &DeviceSPI{
|
||||
CSB: csb, // chip select
|
||||
Bus: spi,
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
package bmp388
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
errConfigWrite = errors.New("bmp388: failed to configure sensor, check connection")
|
||||
errConfig = errors.New("bmp388: there is a problem with the configuration, try reducing ODR")
|
||||
errCaliRead = errors.New("bmp388: failed to read calibration coefficient register")
|
||||
errSoftReset = errors.New("bmp388: failed to perform a soft reset")
|
||||
errNotConnected = errors.New("bmp388: not connected")
|
||||
)
|
||||
|
||||
type Oversampling byte
|
||||
type Mode byte
|
||||
type OutputDataRate byte
|
||||
type FilterCoefficient byte
|
||||
|
||||
// Config contains settings for filtering, sampling, and modes of operation
|
||||
type Config struct {
|
||||
Pressure Oversampling
|
||||
Temperature Oversampling
|
||||
Mode Mode
|
||||
ODR OutputDataRate
|
||||
IIR FilterCoefficient
|
||||
}
|
||||
|
||||
// Device wraps the I2C connection and configuration values for the BMP388
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint8
|
||||
cali calibrationCoefficients
|
||||
Config Config
|
||||
}
|
||||
|
||||
type calibrationCoefficients struct {
|
||||
// Temperature compensation
|
||||
t1 uint16
|
||||
t2 uint16
|
||||
t3 int8
|
||||
|
||||
// Pressure compensation
|
||||
p1 int16
|
||||
p2 int16
|
||||
p3 int8
|
||||
p4 int8
|
||||
p5 uint16
|
||||
p6 uint16
|
||||
p7 int8
|
||||
p8 int8
|
||||
p9 int16
|
||||
p10 int8
|
||||
p11 int8
|
||||
}
|
||||
|
||||
// New returns a bmp388 struct with the default I2C address. Configure must also be called after instanting
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure can enable settings on the BMP388 and reads the calibration coefficients
|
||||
func (d *Device) Configure(config Config) (err error) {
|
||||
d.Config = config
|
||||
|
||||
if d.Config == (Config{}) {
|
||||
d.Config.Mode = Normal
|
||||
}
|
||||
|
||||
// Turning on the pressure and temperature sensors and setting the measurement mode
|
||||
err = d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
|
||||
|
||||
// Configure the oversampling, output data rate, and iir filter coefficient settings
|
||||
err = d.writeRegister(RegOSR, byte(d.Config.Pressure|d.Config.Temperature<<3))
|
||||
err = d.writeRegister(RegODR, byte(d.Config.ODR))
|
||||
err = d.writeRegister(RegIIR, byte(d.Config.IIR<<1))
|
||||
|
||||
if err != nil {
|
||||
return errConfigWrite
|
||||
}
|
||||
|
||||
// Check if there is a problem with the given configuration
|
||||
if d.configurationError() {
|
||||
return errConfig
|
||||
}
|
||||
|
||||
// Reading the builtin calibration coefficients and parsing them per the datasheet. The compensation formula given
|
||||
// in the datasheet is implemented in floating point
|
||||
buffer, err := d.readRegister(RegCali, 21)
|
||||
if err != nil {
|
||||
return errCaliRead
|
||||
}
|
||||
|
||||
d.cali.t1 = uint16(buffer[1])<<8 | uint16(buffer[0])
|
||||
d.cali.t2 = uint16(buffer[3])<<8 | uint16(buffer[2])
|
||||
d.cali.t3 = int8(buffer[4])
|
||||
|
||||
d.cali.p1 = int16(buffer[6])<<8 | int16(buffer[5])
|
||||
d.cali.p2 = int16(buffer[8])<<8 | int16(buffer[7])
|
||||
d.cali.p3 = int8(buffer[9])
|
||||
d.cali.p4 = int8(buffer[10])
|
||||
d.cali.p5 = uint16(buffer[12])<<8 | uint16(buffer[11])
|
||||
d.cali.p6 = uint16(buffer[14])<<8 | uint16(buffer[13])
|
||||
d.cali.p7 = int8(buffer[15])
|
||||
d.cali.p8 = int8(buffer[16])
|
||||
d.cali.p9 = int16(buffer[18])<<8 | int16(buffer[17])
|
||||
d.cali.p10 = int8(buffer[19])
|
||||
d.cali.p11 = int8(buffer[20])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the temperature registers and compute a compensation value for the temperature and pressure compensation
|
||||
// calculations. This is not the temperature itself.
|
||||
func (d *Device) tlinCompensate() (int64, error) {
|
||||
rawTemp, err := d.readSensorData(RegTemp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// pulled from C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
|
||||
partialData1 := rawTemp - (256 * int64(d.cali.t1))
|
||||
partialData2 := int64(d.cali.t2) * partialData1
|
||||
partialData3 := (partialData1 * partialData1)
|
||||
partialData4 := partialData3 * int64(d.cali.t3)
|
||||
partialData5 := (partialData2 * 262144) + partialData4
|
||||
return partialData5 / 4294967296, nil
|
||||
|
||||
}
|
||||
|
||||
// ReadTemperature returns the temperature in centicelsius, i.e 2426 / 100 = 24.26 C
|
||||
func (d *Device) ReadTemperature() (int32, error) {
|
||||
|
||||
tlin, err := d.tlinCompensate()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
temp := (tlin * 25) / 16384
|
||||
return int32(temp), nil
|
||||
}
|
||||
|
||||
// ReadPressure returns the pressure in centipascals, i.e 10132520 / 100 = 101325.20 Pa
|
||||
func (d *Device) ReadPressure() (int32, error) {
|
||||
|
||||
tlin, err := d.tlinCompensate()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rawPress, err := d.readSensorData(RegPress)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// code pulled from bmp388 C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
|
||||
partialData1 := tlin * tlin
|
||||
partialData2 := partialData1 / 64
|
||||
partialData3 := (partialData2 * tlin) / 256
|
||||
partialData4 := (int64(d.cali.p8) * partialData3) / 32
|
||||
partialData5 := (int64(d.cali.p7) * partialData1) * 16
|
||||
partialData6 := (int64(d.cali.p6) * tlin) * 4194304
|
||||
offset := (int64(d.cali.p5) * 140737488355328) + partialData4 + partialData5 + partialData6
|
||||
partialData2 = (int64(d.cali.p4) * partialData3) / 32
|
||||
partialData4 = (int64(d.cali.p3) * partialData1) * 4
|
||||
partialData5 = (int64(d.cali.p2) - 16384) * tlin * 2097152
|
||||
sensitivity := ((int64(d.cali.p1) - 16384) * 70368744177664) + partialData2 + partialData4 + partialData5
|
||||
partialData1 = (sensitivity / 16777216) * rawPress
|
||||
partialData2 = int64(d.cali.p10) * tlin
|
||||
partialData3 = partialData2 + (65536 * int64(d.cali.p9))
|
||||
partialData4 = (partialData3 * rawPress) / 8192
|
||||
|
||||
// dividing by 10 followed by multiplying by 10
|
||||
// To avoid overflow caused by (pressure * partial_data4)
|
||||
partialData5 = (rawPress * (partialData4 / 10)) / 512
|
||||
partialData5 = partialData5 * 10
|
||||
partialData6 = (int64)(uint64(rawPress) * uint64(rawPress))
|
||||
partialData2 = (int64(d.cali.p11) * partialData6) / 65536
|
||||
partialData3 = (partialData2 * rawPress) / 128
|
||||
partialData4 = (offset / 4) + partialData1 + partialData5 + partialData3
|
||||
compPress := ((uint64(partialData4) * 25) / uint64(1099511627776))
|
||||
return int32(compPress), nil
|
||||
}
|
||||
|
||||
// SoftReset commands the BMP388 to reset of all user configuration settings
|
||||
func (d *Device) SoftReset() error {
|
||||
err := d.writeRegister(RegCmd, SoftReset)
|
||||
if err != nil {
|
||||
return errSoftReset
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connected tries to reach the bmp388 and check its chip id register. Returns true if it was able to successfully
|
||||
// communicate over i2c and returns the correct value
|
||||
func (d *Device) Connected() bool {
|
||||
data, err := d.readRegister(RegChipId, 1)
|
||||
return err == nil && data[0] == ChipId // returns true if i2c comm was good and response equals 0x50
|
||||
}
|
||||
|
||||
// SetMode changes the run mode of the sensor, NORMAL is the one to use for most cases. Use FORCED if you plan to take
|
||||
// measurements infrequently and want to conserve power. SLEEP will of course put the sensor to sleep
|
||||
func (d *Device) SetMode(mode Mode) error {
|
||||
d.Config.Mode = mode
|
||||
return d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
|
||||
}
|
||||
|
||||
func (d *Device) readSensorData(register byte) (data int64, err error) {
|
||||
|
||||
if !d.Connected() {
|
||||
return 0, errNotConnected
|
||||
}
|
||||
|
||||
// put the sensor back into forced mode to get a reading, the sensor goes back to sleep after taking one read in
|
||||
// forced mode
|
||||
if d.Config.Mode != Normal {
|
||||
err = d.SetMode(Forced)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bytes, err := d.readRegister(register, 3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data = int64(bytes[2])<<16 | int64(bytes[1])<<8 | int64(bytes[0])
|
||||
return
|
||||
}
|
||||
|
||||
// configurationError checks the register error for the configuration error bit. The bit is cleared on read by the bmp.
|
||||
func (d *Device) configurationError() bool {
|
||||
data, err := d.readRegister(RegErr, 1)
|
||||
return err == nil && (data[0]&0x04) != 0
|
||||
}
|
||||
|
||||
func (d *Device) readRegister(register byte, len int) (data []byte, err error) {
|
||||
data = make([]byte, len)
|
||||
err = d.bus.ReadRegister(d.Address, register, data)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) writeRegister(register byte, data byte) error {
|
||||
return d.bus.WriteRegister(d.Address, register, []byte{data})
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Package bmp388 provides a driver for Bosch's BMP388 digital temperature & pressure sensor.
|
||||
// The datasheet can be found here: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf
|
||||
package bmp388
|
||||
|
||||
const Address byte = 0x77 // default I2C address
|
||||
|
||||
const (
|
||||
RegChipId byte = 0x00 // useful for checking the connection
|
||||
RegCali byte = 0x31 // pressure & temperature compensation calibration coefficients
|
||||
RegPress byte = 0x04 // start of pressure data registers
|
||||
RegTemp byte = 0x07 // start of temperature data registers
|
||||
RegPwrCtrl byte = 0x1B // measurement mode & pressure/temperature sensor power register
|
||||
RegOSR byte = 0x1C // oversampling settings register
|
||||
RegODR byte = 0x1D //
|
||||
RegCmd byte = 0x7E // miscellaneous command register
|
||||
RegStat byte = 0x03 // sensor status register
|
||||
RegErr byte = 0x02 // error status register
|
||||
RegIIR byte = 0x1F
|
||||
)
|
||||
|
||||
const (
|
||||
ChipId byte = 0x50 // correct response if reading from chip id register
|
||||
PwrPress byte = 0x01 // power on pressure sensor
|
||||
PwrTemp byte = 0x02 // power on temperature sensor
|
||||
SoftReset byte = 0xB6 // command to reset all user configuration
|
||||
DRDYPress byte = 0x20 // for checking if pressure data is ready
|
||||
DRDYTemp byte = 0x40 // for checking if pressure data is ready
|
||||
)
|
||||
|
||||
// The difference between forced and normal mode is the bmp388 goes to sleep after taking a measurement in forced mode.
|
||||
// Set it to forced if you intend to take measurements sporadically and want to save power. The driver will handle
|
||||
// waking the sensor up when the sensor is in forced mode.
|
||||
const (
|
||||
Normal Mode = 0x30
|
||||
Forced Mode = 0x16
|
||||
Sleep Mode = 0x00
|
||||
)
|
||||
|
||||
// Increasing sampling rate increases precision but also the wait time for measurements. The datasheet has a table of
|
||||
// suggested values for oversampling, output data rates, and iir filter coefficients by use case.
|
||||
const (
|
||||
Sampling1X Oversampling = iota
|
||||
Sampling2X
|
||||
Sampling4X
|
||||
Sampling8X
|
||||
Sampling16X
|
||||
Sampling32X
|
||||
)
|
||||
|
||||
// Output data rates in Hz. If increasing the sampling rates you need to decrease the output data rates, else the bmp388
|
||||
// will freeze and Configure() will return a configuration error message. In that case keep decreasing the data rate
|
||||
// until the bmp is happy
|
||||
const (
|
||||
Odr200 OutputDataRate = iota
|
||||
Odr100
|
||||
Odr50
|
||||
Odr25
|
||||
Odr12p5
|
||||
Odr6p25
|
||||
Odr3p1
|
||||
Odr1p5
|
||||
Odr0p78
|
||||
Odr0p39
|
||||
Odr0p2
|
||||
Odr0p1
|
||||
Odr0p05
|
||||
Odr0p02
|
||||
Odr0p01
|
||||
Odr0p006
|
||||
Odr0p003
|
||||
Odr0p0015
|
||||
)
|
||||
|
||||
// IIR filter coefficients, higher values means steadier measurements but slower reaction times
|
||||
const (
|
||||
Coeff0 FilterCoefficient = iota
|
||||
Coeff1
|
||||
Coeff3
|
||||
Coeff7
|
||||
Coeff15
|
||||
Coeff31
|
||||
Coeff63
|
||||
Coeff127
|
||||
)
|
||||
@@ -0,0 +1,116 @@
|
||||
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
||||
//
|
||||
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
|
||||
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
|
||||
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// enum type for device type
|
||||
type DeviceType uint8
|
||||
|
||||
// DeviceType specific parsing of information received from the sensor
|
||||
func (d DeviceType) extractData(buf []byte) (temp int16, hum uint16) {
|
||||
if d == DHT11 {
|
||||
temp = int16(buf[2])
|
||||
if buf[3]&0x80 > 0 {
|
||||
temp = -1 - temp
|
||||
}
|
||||
temp *= 10
|
||||
temp += int16(buf[3] & 0x0f)
|
||||
hum = 10*uint16(buf[0]) + uint16(buf[1])
|
||||
} else {
|
||||
hum = binary.LittleEndian.Uint16(buf[0:2])
|
||||
temp = int16(buf[3])<<8 + int16(buf[2]&0x7f)
|
||||
if buf[2]&0x80 > 0 {
|
||||
temp = -temp
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Celsius and Fahrenheit temperature scales
|
||||
type TemperatureScale uint8
|
||||
|
||||
func (t TemperatureScale) convertToFloat(temp int16) float32 {
|
||||
if t == C {
|
||||
return float32(temp) / 10
|
||||
} else {
|
||||
// Fahrenheit
|
||||
return float32(temp)*(9.0/50.) + 32.
|
||||
}
|
||||
}
|
||||
|
||||
// All functions return ErrorCode instance as error. This class can be used for more efficient error processing
|
||||
type ErrorCode uint8
|
||||
|
||||
const (
|
||||
startTimeout = time.Millisecond * 200
|
||||
startingLow = time.Millisecond * 20
|
||||
|
||||
DHT11 DeviceType = iota
|
||||
DHT22
|
||||
|
||||
C TemperatureScale = iota
|
||||
F
|
||||
|
||||
ChecksumError ErrorCode = iota
|
||||
NoSignalError
|
||||
NoDataError
|
||||
UpdateError
|
||||
UninitializedDataError
|
||||
)
|
||||
|
||||
// error interface implementation for ErrorCode
|
||||
func (e ErrorCode) Error() string {
|
||||
switch e {
|
||||
case ChecksumError:
|
||||
// DHT returns ChecksumError if all the data from the sensor was received, but the checksum does not match.
|
||||
return "checksum mismatch"
|
||||
case NoSignalError:
|
||||
// DHT returns NoSignalError if there was no reply from the sensor. Check sensor connection or the correct pin
|
||||
// sis chosen,
|
||||
return "no signal"
|
||||
case NoDataError:
|
||||
// DHT returns NoDataError if the connection was successfully initialized, but not all 40 bits from
|
||||
// the sensor is received
|
||||
return "no data"
|
||||
case UpdateError:
|
||||
// DHT returns UpdateError if ReadMeasurements function is called before time specified in UpdatePolicy or
|
||||
// less than 2 seconds after past measurement
|
||||
return "cannot update now"
|
||||
case UninitializedDataError:
|
||||
// DHT returns UninitializedDataError if user attempts to access data before first measurement
|
||||
return "no measurements done"
|
||||
}
|
||||
// should never be reached
|
||||
return "unknown error"
|
||||
}
|
||||
|
||||
// Update policy of the DHT device. UpdateTime cannot be shorter than 2 seconds. According to dht specification sensor
|
||||
// will return undefined data if update requested less than 2 seconds before last usage
|
||||
type UpdatePolicy struct {
|
||||
UpdateTime time.Duration
|
||||
UpdateAutomatically bool
|
||||
}
|
||||
|
||||
var (
|
||||
// timeout counter equal to number of ticks per 1 millisecond
|
||||
timeout counter
|
||||
)
|
||||
|
||||
func init() {
|
||||
timeout = cyclesPerMillisecond()
|
||||
}
|
||||
|
||||
func cyclesPerMillisecond() counter {
|
||||
freq := machine.CPUFrequency()
|
||||
freq /= 1000
|
||||
return counter(freq)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// +build mimxrt1062 stm32f405 atsamd51 stm32f103xx k210 stm32f407
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
// This file provides a definition of the counter for boards with frequency higher than 2^8 ticks per millisecond (>64MHz)
|
||||
type counter uint32
|
||||
@@ -0,0 +1,6 @@
|
||||
// +build arduino atmega1284p nrf52840 digispark nrf52 arduino_nano nrf51 atsamd21 fe310 arduino_nano33 circuitplay_express arduino_mega2560
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
// This file provides a definition of the counter for boards with frequency lower than 2^8 ticks per millisecond (<64MHz)
|
||||
type counter uint16
|
||||
@@ -0,0 +1,218 @@
|
||||
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
||||
//
|
||||
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
|
||||
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
|
||||
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DummyDevice provides a basic interface for DHT devices.
|
||||
type DummyDevice interface {
|
||||
ReadMeasurements() error
|
||||
Measurements() (temperature int16, humidity uint16, err error)
|
||||
Temperature() (int16, error)
|
||||
TemperatureFloat(scale TemperatureScale) (float32, error)
|
||||
Humidity() (uint16, error)
|
||||
HumidityFloat() (float32, error)
|
||||
}
|
||||
|
||||
// Basic implementation of the DummyDevice
|
||||
// This implementation takes measurements from sensor only with ReadMeasurements function
|
||||
// and does not provide a protection from too frequent calls for measurements.
|
||||
// Since taking measurements from the sensor is time consuming procedure and blocks interrupts,
|
||||
// user can avoid any hidden calls to the sensor.
|
||||
type device struct {
|
||||
pin machine.Pin
|
||||
|
||||
measurements DeviceType
|
||||
initialized bool
|
||||
|
||||
temperature int16
|
||||
humidity uint16
|
||||
}
|
||||
|
||||
// ReadMeasurements reads data from the sensor.
|
||||
// According to documentation pin should be always, but the t *device restores pin to the state before call.
|
||||
func (t *device) ReadMeasurements() error {
|
||||
// initial waiting
|
||||
state := powerUp(t.pin)
|
||||
defer t.pin.Set(state)
|
||||
err := t.read()
|
||||
if err == nil {
|
||||
t.initialized = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Getter for temperature. Temperature method returns temperature as it is sent by device.
|
||||
// The temperature is measured temperature in Celsius multiplied by 10.
|
||||
// If no successful measurements for this device was performed, returns UninitializedDataError.
|
||||
func (t *device) Temperature() (int16, error) {
|
||||
if !t.initialized {
|
||||
return 0, UninitializedDataError
|
||||
}
|
||||
return t.temperature, nil
|
||||
}
|
||||
|
||||
// Getter for temperature. TemperatureFloat returns temperature in a given scale.
|
||||
// If no successful measurements for this device was performed, returns UninitializedDataError.
|
||||
func (t *device) TemperatureFloat(scale TemperatureScale) (float32, error) {
|
||||
if !t.initialized {
|
||||
return 0, UninitializedDataError
|
||||
}
|
||||
return scale.convertToFloat(t.temperature), nil
|
||||
}
|
||||
|
||||
// Getter for humidity. Humidity returns humidity as it is sent by device.
|
||||
// The humidity is measured in percentages multiplied by 10.
|
||||
// If no successful measurements for this device was performed, returns UninitializedDataError.
|
||||
func (t *device) Humidity() (uint16, error) {
|
||||
if !t.initialized {
|
||||
return 0, UninitializedDataError
|
||||
}
|
||||
return t.humidity, nil
|
||||
}
|
||||
|
||||
// Getter for humidity. HumidityFloat returns humidity in percentages.
|
||||
// If no successful measurements for this device was performed, returns UninitializedDataError.
|
||||
func (t *device) HumidityFloat() (float32, error) {
|
||||
if !t.initialized {
|
||||
return 0, UninitializedDataError
|
||||
}
|
||||
return float32(t.humidity) / 10., nil
|
||||
}
|
||||
|
||||
// Perform initialization of the communication protocol.
|
||||
// Device lowers the voltage on pin for startingLow=20ms and starts listening for response
|
||||
// Section 5.2 in [1]
|
||||
func initiateCommunication(p machine.Pin) {
|
||||
// Send low signal to the device
|
||||
p.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
p.Low()
|
||||
time.Sleep(startingLow)
|
||||
// Set pin to high and wait for reply
|
||||
p.High()
|
||||
p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
}
|
||||
|
||||
// Measurements returns both measurements: temperature and humidity as they sent by the device.
|
||||
// If no successful measurements for this device was performed, returns UninitializedDataError.
|
||||
func (t *device) Measurements() (temperature int16, humidity uint16, err error) {
|
||||
if !t.initialized {
|
||||
return 0, 0, UninitializedDataError
|
||||
}
|
||||
temperature = t.temperature
|
||||
humidity = t.humidity
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Main routine that performs communication with the sensor
|
||||
func (t *device) read() error {
|
||||
// initialize loop variables
|
||||
|
||||
// buffer for the data sent by the sensor. Sensor sends 40 bits = 5 bytes
|
||||
bufferData := [5]byte{}
|
||||
buf := bufferData[:]
|
||||
|
||||
// We perform measurements of the signal from the sensor by counting low and high cycles.
|
||||
// The bit is determined by the relative length of the high signal to low signal.
|
||||
// For 1, high signal will be longer than low, for 0---low is longer.
|
||||
// See section 5.3 [1]
|
||||
signalsData := [80]counter{}
|
||||
signals := signalsData[:]
|
||||
|
||||
// Start communication protocol with sensor
|
||||
initiateCommunication(t.pin)
|
||||
// Wait for sensor's response and abort if sensor does not reply
|
||||
err := waitForDataTransmission(t.pin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// count low and high cycles for sensor's reply
|
||||
receiveSignals(t.pin, signals)
|
||||
|
||||
// process received signals and store the result in the buffer. Abort if data transmission was interrupted and not
|
||||
// all 40 bits were received
|
||||
err = t.extractData(signals[:], buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Compute checksum and compare it to the one in data. Abort if checksum is incorrect
|
||||
if !isValid(buf[:]) {
|
||||
return ChecksumError
|
||||
}
|
||||
|
||||
// Extract temperature and humidity data from buffer
|
||||
t.temperature, t.humidity = t.measurements.extractData(buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// receiveSignals counts number of low and high cycles. The execution is time critical, so the function disables
|
||||
// interrupts
|
||||
func receiveSignals(pin machine.Pin, result []counter) {
|
||||
i := uint8(0)
|
||||
machine.UART1.Interrupt.Disable()
|
||||
defer machine.UART1.Interrupt.Enable()
|
||||
for ; i < 40; i++ {
|
||||
result[i*2] = expectChange(pin, false)
|
||||
result[i*2+1] = expectChange(pin, true)
|
||||
}
|
||||
}
|
||||
|
||||
// extractData process signal counters and transforms them into bits.
|
||||
// if any of the bits were not received (timed-out), returns NoDataError
|
||||
func (t *device) extractData(signals []counter, buf []uint8) error {
|
||||
for i := uint8(0); i < 40; i++ {
|
||||
lowCycle := signals[i*2]
|
||||
highCycle := signals[i*2+1]
|
||||
if lowCycle == timeout || highCycle == timeout {
|
||||
return NoDataError
|
||||
}
|
||||
byteN := i >> 3
|
||||
buf[byteN] <<= 1
|
||||
if highCycle > lowCycle {
|
||||
buf[byteN] |= 1
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitForDataTransmission waits for reply from the sensor.
|
||||
// If no reply received, returns NoSignalError.
|
||||
// For more details, see section 5.2 in [1]
|
||||
func waitForDataTransmission(p machine.Pin) error {
|
||||
// wait for thermometer to pull down
|
||||
if expectChange(p, true) == timeout {
|
||||
return NoSignalError
|
||||
}
|
||||
//wait for thermometer to pull up
|
||||
if expectChange(p, false) == timeout {
|
||||
return NoSignalError
|
||||
}
|
||||
// wait for thermometer to pull down and start sending the data
|
||||
if expectChange(p, true) == timeout {
|
||||
return NoSignalError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Constructor function for a DummyDevice implementation.
|
||||
// This device provides full control to the user.
|
||||
// It does not do any hidden measurements calls and does not check
|
||||
// for 2 seconds delay between measurements.
|
||||
func NewDummyDevice(pin machine.Pin, deviceType DeviceType) DummyDevice {
|
||||
pin.High()
|
||||
return &device{
|
||||
pin: pin,
|
||||
measurements: deviceType,
|
||||
initialized: false,
|
||||
temperature: 0,
|
||||
humidity: 0,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
||||
//
|
||||
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
|
||||
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
|
||||
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Device interface provides main functionality of the DHTXX sensors.
|
||||
type Device interface {
|
||||
DummyDevice
|
||||
Configure(policy UpdatePolicy)
|
||||
}
|
||||
|
||||
// managedDevice struct provides time control and optional automatic data retrieval from the sensor.
|
||||
// It delegates all the functionality to device
|
||||
type managedDevice struct {
|
||||
t device
|
||||
lastUpdate time.Time
|
||||
policy UpdatePolicy
|
||||
}
|
||||
|
||||
// Measurements returns both measurements: temperature and humidity as they sent by the device.
|
||||
// Depending on the UpdatePolicy of the device may update cached measurements.
|
||||
func (m *managedDevice) Measurements() (temperature int16, humidity uint16, err error) {
|
||||
err = m.checkForUpdateOnDataRequest()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return m.t.Measurements()
|
||||
}
|
||||
|
||||
// Getter for temperature. Temperature method returns temperature as it is sent by device.
|
||||
// The temperature is measured temperature in Celsius multiplied by 10.
|
||||
// Depending on the UpdatePolicy of the device may update cached measurements.
|
||||
func (m *managedDevice) Temperature() (temp int16, err error) {
|
||||
err = m.checkForUpdateOnDataRequest()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
temp, err = m.t.Temperature()
|
||||
return
|
||||
}
|
||||
|
||||
func (m *managedDevice) checkForUpdateOnDataRequest() (err error) {
|
||||
// update if necessary
|
||||
if m.policy.UpdateAutomatically {
|
||||
err = m.ReadMeasurements()
|
||||
}
|
||||
// ignore error if the data was updated recently
|
||||
// interface comparison does not work in tinygo. Therefore need to cast to explicit type
|
||||
if code, ok := err.(ErrorCode); ok && code == UpdateError {
|
||||
err = nil
|
||||
}
|
||||
// add error if the data is not initialized
|
||||
if !m.t.initialized {
|
||||
err = UninitializedDataError
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Getter for temperature. TemperatureFloat returns temperature in a given scale.
|
||||
// Depending on the UpdatePolicy of the device may update cached measurements.
|
||||
func (m *managedDevice) TemperatureFloat(scale TemperatureScale) (float32, error) {
|
||||
err := m.checkForUpdateOnDataRequest()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return m.t.TemperatureFloat(scale)
|
||||
}
|
||||
|
||||
// Getter for humidity. Humidity returns humidity as it is sent by device.
|
||||
// The humidity is measured in percentages multiplied by 10.
|
||||
// Depending on the UpdatePolicy of the device may update cached measurements.
|
||||
func (m *managedDevice) Humidity() (hum uint16, err error) {
|
||||
err = m.checkForUpdateOnDataRequest()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return m.t.Humidity()
|
||||
}
|
||||
|
||||
// Getter for humidity. HumidityFloat returns humidity in percentages.
|
||||
// Depending on the UpdatePolicy of the device may update cached measurements.
|
||||
func (m *managedDevice) HumidityFloat() (float32, error) {
|
||||
err := m.checkForUpdateOnDataRequest()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return m.t.HumidityFloat()
|
||||
}
|
||||
|
||||
// ReadMeasurements reads data from the sensor.
|
||||
// The function will return UpdateError if it is called more frequently than specified in UpdatePolicy
|
||||
func (m *managedDevice) ReadMeasurements() (err error) {
|
||||
timestamp := time.Now()
|
||||
if !m.t.initialized || timestamp.Sub(m.lastUpdate) > m.policy.UpdateTime {
|
||||
err = m.t.ReadMeasurements()
|
||||
} else {
|
||||
err = UpdateError
|
||||
}
|
||||
if err == nil {
|
||||
m.lastUpdate = timestamp
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Configure configures UpdatePolicy for Device.
|
||||
// Configure checks for policy.UpdateTime and prevent from updating more frequently than specified in [1][2]
|
||||
// to prevent undefined behaviour of the sensor.
|
||||
func (m *managedDevice) Configure(policy UpdatePolicy) {
|
||||
if policy.UpdateAutomatically && policy.UpdateTime < time.Second*2 {
|
||||
policy.UpdateTime = time.Second * 2
|
||||
}
|
||||
m.policy = policy
|
||||
}
|
||||
|
||||
// Constructor of the Device implementation.
|
||||
// This implementation updates data every 2 seconds during data access.
|
||||
func New(pin machine.Pin, deviceType DeviceType) Device {
|
||||
pin.High()
|
||||
return &managedDevice{
|
||||
t: device{
|
||||
pin: pin,
|
||||
measurements: deviceType,
|
||||
initialized: false,
|
||||
},
|
||||
lastUpdate: time.Time{},
|
||||
policy: UpdatePolicy{
|
||||
UpdateTime: time.Second * 2,
|
||||
UpdateAutomatically: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor of the Device implementation with given UpdatePolicy
|
||||
func NewWithPolicy(pin machine.Pin, deviceType DeviceType, updatePolicy UpdatePolicy) Device {
|
||||
pin.High()
|
||||
result := &managedDevice{
|
||||
t: device{
|
||||
pin: pin,
|
||||
measurements: deviceType,
|
||||
initialized: false,
|
||||
},
|
||||
lastUpdate: time.Time{},
|
||||
}
|
||||
result.Configure(updatePolicy)
|
||||
return result
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Check if the pin is disabled
|
||||
func powerUp(p machine.Pin) bool {
|
||||
state := p.Get()
|
||||
if !state {
|
||||
p.High()
|
||||
time.Sleep(startTimeout)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func expectChange(p machine.Pin, oldState bool) counter {
|
||||
cnt := counter(0)
|
||||
for ; p.Get() == oldState && cnt != timeout; cnt++ {
|
||||
}
|
||||
return cnt
|
||||
}
|
||||
|
||||
func checksum(buf []uint8) uint8 {
|
||||
return buf[4]
|
||||
}
|
||||
func computeChecksum(buf []uint8) uint8 {
|
||||
return buf[0] + buf[1] + buf[2] + buf[3]
|
||||
}
|
||||
|
||||
func isValid(buf []uint8) bool {
|
||||
return checksum(buf) == computeChecksum(buf)
|
||||
}
|
||||
+2
-2
@@ -42,8 +42,8 @@ func (d *Device) SetTime(t time.Time) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Time returns the time and date
|
||||
func (d *Device) Time() (time.Time, error) {
|
||||
// ReadTime returns the date and time
|
||||
func (d *Device) ReadTime() (time.Time, error) {
|
||||
data := make([]byte, 8)
|
||||
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
|
||||
if err != nil {
|
||||
|
||||
+3
-3
@@ -20,17 +20,17 @@ package espat // import "tinygo.org/x/drivers/espat"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/net"
|
||||
)
|
||||
|
||||
// Device wraps UART connection to the ESP8266/ESP32.
|
||||
type Device struct {
|
||||
bus machine.UART
|
||||
bus drivers.UART
|
||||
|
||||
// command responses that come back from the ESP8266/ESP32
|
||||
response []byte
|
||||
@@ -43,7 +43,7 @@ type Device struct {
|
||||
var ActiveDevice *Device
|
||||
|
||||
// New returns a new espat driver. Pass in a fully configured UART bus.
|
||||
func New(b machine.UART) *Device {
|
||||
func New(b drivers.UART) *Device {
|
||||
return &Device{bus: b, response: make([]byte, 512), socketdata: make([]byte, 0, 1024)}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
i2c = &machine.I2C0
|
||||
i2c = machine.I2C0
|
||||
sensor = adt7410.New(i2c)
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/aht20"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
dev := aht20.New(machine.I2C0)
|
||||
dev.Configure()
|
||||
|
||||
dev.Reset()
|
||||
for {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
err := dev.Read()
|
||||
if err != nil {
|
||||
println("Error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
println("temp ", fmtD(dev.DeciCelsius(), 3, 1), "C")
|
||||
println("humidity", fmtD(dev.DeciRelHumidity(), 3, 1), "%")
|
||||
}
|
||||
}
|
||||
|
||||
func fmtD(val int32, i int, f int) string {
|
||||
result := make([]byte, i+f+1)
|
||||
neg := false
|
||||
|
||||
if val < 0 {
|
||||
val = -val
|
||||
neg = true
|
||||
}
|
||||
|
||||
for p := len(result) - 1; p >= 0; p-- {
|
||||
result[p] = byte(int32('0') + (val % 10))
|
||||
val = val / 10
|
||||
|
||||
if p == i+1 && p > 0 {
|
||||
p--
|
||||
result[p] = '.'
|
||||
}
|
||||
}
|
||||
|
||||
if neg {
|
||||
result[0] = '-'
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
var (
|
||||
apa apa102.Device
|
||||
|
||||
led = machine.PWM{machine.LED}
|
||||
pwm = machine.TCC0
|
||||
leds = make([]color.RGBA, 1)
|
||||
wheel = &Wheel{Brightness: 0x10}
|
||||
)
|
||||
@@ -27,12 +27,19 @@ func init() {
|
||||
apa = apa102.NewSoftwareSPI(machine.PA00, machine.PA01, 1)
|
||||
|
||||
// Configure the regular on-board LED for PWM fading
|
||||
machine.InitPWM()
|
||||
led.Configure()
|
||||
|
||||
err := pwm.Configure(machine.PWMConfig{})
|
||||
if err != nil {
|
||||
println("failed to configure PWM")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
channelLED, err := pwm.Channel(machine.LED)
|
||||
if err != nil {
|
||||
println("failed to configure LED PWM channel")
|
||||
return
|
||||
}
|
||||
|
||||
// We'll fade the on-board LED in a goroutine to show/ensure that the APA102
|
||||
// works fine with the scheduler enabled. Comment this out to test this code
|
||||
@@ -43,11 +50,11 @@ func main() {
|
||||
brightening = !brightening
|
||||
continue
|
||||
}
|
||||
var brightness uint16 = uint16(i) << 8
|
||||
var brightness uint32 = uint32(i)
|
||||
if !brightening {
|
||||
brightness = 0xFFFF - brightness
|
||||
brightness = 256 - brightness
|
||||
}
|
||||
led.Set(brightness)
|
||||
pwm.Set(channelLED, pwm.Top()*brightness/256)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/bmp388"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
sensor := bmp388.New(machine.I2C0)
|
||||
if !sensor.Connected() {
|
||||
println("Uh oh, BMP388 not detected")
|
||||
return
|
||||
}
|
||||
|
||||
// The accuracy of the sensor can be increased, at the cost of a slower output rate. Table 9 in Section 3.5 of the
|
||||
// datasheet has recommended settings for common use cases. If increasing the sampling rate, the output data rate
|
||||
// (ODR) will likely have to be decreased. Configure() will return an error if there's a problem with the
|
||||
// configuration settings - keep decreasing the ODR and cycling the power to the sensor until it is happy.
|
||||
err := sensor.Configure(bmp388.Config{
|
||||
Pressure: bmp388.Sampling8X,
|
||||
Temperature: bmp388.Sampling2X,
|
||||
ODR: bmp388.Odr25,
|
||||
IIR: bmp388.Coeff0,
|
||||
Mode: bmp388.Normal,
|
||||
})
|
||||
|
||||
// This is also fine
|
||||
// err := sensor.Configure(bmp388.BMP388Config{})
|
||||
|
||||
if err != nil {
|
||||
println(err)
|
||||
}
|
||||
|
||||
for {
|
||||
temp, err := sensor.ReadTemperature() // returns the temperature in centicelsius
|
||||
press, err := sensor.ReadPressure() // returns the pressure in centipascals
|
||||
|
||||
if err != nil {
|
||||
println(err)
|
||||
} else {
|
||||
println("Temperature: " + strconv.FormatInt(int64(temp), 10) + " cC")
|
||||
println("Pressure: " + strconv.FormatInt(int64(press), 10) + " cPa\n")
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
"tinygo.org/x/drivers/dht"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pin := machine.D6
|
||||
dhtSensor := dht.New(pin, dht.DHT11)
|
||||
for {
|
||||
temp, hum, err := dhtSensor.Measurements()
|
||||
if err != nil {
|
||||
fmt.Printf("Temperature: %02d.%d°C, Humidity: %02d.%d%%\n", temp/10, temp%10, hum/10, hum%10)
|
||||
} else {
|
||||
fmt.Printf("Could not take measurements from the sensor: %s\n", err.Error())
|
||||
}
|
||||
// Measurements cannot be updated only 2 seconds. More frequent calls will return the same value
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ func main() {
|
||||
rtc.SetTime(time.Date(2019, 5, 15, 20, 34, 12, 0, time.UTC))
|
||||
|
||||
for {
|
||||
t, err := rtc.Time()
|
||||
t, err := rtc.ReadTime()
|
||||
if err != nil {
|
||||
println("Error reading date:", err)
|
||||
break
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
func main() {
|
||||
println("GPS I2C Example")
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
ublox := gps.NewI2C(&machine.I2C0)
|
||||
ublox := gps.NewI2C(machine.I2C0)
|
||||
parser := gps.NewParser()
|
||||
var fix gps.Fix
|
||||
for {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package hcsr04
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ina260"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
dev := ina260.New(machine.I2C0)
|
||||
dev.Configure(ina260.Config{
|
||||
AverageMode: ina260.AVGMODE_16,
|
||||
VoltConvTime: ina260.CONVTIME_140USEC,
|
||||
CurrentConvTime: ina260.CONVTIME_140USEC,
|
||||
Mode: ina260.MODE_CONTINUOUS | ina260.MODE_VOLTAGE | ina260.MODE_CURRENT,
|
||||
})
|
||||
|
||||
if dev.Connected() {
|
||||
println("INA260 detected")
|
||||
} else {
|
||||
println("INA260 NOT detected")
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
microvolts := dev.Voltage()
|
||||
microamps := dev.Current()
|
||||
microwatts := dev.Power()
|
||||
|
||||
println(fmtD(microvolts, 4, 3), "mV,", fmtD(microamps, 4, 3), "mA,", fmtD(microwatts, 4, 3), "mW")
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func fmtD(val int32, i int, f int) string {
|
||||
result := make([]byte, i+f+1)
|
||||
neg := false
|
||||
|
||||
if val < 0 {
|
||||
val = -val
|
||||
neg = true
|
||||
}
|
||||
|
||||
for p := len(result) - 1; p >= 0; p-- {
|
||||
result[p] = byte(int32('0') + (val % 10))
|
||||
val = val / 10
|
||||
|
||||
if p == i+1 && p > 0 {
|
||||
p--
|
||||
result[p] = '.'
|
||||
}
|
||||
}
|
||||
|
||||
if neg {
|
||||
result[0] = '-'
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/keypad4x4"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mapping := map[uint8]string{
|
||||
1: "1",
|
||||
2: "2",
|
||||
3: "3",
|
||||
4: "A",
|
||||
5: "4",
|
||||
6: "5",
|
||||
7: "6",
|
||||
8: "B",
|
||||
9: "7",
|
||||
10: "8",
|
||||
11: "9",
|
||||
12: "C",
|
||||
13: "*",
|
||||
14: "0",
|
||||
15: "#",
|
||||
16: "D",
|
||||
}
|
||||
|
||||
keypadDevice := keypad4x4.NewDevice(machine.D2, machine.D3, machine.D4, machine.D5, machine.D6, machine.D7, machine.D8, machine.D9)
|
||||
keypadDevice.Configure()
|
||||
|
||||
for {
|
||||
key := keypadDevice.GetKey()
|
||||
if key != keypad4x4.NoKeyPressed {
|
||||
println("Button: ", mapping[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,31 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxSpeed = 30000
|
||||
maxSpeed = 100
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.InitPWM()
|
||||
err := machine.TCC0.Configure(machine.PWMConfig{
|
||||
Period: 16384e3, // 16.384ms
|
||||
})
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
wheel := l293x.NewWithSpeed(machine.D10, machine.D11, machine.PWM{machine.D12})
|
||||
spc, err := machine.TCC0.Channel(machine.D12)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
wheel := l293x.NewWithSpeed(machine.D10, machine.D11, spc, machine.TCC0)
|
||||
wheel.Configure()
|
||||
|
||||
for i := 0; i <= 10; i++ {
|
||||
println("Forward")
|
||||
var i uint16
|
||||
for i = 0; i < maxSpeed; i += 1000 {
|
||||
var i uint32
|
||||
for i = 0; i < maxSpeed; i += 10 {
|
||||
wheel.Forward(i)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
@@ -30,7 +42,7 @@ func main() {
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
println("Backward")
|
||||
for i = 0; i < maxSpeed; i += 1000 {
|
||||
for i = 0; i < maxSpeed; i += 10 {
|
||||
wheel.Backward(i)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
@@ -8,19 +8,38 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxSpeed = 30000
|
||||
maxSpeed = 100
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.InitPWM()
|
||||
machine.D11.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
machine.D12.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
wheel := l9110x.NewWithSpeed(machine.PWM{machine.D11}, machine.PWM{machine.D12})
|
||||
err := machine.TCC0.Configure(machine.PWMConfig{})
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ca, err := machine.TCC0.Channel(machine.D11)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cb, err := machine.TCC0.Channel(machine.D12)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
wheel := l9110x.NewWithSpeed(ca, cb, machine.TCC0)
|
||||
wheel.Configure()
|
||||
|
||||
for i := 0; i <= 10; i++ {
|
||||
println("Forward")
|
||||
var i uint16
|
||||
for i = 0; i < maxSpeed; i += 1000 {
|
||||
var i uint32
|
||||
for i = 0; i < maxSpeed; i += 10 {
|
||||
wheel.Forward(i)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
@@ -30,7 +49,7 @@ func main() {
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
println("Backward")
|
||||
for i = 0; i < maxSpeed; i += 1000 {
|
||||
for i = 0; i < maxSpeed; i += 10 {
|
||||
wheel.Backward(i)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/max72xx"
|
||||
)
|
||||
|
||||
// example for a 4 digit 7 segment display
|
||||
func main() {
|
||||
// Pins for Arduino Nano 33 IOT
|
||||
err := machine.SPI0.Configure(machine.SPIConfig{
|
||||
SDO: machine.D11, // default SDO pin
|
||||
SCK: machine.D13, // default sck pin
|
||||
LSBFirst: false,
|
||||
Frequency: 10000000,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
|
||||
driver := max72xx.NewDevice(machine.SPI0, machine.D6)
|
||||
driver.Configure()
|
||||
|
||||
digitNumber := 4
|
||||
|
||||
driver.StopDisplayTest()
|
||||
driver.SetDecodeMode(4)
|
||||
driver.SetScanLimit(4)
|
||||
driver.StopShutdownMode()
|
||||
|
||||
for i := 1; i < int(digitNumber); i++ {
|
||||
driver.WriteCommand(byte(i), byte(Blank))
|
||||
}
|
||||
|
||||
for {
|
||||
for _, character := range characters {
|
||||
println("writing", "characterValue:", character.String())
|
||||
driver.WriteCommand(byte(4), byte(character))
|
||||
driver.WriteCommand(byte(3), byte(character))
|
||||
driver.WriteCommand(byte(2), byte(character))
|
||||
driver.WriteCommand(byte(1), byte(character))
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var characters = []Character{
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five,
|
||||
Six,
|
||||
Seven,
|
||||
Eight,
|
||||
Nine,
|
||||
Dash,
|
||||
E,
|
||||
H,
|
||||
L,
|
||||
P,
|
||||
Blank,
|
||||
Dot,
|
||||
}
|
||||
|
||||
// Each bit translates to a pin, which is driven high or low
|
||||
type Character byte
|
||||
|
||||
func (char Character) String() string {
|
||||
switch char {
|
||||
case Zero:
|
||||
return "0"
|
||||
case One:
|
||||
return "1"
|
||||
case Two:
|
||||
return "2"
|
||||
case Three:
|
||||
return "3"
|
||||
case Four:
|
||||
return "4"
|
||||
case Five:
|
||||
return "5"
|
||||
case Six:
|
||||
return "6"
|
||||
case Seven:
|
||||
return "7"
|
||||
case Eight:
|
||||
return "8"
|
||||
case Nine:
|
||||
return "9"
|
||||
case Dash:
|
||||
return "-"
|
||||
case E:
|
||||
return "E"
|
||||
case H:
|
||||
return "H"
|
||||
case L:
|
||||
return "L"
|
||||
case P:
|
||||
return "P"
|
||||
case Blank:
|
||||
return ""
|
||||
case Dot:
|
||||
return "."
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
const (
|
||||
Zero Character = 0 //126
|
||||
One Character = 1 //48
|
||||
Two Character = 2 // 109
|
||||
Three Character = 3 // 121
|
||||
Four Character = 4
|
||||
Five Character = 5
|
||||
Six Character = 6
|
||||
Seven Character = 7
|
||||
Eight Character = 8
|
||||
Nine Character = 9
|
||||
Dash Character = 10
|
||||
E Character = 11
|
||||
H Character = 12
|
||||
L Character = 13
|
||||
P Character = 14
|
||||
Blank Character = 15
|
||||
Dot Character = 128
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
// This example demonstrates putting several mcp23017 devices together into
|
||||
// a single virtual I/O array.
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/mcp23017"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Assume the devices are at addresses 0x20, 0x21
|
||||
dev, err := mcp23017.NewI2CDevices(machine.I2C0, 0x20, 0x21)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Configure pin 0 for input and all the others for output.
|
||||
if err := dev.SetModes([]mcp23017.PinMode{
|
||||
mcp23017.Input | mcp23017.Pullup,
|
||||
mcp23017.Output,
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
input := dev.Pin(0)
|
||||
// Make a mask that represents all the output pins.
|
||||
// Note that this leverages the driver behaviour which replicates the highest bit in
|
||||
// the last slice element (1 in this case) to all other pins
|
||||
outputMask := mcp23017.PinSlice{^mcp23017.Pins(1 << 0)} // All except pin 0
|
||||
inputVal, err := input.Get()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
println("input value: ", inputVal)
|
||||
// Set the values of all the output pins.
|
||||
err = dev.SetPins(mcp23017.PinSlice{0b1011011_01101110, 0b11111101_11100110}, outputMask)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/mcp23017"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dev, err := mcp23017.NewI2C(machine.I2C0, 0x20)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Configure pin 0 for input and all the others for output.
|
||||
if err := dev.SetModes([]mcp23017.PinMode{
|
||||
mcp23017.Input | mcp23017.Pullup,
|
||||
mcp23017.Output,
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
input := dev.Pin(0)
|
||||
outputMask := ^mcp23017.Pins(1 << 0) // All except pin 0
|
||||
inputVal, err := input.Get()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
println("input value: ", inputVal)
|
||||
// Set the values of all the output pins.
|
||||
err = dev.SetPins(0b1011011_01101110, outputMask)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/mcp2515"
|
||||
)
|
||||
|
||||
var (
|
||||
spi = machine.SPI0
|
||||
csPin = machine.D5
|
||||
)
|
||||
|
||||
func main() {
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 115200,
|
||||
SCK: machine.SPI0_SCK_PIN,
|
||||
SDO: machine.SPI0_SDO_PIN,
|
||||
SDI: machine.SPI0_SDI_PIN,
|
||||
Mode: 0})
|
||||
can := mcp2515.New(spi, csPin)
|
||||
can.Configure()
|
||||
err := can.Begin(mcp2515.CAN500kBps, mcp2515.Clock8MHz)
|
||||
if err != nil {
|
||||
failMessage(err.Error())
|
||||
}
|
||||
|
||||
for {
|
||||
err := can.Tx(0x111, 8, []byte{0x00, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA})
|
||||
if err != nil {
|
||||
failMessage(err.Error())
|
||||
}
|
||||
if can.Received() {
|
||||
msg, err := can.Rx()
|
||||
if err != nil {
|
||||
failMessage(err.Error())
|
||||
}
|
||||
fmt.Printf("CAN-ID: %03X dlc: %d data: ", msg.ID, msg.Dlc)
|
||||
for _, b := range msg.Data {
|
||||
fmt.Printf("%02X ", b)
|
||||
}
|
||||
fmt.Print("\r\n")
|
||||
}
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
}
|
||||
|
||||
func failMessage(msg string) {
|
||||
for {
|
||||
println(msg)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/p1am"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for {
|
||||
if err := loop(); err != nil {
|
||||
fmt.Printf("loop failed, retrying: %v\n", err)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
func loop() error {
|
||||
led := machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
sw := machine.SWITCH
|
||||
sw.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
|
||||
if err := p1am.Controller.Initialize(); err != nil {
|
||||
return fmt.Errorf("initializing controller: %w", err)
|
||||
}
|
||||
|
||||
version, err := p1am.Controller.Version()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching base controller version: %w", err)
|
||||
}
|
||||
fmt.Printf("Base controller version: %d.%d.%d\n", version[0], version[1], version[2])
|
||||
|
||||
for i := 1; i <= p1am.Controller.Slots; i++ {
|
||||
slot := p1am.Controller.Slot(i)
|
||||
fmt.Printf("Slot %d: ID 0x%08x, Props %+v\n", i, slot.ID, slot.Props)
|
||||
}
|
||||
|
||||
slot1 := p1am.Controller.Slot(1)
|
||||
var lastInput uint32
|
||||
state := sw.Get()
|
||||
for {
|
||||
if active, err := p1am.Controller.Active(); err != nil || !active {
|
||||
return fmt.Errorf("controller active %v: %v", active, err)
|
||||
}
|
||||
if state != sw.Get() {
|
||||
state = sw.Get()
|
||||
fmt.Printf("New switch state: %v\n", state)
|
||||
if slot1.Props.DO > 0 {
|
||||
if err := slot1.Channel(1).WriteDiscrete(state); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if slot1.Props.DI > 0 {
|
||||
sstate, err := slot1.ReadDiscrete()
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading slot: %w", err)
|
||||
}
|
||||
if sstate != lastInput {
|
||||
lastInput = sstate
|
||||
fmt.Printf("new DI state: %#b\n", sstate)
|
||||
}
|
||||
}
|
||||
if state {
|
||||
led.High()
|
||||
} else {
|
||||
led.Low()
|
||||
}
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/pcf8563"
|
||||
)
|
||||
|
||||
var (
|
||||
i2c = machine.I2C0
|
||||
rtc = pcf8563.New(i2c)
|
||||
)
|
||||
|
||||
func main() {
|
||||
i2c.Configure(machine.I2CConfig{Frequency: machine.TWI_FREQ_400KHZ})
|
||||
rtc.Reset()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
rtc.SetTime(time.Date(2006, 1, 2, 15, 4, 50, 0, time.UTC))
|
||||
|
||||
rtc.SetAlarm(time.Date(2006, 1, 2, 15, 5, 0, 0, time.UTC))
|
||||
rtc.EnableAlarmInterrupt()
|
||||
|
||||
prev := -1
|
||||
|
||||
for {
|
||||
for {
|
||||
t, _ := rtc.ReadTime()
|
||||
if prev != t.Second() {
|
||||
fmt.Printf("%s\r\n", t.String())
|
||||
prev = t.Second()
|
||||
|
||||
if rtc.AlarmTriggered() {
|
||||
fmt.Printf("alarm triggered\r\n")
|
||||
rtc.ClearAlarm()
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/pcf8563"
|
||||
)
|
||||
|
||||
var (
|
||||
i2c = machine.I2C0
|
||||
rtc = pcf8563.New(i2c)
|
||||
)
|
||||
|
||||
func main() {
|
||||
i2c.Configure(machine.I2CConfig{Frequency: machine.TWI_FREQ_400KHZ})
|
||||
rtc.Reset()
|
||||
|
||||
for {
|
||||
rtc.SetOscillatorFrequency(pcf8563.RTC_COT_1HZ)
|
||||
time.Sleep(3 * time.Second)
|
||||
rtc.SetOscillatorFrequency(pcf8563.RTC_COT_32HZ)
|
||||
time.Sleep(3 * time.Second)
|
||||
rtc.SetOscillatorFrequency(pcf8563.RTC_COT_1KHZ)
|
||||
time.Sleep(3 * time.Second)
|
||||
rtc.SetOscillatorFrequency(pcf8563.RTC_COT_32KHZ)
|
||||
time.Sleep(3 * time.Second)
|
||||
rtc.SetOscillatorFrequency(pcf8563.RTC_COT_DISABLE)
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/pcf8563"
|
||||
)
|
||||
|
||||
var (
|
||||
i2c = machine.I2C0
|
||||
rtc = pcf8563.New(i2c)
|
||||
)
|
||||
|
||||
func main() {
|
||||
i2c.Configure(machine.I2CConfig{Frequency: machine.TWI_FREQ_400KHZ})
|
||||
rtc.Reset()
|
||||
|
||||
rtc.SetTime(time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC))
|
||||
|
||||
prev := -1
|
||||
|
||||
for {
|
||||
for {
|
||||
t, _ := rtc.ReadTime()
|
||||
if prev != t.Second() {
|
||||
fmt.Printf("%s\r\n", t.String())
|
||||
prev = t.Second()
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/pcf8563"
|
||||
)
|
||||
|
||||
var (
|
||||
i2c = machine.I2C0
|
||||
rtc = pcf8563.New(i2c)
|
||||
)
|
||||
|
||||
func main() {
|
||||
i2c.Configure(machine.I2CConfig{Frequency: machine.TWI_FREQ_400KHZ})
|
||||
rtc.Reset()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
rtc.SetTime(time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC))
|
||||
|
||||
rtc.SetTimer(15 * time.Second)
|
||||
rtc.EnableTimerInterrupt()
|
||||
|
||||
prev := -1
|
||||
|
||||
for {
|
||||
for {
|
||||
t, _ := rtc.ReadTime()
|
||||
if prev != t.Second() {
|
||||
fmt.Printf("%s\r\n", t.String())
|
||||
prev = t.Second()
|
||||
|
||||
if rtc.TimerTriggered() {
|
||||
fmt.Printf("timer triggered\r\n")
|
||||
rtc.ClearTimer()
|
||||
rtc.SetTimer(10 * time.Second)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/servo"
|
||||
)
|
||||
|
||||
// Configuration for the Arduino Uno.
|
||||
// Please change the PWM and pin if you want to try this example on a different
|
||||
// board.
|
||||
var (
|
||||
pwm = machine.Timer1
|
||||
pin = machine.D9
|
||||
)
|
||||
|
||||
func main() {
|
||||
s, err := servo.New(pwm, pin)
|
||||
if err != nil {
|
||||
for {
|
||||
println("could not configure servo")
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// This example shows how to use 128x64 display over I2C
|
||||
// Tested on Seeeduino XIAO Expansion Board https://wiki.seeedstudio.com/Seeeduino-XIAO-Expansion-Board/
|
||||
//
|
||||
// According to manual, I2C address of the display is 0x78, but that's 8-bit address.
|
||||
// TinyGo operates on 7-bit addresses and respective 7-bit address would be 0x3C, which we use below.
|
||||
//
|
||||
// To learn more about different types of I2C addresses, please see following page
|
||||
// https://www.totalphase.com/support/articles/200349176-7-bit-8-bit-and-10-bit-I2C-Slave-Addressing
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1306"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
|
||||
display := ssd1306.NewI2C(machine.I2C0)
|
||||
display.Configure(ssd1306.Config{
|
||||
Address: 0x3C,
|
||||
Width: 128,
|
||||
Height: 64,
|
||||
})
|
||||
|
||||
display.ClearDisplay()
|
||||
|
||||
x := int16(0)
|
||||
y := int16(0)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == 127 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == 63 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package ssd1331
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package ssd1351
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/tm1637"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
tm := tm1637.New(machine.D2, machine.D3, 7) // clk, dio, brightness
|
||||
tm.Configure()
|
||||
|
||||
tm.ClearDisplay()
|
||||
|
||||
tm.DisplayText([]byte("Tiny"))
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
tm.ClearDisplay()
|
||||
|
||||
tm.DisplayChr(byte('G'), 1)
|
||||
tm.DisplayDigit(0, 2) // looks like O
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
tm.DisplayClock(12, 59, true)
|
||||
|
||||
for i := uint8(0); i < 8; i++ {
|
||||
tm.Brightness(i)
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
}
|
||||
|
||||
i := int16(0)
|
||||
for {
|
||||
tm.DisplayNumber(i)
|
||||
i++
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/tone"
|
||||
)
|
||||
|
||||
var (
|
||||
// Configuration for the Adafruit Circuit Playground Bluefruit.
|
||||
pwm = machine.PWM0
|
||||
pin = machine.D12
|
||||
)
|
||||
|
||||
func main() {
|
||||
speaker, err := tone.New(pwm, pin)
|
||||
if err != nil {
|
||||
println("failed to configure PWM")
|
||||
return
|
||||
}
|
||||
|
||||
// Two tone siren.
|
||||
for {
|
||||
println("nee")
|
||||
speaker.SetNote(tone.B5)
|
||||
time.Sleep(time.Second / 2)
|
||||
|
||||
println("naw")
|
||||
speaker.SetNote(tone.A5)
|
||||
time.Sleep(time.Second / 2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// This example connects to Access Point and prints some info
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/wifinina"
|
||||
)
|
||||
|
||||
// access point info
|
||||
const ssid = ""
|
||||
const pass = ""
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
spi = machine.NINA_SPI
|
||||
|
||||
// this is the ESP chip that has the WIFININA firmware flashed on it
|
||||
adaptor *wifinina.Device
|
||||
)
|
||||
|
||||
func setup() {
|
||||
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 8 * 1e6,
|
||||
SDO: machine.NINA_SDO,
|
||||
SDI: machine.NINA_SDI,
|
||||
SCK: machine.NINA_SCK,
|
||||
})
|
||||
|
||||
adaptor = wifinina.New(spi,
|
||||
machine.NINA_CS,
|
||||
machine.NINA_ACK,
|
||||
machine.NINA_GPIO0,
|
||||
machine.NINA_RESETN)
|
||||
adaptor.Configure()
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
setup()
|
||||
|
||||
waitSerial()
|
||||
|
||||
connectToAP()
|
||||
|
||||
for {
|
||||
println("---------------------------------")
|
||||
printIPs()
|
||||
printTime()
|
||||
printMacAddress()
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func printIPs() {
|
||||
ip, subnet, gateway, err := adaptor.GetIP()
|
||||
if err != nil {
|
||||
println("IP: Unknown (error: ", err.Error(), ")")
|
||||
return
|
||||
}
|
||||
println("IP: ", ip.String())
|
||||
println("Subnet: ", subnet.String())
|
||||
println("Gateway IP: ", gateway.String())
|
||||
}
|
||||
|
||||
func printTime() {
|
||||
print("Time: ")
|
||||
t, err := adaptor.GetTime()
|
||||
if err != nil {
|
||||
println("Unknown (error: ", err.Error(), ")")
|
||||
}
|
||||
println(time.Unix(int64(t), 0).String())
|
||||
}
|
||||
|
||||
func printMacAddress() {
|
||||
print("MAC Address: ")
|
||||
b := make([]byte, 8)
|
||||
mac, err := adaptor.GetMACAddress()
|
||||
if err != nil {
|
||||
println("Unknown (", err.Error(), ")")
|
||||
}
|
||||
binary.LittleEndian.PutUint64(b, uint64(mac))
|
||||
macAddress := ""
|
||||
for i := 5; i >= 0; i-- {
|
||||
macAddress += fmt.Sprintf("%0X", b[i])
|
||||
if i != 0 {
|
||||
macAddress += ":"
|
||||
}
|
||||
}
|
||||
println(macAddress)
|
||||
}
|
||||
|
||||
// Wait for user to open serial console
|
||||
func waitSerial() {
|
||||
for !machine.UART0.DTR() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
if len(ssid) == 0 || len(pass) == 0 {
|
||||
for {
|
||||
println("Connection failed: Either ssid or password not set")
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
message("Connecting to " + ssid)
|
||||
adaptor.SetPassphrase(ssid, pass)
|
||||
for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; {
|
||||
message("Connection status: " + st.String())
|
||||
time.Sleep(1 * time.Second)
|
||||
st, _ = adaptor.GetConnectionStatus()
|
||||
}
|
||||
message("Connected.")
|
||||
}
|
||||
|
||||
func message(msg string) {
|
||||
println(msg, "\r")
|
||||
}
|
||||
@@ -35,28 +35,16 @@ const server = "tcp://test.mosquitto.org:1883"
|
||||
var (
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
uart = machine.UART2
|
||||
tx = machine.NINA_TX
|
||||
rx = machine.NINA_RX
|
||||
spi = machine.NINA_SPI
|
||||
spi = machine.NINA_SPI
|
||||
|
||||
// this is the ESP chip that has the WIFININA firmware flashed on it
|
||||
adaptor = &wifinina.Device{
|
||||
SPI: spi,
|
||||
CS: machine.NINA_CS,
|
||||
ACK: machine.NINA_ACK,
|
||||
GPIO0: machine.NINA_GPIO0,
|
||||
RESET: machine.NINA_RESETN,
|
||||
}
|
||||
|
||||
console = machine.UART0
|
||||
adaptor *wifinina.Device
|
||||
topic = "tinygo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
time.Sleep(3000 * time.Millisecond)
|
||||
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
@@ -68,6 +56,11 @@ func main() {
|
||||
})
|
||||
|
||||
// Init esp8266/esp32
|
||||
adaptor = wifinina.New(spi,
|
||||
machine.NINA_CS,
|
||||
machine.NINA_ACK,
|
||||
machine.NINA_GPIO0,
|
||||
machine.NINA_RESETN)
|
||||
adaptor.Configure()
|
||||
|
||||
connectToAP()
|
||||
@@ -97,7 +90,7 @@ func main() {
|
||||
println(err.Error())
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Right now this code is never reached. Need a way to trigger it...
|
||||
|
||||
@@ -32,23 +32,11 @@ const server = "tcp://test.mosquitto.org:1883"
|
||||
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
uart = machine.UART2
|
||||
tx = machine.NINA_TX
|
||||
rx = machine.NINA_RX
|
||||
spi = machine.NINA_SPI
|
||||
spi = machine.NINA_SPI
|
||||
|
||||
// this is the ESP chip that has the WIFININA firmware flashed on it
|
||||
adaptor = &wifinina.Device{
|
||||
SPI: spi,
|
||||
CS: machine.NINA_CS,
|
||||
ACK: machine.NINA_ACK,
|
||||
GPIO0: machine.NINA_GPIO0,
|
||||
RESET: machine.NINA_RESETN,
|
||||
}
|
||||
|
||||
console = machine.UART0
|
||||
adaptor *wifinina.Device
|
||||
|
||||
cl mqtt.Client
|
||||
topicTx = "tinygo/tx"
|
||||
@@ -63,7 +51,6 @@ func subHandler(client mqtt.Client, msg mqtt.Message) {
|
||||
func main() {
|
||||
time.Sleep(3000 * time.Millisecond)
|
||||
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
@@ -75,6 +62,11 @@ func main() {
|
||||
})
|
||||
|
||||
// Init esp8266/esp32
|
||||
adaptor = wifinina.New(spi,
|
||||
machine.NINA_CS,
|
||||
machine.NINA_ACK,
|
||||
machine.NINA_GPIO0,
|
||||
machine.NINA_RESETN)
|
||||
adaptor.Configure()
|
||||
|
||||
connectToAP()
|
||||
|
||||
@@ -23,36 +23,41 @@ const ntpHost = "129.6.15.29"
|
||||
|
||||
const NTP_PACKET_SIZE = 48
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
|
||||
// this is the ESP chip that has the WIFININA firmware flashed on it
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
adaptor = wifinina.Device{
|
||||
SPI: machine.NINA_SPI,
|
||||
CS: machine.NINA_CS,
|
||||
ACK: machine.NINA_ACK,
|
||||
GPIO0: machine.NINA_GPIO0,
|
||||
RESET: machine.NINA_RESETN,
|
||||
}
|
||||
spi = machine.NINA_SPI
|
||||
|
||||
b = make([]byte, NTP_PACKET_SIZE)
|
||||
|
||||
console = machine.UART0
|
||||
// this is the ESP chip that has the WIFININA firmware flashed on it
|
||||
adaptor *wifinina.Device
|
||||
b = make([]byte, NTP_PACKET_SIZE)
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Init esp32
|
||||
func setup() {
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
machine.NINA_SPI.Configure(machine.SPIConfig{
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 8 * 1e6,
|
||||
SDO: machine.NINA_SDO,
|
||||
SDI: machine.NINA_SDI,
|
||||
SCK: machine.NINA_SCK,
|
||||
})
|
||||
adaptor.Configure()
|
||||
|
||||
// connect to access point
|
||||
adaptor = wifinina.New(spi,
|
||||
machine.NINA_CS,
|
||||
machine.NINA_ACK,
|
||||
machine.NINA_GPIO0,
|
||||
machine.NINA_RESETN)
|
||||
adaptor.Configure()
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
setup()
|
||||
|
||||
waitSerial()
|
||||
|
||||
connectToAP()
|
||||
|
||||
// now make UDP connection
|
||||
@@ -83,10 +88,13 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Right now this code is never reached. Need a way to trigger it...
|
||||
println("Disconnecting UDP...")
|
||||
conn.Close()
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
// Wait for user to open serial console
|
||||
func waitSerial() {
|
||||
for !machine.UART0.DTR() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func getCurrentTime(conn *net.UDPSerialConn) (time.Time, error) {
|
||||
@@ -143,6 +151,12 @@ func clearBuffer() {
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
if len(ssid) == 0 || len(pass) == 0 {
|
||||
for {
|
||||
println("Connection failed: Either ssid or password not set")
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
message("Connecting to " + ssid)
|
||||
adaptor.SetPassphrase(ssid, pass)
|
||||
|
||||
@@ -29,29 +29,16 @@ const serverIP = ""
|
||||
var (
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
uart = machine.UART2
|
||||
tx = machine.NINA_TX
|
||||
rx = machine.NINA_RX
|
||||
spi = machine.NINA_SPI
|
||||
spi = machine.NINA_SPI
|
||||
|
||||
// this is the ESP chip that has the WIFININA firmware flashed on it
|
||||
adaptor = &wifinina.Device{
|
||||
SPI: spi,
|
||||
CS: machine.NINA_CS,
|
||||
ACK: machine.NINA_ACK,
|
||||
GPIO0: machine.NINA_GPIO0,
|
||||
RESET: machine.NINA_RESETN,
|
||||
}
|
||||
|
||||
console = machine.UART0
|
||||
adaptor *wifinina.Device
|
||||
)
|
||||
|
||||
var buf = &bytes.Buffer{}
|
||||
|
||||
func main() {
|
||||
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 8 * 1e6,
|
||||
@@ -60,6 +47,11 @@ func main() {
|
||||
SCK: machine.NINA_SCK,
|
||||
})
|
||||
|
||||
adaptor = wifinina.New(spi,
|
||||
machine.NINA_CS,
|
||||
machine.NINA_ACK,
|
||||
machine.NINA_GPIO0,
|
||||
machine.NINA_RESETN)
|
||||
adaptor.Configure()
|
||||
|
||||
connectToAP()
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
// This example opens a TCP connection using a device with WiFiNINA firmware
|
||||
// and sends a HTTPS request to retrieve a webpage
|
||||
//
|
||||
// You shall see "strict-transport-security" header in the response,
|
||||
// this confirms communication is indeed over HTTPS
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
"tinygo.org/x/drivers/net/tls"
|
||||
"tinygo.org/x/drivers/wifinina"
|
||||
)
|
||||
|
||||
// access point info
|
||||
const ssid = ""
|
||||
const pass = ""
|
||||
|
||||
// IP address of the server aka "hub". Replace with your own info.
|
||||
const server = "tinygo.org"
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
spi = machine.NINA_SPI
|
||||
|
||||
// this is the ESP chip that has the WIFININA firmware flashed on it
|
||||
adaptor *wifinina.Device
|
||||
)
|
||||
|
||||
var buf [256]byte
|
||||
|
||||
var lastRequestTime time.Time
|
||||
var conn net.Conn
|
||||
|
||||
func setup() {
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 8 * 1e6,
|
||||
SDO: machine.NINA_SDO,
|
||||
SDI: machine.NINA_SDI,
|
||||
SCK: machine.NINA_SCK,
|
||||
})
|
||||
|
||||
adaptor = wifinina.New(spi,
|
||||
machine.NINA_CS,
|
||||
machine.NINA_ACK,
|
||||
machine.NINA_GPIO0,
|
||||
machine.NINA_RESETN)
|
||||
adaptor.Configure()
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
setup()
|
||||
|
||||
waitSerial()
|
||||
|
||||
connectToAP()
|
||||
|
||||
for {
|
||||
readConnection()
|
||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 10000 {
|
||||
makeHTTPSRequest()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Wait for user to open serial console
|
||||
func waitSerial() {
|
||||
for !machine.UART0.DTR() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func readConnection() {
|
||||
if conn != nil {
|
||||
for n, err := conn.Read(buf[:]); n > 0; n, err = conn.Read(buf[:]) {
|
||||
if err != nil {
|
||||
println("Read error: " + err.Error())
|
||||
} else {
|
||||
print(string(buf[0:n]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeHTTPSRequest() {
|
||||
|
||||
var err error
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
message("\r\n---------------\r\nDialing TCP connection")
|
||||
conn, err = tls.Dial("tcp", server, nil)
|
||||
for ; err != nil; conn, err = tls.Dial("tcp", server, nil) {
|
||||
message("Connection failed: " + err.Error())
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
println("Connected!\r")
|
||||
|
||||
print("Sending HTTPS request...")
|
||||
fmt.Fprintln(conn, "GET / HTTP/1.1")
|
||||
fmt.Fprintln(conn, "Host:", strings.Split(server, ":")[0])
|
||||
fmt.Fprintln(conn, "User-Agent: TinyGo")
|
||||
fmt.Fprintln(conn, "Connection: close")
|
||||
fmt.Fprintln(conn)
|
||||
println("Sent!\r\n\r")
|
||||
|
||||
lastRequestTime = time.Now()
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
if len(ssid) == 0 || len(pass) == 0 {
|
||||
for {
|
||||
println("Connection failed: Either ssid or password not set")
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
message("Connecting to " + ssid)
|
||||
adaptor.SetPassphrase(ssid, pass)
|
||||
for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; {
|
||||
message("Connection status: " + st.String())
|
||||
time.Sleep(1 * time.Second)
|
||||
st, _ = adaptor.GetConnectionStatus()
|
||||
}
|
||||
message("Connected.")
|
||||
time.Sleep(2 * time.Second)
|
||||
ip, _, _, err := adaptor.GetIP()
|
||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||
message(err.Error())
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
message(ip.String())
|
||||
}
|
||||
|
||||
func message(msg string) {
|
||||
println(msg, "\r")
|
||||
}
|
||||
@@ -22,19 +22,9 @@ const pass = ""
|
||||
// IP address of the server aka "hub". Replace with your own info.
|
||||
const hubIP = ""
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
|
||||
// this is the ESP chip that has the WIFININA firmware flashed on it
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
adaptor = &wifinina.Device{
|
||||
SPI: machine.NINA_SPI,
|
||||
CS: machine.NINA_CS,
|
||||
ACK: machine.NINA_ACK,
|
||||
GPIO0: machine.NINA_GPIO0,
|
||||
RESET: machine.NINA_RESETN,
|
||||
}
|
||||
adaptor *wifinina.Device
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -47,6 +37,14 @@ func main() {
|
||||
SDI: machine.NINA_SDI,
|
||||
SCK: machine.NINA_SCK,
|
||||
})
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
adaptor = wifinina.New(machine.NINA_SPI,
|
||||
machine.NINA_CS,
|
||||
machine.NINA_ACK,
|
||||
machine.NINA_GPIO0,
|
||||
machine.NINA_RESETN)
|
||||
adaptor.Configure()
|
||||
|
||||
// connect to access point
|
||||
|
||||
@@ -27,21 +27,10 @@ const server = "tinygo.org"
|
||||
var (
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
uart = machine.UART2
|
||||
tx = machine.NINA_TX
|
||||
rx = machine.NINA_RX
|
||||
spi = machine.NINA_SPI
|
||||
spi = machine.NINA_SPI
|
||||
|
||||
// this is the ESP chip that has the WIFININA firmware flashed on it
|
||||
adaptor = &wifinina.Device{
|
||||
SPI: spi,
|
||||
CS: machine.NINA_CS,
|
||||
ACK: machine.NINA_ACK,
|
||||
GPIO0: machine.NINA_GPIO0,
|
||||
RESET: machine.NINA_RESETN,
|
||||
}
|
||||
|
||||
console = machine.UART0
|
||||
adaptor *wifinina.Device
|
||||
)
|
||||
|
||||
var buf [256]byte
|
||||
@@ -49,10 +38,7 @@ var buf [256]byte
|
||||
var lastRequestTime time.Time
|
||||
var conn net.Conn
|
||||
|
||||
func main() {
|
||||
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
|
||||
func setup() {
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 8 * 1e6,
|
||||
@@ -61,17 +47,39 @@ func main() {
|
||||
SCK: machine.NINA_SCK,
|
||||
})
|
||||
|
||||
adaptor = wifinina.New(spi,
|
||||
machine.NINA_CS,
|
||||
machine.NINA_ACK,
|
||||
machine.NINA_GPIO0,
|
||||
machine.NINA_RESETN)
|
||||
adaptor.Configure()
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
setup()
|
||||
|
||||
waitSerial()
|
||||
|
||||
connectToAP()
|
||||
|
||||
for {
|
||||
loop()
|
||||
readConnection()
|
||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 10000 {
|
||||
makeHTTPRequest()
|
||||
}
|
||||
}
|
||||
println("Done.")
|
||||
|
||||
}
|
||||
|
||||
func loop() {
|
||||
// Wait for user to open serial console
|
||||
func waitSerial() {
|
||||
for !machine.UART0.DTR() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func readConnection() {
|
||||
if conn != nil {
|
||||
for n, err := conn.Read(buf[:]); n > 0; n, err = conn.Read(buf[:]) {
|
||||
if err != nil {
|
||||
@@ -81,9 +89,6 @@ func loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 10000 {
|
||||
makeHTTPRequest()
|
||||
}
|
||||
}
|
||||
|
||||
func makeHTTPRequest() {
|
||||
@@ -101,7 +106,7 @@ func makeHTTPRequest() {
|
||||
message("\r\n---------------\r\nDialing TCP connection")
|
||||
conn, err = net.DialTCP("tcp", laddr, raddr)
|
||||
for ; err != nil; conn, err = net.DialTCP("tcp", laddr, raddr) {
|
||||
message("connection failed: " + err.Error())
|
||||
message("Connection failed: " + err.Error())
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
println("Connected!\r")
|
||||
@@ -109,7 +114,7 @@ func makeHTTPRequest() {
|
||||
print("Sending HTTP request...")
|
||||
fmt.Fprintln(conn, "GET / HTTP/1.1")
|
||||
fmt.Fprintln(conn, "Host:", server)
|
||||
fmt.Fprintln(conn, "User-Agent: TinyGo/0.10.0")
|
||||
fmt.Fprintln(conn, "User-Agent: TinyGo")
|
||||
fmt.Fprintln(conn, "Connection: close")
|
||||
fmt.Fprintln(conn)
|
||||
println("Sent!\r\n\r")
|
||||
@@ -117,19 +122,14 @@ func makeHTTPRequest() {
|
||||
lastRequestTime = time.Now()
|
||||
}
|
||||
|
||||
func readLine(conn *net.TCPSerialConn) string {
|
||||
println("Attempting to read...\r")
|
||||
b := buf[:]
|
||||
for expiry := time.Now().Unix() + 10; time.Now().Unix() > expiry; {
|
||||
if n, err := conn.Read(b); n > 0 && err == nil {
|
||||
return string(b[0:n])
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
if len(ssid) == 0 || len(pass) == 0 {
|
||||
for {
|
||||
println("Connection failed: Either ssid or password not set")
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
message("Connecting to " + ssid)
|
||||
adaptor.SetPassphrase(ssid, pass)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// +build arduino
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
// Replace neo in the code below to match the pin
|
||||
// that you are using if different.
|
||||
var neo = machine.D2
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !digispark
|
||||
// +build !digispark,!arduino
|
||||
|
||||
package main
|
||||
|
||||
|
||||
+3
-2
@@ -133,6 +133,8 @@ func (dev *Device) Configure(config *DeviceConfig) (err error) {
|
||||
time.Sleep(30 * time.Microsecond)
|
||||
|
||||
// Speed up to max device frequency
|
||||
// I propose a check here for max frequency, but not put that functionality directly into the driver.
|
||||
// Either that or we have to change the signature of the SPI interface in the machine package itself.
|
||||
if dev.attrs.MaxClockSpeedMHz > 0 {
|
||||
err := dev.trans.setClockSpeed(uint32(dev.attrs.MaxClockSpeedMHz) * 1e6)
|
||||
if err != nil {
|
||||
@@ -288,9 +290,8 @@ func (dev *Device) EraseBlockSize() int64 {
|
||||
// supports this. The start and len parameters are in block numbers, use
|
||||
// EraseBlockSize to map addresses to blocks.
|
||||
func (dev *Device) EraseBlocks(start, len int64) error {
|
||||
// TODO: maybe combine sector erase operations into block erase operations
|
||||
for i := start; i < start+len; i++ {
|
||||
if err := dev.EraseSector(uint32(i)); err != nil {
|
||||
if err := dev.EraseBlock(uint32(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package flash
|
||||
|
||||
import "machine"
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
type transport interface {
|
||||
configure(config *DeviceConfig)
|
||||
|
||||
+2
-3
@@ -4,7 +4,6 @@ package gps // import "tinygo.org/x/drivers/gps"
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"machine"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -21,13 +20,13 @@ type Device struct {
|
||||
buffer []byte
|
||||
bufIdx int
|
||||
sentence strings.Builder
|
||||
uart *machine.UART
|
||||
uart drivers.UART
|
||||
bus drivers.I2C
|
||||
address uint16
|
||||
}
|
||||
|
||||
// NewUART creates a new UART GPS connection. The UART must already be configured.
|
||||
func NewUART(uart *machine.UART) Device {
|
||||
func NewUART(uart drivers.UART) Device {
|
||||
return Device{
|
||||
uart: uart,
|
||||
buffer: make([]byte, bufferSize),
|
||||
|
||||
+11
-1
@@ -57,9 +57,16 @@ func (g *GPIO) SetCommandMode(set bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// WriteOnly is true if you passed rw in as machine.NoPin
|
||||
func (g *GPIO) WriteOnly() bool {
|
||||
return g.rw == machine.NoPin
|
||||
}
|
||||
|
||||
// Write writes len(data) bytes from data to display driver
|
||||
func (g *GPIO) Write(data []byte) (n int, err error) {
|
||||
g.rw.Low()
|
||||
if !g.WriteOnly() {
|
||||
g.rw.Low()
|
||||
}
|
||||
for _, d := range data {
|
||||
g.write(d)
|
||||
n++
|
||||
@@ -89,6 +96,9 @@ func (g *GPIO) Read(data []byte) (n int, err error) {
|
||||
if len(data) == 0 {
|
||||
return 0, errors.New("length greater than 0 is required")
|
||||
}
|
||||
if g.WriteOnly() {
|
||||
return 0, errors.New("Read not supported if RW not wired")
|
||||
}
|
||||
g.rw.High()
|
||||
g.reconfigureGPIOMode(machine.PinInput)
|
||||
for i := 0; i < len(data); i++ {
|
||||
|
||||
+60
-9
@@ -11,9 +11,23 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// These are the default execution times for the Clear and
|
||||
// Home commands and everything else.
|
||||
//
|
||||
// These are used if RW is passed as machine.NoPin and ignored
|
||||
// otherwise.
|
||||
//
|
||||
// They are set conservatively here and can be tweaked in the
|
||||
// Config structure.
|
||||
DefaultClearHomeTime = 80 * time.Millisecond
|
||||
DefaultInstrExecTime = 80 * time.Microsecond
|
||||
)
|
||||
|
||||
type Buser interface {
|
||||
io.ReadWriter
|
||||
SetCommandMode(set bool)
|
||||
WriteOnly() bool
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
@@ -28,6 +42,9 @@ type Device struct {
|
||||
|
||||
cursor cursor
|
||||
busyStatus []byte
|
||||
|
||||
clearHomeTime time.Duration // time clear/home instructions might take
|
||||
instrExecTime time.Duration // time all other instructions might take
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
@@ -35,14 +52,18 @@ type cursor struct {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Width int16
|
||||
Height int16
|
||||
CursorBlink bool
|
||||
CursorOnOff bool
|
||||
Font uint8
|
||||
Width int16
|
||||
Height int16
|
||||
CursorBlink bool
|
||||
CursorOnOff bool
|
||||
Font uint8
|
||||
ClearHomeTime time.Duration // time clear/home instructions might take - use 0 for the default
|
||||
InstrExecTime time.Duration // time all other instructions might take - use 0 for the default
|
||||
}
|
||||
|
||||
// NewGPIO4Bit returns 4bit data length HD44780 driver. Datapins are LCD DB pins starting from DB4 to DB7
|
||||
//
|
||||
// If your device has RW set permanently to ground then pass in rw as machine.NoPin
|
||||
func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
|
||||
const fourBitMode = 4
|
||||
if len(dataPins) != fourBitMode {
|
||||
@@ -52,6 +73,8 @@ func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error)
|
||||
}
|
||||
|
||||
// NewGPIO8Bit returns 8bit data length HD44780 driver. Datapins are LCD DB pins starting from DB0 to DB7
|
||||
//
|
||||
// If your device has RW set permanently to ground then pass in rw as machine.NoPin
|
||||
func NewGPIO8Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
|
||||
const eightBitMode = 8
|
||||
if len(dataPins) != eightBitMode {
|
||||
@@ -68,6 +91,8 @@ func (d *Device) Configure(cfg Config) error {
|
||||
if d.width == 0 || d.height == 0 {
|
||||
return errors.New("width and height must be set")
|
||||
}
|
||||
d.clearHomeTime = cfg.ClearHomeTime
|
||||
d.instrExecTime = cfg.InstrExecTime
|
||||
memoryMap := uint8(ONE_LINE)
|
||||
if d.height > 1 {
|
||||
memoryMap = TWO_LINE
|
||||
@@ -186,7 +211,7 @@ func (d *Device) SendCommand(command byte) {
|
||||
d.bus.SetCommandMode(true)
|
||||
d.bus.Write([]byte{command})
|
||||
|
||||
for d.Busy() {
|
||||
for d.busy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +220,7 @@ func (d *Device) sendData(data byte) {
|
||||
d.bus.SetCommandMode(false)
|
||||
d.bus.Write([]byte{data})
|
||||
|
||||
for d.Busy() {
|
||||
for d.busy(false) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,13 +232,39 @@ func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// Busy returns true when hd447890 is busy
|
||||
func (d *Device) Busy() bool {
|
||||
// busy returns true when hd447890 is busy
|
||||
// or after the timeout specified
|
||||
func (d *Device) busy(longDelay bool) bool {
|
||||
if d.bus.WriteOnly() {
|
||||
// Can't read busy flag if write only, so sleep a bit then return
|
||||
if longDelay {
|
||||
// Note that we sleep like this so the default
|
||||
// time.Sleep is time.Sleep(constant) as
|
||||
// time.Sleep(variable) doesn't seem to work on AVR yet
|
||||
if d.clearHomeTime != 0 {
|
||||
time.Sleep(d.clearHomeTime)
|
||||
} else {
|
||||
time.Sleep(DefaultClearHomeTime)
|
||||
}
|
||||
} else {
|
||||
if d.instrExecTime != 0 {
|
||||
time.Sleep(d.instrExecTime)
|
||||
} else {
|
||||
time.Sleep(DefaultInstrExecTime)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
d.bus.SetCommandMode(true)
|
||||
d.bus.Read(d.busyStatus)
|
||||
return (d.busyStatus[0] & BUSY) > 0
|
||||
}
|
||||
|
||||
// Busy returns true when hd447890 is busy
|
||||
func (d *Device) Busy() bool {
|
||||
return d.busy(false)
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
return int16(d.width), int16(d.height)
|
||||
|
||||
+4
-2
@@ -9,6 +9,8 @@ import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -21,7 +23,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
bus drivers.SPI
|
||||
a machine.Pin
|
||||
b machine.Pin
|
||||
c machine.Pin
|
||||
@@ -52,7 +54,7 @@ type Device struct {
|
||||
}
|
||||
|
||||
// New returns a new HUB75 driver. Pass in a fully configured SPI bus.
|
||||
func New(b machine.SPI, latPin, oePin, aPin, bPin, cPin, dPin machine.Pin) Device {
|
||||
func New(b drivers.SPI, latPin, oePin, aPin, bPin, cPin, dPin machine.Pin) Device {
|
||||
aPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
bPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
cPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package ina260
|
||||
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
// Device wraps an I2C connection to an INA260 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
// Config holds the configuration of the INA260 device.
|
||||
type Config struct {
|
||||
// One of AVGMODE_XXX
|
||||
AverageMode byte
|
||||
|
||||
// One of CONVTIME_XXXXUSEC
|
||||
VoltConvTime byte
|
||||
|
||||
// One of CONVTIME_XXXXUSEC
|
||||
CurrentConvTime byte
|
||||
|
||||
// Multiple of MODE_XXXX
|
||||
Mode byte
|
||||
}
|
||||
|
||||
// New creates a new INA260 connection. The I2C bus must already be
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure sets up the device.
|
||||
//
|
||||
// This only needs to be called to override built-in defaults. By default,
|
||||
// the device starts with:
|
||||
//
|
||||
// * AverageMode = AVGMODE_1
|
||||
// * VoltConvTime = CONVTIME_1100USEC
|
||||
// * CurrentConvTime = CONVTIME_1100USEC
|
||||
// * Mode = MODE_CONTINUOUS | MODE_VOLTAGE | MODE_CURRENT
|
||||
//
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
var val uint16
|
||||
|
||||
val = uint16(cfg.AverageMode&0x7) << 9
|
||||
val |= uint16(cfg.VoltConvTime&0x7) << 6
|
||||
val |= uint16(cfg.CurrentConvTime&0x7) << 3
|
||||
val |= uint16(cfg.Mode & 0x7)
|
||||
|
||||
d.WriteRegister(REG_CONFIG, val)
|
||||
}
|
||||
|
||||
// Resets the device, setting all registers to default values
|
||||
func (d *Device) Reset() {
|
||||
d.WriteRegister(REG_CONFIG, 0x8000)
|
||||
}
|
||||
|
||||
// Connected returns whether an INA260 has been found.
|
||||
func (d *Device) Connected() bool {
|
||||
return d.ReadRegister(REG_MANF_ID) == MANF_ID &&
|
||||
(d.ReadRegister(REG_DIE_ID)&DEVICE_ID_MASK) == DEVICE_ID
|
||||
}
|
||||
|
||||
// Gets the measured current in µA (max resolution 1.25mA)
|
||||
func (d *Device) Current() int32 {
|
||||
val := d.ReadRegister(REG_CURRENT)
|
||||
|
||||
if val&0x8000 == 0 {
|
||||
return int32(val) * 1250
|
||||
}
|
||||
|
||||
// Two's complement, convert to signed int
|
||||
return -(int32(^val) + 1) * 1250
|
||||
}
|
||||
|
||||
// Gets the measured voltage in µV (max resolution 1.25mV)
|
||||
func (d *Device) Voltage() int32 {
|
||||
val := d.ReadRegister(REG_BUSVOLTAGE)
|
||||
|
||||
if val&0x8000 == 0 {
|
||||
return int32(val) * 1250
|
||||
}
|
||||
|
||||
// Two's complement, convert to signed int
|
||||
return -(int32(^val) + 1) * 1250
|
||||
}
|
||||
|
||||
// Gets the measured power in µW (max resolution 10mW)
|
||||
func (d *Device) Power() int32 {
|
||||
return int32(d.ReadRegister(REG_POWER)) * 10000
|
||||
}
|
||||
|
||||
// Read a register
|
||||
func (d *Device) ReadRegister(reg uint8) uint16 {
|
||||
data := []byte{0, 0}
|
||||
d.bus.ReadRegister(uint8(d.Address), reg, data)
|
||||
return (uint16(data[0]) << 8) | uint16(data[1])
|
||||
}
|
||||
|
||||
// Write to a register
|
||||
func (d *Device) WriteRegister(reg uint8, v uint16) {
|
||||
data := []byte{0, 0}
|
||||
data[0] = byte(v >> 8)
|
||||
data[1] = byte(v & 0xff)
|
||||
|
||||
d.bus.WriteRegister(uint8(d.Address), reg, data)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package ina260
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"tinygo.org/x/drivers/tester"
|
||||
)
|
||||
|
||||
func TestDefaultI2CAddress(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Address, qt.Equals, uint16(Address))
|
||||
}
|
||||
|
||||
func TestConnected(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = defaultRegisters()
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Connected(), qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestVoltage(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = defaultRegisters()
|
||||
fake.Registers[REG_BUSVOLTAGE] = 0x2570
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
// Datasheet: 2570h = 11.98V = 11980mV = 11980000uV
|
||||
c.Assert(dev.Voltage(), qt.Equals, int32(11980000))
|
||||
}
|
||||
|
||||
func TestCurrent(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = defaultRegisters()
|
||||
fake.Registers[REG_CURRENT] = 0x2710
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
// Datasheet: 2710h = 12.5A = 12500mA = 12500000uA
|
||||
c.Assert(dev.Current(), qt.Equals, int32(12500000))
|
||||
}
|
||||
|
||||
func TestPower(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice16(c, Address)
|
||||
fake.Registers = defaultRegisters()
|
||||
fake.Registers[REG_POWER] = 0x3A7F
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
// 3A7Fh = 149.75W = 149750mW = 149750000uW
|
||||
c.Assert(dev.Power(), qt.Equals, int32(149750000))
|
||||
}
|
||||
|
||||
// defaultRegisters returns the default values for all of the device's registers.
|
||||
// set TI INA260 datasheet for power-on defaults
|
||||
func defaultRegisters() map[uint8]uint16 {
|
||||
return map[uint8]uint16{
|
||||
REG_CONFIG: 0x6127,
|
||||
REG_CURRENT: 0x0000,
|
||||
REG_BUSVOLTAGE: 0x0000,
|
||||
REG_POWER: 0x0000,
|
||||
REG_MASKENABLE: 0x0000,
|
||||
REG_ALERTLIMIT: 0x0000,
|
||||
REG_MANF_ID: 0x5449,
|
||||
REG_DIE_ID: 0x2270,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package ina260
|
||||
|
||||
// The default I2C address for this device.
|
||||
//
|
||||
// The actual address is configurable by connecting address pins.
|
||||
const Address = 0x40
|
||||
|
||||
// Registers
|
||||
const (
|
||||
REG_CONFIG = 0x00
|
||||
REG_CURRENT = 0x01
|
||||
REG_BUSVOLTAGE = 0x02
|
||||
REG_POWER = 0x03
|
||||
REG_MASKENABLE = 0x06
|
||||
REG_ALERTLIMIT = 0x07
|
||||
REG_MANF_ID = 0xFE
|
||||
REG_DIE_ID = 0xFF
|
||||
)
|
||||
|
||||
// Well-Known Values
|
||||
const (
|
||||
MANF_ID = 0x5449 // TI
|
||||
DEVICE_ID = 0x2270 // 227h
|
||||
DEVICE_ID_MASK = 0xFFF0
|
||||
|
||||
AVGMODE_1 = 0
|
||||
AVGMODE_4 = 1
|
||||
AVGMODE_16 = 2
|
||||
AVGMODE_64 = 3
|
||||
AVGMODE_128 = 4
|
||||
AVGMODE_256 = 5
|
||||
AVGMODE_512 = 6
|
||||
AVGMODE_1024 = 7
|
||||
|
||||
CONVTIME_140USEC = 0
|
||||
CONVTIME_204USEC = 1
|
||||
CONVTIME_332USEC = 2
|
||||
CONVTIME_588USEC = 3
|
||||
CONVTIME_1100USEC = 4 // 1.1 ms
|
||||
CONVTIME_2116USEC = 5 // 2.1 ms
|
||||
CONVTIME_4156USEC = 6 // 4.2 ms
|
||||
CONVTIME_8244USEC = 7 // 8.2 ms
|
||||
|
||||
MODE_CONTINUOUS = 0x4
|
||||
MODE_TRIGGERED = 0x0
|
||||
MODE_VOLTAGE = 0x2
|
||||
MODE_NO_VOLTAGE = 0x0
|
||||
MODE_CURRENT = 0x1
|
||||
MODE_NO_CURRENT = 0x0
|
||||
)
|
||||
@@ -0,0 +1,107 @@
|
||||
package keypad4x4
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
// NoKeyPressed is used, when no key was pressed
|
||||
const NoKeyPressed = 255
|
||||
|
||||
// Device is used as 4x4 keypad driver
|
||||
type Device interface {
|
||||
Configure()
|
||||
GetKey() uint8
|
||||
GetIndices() (int, int)
|
||||
}
|
||||
|
||||
// device is a driver for 4x4 keypads
|
||||
type device struct {
|
||||
inputEnabled bool
|
||||
lastColumn int
|
||||
lastRow int
|
||||
columns [4]machine.Pin
|
||||
rows [4]machine.Pin
|
||||
mapping [4][4]uint8
|
||||
}
|
||||
|
||||
// takes r4 -r1 pins and c4 - c1 pins
|
||||
func NewDevice(r4, r3, r2, r1, c4, c3, c2, c1 machine.Pin) Device {
|
||||
result := &device{}
|
||||
result.columns = [4]machine.Pin{c4, c3, c2, c1}
|
||||
result.rows = [4]machine.Pin{r4, r3, r2, r1}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Configure sets the column pins as input and the row pins as output
|
||||
func (keypad *device) Configure() {
|
||||
inputConfig := machine.PinConfig{Mode: machine.PinInputPullup}
|
||||
for i := range keypad.columns {
|
||||
keypad.columns[i].Configure(inputConfig)
|
||||
}
|
||||
|
||||
outputConfig := machine.PinConfig{Mode: machine.PinOutput}
|
||||
for i := range keypad.rows {
|
||||
keypad.rows[i].Configure(outputConfig)
|
||||
keypad.rows[i].High()
|
||||
}
|
||||
|
||||
keypad.mapping = [4][4]uint8{
|
||||
{0, 1, 2, 3},
|
||||
{4, 5, 6, 7},
|
||||
{8, 9, 10, 11},
|
||||
{12, 13, 14, 15},
|
||||
}
|
||||
|
||||
keypad.inputEnabled = true
|
||||
keypad.lastColumn = -1
|
||||
keypad.lastRow = -1
|
||||
}
|
||||
|
||||
// GetKey returns the code for the given key.
|
||||
// The codes start with 0 at the upper left end of the keypad and end with 15 at the lower right end of the keypad
|
||||
// Example:
|
||||
// 0 1 2 3
|
||||
// 4 5 6 7
|
||||
// 8 9 10 11
|
||||
// 12 13 14 15
|
||||
// returns 255 for no keyPressed
|
||||
func (keypad *device) GetKey() uint8 {
|
||||
row, column := keypad.GetIndices()
|
||||
if row == -1 && column == -1 {
|
||||
return NoKeyPressed
|
||||
}
|
||||
|
||||
return keypad.mapping[row][column]
|
||||
}
|
||||
|
||||
// GetIndices returns the position of the pressed key
|
||||
func (keypad *device) GetIndices() (int, int) {
|
||||
for rowIndex, rowPin := range keypad.rows {
|
||||
rowPin.Low()
|
||||
|
||||
for columnIndex := range keypad.columns {
|
||||
columnPin := keypad.columns[columnIndex]
|
||||
|
||||
if !columnPin.Get() && keypad.inputEnabled {
|
||||
keypad.inputEnabled = false
|
||||
|
||||
keypad.lastColumn = columnIndex
|
||||
keypad.lastRow = rowIndex
|
||||
|
||||
return keypad.lastRow, keypad.lastColumn
|
||||
}
|
||||
|
||||
if columnPin.Get() &&
|
||||
columnIndex == keypad.lastColumn &&
|
||||
rowIndex == keypad.lastRow &&
|
||||
!keypad.inputEnabled {
|
||||
keypad.inputEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
rowPin.High()
|
||||
}
|
||||
|
||||
return -1, -1
|
||||
}
|
||||
+38
-16
@@ -56,49 +56,71 @@ func (d *Device) Stop() {
|
||||
d.en.Low()
|
||||
}
|
||||
|
||||
// PWM is the interface necessary for controlling the motor driver.
|
||||
type PWM interface {
|
||||
Configure(config machine.PWMConfig) error
|
||||
Channel(pin machine.Pin) (channel uint8, err error)
|
||||
Top() uint32
|
||||
Set(channel uint8, value uint32)
|
||||
SetPeriod(period uint64) error
|
||||
}
|
||||
|
||||
// PWMDevice is a motor with speed control.
|
||||
// a1 and a2 are the directional GPIO pins.
|
||||
// en is the PWM pin that controls the motor speed.
|
||||
type PWMDevice struct {
|
||||
a1, a2 machine.Pin
|
||||
en machine.PWM
|
||||
spc uint8
|
||||
pwm PWM
|
||||
}
|
||||
|
||||
// NewWithSpeed returns a new PWMMotor driver that uses a PWM pin to control speed.
|
||||
func NewWithSpeed(direction1, direction2 machine.Pin, speedPin machine.PWM) PWMDevice {
|
||||
// NewWithSpeed returns a new PWMMotor driver that uses an already configured PWM channel
|
||||
// to control speed.
|
||||
func NewWithSpeed(direction1, direction2 machine.Pin, spc uint8, pwm PWM) PWMDevice {
|
||||
return PWMDevice{
|
||||
a1: direction1,
|
||||
a2: direction2,
|
||||
en: speedPin,
|
||||
a1: direction1,
|
||||
a2: direction2,
|
||||
spc: spc,
|
||||
pwm: pwm,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures the PWMDevice.
|
||||
func (d *PWMDevice) Configure() {
|
||||
// Configure configures the PWMDevice. Note that the PWM interface and
|
||||
// channel must already be configured, this function will not do it for you.
|
||||
func (d *PWMDevice) Configure() error {
|
||||
d.a1.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.a2.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.en.Configure()
|
||||
|
||||
d.Stop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Forward turns motor on in forward direction at specific speed.
|
||||
func (d *PWMDevice) Forward(speed uint16) {
|
||||
// Forward turns motor on in forward direction at specific speed as a percentage.
|
||||
func (d *PWMDevice) Forward(speed uint32) {
|
||||
if speed > 100 {
|
||||
speed = 100
|
||||
}
|
||||
|
||||
d.a1.High()
|
||||
d.a2.Low()
|
||||
d.en.Set(speed)
|
||||
d.pwm.Set(d.spc, d.pwm.Top()*speed/100)
|
||||
}
|
||||
|
||||
// Backward turns motor on in backward direction at specific speed.
|
||||
func (d *PWMDevice) Backward(speed uint16) {
|
||||
// Backward turns motor on in backward direction at specific speed as a percentage.
|
||||
func (d *PWMDevice) Backward(speed uint32) {
|
||||
if speed > 100 {
|
||||
speed = 100
|
||||
}
|
||||
|
||||
d.a1.Low()
|
||||
d.a2.High()
|
||||
d.en.Set(speed)
|
||||
d.pwm.Set(d.spc, d.pwm.Top()*speed/100)
|
||||
}
|
||||
|
||||
// Stop turns motor off.
|
||||
func (d *PWMDevice) Stop() {
|
||||
d.a1.Low()
|
||||
d.a2.Low()
|
||||
d.en.Set(0)
|
||||
d.pwm.Set(d.spc, 0)
|
||||
}
|
||||
|
||||
+29
-19
@@ -49,42 +49,52 @@ func (d *Device) Stop() {
|
||||
d.ib.Low()
|
||||
}
|
||||
|
||||
// PWM is the interface necessary for controlling the motor driver.
|
||||
type PWM interface {
|
||||
Configure(config machine.PWMConfig) error
|
||||
Channel(pin machine.Pin) (channel uint8, err error)
|
||||
Top() uint32
|
||||
Set(channel uint8, value uint32)
|
||||
SetPeriod(period uint64) error
|
||||
}
|
||||
|
||||
// PWMDevice is a motor with speed control.
|
||||
// ia and ib are the directional/speed PWM pins.
|
||||
type PWMDevice struct {
|
||||
ia, ib machine.PWM
|
||||
pwm PWM
|
||||
ca, cb uint8
|
||||
}
|
||||
|
||||
// NewWithSpeed returns a new PWMMotor driver that uses 2 PWM pins to control both direction and speed.
|
||||
func NewWithSpeed(direction1, direction2 machine.PWM) PWMDevice {
|
||||
func NewWithSpeed(ca, cb uint8, pwm PWM) PWMDevice {
|
||||
return PWMDevice{
|
||||
ia: direction1,
|
||||
ib: direction2,
|
||||
pwm: pwm,
|
||||
ca: ca,
|
||||
cb: cb,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures the PWMDevice.
|
||||
func (d *PWMDevice) Configure() {
|
||||
d.ia.Configure()
|
||||
d.ib.Configure()
|
||||
|
||||
// Configure configures the PWMDevice. Note that the pins, PWM interface,
|
||||
// and channels must all already be configured.
|
||||
func (d *PWMDevice) Configure() (err error) {
|
||||
d.Stop()
|
||||
return
|
||||
}
|
||||
|
||||
// Forward turns motor on in forward direction at specific speed.
|
||||
func (d *PWMDevice) Forward(speed uint16) {
|
||||
d.ia.Set(speed)
|
||||
d.ib.Set(0)
|
||||
// Forward turns motor on in forward direction at specific speed as a percentage.
|
||||
func (d *PWMDevice) Forward(speed uint32) {
|
||||
d.pwm.Set(d.ca, d.pwm.Top()*speed/100)
|
||||
d.pwm.Set(d.cb, 0)
|
||||
}
|
||||
|
||||
// Backward turns motor on in backward direction at specific speed.
|
||||
func (d *PWMDevice) Backward(speed uint16) {
|
||||
d.ia.Set(0)
|
||||
d.ib.Set(speed)
|
||||
// Backward turns motor on in backward direction at specific speed as a percentage.
|
||||
func (d *PWMDevice) Backward(speed uint32) {
|
||||
d.pwm.Set(d.ca, 0)
|
||||
d.pwm.Set(d.cb, d.pwm.Top()*speed/100)
|
||||
}
|
||||
|
||||
// Stop turns motor off.
|
||||
func (d *PWMDevice) Stop() {
|
||||
d.ia.Set(0)
|
||||
d.ib.Set(0)
|
||||
d.pwm.Set(d.ca, 0)
|
||||
d.pwm.Set(d.cb, 0)
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ func TestWhoAmI(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice(c, ADDRESS)
|
||||
fake.SetupRegisters(defaultRegisters())
|
||||
copy(fake.Registers[:], defaultRegisters())
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Connected(), qt.Equals, true)
|
||||
|
||||
fake.SetupRegister(WHO_AM_I, 0x99)
|
||||
fake.Registers[WHO_AM_I] = 0x99
|
||||
c.Assert(dev.Connected(), qt.Equals, false)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// Driver works for max7219 and 7221
|
||||
// Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX7219-MAX7221.pdf
|
||||
package max72xx
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
cs machine.Pin
|
||||
}
|
||||
|
||||
// NewDriver creates a new max7219 connection. The SPI wire must already be configured
|
||||
// The SPI frequency must not be higher than 10MHz.
|
||||
// parameter cs: the datasheet also refers to this pin as "load" pin.
|
||||
func NewDevice(bus machine.SPI, cs machine.Pin) *Device {
|
||||
return &Device{
|
||||
bus: bus,
|
||||
cs: cs,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure setups the pins.
|
||||
func (driver *Device) Configure() {
|
||||
outPutConfig := machine.PinConfig{Mode: machine.PinOutput}
|
||||
|
||||
driver.cs.Configure(outPutConfig)
|
||||
}
|
||||
|
||||
// SetScanLimit sets the scan limit. Maximum is 8.
|
||||
// Example: a 4 digit 7SegmentDisplay has a scan limit of 4
|
||||
func (driver *Device) SetScanLimit(digitNumber uint8) {
|
||||
driver.WriteCommand(byte(REG_SCANLIMIT), byte(digitNumber-1))
|
||||
}
|
||||
|
||||
// SetDecodeMode sets the decode mode for 7 segment displays.
|
||||
// digitNumber = 1 -> 1 digit gets decoded
|
||||
// digitNumber = 2 or 3, or 4 -> 4 digit are being decoded
|
||||
// digitNumber = 8 -> 8 digits are being decoded
|
||||
// digitNumber 0 || digitNumber > 8 -> no decoding is being used
|
||||
func (driver *Device) SetDecodeMode(digitNumber uint8) {
|
||||
switch digitNumber {
|
||||
case 1: // only decode first digit
|
||||
driver.WriteCommand(REG_DECODE_MODE, 0x01)
|
||||
case 2, 3, 4: // decode digits 3-0
|
||||
driver.WriteCommand(REG_DECODE_MODE, 0x0F)
|
||||
case 8: // decode 8 digits
|
||||
driver.WriteCommand(REG_DECODE_MODE, 0xFF)
|
||||
default:
|
||||
driver.WriteCommand(REG_DECODE_MODE, 0x00)
|
||||
}
|
||||
}
|
||||
|
||||
// StartShutdownMode sets the IC into a low power shutdown mode.
|
||||
func (driver *Device) StartShutdownMode() {
|
||||
driver.WriteCommand(REG_SHUTDOWN, 0x00)
|
||||
}
|
||||
|
||||
// StartShutdownMode sets the IC into normal operation mode.
|
||||
func (driver *Device) StopShutdownMode() {
|
||||
driver.WriteCommand(REG_SHUTDOWN, 0x01)
|
||||
}
|
||||
|
||||
// StartDisplayTest starts a display test.
|
||||
func (driver *Device) StartDisplayTest() {
|
||||
driver.WriteCommand(REG_DISPLAY_TEST, 0x01)
|
||||
}
|
||||
|
||||
// StopDisplayTest stops the display test and gets into normal operation mode.
|
||||
func (driver *Device) StopDisplayTest() {
|
||||
driver.WriteCommand(REG_DISPLAY_TEST, 0x00)
|
||||
}
|
||||
|
||||
func (driver *Device) writeByte(data byte) {
|
||||
driver.bus.Transfer(data)
|
||||
}
|
||||
|
||||
// WriteCommand write data to a given register.
|
||||
func (driver *Device) WriteCommand(register, data byte) {
|
||||
driver.cs.Low()
|
||||
driver.writeByte(register)
|
||||
driver.writeByte(data)
|
||||
driver.cs.High()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package max72xx
|
||||
|
||||
const (
|
||||
REG_NOOP byte = 0x00
|
||||
REG_DIGIT0 byte = 0x01
|
||||
REG_DIGIT1 byte = 0x02
|
||||
REG_DIGIT2 byte = 0x03
|
||||
REG_DIGIT3 byte = 0x04
|
||||
REG_DIGIT4 byte = 0x05
|
||||
REG_DIGIT5 byte = 0x06
|
||||
REG_DIGIT6 byte = 0x07
|
||||
REG_DIGIT7 byte = 0x08
|
||||
REG_DECODE_MODE byte = 0x09 // turn of for led matrix, turn on for digits
|
||||
REG_INTENSITY byte = 0x0A
|
||||
REG_SCANLIMIT byte = 0x0B
|
||||
REG_SHUTDOWN byte = 0x0C // turn on for no shutdown mode
|
||||
REG_DISPLAY_TEST byte = 0x0F // turn off for no display test
|
||||
)
|
||||
@@ -0,0 +1,381 @@
|
||||
// Package mcp23017 implements a driver for the MCP23017
|
||||
// I2C port expander chip. See https://www.microchip.com/wwwproducts/en/MCP23017
|
||||
// for details of the interface.
|
||||
//
|
||||
// It also provides a way of joining several such devices into one logical
|
||||
// device (see the Devices type).
|
||||
package mcp23017
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// hwAddressFixed holds the bits of the hardware address
|
||||
// that are fixed by the chip. Bits 0-3 (those in hwAddressMask)
|
||||
// are user-defined by the A0-A2 pins on the chip.
|
||||
hwAddress = uint8(0b010_0000)
|
||||
// hwAddressMask holds the bits that are significant in hwAddress.
|
||||
hwAddressMask = uint8(0b111_1000)
|
||||
)
|
||||
|
||||
type register uint8
|
||||
|
||||
const (
|
||||
// The following registers all refer to port A (except
|
||||
// rIOCON with is port-agnostic).
|
||||
// ORing them with portB makes them refer to port B.
|
||||
rIODIR = register(0x00) // I/O direction. 0=output; 1=input.
|
||||
rIOPOL = register(0x02) // Invert input values. 0=normal; 1=inverted.
|
||||
rGPINTEN = register(0x04)
|
||||
rDEFVAL = register(0x06)
|
||||
rINTCON = register(0x08)
|
||||
rIOCON = register(0x0A)
|
||||
rGPPU = register(0x0C) // Pull up; 0=no pull-up; 1=pull-up.
|
||||
rINTF = register(0x0E)
|
||||
rINTCAP = register(0x10)
|
||||
rGPIO = register(0x12) // GPIO pin values.
|
||||
rOLAT = register(0x14)
|
||||
registerCount = 0x16
|
||||
|
||||
portB = register(0x1)
|
||||
)
|
||||
|
||||
// PinCount is the number of GPIO pins available on the chip.
|
||||
const PinCount = 16
|
||||
|
||||
// PinMode represents a possible I/O mode for a pin.
|
||||
// The zero value represents the default value
|
||||
// after the chip is reset (input).
|
||||
type PinMode uint8
|
||||
|
||||
const (
|
||||
// Input configures a pin as an input.
|
||||
Input = PinMode(0)
|
||||
// Output configures a pin as an output.
|
||||
Output = PinMode(1)
|
||||
|
||||
// Direction is the bit mask of the pin mode representing
|
||||
// the I/O direction.
|
||||
Direction = PinMode(1)
|
||||
|
||||
// Pullup can be bitwise-or'd with Input
|
||||
// to cause the pull-up resistor on the pin to
|
||||
// be enabled.
|
||||
Pullup = PinMode(2)
|
||||
|
||||
// Invert can be bitwise-or'd with Input to
|
||||
// cause the pin value to reflect the inverted
|
||||
// value on the pin.
|
||||
Invert = PinMode(4)
|
||||
)
|
||||
|
||||
// ErrInvalidHWAddress is returned when the hardware address
|
||||
// of the device is not valid (only some bits can be set by the
|
||||
// address pins).
|
||||
var ErrInvalidHWAddress = errors.New("invalid hardware address")
|
||||
|
||||
// I2C represents an I2C bus. It is notably implemented by the
|
||||
// machine.I2C type.
|
||||
type I2C interface {
|
||||
ReadRegister(addr uint8, r uint8, buf []byte) error
|
||||
WriteRegister(addr uint8, r uint8, buf []byte) error
|
||||
}
|
||||
|
||||
// New returns a new MCP23017 device at the given I2C address
|
||||
// on the given bus.
|
||||
// It returns ErrInvalidHWAddress if the address isn't possible for the device.
|
||||
//
|
||||
// By default all pins are configured as inputs.
|
||||
func NewI2C(bus I2C, address uint8) (*Device, error) {
|
||||
if address&hwAddressMask != hwAddress {
|
||||
return nil, ErrInvalidHWAddress
|
||||
}
|
||||
d := &Device{
|
||||
bus: bus,
|
||||
addr: address,
|
||||
}
|
||||
pins, err := d.GetPins()
|
||||
if err != nil {
|
||||
return nil, errors.New("cannot initialize mcp23017 device at " + hex(address) + ": " + err.Error())
|
||||
}
|
||||
d.pins = pins
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func hex(x uint8) string {
|
||||
digits := "0123456789abcdef"
|
||||
return "0x" + digits[x>>4:x>>4+1] + digits[x&0xf:x&0xf+1]
|
||||
}
|
||||
|
||||
// Device represents an MCP23017 device.
|
||||
type Device struct {
|
||||
// TODO would it be good to have a mutex here so that independent goroutines
|
||||
// could change pins without needing to do the locking themselves?
|
||||
|
||||
// bus holds the reference the I2C bus that the device lives on.
|
||||
// It's an interface so that we can write tests for it.
|
||||
bus I2C
|
||||
addr uint8
|
||||
// pins caches the most recent pin values that have been set.
|
||||
// This enables us to change individual pin values without
|
||||
// doing a read followed by a write.
|
||||
pins Pins
|
||||
}
|
||||
|
||||
// GetPins reads all 16 pins from ports A and B.
|
||||
func (d *Device) GetPins() (Pins, error) {
|
||||
return d.readRegisterAB(rGPIO)
|
||||
}
|
||||
|
||||
// SetPins sets all the pins for which mask is high
|
||||
// to their respective values in pins.
|
||||
//
|
||||
// That is, it does the equivalent of:
|
||||
//
|
||||
// for i := 0; i < PinCount; i++ {
|
||||
// if mask.Get(i) {
|
||||
// d.Pin(i).Set(pins.Get(i))
|
||||
// }
|
||||
// }
|
||||
func (d *Device) SetPins(pins, mask Pins) error {
|
||||
if mask == 0 {
|
||||
return nil
|
||||
}
|
||||
newPins := (d.pins &^ mask) | (pins & mask)
|
||||
if newPins == d.pins {
|
||||
return nil
|
||||
}
|
||||
err := d.writeRegisterAB(rGPIO, newPins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.pins = newPins
|
||||
return nil
|
||||
}
|
||||
|
||||
// TogglePins inverts the values on all pins for
|
||||
// which mask is high.
|
||||
func (d *Device) TogglePins(mask Pins) error {
|
||||
if mask == 0 {
|
||||
return nil
|
||||
}
|
||||
return d.SetPins(^d.pins, mask)
|
||||
}
|
||||
|
||||
// Pin returns a Pin representing the given pin number (from 0 to 15).
|
||||
// Pin numbers from 0 to 7 represent port A pins 0 to 7.
|
||||
// Pin numbers from 8 to 15 represent port B pins 0 to 7.
|
||||
func (d *Device) Pin(pin int) Pin {
|
||||
if pin < 0 || pin >= PinCount {
|
||||
panic("pin out of range")
|
||||
}
|
||||
var mask Pins
|
||||
mask.High(pin)
|
||||
return Pin{
|
||||
dev: d,
|
||||
mask: mask,
|
||||
pin: uint8(pin),
|
||||
}
|
||||
}
|
||||
|
||||
// SetAllModes sets the mode of all the pins in a single operation.
|
||||
// If len(modes) is less than PinCount, all remaining pins
|
||||
// will be set fo modes[len(modes)-1], or PinMode(0) if
|
||||
// modes is empty.
|
||||
//
|
||||
// If len(modes) is greater than PinCount, the excess entries
|
||||
// will be ignored.
|
||||
func (d *Device) SetModes(modes []PinMode) error {
|
||||
defaultMode := PinMode(0)
|
||||
if len(modes) > 0 {
|
||||
defaultMode = modes[len(modes)-1]
|
||||
}
|
||||
var dir, pullup, invert Pins
|
||||
for i := 0; i < PinCount; i++ {
|
||||
mode := defaultMode
|
||||
if i < len(modes) {
|
||||
mode = modes[i]
|
||||
}
|
||||
if mode&Direction == Input {
|
||||
dir.High(i)
|
||||
}
|
||||
if mode&Pullup != 0 {
|
||||
pullup.High(i)
|
||||
}
|
||||
if mode&Invert != 0 {
|
||||
invert.High(i)
|
||||
}
|
||||
}
|
||||
if err := d.writeRegisterAB(rIODIR, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.writeRegisterAB(rGPPU, pullup); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.writeRegisterAB(rIOPOL, invert); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetModes reads the modes of all the pins into modes.
|
||||
// It's OK if len(modes) is not PinCount - excess entries
|
||||
// will be left unset.
|
||||
func (d *Device) GetModes(modes []PinMode) error {
|
||||
dir, err := d.readRegisterAB(rIODIR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pullup, err := d.readRegisterAB(rGPPU)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invert, err := d.readRegisterAB(rIOPOL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(modes) > PinCount {
|
||||
modes = modes[:PinCount]
|
||||
}
|
||||
for i := range modes {
|
||||
mode := Output
|
||||
if dir.Get(i) {
|
||||
mode = Input
|
||||
}
|
||||
if pullup.Get(i) {
|
||||
mode |= Pullup
|
||||
}
|
||||
if invert.Get(i) {
|
||||
mode |= Invert
|
||||
}
|
||||
modes[i] = mode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) writeRegisterAB(r register, val Pins) error {
|
||||
// We rely on the auto-incrementing sequential write
|
||||
// and the fact that registers alternate between A and B
|
||||
// to write both ports in a single operation.
|
||||
buf := [2]byte{uint8(val), uint8(val >> 8)}
|
||||
return d.bus.WriteRegister(d.addr, uint8(r&^portB), buf[:])
|
||||
}
|
||||
|
||||
func (d *Device) readRegisterAB(r register) (Pins, error) {
|
||||
// We rely on the auto-incrementing sequential write
|
||||
// and the fact that registers alternate between A and B
|
||||
// to read both ports in a single operation.
|
||||
var buf [2]byte
|
||||
if err := d.bus.ReadRegister(d.addr, uint8(r), buf[:]); err != nil {
|
||||
return Pins(0), err
|
||||
}
|
||||
return Pins(buf[0]) | (Pins(buf[1]) << 8), nil
|
||||
}
|
||||
|
||||
// Pin represents a single GPIO pin on the device.
|
||||
type Pin struct {
|
||||
// mask holds the mask of the pin.
|
||||
mask Pins
|
||||
// pin holds the actual pin number.
|
||||
pin uint8
|
||||
dev *Device
|
||||
}
|
||||
|
||||
// Set sets the pin to the given value.
|
||||
func (p Pin) Set(value bool) error {
|
||||
// TODO currently this always writes both registers when
|
||||
// technically it only needs to write one. We could potentially
|
||||
// optimize that.
|
||||
if value {
|
||||
return p.dev.SetPins(^Pins(0), p.mask)
|
||||
} else {
|
||||
return p.dev.SetPins(0, p.mask)
|
||||
}
|
||||
}
|
||||
|
||||
// High is short for p.Set(true).
|
||||
func (p Pin) High() error {
|
||||
return p.Set(true)
|
||||
}
|
||||
|
||||
// High is short for p.Set(false).
|
||||
func (p Pin) Low() error {
|
||||
return p.Set(false)
|
||||
}
|
||||
|
||||
// Toggle inverts the value output on the pin.
|
||||
func (p Pin) Toggle() error {
|
||||
return p.dev.TogglePins(p.mask)
|
||||
}
|
||||
|
||||
// Get returns the current value of the given pin.
|
||||
func (p Pin) Get() (bool, error) {
|
||||
// TODO this reads 2 registers when we could read just one.
|
||||
pins, err := p.dev.GetPins()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return pins&p.mask != 0, nil
|
||||
}
|
||||
|
||||
// SetMode configures the pin to the given mode.
|
||||
func (p Pin) SetMode(mode PinMode) error {
|
||||
// We could use a more efficient single-register
|
||||
// read/write pattern but setting pin modes isn't an
|
||||
// operation that's likely to need to be efficient, so
|
||||
// use less code and use Get/SetModes directly.
|
||||
modes := make([]PinMode, PinCount)
|
||||
if err := p.dev.GetModes(modes); err != nil {
|
||||
return err
|
||||
}
|
||||
modes[p.pin] = mode
|
||||
return p.dev.SetModes(modes)
|
||||
}
|
||||
|
||||
// GetMode returns the mode of the pin.
|
||||
func (p Pin) GetMode() (PinMode, error) {
|
||||
modes := make([]PinMode, PinCount)
|
||||
if err := p.dev.GetModes(modes); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return modes[p.pin], nil
|
||||
}
|
||||
|
||||
// Pins represents a bitmask of pin values.
|
||||
// Port A values are in bits 0-8 (numbered from least significant bit)
|
||||
// Port B values are in bits 9-15.
|
||||
type Pins uint16
|
||||
|
||||
// Set sets the value for the given pin.
|
||||
func (p *Pins) Set(pin int, value bool) {
|
||||
if value {
|
||||
p.High(pin)
|
||||
} else {
|
||||
p.Low(pin)
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value for the given pin.
|
||||
func (p Pins) Get(pin int) bool {
|
||||
return (p & pinMask(pin)) != 0
|
||||
}
|
||||
|
||||
// High is short for p.Set(pin, true).
|
||||
func (p *Pins) High(pin int) {
|
||||
*p |= pinMask(pin)
|
||||
}
|
||||
|
||||
// Low is short for p.Set(pin, false).
|
||||
func (p *Pins) Low(pin int) {
|
||||
*p &^= pinMask(pin)
|
||||
}
|
||||
|
||||
// Toggle inverts the value of the given pin.
|
||||
func (p *Pins) Toggle(pin int) {
|
||||
*p ^= pinMask(pin)
|
||||
}
|
||||
|
||||
func pinMask(pin int) Pins {
|
||||
return 1 << pin
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package mcp23017
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
|
||||
"tinygo.org/x/drivers/tester"
|
||||
)
|
||||
|
||||
func TestGetPins(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := newDevice(bus, 0x20)
|
||||
fdev.Registers[rGPIO] = 0b10101100
|
||||
fdev.Registers[rGPIO|portB] = 0b01010011
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins, err := dev.GetPins()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.Equals, Pins(0b01010011_10101100))
|
||||
}
|
||||
|
||||
func TestSetPins(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := newDevice(bus, 0x20)
|
||||
fdev.Registers[rGPIO] = 0b00001111
|
||||
fdev.Registers[rGPIO|portB] = 0b11110000
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins, err := dev.GetPins()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.Equals, Pins(0b11110000_00001111))
|
||||
|
||||
err = dev.SetPins(0b01100000_00110000, 0b10101010_01010101)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins, err = dev.GetPins()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.Equals, Pins(0b01110000_0001_1010))
|
||||
|
||||
// The logic uses the cached value of the pins rather than
|
||||
// reading it from the registers each time.
|
||||
fdev.Registers[rGPIO] = 0
|
||||
fdev.Registers[rGPIO|portB] = 0
|
||||
|
||||
err = dev.SetPins(0b01000000_00110000, 0b01100000_00000000)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins, err = dev.GetPins()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.Equals, Pins(0b01010000_00011010))
|
||||
}
|
||||
|
||||
func TestTogglePins(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := newDevice(bus, 0x20)
|
||||
fdev.Registers[rGPIO] = 0b00001111
|
||||
fdev.Registers[rGPIO|portB] = 0b11110000
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins, err := dev.GetPins()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.Equals, Pins(0b11110000_00001111))
|
||||
|
||||
err = dev.TogglePins(0b10101010_01010101)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins, err = dev.GetPins()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.Equals, Pins(0b01011010_01011010))
|
||||
}
|
||||
|
||||
func TestSetGetModes(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := newDevice(bus, 0x20)
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
// Calling SetModes with less items in than there are
|
||||
// pins should use the last item for all the unspecified ones.
|
||||
err = dev.SetModes([]PinMode{Input | Invert, Output})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b00000001))
|
||||
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b00000001))
|
||||
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0))
|
||||
|
||||
modes := make([]PinMode, 17)
|
||||
err = dev.GetModes(modes)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(modes[0], qt.Equals, Input|Invert)
|
||||
for i, m := range modes[1:16] {
|
||||
c.Assert(m, qt.Equals, Output, qt.Commentf("index %d", i))
|
||||
}
|
||||
c.Assert(modes[16], qt.Equals, PinMode(0))
|
||||
|
||||
// Using an empty slice should reset all the modes to the initial state.
|
||||
err = dev.SetModes(nil)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111111))
|
||||
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0))
|
||||
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0))
|
||||
}
|
||||
|
||||
func TestPinSetGet(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := newDevice(bus, 0x20)
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pin := dev.Pin(1)
|
||||
v, err := pin.Get()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(v, qt.Equals, false)
|
||||
err = pin.Set(true)
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
|
||||
v, err = pin.Get()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(v, qt.Equals, true)
|
||||
err = pin.Set(false)
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
|
||||
err = pin.High()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
|
||||
err = pin.Low()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
|
||||
}
|
||||
|
||||
func TestPinToggle(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := newDevice(bus, 0x20)
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pin := dev.Pin(1)
|
||||
v, err := pin.Get()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(v, qt.Equals, false)
|
||||
err = pin.Toggle()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0b10))
|
||||
err = pin.Toggle()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev.Registers[rGPIO], qt.Equals, uint8(0))
|
||||
}
|
||||
|
||||
func TestPinMode(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := newDevice(bus, 0x20)
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pin := dev.Pin(1)
|
||||
mode, err := pin.GetMode()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(mode, qt.Equals, PinMode(0))
|
||||
c.Assert(mode&Direction, qt.Equals, Input)
|
||||
|
||||
err = pin.SetMode(Input | Pullup | Invert)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111111))
|
||||
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
|
||||
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
|
||||
|
||||
mode, err = pin.GetMode()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(mode, qt.Equals, Input|Pullup|Invert)
|
||||
|
||||
// Set another pin to output.
|
||||
err = dev.Pin(2).SetMode(Output)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111011))
|
||||
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
|
||||
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
|
||||
|
||||
// Check that changing a pin in port B works too.
|
||||
err = dev.Pin(8).SetMode(Output)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev.Registers[rIODIR], qt.Equals, uint8(0b11111011))
|
||||
c.Assert(fdev.Registers[rIODIR|portB], qt.Equals, uint8(0b11111110))
|
||||
c.Assert(fdev.Registers[rIOPOL], qt.Equals, uint8(0b10))
|
||||
c.Assert(fdev.Registers[rIOPOL|portB], qt.Equals, uint8(0))
|
||||
c.Assert(fdev.Registers[rGPPU], qt.Equals, uint8(0b10))
|
||||
c.Assert(fdev.Registers[rGPPU|portB], qt.Equals, uint8(0))
|
||||
}
|
||||
|
||||
func TestPins(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
var p Pins
|
||||
p.Set(1, true)
|
||||
c.Assert(p, qt.Equals, Pins(0b10))
|
||||
c.Assert(p.Get(1), qt.Equals, true)
|
||||
c.Assert(p.Get(0), qt.Equals, false)
|
||||
c.Assert(p.Get(16), qt.Equals, false)
|
||||
p.High(2)
|
||||
c.Assert(p, qt.Equals, Pins(0b110))
|
||||
p.Low(1)
|
||||
c.Assert(p, qt.Equals, Pins(0b100))
|
||||
}
|
||||
|
||||
func TestInitWithError(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := newDevice(bus, 0x20)
|
||||
fdev.Err = fmt.Errorf("some error")
|
||||
dev, err := NewI2C(bus, 0x20)
|
||||
c.Assert(err, qt.ErrorMatches, `cannot initialize mcp23017 device at 0x20: some error`)
|
||||
c.Assert(dev, qt.IsNil)
|
||||
}
|
||||
|
||||
func newDevice(bus *tester.I2CBus, addr uint8) *tester.I2CDevice8 {
|
||||
fdev := bus.NewDevice(addr)
|
||||
// IODIRA and IODIRB are all ones by default.
|
||||
fdev.Registers[rIODIR] = 0xff
|
||||
fdev.Registers[rIODIR|portB] = 0xff
|
||||
return fdev
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package mcp23017
|
||||
|
||||
// All is a convenience value that represents all pins high (or all mask bits one).
|
||||
var All = PinSlice{0xffff}
|
||||
|
||||
// Devices holds a slice of devices that can be treated as one
|
||||
// contiguous set of devices. Earlier entries in the slice have
|
||||
// lower-numbered pins, so index 0 holds pins 0-7, index 1 holds
|
||||
// pins 8-15, etc.
|
||||
type Devices []*Device
|
||||
|
||||
// NewI2CDevices returns a Devices slice holding the Device values
|
||||
// for all the given addresses on the given bus.
|
||||
// When more than one bus is in use, create the slice yourself.
|
||||
func NewI2CDevices(bus I2C, addrs ...uint8) (Devices, error) {
|
||||
devs := make(Devices, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
dev, err := NewI2C(bus, addr)
|
||||
if err != nil {
|
||||
// TODO return a more informative error.
|
||||
return nil, err
|
||||
}
|
||||
devs[i] = dev
|
||||
}
|
||||
return devs, nil
|
||||
}
|
||||
|
||||
// SetModes sets the pin modes of all the pins on all the devices in devs.
|
||||
// If there are less entries in modes than there are pins, the
|
||||
// last entry is replicated to all of them (or PinMode(0) if modes
|
||||
// is empty).
|
||||
func (devs Devices) SetModes(modes []PinMode) error {
|
||||
var defaultModes []PinMode
|
||||
if len(modes) > 0 {
|
||||
defaultModes = modes[len(modes)-1:]
|
||||
}
|
||||
for i, dev := range devs {
|
||||
pinStart := i * PinCount
|
||||
var devModes []PinMode
|
||||
if pinStart < len(modes) {
|
||||
devModes = modes[pinStart:]
|
||||
} else {
|
||||
devModes = defaultModes
|
||||
}
|
||||
if err := dev.SetModes(devModes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetModes gets the pin modes from the devices.
|
||||
// It's OK if modes isn't the same length as all the pins:
|
||||
// extra entries will be left unchanged.
|
||||
func (devs Devices) GetModes(modes []PinMode) error {
|
||||
for i, dev := range devs {
|
||||
pinStart := i * PinCount
|
||||
if pinStart >= len(modes) {
|
||||
break
|
||||
}
|
||||
if err := dev.GetModes(modes[pinStart:]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pin returns the pin for the given number.
|
||||
func (devs Devices) Pin(pin int) Pin {
|
||||
if pin < 0 || pin >= len(devs)*PinCount {
|
||||
panic("pin out of range")
|
||||
}
|
||||
return devs[pin/PinCount].Pin(pin % PinCount)
|
||||
}
|
||||
|
||||
// GetPins returns pin values for all the pins.
|
||||
func (devs Devices) GetPins(pins PinSlice) error {
|
||||
for i, dev := range devs {
|
||||
if i >= len(pins) {
|
||||
break
|
||||
}
|
||||
devPins, err := dev.GetPins()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pins[i] = devPins
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPins sets all the pins for which mask is high
|
||||
// to their respective values in pins.
|
||||
//
|
||||
// That is, it does the equivalent of:
|
||||
//
|
||||
// for i := 0; i < PinCount*len(devs); i++ {
|
||||
// if mask.Get(i) {
|
||||
// d.Pin(i).Set(pins.Get(i))
|
||||
// }
|
||||
// }
|
||||
func (devs Devices) SetPins(pins, mask PinSlice) error {
|
||||
defaultPins := pins.extra()
|
||||
defaultMask := mask.extra()
|
||||
for i, dev := range devs {
|
||||
devPins := defaultPins
|
||||
if i < len(pins) {
|
||||
devPins = pins[i]
|
||||
}
|
||||
devMask := defaultMask
|
||||
if i < len(mask) {
|
||||
devMask = mask[i]
|
||||
}
|
||||
if err := dev.SetPins(devPins, devMask); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TogglePins inverts the values on all pins for
|
||||
// which mask is high.
|
||||
func (devs Devices) TogglePins(mask PinSlice) error {
|
||||
defaultMask := mask.extra()
|
||||
for i, dev := range devs {
|
||||
devMask := defaultMask
|
||||
if i < len(mask) {
|
||||
devMask = mask[i]
|
||||
}
|
||||
if err := dev.TogglePins(devMask); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PinSlice represents an arbitrary nunber of pins, each element corresponding
|
||||
// to the pins for one device. The value of the highest numbered pin in the
|
||||
// slice is extended to all other pins beyond the end of the slice.
|
||||
type PinSlice []Pins
|
||||
|
||||
// Get returns the value for the given pin. If the length of pins is too short
|
||||
// for the pin number, the value of the highest available pin is returned.
|
||||
// That is, the highest numbered pin in the last element of pins
|
||||
// is effectively replicated to all other elements.
|
||||
//
|
||||
// This means that PinSlice{} means "all pins high" and
|
||||
// PinSlice{0xffff} means "all pins low".
|
||||
func (pins PinSlice) Get(i int) bool {
|
||||
if len(pins) == 0 || i < 0 {
|
||||
return false
|
||||
}
|
||||
if i >= len(pins)*PinCount {
|
||||
return pins[len(pins)-1].Get(PinCount - 1)
|
||||
}
|
||||
return pins[i/PinCount].Get(i % PinCount)
|
||||
}
|
||||
|
||||
// Set sets the value for the given pin.
|
||||
func (pins PinSlice) Set(i int, value bool) {
|
||||
pins[i/PinCount].Set(i%PinCount, value)
|
||||
}
|
||||
|
||||
// High is short for p.Set(pin, true).
|
||||
func (pins PinSlice) High(pin int) {
|
||||
pins[pin/PinCount].High(pin % PinCount)
|
||||
}
|
||||
|
||||
// High is short for p.Set(pin, false).
|
||||
func (pins PinSlice) Low(pin int) {
|
||||
pins[pin/PinCount].Low(pin % PinCount)
|
||||
}
|
||||
|
||||
// Toggle inverts the value of the given pin.
|
||||
func (pins PinSlice) Toggle(pin int) {
|
||||
pins[pin/PinCount].Toggle(pin % PinCount)
|
||||
}
|
||||
|
||||
// Ensure checks that pins has enough space to store
|
||||
// at least length pins. If it does, it returns pins unchanged.
|
||||
// Otherwise, it returns pins with elements appended as needed,
|
||||
// populating additonal elements by replicating the
|
||||
// highest pin (mirroring the behavior of PinSlice.Get).
|
||||
func (pins PinSlice) Ensure(length int) PinSlice {
|
||||
if length == 0 {
|
||||
return pins
|
||||
}
|
||||
n := length/PinCount + 1
|
||||
if len(pins) >= n {
|
||||
return pins
|
||||
}
|
||||
// TODO we could potentially make use of additional
|
||||
// extra capacity in pins when available instead
|
||||
// of allocating a new slice always.
|
||||
newPins := make(PinSlice, n)
|
||||
copy(newPins, pins)
|
||||
if extend := pins.extra(); extend != 0 {
|
||||
for i := len(pins); i < n; i++ {
|
||||
newPins[i] = extend
|
||||
}
|
||||
}
|
||||
return newPins
|
||||
}
|
||||
|
||||
// extra returns the value of implied extra elements beyond
|
||||
// the end of pins.
|
||||
func (pins PinSlice) extra() Pins {
|
||||
if len(pins) == 0 || !pins[len(pins)-1].Get(PinCount-1) {
|
||||
return 0
|
||||
}
|
||||
return ^Pins(0)
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package mcp23017
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
|
||||
"tinygo.org/x/drivers/tester"
|
||||
)
|
||||
|
||||
func TestDevicesGetPins(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev0 := newDevice(bus, 0x20)
|
||||
fdev1 := newDevice(bus, 0x21)
|
||||
fdev0.Registers[rGPIO] = 0b10101100
|
||||
fdev0.Registers[rGPIO|portB] = 0b01010011
|
||||
fdev1.Registers[rGPIO] = 0b10101101
|
||||
fdev1.Registers[rGPIO|portB] = 0b01010010
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins := make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101100, 0b01010010_10101101})
|
||||
|
||||
// It's OK to pass less elements than there are devices.
|
||||
pins = make(PinSlice, 1)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101100})
|
||||
}
|
||||
|
||||
func TestDevicesSetPinsAllOff(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev0 := newDevice(bus, 0x20)
|
||||
fdev1 := newDevice(bus, 0x21)
|
||||
fdev0.Registers[rGPIO] = 0b10101100
|
||||
fdev0.Registers[rGPIO|portB] = 0b01010011
|
||||
fdev1.Registers[rGPIO] = 0b10101101
|
||||
fdev1.Registers[rGPIO|portB] = 0b01010010
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
err = devs.SetPins(nil, PinSlice{0xffff})
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins := make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0, 0})
|
||||
}
|
||||
|
||||
func TestDevicesSetPinsAllOn(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev0 := newDevice(bus, 0x20)
|
||||
fdev1 := newDevice(bus, 0x21)
|
||||
fdev0.Registers[rGPIO] = 0b10101100
|
||||
fdev0.Registers[rGPIO|portB] = 0b01010011
|
||||
fdev1.Registers[rGPIO] = 0b10101101
|
||||
fdev1.Registers[rGPIO|portB] = 0b01010010
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
err = devs.SetPins(PinSlice{0xffff}, PinSlice{0xffff})
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins := make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0xffff, 0xffff})
|
||||
}
|
||||
|
||||
func TestDevicesSetPinsMask(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev0 := newDevice(bus, 0x20)
|
||||
fdev1 := newDevice(bus, 0x21)
|
||||
fdev0.Registers[rGPIO] = 0b10101100
|
||||
fdev0.Registers[rGPIO|portB] = 0b01010011
|
||||
fdev1.Registers[rGPIO] = 0b10101101
|
||||
fdev1.Registers[rGPIO|portB] = 0b01010010
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
// Sanity check the original value of the pins.
|
||||
pins := make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101100, 0b01010010_10101101})
|
||||
|
||||
pins = make(PinSlice, 2)
|
||||
pins.High(0)
|
||||
pins.High(1)
|
||||
mask := make(PinSlice, 2)
|
||||
mask.High(0)
|
||||
mask.High(16)
|
||||
|
||||
err = devs.SetPins(pins, mask)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins = make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0b01010011_10101101, 0b01010010_10101100})
|
||||
}
|
||||
|
||||
func TestDevicesTogglePins(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
newDevice(bus, 0x20)
|
||||
newDevice(bus, 0x21)
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
mask := make(PinSlice, 2)
|
||||
mask.High(0)
|
||||
mask.High(16)
|
||||
|
||||
err = devs.TogglePins(mask)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pins := make(PinSlice, 2)
|
||||
err = devs.GetPins(pins)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0b00000000_00000001, 0b00000000_00000001})
|
||||
}
|
||||
|
||||
func TestDevicesSetGetModes(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev0 := newDevice(bus, 0x20)
|
||||
fdev1 := newDevice(bus, 0x21)
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
// Sanity check that IODIR registers start off all ones.
|
||||
c.Assert(fdev0.Registers[rIODIR], qt.Equals, uint8(0xff))
|
||||
|
||||
// The last entry is replicated to fill them all.
|
||||
err = devs.SetModes([]PinMode{Input | Pullup, Output})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fdev0.Registers[rIODIR], qt.Equals, uint8(1))
|
||||
c.Assert(fdev0.Registers[rIODIR|portB], qt.Equals, uint8(0))
|
||||
c.Assert(fdev1.Registers[rIODIR], qt.Equals, uint8(0))
|
||||
c.Assert(fdev1.Registers[rIODIR|portB], qt.Equals, uint8(0))
|
||||
|
||||
modes := make([]PinMode, 2)
|
||||
err = devs.GetModes(modes)
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(modes, qt.DeepEquals, []PinMode{Input | Pullup, Output})
|
||||
|
||||
// It's OK to pass a smaller slice to GetModes.
|
||||
modes = make([]PinMode, 1)
|
||||
err = devs.GetModes(modes)
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(modes, qt.DeepEquals, []PinMode{Input | Pullup})
|
||||
}
|
||||
|
||||
func TestDevicesPin(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
newDevice(bus, 0x20)
|
||||
fdev1 := newDevice(bus, 0x21)
|
||||
devs, err := NewI2CDevices(bus, 0x20, 0x21)
|
||||
c.Assert(err, qt.IsNil)
|
||||
pin := devs.Pin(16)
|
||||
v, err := pin.Get()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(v, qt.Equals, false)
|
||||
err = pin.High()
|
||||
c.Assert(err, qt.Equals, nil)
|
||||
c.Assert(fdev1.Registers[rGPIO], qt.Equals, uint8(1))
|
||||
}
|
||||
|
||||
func TestPinSlice(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
pins := PinSlice(nil).Ensure(20)
|
||||
pins.Set(16, true)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0, 1})
|
||||
pins.Set(31, true)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0, 0b10000000_00000001})
|
||||
c.Assert(pins.Get(0), qt.Equals, false)
|
||||
c.Assert(pins.Get(16), qt.Equals, true)
|
||||
pins = pins.Ensure(40)
|
||||
c.Assert(pins, qt.DeepEquals, PinSlice{0, 0b10000000_00000001, 0xffff})
|
||||
pins.Low(16)
|
||||
c.Assert(pins.Get(16), qt.Equals, false)
|
||||
pins.High(16)
|
||||
c.Assert(pins.Get(16), qt.Equals, true)
|
||||
pins.Toggle(16)
|
||||
c.Assert(pins.Get(16), qt.Equals, false)
|
||||
}
|
||||
@@ -0,0 +1,786 @@
|
||||
// Package mcp2515 implements a driver for the MCP2515 CAN Controller.
|
||||
//
|
||||
// Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/MCP2515-Stand-Alone-CAN-Controller-with-SPI-20001801J.pdf
|
||||
//
|
||||
// Reference: https://github.com/coryjfowler/MCP_CAN_lib
|
||||
package mcp2515 // import "tinygo.org/x/drivers/mcp2515"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps MCP2515 SPI CAN Module.
|
||||
type Device struct {
|
||||
spi SPI
|
||||
cs machine.Pin
|
||||
msg *CANMsg
|
||||
mcpMode byte
|
||||
}
|
||||
|
||||
// CANMsg stores CAN message fields.
|
||||
type CANMsg struct {
|
||||
ID uint32
|
||||
Dlc uint8
|
||||
Data []byte
|
||||
Ext bool
|
||||
Rtr bool
|
||||
}
|
||||
|
||||
const (
|
||||
bufferSize int = 64
|
||||
)
|
||||
|
||||
// New returns a new MCP2515 driver. Pass in a fully configured SPI bus.
|
||||
func New(b drivers.SPI, csPin machine.Pin) *Device {
|
||||
d := &Device{
|
||||
spi: SPI{
|
||||
bus: b,
|
||||
tx: make([]byte, 0, bufferSize),
|
||||
rx: make([]byte, 0, bufferSize),
|
||||
},
|
||||
cs: csPin,
|
||||
msg: &CANMsg{},
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication.
|
||||
func (d *Device) Configure() {
|
||||
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
}
|
||||
|
||||
const beginTimeoutValue int = 10
|
||||
|
||||
// Begin starts the CAN controller.
|
||||
func (d *Device) Begin(speed byte, clock byte) error {
|
||||
timeOutCount := 0
|
||||
for {
|
||||
err := d.init(speed, clock)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
timeOutCount++
|
||||
if timeOutCount >= beginTimeoutValue {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Received returns true if CAN message is received.
|
||||
func (d *Device) Received() bool {
|
||||
res, err := d.readStatus()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// if RX STATUS INSTRUCTION result is not 0x00 (= No RX message)
|
||||
// TODO: reconsider this logic
|
||||
return (res & mcpStatRxifMask) != 0x00
|
||||
}
|
||||
|
||||
// Rx returns received CAN message.
|
||||
func (d *Device) Rx() (*CANMsg, error) {
|
||||
err := d.readMsg()
|
||||
return d.msg, err
|
||||
}
|
||||
|
||||
// Tx transmits CAN Message.
|
||||
func (d *Device) Tx(canid uint32, dlc uint8, data []byte) error {
|
||||
// TODO: add ext, rtrBit, waitSent
|
||||
timeoutCount := 0
|
||||
|
||||
var bufNum, res uint8
|
||||
var err error
|
||||
res = mcpAlltxbusy
|
||||
for res == mcpAlltxbusy && (timeoutCount < timeoutvalue) {
|
||||
if timeoutCount > 0 {
|
||||
time.Sleep(time.Microsecond * 10)
|
||||
}
|
||||
bufNum, res, err = d.getNextFreeTxBuf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeoutCount++
|
||||
}
|
||||
if timeoutCount == timeoutvalue {
|
||||
return fmt.Errorf("Tx: Tx timeout")
|
||||
}
|
||||
err = d.writeCANMsg(bufNum, canid, 0, 0, dlc, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) init(speed, clock byte) error {
|
||||
err := d.Reset()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.setCANCTRLMode(modeConfig); err != nil {
|
||||
return fmt.Errorf("setCANCTRLMode %s: ", err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
|
||||
// set baudrate
|
||||
if err := d.configRate(speed, clock); err != nil {
|
||||
return fmt.Errorf("configRate %s: ", err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
|
||||
if err := d.initCANBuffers(); err != nil {
|
||||
return fmt.Errorf("initCANBuffers: %s ", err)
|
||||
}
|
||||
if err := d.setRegister(mcpCANINTE, mcpRX0IF|mcpRX1IF); err != nil {
|
||||
return fmt.Errorf("setRegister: %s ", err)
|
||||
}
|
||||
if err := d.modifyRegister(mcpRXB0CTRL, mcpRxbRxMask|mcpRxbBuktMask, mcpRxbRxStdExt|mcpRxbBuktMask); err != nil {
|
||||
return fmt.Errorf("modifyRegister: %s ", err)
|
||||
}
|
||||
if err := d.modifyRegister(mcpRXB1CTRL, mcpRxbRxMask, mcpRxbRxStdExt); err != nil {
|
||||
return fmt.Errorf("modifyRegister: %s ", err)
|
||||
}
|
||||
|
||||
if err := d.setMode(modeNormal); err != nil {
|
||||
return fmt.Errorf("setMode %s: ", err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset resets mcp2515.
|
||||
func (d *Device) Reset() error {
|
||||
d.cs.Low()
|
||||
_, err := d.spi.readWrite(mcpReset)
|
||||
d.cs.High()
|
||||
// time.Sleep(time.Microsecond * 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) setCANCTRLMode(newMode byte) error {
|
||||
// If the chip is asleep and we want to change mode then a manual wake needs to be done
|
||||
// This is done by setting the wake up interrupt flag
|
||||
// This undocumented trick was found at https://github.com/mkleemann/can/blob/master/can_sleep_mcp2515.c
|
||||
m, err := d.getMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m == modeSleep && newMode != modeSleep {
|
||||
r, err := d.readRegister(mcpCANINTE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wakeIntEnabled := (r & mcpWAKIF) == 0x00
|
||||
if !wakeIntEnabled {
|
||||
d.modifyRegister(mcpCANINTE, mcpWAKIF, mcpWAKIF)
|
||||
}
|
||||
// Set wake flag (this does the actual waking up)
|
||||
d.modifyRegister(mcpCANINTF, mcpWAKIF, mcpWAKIF)
|
||||
|
||||
// Wait for the chip to exit SLEEP and enter LISTENONLY mode.
|
||||
|
||||
// If the chip is not connected to a CAN bus (or the bus has no other powered nodes) it will sometimes trigger the wake interrupt as soon
|
||||
// as it's put to sleep, but it will stay in SLEEP mode instead of automatically switching to LISTENONLY mode.
|
||||
// In this situation the mode needs to be manually set to LISTENONLY.
|
||||
|
||||
if err := d.requestNewMode(modeListenOnly); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Turn wake interrupt back off if it was originally off
|
||||
if !wakeIntEnabled {
|
||||
d.modifyRegister(mcpCANINTE, mcpWAKIF, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear wake flag
|
||||
d.modifyRegister(mcpCANINTF, mcpWAKIF, 0)
|
||||
|
||||
return d.requestNewMode(newMode)
|
||||
}
|
||||
|
||||
func (d *Device) setMode(opMode byte) error {
|
||||
if opMode != modeSleep {
|
||||
d.mcpMode = opMode
|
||||
}
|
||||
|
||||
err := d.setCANCTRLMode(opMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) getMode() (byte, error) {
|
||||
r, err := d.readRegister(mcpCANSTAT)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return r & modeMask, nil
|
||||
}
|
||||
|
||||
func (d *Device) configRate(speed, clock byte) error {
|
||||
// TODO: add another baudrate
|
||||
var cfg1, cfg2, cfg3 byte
|
||||
set := true
|
||||
switch clock {
|
||||
case Clock16MHz:
|
||||
switch speed {
|
||||
case CAN500kBps:
|
||||
cfg1 = mcp16mHz500kBpsCfg1
|
||||
cfg2 = mcp16mHz500kBpsCfg2
|
||||
cfg3 = mcp16mHz500kBpsCfg3
|
||||
case CAN1000kBps:
|
||||
cfg1 = mcp16mHz1000kBpsCfg1
|
||||
cfg2 = mcp16mHz1000kBpsCfg2
|
||||
cfg3 = mcp16mHz1000kBpsCfg3
|
||||
default:
|
||||
set = false
|
||||
}
|
||||
case Clock8MHz:
|
||||
switch speed {
|
||||
case CAN500kBps:
|
||||
cfg1 = mcp8mHz500kBpsCfg1
|
||||
cfg2 = mcp8mHz500kBpsCfg2
|
||||
cfg3 = mcp8mHz500kBpsCfg3
|
||||
case CAN1000kBps:
|
||||
cfg1 = mcp8mHz1000kBpsCfg1
|
||||
cfg2 = mcp8mHz1000kBpsCfg2
|
||||
cfg3 = mcp8mHz1000kBpsCfg3
|
||||
default:
|
||||
set = false
|
||||
}
|
||||
default:
|
||||
set = false
|
||||
}
|
||||
if !set {
|
||||
return errors.New("invalid parameter")
|
||||
}
|
||||
if err := d.setRegister(mcpCNF1, cfg1); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.setRegister(mcpCNF2, cfg2); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.setRegister(mcpCNF3, cfg3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) initCANBuffers() error {
|
||||
a1 := byte(mcpTXB0CTRL)
|
||||
a2 := byte(mcpTXB1CTRL)
|
||||
a3 := byte(mcpTXB2CTRL)
|
||||
for i := 0; i < 14; i++ {
|
||||
if err := d.setRegister(a1, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.setRegister(a2, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.setRegister(a3, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
a1++
|
||||
a2++
|
||||
a3++
|
||||
}
|
||||
|
||||
if err := d.setRegister(mcpRXB0CTRL, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.setRegister(mcpRXB1CTRL, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) readMsg() error {
|
||||
status, err := d.readRxTxStatus()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (status & mcpRX0IF) == 0x01 {
|
||||
err := d.readRxBuffer(mcpReadRx0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if (status & mcpRX1IF) == 0x02 {
|
||||
err := d.readRxBuffer(mcpReadRx1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("readMsg: nothing is received")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) readRxBuffer(loadAddr uint8) error {
|
||||
msg := d.msg
|
||||
d.cs.Low()
|
||||
defer d.cs.High()
|
||||
_, err := d.spi.readWrite(loadAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.spi.read(4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := d.spi.rx
|
||||
msg.ID = uint32((uint32(buf[0]) << 3) + (uint32(buf[1]) >> 5))
|
||||
msg.Ext = false
|
||||
if (buf[1] & mcpTxbExideM) == mcpTxbExideM {
|
||||
// extended id
|
||||
msg.ID = uint32(uint32(msg.ID<<2) + uint32(buf[1]&0x03))
|
||||
msg.ID = uint32(uint32(msg.ID<<8) + uint32(buf[2]))
|
||||
msg.ID = uint32(uint32(msg.ID<<8) + uint32(buf[3]))
|
||||
msg.Ext = true
|
||||
}
|
||||
err = d.spi.read(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgSize := d.spi.rx[0]
|
||||
msg.Dlc = uint8(msgSize & mcpDlcMask)
|
||||
msg.Rtr = false
|
||||
if (msgSize & mcpRtrMask) == 0x40 {
|
||||
msg.Rtr = true
|
||||
}
|
||||
readLen := uint8(canMaxCharInMessage)
|
||||
if msg.Dlc < canMaxCharInMessage {
|
||||
readLen = msg.Dlc
|
||||
}
|
||||
err = d.spi.read(int(readLen))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg.Data = d.spi.rx
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) getNextFreeTxBuf() (uint8, uint8, error) {
|
||||
status, err := d.readStatus()
|
||||
if err != nil {
|
||||
return 0, mcpAlltxbusy, err
|
||||
}
|
||||
status &= mcpStatTxPendingMask
|
||||
|
||||
bufNum := uint8(0x00)
|
||||
|
||||
if status == mcpStatTxPendingMask {
|
||||
return 0, mcpAlltxbusy, nil
|
||||
}
|
||||
|
||||
for i := 0; i < int(mcpNTxbuffers-nReservedTx(0)); i++ {
|
||||
if (status & txStatusPendingFlag(uint8(i))) == 0 {
|
||||
bufNum = txCtrlReg(uint8(i)) + 1
|
||||
d.modifyRegister(mcpCANINTF, txIfFlag(uint8(i)), 0)
|
||||
return bufNum, mcp2515Ok, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, mcpAlltxbusy, nil
|
||||
}
|
||||
|
||||
func (d *Device) writeCANMsg(bufNum uint8, canid uint32, ext, rtrBit, dlc uint8, data []byte) error {
|
||||
d.cs.Low()
|
||||
defer d.cs.High()
|
||||
_, err := d.spi.readWrite(txSidhToLoad(bufNum))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.spi.clearBuffer(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.spi.setTxBufData(canid, ext, rtrBit, dlc, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.spi.write()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Since cs.Low and cs.High are executed in d.startTransmission,
|
||||
// it is necessary to set cs.High once to separate the instruction of mcp2515.
|
||||
d.cs.High()
|
||||
|
||||
err = d.startTransmission(bufNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SPI) setTxBufData(canid uint32, ext, rtrBit, dlc uint8, data []byte) error {
|
||||
canid = canid & 0x0FFFF
|
||||
if ext == 1 {
|
||||
// TODO: add Extended ID
|
||||
err := s.setTxData(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.setTxData(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.setTxData(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.setTxData(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := s.setTxData(byte(canid >> 3))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.setTxData(byte((canid & 0x07) << 5))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.setTxData(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.setTxData(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rtrBit == 1 {
|
||||
dlc |= mcpRtrMask
|
||||
} else {
|
||||
dlc |= (0)
|
||||
}
|
||||
err := s.setTxData(dlc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range data {
|
||||
err := s.setTxData(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) startTransmission(bufNum uint8) error {
|
||||
d.cs.Low()
|
||||
_, err := d.spi.readWrite(txSidhToRTS(bufNum))
|
||||
d.cs.High()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func nReservedTx(number uint8) uint8 {
|
||||
if number < mcpNTxbuffers {
|
||||
return number
|
||||
}
|
||||
return mcpNTxbuffers - 1
|
||||
}
|
||||
|
||||
func txStatusPendingFlag(i uint8) uint8 {
|
||||
ret := uint8(0)
|
||||
switch i {
|
||||
case 0:
|
||||
ret = mcpStatTx0Pending
|
||||
case 1:
|
||||
ret = mcpStatTx1Pending
|
||||
case 2:
|
||||
ret = mcpStatTx2Pending
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func txCtrlReg(status uint8) uint8 {
|
||||
ret := uint8(0)
|
||||
switch status {
|
||||
case 0:
|
||||
ret = mcpTXB0CTRL
|
||||
case 1:
|
||||
ret = mcpTXB1CTRL
|
||||
case 2:
|
||||
ret = mcpTXB2CTRL
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func txIfFlag(i uint8) uint8 {
|
||||
ret := uint8(0)
|
||||
switch i {
|
||||
case 0:
|
||||
ret = mcpTX0IF
|
||||
case 1:
|
||||
ret = mcpTX1IF
|
||||
case 2:
|
||||
ret = mcpTX2IF
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func txSidhToSidh(i uint8) uint8 {
|
||||
ret := uint8(0)
|
||||
switch i {
|
||||
case mcpTX0IF:
|
||||
ret = mcpTXB0SIDH
|
||||
case mcpTX1IF:
|
||||
ret = mcpTXB1SIDH
|
||||
case mcpTX2IF:
|
||||
ret = mcpTXB2SIDH
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func txSidhToRTS(i uint8) uint8 {
|
||||
ret := uint8(0)
|
||||
switch i {
|
||||
case mcpTXB0SIDH:
|
||||
ret = mcpRtsTx0
|
||||
case mcpTXB1SIDH:
|
||||
ret = mcpRtsTx1
|
||||
case mcpTXB2SIDH:
|
||||
ret = mcpRtsTx2
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func txSidhToLoad(i uint8) uint8 {
|
||||
ret := uint8(0)
|
||||
switch i {
|
||||
case mcpTXB0SIDH:
|
||||
ret = mcpLoadTx0
|
||||
case mcpTXB1SIDH:
|
||||
ret = mcpLoadTx1
|
||||
case mcpTXB2SIDH:
|
||||
ret = mcpLoadTx2
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d *Device) setRegister(addr, value byte) error {
|
||||
d.cs.Low()
|
||||
defer d.cs.High()
|
||||
_, err := d.spi.readWrite(mcpWrite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.spi.readWrite(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.spi.readWrite(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// time.Sleep(time.Microsecond * 4)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) readRegister(addr byte) (byte, error) {
|
||||
d.cs.Low()
|
||||
defer d.cs.High()
|
||||
_, err := d.spi.readWrite(mcpRead)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = d.spi.readWrite(addr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = d.spi.read(1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// time.Sleep(time.Microsecond * 4)
|
||||
return d.spi.rx[0], nil
|
||||
}
|
||||
|
||||
func (d *Device) modifyRegister(addr, mask, data byte) error {
|
||||
d.cs.Low()
|
||||
defer d.cs.High()
|
||||
_, err := d.spi.readWrite(mcpBitMod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.spi.readWrite(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.spi.readWrite(mask)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.spi.readWrite(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// time.Sleep(time.Microsecond * 4)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) requestNewMode(newMode byte) error {
|
||||
s := time.Now()
|
||||
for {
|
||||
err := d.modifyRegister(mcpCANCTRL, modeMask, newMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := d.readRegister(mcpCANSTAT)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r&modeMask == newMode {
|
||||
return nil
|
||||
} else if e := time.Now(); e.Sub(s) > 200*time.Millisecond {
|
||||
return errors.New("requestNewMode max time expired")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) readStatus() (byte, error) {
|
||||
d.cs.Low()
|
||||
defer d.cs.High()
|
||||
_, err := d.spi.readWrite(mcpReadStatus)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = d.spi.read(1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return d.spi.rx[0], nil
|
||||
}
|
||||
|
||||
func (d *Device) readRxTxStatus() (byte, error) {
|
||||
status, err := d.readStatus()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ret := status & (mcpStatTxifMask | mcpStatRxifMask)
|
||||
if (status & mcpStatTx0if) == 0x08 {
|
||||
ret |= mcpTX0IF
|
||||
}
|
||||
if (status & mcpStatTx1if) == 0x20 {
|
||||
ret |= mcpTX1IF
|
||||
}
|
||||
if (status & mcpStatTx2if) == 0x80 {
|
||||
ret |= mcpTX2IF
|
||||
}
|
||||
ret |= ret & mcpStatRxifMask
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type SPI struct {
|
||||
bus drivers.SPI
|
||||
tx []byte
|
||||
rx []byte
|
||||
}
|
||||
|
||||
const (
|
||||
tx = iota
|
||||
rx
|
||||
)
|
||||
|
||||
func (s *SPI) readWrite(w byte) (byte, error) {
|
||||
return s.bus.Transfer(w)
|
||||
}
|
||||
|
||||
func (s *SPI) read(readLength int) error {
|
||||
err := s.clearBuffer(rx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.setBufferLength(readLength, rx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.bus.Tx(nil, s.rx)
|
||||
}
|
||||
|
||||
func (s *SPI) write() error {
|
||||
return s.bus.Tx(s.tx, nil)
|
||||
}
|
||||
|
||||
func (s *SPI) clearBuffer(dir int) error { return s.setBufferLength(0, dir) }
|
||||
|
||||
func (s *SPI) setBufferLength(length int, dir int) error {
|
||||
if dir == tx {
|
||||
if length > cap(s.tx) {
|
||||
return fmt.Errorf("length is longer than capacity")
|
||||
}
|
||||
s.tx = s.tx[:length]
|
||||
} else if dir == rx {
|
||||
if length > cap(s.rx) {
|
||||
return fmt.Errorf("length is longer than capacity")
|
||||
}
|
||||
s.rx = s.rx[:length]
|
||||
} else {
|
||||
return fmt.Errorf("invalid direction")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SPI) setTxData(data byte) error {
|
||||
if len(s.tx) >= bufferSize {
|
||||
return fmt.Errorf("cannot expand buffer (to avoid memory allocation)")
|
||||
}
|
||||
s.tx = append(s.tx, data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) dumpMode() error {
|
||||
m, err := d.getMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Mode: %02X\r\n", m)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) dumpRegister(addr byte) error {
|
||||
r, err := d.readRegister(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Register: %02X = %02X\r\n", addr, r)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
// Package mcp2515 implements a driver for the MCP2515 CAN Controller.
|
||||
//
|
||||
// Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/MCP2515-Stand-Alone-CAN-Controller-with-SPI-20001801J.pdf
|
||||
//
|
||||
package mcp2515 // import "tinygo.org/x/drivers/mcp2515"
|
||||
|
||||
const DebugEn = 0
|
||||
|
||||
const (
|
||||
// begin mt
|
||||
|
||||
timeoutvalue = 50
|
||||
mcpSidh = 0
|
||||
mcpSidl = 1
|
||||
mcpEid8 = 2
|
||||
mcpEid0 = 3
|
||||
|
||||
mcpTxbExideM = 0x08 // in txbnsidl
|
||||
mcpDlcMask = 0x0f //= 4 lsbits
|
||||
mcpRtrMask = 0x40 // =(1<=<6) bit= 6
|
||||
|
||||
mcpRxbRxAny = 0x60
|
||||
mcpRxbRxExt = 0x40
|
||||
mcpRxbRxStd = 0x20
|
||||
mcpRxbRxStdExt = 0x00
|
||||
mcpRxbRxMask = 0x60
|
||||
mcpRxbBuktMask = 1 << 2
|
||||
|
||||
// bits in the txbnctrl registers.
|
||||
|
||||
mcpTxbTxbufeM = 0x80
|
||||
mcpTxbAbtfM = 0x40
|
||||
mcpTxbMloaM = 0x20
|
||||
mcpTxbTxerrM = 0x10
|
||||
mcpTxbTxreqM = 0x08
|
||||
mcpTxbTxieM = 0x04
|
||||
mcpTxbTxp10M = 0x03
|
||||
|
||||
mcpTxbRtrM = 0x40 // in txbndlc
|
||||
mcpRxbIdeM = 0x08 // in rxbnsidl
|
||||
mcpRxbRtrM = 0x40 // in rxbndlc
|
||||
|
||||
mcpStatTxPendingMask = 0x54
|
||||
mcpStatTx0Pending = 0x04
|
||||
mcpStatTx1Pending = 0x10
|
||||
mcpStatTx2Pending = 0x40
|
||||
mcpStatTxifMask = 0xa8
|
||||
mcpStatTx0if = 0x08
|
||||
mcpStatTx1if = 0x20
|
||||
mcpStatTx2if = 0x80
|
||||
mcpStatRxifMask = 0x03
|
||||
mcpStatRx0if = 1 << 0
|
||||
mcpStatRx1if = 1 << 1
|
||||
|
||||
mcpEflgRx1ovr = 1 << 7
|
||||
mcpEflgRx0ovr = 1 << 6
|
||||
mcpEflgTxbo = 1 << 5
|
||||
mcpEflgTxep = 1 << 4
|
||||
mcpEflgRxep = 1 << 3
|
||||
mcpEflgTxwar = 1 << 2
|
||||
mcpEflgRxwar = 1 << 1
|
||||
mcpEflgEwarn = 1 << 0
|
||||
mcpEflgErrormask = 0xf8 //= 5 ms-bits
|
||||
|
||||
// define mcp2515 register addresses
|
||||
|
||||
mcpRXF0SIDH = 0x00
|
||||
mcpRXF0SIDL = 0x01
|
||||
mcpRXF0EID8 = 0x02
|
||||
mcpRXF0EID0 = 0x03
|
||||
mcpRXF1SIDH = 0x04
|
||||
mcpRXF1SIDL = 0x05
|
||||
mcpRXF1EID8 = 0x06
|
||||
mcpRXF1EID0 = 0x07
|
||||
mcpRXF2SIDH = 0x08
|
||||
mcpRXF2SIDL = 0x09
|
||||
mcpRXF2EID8 = 0x0a
|
||||
mcpRXF2EID0 = 0x0b
|
||||
mcpBFPCTRL = 0x0c
|
||||
mcpTXRTSCTRl = 0x0d
|
||||
mcpCANSTAT = 0x0e
|
||||
mcpCANCTRL = 0x0f
|
||||
mcpRXF3SIDH = 0x10
|
||||
mcpRXF3SIDL = 0x11
|
||||
mcpRXF3EID8 = 0x12
|
||||
mcpRXF3EID0 = 0x13
|
||||
mcpRXF4SIDH = 0x14
|
||||
mcpRXF4SIDL = 0x15
|
||||
mcpRXF4EID8 = 0x16
|
||||
mcpRXF4EID0 = 0x17
|
||||
mcpRXF5SIDH = 0x18
|
||||
mcpRXF5SIDL = 0x19
|
||||
mcpRXF5EID8 = 0x1a
|
||||
mcpRXF5EID0 = 0x1b
|
||||
mcpTEC = 0x1c
|
||||
mcpREC = 0x1d
|
||||
mcpRXM0SIDH = 0x20
|
||||
mcpRXM0SIDL = 0x21
|
||||
mcpRXM0EID8 = 0x22
|
||||
mcpRXM0EID0 = 0x23
|
||||
mcpRXM1SIDH = 0x24
|
||||
mcpRXM1SIDL = 0x25
|
||||
mcpRXM1EID8 = 0x26
|
||||
mcpRXM1EID0 = 0x27
|
||||
mcpCNF3 = 0x28
|
||||
mcpCNF2 = 0x29
|
||||
mcpCNF1 = 0x2a
|
||||
mcpCANINTE = 0x2b
|
||||
mcpCANINTF = 0x2c
|
||||
mcpEFLG = 0x2d
|
||||
mcpTXB0CTRL = 0x30
|
||||
mcpTXB0SIDH = 0x31
|
||||
mcpTXB1CTRL = 0x40
|
||||
mcpTXB1SIDH = 0x41
|
||||
mcpTXB2CTRL = 0x50
|
||||
mcpTXB2SIDH = 0x51
|
||||
mcpRXB0CTRL = 0x60
|
||||
mcpRXB0SIDH = 0x61
|
||||
mcpRXB1CTRL = 0x70
|
||||
mcpRXB1SIDH = 0x71
|
||||
|
||||
mcpTxInt = 0x1c // enable all transmit interrup ts
|
||||
mcpTx01Int = 0x0c // enable txb0 and txb1 interru pts
|
||||
mcpRxInt = 0x03 // enable receive interrupts
|
||||
mcpNoInt = 0x00 // disable all interrupts
|
||||
|
||||
mcpTx01Mask = 0x14
|
||||
mcpTxMask = 0x54
|
||||
|
||||
// define spi instruction set
|
||||
mcpWrite = 0x02
|
||||
mcpRead = 0x03
|
||||
mcpBitMod = 0x05
|
||||
mcpLoadTx0 = 0x40
|
||||
mcpLoadTx1 = 0x42
|
||||
mcpLoadTx2 = 0x44
|
||||
|
||||
mcpRtsTx0 = 0x81
|
||||
mcpRtsTx1 = 0x82
|
||||
mcpRtsTx2 = 0x84
|
||||
mcpRtsAll = 0x87
|
||||
mcpReadRx0 = 0x90
|
||||
mcpReadRx1 = 0x94
|
||||
mcpReadStatus = 0xa0
|
||||
mcpRxStatus = 0xb0
|
||||
mcpReset = 0xc0
|
||||
|
||||
// canctrl register values
|
||||
|
||||
modeNormal = 0x00
|
||||
modeSleep = 0x20
|
||||
modeLoopBack = 0x40
|
||||
modeListenOnly = 0x60
|
||||
modeConfig = 0x80
|
||||
modePowerUp = 0xe0
|
||||
modeMask = 0xe0
|
||||
abortTx = 0x10
|
||||
modeOneShot = 0x08
|
||||
clkoutEnable = 0x04
|
||||
clkoutDisable = 0x00
|
||||
clkoutPs1 = 0x00
|
||||
clkoutPs2 = 0x01
|
||||
clkoutPs4 = 0x02
|
||||
clkoutPs8 = 0x03
|
||||
|
||||
// cnf1 register values
|
||||
|
||||
sjw1 = 0x00
|
||||
sjw2 = 0x40
|
||||
sjw3 = 0x80
|
||||
sjw4 = 0xc0
|
||||
|
||||
// cnf2 register values
|
||||
|
||||
btlmode = 0x80
|
||||
sample1x = 0x00
|
||||
sample3x = 0x40
|
||||
|
||||
// cnf3 register values
|
||||
|
||||
sofEnable = 0x80
|
||||
sofDisable = 0x00
|
||||
wakfilEnable = 0x40
|
||||
wakfilDisable = 0x00
|
||||
|
||||
// canintf register bits
|
||||
|
||||
mcpRX0IF = 0x01
|
||||
mcpRX1IF = 0x02
|
||||
mcpTX0IF = 0x04
|
||||
mcpTX1IF = 0x08
|
||||
mcpTX2IF = 0x10
|
||||
mcpERRIF = 0x20
|
||||
mcpWAKIF = 0x40
|
||||
mcpMERRF = 0x80
|
||||
|
||||
// bfpctrl register bits
|
||||
|
||||
b1bfs = 0x20
|
||||
b0bfs = 0x10
|
||||
b1bfe = 0x08
|
||||
b0bfe = 0x04
|
||||
b1bfm = 0x02
|
||||
b0bfm = 0x01
|
||||
|
||||
// txrtctrl register bits
|
||||
|
||||
b2rts = 0x20
|
||||
b1rts = 0x10
|
||||
b0rts = 0x08
|
||||
b2rtsm = 0x04
|
||||
b1rtsm = 0x02
|
||||
b0rtsm = 0x01
|
||||
|
||||
// clock
|
||||
|
||||
Clock16MHz = 1
|
||||
Clock8MHz = 2
|
||||
|
||||
// speed= 16m
|
||||
|
||||
mcp16mHz1000kBpsCfg1 = 0x00
|
||||
mcp16mHz1000kBpsCfg2 = 0xd0
|
||||
mcp16mHz1000kBpsCfg3 = 0x82
|
||||
|
||||
mcp16mHz500kBpsCfg1 = 0x00
|
||||
mcp16mHz500kBpsCfg2 = 0xf0
|
||||
mcp16mHz500kBpsCfg3 = 0x86
|
||||
|
||||
mcp16mHz250kBpsCfg1 = 0x41
|
||||
mcp16mHz250kBpsCfg2 = 0xf1
|
||||
mcp16mHz250kBpsCfg3 = 0x85
|
||||
|
||||
mcp16mHz200kBpsCfg1 = 0x01
|
||||
mcp16mHz200kBpsCfg2 = 0xfa
|
||||
mcp16mHz200kBpsCfg3 = 0x87
|
||||
|
||||
mcp16mHz125kBpsCfg1 = 0x03
|
||||
mcp16mHz125kBpsCfg2 = 0xf0
|
||||
mcp16mHz125kBpsCfg3 = 0x86
|
||||
|
||||
mcp16mHz100kBpsCfg1 = 0x03
|
||||
mcp16mHz100kBpsCfg2 = 0xfa
|
||||
mcp16mHz100kBpsCfg3 = 0x87
|
||||
|
||||
mcp16mHz95kBpsCfg1 = 0x03
|
||||
mcp16mHz95kBpsCfg2 = 0xad
|
||||
mcp16mHz95kBpsCfg3 = 0x07
|
||||
|
||||
mcp16mHz83k3BpsCfg1 = 0x03
|
||||
mcp16mHz83k3BpsCfg2 = 0xbe
|
||||
mcp16mHz83k3BpsCfg3 = 0x07
|
||||
|
||||
mcp16mHz80kBpsCfg1 = 0x03
|
||||
mcp16mHz80kBpsCfg2 = 0xff
|
||||
mcp16mHz80kBpsCfg3 = 0x87
|
||||
|
||||
mcp16mHz50kBpsCfg1 = 0x07
|
||||
mcp16mHz50kBpsCfg2 = 0xfa
|
||||
mcp16mHz50kBpsCfg3 = 0x87
|
||||
|
||||
mcp16mHz40kBpsCfg1 = 0x07
|
||||
mcp16mHz40kBpsCfg2 = 0xff
|
||||
mcp16mHz40kBpsCfg3 = 0x87
|
||||
|
||||
mcp16mHz33kBpsCfg1 = 0x09
|
||||
mcp16mHz33kBpsCfg2 = 0xbe
|
||||
mcp16mHz33kBpsCfg3 = 0x07
|
||||
|
||||
mcp16mHz31k25BpsCfg1 = 0x0f
|
||||
mcp16mHz31k25BpsCfg2 = 0xf1
|
||||
mcp16mHz31k25BpsCfg3 = 0x85
|
||||
|
||||
mcp16mHz25kBpsCfg1 = 0x0f
|
||||
mcp16mHz25kBpsCfg2 = 0xba
|
||||
mcp16mHz25kBpsCfg3 = 0x07
|
||||
|
||||
mcp16mHz20kBpsCfg1 = 0x0f
|
||||
mcp16mHz20kBpsCfg2 = 0xff
|
||||
mcp16mHz20kBpsCfg3 = 0x87
|
||||
|
||||
mcp16mHz10kBpsCfg1 = 0x1f
|
||||
mcp16mHz10kBpsCfg2 = 0xff
|
||||
mcp16mHz10kBpsCfg3 = 0x87
|
||||
|
||||
mcp16mHz5kBpsCfg1 = 0x3f
|
||||
mcp16mHz5kBpsCfg2 = 0xff
|
||||
mcp16mHz5kBpsCfg3 = 0x87
|
||||
|
||||
mcp16mHz666kBpsCfg1 = 0x00
|
||||
mcp16mHz666kBpsCfg2 = 0xa0
|
||||
mcp16mHz666kBpsCfg3 = 0x04
|
||||
|
||||
// speed= 8m
|
||||
|
||||
mcp8mHz1000kBpsCfg1 = 0x00
|
||||
mcp8mHz1000kBpsCfg2 = 0x80
|
||||
mcp8mHz1000kBpsCfg3 = 0x00
|
||||
|
||||
mcp8mHz500kBpsCfg1 = 0x00
|
||||
mcp8mHz500kBpsCfg2 = 0x90
|
||||
mcp8mHz500kBpsCfg3 = 0x02
|
||||
|
||||
mcp8mHz250kBpsCfg1 = 0x00
|
||||
mcp8mHz250kBpsCfg2 = 0xb1
|
||||
mcp8mHz250kBpsCfg3 = 0x05
|
||||
|
||||
mcp8mHz200kBpsCfg1 = 0x00
|
||||
mcp8mHz200kBpsCfg2 = 0xb4
|
||||
mcp8mHz200kBpsCfg3 = 0x06
|
||||
|
||||
mcp8mHz125kBpsCfg1 = 0x01
|
||||
mcp8mHz125kBpsCfg2 = 0xb1
|
||||
mcp8mHz125kBpsCfg3 = 0x05
|
||||
|
||||
mcp8mHz100kBpsCfg1 = 0x01
|
||||
mcp8mHz100kBpsCfg2 = 0xb4
|
||||
mcp8mHz100kBpsCfg3 = 0x06
|
||||
|
||||
mcp8mHz80kBpsCfg1 = 0x01
|
||||
mcp8mHz80kBpsCfg2 = 0xbf
|
||||
mcp8mHz80kBpsCfg3 = 0x07
|
||||
|
||||
mcp8mHz50kBpsCfg1 = 0x03
|
||||
mcp8mHz50kBpsCfg2 = 0xb4
|
||||
mcp8mHz50kBpsCfg3 = 0x06
|
||||
|
||||
mcp8mHz40kBpsCfg1 = 0x03
|
||||
mcp8mHz40kBpsCfg2 = 0xbf
|
||||
mcp8mHz40kBpsCfg3 = 0x07
|
||||
|
||||
mcp8mHz31k25BpsCfg1 = 0x07
|
||||
mcp8mHz31k25BpsCfg2 = 0xa4
|
||||
mcp8mHz31k25BpsCfg3 = 0x04
|
||||
|
||||
mcp8mHz20kBpsCfg1 = 0x07
|
||||
mcp8mHz20kBpsCfg2 = 0xbf
|
||||
mcp8mHz20kBpsCfg3 = 0x07
|
||||
|
||||
mcp8mHz10kBpsCfg1 = 0x0f
|
||||
mcp8mHz10kBpsCfg2 = 0xbf
|
||||
mcp8mHz10kBpsCfg3 = 0x07
|
||||
|
||||
mcp8mHz5kBpsCfg1 = 0x1f
|
||||
mcp8mHz5kBpsCfg2 = 0xbf
|
||||
mcp8mHz5kBpsCfg3 = 0x07
|
||||
|
||||
mcp16mHz47kBpsCfg1 = 0x06
|
||||
mcp16mHz47kBpsCfg2 = 0xbe
|
||||
mcp16mHz47kBpsCfg3 = 0x07
|
||||
|
||||
mcpdebug = 0
|
||||
mcpdebugTxbuf = 0
|
||||
mcpNTxbuffers = 3
|
||||
|
||||
mcpRxbuf0 = 0x61
|
||||
mcpRxbuf1 = 0x71
|
||||
|
||||
mcp2515Ok = 0
|
||||
mcp2515Fail = 1
|
||||
mcpAlltxbusy = 2
|
||||
|
||||
candebug = 1
|
||||
|
||||
canuseloop = 0
|
||||
|
||||
cansendtimeout = 200 // milliseconds
|
||||
|
||||
mcpPinHiz = 0
|
||||
mcpPinInt = 1
|
||||
mcpPinOut = 2
|
||||
mcpPinIn = 3
|
||||
|
||||
mcpRx0bf = 0
|
||||
mcpRx1bf = 1
|
||||
mcpTx0rts = 2
|
||||
mcpTx1rts = 3
|
||||
mcpTx2rts = 4
|
||||
|
||||
// initial value of gcanautoprocess
|
||||
|
||||
canautoprocess = 1
|
||||
canautoon = 1
|
||||
canautooff = 0
|
||||
canStdid = 0
|
||||
canExtid = 1
|
||||
candefaultident = 0x55cc
|
||||
candefaultidentext = 1
|
||||
|
||||
CAN5kBps = 1
|
||||
CAN10kBps = 2
|
||||
CAN20kBps = 3
|
||||
CAN25kBps = 4
|
||||
CAN31k25Bps = 5
|
||||
CAN33kBps = 6
|
||||
CAN40kBps = 7
|
||||
CAN50kBps = 8
|
||||
CAN80kBps = 9
|
||||
CAN83k3Bps = 10
|
||||
CAN95kBps = 11
|
||||
CAN100kBps = 12
|
||||
CAN125kBps = 13
|
||||
CAN200kBps = 14
|
||||
CAN250kBps = 15
|
||||
CAN500kBps = 16
|
||||
CAN666kBps = 17
|
||||
CAN1000kBps = 18
|
||||
CAN47kBps = 19
|
||||
|
||||
canOk = 0
|
||||
canFailinit = 1
|
||||
canFailtx = 2
|
||||
canMsgavail = 3
|
||||
canNomsg = 4
|
||||
canCtrlerror = 5
|
||||
canGettxbftimeout = 6
|
||||
canSendmsgtimeout = 7
|
||||
canFail = 0xff
|
||||
|
||||
canMaxCharInMessage = 8
|
||||
)
|
||||
+4
-2
@@ -7,11 +7,13 @@ package mcp3008 // import "tinygo.org/x/drivers/mcp3008"
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps MCP3008 SPI ADC.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
bus drivers.SPI
|
||||
cs machine.Pin
|
||||
tx []byte
|
||||
rx []byte
|
||||
@@ -32,7 +34,7 @@ type ADCPin struct {
|
||||
}
|
||||
|
||||
// New returns a new MCP3008 driver. Pass in a fully configured SPI bus.
|
||||
func New(b machine.SPI, csPin machine.Pin) *Device {
|
||||
func New(b drivers.SPI, csPin machine.Pin) *Device {
|
||||
d := &Device{bus: b,
|
||||
cs: csPin,
|
||||
tx: make([]byte, 3),
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// +build microbit
|
||||
|
||||
// Package microbitmatrix implements a driver for the BBC micro:bit's LED matrix.
|
||||
//
|
||||
// Schematic: https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf
|
||||
//
|
||||
package microbitmatrix // import "tinygo.org/x/drivers/microbitmatrix"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
var matrixRotations = [4][5][5][2]uint8{
|
||||
{ // 0
|
||||
{{0, 0}, {1, 3}, {0, 1}, {1, 4}, {0, 2}},
|
||||
{{2, 3}, {2, 4}, {2, 5}, {2, 6}, {2, 7}},
|
||||
{{1, 1}, {0, 8}, {1, 2}, {2, 8}, {1, 0}},
|
||||
{{0, 7}, {0, 6}, {0, 5}, {0, 4}, {0, 3}},
|
||||
{{2, 2}, {1, 6}, {2, 0}, {1, 5}, {2, 1}},
|
||||
},
|
||||
{ // 90 CCW
|
||||
{{0, 2}, {2, 7}, {1, 0}, {0, 3}, {2, 1}},
|
||||
{{1, 4}, {2, 6}, {2, 8}, {0, 4}, {1, 5}},
|
||||
{{0, 1}, {2, 5}, {1, 2}, {0, 5}, {2, 0}},
|
||||
{{1, 3}, {2, 4}, {0, 8}, {0, 6}, {1, 6}},
|
||||
{{0, 0}, {2, 3}, {1, 1}, {0, 7}, {2, 2}},
|
||||
},
|
||||
{ // 180
|
||||
{{2, 1}, {1, 5}, {2, 0}, {1, 6}, {2, 2}},
|
||||
{{0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}},
|
||||
{{1, 0}, {2, 8}, {1, 2}, {0, 8}, {1, 1}},
|
||||
{{2, 7}, {2, 6}, {2, 5}, {2, 4}, {2, 3}},
|
||||
{{0, 2}, {1, 4}, {0, 1}, {1, 3}, {0, 0}},
|
||||
},
|
||||
{ // 270
|
||||
{{2, 2}, {0, 7}, {1, 1}, {2, 3}, {0, 0}},
|
||||
{{1, 6}, {0, 6}, {0, 8}, {2, 4}, {1, 3}},
|
||||
{{2, 0}, {0, 5}, {1, 2}, {2, 5}, {0, 1}},
|
||||
{{1, 5}, {0, 4}, {2, 8}, {2, 6}, {1, 4}},
|
||||
{{2, 1}, {0, 3}, {1, 0}, {2, 7}, {0, 2}},
|
||||
},
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
pin [12]machine.Pin
|
||||
buffer [3][9]bool
|
||||
rotation uint8
|
||||
}
|
||||
|
||||
// Configure sets up the device.
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
d.SetRotation(cfg.Rotation)
|
||||
|
||||
for i := machine.LED_COL_1; i <= machine.LED_ROW_3; i++ {
|
||||
d.pin[i-machine.LED_COL_1] = i
|
||||
d.pin[i-machine.LED_COL_1].Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
}
|
||||
d.ClearDisplay()
|
||||
d.DisableAll()
|
||||
}
|
||||
|
||||
// Display sends the buffer (if any) to the screen.
|
||||
func (d *Device) Display() error {
|
||||
for row := 0; row < 3; row++ {
|
||||
d.DisableAll()
|
||||
d.pin[9+row].High()
|
||||
|
||||
for col := 0; col < 9; col++ {
|
||||
if d.buffer[row][col] {
|
||||
d.pin[col].Low()
|
||||
}
|
||||
|
||||
}
|
||||
time.Sleep(time.Millisecond * 2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearDisplay erases the internal buffer
|
||||
func (d *Device) ClearDisplay() {
|
||||
for row := 0; row < 3; row++ {
|
||||
for col := 0; col < 9; col++ {
|
||||
d.buffer[row][col] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DisableAll disables all the LEDs without modifying the buffer
|
||||
func (d *Device) DisableAll() {
|
||||
for i := machine.LED_COL_1; i <= machine.LED_COL_9; i++ {
|
||||
d.pin[i-machine.LED_COL_1].High()
|
||||
}
|
||||
for i := machine.LED_ROW_1; i <= machine.LED_ROW_3; i++ {
|
||||
d.pin[i-machine.LED_COL_1].Low()
|
||||
}
|
||||
}
|
||||
|
||||
// EnableAll enables all the LEDs without modifying the buffer
|
||||
func (d *Device) EnableAll() {
|
||||
for i := machine.LED_COL_1; i <= machine.LED_COL_9; i++ {
|
||||
d.pin[i-machine.LED_COL_1].Low()
|
||||
}
|
||||
for i := machine.LED_ROW_1; i <= machine.LED_ROW_3; i++ {
|
||||
d.pin[i-machine.LED_COL_1].High()
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
return 5, 5
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// +build microbit_v2
|
||||
|
||||
// Package microbitmatrix implements a driver for the BBC micro:bit version 2 LED matrix.
|
||||
//
|
||||
// Schematic:
|
||||
//
|
||||
package microbitmatrix // import "tinygo.org/x/drivers/microbitmatrix"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
var matrixRotations = [4][5][5][2]uint8{
|
||||
{ // 0
|
||||
{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}},
|
||||
{{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}},
|
||||
{{0, 2}, {1, 2}, {2, 2}, {3, 2}, {4, 2}},
|
||||
{{0, 3}, {1, 3}, {2, 3}, {3, 3}, {4, 3}},
|
||||
{{0, 4}, {1, 4}, {2, 4}, {3, 4}, {4, 4}},
|
||||
},
|
||||
{ // 90 CCW
|
||||
{{4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 4}},
|
||||
{{3, 0}, {3, 1}, {3, 2}, {3, 3}, {3, 4}},
|
||||
{{2, 0}, {2, 1}, {2, 2}, {2, 3}, {2, 4}},
|
||||
{{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4}},
|
||||
{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}},
|
||||
},
|
||||
{ // 180
|
||||
{{4, 4}, {3, 4}, {2, 4}, {1, 4}, {0, 4}},
|
||||
{{4, 3}, {3, 3}, {2, 3}, {1, 3}, {0, 3}},
|
||||
{{4, 2}, {3, 2}, {2, 2}, {1, 2}, {0, 2}},
|
||||
{{4, 1}, {3, 1}, {2, 1}, {1, 1}, {0, 1}},
|
||||
{{4, 0}, {3, 0}, {2, 0}, {1, 0}, {0, 0}},
|
||||
},
|
||||
{ // 270
|
||||
{{0, 4}, {0, 3}, {0, 2}, {0, 1}, {0, 0}},
|
||||
{{1, 4}, {1, 3}, {1, 2}, {1, 1}, {1, 0}},
|
||||
{{2, 4}, {2, 3}, {2, 2}, {2, 1}, {2, 0}},
|
||||
{{3, 4}, {3, 3}, {3, 2}, {3, 1}, {3, 0}},
|
||||
{{4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}},
|
||||
},
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
pin [10]machine.Pin
|
||||
buffer [5][5]bool
|
||||
rotation uint8
|
||||
}
|
||||
|
||||
// Configure sets up the device.
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
d.SetRotation(cfg.Rotation)
|
||||
|
||||
d.pin[0] = machine.LED_COL_1
|
||||
d.pin[1] = machine.LED_COL_2
|
||||
d.pin[2] = machine.LED_COL_3
|
||||
d.pin[3] = machine.LED_COL_4
|
||||
d.pin[4] = machine.LED_COL_5
|
||||
|
||||
d.pin[5] = machine.LED_ROW_1
|
||||
d.pin[6] = machine.LED_ROW_2
|
||||
d.pin[7] = machine.LED_ROW_3
|
||||
d.pin[8] = machine.LED_ROW_4
|
||||
d.pin[9] = machine.LED_ROW_5
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
d.pin[i].Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
}
|
||||
|
||||
d.ClearDisplay()
|
||||
d.DisableAll()
|
||||
}
|
||||
|
||||
// Display sends the buffer (if any) to the screen.
|
||||
func (d *Device) Display() error {
|
||||
for x := 0; x < 5; x++ {
|
||||
d.DisableAll()
|
||||
d.pin[x].Low()
|
||||
|
||||
for y := 0; y < 5; y++ {
|
||||
if d.buffer[x][y] {
|
||||
d.pin[5+y].High()
|
||||
} else {
|
||||
d.pin[5+y].Low()
|
||||
}
|
||||
|
||||
}
|
||||
time.Sleep(time.Millisecond * 4)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearDisplay erases the internal buffer
|
||||
func (d *Device) ClearDisplay() {
|
||||
for row := 0; row < 5; row++ {
|
||||
for col := 0; col < 5; col++ {
|
||||
d.buffer[row][col] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DisableAll disables all the LEDs without modifying the buffer
|
||||
func (d *Device) DisableAll() {
|
||||
for i := 0; i < 5; i++ {
|
||||
d.pin[i].High()
|
||||
d.pin[5+i].Low()
|
||||
}
|
||||
}
|
||||
|
||||
// EnableAll enables all the LEDs without modifying the buffer
|
||||
func (d *Device) EnableAll() {
|
||||
for i := 0; i < 5; i++ {
|
||||
d.pin[i].Low()
|
||||
d.pin[5+i].High()
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
return 5, 5
|
||||
}
|
||||
@@ -6,68 +6,17 @@ package microbitmatrix // import "tinygo.org/x/drivers/microbitmatrix"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
var matrixRotations = [4][5][5][2]uint8{
|
||||
{ // 0
|
||||
{{0, 0}, {1, 3}, {0, 1}, {1, 4}, {0, 2}},
|
||||
{{2, 3}, {2, 4}, {2, 5}, {2, 6}, {2, 7}},
|
||||
{{1, 1}, {0, 8}, {1, 2}, {2, 8}, {1, 0}},
|
||||
{{0, 7}, {0, 6}, {0, 5}, {0, 4}, {0, 3}},
|
||||
{{2, 2}, {1, 6}, {2, 0}, {1, 5}, {2, 1}},
|
||||
},
|
||||
{ // 90 CCW
|
||||
{{0, 2}, {2, 7}, {1, 0}, {0, 3}, {2, 1}},
|
||||
{{1, 4}, {2, 6}, {2, 8}, {0, 4}, {1, 5}},
|
||||
{{0, 1}, {2, 5}, {1, 2}, {0, 5}, {2, 0}},
|
||||
{{1, 3}, {2, 4}, {0, 8}, {0, 6}, {1, 6}},
|
||||
{{0, 0}, {2, 3}, {1, 1}, {0, 7}, {2, 2}},
|
||||
},
|
||||
{ // 180
|
||||
{{2, 1}, {1, 5}, {2, 0}, {1, 6}, {2, 2}},
|
||||
{{0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}},
|
||||
{{1, 0}, {2, 8}, {1, 2}, {0, 8}, {1, 1}},
|
||||
{{2, 7}, {2, 6}, {2, 5}, {2, 4}, {2, 3}},
|
||||
{{0, 2}, {1, 4}, {0, 1}, {1, 3}, {0, 0}},
|
||||
},
|
||||
{ // 270
|
||||
{{2, 2}, {0, 7}, {1, 1}, {2, 3}, {0, 0}},
|
||||
{{1, 6}, {0, 6}, {0, 8}, {2, 4}, {1, 3}},
|
||||
{{2, 0}, {0, 5}, {1, 2}, {2, 5}, {0, 1}},
|
||||
{{1, 5}, {0, 4}, {2, 8}, {2, 6}, {1, 4}},
|
||||
{{2, 1}, {0, 3}, {1, 0}, {2, 7}, {0, 2}},
|
||||
},
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Rotation uint8
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
pin [12]machine.Pin
|
||||
buffer [3][9]bool
|
||||
rotation uint8
|
||||
}
|
||||
|
||||
// New returns a new microbitmatrix driver.
|
||||
func New() Device {
|
||||
return Device{}
|
||||
}
|
||||
|
||||
// Configure sets up the device.
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
d.SetRotation(cfg.Rotation)
|
||||
|
||||
for i := machine.LED_COL_1; i <= machine.LED_ROW_3; i++ {
|
||||
d.pin[i-machine.LED_COL_1] = i
|
||||
d.pin[i-machine.LED_COL_1].Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
}
|
||||
d.ClearDisplay()
|
||||
d.DisableAll()
|
||||
}
|
||||
|
||||
// SetRotation changes the rotation of the LED matrix
|
||||
func (d *Device) SetRotation(rotation uint8) {
|
||||
d.rotation = rotation % 4
|
||||
@@ -92,54 +41,3 @@ func (d *Device) GetPixel(x int16, y int16) bool {
|
||||
}
|
||||
return d.buffer[matrixRotations[d.rotation][x][y][0]][matrixRotations[d.rotation][x][y][1]]
|
||||
}
|
||||
|
||||
// Display sends the buffer (if any) to the screen.
|
||||
func (d *Device) Display() error {
|
||||
for row := 0; row < 3; row++ {
|
||||
d.DisableAll()
|
||||
d.pin[9+row].High()
|
||||
|
||||
for col := 0; col < 9; col++ {
|
||||
if d.buffer[row][col] {
|
||||
d.pin[col].Low()
|
||||
}
|
||||
|
||||
}
|
||||
time.Sleep(time.Millisecond * 2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearDisplay erases the internal buffer
|
||||
func (d *Device) ClearDisplay() {
|
||||
for row := 0; row < 3; row++ {
|
||||
for col := 0; col < 9; col++ {
|
||||
d.buffer[row][col] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DisableAll disables all the LEDs without modifying the buffer
|
||||
func (d *Device) DisableAll() {
|
||||
for i := machine.LED_COL_1; i <= machine.LED_COL_9; i++ {
|
||||
d.pin[i-machine.LED_COL_1].High()
|
||||
}
|
||||
for i := machine.LED_ROW_1; i <= machine.LED_ROW_3; i++ {
|
||||
d.pin[i-machine.LED_COL_1].Low()
|
||||
}
|
||||
}
|
||||
|
||||
// EnableAll enables all the LEDs without modifying the buffer
|
||||
func (d *Device) EnableAll() {
|
||||
for i := machine.LED_COL_1; i <= machine.LED_COL_9; i++ {
|
||||
d.pin[i-machine.LED_COL_1].Low()
|
||||
}
|
||||
for i := machine.LED_ROW_1; i <= machine.LED_ROW_3; i++ {
|
||||
d.pin[i-machine.LED_COL_1].High()
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
return 5, 5
|
||||
}
|
||||
|
||||
+6
-2
@@ -4,6 +4,7 @@ package tls
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
)
|
||||
@@ -17,14 +18,17 @@ func Dial(network, address string, config *Config) (*net.TCPSerialConn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := raddr.IP.String()
|
||||
hostname := strings.Split(address, ":")[0]
|
||||
sendport := strconv.Itoa(raddr.Port)
|
||||
if sendport == "0" {
|
||||
sendport = "443"
|
||||
}
|
||||
|
||||
// disconnect any old socket
|
||||
net.ActiveDevice.DisconnectSocket()
|
||||
|
||||
// connect new socket
|
||||
err = net.ActiveDevice.ConnectSSLSocket(addr, sendport)
|
||||
err = net.ActiveDevice.ConnectSSLSocket(hostname, sendport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package p1am
|
||||
|
||||
//go:generate go run ./internal/cmd/gen_defines
|
||||
|
||||
type ModuleProps struct {
|
||||
ModuleID uint32
|
||||
DI, DO, AI, AO, Status, Config, DataSize byte
|
||||
Name string
|
||||
}
|
||||
|
||||
var modules = []ModuleProps{
|
||||
//{0x000000ID,di,do,ai,ao,st,cf,ds}
|
||||
{0x00000000, 0, 0, 0, 0, 0, 0, 0, "Empty"}, //Empty first entry for defaultgs
|
||||
|
||||
{0x04A00081, 1, 0, 0, 0, 0, 0, 1, "P1-08ND3"}, //P1-08ND3
|
||||
|
||||
{0x04A00085, 1, 0, 0, 0, 0, 0, 1, "P1-08NA"}, //P1-08NA
|
||||
|
||||
{0x04A00087, 1, 0, 0, 0, 0, 0, 1, "P1-08SIM"}, //P1-08SIM
|
||||
|
||||
{0x04A00088, 1, 0, 0, 0, 0, 0, 1, "P1-08NE3"}, //P1-08NE3
|
||||
|
||||
{0x05200082, 2, 0, 0, 0, 0, 0, 1, "P1-16ND3"}, //P1-16ND3
|
||||
|
||||
{0x05200089, 2, 0, 0, 0, 0, 0, 1, "P1-16NE3"}, //P1-16NE3
|
||||
|
||||
{0x1403F481, 0, 0, 0, 32, 4, 4, 0xA0, "P1-04PWM"}, //P1-04PWM
|
||||
|
||||
{0x1404008D, 0, 1, 0, 0, 0, 0, 1, "P1-08TA"}, //P1-08TA
|
||||
|
||||
{0x1404008F, 0, 1, 0, 0, 0, 0, 1, "P1-08TRS"}, //P1-08TRS
|
||||
|
||||
{0x14040091, 0, 2, 0, 0, 0, 0, 1, "P1-16TR"}, //P1-16TR
|
||||
|
||||
{0x14050081, 0, 1, 0, 0, 0, 0, 1, "P1-08TD1"}, //P1-08TD1
|
||||
|
||||
{0x14050082, 0, 1, 0, 0, 0, 0, 1, "P1-08TD2"}, //P1-08TD2
|
||||
|
||||
{0x14080085, 0, 2, 0, 0, 0, 0, 1, "P1-15TD1"}, //P1-15TD1
|
||||
|
||||
{0x14080086, 0, 2, 0, 0, 0, 0, 1, "P1-15TD2"}, //P1-15TD2
|
||||
|
||||
{0x24A50081, 1, 1, 0, 0, 0, 0, 1, "P1-16CDR"}, //P1-16CDR
|
||||
|
||||
{0x24A50082, 1, 1, 0, 0, 0, 0, 1, "P1-15CDD1"}, //P1-15CDD1
|
||||
|
||||
{0x24A50083, 1, 1, 0, 0, 0, 0, 1, "P1-15CDD2"}, //P1-15CDD2
|
||||
|
||||
{0x34605581, 0, 0, 16, 0, 12, 18, 16, "P1-04AD"}, //P1-04AD
|
||||
|
||||
{0x34605588, 0, 0, 16, 0, 12, 8, 16, "P1-04RTD"}, //P1-04RTD
|
||||
|
||||
{0x3460558F, 0, 0, 16, 0, 12, 2, 12, "P1-04ADL-1"}, //P1-04ADL-1
|
||||
|
||||
{0x34605590, 0, 0, 16, 0, 12, 2, 12, "P1-04ADL-2"}, //P1-04ADL-2
|
||||
|
||||
{0x34608C81, 0, 0, 16, 0, 12, 20, 32, "P1-04THM"}, //P1-04THM
|
||||
|
||||
{0x34608C8E, 0, 0, 16, 0, 12, 8, 32, "P1-04NTC"}, //P1-04NTC
|
||||
|
||||
{0x34A0558A, 0, 0, 32, 0, 12, 2, 12, "P1-08ADL-1"}, //P1-08ADL-1
|
||||
|
||||
{0x34A0558B, 0, 0, 32, 0, 12, 2, 12, "P1-08ADL-2"}, //P1-08ADL-2
|
||||
|
||||
{0x34A5A481, 2, 0, 36, 36, 4, 12, 0xC0, "P1-02HSC"}, //P1-02HSC
|
||||
|
||||
{0x44035583, 0, 0, 0, 16, 4, 0, 12, "P1-04DAL-1"}, //P1-04DAL-1
|
||||
|
||||
{0x44035584, 0, 0, 0, 16, 4, 0, 12, "P1-04DAL-2"}, //P1-04DAL-2
|
||||
|
||||
{0x44055588, 0, 0, 0, 32, 4, 0, 12, "P1-08DAL-1"}, //P1-08DAL-1
|
||||
|
||||
{0x44055589, 0, 0, 0, 32, 4, 0, 12, "P1-08DAL-2"}, //P1-08DAL-2
|
||||
|
||||
{0x5461A783, 0, 0, 16, 8, 12, 2, 12, "P1-4ADL2DAL-1"}, //P1-4ADL2DAL-1
|
||||
|
||||
{0x5461A784, 0, 0, 16, 8, 12, 2, 12, "P1-4ADL2DAL-2"}, //P1-4ADL2DAL-2
|
||||
|
||||
{0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, "BAD SLOT"}, //empty in case no modules are defined.
|
||||
|
||||
{0x00000000, 0, 0, 0, 0, 0, 0, 0, "BAD SLOT"}, //empty in case no modules are defined.
|
||||
}
|
||||
|
||||
var defaultConfig = map[uint32][]byte{
|
||||
0x34605590:// P1_04ADL_2_DEFAULT_CONFIG
|
||||
{0x40, 0x03},
|
||||
0x34608C8E: // P1_04NTC_DEFAULT_CONFIG
|
||||
{0x40, 0x03, 0x60, 0x05,
|
||||
0x20, 0x00, 0x80, 0x02},
|
||||
0x34608C81: // P1_04THM_DEFAULT_CONFIG
|
||||
{0x40, 0x03, 0x60, 0x05,
|
||||
0x21, 0x00, 0x22, 0x00,
|
||||
0x23, 0x00, 0x24, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00},
|
||||
0x34605588: // P1_04RTD_DEFAULT_CONFIG
|
||||
{0x40, 0x03, 0x60, 0x05,
|
||||
0x20, 0x01, 0x80, 0x00},
|
||||
0x34605581: // P1_04AD_DEFAULT_CONFIG
|
||||
{0x40, 0x03, 0x00, 0x00,
|
||||
0x20, 0x03, 0x00, 0x00,
|
||||
0x21, 0x03, 0x00, 0x00,
|
||||
0x22, 0x03, 0x00, 0x00,
|
||||
0x23, 0x03},
|
||||
0x3460558F:// P1_04ADL_1_DEFAULT_CONFIG
|
||||
{0x40, 0x03},
|
||||
0x34A0558A:// P1_08ADL_1_DEFAULT_CONFIG
|
||||
{0x40, 0x07},
|
||||
0x34A0558B:// P1_08ADL_2_DEFAULT_CONFIG
|
||||
{0x40, 0x07},
|
||||
0x5461A783:// P1_04ADL2DAL_1_DEFAULT_CONFIG
|
||||
{0x40, 0x03},
|
||||
0x5461A784:// P1_04ADL2DAL_2_DEFAULT_CONFIG
|
||||
{0x40, 0x03},
|
||||
0x1403F481:// P1_04PWM_DEFAULT_CONFIG
|
||||
{0x02, 0x02, 0x02, 0x02},
|
||||
0x34A5A481: // P1_02HSC_DEFAULT_CONFIG
|
||||
{0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x01},
|
||||
}
|
||||
|
||||
const NUMBER_OF_MODULES = 15 //Current max 15 Modules
|
||||
const SWITCH_BUILTIN = 31
|
||||
const baseEnable = 33
|
||||
const MOD_HDR = 0x02
|
||||
const VERSION_HDR = 0x03
|
||||
const ACTIVE_HDR = 0x04
|
||||
const DROPOUT_HDR = 0x05
|
||||
const CFG_HDR = 0x10
|
||||
const READ_CFG_HDR = 0x11
|
||||
const PETWD_HDR = 0x30
|
||||
const STARTWD_HDR = 0x31
|
||||
const STOPWD_HDR = 0x32
|
||||
const CONFIGWD_HDR = 0x33
|
||||
const READ_STATUS_HDR = 0x40
|
||||
const READ_DISCRETE_HDR = 0x50
|
||||
const READ_ANALOG_HDR = 0x51
|
||||
const READ_BLOCK_HDR = 0x52
|
||||
const WRITE_DISCRETE_HDR = 0x60
|
||||
const WRITE_ANALOG_HDR = 0x61
|
||||
const WRITE_BLOCK_HDR = 0x62
|
||||
const FW_UPDATE_HDR = 0xAA
|
||||
const DUMMY = 0xFF
|
||||
const EMPTY_SLOT_ID = 0xFFFFFFFE
|
||||
const MAX_TIMEOUT = 0xFFFFFFFF
|
||||
const DISCRETE_IN_BLOCK = 0
|
||||
const ANALOG_IN_BLOCK = 1
|
||||
const DISCRETE_OUT_BLOCK = 2
|
||||
const ANALOG_OUT_BLOCK = 3
|
||||
const STATUS_IN_BLOCK = 4
|
||||
const MISSING24V_STATUS = 3
|
||||
const BURNOUT_STATUS = 5
|
||||
const UNDER_RANGE_STATUS = 7
|
||||
const OVER_RANGE_STATUS = 11
|
||||
const TOGGLE = 0x01
|
||||
const HOLD = 0x00
|
||||
@@ -0,0 +1,133 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var tmpl = template.Must(template.New("main").Parse(`package p1am
|
||||
|
||||
//go:generate go run ./internal/cmd/gen_defines
|
||||
|
||||
type ModuleProps struct {
|
||||
ModuleID uint32
|
||||
DI, DO, AI, AO, Status, Config, DataSize byte
|
||||
Name string
|
||||
}
|
||||
|
||||
var modules = []ModuleProps{
|
||||
{{.MDB -}}
|
||||
}
|
||||
|
||||
var defaultConfig = map[uint32][]byte{
|
||||
{{range .Configs -}}
|
||||
0x{{.ID}}: // {{.Name}}
|
||||
{{index $.DefaultConfigs .Name}},
|
||||
{{end}}
|
||||
}
|
||||
|
||||
{{range .Defines}}
|
||||
const {{.Name}} = {{.Value}}{{.Comment -}}
|
||||
{{end}}
|
||||
`))
|
||||
|
||||
func findLibrary() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, dir := range []string{
|
||||
"Documents/Arduino",
|
||||
"Arduino",
|
||||
} {
|
||||
dir = filepath.Join(home, dir, "libraries/P1AM/src")
|
||||
if _, err := os.Stat(dir); err == nil {
|
||||
return dir
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func definitions(path string, delim string) []string {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return strings.Split(string(data), delim)
|
||||
}
|
||||
|
||||
var (
|
||||
mdbRE = regexp.MustCompile(`(?s)mdb\[\] = \{\s*(.+)\}`)
|
||||
configRE = regexp.MustCompile(`(?s)const char (.*?)\[\] = (.+)`)
|
||||
caseRE = regexp.MustCompile(`(?s)case 0x([^:]+):\s+return \(char\*\)(.+)`)
|
||||
defineRE = regexp.MustCompile(`(?ms)^\s*#define (\S+)\s+(\d+|0x[0-9a-fA-F]+)(\s+.*?)?\s*$`)
|
||||
)
|
||||
|
||||
func main() {
|
||||
base := findLibrary()
|
||||
if base == "" {
|
||||
log.Fatal("can't find Arduino library")
|
||||
}
|
||||
var data = struct {
|
||||
MDB string
|
||||
DefaultConfigs map[string]string
|
||||
Configs []struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
Defines []struct {
|
||||
Name string
|
||||
Value string
|
||||
Comment string
|
||||
}
|
||||
}{
|
||||
DefaultConfigs: make(map[string]string),
|
||||
}
|
||||
for _, line := range definitions(filepath.Join(base, "Module_List.h"), ";") {
|
||||
if matches := mdbRE.FindStringSubmatch(line); matches != nil {
|
||||
data.MDB = regexp.MustCompile(`}\s*//`).ReplaceAllString(matches[1], `}, //`)
|
||||
}
|
||||
if matches := configRE.FindStringSubmatch(line); matches != nil {
|
||||
data.DefaultConfigs[matches[1]] = matches[2]
|
||||
}
|
||||
}
|
||||
|
||||
for _, line := range definitions(filepath.Join(base, "P1AM.cpp"), ";") {
|
||||
if matches := caseRE.FindStringSubmatch(line); matches != nil {
|
||||
data.Configs = append(data.Configs, struct{ ID, Name string }{
|
||||
ID: matches[1],
|
||||
Name: matches[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, line := range definitions(filepath.Join(base, "defines.h"), "\n") {
|
||||
if matches := defineRE.FindStringSubmatch(line); matches != nil {
|
||||
data.Defines = append(data.Defines, struct{ Name, Value, Comment string }{
|
||||
Name: matches[1],
|
||||
Value: matches[2],
|
||||
Comment: matches[3],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, &data); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
formatted, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Printf("failed to compile %s", buf.Bytes())
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile("defines.go", formatted, 0666); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
+429
@@ -0,0 +1,429 @@
|
||||
// Driver for the P1AM-100 base controller.
|
||||
//
|
||||
// This is an embedded device on the P1AM-100 board.
|
||||
// Based on v1.0.1 of the Arduino library: https://github.com/facts-engineering/P1AM/tree/1.0.1
|
||||
|
||||
package p1am
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
type P1AM struct {
|
||||
bus machine.SPI
|
||||
slaveSelectPin, slaveAckPin, baseEnablePin machine.Pin
|
||||
|
||||
// SkipAutoConfig will skip loading a default configuration into each module.
|
||||
SkipAutoConfig bool
|
||||
|
||||
Slots int
|
||||
// Access slots via Slot()
|
||||
slots []Slot
|
||||
}
|
||||
|
||||
var Controller = P1AM{
|
||||
bus: machine.SPI0,
|
||||
slaveSelectPin: machine.BASE_SLAVE_SELECT_PIN,
|
||||
slaveAckPin: machine.BASE_SLAVE_ACK_PIN,
|
||||
baseEnablePin: machine.BASE_ENABLE_PIN,
|
||||
}
|
||||
|
||||
type baseSlotConstants struct {
|
||||
DI, DO, AI, AO, Status, Config, DataSize byte
|
||||
}
|
||||
|
||||
func (p *P1AM) Initialize() error {
|
||||
p.slaveSelectPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
p.slaveAckPin.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
p.baseEnablePin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
if err := p.bus.Configure(machine.SPIConfig{
|
||||
Frequency: 1000000,
|
||||
Mode: 2,
|
||||
LSBFirst: false,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.SetEnabled(true)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if err := p.waitAck(5 * time.Second); err != nil {
|
||||
return errors.New("no base controller activity; check external supply connection")
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
if err := p.handleHDR(MOD_HDR); err == nil {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
slots, err := p.spiSendRecvByte(0xFF)
|
||||
if err == nil && slots > 0 && slots <= 15 {
|
||||
p.Slots = int(slots)
|
||||
break
|
||||
}
|
||||
}
|
||||
if i > 2 {
|
||||
// Try restarting the base controller
|
||||
p.SetEnabled(false)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
p.SetEnabled(true)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
if p.Slots <= 0 || p.Slots > 15 {
|
||||
return errors.New("zero modules in the base")
|
||||
}
|
||||
|
||||
moduleIDs := make([]uint32, p.Slots)
|
||||
|
||||
p.waitAck(200 * time.Millisecond)
|
||||
if err := binary.Read(p, binary.LittleEndian, &moduleIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseConstants := make([]baseSlotConstants, p.Slots)
|
||||
p.slots = make([]Slot, p.Slots)
|
||||
|
||||
for i := 1; i <= p.Slots; i++ {
|
||||
slot := p.Slot(i)
|
||||
slot.p = p
|
||||
slot.slot = byte(i)
|
||||
slot.ID = moduleIDs[i-1]
|
||||
// What if 0xFFFFFFFF isn't at position -2?
|
||||
slot.Props = &modules[len(modules)-2]
|
||||
for j := 0; j < len(modules); j++ {
|
||||
if modules[j].ModuleID == slot.ID {
|
||||
slot.Props = &modules[j]
|
||||
}
|
||||
bc := &baseConstants[i-1]
|
||||
bc.DI = slot.Props.DI
|
||||
bc.DO = slot.Props.DO
|
||||
bc.AI = slot.Props.AI
|
||||
bc.AO = slot.Props.AO
|
||||
bc.Status = slot.Props.Status
|
||||
bc.Config = slot.Props.Config
|
||||
bc.DataSize = slot.Props.DataSize
|
||||
}
|
||||
}
|
||||
|
||||
p.waitAck(200 * time.Millisecond)
|
||||
if err := binary.Write(p, binary.LittleEndian, &baseConstants); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !p.SkipAutoConfig {
|
||||
for i := 1; i <= p.Slots; i++ {
|
||||
s := p.Slot(i)
|
||||
if s.Props.Config > 0 {
|
||||
cfg := defaultConfig[s.ID]
|
||||
if cfg != nil {
|
||||
s.Configure(cfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *P1AM) Version() ([3]byte, error) {
|
||||
if err := p.handleHDR(VERSION_HDR); err != nil {
|
||||
return [3]byte{}, err
|
||||
}
|
||||
var buf [4]byte
|
||||
if err := p.spiSendRecvBuf(nil, buf[:]); err != nil {
|
||||
return [3]byte{}, err
|
||||
}
|
||||
return [3]byte{
|
||||
byte(buf[1] >> 4),
|
||||
byte(buf[1] & 0xF),
|
||||
byte(buf[0]),
|
||||
}, p.dataSync()
|
||||
}
|
||||
|
||||
func (p *P1AM) Active() (bool, error) {
|
||||
if _, err := p.spiSendRecvByte(ACTIVE_HDR); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := p.waitAck(200 * time.Millisecond); err != nil {
|
||||
return false, err
|
||||
}
|
||||
buf, err := p.spiSendRecvByte(DUMMY)
|
||||
defer p.dataSync()
|
||||
return buf != 0, err
|
||||
}
|
||||
|
||||
const wdToggleTime = 100 * time.Millisecond
|
||||
|
||||
func (p *P1AM) ConfigureWatchdog(interval time.Duration, reset bool) error {
|
||||
ms := interval / time.Millisecond
|
||||
toggleMs := wdToggleTime / time.Millisecond
|
||||
resetB := byte(0)
|
||||
if reset {
|
||||
resetB = 1
|
||||
}
|
||||
buf := [6]byte{
|
||||
CONFIGWD_HDR,
|
||||
byte(ms),
|
||||
byte(ms >> 8),
|
||||
byte(toggleMs),
|
||||
byte(toggleMs >> 8),
|
||||
resetB,
|
||||
}
|
||||
if err := p.spiSendRecvBuf(buf[:], nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.dataSync()
|
||||
}
|
||||
|
||||
func (p *P1AM) sendWatchdog(hdr byte) error {
|
||||
if _, err := p.spiSendRecvByte(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.waitAck(200 * time.Millisecond); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := p.spiSendRecvByte(DUMMY); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.dataSync()
|
||||
}
|
||||
|
||||
func (p *P1AM) StartWatchdog() error {
|
||||
return p.sendWatchdog(STARTWD_HDR)
|
||||
}
|
||||
|
||||
func (p *P1AM) StopWatchdog() error {
|
||||
return p.sendWatchdog(STOPWD_HDR)
|
||||
}
|
||||
|
||||
func (p *P1AM) PetWatchdog() error {
|
||||
return p.sendWatchdog(PETWD_HDR)
|
||||
}
|
||||
|
||||
func (p *P1AM) Slot(i int) *Slot {
|
||||
if i < 1 || i > p.Slots {
|
||||
return nil
|
||||
}
|
||||
return &p.slots[i-1]
|
||||
}
|
||||
|
||||
type Slot struct {
|
||||
p *P1AM
|
||||
slot byte
|
||||
ID uint32
|
||||
// TODO: Embed this?
|
||||
Props *ModuleProps
|
||||
}
|
||||
|
||||
func (s *Slot) Configure(data []byte) error {
|
||||
if s == nil {
|
||||
return errors.New("invalid slot")
|
||||
}
|
||||
if len(data) != int(s.Props.Config) {
|
||||
return fmt.Errorf("expected %d config bytes, got %d", s.Props.Config, len(data))
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return errors.New("no config bytes")
|
||||
}
|
||||
|
||||
out := make([]byte, len(data)+2)
|
||||
out[0] = CFG_HDR
|
||||
out[1] = s.slot
|
||||
copy(out[2:], data)
|
||||
|
||||
if err := s.p.spiSendRecvBuf(out, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
s.p.dataSync()
|
||||
s.p.dataSync()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Slot) ReadDiscrete() (uint32, error) {
|
||||
if s == nil {
|
||||
return 0, errors.New("invalid slot")
|
||||
}
|
||||
bytes := s.Props.DI
|
||||
out := [2]byte{
|
||||
READ_DISCRETE_HDR,
|
||||
s.slot,
|
||||
}
|
||||
if err := s.p.spiSendRecvBuf(out[:], nil); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := s.p.waitAck(200 * time.Millisecond); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var data [4]byte
|
||||
if err := s.p.spiSendRecvBuf(nil, data[:bytes]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err := s.p.dataSync()
|
||||
return binary.LittleEndian.Uint32(data[:]), err
|
||||
}
|
||||
|
||||
func (s *Slot) WriteDiscrete(value uint32) error {
|
||||
return s.writeDiscrete(0, value)
|
||||
}
|
||||
|
||||
func (s *Slot) writeDiscrete(channel byte, value uint32) error {
|
||||
if s == nil {
|
||||
return errors.New("invalid slot")
|
||||
}
|
||||
bytes := s.Props.DO
|
||||
buf := [7]byte{
|
||||
WRITE_DISCRETE_HDR,
|
||||
s.slot,
|
||||
channel,
|
||||
}
|
||||
binary.LittleEndian.PutUint32(buf[3:], value)
|
||||
out := buf[:3+bytes]
|
||||
if channel != 0 {
|
||||
out = buf[:4]
|
||||
out[3] &= 1
|
||||
}
|
||||
if err := s.p.spiSendRecvBuf(out, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.p.dataSync()
|
||||
}
|
||||
|
||||
type Channel struct {
|
||||
s *Slot
|
||||
channel int
|
||||
}
|
||||
|
||||
func (s *Slot) Channel(channel int) Channel {
|
||||
return Channel{
|
||||
s: s,
|
||||
channel: channel,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Channel) ReadDiscrete() (bool, error) {
|
||||
if c.channel < 1 || c.channel > int(c.s.Props.DI)*8 {
|
||||
return false, errors.New("invalid channel")
|
||||
}
|
||||
data, err := c.s.ReadDiscrete()
|
||||
return (data>>(c.channel-1))&1 == 1, err
|
||||
}
|
||||
|
||||
func (c Channel) WriteDiscrete(value bool) error {
|
||||
if c.channel < 1 || c.channel > int(c.s.Props.DO)*8 {
|
||||
return errors.New("invalid channel")
|
||||
}
|
||||
data := uint32(0)
|
||||
if value {
|
||||
data = 1
|
||||
}
|
||||
return c.s.writeDiscrete(byte(c.channel), data)
|
||||
}
|
||||
|
||||
const ackTimeout = 200 * time.Millisecond
|
||||
|
||||
func awaitPin(pin machine.Pin, state bool, timeout time.Duration) bool {
|
||||
start := time.Now()
|
||||
for pin.Get() != state {
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
if time.Since(start) > timeout {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
// TODO: Use channels when https://github.com/tinygo-org/tinygo/pull/1402 is merged.
|
||||
// edge := machine.PinRising
|
||||
// if state {
|
||||
// edge = machine.PinFalling
|
||||
// }
|
||||
// ch := make(chan struct{}, 1)
|
||||
// defer close(ch)
|
||||
// pin.SetInterrupt(edge, func(machine.Pin) {
|
||||
// ch <- struct{}{}
|
||||
// })
|
||||
// defer pin.SetInterrupt(0, nil)
|
||||
// select {
|
||||
// case <-ch:
|
||||
// return true
|
||||
// case <-time.After(timeout):
|
||||
// return false
|
||||
// }
|
||||
}
|
||||
|
||||
var dataSyncErr = errors.New("base sync timeout")
|
||||
|
||||
func (p *P1AM) dataSync() error {
|
||||
if !awaitPin(p.slaveAckPin, true, ackTimeout) {
|
||||
return dataSyncErr
|
||||
}
|
||||
time.Sleep(time.Microsecond)
|
||||
if !awaitPin(p.slaveAckPin, false, ackTimeout) {
|
||||
return dataSyncErr
|
||||
}
|
||||
time.Sleep(time.Microsecond)
|
||||
if !awaitPin(p.slaveAckPin, true, ackTimeout) {
|
||||
return dataSyncErr
|
||||
}
|
||||
time.Sleep(time.Microsecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *P1AM) handleHDR(HDR byte) error {
|
||||
for !p.slaveAckPin.Get() {
|
||||
}
|
||||
if _, err := p.spiSendRecvByte(HDR); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.spiTimeout(MAX_TIMEOUT*time.Millisecond, HDR, 2*time.Second)
|
||||
}
|
||||
|
||||
func (p *P1AM) Read(data []byte) (int, error) {
|
||||
return len(data), p.spiSendRecvBuf(nil, data)
|
||||
}
|
||||
|
||||
func (p *P1AM) Write(data []byte) (int, error) {
|
||||
return len(data), p.spiSendRecvBuf(data, nil)
|
||||
}
|
||||
|
||||
func (p *P1AM) spiSendRecvBuf(w, r []byte) error {
|
||||
p.slaveSelectPin.Low()
|
||||
defer p.slaveSelectPin.High()
|
||||
return p.bus.Tx(w, r)
|
||||
}
|
||||
|
||||
func (p *P1AM) spiSendRecvByte(data byte) (byte, error) {
|
||||
p.slaveSelectPin.Low()
|
||||
defer p.slaveSelectPin.High()
|
||||
return p.bus.Transfer(data)
|
||||
}
|
||||
|
||||
func (p *P1AM) waitAck(timeout time.Duration) error {
|
||||
return p.spiTimeout(timeout, 0, 0)
|
||||
}
|
||||
|
||||
var timeoutErr = errors.New("timeout")
|
||||
|
||||
func (p *P1AM) spiTimeout(timeout time.Duration, resendMsg byte, retryPeriod time.Duration) error {
|
||||
end := time.Now().Add(timeout)
|
||||
retry := time.Now().Add(retryPeriod)
|
||||
for time.Now().Before(end) {
|
||||
if p.slaveAckPin.Get() {
|
||||
time.Sleep(50 * time.Microsecond)
|
||||
return nil
|
||||
}
|
||||
if retryPeriod > 0 && time.Now().After(retry) {
|
||||
p.spiSendRecvByte(resendMsg)
|
||||
retry = retry.Add(retryPeriod)
|
||||
}
|
||||
}
|
||||
return timeoutErr
|
||||
}
|
||||
|
||||
func (p *P1AM) SetEnabled(enabled bool) {
|
||||
p.baseEnablePin.Set(enabled)
|
||||
}
|
||||
+4
-2
@@ -9,11 +9,13 @@ import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
bus drivers.SPI
|
||||
dcPin machine.Pin
|
||||
rstPin machine.Pin
|
||||
scePin machine.Pin
|
||||
@@ -29,7 +31,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new PCD8544 connection. The SPI bus must already be configured.
|
||||
func New(bus machine.SPI, dcPin, rstPin, scePin machine.Pin) *Device {
|
||||
func New(bus drivers.SPI, dcPin, rstPin, scePin machine.Pin) *Device {
|
||||
return &Device{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
// Package pcf8563 implements a driver for the PCF8563 CMOS Real-Time Clock (RTC)
|
||||
//
|
||||
// Datasheet: https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf
|
||||
//
|
||||
|
||||
package pcf8563
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a PCF8563 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
// New creates a new PCF8563 connection. I2C bus must be already configured.
|
||||
func New(i2c drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: i2c,
|
||||
Address: PCF8563_ADDR,
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the `control and status registers`. When this method is
|
||||
// called, it writes `0x00` to the `control and status registers`. This will
|
||||
// cause `Alarm` and `Timer` to become Inactive. Please refer to the datasheet
|
||||
// for details.
|
||||
func (d *Device) Reset() (err error) {
|
||||
return d.bus.Tx(d.Address, []byte{0x00, 0x00, 0x00}, nil)
|
||||
}
|
||||
|
||||
// SetTime sets the time and date
|
||||
func (d *Device) SetTime(t time.Time) error {
|
||||
var buf [9]byte
|
||||
buf[0] = 0x02
|
||||
buf[1] = decToBcd(t.Second())
|
||||
buf[2] = decToBcd(t.Minute())
|
||||
buf[3] = decToBcd(t.Hour())
|
||||
buf[4] = decToBcd(t.Day())
|
||||
buf[5] = decToBcd(int(t.Weekday() + 1))
|
||||
buf[6] = decToBcd(int(t.Month()))
|
||||
buf[7] = decToBcd(t.Year() - 2000)
|
||||
err := d.bus.Tx(d.Address, buf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadTime returns the date and time
|
||||
func (d *Device) ReadTime() (time.Time, error) {
|
||||
var buf [9]byte
|
||||
err := d.bus.Tx(d.Address, []byte{0x00}, buf[:])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
seconds := bcdToDec(buf[2] & 0x7F)
|
||||
minute := bcdToDec(buf[3] % 0x7F)
|
||||
hour := bcdToDec(buf[4] & 0x3F)
|
||||
day := bcdToDec(buf[5] & 0x3F)
|
||||
month := time.Month(bcdToDec(buf[7] & 0x0F))
|
||||
year := int(bcdToDec(buf[8])) + 2000
|
||||
|
||||
t := time.Date(year, month, day, hour, minute, seconds, 0, time.UTC)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// SetAlarm sets the alarm
|
||||
func (d *Device) SetAlarm(t time.Time) error {
|
||||
var buf [5]byte
|
||||
buf[0] = 0x09
|
||||
buf[1] = RTC_ALARM_ENABLE | decToBcd(t.Minute())
|
||||
buf[2] = RTC_ALARM_ENABLE | decToBcd(t.Hour())
|
||||
buf[3] = RTC_ALARM_ENABLE | decToBcd(t.Day())
|
||||
buf[4] = RTC_ALARM_DISABLE
|
||||
err := d.bus.Tx(d.Address, buf[:], nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// enable alarm
|
||||
buf[0] = 0x01
|
||||
err = d.bus.Tx(d.Address, buf[:1], buf[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf[1] |= RTC_CTRL_AF
|
||||
err = d.bus.Tx(d.Address, buf[:2], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// ClearAlarm disables alarm.
|
||||
func (d *Device) ClearAlarm() error {
|
||||
var buf [2]byte
|
||||
buf[0] = 0x01
|
||||
err := d.bus.Tx(d.Address, buf[:1], buf[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf[1] &= ^uint8(RTC_CTRL_AF)
|
||||
err = d.bus.Tx(d.Address, buf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// EnableAlarmInterrupt enables alarm interrupt. When triggered, INT pin (3)
|
||||
// goes low.
|
||||
func (d *Device) EnableAlarmInterrupt() error {
|
||||
var buf [2]byte
|
||||
buf[0] = 0x01
|
||||
err := d.bus.Tx(d.Address, buf[:1], buf[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf[1] |= RTC_CTRL_AIE
|
||||
err = d.bus.Tx(d.Address, buf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// DisableAlarmInterrupt disable alarm interrupt.
|
||||
func (d *Device) DisableAlarmInterrupt() error {
|
||||
var buf [2]byte
|
||||
buf[0] = 0x01
|
||||
err := d.bus.Tx(d.Address, buf[:1], buf[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf[1] &= ^uint8(RTC_CTRL_AIE)
|
||||
err = d.bus.Tx(d.Address, buf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// AlarmTriggered returns whether or not an Alarm has been triggered.
|
||||
func (d *Device) AlarmTriggered() bool {
|
||||
var buf [1]byte
|
||||
buf[0] = 0x01
|
||||
err := d.bus.Tx(d.Address, buf[:], buf[:])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (buf[0] & RTC_CTRL_AF) != 0
|
||||
}
|
||||
|
||||
// SetTimer sets timer. The available durations are 1 to 127 seconds. If any
|
||||
// other value is specified, it will be truncated.
|
||||
func (d *Device) SetTimer(dur time.Duration) error {
|
||||
var buf [3]byte
|
||||
|
||||
sec := dur / time.Second
|
||||
if sec > 127 {
|
||||
sec = 127
|
||||
}
|
||||
|
||||
// Treat as sec timer.
|
||||
buf[0] = 0x0E
|
||||
buf[1] = RTC_TIMER_1S
|
||||
buf[2] = byte(sec)
|
||||
err := d.bus.Tx(d.Address, buf[:], nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// enable alarm
|
||||
buf[0] = 0x01
|
||||
err = d.bus.Tx(d.Address, buf[:1], buf[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf[1] |= RTC_CTRL_TF
|
||||
err = d.bus.Tx(d.Address, buf[:2], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// ClearTimer disables timer.
|
||||
func (d *Device) ClearTimer() error {
|
||||
var buf [2]byte
|
||||
buf[0] = 0x01
|
||||
err := d.bus.Tx(d.Address, buf[:1], buf[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf[1] &= ^uint8(RTC_CTRL_TF)
|
||||
err = d.bus.Tx(d.Address, buf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// EnableTimerInterrupt enables timer interrupt. When triggered, INT pin (3)
|
||||
// goes low.
|
||||
func (d *Device) EnableTimerInterrupt() error {
|
||||
var buf [2]byte
|
||||
buf[0] = 0x01
|
||||
err := d.bus.Tx(d.Address, buf[:1], buf[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf[1] |= RTC_CTRL_TIE
|
||||
err = d.bus.Tx(d.Address, buf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// DisableTimerInterrupt disable timer interrupt.
|
||||
func (d *Device) DisableTimerInterrupt() error {
|
||||
var buf [2]byte
|
||||
buf[0] = 0x01
|
||||
err := d.bus.Tx(d.Address, buf[:1], buf[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf[1] &= ^uint8(RTC_CTRL_TIE)
|
||||
err = d.bus.Tx(d.Address, buf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// TimerTriggered returns whether or not an Alarm has been triggered.
|
||||
func (d *Device) TimerTriggered() bool {
|
||||
var buf [1]byte
|
||||
buf[0] = 0x01
|
||||
err := d.bus.Tx(d.Address, buf[:], buf[:])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (buf[0] & RTC_CTRL_TF) != 0
|
||||
}
|
||||
|
||||
// SetOscillatorFrequency sets output oscillator frequency
|
||||
// Available modes: RTC_COT_DISABLE, RTC_COT_32KHZ, RTC_COT_1KHZ,
|
||||
// RTC_COT_32Hz, RTC_COT_1HZ.
|
||||
func (d *Device) SetOscillatorFrequency(sqw uint8) error {
|
||||
var buf [2]byte
|
||||
buf[0] = 0x0D
|
||||
buf[1] = sqw
|
||||
return d.bus.Tx(d.Address, buf[:], nil)
|
||||
}
|
||||
|
||||
// decToBcd converts int to BCD
|
||||
func decToBcd(dec int) uint8 {
|
||||
return uint8(dec + 6*(dec/10))
|
||||
}
|
||||
|
||||
// bcdToDec converts BCD to int
|
||||
func bcdToDec(bcd uint8) int {
|
||||
return int(bcd - 6*(bcd>>4))
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package pcf8563
|
||||
|
||||
// Registers
|
||||
const (
|
||||
PCF8563_ADDR = 0x51 // R:A3 W:A2
|
||||
|
||||
I2C_SPEED_STANDARD = 100000
|
||||
I2C_SPEED_DOUBLE = 200000
|
||||
|
||||
RTC_CTRL_STOP = 0x20
|
||||
RTC_CTRL_TITP = 0x10
|
||||
RTC_CTRL_AF = 0x08
|
||||
RTC_CTRL_TF = 0x04
|
||||
RTC_CTRL_AIE = 0x02
|
||||
RTC_CTRL_TIE = 0x01
|
||||
|
||||
RTC_COT_DISABLE = 0x00
|
||||
RTC_COT_32KHZ = 0x80
|
||||
RTC_COT_1KHZ = 0x81
|
||||
RTC_COT_32HZ = 0x82
|
||||
RTC_COT_1HZ = 0x83
|
||||
|
||||
RTC_TIMER_DISABLE = 0x00
|
||||
RTC_TIMER_4KHZ = 0x80
|
||||
RTC_TIMER_64HZ = 0x81
|
||||
RTC_TIMER_1S = 0x82
|
||||
RTC_TIMER_60S = 0x83
|
||||
|
||||
RTC_ALARM_DISABLE = 0x80
|
||||
RTC_ALARM_ENABLE = 0x00
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
package servo
|
||||
|
||||
import "machine"
|
||||
|
||||
// PWM is the interface necessary for controlling typical servo motors.
|
||||
type PWM interface {
|
||||
Configure(config machine.PWMConfig) error
|
||||
Channel(pin machine.Pin) (channel uint8, err error)
|
||||
Top() uint32
|
||||
Set(channel uint8, value uint32)
|
||||
}
|
||||
|
||||
// Array is an array of servos controlled by a single PWM peripheral. On most
|
||||
// chips, one PWM peripheral can control multiple servos (usually two or four).
|
||||
type Array struct {
|
||||
pwm PWM
|
||||
}
|
||||
|
||||
// Servo is a single servo (connected to one PWM output) that's part of a servo
|
||||
// array.
|
||||
type Servo struct {
|
||||
pwm PWM
|
||||
channel uint8
|
||||
}
|
||||
|
||||
const pwmPeriod = 20e6 // 20ms
|
||||
|
||||
// NewArray returns a new servo array based on the given PWM, for if you want to
|
||||
// control multiple servos from a single PWM peripheral. Using a single PWM for
|
||||
// multiple servos saves PWM peripherals for other uses and might use less power
|
||||
// depending on the chip.
|
||||
//
|
||||
// If you only want to control a single servo, you could use the New shorthand
|
||||
// instead.
|
||||
func NewArray(pwm PWM) (Array, error) {
|
||||
err := pwm.Configure(machine.PWMConfig{
|
||||
Period: pwmPeriod,
|
||||
})
|
||||
if err != nil {
|
||||
return Array{}, err
|
||||
}
|
||||
return Array{pwm}, nil
|
||||
}
|
||||
|
||||
// Add adds a new servo to the servo array. Please check the chip documentation
|
||||
// which pins can be controlled by the given PWM: depending on the chip this
|
||||
// might be rigid (only a single pin) or very flexible (you can pick any pin).
|
||||
func (array Array) Add(pin machine.Pin) (Servo, error) {
|
||||
channel, err := array.pwm.Channel(pin)
|
||||
if err != nil {
|
||||
return Servo{}, err
|
||||
}
|
||||
return Servo{
|
||||
pwm: array.pwm,
|
||||
channel: channel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// New is a shorthand for NewArray and array.Add. This is useful if you only
|
||||
// want to control just a single servo.
|
||||
func New(pwm PWM, pin machine.Pin) (Servo, error) {
|
||||
array, err := NewArray(pwm)
|
||||
if err != nil {
|
||||
return Servo{}, err
|
||||
}
|
||||
return array.Add(pin)
|
||||
}
|
||||
|
||||
// SetMicroseconds sets the output signal to be high for the given number of
|
||||
// microseconds. For many servos the range is normally between 1000µs and 2000µs
|
||||
// for 90° of rotation (with 1500µs being the 'neutral' middle position).
|
||||
//
|
||||
// In many cases they can actually go a bit further, with a wider range of
|
||||
// supported pulse ranges. For example, they might allow pulse widths from 500µs
|
||||
// to 2500µs, but be warned that going outside of the 1000µs-2000µs range might
|
||||
// break the servo as it might destroy the gears if it doesn't support this
|
||||
// range. Therefore, to be sure check the datasheet before you try values
|
||||
// outside of the 1000µs-2000µs range.
|
||||
func (s Servo) SetMicroseconds(microseconds int16) {
|
||||
value := uint64(s.pwm.Top()) * uint64(microseconds) / (pwmPeriod / 1000)
|
||||
s.pwm.Set(s.channel, uint32(value))
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package drivers
|
||||
|
||||
// SPI represents a SPI bus. It is implemented by the machine.SPI type.
|
||||
type SPI interface {
|
||||
// Tx transmits the given buffer w and receives at the same time the buffer r.
|
||||
// The two buffers must be the same length. The only exception is when w or r are nil,
|
||||
// in which case Tx only transmits (without receiving) or only receives (while sending 0 bytes).
|
||||
Tx(w, r []byte) error
|
||||
|
||||
// Transfer writes a single byte out on the SPI bus and receives a byte at the same time.
|
||||
// If you want to transfer multiple bytes, it is more efficient to use Tx instead.
|
||||
Transfer(b byte) (byte, error)
|
||||
}
|
||||
+9
-5
@@ -13,7 +13,7 @@ import (
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
// Device wraps I2C or SPI connection.
|
||||
type Device struct {
|
||||
bus Buser
|
||||
buffer []byte
|
||||
@@ -21,6 +21,7 @@ type Device struct {
|
||||
height int16
|
||||
bufferSize int16
|
||||
vccState VccMode
|
||||
canReset bool
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
@@ -37,7 +38,7 @@ type I2CBus struct {
|
||||
}
|
||||
|
||||
type SPIBus struct {
|
||||
wire machine.SPI
|
||||
wire drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
@@ -62,7 +63,7 @@ func NewI2C(bus drivers.I2C) Device {
|
||||
}
|
||||
|
||||
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
|
||||
func NewSPI(bus machine.SPI, dcPin, resetPin, csPin machine.Pin) Device {
|
||||
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
@@ -98,6 +99,7 @@ func (d *Device) Configure(cfg Config) {
|
||||
}
|
||||
d.bufferSize = d.width * d.height / 8
|
||||
d.buffer = make([]byte, d.bufferSize)
|
||||
d.canReset = cfg.Address != 0 || d.width != 128 || d.height != 64 // I2C or not 128x64
|
||||
|
||||
d.bus.configure()
|
||||
|
||||
@@ -178,9 +180,11 @@ func (d *Device) ClearDisplay() {
|
||||
|
||||
// Display sends the whole buffer to the screen
|
||||
func (d *Device) Display() error {
|
||||
// Reset the screen to 0x0
|
||||
// This works fine with I2C
|
||||
// In the 128x64 (SPI) screen resetting to 0x0 after 128 times corrupt the buffer
|
||||
// Since we're printing the whole buffer, avoid resetting it
|
||||
if d.width != 128 || d.height != 64 {
|
||||
// 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))
|
||||
|
||||
+4
-2
@@ -10,6 +10,8 @@ import (
|
||||
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type Model uint8
|
||||
@@ -17,7 +19,7 @@ type Rotation uint8
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
bus drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
@@ -35,7 +37,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new SSD1331 connection. The SPI wire must already be configured.
|
||||
func New(bus machine.SPI, resetPin, dcPin, csPin machine.Pin) Device {
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
+4
-2
@@ -9,6 +9,8 @@ import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -18,7 +20,7 @@ var (
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
bus drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
@@ -40,7 +42,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new SSD1351 connection. The SPI wire must already be configured.
|
||||
func New(bus machine.SPI, resetPin, dcPin, csPin, enPin, rwPin machine.Pin) Device {
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, enPin, rwPin machine.Pin) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
|
||||
+4
-2
@@ -10,6 +10,8 @@ import (
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
type Model uint8
|
||||
@@ -17,7 +19,7 @@ type Rotation uint8
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
bus drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
@@ -44,7 +46,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new ST7735 connection. The SPI wire must already be configured.
|
||||
func New(bus machine.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
@@ -50,6 +50,8 @@ const (
|
||||
GMCTRP1 = 0xE0
|
||||
GMCTRN1 = 0xE1
|
||||
GSCAN = 0x45
|
||||
VSCRDEF = 0x33
|
||||
VSCRSADD = 0x37
|
||||
|
||||
NO_ROTATION Rotation = 0
|
||||
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||
|
||||
+34
-10
@@ -12,15 +12,19 @@ import (
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Rotation controls the rotation used by the display.
|
||||
type Rotation uint8
|
||||
|
||||
// FrameRate controls the frame rate used by the display.
|
||||
type FrameRate uint8
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
bus drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
@@ -50,7 +54,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new ST7789 connection. The SPI wire must already be configured.
|
||||
func New(bus machine.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
||||
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
@@ -276,8 +280,7 @@ func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// FillRectangle fills a rectangle at a given coordinates with a buffer
|
||||
// FillRectangleWithBuffer fills buffer with a rectangle at a given coordinates.
|
||||
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
|
||||
i, j := d.Size()
|
||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||
@@ -369,12 +372,12 @@ func (d *Device) SetRotation(rotation Rotation) {
|
||||
d.Data(madctl)
|
||||
}
|
||||
|
||||
// Command sends a command to the display
|
||||
// Command sends a command to the display.
|
||||
func (d *Device) Command(command uint8) {
|
||||
d.Tx([]byte{command}, true)
|
||||
}
|
||||
|
||||
// Command sends a data to the display
|
||||
// Data sends data to the display.
|
||||
func (d *Device) Data(data uint8) {
|
||||
d.Tx([]byte{data}, false)
|
||||
}
|
||||
@@ -392,13 +395,13 @@ func (d *Device) Tx(data []byte, isCommand bool) {
|
||||
}
|
||||
|
||||
// Rx reads data from the display
|
||||
func (d *Device) Rx(command uint8, read_bytes []byte) {
|
||||
func (d *Device) Rx(command uint8, data []byte) {
|
||||
d.dcPin.Low()
|
||||
d.csPin.Low()
|
||||
d.bus.Transfer(command)
|
||||
d.dcPin.High()
|
||||
for i := range read_bytes {
|
||||
read_bytes[i], _ = d.bus.Transfer(0xFF)
|
||||
for i := range data {
|
||||
data[i], _ = d.bus.Transfer(0xFF)
|
||||
}
|
||||
d.csPin.High()
|
||||
}
|
||||
@@ -420,7 +423,7 @@ func (d *Device) EnableBacklight(enable bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// InverColors inverts the colors of the screen
|
||||
// InvertColors inverts the colors of the screen
|
||||
func (d *Device) InvertColors(invert bool) {
|
||||
if invert {
|
||||
d.Command(INVON)
|
||||
@@ -434,6 +437,27 @@ func (d *Device) IsBGR(bgr bool) {
|
||||
d.isBGR = bgr
|
||||
}
|
||||
|
||||
// SetScrollArea sets an area to scroll with fixed top and bottom parts of the display.
|
||||
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
|
||||
d.Command(VSCRDEF)
|
||||
d.Tx([]uint8{
|
||||
uint8(topFixedArea >> 8), uint8(topFixedArea),
|
||||
uint8(d.height - topFixedArea - bottomFixedArea>>8), uint8(d.height - topFixedArea - bottomFixedArea),
|
||||
uint8(bottomFixedArea >> 8), uint8(bottomFixedArea)},
|
||||
false)
|
||||
}
|
||||
|
||||
// SetScroll sets the vertical scroll address of the display.
|
||||
func (d *Device) SetScroll(line int16) {
|
||||
d.Command(VSCRSADD)
|
||||
d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false)
|
||||
}
|
||||
|
||||
// StopScroll returns the display to its normal state.
|
||||
func (d *Device) StopScroll() {
|
||||
d.Command(NORON)
|
||||
}
|
||||
|
||||
// RGBATo565 converts a color.RGBA to uint16 used in the display
|
||||
func RGBATo565(c color.RGBA) uint16 {
|
||||
r, g, b, _ := c.RGBA()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user