Compare commits

..

1 Commits

Author SHA1 Message Date
deadprogram 68d4991da1 ateccx08: inttial implementation for ATECCx08
This working implementation for the ATECCx08 family of cryptgraphic processors
has random number generation and other needed supporting functions.

It also includes a sample of how to connect it to the Go crypto/rand package.

More cryptographic functions await a future interation.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-20 20:44:51 +01:00
456 changed files with 19271 additions and 35232 deletions
-3
View File
@@ -1,3 +0,0 @@
# These are supported funding model platforms
open_collective: tinygo
+3 -7
View File
@@ -11,12 +11,10 @@ on:
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/tinygo-org/tinygo:latest
options: --user root
container: tinygo/tinygo-dev
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v2
- name: TinyGo version check
run: tinygo version
- name: Enforce Go Formatted Code
@@ -24,6 +22,4 @@ jobs:
- name: Run unit tests
run: make unit-test
- name: Run build and smoke tests
run: |
go env -w GOFLAGS=-buildvcs=false
make smoke-test
run: make smoke-test
-526
View File
@@ -1,529 +1,3 @@
0.35.0
---
- **new devices**
- **unoqmatrix**
- LED matrix on the Arduino Uno Q
- **waveshare-epd (ssd1680)**
- Add driver for Waveshare 2.9 inch v2 e-paper display
- **enhancements**
- **gps**
- add UBX config command support (#831)
- improve implementation for UBX config commands
- revamp validSentence() to avoid heap allocation for errors
- export some errors for checking/suppression from client
- improvements and corrections for config commands
- **lora**
- fill out more constants for lora device
- **mcp2515**
- add support for extended CAN IDs (#857)
- **si5351**
- complete refactor for more complete interface
- **st7735**
- remove dependency on the machine package
- **sx127x**
- add functions used for FSK radio communication
- **ws2812**
- add brightness control
- add PIO support for RP2040/RP2350
- **bugfixes**
- **st7789**
- fix scroll on rotated displays
- fix driver when rotated 90º
- **ws2812**
- fix brightness control issues (#858)
0.34.0
---
- **core**
- add regmap package to facilitate heapless driver development
- PinInput+PinOutput HAL (#753, reloaded) (#795)
- Add Device8I2C/SPI types and their logic (#801)
- **new devices**
- **bno8x**
- Add support for CEVA BNO08x 9DoF sensor (#809)
- **hineyhsc**
- Add Honeywell HSC TruStability SPI+I2C pressure sensor driver (#799)
- **p25q16h**
- added support for P25Q16H flash chip for xiao-ble target
- **si5351**
- add support for si5351 (#810)
- **w25q80dv**
- added support for W25Q80DV flash chip for xiao-ble target
- **w5500**
- initial version the driver (#788)
- **enhancements**
- **ds3231**
- DS3231 Alarm features (#805)
- **general**
- add simplest driver ports
- **lis3dh**
- add Update and Acceleration calls
- use correct error handling and make configurable
- **lsm9ds1**
- avoid unnecessary heap allocations
- **pixel**
- add Grayscale2bit color (#817)
- **scd4x**
- add support for SCD41 single-shot measurements
- remove dead code
- update package to use standard methods
- **si5351**
- add many missing functions needed for convenient use.
- **ssd1xxx**
- break dependency from machine package (#812)
- **test**
- Add TestImageRGB888 and TestImageRGB555
- **bugfixes**
- **quadrature**
- add RP2350 to quadrature_interrupt.go
- **pixel**
- correct logic error in image size checks in pixel's tests
- correct logic error in image size checks in pixel's tests (Monochrome)
- correct RGB555 to RGBA conversion logic
0.33.0
---
- **new devices**
- **ens160**
- Add ens160 i2c driver
- **lsm303dlhc**
- added support for LSM303DLHC e-Compass; (#783)
- **seesaw**
- add support for Adafruit Seesaw encoders
- **enhancements**
- **ws2812**
- add RP2350 support
- **ssd1306**
- avoid unnecessary heap allocations (#767)
- **gps**
- allow gps init with address
- **lsm6ds3tr**
- avoid unnecessary heap allocations (#766)
- **bugfixes**
- **gps**
- Fix gps time calculation (#785)
0.32.0
---
- **enhancements**
- **bmp280**
- remove alloc on read sensor data
- **ws2812**
- add 200MHz support for the Cortex-M0/rp2040
- **bugfixes**
- **ssd1306**
- remove time.Sleep from SSD1306 SPI transfer code
- **tmc2209**
- tmc2209 bug fixes (#755)
- **docs**
- **contributing**
- add driver design pointer to CONTRIBUTING.md
0.31.0
---
---
- **enhancements**
- **spi**
- update all SPI usage to use either *machine.SPI or drivers.SPI
0.30.0
---
- **new devices**
- **comboat**
- Add wifi driver comboat for Elecrow W5 rp2040 and rp2350 devices (#741)
- **max6675**
- Add MAX6675 device
- **TMC2209**
- Added TMC2209 support (#727)
- **TMC5160**
- Added TMC5160 support (#725)
- **sharpmem**
- Add sharpmem (#724)
- **enhancements**
- **net**
- move to latest golang.org/x/net v0.33.0 (#732)
- **microphone**
- update microphone driver to use latest i2s interface
- **bugfixes**
- **net**
- fix typo in DHCP error message
- **aht20**
- Fixed bug in aht20 driver
- **hub75**
- fix data buffering
0.29.0
---
- **new devices**
- **epd1in54**
- Waveshare 1.54inch B/W e-Paper display (#704)
- **touch**
- add capacitive touch sensing on normal GPIO pins
- **INA219**
- I2C INA219 driver (#705)
- **pcf8591**
- add ADC only implementation for I2C ADC/DAC (#690)
- **enhancements**
- **pixel**
- add NewImageFromBytes() function to allow creating image from existing slice
- **servo**
- Add function `SetAngleWithMicroseconds` (#695)
- **onewire**
- onewire improvements
- **ssd1306**
- Add function `SetFlip` and `GetFlip` (#702)
- **uc8151**
- add FillRectangle() and SetScroll() functions to satisfy tinyterm.Displayer interface
- **ssd1306**
- add FillRectangle() and SetScroll() functions to satisfy tinyterm.Displayer interface
- **bugfixes**
- **pixel**
- fix Monochrome setPixel
- **docs**
- **readme**
- discuss need to change variables in examples
- **sponsor**
- Add sponsor button to key repositories
0.28.0
---
- **new devices**
- **epd2in66b**
- Waveshare 2.66inch E-Paper Display Module (B) for Raspberry Pi Pico (#673)
- **mcp9808**
- Add driver for MCP9808 i2c temperature sensor (#676)
- **enhancements**
- **encoders**
- add atsamd21, atsamd51, atsame5x
- **pixel**
- add support for Monochrome types such as the SSD1306 display
- **rtl8720dn**
- implement ConnectModeAP
- **servo**
- add function SetAngle() to simplify API for most common use case
- **ssd1306**
- add DrawBitmap() function to complete Displayer interface
- add rotation functions for Displayer interface
- add Sleep() function for Displayer interface
- **uc8151**
- improvements to speed and also add flicker-free mode based on @antirez code example
- update to support all functions needed by tinygl and board package Displayer interface
- **wifinina**
- implement ConnectModeAP
- **bugfixes**
- **ft6336**
- ignore bogus touch events
- **pixel**
- fix Image[Monochrome].Set for larger images
- **uc8151**
- correct DrawBitmap() also refactor SendCommand() and SendData() for clarity
- **ws2812**
- Fix typo and move initialization of neo to init()
- **examples**
- **ws2812**
- Simplify examples/ws2812
0.27.0
---
- **core**
- prepare for CGo changes in TinyGo
- **new devices**
- **adafruit4650**
- support for Adafruit 4650 feather OLED
- **net**
- new networking support based on tinygo net package
- **pixel**
- add package for efficiently working with raw pixel buffers
- **rotary**
- Adding driver for rotary encoder support
- **seesaw**
- Adding support for Adafruit Seesaw platform
- **sgp30**
- add SGP30 air quality sensor
- **sk6812**
- added support for SK6812 to WS2812 device (#610)
- **enhancements**
- **epd2in13**
- add Sleep method like other displays
- unify rotation configuration with other displays
- use better black/white approximation
- **ili9341**
- add DrawBitmap method
- **lora/lorawan**
- LoRa WAN US915 Support
- LoRa WAN add setter functions
- refactor shared functionality for channels/regions
- **mcp2515**
- Add more line speeds to mcp2515.go (#626)
- **rtl8720dn**
- use drivers package version as the driver version
- **ssd1306**
- improvements needed for Thumby SPI display
- **st7735**
- make the display generic over RGB565 and RGB444
- **st7789**
- add DrawBitmap method
- make the display generic over RGB565 and RGB444
- **wifinina**
- add ResetIsHigh cfg switch for MKR 1010 (copied from #561)
- maintenence. Also see PR #4085 in the main TinyGo repo
- use drivers package version as the driver version
- **bugfixes**
- **adxl345**
- Use int16 for ADXL345 readings (#656)
- **at24cx**
- fixed the description of the device struct
- **rtl8720dn**
- allow connecting to open wifi access points
- fix check for bad Wifi connect
- **sh1106**
- fix I2C interface and add smoketest
- fixed the description of the device struct
- **wifinina**
- add 'unknown failure' reason code for AP connect
- fix concurrency issues with multiple sockets
- fix wifinina UDP send
- **examples**
- **ds3231**
- fix the description in the example
- **lorawan**
- add missing functions for simulated interface
- modify atcmd and basic demo to support choosing any one of the supported regions at compile time by using ldflags
- **net**
- all networking examples now using netdev and netlink.
- **build**
- **all**
- fix broken testrunner
- migrated legacy I2C
- add natiu package for tests
- **smoketest**
- add stack-size param for net tests.
- allow stack-size flag as it is needed for net examples
0.26.0
---
- **core**
- i2c iface refactor: Resolve #559
- fix uses of legacy i2c WriteRegister calls
- add correct Tx implementation for mock I2C interfaces
- bump golang.org/x/net version
- **new devices**
- **bma42x**
- add new BMA421/BMA425 driver
- **ndir**
- add Sandbox Electronics NDIR CO2 sensor driver (#580)
- **mpu9150**
- implement driver for Mpu9150 (#596)
- **sht4x**
- implement driver for sht4x (#597)
- **pcf8523**
- implement driver for pcf8523 (#599)
- **enhancements**
- **ssd1306**
- improve bus error handling
- **bugfixes**
- **st7789**
- fix scrolling when rotated by 180°
- **st7789**
- fix incorrect Rotation configuration
- fix SetScrollArea
- **ili9341**
- fix SetScrollArea
- **build**
- use latest tag of tinygo-dev container for running tests
0.25.0
---
- **core**
- add Sensor interface and Measurement type
- **delay**
- add new package for cycle-accurate delays
- **new devices**
- **AS560x**
- Add support for ams AS560x on-axis magnetic rotary position sensors
- **onewire**
- first implementation of 1-wire protocol (#505)
- **mpu6886**
- initial implementation
- **ttp229**
- initial support for ttp229 (BSF)
- **enhancements**
- **gps**
- make the date available in addition to the time (#532)
- **i2csoft**
- use cycle counting for delays
- **ili9341**
- add EnableTEOutput to be able to sync drawing with VSYNC
- add sleep mode
- unify rotation support
- **st7735**
- add DrawRGBBitmap8 method to draw raw RGB565 buffers
- add sleep mode
- unify rotation support
- **st7789**
- added DrawRGBBitmap8 (same as ili9341 & st7735)
- allow changing the color format using COLMOD
- make it possible to configure gamma
- support the chip select pin
- update saved rotation in SetRotation
- add sleep mode
- unify rotation support
- **sx126x/sx127x**
- Reduce spi buffer size, add missing select when using channels
- Remove heap alloc in interrupt, add non blocking channel send/receive, and other cleanups
- **wifinina**
- add generated strings, improved debugging system and messages
- add ResetIsHigh to control the behavior of the RESET pin for boards like the Arduino MKR 1010
- only add generated strings when using wifidebug tag
- **bugfixes**
- **ds3231**
- Document incorrect leap year 2100
- Fix negative temperature conversion
- **ili9341**
- fix Size() for mirrored rotation
- **st7789**
- avoid heap allocations after the driver is created
- **net**
- Revert "(#501) make IP.String() method return something sensible"
- **wifinina**
- small timing adjustments in Configure() to better ensure device reset
- **examples**
- **sdcard**
- remove tinyfs example and replace with link to tinyfs repo in docs
- **wifinina**
- improve connectToAP() and other needed minor corrections
- **build**
- switch to ghcr.io for docker container
- run smoke tests in parallel
- **Makefile**
- add XTENSA=0 flag to skip Xtensa tests
- remove AVR=0 flag
- **docs**
- remove full list of devices from README, better to keep it on the tinygo.org site
- update LICENSE year
0.24.0
---
- **new devices**
- **lora**
- created shared RadioEvent
- move shared config for sx126x/sx127x to single package
- **lorawan**
- add initial LoRaWAN stack support
- Basic implementation of Lorawan Regional Settings and EU868/AU915 regions
- **qmi8658c**
- Add support for the QMI8658C sensor (#467)
- **sh1106**
- add support for SH1106 display driver
- **sx127x**
- Driver for Semtech sx127x radio modules
- **enhancements**
- **bme280**
- improve config support
- add ReadAltitude() function copied from BMP280 driver
- **buzzer**
- make all note durations float64
- no tone during rest
- **dht22**
- update DHT22 receive to use runtime/interrupt
- **gps**
- add support for GLL sentence type, add original sentence to gps errors
- improve error handling
- improve parsing and add tests to verify
- **microbitmatrix**
- add link to schema for microbit V2
- add smoke test for microbitmatrix with microbit-v2
- add support for brightness of led pixels
- harmonize v1 and v2 implementation
- move Size() to version agnostic part
- **mpu6050**
- add functions to configure clock, and scaling for accelerometer and gyroscope
- **net/http**
- add PostForm()
- **sx126x**
- add Reset() and needed pin
- move RadioController into separate file for clarity
- pre-define all errors to avoid heap allocations
- refactor to RadioController interface to more easily handle non-STM32WL boards and remove duplicated code
- **vl53l1x**
- Add getter for the effective SPAD count
- **wifinina**
- add support for http server (#480)
- **bugfixes**
- **lsm303agr**
- fix I2C address auto increment for multi data read
- **net**
- (#501) make IP.String() method return something sensible
- **mpu6050**
- return I2C error when configuring fails
- **sx126x**
- fix in SetBandwidth function
- actually set the frequency when calling SetFrequency()
- correct RX/TX pin mapping for TheThingsIndustries GNSE board
- **examples**
- **LoRaWAN**
- example with LoRaWAN AT command set implementation
- basic example
- update all remaining examples for refactored API
- **sx126x**
- fix bandwidth,tx power in lora//lora_continuous example
- **sx127x**
- rx/tx example
- **build**
- remove older format build tags
- update to actions/checkout@v3
- work around for CVE-2022-24765
0.23.0
---
- **new devices**
-3
View File
@@ -8,9 +8,6 @@ We would like your help to make this project better, so we appreciate any contri
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
### Driver design
Before porting or writing a driver from scratch please read **[Driver Design for TinyGo](https://tinygo.org/docs/guides/driver-design)**.
### One of the TinyGo drivers is not working as you expect
Please open a Github issue with your problem, and we will be happy to assist.
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright The TinyGo Authors. All rights reserved.
Copyright (c) 2018-2022 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
+243 -17
View File
@@ -7,10 +7,250 @@ FMT_PATHS = ./
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
XTENSA ?= 1
smoke-test:
@mkdir -p build
@go run ./smoketest.go -xtensa=$(XTENSA) smoketest.sh
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adt7410/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adxl345/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/amg88xx
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/apds9960/proximity/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/itsybitsy-m0/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/at24cx/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bh1750/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/blinkm/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmi160/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp180/main.go
@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
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/easystepper/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/espconsole/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/esphub/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/espstation/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/flash/console/spi
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/flash/console/qspi
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/gc9a01/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/i2c/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/uart/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/hcsr04/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hd44780/customchar/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hd44780/text/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/hd44780i2c/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/hts221/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hub75/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/basic
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/ili9341/basic
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/pyportal_boing
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/scroll
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/ili9341/scroll
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/slideshow
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/lps22hb/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/lsm303agr/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/lsm6ds3/main.go
@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=microbit-v2 ./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=pico ./examples/pca9685/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setbuffer/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
@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
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/shtc3/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/i2c_128x32/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/spi_128x64/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1331/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7735/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/main.go
@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
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl53l1x/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl6180x/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd4in2/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/ntpclient/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/udpstation/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/tcpclient/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/webclient/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examples/ws2812
@md5sum ./build/test.bin
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
@md5sum ./build/test.hex
ifneq ($(AVR), 0)
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
@md5sum ./build/test.hex
endif
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/microphone/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/buzzer/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/veml6070/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l293x/simple/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l293x/speed/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l9110x/simple/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l9110x/speed/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=nucleo-f103rb ./examples/shiftregister/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=hifive1b ./examples/ssd1351/main.go
@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=pico ./examples/qmi8658c/main.go
@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
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/console/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/tinyfs/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/webclient/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/webserver/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/mqttsub/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/i2csoft/adt7410/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.elf -target=wioterminal ./examples/axp192/m5stack-core2-blinky/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/xpt2046/main.go
@md5sum ./build/test.uf2
tinygo build -size short -o ./build/test.elf -target=m5stack-core2 ./examples/ft6336/basic/
@md5sum ./build/test.elf
tinygo build -size short -o ./build/test.elf -target=m5stack-core2 ./examples/ft6336/touchpaint/
@md5sum ./build/test.elf
tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/sx126x/lora_rxtx/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/ssd1289/main.go
@md5sum ./build/test.uf2
tinygo build -size short -o ./build/test.hex -target=pico ./examples/irremote/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=badger2040 ./examples/uc8151/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/scd4x/main.go
@md5sum ./build/test.uf2
tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go
@md5sum ./build/test.uf2
tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/lora/lorawan/atcmd/
@md5sum ./build/test.hex
# rwildcard is a recursive version of $(wildcard)
@@ -19,24 +259,10 @@ rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst
# Recursively find all *_test.go files from cwd & reduce to unique dir names
HAS_TESTS = $(sort $(dir $(call rwildcard,,*_test.go)))
# Exclude anything we explicitly don't want to test for whatever reason
EXCLUDE_TESTS = image waveshare-epd/epd2in66b
EXCLUDE_TESTS = image
TESTS = $(filter-out $(addsuffix /%,$(EXCLUDE_TESTS)),$(HAS_TESTS))
unit-test:
@go test -v $(addprefix ./,$(TESTS))
test: clean fmt-check unit-test smoke-test
EXCLUDE_DIRS = build cmd examples internal lora ndir netdev netlink tester
drivers-count:
@root_count=$$(find . -mindepth 1 -maxdepth 1 -type d | grep -vE '^\./($(subst $(space),|,$(EXCLUDE_DIRS)))$$' | wc -l); \
epd_count=$$(find ./waveshare-epd -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l); \
total=$$((root_count + epd_count)); \
echo "Total drivers: $$total (root: $$root_count, waveshare-epd: $$epd_count)"
drivers-list:
@{ \
find . -mindepth 1 -maxdepth 1 -type d | grep -vE '^\./($(subst $(space),|,$(EXCLUDE_DIRS)))$$'; \
if [ -d ./waveshare-epd ]; then find ./waveshare-epd -mindepth 1 -maxdepth 1 -type d; fi; \
} | sed 's|^\./||' | sort
-233
View File
@@ -1,233 +0,0 @@
### Table of Contents
- ["net" Package](#net-package)
- [Using "net" Package](#using-net-package)
- [Using "net/http" Package](#using-nethttp-package)
- [Using "crypto/tls" Package](#using-cryptotls-package)
- [Using Sockets](#using-sockets)
## "net" Package
TinyGo's "net" package is ported from Go. The port offers a subset of Go's
"net" package. The subset maintains Go 1 compatiblity guarantee. A Go
application that uses "net" will most-likey just work on TinyGo if the usage is
within the subset offered. (There may be external constraints such as limited
SRAM on some targets that may limit full "net" functionality).
Continue below for details on using "net" and "net/http" packages.
See src/net/READMD.md in the TinyGo repo for more details on maintaining
TinyGo's "net" package.
## Using "net" Package
Ideally, TinyGo's "net" package would be Go's "net" package and applications
using "net" would just work, as-is. TinyGo's net package is a partial port of
Go's net package, so some things may not work because they have not been
ported.
There are a few features excluded during the porting process, in particular:
- No IPv6 support
- No DualStack support
Run ```go doc -all ./src/net``` in TinyGo repo to see full listing of what has
been ported. Here is a list of things known to work. You can find examples
of these at [examples/net](examples/net/).
### What is Known to Work
(These are all IPv4 only).
- TCP client and server
- UDP client
- TLS client
- HTTP client and server
- HTTPS client
- NTP client (UDP)
- MQTT client (paho & natiu)
- WebSocket client and server
Multiple sockets can be opened in a single app. For example, the app could run
as an http server listen on port :80 and also use NTP to get the current time
or send something over MQTT. There is a practical limit to the number of
active sockets per app, around 8 or 10, so don't go crazy.
Applications using Go's net package will need a few setup steps to work with
TinyGo's net package. The steps are required before using "net".
### Step 1: Probe to Load Network Driver
Call Probe() to load the correct network driver for your target. Probe()
allows the app to work on multiple targets.
```go
package main
import (
"tinygo.org/x/drivers/netlink/probe"
)
func main() {
// load network driver for target
link, dev := probe.Probe()
...
}
```
Probe() will load the driver with default configuration for the target. For
custom configuration, the app can open code Probe() for the target
requirements.
Probe() returns a [Netlinker](netlink/README.md) and a
[Netdever](netdev/README.md), interfaces implemented by the network driver.
Next, we'll use the Netlinker interface to connect the target to an IP network.
### Step 2: Connect to an IP Network
Before the net package is fully functional, we need to connect the target to an
IP network.
```go
package main
import (
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
func main() {
// load network driver for target
link, _ := probe.Probe()
// Connect target to IP network
link.NetConnect(&netlink.ConnectParams{
Ssid: "my SSID",
Passphrase: "my passphrase",
})
// OK to use "net" from here on
...
}
```
Optionally, get notified of IP network connects and disconnects:
```go
link.Notify(func(e netlink.Event) {
switch e {
case netlink.EventNetUp: println("Network UP")
case netlink.EventNetDown: println("Network DOWN")
})
```
Here is an example of an http server listening on port :8080:
```go
package main
import (
"fmt"
"net/http"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
func HelloServer(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
func main() {
// load network driver for target
link, _ := probe.Probe()
// Connect target to IP network
link.NetConnect(&netlink.ConnectParams{
Ssid: "my SSID",
Passphrase: "my passphrase",
})
// Serve it up
http.HandleFunc("/", HelloServer)
http.ListenAndServe(":8080", nil)
}
```
## Using "net/http" Package
TinyGo's net/http package is a partial port of Go's net/http package, providing
a subset of the full net/http package. There are a few features excluded
during the porting process, in particular:
- No HTTP/2 support
- No TLS support for HTTP servers (no https servers)
- HTTP client request can't be reused
HTTP client methods (http.Get, http.Head, http.Post, and http.PostForm) are
functional. Dial clients support both HTTP and HTTPS URLs.
HTTP server methods and objects are mostly ported, but for HTTP only; HTTPS
servers are not supported.
HTTP request and response handling code is mostly ported, so most the intricacy
of parsing and writing headers is handled as in the full net/http package.
Run ```go doc -all ./src/net/http``` in TinyGo repo to see full listing.
## Using "crypto/tls" Package
TinyGo's TLS support (crypto/tls) relies on hardware offload of the TLS
protocol. This is different from Go's crypto/tls package which handles the TLS
protocol in software.
TinyGo's TLS support is only available for client applications. You can
http.Get() to an https:// address, but you cannot http.ListenAndServeTLS() an
https server.
The offloading hardware has pre-defined TLS certificates built-in.
## Using Sockets
The Netdever interface is a BSD socket-like interface so an application can make direct
socket calls, bypassing the "net" package for the lowest overhead.
Here is a simple TCP client application using direct sockets:
```go
package main
import (
"net" // only need to parse IP address
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
func main() {
// load network driver for target
link, dev := probe.Probe()
// Connect target to IP network
link.NetConnect(&netlink.ConnectParams{
Ssid: "my SSID",
Passphrase: "my passphrase",
})
// omit error handling
sock, _ := dev.Socket(netdev.AF_INET, netdev.SOCK_STREAM, netdev.IPPROTO_TCP)
dev.Connect(sock, "", net.ParseIP("10.0.0.100"), 8080)
dev.Send(sock, []bytes("hello"), 0, 0)
dev.Close(sock)
link.NetDisconnect()
}
```
+98 -27
View File
@@ -3,13 +3,7 @@
[![PkgGoDev](https://pkg.go.dev/badge/tinygo.org/x/drivers)](https://pkg.go.dev/tinygo.org/x/drivers) [![Build](https://github.com/tinygo-org/drivers/actions/workflows/build.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/drivers/actions/workflows/build.yml)
This package provides a collection of over 140 different hardware drivers for devices such as sensors, displays, wireless adaptors, and actuators, that can be used together with [TinyGo](https://tinygo.org).
For the complete list, please see:
https://tinygo.org/docs/reference/devices/
> [!IMPORTANT]
> You can help TinyGo with a financial contribution using OpenCollective. Please see https://opencollective.com/tinygo for more information. Thank you!
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
@@ -19,7 +13,7 @@ go get tinygo.org/x/drivers
## How to use
Here is an example in TinyGo that uses the BMP180 digital barometer. This example should work on any board that supports I2C:
Here is an example in TinyGo that uses the BMP180 digital barometer:
```go
package main
@@ -56,27 +50,104 @@ func main() {
}
```
## Examples Using GPIO or SPI
## Currently supported devices
If compiling these examples directly you are likely to need to make minor changes to the defined variables to map the pins for the board you are using. For example, this block in main.go:
The following 91 devices are supported.
```golang
var (
spi = machine.SPI0
csPin = machine.D5
)
```
It might not be obvious, but you need to change these to match how you wired your specific board. Constants are [defined for each supported microcontroller](https://tinygo.org/docs/reference/microcontrollers/).
For example, to change the definitions for use on a Raspberry Pi Pico using typical wiring, you might need to do this:
```golang
var (
spi = machine.SPI0
csPin = machine.GP17
)
```
| 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 |
| [APDS9960 Digital proximity, ambient light, RGB and gesture sensor](https://cdn.sparkfun.com/assets/learn_tutorials/3/2/1/Avago-APDS-9960-datasheet.pdf) | I2C |
| [AT24CX 2-wire serial EEPROM](https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf) | I2C |
| [ATECCx08 cryptographic processor](https://datasheet.octopart.com/ATSAMA5D27-WLSOM1-Microchip-datasheet-149595509.pdf) | I2C |
| [AXP192 single Cell Li-Battery and Power System Management](https://github.com/m5stack/M5-Schematic/blob/master/Core/AXP192%20Datasheet_v1.1_en_draft_2211.pdf) | I2C |
| [BBC micro:bit LED matrix](https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf) | GPIO |
| [BH1750 ambient light sensor](https://www.mouser.com/ds/2/348/bh1750fvi-e-186247.pdf) | I2C |
| [BlinkM RGB LED](http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf) | I2C |
| [BME280 humidity/pressure sensor](https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf) | I2C |
| [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 |
| [ESP8266/ESP32 AT Command set for WiFi/TCP/UDP](https://github.com/espressif/esp32-at) | UART |
| [FT6336 touch controller](https://focuslcds.com/content/FT6236.pdf) | I2C |
| [GPS module](https://www.u-blox.com/en/product/neo-6-series) | I2C/UART |
| [HC-SR04 Ultrasonic distance sensor](https://cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf) | GPIO |
| [HD44780 LCD controller](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) | GPIO/I2C |
| [HTS221 digital humidity and temperature sensor](https://www.st.com/resource/en/datasheet/hts221.pdf) | I2C |
| [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI |
| [software I2C driver](https://www.ti.com/lit/an/slva704/slva704.pdf) | GPIO |
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
| [INA260 Volt/Amp/Power meter](https://www.ti.com/lit/ds/symlink/ina260.pdf) | I2C |
| [Infrared remote control](https://en.wikipedia.org/wiki/Consumer_IR) | GPIO |
| [IS31FL3731 matrix LED driver](https://www.lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf) | I2C |
| [4x4 Membrane Keypad](https://cdn.sparkfun.com/assets/f/f/a/5/0/DS-16038.pdf) | GPIO |
| [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM |
| [L9110x motor driver](https://www.elecrow.com/download/datasheet-l9110.pdf) | GPIO/PWM |
| [LIS2MDL magnetometer](https://www.st.com/resource/en/datasheet/lis2mdl.pdf) | I2C |
| [LIS3DH accelerometer](https://www.st.com/resource/en/datasheet/lis3dh.pdf) | I2C |
| [LPS22HB MEMS nano pressure sensor](https://www.st.com/resource/en/datasheet/dm00140895.pdf) | I2C |
| [LSM6DS3 accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3.pdf) | I2C |
| [LSM6DSOX accelerometer](https://www.st.com/resource/en/datasheet/lsm6dsox.pdf) | I2C |
| [LSM6DS3TR accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3tr.pdf) | I2C |
| [LSM303AGR accelerometer](https://www.st.com/resource/en/datasheet/lsm303agr.pdf) | I2C |
| [LSM9DS1 accelerometer](https://www.st.com/resource/en/datasheet/lsm9ds1.pdf) | I2C |
| [Makey Button](https://makeymakey.com/) | GPIO |
| [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 |
| [MCP2515 Stand-Alone CAN Controller with SPI Interface](https://ww1.microchip.com/downloads/en/DeviceDoc/MCP2515-Family-Data-Sheet-DS20001801K.pdf) | SPI |
| [MCP3008 analog to digital converter (ADC)](http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf) | SPI |
| [MCP23017 port expander](https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf) | I2C |
| [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 |
| [QMI8658C accelerometer/gyroscope](https://www.qstcorp.com/upload/pdf/202202/%EF%BC%88%E5%B7%B2%E4%BC%A0%EF%BC%89QMI8658C%20datasheet%20rev%200.9.pdf) | I2C |
| [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO |
| [RTL8720DN 2.4G/5G Dual Bands Wireless and BLE5.0](https://www.seeedstudio.com/Realtek8720DN-2-4G-5G-Dual-Bands-Wireless-and-BLE5-0-Combo-Module-p-4442.html) | UART |
| [SCD4x CO2 Sensor](https://sensirion.com/media/documents/C4B87CE6/627C2DCD/CD_DS_SCD40_SCD41_Datasheet_D1.pdf) | I2C |
| [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 |
| [SH1106 OLED display](https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf) | I2C / SPI |
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
| [SHTC3 Digital Humidity Sensor (RH/T)](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHTC3_Datasheet.pdf) | I2C |
| [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI |
| [SPI SDCARD/MMC](https://en.wikipedia.org/wiki/SD_card) | SPI |
| [SSD1306 OLED display](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) | I2C / SPI |
| [SSD1331 TFT color display](https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/) | SPI |
| [SSD1351 OLED display](https://download.mikroe.com/documents/datasheets/ssd1351-revision-1.3.pdf) | SPI |
| [ST7735 TFT color display](https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/) | SPI |
| [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 |
| [UC8151 All-in-one driver IC for ESL](https://www.buydisplay.com/download/ic/UC8151C.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 |
| [VL6180X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl6180x.pdf) | I2C |
| [Waveshare 2.13" (B & C) e-paper display](https://www.waveshare.com/w/upload/d/d3/2.13inch-e-paper-b-Specification.pdf) | SPI |
| [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI |
| [Waveshare 2.9" e-paper display (V1)](https://www.waveshare.com/w/upload/e/e6/2.9inch_e-Paper_Datasheet.pdf) | SPI |
| [Waveshare 4.2" e-paper B/W display](https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf) | SPI |
| [Waveshare GC9A01 TFT round display](https://www.waveshare.com/w/upload/5/5e/GC9A01A.pdf) | SPI |
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
| [XPT2046 touch controller](http://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf) | GPIO |
| [Semtech SX126x Lora](https://www.semtech.com/products/wireless-rf/lora-connect/sx1261) | SPI |
| [SSD1289 TFT color display](http://aitendo3.sakura.ne.jp/aitendo_data/product_img/lcd/tft2/M032C1289TP/3.2-SSD1289.pdf) | GPIO |
## Contributing
-196
View File
@@ -1,196 +0,0 @@
// Package adafruit4650 implements a driver for the Adafruit FeatherWing OLED - 128x64 OLED display.
// The display is backed itself by a SH1107 driver chip.
//
// Store: https://www.adafruit.com/product/4650
//
// Documentation: https://learn.adafruit.com/adafruit-128x64-oled-featherwing
package adafruit4650
import (
"image/color"
"time"
"tinygo.org/x/drivers"
)
const DefaultAddress = 0x3c
const (
commandSetLowColumn = 0x00
commandSetHighColumn = 0x10
commandSetPage = 0xb0
)
const (
width = 128
height = 64
)
// Device represents an Adafruit 4650 device
type Device struct {
bus drivers.I2C
Address uint8
buffer []byte
width int16
height int16
}
// New creates a new device, not configuring anything yet.
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: DefaultAddress,
width: width,
height: height,
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure() error {
bufferSize := d.width * d.height / 8
d.buffer = make([]byte, bufferSize)
// This sequence is an amalgamation of the datasheet, official Arduino driver, CircuitPython driver and other drivers
initSequence := []byte{
0xae, // display off, sleep mode
//0xd5, 0x41, // set display clock divider (from original datasheet)
0xd5, 0x51, // set display clock divider (from Adafruit driver)
0xd9, 0x22, // pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR)
0x20, // memory mode
0x81, 0x4f, // contrast setting = 0x4f
0xad, 0x8a, // set dc/dc pump
0xa0, // segment remap, flip-x
0xc0, // common output scan direction
0xdc, 0x00, // set display start line 0 (POR=0)
0xa8, 0x3f, // multiplex ratio, height - 1 = 0x3f
0xd3, 0x60, // set display offset mode = 0x60
0xdb, 0x35, // VCOM deselect level = 0.770 (POR)
0xa4, // entire display off, retain RAM, normal status (POR)
0xa6, // normal (not reversed) display
0xaf, // display on
}
err := d.writeCommands(initSequence)
if err != nil {
return err
}
// recommended in the datasheet, same in other drivers
time.Sleep(100 * time.Millisecond)
return nil
}
// ClearDisplay clears the image buffer as well as the actual display
func (d *Device) ClearDisplay() error {
d.ClearBuffer()
return d.Display()
}
// ClearBuffer clears the buffer
func (d *Device) ClearBuffer() {
bzero(d.buffer)
}
// SetPixel modifies the internal buffer. Since this display has a bit-depth of 1 bit any non-zero
// color component will be treated as 'on', otherwise 'off'.
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
if x < 0 || x >= d.width || y < 0 || y >= d.height {
return
}
// RAM layout
// *-----> y
// |
// x| col0 col1 ... col63
// v p0 a0 b0 ..
// a1 b1 ..
// .. .. ..
// a7 b7 ..
// p1 a0 b0
// a1 b1
//
//flip y - so the display orientation matches the silk screen labeling etc.
y = d.height - y - 1
page := x / 8
bytesPerPage := d.height
byteIndex := y + bytesPerPage*page
bit := x % 8
if (c.R | c.G | c.B) != 0 {
d.buffer[byteIndex] |= 1 << uint8(bit)
} else {
d.buffer[byteIndex] &^= 1 << uint8(bit)
}
}
// Display sends the whole buffer to the screen
func (d *Device) Display() error {
bytesPerPage := d.height
pages := (d.width + 7) / 8
for page := int16(0); page < pages; page++ {
err := d.setRAMPosition(uint8(page), 0)
if err != nil {
return err
}
offset := page * bytesPerPage
err = d.writeRAM(d.buffer[offset : offset+bytesPerPage])
if err != nil {
return err
}
}
return nil
}
// setRAMPosition updates the device's current page and column position
func (d *Device) setRAMPosition(page uint8, column uint8) error {
if page > 15 {
panic("page out of bounds")
}
if column > 127 {
panic("column out of bounds")
}
setPage := commandSetPage | (page & 0xF)
lo := column & 0xF
setLowColumn := commandSetLowColumn | lo
hi := (column >> 4) & 0x7
setHighColumn := commandSetHighColumn | hi
cmds := []byte{
setPage,
setLowColumn,
setHighColumn,
}
return d.writeCommands(cmds)
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return d.width, d.height
}
func (d *Device) writeCommands(commands []byte) error {
onlyCommandsFollowing := byte(0x00)
return d.bus.Tx(uint16(d.Address), append([]byte{onlyCommandsFollowing}, commands...), nil)
}
func (d *Device) writeRAM(data []byte) error {
onlyRAMFollowing := byte(0x40)
return d.bus.Tx(uint16(d.Address), append([]byte{onlyRAMFollowing}, data...), nil)
}
func bzero(buf []byte) {
for i := range buf {
buf[i] = 0
}
}
-176
View File
@@ -1,176 +0,0 @@
package adafruit4650
import (
"bytes"
_ "embed"
"encoding/hex"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"os"
"testing"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)
//go:embed expected_hello_world.png
var expectedHelloWorld []byte
// mockBus mocks a fake i2c device adafruit4650 display.
// The memory layout assumes that clients set up the device in a particular way and always send complete
// pages to the device buffer.
type mockBus struct {
img draw.Image
line int
addr uint8
currentPage int
currentColumn int
}
func (m *mockBus) Tx(addr uint16, w, r []byte) error {
if addr != uint16(m.addr) {
panic("unexpected address")
}
if r != nil {
panic("mock does not support reads")
}
if w[0] == 0x00 {
if w[1]&0xf0 == 0xb0 {
m.currentPage = int(w[1] & 0x0f)
lo := w[2] & 0x0f
hi := w[2] & 0x07
m.currentColumn = int(hi<<4 | lo)
}
return nil
}
if w[0] != 0x40 {
panic("unexpected first byte: " + hex.EncodeToString(w[0:1]))
}
return m.writeRAM(w[1:])
}
func newMock() *mockBus {
m := image.NewRGBA(image.Rect(0, 0, width, height))
return &mockBus{img: m, addr: DefaultAddress, currentPage: -1, currentColumn: -1}
}
func (m *mockBus) writeRAM(data []byte) error {
// RAM layout
// *-----> y
// |
// x| col0 col1 ... col63
// v p0 a0 b0 ..
// a1 b1 ..
// .. .. ..
// a7 b7 ..
// p1 a0 b0
// a1 b1
//
fmt.Printf("writing page %d\n", m.currentPage)
// assuming entire pages will be written
for x := 0; x < 8; x++ {
for y := 0; y < height; y++ {
col := data[y]
c := color.Black
if col&(1<<x) != 0 {
c = color.White
}
m.img.Set(x+m.currentPage*8, height-y-1, c)
}
}
return nil
}
func (m *mockBus) toImage() *image.RGBA {
container := image.NewRGBA(m.img.Bounds().Inset(-1))
draw.Draw(container, container.Bounds(), image.NewUniform(color.RGBA{G: 255, A: 255}), image.Point{}, draw.Over)
draw.Draw(container, m.img.Bounds(), m.img, image.Point{}, draw.Over)
return container
}
func TestDevice_Display(t *testing.T) {
bus := newMock()
dev := New(bus)
dev.Configure()
drawPlus(&dev)
drawHellowWorld(&dev)
//when
dev.Display()
//then
actual := bus.toImage()
expected, err := png.Decode(bytes.NewReader(expectedHelloWorld))
if err != nil {
panic(err)
}
assertEqualImages(t, actual, expected)
}
func drawPlus(d drivers.Displayer) {
for i := int16(0); i < 128; i++ {
d.SetPixel(i, 32, color.RGBA{R: 1})
}
for i := int16(0); i < 64; i++ {
d.SetPixel(64, i, color.RGBA{R: 1})
}
}
func drawHellowWorld(d drivers.Displayer) {
tinyfont.WriteLine(d, &freemono.Regular9pt7b, 0, 32, "Hello World!", color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
}
func assertEqualImages(t testing.TB, actual, expected image.Image) {
if actual.Bounds().Dx() != expected.Bounds().Dx() || actual.Bounds().Dy() != expected.Bounds().Dy() {
f := writeImage(actual)
t.Fatalf("differing size: was %v, expected %v, saved actual to %s", actual.Bounds(), expected.Bounds(), f)
}
bb := expected.Bounds()
for x := bb.Min.X; x < bb.Max.X; x++ {
for y := bb.Min.Y; y < bb.Max.Y; y++ {
actualBB := actual.Bounds()
if actual.At(x+actualBB.Min.X, y+actualBB.Min.Y) != expected.At(x, y) {
f := writeImage(actual)
t.Fatalf("different pixel at %d/%d: %v != %v, saved actual at %s", x, y, actual.At(x, y), expected.At(x, y), f)
}
}
}
}
func writeImage(img image.Image) string {
fn := fmt.Sprintf("%d.png", time.Now().Unix())
f, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
defer f.Close()
err = png.Encode(f, img)
if err != nil {
panic(err)
}
return fn
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

+3 -4
View File
@@ -7,7 +7,6 @@ import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
type Error uint8
@@ -55,7 +54,7 @@ func (d *Device) Configure() (err error) {
// Connected returns whether sensor has been found.
func (d *Device) Connected() bool {
data := []byte{0}
legacy.ReadRegister(d.bus, uint8(d.Address), RegID, data)
d.bus.ReadRegister(uint8(d.Address), RegID, data)
return data[0]&0xF8 == 0xC8
}
@@ -82,11 +81,11 @@ func (d *Device) writeByte(reg uint8, data byte) {
}
func (d *Device) readByte(reg uint8) byte {
legacy.ReadRegister(d.bus, d.Address, reg, d.buf)
d.bus.ReadRegister(d.Address, reg, d.buf)
return d.buf[0]
}
func (d *Device) readUint16(reg uint8) uint16 {
legacy.ReadRegister(d.bus, d.Address, reg, d.buf)
d.bus.ReadRegister(d.Address, reg, d.buf)
return uint16(d.buf[0])<<8 | uint16(d.buf[1])
}
+17 -20
View File
@@ -5,10 +5,7 @@
// Datasheet JP: http://www.analog.com/media/jp/technical-documentation/data-sheets/ADXL345_jp.pdf
package adxl345 // import "tinygo.org/x/drivers/adxl345"
import (
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
import "tinygo.org/x/drivers"
type Range uint8
type Rate uint8
@@ -71,21 +68,21 @@ func New(bus drivers.I2C) Device {
// Configure sets up the device for communication
func (d *Device) Configure() {
legacy.WriteRegister(d.bus, uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
legacy.WriteRegister(d.bus, uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
legacy.WriteRegister(d.bus, uint8(d.Address), REG_DATA_FORMAT, []byte{d.dataFormat.toByte()})
d.bus.WriteRegister(uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
d.bus.WriteRegister(uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
d.bus.WriteRegister(uint8(d.Address), REG_DATA_FORMAT, []byte{d.dataFormat.toByte()})
}
// Halt stops the sensor, values will not updated
func (d *Device) Halt() {
d.powerCtl.measure = 0
legacy.WriteRegister(d.bus, uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
d.bus.WriteRegister(uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
}
// Restart makes reading the sensor working again after a halt
func (d *Device) Restart() {
d.powerCtl.measure = 1
legacy.WriteRegister(d.bus, uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
d.bus.WriteRegister(uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
}
// ReadAcceleration reads the current acceleration from the device and returns
@@ -95,18 +92,18 @@ func (d *Device) Restart() {
func (d *Device) ReadAcceleration() (x int32, y int32, z int32, err error) {
rx, ry, rz := d.ReadRawAcceleration()
x = int32(d.dataFormat.convertToIS(rx))
y = int32(d.dataFormat.convertToIS(ry))
z = int32(d.dataFormat.convertToIS(rz))
x = d.dataFormat.convertToIS(rx)
y = d.dataFormat.convertToIS(ry)
z = d.dataFormat.convertToIS(rz)
return
}
// ReadRawAcceleration reads the sensor values and returns the raw x, y and z axis
// from the adxl345.
func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
func (d *Device) ReadRawAcceleration() (x int32, y int32, z int32) {
data := []byte{0, 0, 0, 0, 0, 0}
legacy.ReadRegister(d.bus, uint8(d.Address), REG_DATAX0, data)
d.bus.ReadRegister(uint8(d.Address), REG_DATAX0, data)
x = readIntLE(data[0], data[1])
y = readIntLE(data[2], data[3])
@@ -122,25 +119,25 @@ func (d *Device) UseLowPower(power bool) {
} else {
d.bwRate.lowPower = 0
}
legacy.WriteRegister(d.bus, uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
d.bus.WriteRegister(uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
}
// SetRate change the current rate of the sensor
func (d *Device) SetRate(rate Rate) bool {
d.bwRate.rate = rate & 0x0F
legacy.WriteRegister(d.bus, uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
d.bus.WriteRegister(uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
return true
}
// SetRange change the current range of the sensor
func (d *Device) SetRange(sensorRange Range) bool {
d.dataFormat.sensorRange = sensorRange & 0x03
legacy.WriteRegister(d.bus, uint8(d.Address), REG_DATA_FORMAT, []byte{d.dataFormat.toByte()})
d.bus.WriteRegister(uint8(d.Address), REG_DATA_FORMAT, []byte{d.dataFormat.toByte()})
return true
}
// convertToIS adjusts the raw values from the adxl345 with the range configuration
func (d *dataFormat) convertToIS(rawValue int16) int16 {
func (d *dataFormat) convertToIS(rawValue int32) int32 {
switch d.sensorRange {
case RANGE_2G:
return rawValue * 4 // rawValue * 2 * 1000 / 512
@@ -190,6 +187,6 @@ func (b *bwRate) toByte() (bits uint8) {
}
// readInt converts two bytes to int16
func readIntLE(msb byte, lsb byte) int16 {
return int16(uint16(msb) | uint16(lsb)<<8)
func readIntLE(msb byte, lsb byte) int32 {
return int32(uint16(msb) | uint16(lsb)<<8)
}
+2 -2
View File
@@ -29,7 +29,7 @@ func New(bus drivers.I2C) Device {
func (d *Device) Configure() {
// Check initialization state
status := d.Status()
if status&STATUS_CALIBRATED == 1 {
if status&0x08 == 1 {
// Device is initialized
return
}
@@ -69,7 +69,7 @@ func (d *Device) Read() error {
}
// If measurement complete, store values
if data[0]&STATUS_CALIBRATED != 0 && data[0]&STATUS_BUSY == 0 {
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
+16 -17
View File
@@ -8,7 +8,6 @@ import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// Device wraps an I2C connection to a AMG88xx device.
@@ -49,7 +48,7 @@ func (d *Device) Configure(cfg Config) {
// ReadPixels returns the 64 values (8x8 grid) of the sensor converted to millicelsius
func (d *Device) ReadPixels(buffer *[64]int16) {
legacy.ReadRegister(d.bus, uint8(d.Address), PIXEL_OFFSET, d.data)
d.bus.ReadRegister(uint8(d.Address), PIXEL_OFFSET, d.data)
for i := 0; i < 64; i++ {
buffer[i] = int16((uint16(d.data[2*i+1]) << 8) | uint16(d.data[2*i]))
if (buffer[i] & (1 << 11)) > 0 { // temperature negative
@@ -62,17 +61,17 @@ func (d *Device) ReadPixels(buffer *[64]int16) {
// SetPCTL sets the PCTL
func (d *Device) SetPCTL(pctl uint8) {
legacy.WriteRegister(d.bus, uint8(d.Address), PCTL, []byte{pctl})
d.bus.WriteRegister(uint8(d.Address), PCTL, []byte{pctl})
}
// SetReset sets the reset value
func (d *Device) SetReset(rst uint8) {
legacy.WriteRegister(d.bus, uint8(d.Address), RST, []byte{rst})
d.bus.WriteRegister(uint8(d.Address), RST, []byte{rst})
}
// SetFrameRate configures the frame rate
func (d *Device) SetFrameRate(framerate uint8) {
legacy.WriteRegister(d.bus, uint8(d.Address), FPSC, []byte{framerate & 0x01})
d.bus.WriteRegister(uint8(d.Address), FPSC, []byte{framerate & 0x01})
}
// SetMovingAverageMode sets the moving average mode
@@ -81,7 +80,7 @@ func (d *Device) SetMovingAverageMode(mode bool) {
if mode {
value = 1
}
legacy.WriteRegister(d.bus, uint8(d.Address), AVE, []byte{value << 5})
d.bus.WriteRegister(uint8(d.Address), AVE, []byte{value << 5})
}
// SetInterruptLevels sets the interrupt levels
@@ -98,8 +97,8 @@ func (d *Device) SetInterruptLevelsHysteresis(high int16, low int16, hysteresis
if high > 4095 {
high = 4095
}
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8(high & 0xFF)})
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8((high & 0xFF) >> 4)})
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8(high & 0xFF)})
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8((high & 0xFF) >> 4)})
low = low / PIXEL_TEMP_CONVERSION
if low < -4095 {
@@ -108,8 +107,8 @@ func (d *Device) SetInterruptLevelsHysteresis(high int16, low int16, hysteresis
if low > 4095 {
low = 4095
}
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8(low & 0xFF)})
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8((low & 0xFF) >> 4)})
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8(low & 0xFF)})
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8((low & 0xFF) >> 4)})
hysteresis = hysteresis / PIXEL_TEMP_CONVERSION
if hysteresis < -4095 {
@@ -118,32 +117,32 @@ func (d *Device) SetInterruptLevelsHysteresis(high int16, low int16, hysteresis
if hysteresis > 4095 {
hysteresis = 4095
}
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8(hysteresis & 0xFF)})
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8((hysteresis & 0xFF) >> 4)})
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8(hysteresis & 0xFF)})
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8((hysteresis & 0xFF) >> 4)})
}
// EnableInterrupt enables the interrupt pin on the device
func (d *Device) EnableInterrupt() {
d.interruptEnable = 1
legacy.WriteRegister(d.bus, uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
d.bus.WriteRegister(uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
}
// DisableInterrupt disables the interrupt pin on the device
func (d *Device) DisableInterrupt() {
d.interruptEnable = 0
legacy.WriteRegister(d.bus, uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
d.bus.WriteRegister(uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
}
// SetInterruptMode sets the interrupt mode
func (d *Device) SetInterruptMode(mode InterruptMode) {
d.interruptMode = mode
legacy.WriteRegister(d.bus, uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
d.bus.WriteRegister(uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
}
// GetInterrupt reads the state of the triggered interrupts
func (d *Device) GetInterrupt() []uint8 {
data := make([]uint8, 8)
legacy.ReadRegister(d.bus, uint8(d.Address), INT_OFFSET, data)
d.bus.ReadRegister(uint8(d.Address), INT_OFFSET, data)
return data
}
@@ -155,6 +154,6 @@ func (d *Device) ClearInterrupt() {
// ReadThermistor reads the onboard thermistor
func (d *Device) ReadThermistor() int16 {
data := make([]uint8, 2)
legacy.ReadRegister(d.bus, uint8(d.Address), TTHL, data)
d.bus.ReadRegister(uint8(d.Address), TTHL, data)
return (int16((uint16(data[1])<<8)|uint16(data[0])) * THERMISTOR_CONVERSION) / 10
}
+3 -7
View File
@@ -5,10 +5,9 @@ package apa102 // import "tinygo.org/x/drivers/apa102"
import (
"image/color"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
const (
@@ -38,11 +37,8 @@ func New(b drivers.SPI) *Device {
// NewSoftwareSPI returns a new APA102 driver that will use a software based
// implementation of the SPI protocol.
func NewSoftwareSPI(sckPin, sdoPin pin.Output, delay uint32) *Device {
return New(&bbSPI{SCK: sckPin.Set, SDO: sdoPin.Set, Delay: delay, configurePins: func() {
legacy.ConfigurePinOut(sckPin)
legacy.ConfigurePinOut(sdoPin)
}})
func NewSoftwareSPI(sckPin, sdoPin machine.Pin, delay uint32) *Device {
return New(&bbSPI{SCK: sckPin, SDO: sdoPin, Delay: delay})
}
// WriteColors writes the given RGBA color slice out using the APA102 protocol.
+6 -12
View File
@@ -1,9 +1,6 @@
package apa102
import (
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
import "machine"
// bbSPI is a dumb bit-bang implementation of SPI protocol that is hardcoded
// to mode 0 and ignores trying to receive data. Just enough for the APA102.
@@ -11,18 +8,15 @@ import (
// most purposes other than the APA102 package. It might be desirable to make
// this more generic and include it in the TinyGo "machine" package instead.
type bbSPI struct {
SCK pin.OutputFunc
SDO pin.OutputFunc
Delay uint32
configurePins func()
SCK machine.Pin
SDO machine.Pin
Delay uint32
}
// Configure sets up the SCK and SDO pins as outputs and sets them low
func (s *bbSPI) Configure() {
if s.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
s.configurePins()
s.SCK.Configure(machine.PinConfig{Mode: machine.PinOutput})
s.SDO.Configure(machine.PinConfig{Mode: machine.PinOutput})
s.SCK.Low()
s.SDO.Low()
if s.Delay == 0 {
+25 -26
View File
@@ -8,7 +8,6 @@ import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// Device wraps an I2C connection to a APDS-9960 device.
@@ -69,7 +68,7 @@ func New(bus drivers.I2C) Device {
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
legacy.ReadRegister(d.bus, d.Address, APDS9960_ID_REG, data)
d.bus.ReadRegister(d.Address, APDS9960_ID_REG, data)
return data[0] == 0xAB
}
@@ -81,7 +80,7 @@ func (d *Device) GetMode() uint8 {
// DisableAll turns off the device and all functions
func (d *Device) DisableAll() {
d.enable(enableConfig{})
legacy.WriteRegister(d.bus, d.Address, APDS9960_GCONF4_REG, []byte{0x00})
d.bus.WriteRegister(d.Address, APDS9960_GCONF4_REG, []byte{0x00})
d.mode = MODE_NONE
d.gesture.detected = GESTURE_NONE
}
@@ -89,13 +88,13 @@ func (d *Device) DisableAll() {
// SetProximityPulse sets proximity pulse length (4, 8, 16, 32) and count (1~64)
// default: 16, 64
func (d *Device) SetProximityPulse(length, count uint8) {
legacy.WriteRegister(d.bus, d.Address, APDS9960_PPULSE_REG, []byte{getPulseLength(length)<<6 | getPulseCount(count)})
d.bus.WriteRegister(d.Address, APDS9960_PPULSE_REG, []byte{getPulseLength(length)<<6 | getPulseCount(count)})
}
// SetGesturePulse sets gesture pulse length (4, 8, 16, 32) and count (1~64)
// default: 16, 64
func (d *Device) SetGesturePulse(length, count uint8) {
legacy.WriteRegister(d.bus, d.Address, APDS9960_GPULSE_REG, []byte{getPulseLength(length)<<6 | getPulseCount(count)})
d.bus.WriteRegister(d.Address, APDS9960_GPULSE_REG, []byte{getPulseLength(length)<<6 | getPulseCount(count)})
}
// SetADCIntegrationCycles sets ALS/color ADC internal integration cycles (1~256, 1 cycle = 2.78 ms)
@@ -104,14 +103,14 @@ func (d *Device) SetADCIntegrationCycles(cycles uint16) {
if cycles > 256 {
cycles = 256
}
legacy.WriteRegister(d.bus, d.Address, APDS9960_ATIME_REG, []byte{uint8(256 - cycles)})
d.bus.WriteRegister(d.Address, APDS9960_ATIME_REG, []byte{uint8(256 - cycles)})
}
// SetGains sets proximity/gesture gain (1, 2, 4, 8x) and ALS/color gain (1, 4, 16, 64x)
// default: 1, 1, 4
func (d *Device) SetGains(proximityGain, gestureGain, colorGain uint8) {
legacy.WriteRegister(d.bus, d.Address, APDS9960_CONTROL_REG, []byte{getProximityGain(proximityGain)<<2 | getALSGain(colorGain)})
legacy.WriteRegister(d.bus, d.Address, APDS9960_GCONF2_REG, []byte{getProximityGain(gestureGain) << 5})
d.bus.WriteRegister(d.Address, APDS9960_CONTROL_REG, []byte{getProximityGain(proximityGain)<<2 | getALSGain(colorGain)})
d.bus.WriteRegister(d.Address, APDS9960_GCONF2_REG, []byte{getProximityGain(gestureGain) << 5})
}
// LEDBoost sets proximity and gesture LED current level (100, 150, 200, 300 (%))
@@ -128,7 +127,7 @@ func (d *Device) LEDBoost(percent uint16) {
case 300:
v = 3
}
legacy.WriteRegister(d.bus, d.Address, APDS9960_CONFIG2_REG, []byte{0x01 | v<<4})
d.bus.WriteRegister(d.Address, APDS9960_CONFIG2_REG, []byte{0x01 | v<<4})
}
// Setthreshold sets threshold (0~255) for detecting gestures
@@ -169,7 +168,7 @@ func (d *Device) ReadProximity() (proximity int32) {
return 0
}
data := []byte{0}
legacy.ReadRegister(d.bus, d.Address, APDS9960_PDATA_REG, data)
d.bus.ReadRegister(d.Address, APDS9960_PDATA_REG, data)
return 255 - int32(data[0])
}
@@ -196,14 +195,14 @@ func (d *Device) ReadColor() (r int32, g int32, b int32, clear int32) {
return
}
data := []byte{0, 0, 0, 0, 0, 0, 0, 0}
legacy.ReadRegister(d.bus, d.Address, APDS9960_CDATAL_REG, data[:1])
legacy.ReadRegister(d.bus, d.Address, APDS9960_CDATAH_REG, data[1:2])
legacy.ReadRegister(d.bus, d.Address, APDS9960_RDATAL_REG, data[2:3])
legacy.ReadRegister(d.bus, d.Address, APDS9960_RDATAH_REG, data[3:4])
legacy.ReadRegister(d.bus, d.Address, APDS9960_GDATAL_REG, data[4:5])
legacy.ReadRegister(d.bus, d.Address, APDS9960_GDATAH_REG, data[5:6])
legacy.ReadRegister(d.bus, d.Address, APDS9960_BDATAL_REG, data[6:7])
legacy.ReadRegister(d.bus, d.Address, APDS9960_BDATAH_REG, data[7:])
d.bus.ReadRegister(d.Address, APDS9960_CDATAL_REG, data[:1])
d.bus.ReadRegister(d.Address, APDS9960_CDATAH_REG, data[1:2])
d.bus.ReadRegister(d.Address, APDS9960_RDATAL_REG, data[2:3])
d.bus.ReadRegister(d.Address, APDS9960_RDATAH_REG, data[3:4])
d.bus.ReadRegister(d.Address, APDS9960_GDATAL_REG, data[4:5])
d.bus.ReadRegister(d.Address, APDS9960_GDATAH_REG, data[5:6])
d.bus.ReadRegister(d.Address, APDS9960_BDATAL_REG, data[6:7])
d.bus.ReadRegister(d.Address, APDS9960_BDATAH_REG, data[7:])
clear = int32(uint16(data[1])<<8 | uint16(data[0]))
r = int32(uint16(data[3])<<8 | uint16(data[2]))
g = int32(uint16(data[5])<<8 | uint16(data[4]))
@@ -235,13 +234,13 @@ func (d *Device) GestureAvailable() bool {
data := []byte{0, 0, 0, 0}
// check GVALID
legacy.ReadRegister(d.bus, d.Address, APDS9960_GSTATUS_REG, data[:1])
d.bus.ReadRegister(d.Address, APDS9960_GSTATUS_REG, data[:1])
if data[0]&0x01 == 0 {
return false
}
// get number of data sets available in FIFO
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFLVL_REG, data[:1])
d.bus.ReadRegister(d.Address, APDS9960_GFLVL_REG, data[:1])
availableDataSets := data[0]
if availableDataSets == 0 {
return false
@@ -250,10 +249,10 @@ func (d *Device) GestureAvailable() bool {
// read up, down, left and right proximity data from FIFO
var dataSets [32][4]uint8
for i := uint8(0); i < availableDataSets; i++ {
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_U_REG, data[:1])
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_D_REG, data[1:2])
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_L_REG, data[2:3])
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_R_REG, data[3:4])
d.bus.ReadRegister(d.Address, APDS9960_GFIFO_U_REG, data[:1])
d.bus.ReadRegister(d.Address, APDS9960_GFIFO_D_REG, data[1:2])
d.bus.ReadRegister(d.Address, APDS9960_GFIFO_L_REG, data[2:3])
d.bus.ReadRegister(d.Address, APDS9960_GFIFO_R_REG, data[3:4])
for j := uint8(0); j < 4; j++ {
dataSets[i][j] = data[j]
}
@@ -386,7 +385,7 @@ func (d *Device) enable(cfg enableConfig) {
}
data := []byte{gen<<6 | pien<<5 | aien<<4 | wen<<3 | pen<<2 | aen<<1 | pon}
legacy.WriteRegister(d.bus, d.Address, APDS9960_ENABLE_REG, data)
d.bus.WriteRegister(d.Address, APDS9960_ENABLE_REG, data)
if cfg.PON {
time.Sleep(time.Millisecond * 10)
@@ -395,7 +394,7 @@ func (d *Device) enable(cfg enableConfig) {
func (d *Device) readStatus(param string) bool {
data := []byte{0}
legacy.ReadRegister(d.bus, d.Address, APDS9960_STATUS_REG, data)
d.bus.ReadRegister(d.Address, APDS9960_STATUS_REG, data)
switch param {
case "CPSAT":
-172
View File
@@ -1,172 +0,0 @@
// Product: https://ams.com/as5600
// Datasheet: https://ams.com/documents/20143/36005/AS5600_DS000365_5-00.pdf
package as560x // import tinygo.org/x/drivers/ams560x
import (
"time"
"tinygo.org/x/drivers"
)
// AS5600 includes MPOS & MANG in addition to ZPOS to set a 'narrower angle range'
// ZPOS enables setting the 'zero position' of the device to any RAW_ANGLE value.
// MPOS ('max position') & MANG 'max angle' enable a 'partial range' on the AS5600.
// The value in ANGLE is scaled & adjusted by the device according to ZPOS and MPOS/MANG.
// The entire 12-bit range is 'compressed' into the RAW_ANGLE range of ZPOS->MPOS
// (or ZPOS->ZPOS+MANG) thus enabling a higher resolution for a partial range.
// if ZPOS > MPOS (or ZPOS + MANG > 4095) i.e. the incremental range 'crosses zero'
// then the device will automatically compensate for the correct range.
// For RAW_ANGLE values outside of the partial range, ANGLE will be 'capped' at either
// 0 or 4095, depending on 'which end of the partial range is closer.'
// AS5600Device represents an ams AS5600 device driver accessed over I2C
type AS5600Device struct {
// promote BaseDevice
BaseDevice
}
// NewAS5600 creates a new AS5600Device given an I2C bus
func NewAS5600(bus drivers.I2C) AS5600Device {
// Create base device
baseDev := newBaseDevice(bus)
// Add AS5600 specific registers
baseDev.registers[MPOS] = newI2CRegister(MPOS, 0, 0xfff, 2, reg_read|reg_write|reg_program)
baseDev.registers[MANG] = newI2CRegister(MANG, 0, 0xfff, 2, reg_read|reg_write|reg_program)
// Add AS5600 specific 'virtual registers'
conf, ok := baseDev.registers[CONF]
if ok {
baseDev.registers[PWMF] = newVirtualRegister(conf, 6, 0b11)
baseDev.registers[OUTS] = newVirtualRegister(conf, 4, 0b11)
}
// Return the device
return AS5600Device{baseDev}
}
// Configure sets up the AMS AS5600 sensor device with the given configuration.
func (d *AS5600Device) Configure(cfg Config) error {
// Call the BaseDevice method to do the actual Configure
d.BaseDevice.Configure(cfg)
// For AS5600 devices we need to calculate the maxAngle on startup from ZPOS/MPOS/MANG
// These could have been permanently BURN'ed (by writing BURN register with BURN_ANGLE/BURN_SETTING)
// or may have already been written in previous runs without a power cycle since.
mpos, err := d.ReadRegister(MPOS)
if nil != err {
return err
}
mang, err := d.ReadRegister(MANG)
if nil != err {
return err
}
// Read ZPOS for side effect of caching only so that next calculateEffectiveMaxAngle() can't fail
if _, err = d.ReadRegister(ZPOS); nil != err {
return err
}
if mpos != 0 {
// If MPOS is set, use MPOS regardless of MANG
err = d.calculateEffectiveMaxAngle(MPOS, mpos)
} else if mang != 0 {
// If MANG is set and MPOS == 0, use MANG
err = d.calculateEffectiveMaxAngle(MANG, mang)
} else {
// if neither is set, we have no narrow range
d.maxAngle = NATIVE_ANGLE_RANGE
}
return err
}
// calculateEffectiveMaxAngle calculates d.maxAngle after one of ZPOS/MPOS/MANG have been written
func (d *AS5600Device) calculateEffectiveMaxAngle(register uint8, value uint16) error {
var zpos, mpos uint16 = 0, 0
var err error = nil
switch register {
case MANG:
d.maxAngle = value // The easy case
return nil
case ZPOS:
zpos = value
mpos, err = d.ReadRegister(MPOS)
case MPOS:
mpos = value
zpos, err = d.ReadRegister(ZPOS)
default:
panic("calculateEffectiveMaxAngle() can only work from ZPOS, MPOS or MANG")
}
if nil != err {
return err
}
// MANG is effectively MPOS-ZPOS
mang := int(mpos) - int(zpos)
// correct for mpos < zpos
if mang < 0 {
mang += NATIVE_ANGLE_RANGE
}
d.maxAngle = uint16(mang)
return nil
}
// WriteRegister writes the given value for the given register to the AS560x device via I2C
func (d *AS5600Device) WriteRegister(address uint8, value uint16) error {
// Call the BaseDevice method to do the actual write
if err := d.BaseDevice.WriteRegister(address, value); err != nil {
return err
}
// When either ZPOS/MANG/MPOS are set we need to recalculate maxAngle
// We also may need to invalidate some cached values for the other two registers
recalc := false
switch address {
case ZPOS:
// Setting a new ZPOS invalidates MPOS but not MANG
d.registers[MPOS].invalidate()
recalc = true
case MPOS:
// Setting a new MPOS invalidates MANG but not ZPOS
d.registers[MANG].invalidate()
recalc = true
case MANG:
// Setting a new MANG invalidates MPOS but not ZPOS
d.registers[MPOS].invalidate()
recalc = true
}
if recalc {
// Datasheet tells us to wait at least 1ms before reading back
time.Sleep(time.Millisecond * 10) // conservative wait
return d.calculateEffectiveMaxAngle(address, value)
}
return nil
}
// GetMaxPosition returns the 'max position' (MPOS) in different units
func (d *AS5600Device) GetMaxPosition(units AngleUnit) (uint16, float32, error) {
mpos, err := d.ReadRegister(MPOS)
if nil != err {
return 0, 0.0, err
}
// Convert to requested units
i, f := convertFromNativeAngle(mpos, NATIVE_ANGLE_RANGE, units)
return i, f, nil
}
// SetMaxPosition sets the 'max position' (MPOS) in different units
func (d *AS5600Device) SetMaxPosition(mpos float32, units AngleUnit) error {
return d.WriteRegister(MPOS, convertToNativeAngle(mpos, units))
}
// GetMaxAngle returns the 'max position' (MANG) in different units
func (d *AS5600Device) GetMaxAngle(units AngleUnit) (uint16, float32, error) {
mang, err := d.ReadRegister(MANG)
if nil != err {
return 0, 0.0, err
}
// Convert to requested units
i, f := convertFromNativeAngle(mang, NATIVE_ANGLE_RANGE, units)
return i, f, nil
}
// SetMaxAngle sets the 'max angle' (MANG) in different units
func (d *AS5600Device) SetMaxAngle(mang float32, units AngleUnit) error {
return d.WriteRegister(MANG, convertToNativeAngle(mang, units))
}
-22
View File
@@ -1,22 +0,0 @@
// Product: https://ams.com/as5601
// Datasheet: https://ams.com/documents/20143/36005/AS5601_DS000395_3-00.pdf
package as560x // import tinygo.org/x/drivers/ams560x
import "tinygo.org/x/drivers"
// AS5601Device represents an ams AS5601 device driver accessed over I2C
type AS5601Device struct {
BaseDevice // promote base device
}
// NewAS5601 creates a new AS5601Device given an I2C bus
func NewAS5601(bus drivers.I2C) AS5601Device {
// Create base device
baseDev := newBaseDevice(bus)
// Add AS5601 specific registers
baseDev.registers[ABN] = newI2CRegister(ABN, 0, 0b1111, 1, reg_read|reg_write|reg_program)
baseDev.registers[PUSHTHR] = newI2CRegister(PUSHTHR, 0, 0xff, 1, reg_read|reg_write|reg_program)
// Return the device
return AS5601Device{baseDev}
}
-198
View File
@@ -1,198 +0,0 @@
// Package as560x implements drivers for the ams AS5600/AS5601 on-axis magnetic rotary position sensors
//
// Product Pages:
// AS5600: https://ams.com/as5600
// AS5601: https://ams.com/as5601
//
// Datasheets:
// AS5600: https://ams.com/documents/20143/36005/AS5600_DS000365_5-00.pdf
// AS5601: https://ams.com/documents/20143/36005/AS5601_DS000395_3-00.pdf
//
package as560x // import tinygo.org/x/drivers/ams560x
import (
"errors"
"tinygo.org/x/drivers"
)
// Config holds the configuration for the AMS AS560x sensor devices.
type Config struct {
// Address is the I2C address of the AS560x device. If left zero this will default to 0x36
Address uint8
}
// MagnetStrength is an enum to indicate the magnetic field strength detected by the AS560x sensors.
type MagnetStrength int
const (
// MagnetTooWeak indicates that the magnet strength is too weak (AGC maximum gain overflow) - move it closer
MagnetTooWeak MagnetStrength = iota - 1
// MagnetOk indicates that the magnet strength is about right.
MagnetOk
// MagnetTooStrong indicates that the magnet strength is too strong (AGC minimum gain overflow) - move it further away
MagnetTooStrong
)
// AngleUnit is an enum to allow the use of different units when reading/writing angles from the AS560x sensors.
type AngleUnit int
const (
// ANGLE_NATIVE uses the device's native angle measurement. i.e. 12-bit integer, 0 <= angle <= 0xfff (4095)
ANGLE_NATIVE AngleUnit = iota
// ANGLE_DEGREES_INT measures angles in degrees using integer arithmetic for speed. i.e. 0 <= angle < 360
ANGLE_DEGREES_INT
// ANGLE_DEGREES_FLOAT measures angles in degrees using floating point (slower). i.e. 0.0 <= angle < 360.0
ANGLE_DEGREES_FLOAT
// ANGLE_RADIANS measures angles in radians using floating point (slower). i.e. 0.0 <= angle < 2 * PI
ANGLE_RADIANS
)
const (
// NATIVE_ANGLE_MAX is the maximum valid value for a native angle for a AS560x device
NATIVE_ANGLE_MAX = (1 << 12) - 1 + iota
// NATIVE_ANGLE_RANGE is the number of unique values for native angles for a AS560x device
NATIVE_ANGLE_RANGE
)
var (
errRegisterNotFound = errors.New("Register not found")
errMaxBurnAngle = errors.New("Max BURN_ANGLE limit reached")
)
// BaseDevice handles the common behaviour between AS5600 & AS5601 devices
type BaseDevice struct {
bus drivers.I2C
address uint8
registers map[uint8]*i2cRegister
maxAngle uint16
}
// newBaseDevice creates a new base device given an I2C bus.
func newBaseDevice(bus drivers.I2C) BaseDevice {
// Add all 'base' registers, common to both AS5600 & AS5601
conf := newI2CRegister(CONF, 0, 0x3fff, 2, reg_read|reg_write|reg_program)
status := newI2CRegister(STATUS, 0, 0xff, 1, reg_read)
regs := map[uint8]*i2cRegister{
ZPOS: newI2CRegister(ZPOS, 0, 0xfff, 2, reg_read|reg_write|reg_program),
CONF: conf,
RAW_ANGLE: newI2CRegister(RAW_ANGLE, 0, 0xfff, 2, reg_read),
ANGLE: newI2CRegister(ANGLE, 0, 0xfff, 2, reg_read),
STATUS: status,
AGC: newI2CRegister(AGC, 0, 0xff, 1, reg_read),
MAGNITUDE: newI2CRegister(MAGNITUDE, 0, 0xfff, 2, reg_read),
BURN: newI2CRegister(BURN, 0, 0xff, 1, reg_write),
// Add common 'virtual registers' These are bitfields within the common registers above
// A virtual register provides a convenient way to access the fields of a registers
// by handling all of the necessary bitfield shifting and masking operations
WD: newVirtualRegister(conf, 13, 0b1),
FTH: newVirtualRegister(conf, 10, 0b111),
SF: newVirtualRegister(conf, 8, 0b11),
HYST: newVirtualRegister(conf, 2, 0b11),
PM: newVirtualRegister(conf, 0, 0b11),
MD: newVirtualRegister(status, 5, 0b1),
ML: newVirtualRegister(status, 4, 0b1),
MH: newVirtualRegister(status, 3, 0b1),
}
return BaseDevice{bus, DefaultAddress, regs, NATIVE_ANGLE_RANGE}
}
// Configure sets up the AMS AS560x sensor device with the given configuration.
func (d *BaseDevice) Configure(cfg Config) {
if cfg.Address == 0 {
cfg.Address = DefaultAddress
}
d.address = cfg.Address
}
// ReadRegister reads the value for the given register from the AS560x device via I2C
func (d *BaseDevice) ReadRegister(address uint8) (uint16, error) {
reg, ok := d.registers[address]
if !ok {
return 0, errRegisterNotFound
}
return reg.read(d.bus, d.address)
}
// WriteRegister writes the given value for the given register to the AS560x device via I2C
func (d *BaseDevice) WriteRegister(address uint8, value uint16) error {
reg, ok := d.registers[address]
if !ok {
return errRegisterNotFound
}
return reg.write(d.bus, d.address, value)
}
// GetZeroPosition returns the 'zero position' (ZPOS) in various units
func (d *BaseDevice) GetZeroPosition(units AngleUnit) (uint16, float32, error) {
zpos, err := d.ReadRegister(ZPOS)
if nil != err {
return 0, 0.0, err
}
// Convert to requested units
i, f := convertFromNativeAngle(zpos, NATIVE_ANGLE_RANGE, units)
return i, f, nil
}
// SetZeroPosition sets the 'zero position' (ZPOS) in various units
func (d *BaseDevice) SetZeroPosition(zpos float32, units AngleUnit) error {
return d.WriteRegister(ZPOS, convertToNativeAngle(zpos, units))
}
// RawAngle reads the (unscaled & unadjusted) RAW_ANGLE register in various units
func (d *BaseDevice) RawAngle(units AngleUnit) (uint16, float32, error) {
angle, err := d.ReadRegister(RAW_ANGLE)
if nil != err {
return 0, 0.0, err
}
// Convert to requested units
i, f := convertFromNativeAngle(angle, NATIVE_ANGLE_RANGE, units)
return i, f, nil
}
// Angle reads the (scaled & adjusted) ANGLE register in various units
func (d *BaseDevice) Angle(units AngleUnit) (uint16, float32, error) {
// ZPOS enables setting the 'zero position' of the device to any RAW_ANGLE value
// ANGLE is RAW_ANGLE adjusted relative to ZPOS.
angle, err := d.ReadRegister(ANGLE)
if nil != err {
return 0, 0.0, err
}
// Convert to requested units
i, f := convertFromNativeAngle(angle, d.maxAngle, units)
return i, f, nil
}
// MagnetStatus reads the STATUS register and reports magnet position characteristics
func (d *BaseDevice) MagnetStatus() (detected bool, strength MagnetStrength, err error) {
status, err := d.ReadRegister(STATUS)
if nil != err {
return false, MagnetOk, err
}
detected = (status & STATUS_MD) != 0
strength = MagnetOk
if (status & STATUS_ML) != 0 {
strength = MagnetTooWeak
} else if (status & STATUS_MH) != 0 {
strength = MagnetTooStrong
}
return
}
// Burn is a convenience method to program the device permanently by writing to the BURN register (limited number of times use!)
func (d *BaseDevice) Burn(burnCmd BURN_CMD) error {
if BURN_ANGLE == burnCmd {
// BURN_ANGLE can only be executed up to 3 times.
// We can check this in advance by reading ZMCO before writing to the BURN register.
numBurns, err := d.ReadRegister(ZMCO)
if nil != err {
return err
}
if numBurns >= BURN_ANGLE_COUNT_MAX {
// We're outta BURNs :(
return errMaxBurnAngle
}
}
return d.WriteRegister(BURN, uint16(burnCmd))
}
-95
View File
@@ -1,95 +0,0 @@
package as560x // import tinygo.org/x/drivers/ams560x
import "math"
// convertFromNativeAngle converts and scales an angle from the device's native 12-bit range to the requested units
func convertFromNativeAngle(angle uint16, maxAngle uint16, units AngleUnit) (uint16, float32) {
// MANG == 0 & MANG == NATIVE_ANGLE_RANGE (1 << 12) mean the same thing: use full circle range
// but the latter makes the maths/code simpler
if 0 == maxAngle {
maxAngle = NATIVE_ANGLE_RANGE
}
switch units {
case ANGLE_NATIVE:
// For native angles, scaling has already been done by the device
return angle, float32(angle)
case ANGLE_DEGREES_INT:
// Convert to degrees using integer arithmetic. Less accuracy but faster
var deg int = 0
if NATIVE_ANGLE_RANGE == maxAngle {
// Simplify the conversion when using the full range
deg = int(angle) * 360 >> 12
} else {
// Using an integer degrees scale with a narrower native range is pointless since we don't
// benefit at all from the increase in native resolution, in fact we LOSE precision.
// Alas, we have to return something
// First get maxAngle on the degrees scale
degMang, _ := convertFromNativeAngle(maxAngle, NATIVE_ANGLE_RANGE, units)
// Now scale angle
deg = int(angle) * int(degMang) / NATIVE_ANGLE_RANGE
}
return uint16(deg), float32(deg)
case ANGLE_DEGREES_FLOAT:
// Convert to degrees using floating point. More accuracy at expense of speed
var degF float32 = 0.0
if NATIVE_ANGLE_RANGE == maxAngle {
// Simplify the conversion when using the full range
degF = float32(angle) * 360.0 / NATIVE_ANGLE_RANGE
} else {
// Scale to degrees using a narrower native range
// First get maxAngle on the degrees scale
_, degMangF := convertFromNativeAngle(maxAngle, NATIVE_ANGLE_RANGE, units)
// Now scale angle
degF = float32(angle) * degMangF / NATIVE_ANGLE_RANGE
}
return uint16(degF), degF
case ANGLE_RADIANS:
// Convert to radians. Can only be done using floating point.
var rad float32 = 0.0
if NATIVE_ANGLE_RANGE == maxAngle {
// Simplify the conversion when using the full range
rad = float32(angle) * 2 * math.Pi / NATIVE_ANGLE_RANGE
} else {
// Scale to radians using a narrower native range
// First get maxAngle on the radians scale
_, radMang := convertFromNativeAngle(maxAngle, NATIVE_ANGLE_RANGE, units)
// Now scale angle
rad = float32(angle) * radMang / NATIVE_ANGLE_RANGE
}
return uint16(rad), rad
default:
panic("Unknown angle measurement unit")
}
}
// convertToNativeAngle converts an angle from the requested units to the device's native 12-bit range.
func convertToNativeAngle(angle float32, units AngleUnit) uint16 {
var pos uint16 = 0
switch units {
case ANGLE_NATIVE:
pos = uint16(angle)
case ANGLE_DEGREES_INT:
fallthrough
case ANGLE_DEGREES_FLOAT:
// Convert from degrees
angle = float32(math.Mod(float64(angle), 360.0))
if angle < 0.0 {
angle += 360.0
}
pos = uint16(math.Round(float64(angle) * NATIVE_ANGLE_RANGE / 360.0))
case ANGLE_RADIANS:
// Convert from radians
const circRad = 2.0 * math.Pi
angle = float32(math.Mod(float64(angle), circRad))
if angle < 0.0 {
angle += circRad
}
pos = uint16(math.Round(float64(angle) * NATIVE_ANGLE_RANGE / circRad))
default:
panic("Unknown angle measurement unit")
}
if pos > NATIVE_ANGLE_MAX {
pos = NATIVE_ANGLE_MAX
}
return pos
}
-170
View File
@@ -1,170 +0,0 @@
package as560x // import tinygo.org/x/drivers/ams560x
import (
"encoding/binary"
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// registerAttributes is a bitfield of attributes for a register
type registerAttributes uint8
const (
// reg_read indicates that the register is readable
reg_read registerAttributes = 1 << iota
// reg_write indicates that the register is writeable
reg_write
// reg_program indicates that the register can be permanently programmed ('BURNed')
reg_program
)
var (
errRegisterNotReadable = errors.New("Register is not readable")
errRegisterNotWriteable = errors.New("Register is not writeable")
)
// i2cRegister encapsulates the address, structure and read/write logic for a register on a AS560x device
type i2cRegister struct {
// host is the 'host register' for virtual registers. Physical/root registers have this set to self
host *i2cRegister
// address is the i2c address of the register. For 2-byte (word) addresses it's the low byte which holds the MSBs
address uint8
// shift is the number of bits the value is 'left shifted' into the register byte/word (0-15)
shift uint16
// mask is a bitwise mask applied to the register AFTER 'right shifting' to mask the register value
mask uint16
// num_bytes is the width of the register in bytes, 1 or 2.
num_bytes uint8
// attributes holds the register attributes. A bitfield of REG_xyz constants
attributes registerAttributes
// cached indicates whether we are holding a cached value of the register in value
cached bool
// value can be used as a 'cache' of the register's value for writeable registers.
value uint16
}
// newI2CRegister returns a pointer to a new i2cRegister with no cached value
func newI2CRegister(address uint8, shift uint16, mask uint16, num_bytes uint8, attributes registerAttributes) *i2cRegister {
reg := &i2cRegister{
address: address,
shift: shift,
mask: mask,
num_bytes: num_bytes,
attributes: attributes,
}
// root registers host themselves
reg.host = reg
return reg
}
// newVirtualRegister returns a pointer to a new i2cRegister with the given host register and shift/mask.
func newVirtualRegister(host *i2cRegister, shift uint16, mask uint16) *i2cRegister {
return &i2cRegister{
host: host,
address: host.address,
shift: shift,
mask: mask,
num_bytes: host.num_bytes,
attributes: host.attributes,
}
}
// invalidate invalidates any cached value for the register and forces an I2C read on the next read()
func (r *i2cRegister) invalidate() {
r.host.cached = false
r.host.value = 0
}
// readShiftAndMask is an internal method to read a value for the register over the given I2C bus from the device with the given address applying the given shift and mask
func (r *i2cRegister) readShiftAndMask(bus drivers.I2C, deviceAddress uint8, shift uint16, mask uint16) (uint16, error) {
if r.host.attributes&reg_read == 0 {
return 0, errRegisterNotReadable
}
// Only read over I2C if we don't have the host register value cached
var val uint16 = r.host.value
if !r.host.cached {
// To avoid an alloc we always use an array of 2 bytes
var buffer [2]byte
var buf []byte
if r.host.num_bytes < 2 {
buf = buffer[:1]
} else {
buf = buffer[:]
}
// Read the host register over I2C
err := legacy.ReadRegister(bus, deviceAddress, r.host.address, buf)
if nil != err {
return 0, err
}
// Unpack data from I2C
if r.host.num_bytes > 1 {
val = binary.BigEndian.Uint16(buf)
} else {
val = uint16(buf[0])
}
// cache this value if the host register is writeable. Note we cache the entire buffer without applying shift/mask
if r.host.attributes&reg_write != 0 {
r.host.value = val
r.host.cached = true
}
}
// Shift and mask the value before returning
val >>= shift
val &= mask
return val, nil
}
// read reads a value for the register over the given I2C bus from the device with the given address.
func (r *i2cRegister) read(bus drivers.I2C, deviceAddress uint8) (uint16, error) {
return r.readShiftAndMask(bus, deviceAddress, r.shift, r.mask)
}
// write writes a value for the register over the given I2C bus to the device with the given address.
func (r *i2cRegister) write(bus drivers.I2C, deviceAddress uint8, value uint16) error {
if r.host.attributes&reg_write == 0 {
return errRegisterNotWriteable
}
var newValue uint16 = 0
// Data sheet tells us to do a read first, modify only the desired bits and then write back
// since (quote:) 'Blank fields may contain factory settings'
// We will also need to do this anyway to support virtualRegister mappings on some registers
// (e.g. CONF/STATUS)
if (r.host.attributes & reg_read) > 0 { // not all registers are readable, e.g. BURN
// read the host register's entire host byte/word, regardless of shift & mask
readValue, error := r.readShiftAndMask(bus, deviceAddress, 0, 0xffff)
if error != nil {
return error
}
// Zero-out ONLY the relevant bits in newValue we just read
readValue &= (0xffff ^ (r.mask << r.shift))
newValue = readValue
}
// Mask the new value and shift it into place
value &= r.mask
value <<= r.shift
// OR the masked & shifted value back into newValue to be written
newValue |= value
// Pack newValue into a byte buffer to write. To avoid an alloc we always use an array of 2 bytes
var buffer [2]byte
var buf []byte
if r.host.num_bytes < 2 {
buf = buffer[:1]
buf[0] = uint8(newValue & 0xff)
} else {
buf = buffer[:]
binary.BigEndian.PutUint16(buf, newValue)
}
// Write the register from the buffer over I2C
err := legacy.WriteRegister(bus, deviceAddress, r.host.address, buf)
// after successful I2C write, cache this value if the host register (if also readable)
// Note we cache the entire buffer without applying shift/mask
if nil == err && r.host.attributes&reg_read != 0 {
r.host.value = newValue
r.host.cached = true
}
return err
}
-208
View File
@@ -1,208 +0,0 @@
package as560x // import tinygo.org/x/drivers/ams560x
// DefaultAddress is the default I2C address of the AMS AS560x sensors (0x36).
const DefaultAddress uint8 = 0x36
// AS560x common device registers
const (
// ZMCO contains the number of times a BURN_ANGLE command has been executed (max 3 burns)
ZMCO = 0x00
// ZPOS is the zero (start) position in RAW_ANGLE terms.
ZPOS = 0x01
// CONF supports custom config. Raw 14-bit register. See datasheet for mapping or use 'virtual registers' below.
CONF = 0x07
// STATUS indicates magnet position. Encapsulates MD, ML & MH. See also 'virtual registers' below.
STATUS = 0x0b
// RAW_ANGLE is the raw unscaled & unadjusted angle (12 bit: 0-4095/0xfff)
RAW_ANGLE = 0x0c
// ANGLE is RAW_ANGLE scaled & adjusted according to ZPOS (and MPOS/MANG on AS5600). (12 bit: 0-4095/0xfff)
ANGLE = 0x0e
// AGC is the Automatic Gain Control based on temp, airgap etc. 0-255 @ 5V, 0-128 @ 3.3V.
AGC = 0x1a
// MAGNITUDE indicates the magnitude value of the internal CORDIC output. See datasheet for more info.
MAGNITUDE = 0x1b
// BURN performs permanent programming of some registers. See BURN_XYZ cmd constants below for commands.
BURN = 0xff
)
// AS5600 specific registers
const (
// MPOS is the maximum position in RAW_ANGLE terms. With ZPOS, defines a 'narrower angle' for higher resolution.
MPOS = 0x03
// MANG is the maximum angle. With ZPOS, defines a 'narrower angle' for higher resolution.
MANG = 0x05
)
// AS5601 specific registers
const (
// ABN. See datasheet for mapping
ABN = 0x09
// PUSHTHR. Configures push-button function. See datasheet and AGC
PUSHTHR = 0x0a
)
// 'Virtual Registers' (VRs) are bitfields within the registers above.
// These are not real register addresses recognized by the chip,
// but they are recognized by the driver for convenience.
// virtualRegisterStartAddress defines the start of the virtual register address range.
const virtualRegisterStartAddress = 0xa0
const (
// VRs for CONF
// WD is a Virtual Register for the Watchdog timer. See WATCHDOG_TIMER consts.
WD = iota + virtualRegisterStartAddress
// FTH is a Virtual Register for the Fast Filter Threshold. See FAST_FILTER_THRESHOLD consts.
FTH
// SF is a Virtual Register for the Slow Filter. See SLOW_FILTER_RESPONSE consts.
SF
// PWMF is a Virtual Register for PWM Frequency (AS5600 ONLY). See PWM_FREQUENCY consts.
PWMF
// OUTS is a Virtual Register for the Output Stage (AS5600 ONLY). See OUTPUT_STAGE consts.
OUTS
// HYST is a Virtual Register for Hysteresis. See HYSTERESIS consts.
HYST
// PM is a Virtual Register for the Power Mode. See POWER_MODE consts.
PM
// VRs for STATUS (0 = unset, 1 = set)
// MD is a Virtual Register for the 'Magnet was detected' flag.
MD
// ML is a Virtual Register for the 'AGC maximum gain overflow' a.k.a 'magnet too weak' flag.
ML
// MH is a Virtual Register for the 'AGC minimum gain overflow' a.k.a 'magnet too strong' flag.
MH
)
// POWER_MODE values for the PM component of CONF (and the PM VR)
const (
// PM_NOM is the normal 'always on' power mode. No polling, max 6.5mA current
PM_NOM = iota
// PM_LPM1 is Low Power Mode 1. 5ms polling, max 3.4mA current
PM_LPM1
// PM_LPM2 is Low Power Mode 2. 20ms polling, max 1.8mA current
PM_LPM2
// PM_LPM3 is Low Power Mode 3. 100ms polling, max 1.5mA current
PM_LPM3
)
// HYSTERESIS values for the HYST component of CONF (and the HYST VR)
const (
// HYST_OFF disables any hysteresis of the output
HYST_OFF = iota
// HYST_1LSB enables output hysteresis using 1 LSB
HYST_1LSB
// HYST_2LSB enables output hysteresis using 2 LSBs
HYST_2LSB
// HYST_3LSB enables output hysteresis using 3 LSBs
HYST_3LSB
)
// OUTPUT_STAGE values for the OUTS component of CONF (and the OUTS VR - AS5600 ONLY)
const (
// OS_ANALOG_FULL_RANGE enables analog output with full range (0%-100% VDD)
OS_ANALOG_FULL_RANGE = iota
// OS_ANALOG_REDUCED_RANGE enables analog output with reduced range (10%-90% VDD)
OS_ANALOG_REDUCED_RANGE
// OS_DIGITAL_PWM enables digital PWM output. Frequency determined by PWMF
OS_DIGITAL_PWM
)
// PWM_FREQUENCY values for the PWMF component of CONF (and the PWMF VR - ASS5600 ONLY)
const (
// PWMF_115_HZ enables PWM at 115 Hz
PWMF_115_HZ = iota
// PWMF_230_HZ enables PWM at 230 Hz
PWMF_230_HZ
// PWMF_460_HZ enables PWM at 460 Hz
PWMF_460_HZ
// PWMF_920_HZ enables PWM at 920 Hz
PWMF_920_HZ
)
// SLOW_FILTER_RESPONSE values for the SF (slow filter) component of CONF (and the SF VR)
const (
// SF_16X enables a 16x Slow Filter step response
SF_16X = iota
// SF_8X enables a 8x Slow Filter step response
SF_8X
// SF_4X enables a 4x Slow Filter step response
SF_4X
// SF_2X enables a 2x Slow Filter step response
SF_2X
)
// FAST_FILTER_THRESHOLD values for the FTH (fast filter threshold) component of CONF (and the FTH VR)
const (
// FTH_NONE disables the fast filter (slow filter only)
FTH_NONE = iota
// FTH_6LSB enables a fast filter threshold with 6 LSBs
FTH_6LSB
// FTH_7LSB enables a fast filter threshold with 7 LSBs
FTH_7LSB
// FTH_9LSB enables a fast filter threshold with 9 LSBs
FTH_9LSB
// FTH_18LSB enables a fast filter threshold with 18 LSBs
FTH_18LSB
// FTH_21LSB enables a fast filter threshold with 21 LSBs
FTH_21LSB
// FTH_24LSB enables a fast filter threshold with 24 LSBs
FTH_24SB
// FTH_10LSB enables a fast filter threshold with 10 LSBs
FTH_10LSB
)
// WATCHDOG_TIMER values for the WD component of CONF (and the WD VR)
const (
// WD_OFF disables the Watchdog Timer
WD_OFF = iota
// WD_ON enables the Watchdog Timer (automatic entry into LPM3 low-power mode enabled)
WD_ON
)
// constants for the raw STATUS register bitfield value.
const (
// STATUS_MH is set in STATUS when the magnet field is too strong (AGC minimum gain overflow)
STATUS_MH = 1 << (iota + 3)
// STATUS_ML is set in STATUS when the magnet field is too weak (AGC maximum gain overflow)
STATUS_ML
// STATUS_MD is set n STATUS when the magnet is detected. Doesn't seem to work with some units.
STATUS_MD
)
// ABN_MAPPING values for the ABN register (AS5601 ONLY)
const (
// ABN_8 configures 8 output positions (61 Hz)
ABN_8 = iota
// ABN_16 configures 16 output positions (122 Hz)
ABN_16
// ABN_32 configures 32 output positions (244 Hz)
ABN_32
// ABN_64 configures 64 output positions (488 Hz)
ABN_64
// ABN_128 configures 128 output positions (976 Hz)
ABN_128
// ABN_256 configures 256 output positions (1.95 KHz)
ABN_256
// ABN_512 configures 512 output positions (3.9 KHz)
ABN_512
// ABN_1024 configures 1024 output positions (7.8 KHz)
ABN_1024
// ABN_2048 configures 2048 output positions (15.6 KHz)
ABN_2048
)
// BURN_CMD is a command to write to the BURN register.
type BURN_CMD uint16
const (
// BURN_ANGLE is the value to write to BURN to permanently program ZPOS & MPOS (Max 3 times!)
BURN_ANGLE BURN_CMD = 0x80
// BURN_SETTING is the value to write to BURN to permanently program MANG & CONF (ONCE ONLY!)
BURN_SETTING BURN_CMD = 0x40
)
// BURN_ANGLE_COUNT_MAX is a constant for the maximum number of times a BURN_ANGLE command can be executed. Compare this with ZMCO
const BURN_ANGLE_COUNT_MAX uint16 = 3
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"tinygo.org/x/drivers"
)
// Device wraps an I2C connection to an AT24CX device.
// Device wraps an I2C connection to a DS3231 device.
type Device struct {
bus drivers.I2C
Address uint16
+258
View File
@@ -0,0 +1,258 @@
// Package ateccx08 provides a driver for the ATECCx08 I2C cryptographic co-processor.
//
// Datasheet: https://datasheet.octopart.com/ATSAMA5D27-WLSOM1-Microchip-datasheet-149595509.pdf
package ateccx08 // import "tinygo.org/x/drivers/ateccx08"
import (
"errors"
"time"
"tinygo.org/x/drivers"
)
var (
maxCommandTime = (200 + 50) * time.Millisecond
)
var (
ErrWakeup = errors.New("error on wakeup")
ErrInvalidCRCCheck = errors.New("invalid CRC check")
ErrLockFailed = errors.New("locked failed")
)
type Device struct {
bus drivers.I2C
Address uint8
}
// New returns ATECCx08 device for the provided I2C bus using default address.
func New(i2c drivers.I2C) *Device {
return &Device{
bus: i2c,
Address: Address,
}
}
// Configure the ATECCx08 device.
func (d *Device) Configure() error {
return nil
}
// Connected returns whether ATECCx08 has been found.
func (d *Device) Connected() bool {
if err := d.Wakeup(); err != nil {
return false
}
v, err := d.Version()
if err != nil {
return false
}
return (v == ATECC508 || v == ATECC608)
}
// Wakeup the ATECC by trying to write something to address 0x00
func (d *Device) Wakeup() error {
d.bus.Tx(uint16(0x0), []byte{0x00}, nil)
time.Sleep(1500 * time.Microsecond)
d.bus.Tx(uint16(d.Address), []byte{0x00}, nil)
time.Sleep(maxCommandTime)
var status [4]byte
if err := d.readResponse(status[:]); err != nil {
return err
}
if status[0] != StatusAfterWake {
return ErrWakeup
}
return nil
}
// Sleep puts the ATECC to sleep.
func (d *Device) Sleep() {
d.bus.Tx(uint16(d.Address), []byte{0x01}, nil)
time.Sleep(time.Millisecond)
}
// Idle puts the ATECC in idle mode.
func (d *Device) Idle() {
d.bus.Tx(uint16(d.Address), []byte{0x02}, nil)
time.Sleep(time.Millisecond)
}
type ATECCVersion uint16
func (at ATECCVersion) String() string {
switch at {
case ATECC508:
return "ATECC508"
case ATECC608:
return "ATECC608"
case ATECCNone:
return "No ATECCx08"
default:
return "Unknown"
}
}
// Version returns what version of ATECC is being used.
// Either ATECC508, ATECC608, or ATECCNone.
func (d *Device) Version() (ATECCVersion, error) {
var version [4]byte
d.Wakeup()
defer d.Idle()
d.sendCommand(cmdInfo, 0x00, 0, nil)
time.Sleep(maxCommandTime)
if err := d.readResponse(version[:]); err != nil {
return ATECCNone, err
}
return ATECCVersion(uint16(version[2])<<8 | uint16(version[3])&0xf000), nil
}
// Random returns an array of 32 byte-sized random numbers.
func (d *Device) Random() ([32]byte, error) {
var random [32]byte
d.Wakeup()
defer d.Idle()
d.sendCommand(cmdRandom, 0x00, 0, nil)
time.Sleep(23 * time.Millisecond)
err := d.readResponse(random[:])
return random, err
}
// Read reads from the device memory.
func (d *Device) Read(zone, address int, data []byte) error {
d.Wakeup()
defer d.Idle()
d.sendCommand(cmdRead, byte(zone), uint16(address), nil)
time.Sleep(5 * time.Millisecond)
return d.readResponse(data)
}
// IsLocked checks to see if the ATECC is locked.
// Config zone (0) must be locked to generate random numbers.
func (d *Device) IsLocked() bool {
return d.IsZoneLocked(0)
}
// IsZoneLocked checks to see if a specific zone in the ATECC is locked.
func (d *Device) IsZoneLocked(zone int) bool {
var config [4]byte
if zone < 0 || zone > 8 {
return false
}
switch zone {
case 0, 1:
if err := d.Read(0, 0x15, config[:]); err != nil {
return false
}
// LockConfig
loc := 3
// LockData
if zone == 1 {
loc = 2
}
if config[loc] == 0 {
return true
}
default:
if err := d.Read(0, 0x16, config[:]); err != nil {
return false
}
slot := byte(zone<<2) | 2
if (config[0] & slot) == 0 {
return true
}
return false
}
return false
}
// Lock locks a zone in the device.
// Note that you cannot unlock a device zone once locked,
// so make sure you know what you are doing!
func (d *Device) Lock(zone int) error {
var status [1]byte
d.Wakeup()
defer d.Idle()
d.sendCommand(cmdLock, byte(zone)|0x80, 0, nil)
time.Sleep(32 * time.Millisecond)
d.readResponse(status[:])
if status[0] != 0 {
return ErrLockFailed
}
return nil
}
var cmdBuf [64]byte
func (d *Device) sendCommand(opcode, param1 byte, param2 uint16, data []byte) error {
cmdBuf[0] = 0x03
cmdBuf[1] = byte(8 + len(data) - 1)
cmdBuf[2] = opcode
cmdBuf[3] = param1
cmdBuf[4] = byte(param2 & 0xff)
cmdBuf[5] = byte(param2 >> 8)
copy(cmdBuf[6:], data)
crc := crc16(cmdBuf[1 : 6+len(data)])
cmdBuf[6+len(data)] = crc[0]
cmdBuf[6+len(data)+1] = crc[1]
if err := d.bus.Tx(uint16(d.Address), cmdBuf[:6+len(data)+2], nil); err != nil {
return err
}
time.Sleep(time.Millisecond)
return nil
}
func (d *Device) readResponse(data []byte) error {
var sz [1]byte
if err := d.bus.Tx(uint16(d.Address), []byte{cmdAddress}, sz[:]); err != nil {
return err
}
rx := make([]byte, sz[0])
if err := d.bus.Tx(uint16(d.Address), []byte{cmdAddress}, rx); err != nil {
return err
}
size := len(rx) - 2
payload := rx[:size]
payloaddata := rx[1:size]
payloadcrc := rx[size:]
crcCheck := crc16(payload)
if !(crcCheck[0] == payloadcrc[0] &&
crcCheck[1] == payloadcrc[1]) {
return ErrInvalidCRCCheck
}
copy(data, payloaddata)
return nil
}
+32
View File
@@ -0,0 +1,32 @@
// from https://github.com/usbarmory/armoryctl/blob/master/atecc608/atecc608.go#L104
// thank you!
package ateccx08
const (
CRC16Poly uint16 = 0x8005
)
func crc16(data []byte) []byte {
var crc uint16
for i := 0; i < len(data); i++ {
for shift := uint8(0x01); shift > 0x00; shift <<= 1 {
// data and crc bits
var d uint8
var c uint8
if uint8(data[i])&uint8(shift) != 0 {
d = 1
}
c = uint8(crc >> 15)
crc <<= 1
if d != c {
crc ^= CRC16Poly
}
}
}
return []byte{byte(crc & 0xff), byte(crc >> 8)}
}
+39
View File
@@ -0,0 +1,39 @@
package ateccx08
const (
// Address is default I2C address.
Address = 0x60
)
const (
ATECCNone = 0
ATECC508 = 0x5000
ATECC608 = 0x6000
)
const (
cmdAddress = 0x03
cmdCounter = 0x24
cmdGenKey = 0x40
cmdInfo = 0x30
cmdLock = 0x17
cmdNonce = 0x16
cmdRandom = 0x1B
cmdSHA = 0x47
cmdSign = 0x41
cmdWrite = 0x12
cmdRead = 0x02
)
const (
StatusSuccess = 0x00
StatusMiscompare = 0x01
StatusParseError = 0x03
StatusECCFault = 0x05
StatusSelfTestError = 0x07
StatusHealthTestError = 0x08
StatusExecutionError = 0x0f
StatusAfterWake = 0x11
StatusWatchdogExpire = 0xee
StatusCRCError = 0xff
)
+2 -3
View File
@@ -7,7 +7,6 @@ package axp192 // import "tinygo.org/x/drivers/axp192"
import (
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
type Error uint8
@@ -249,10 +248,10 @@ func (d *Device) SetLDOEnable(number uint8, state bool) {
}
func (d *Device) write1Byte(reg, data uint8) {
legacy.WriteRegister(d.bus, d.Address, reg, []byte{data})
d.bus.WriteRegister(d.Address, reg, []byte{data})
}
func (d *Device) read8bit(reg uint8) uint8 {
legacy.ReadRegister(d.bus, d.Address, reg, d.buf[:1])
d.bus.ReadRegister(d.Address, reg, d.buf[:1])
return d.buf[0]
}
Binary file not shown.
Binary file not shown.
-352
View File
@@ -1,352 +0,0 @@
// Package bma42x provides a driver for the BMA421 and BMA425 accelerometer
// chips.
//
// Here is a reasonably good datasheet:
// https://datasheet.lcsc.com/lcsc/1912111437_Bosch-Sensortec-BMA425_C437656.pdf
//
// This driver was originally written for the PineTime, using the datasheet as a
// guide. There is an open source C driver provided by Bosch, but unfortunately
// it needs some small modifications to work with other chips (most importantly,
// the "config file").
// The InfiniTime and Wasp-OS drivers for this accelerometer have also been used
// to figure out some driver details (especially step counting).
package bma42x
import (
_ "embed"
"errors"
"reflect"
"time"
"unsafe"
"tinygo.org/x/drivers"
)
// Driver for BMA421 and BMA425:
// BMA421: https://files.pine64.org/doc/datasheet/pinetime/BST-BMA421-FL000.pdf
// BMA425: https://datasheet.lcsc.com/lcsc/1912111437_Bosch-Sensortec-BMA425_C437656.pdf
// This is the BMA421 firmware from the Wasp-OS project.
// It is identical to the so-called BMA423 firmware in InfiniTime, which I
// suspect to be actually a BMA421 firmware. I don't know where this firmware
// comes from or what the licensing status is.
// It has the FEATURES_IN command prepended, so that it can be written directly
// using I2C.Tx.
// Source: https://github.com/wasp-os/bma42x-upy/blob/master/BMA42X-Sensor-API/bma421.h
//
//go:embed bma421-config-waspos.bin
var bma421Firmware string
// Same as the BMA421 firmware, but for the BMA425.
// Source: https://github.com/wasp-os/bma42x-upy/blob/master/BMA42X-Sensor-API/bma425.h
//
//go:embed bma425-config-waspos.bin
var bma425Firmware string
var (
errUnknownDevice = errors.New("bma42x: unknown device")
errUnsupportedDevice = errors.New("bma42x: device not part of config")
errConfigMismatch = errors.New("bma42x: config mismatch")
errTimeout = errors.New("bma42x: timeout")
errInitFailed = errors.New("bma42x: failed to initialize")
)
const Address = 0x18 // BMA421/BMA425 address
type DeviceType uint8
const (
DeviceBMA421 DeviceType = 1 << iota
DeviceBMA425
AnyDevice = DeviceBMA421 | DeviceBMA425
noDevice DeviceType = 0
)
// Features to enable while configuring the accelerometer.
type Features uint8
const (
FeatureStepCounting = 1 << iota
)
type Config struct {
// Which devices to support (OR the device types together as needed).
Device DeviceType
// Which features to enable. With Features == 0, only the accelerometer will
// be enabled.
Features Features
}
type Device struct {
bus drivers.I2C
address uint8
accelData [6]byte
combinedTempSteps [5]uint8 // [0:3] steps, [4] temperature
dataBuf [2]byte
}
func NewI2C(i2c drivers.I2C, address uint8) *Device {
return &Device{
bus: i2c,
address: address,
}
}
func (d *Device) Connected() bool {
val, err := d.read1(_CHIP_ID)
return err == nil && identifyChip(val) != noDevice
}
func (d *Device) Configure(config Config) error {
if config.Device == 0 {
config.Device = AnyDevice
}
// Check chip ID, to check the connection and to determine which BMA42x
// device we're dealing with.
chipID, err := d.read1(_CHIP_ID)
if err != nil {
return err
}
// Determine which firmware (config file?) we'll be using.
// There is an extra check for the device before using the given firmware.
// This check will typically be optimized away if the given device is not
// configured, so that the firmware (which is 6kB in size!) won't be linked
// into the binary.
var firmware string
switch identifyChip(chipID) {
case DeviceBMA421:
if config.Device&DeviceBMA421 == 0 {
return errUnsupportedDevice
}
firmware = bma421Firmware
case DeviceBMA425:
if config.Device&DeviceBMA425 == 0 {
return errUnsupportedDevice
}
firmware = bma425Firmware
default:
return errUnknownDevice
}
// Reset the chip, to be able to initialize it properly.
// The datasheet says a delay is needed after a SoftReset, but it doesn't
// say how long this delay should be. The bma423 driver however uses a 200ms
// delay, so that's what we'll be using.
err = d.write1(_CMD, cmdSoftReset)
if err != nil {
return err
}
time.Sleep(200 * time.Millisecond)
// Disable power saving.
err = d.write1(_PWR_CONF, 0x00)
if err != nil {
return err
}
time.Sleep(450 * time.Microsecond)
// Start initialization (because the datasheet says so).
err = d.write1(_INIT_CTRL, 0x00)
if err != nil {
return err
}
// Write "config file" (actually a firmware, I think) to the chip.
// To do this, unsafely cast the string to a byte slice to avoid putting it
// in RAM. This is safe in this case because Tx won't write to the 'w'
// slice.
err = d.bus.Tx(uint16(d.address), unsafeStringToSlice(firmware), nil)
if err != nil {
return err
}
// Read the config data back.
// We don't do that, as it slows down configuration and it probably isn't
// _really_ necessary with a reasonably stable I2C bus.
if false {
data := make([]byte, len(firmware)-1)
err = d.readn(_FEATURES_IN, data)
if err != nil {
return err
}
for i, c := range data {
if firmware[i+1] != c {
return errConfigMismatch
}
}
}
// Enable sensors.
err = d.write1(_INIT_CTRL, 0x01)
if err != nil {
return err
}
// Wait until the device is initialized.
start := time.Now()
status := uint8(0) // busy
for status == 0 {
status, err = d.read1(_INTERNAL_STATUS)
if err != nil {
return err // I2C bus error.
}
if status > 1 {
// Expected either 0 ("not_init") or 1 ("init_ok").
return errInitFailed
}
if time.Since(start) >= 150*time.Millisecond {
// The datasheet says initialization should not take longer than
return errTimeout
}
// Don't bother the chip all the time while it's initializing.
time.Sleep(50 * time.Microsecond)
}
if config.Features&FeatureStepCounting != 0 {
// Enable step counter.
// TODO: support step counter parameters.
var buf [71]byte
buf[0] = _FEATURES_IN // prefix buf with the command
data := buf[1:]
err = d.readn(_FEATURES_IN, data)
if err != nil {
return err
}
data[0x3A+1] |= 0x10 // enable step counting by setting a magical bit
err = d.bus.Tx(uint16(d.address), buf[:], nil)
if err != nil {
return err
}
}
// Enable the accelerometer.
err = d.write1(_PWR_CTRL, 0x04)
if err != nil {
return err
}
// Configure accelerometer for low power usage:
// acc_perf_mode=0 (power saving enabled)
// acc_bwp=osr4_avg1 (no averaging)
// acc_odr=50Hz (50Hz sampling interval, enough for the step counter)
const accelConf = 0x00<<7 | 0x00<<4 | 0x07<<0
err = d.write1(_ACC_CONF, accelConf)
if err != nil {
return err
}
// Reduce current consumption.
// With power saving enabled (and the above ACC_CONF) the chip consumes only
// 14µA.
err = d.write1(_PWR_CONF, 0x03)
if err != nil {
return err
}
return nil
}
func (d *Device) Update(which drivers.Measurement) error {
// TODO: combine temperature and step counter into a single read.
if which&drivers.Temperature != 0 {
val, err := d.read1(_TEMPERATURE)
if err != nil {
return err
}
d.combinedTempSteps[4] = val
}
if which&drivers.Acceleration != 0 {
// The acceleration data is stored in DATA8 through DATA13 as 3 12-bit
// values.
err := d.readn(_DATA_8, d.accelData[:]) // ACC_X(LSB)
if err != nil {
return err
}
err = d.readn(_STEP_COUNTER_0, d.combinedTempSteps[:4])
if err != nil {
return err
}
}
return nil
}
// Temperature returns the last read temperature in celsius milli degrees (1°C
// is 1000).
func (d *Device) Temperature() int32 {
// The temperature value is a two's complement number (meaning: signed) in
// units of 1 kelvin, with 0 being 23°C.
return (int32(int8(d.combinedTempSteps[4])) + 23) * 1000
}
// Acceleration returns the last read acceleration in µg (micro-gravity).
// When one of the axes is pointing straight to Earth and the sensor is not
// moving the returned value will be around 1000000 or -1000000.
func (d *Device) Acceleration() (x, y, z int32) {
// Combine raw data from d.accelData (stored as 12-bit signed values) into a
// number (0..4095):
x = int32(d.accelData[0])>>4 | int32(d.accelData[1])<<4
y = int32(d.accelData[2])>>4 | int32(d.accelData[3])<<4
z = int32(d.accelData[4])>>4 | int32(d.accelData[5])<<4
// Sign extend this number to -2048..2047:
x = (x << 20) >> 20
y = (y << 20) >> 20
z = (z << 20) >> 20
// Scale from -512..511 to -1000_000..998_046.
// Or, at the maximum range (4g), from -2048..2047 to -2000_000..3998_046.
// The formula derived as follows (where 512 is the expected value at 1g):
// x = x * 1000_000 / 512
// x = x * (1000_000/64) / (512/64)
// x = x * 15625 / 8
x = x * 15625 / 8
y = y * 15625 / 8
z = z * 15625 / 8
return
}
// Steps returns the number of steps counted since the BMA42x sensor was
// initialized.
func (d *Device) Steps() (steps uint32) {
steps |= uint32(d.combinedTempSteps[0]) << 0
steps |= uint32(d.combinedTempSteps[1]) << 8
steps |= uint32(d.combinedTempSteps[2]) << 16
steps |= uint32(d.combinedTempSteps[3]) << 24
return
}
func (d *Device) read1(register uint8) (uint8, error) {
d.dataBuf[0] = register
err := d.bus.Tx(uint16(d.address), d.dataBuf[:1], d.dataBuf[1:2])
return d.dataBuf[1], err
}
func (d *Device) readn(register uint8, data []byte) error {
d.dataBuf[0] = register
return d.bus.Tx(uint16(d.address), d.dataBuf[:1], data)
}
func (d *Device) write1(register uint8, data uint8) error {
d.dataBuf[0] = register
d.dataBuf[1] = data
return d.bus.Tx(uint16(d.address), d.dataBuf[:2], nil)
}
func unsafeStringToSlice(s string) []byte {
// TODO: use unsafe.Slice(unsafe.StringData(...)) once we require Go 1.20.
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
return unsafe.Slice((*byte)(unsafe.Pointer(sh.Data)), len(s))
}
func identifyChip(chipID uint8) DeviceType {
switch chipID {
case 0x11:
return DeviceBMA421
case 0x13:
return DeviceBMA425
default:
return noDevice
}
}
-73
View File
@@ -1,73 +0,0 @@
package bma42x
const (
// I2C registers
_CHIP_ID = 0x00
_ERR_REG = 0x02
_STATUS = 0x03
_DATA_0 = 0x0A
_DATA_1 = 0x0B
_DATA_2 = 0x0C
_DATA_3 = 0x0D
_DATA_4 = 0x0E
_DATA_5 = 0x0F
_DATA_6 = 0x10
_DATA_7 = 0x11
_DATA_8 = 0x12
_DATA_9 = 0x13
_DATA_10 = 0x14
_DATA_11 = 0x15
_DATA_12 = 0x16
_DATA_13 = 0x17
_SENSORTIME_0 = 0x18
_SENSORTIME_1 = 0x19
_SENSORTIME_2 = 0x1A
_EVENT = 0x1B
_INT_STATUS_0 = 0x1C
_INT_STATUS_1 = 0x1D
_STEP_COUNTER_0 = 0x1E
_STEP_COUNTER_1 = 0x1F
_STEP_COUNTER_2 = 0x20
_STEP_COUNTER_3 = 0x21
_TEMPERATURE = 0x22
_FIFO_LENGTH_0 = 0x24
_FIFO_LENGTH_1 = 0x25
_FIFO_DATA = 0x26
_ACTIVITY_TYPE = 0x27
_INTERNAL_STATUS = 0x2A
_ACC_CONF = 0x40
_ACC_RANGE = 0x41
_AUX_CONF = 0x44
_FIFO_DOWNS = 0x45
_FIFO_WTM_0 = 0x46
_FIFO_WTM_1 = 0x47
_FIFO_CONFIG_0 = 0x48
_FIFO_CONFIG_1 = 0x49
_AUX_DEV_ID = 0x4B
_AUX_IF_CONF = 0x4C
_AUX_RD_ADDR = 0x4D
_AUX_WR_ADDR = 0x4E
_AUX_WR_DATA = 0x4F
_INT1_IO_CTRL = 0x53
_INT2_IO_CTRL = 0x54
_INT_LATCH = 0x55
_INT1_MAP = 0x56
_INT2_MAP = 0x57
_INT_MAP_DATA = 0x58
_INIT_CTRL = 0x59
_FEATURES_IN = 0x5E
_INTERNAL_ERROR = 0x5F
_NVM_CONF = 0x6A
_IF_CONF = 0x6B
_ACC_SELF_TEST = 0x6D
_NV_CONF = 0x70
_OFFSET_0 = 0x71
_OFFSET_1 = 0x72
_OFFSET_2 = 0x73
_PWR_CONF = 0x7C
_PWR_CTRL = 0x7D
_CMD = 0x7E
// Commands send to regCommand.
cmdSoftReset = 0xB6
)
+11 -12
View File
@@ -10,7 +10,6 @@ import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// calibrationCoefficients reads at startup and stores the calibration coefficients
@@ -99,19 +98,19 @@ func (d *Device) ConfigureWithSettings(config Config) {
}
var data [24]byte
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION, data[:])
err := d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION, data[:])
if err != nil {
return
}
var h1 [1]byte
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION_H1, h1[:])
err = d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION_H1, h1[:])
if err != nil {
return
}
var h2lsb [7]byte
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION_H2LSB, h2lsb[:])
err = d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION_H2LSB, h2lsb[:])
if err != nil {
return
}
@@ -138,12 +137,12 @@ func (d *Device) ConfigureWithSettings(config Config) {
d.Reset()
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_CONFIG, []byte{byte(d.Config.Period<<5) | byte(d.Config.IIR<<2)})
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_HUMIDITY_ADDR, []byte{byte(d.Config.Humidity)})
d.bus.WriteRegister(uint8(d.Address), CTRL_CONFIG, []byte{byte(d.Config.Period<<5) | byte(d.Config.IIR<<2)})
d.bus.WriteRegister(uint8(d.Address), CTRL_HUMIDITY_ADDR, []byte{byte(d.Config.Humidity)})
// Normal mode, start measuring now
if d.Config.Mode == ModeNormal {
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
d.bus.WriteRegister(uint8(d.Address), CTRL_MEAS_ADDR, []byte{
byte(d.Config.Temperature<<5) |
byte(d.Config.Pressure<<2) |
byte(d.Config.Mode)})
@@ -154,13 +153,13 @@ func (d *Device) ConfigureWithSettings(config Config) {
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
return data[0] == CHIP_ID
}
// Reset the device
func (d *Device) Reset() {
legacy.WriteRegister(d.bus, uint8(d.Address), CMD_RESET, []byte{0xB6})
d.bus.WriteRegister(uint8(d.Address), CMD_RESET, []byte{0xB6})
}
// SetMode can set the device to Sleep, Normal or Forced mode
@@ -171,7 +170,7 @@ func (d *Device) Reset() {
func (d *Device) SetMode(mode Mode) {
d.Config.Mode = mode
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
d.bus.WriteRegister(uint8(d.Address), CTRL_MEAS_ADDR, []byte{
byte(d.Config.Temperature<<5) |
byte(d.Config.Pressure<<2) |
byte(d.Config.Mode)})
@@ -253,7 +252,7 @@ func readIntLE(msb byte, lsb byte) int16 {
func (d *Device) readData() (data [8]byte, err error) {
if d.Config.Mode == ModeForced {
// Write the CTRL_MEAS register to trigger a measurement
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
d.bus.WriteRegister(uint8(d.Address), CTRL_MEAS_ADDR, []byte{
byte(d.Config.Temperature<<5) |
byte(d.Config.Pressure<<2) |
byte(d.Config.Mode)})
@@ -261,7 +260,7 @@ func (d *Device) readData() (data [8]byte, err error) {
time.Sleep(d.measurementDelay())
}
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_PRESSURE, data[:])
err = d.bus.ReadRegister(uint8(d.Address), REG_PRESSURE, data[:])
if err != nil {
println(err)
return
+24 -31
View File
@@ -1,36 +1,31 @@
package bmi160
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
// DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is
// also an I2C interface, but it is not yet supported.
type DeviceSPI struct {
// Chip select pin
csb pin.OutputFunc
CSB machine.Pin
buf [7]byte
// SPI bus (requires chip select to be usable).
bus drivers.SPI
configurePins func()
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 pin.Output, spi drivers.SPI) *DeviceSPI {
func NewSPI(csb machine.Pin, spi drivers.SPI) *DeviceSPI {
return &DeviceSPI{
csb: csb.Set, // chip select
bus: spi,
configurePins: func() {
legacy.ConfigurePinOut(csb)
},
CSB: csb, // chip select
Bus: spi,
}
}
@@ -38,11 +33,9 @@ func NewSPI(csb pin.Output, spi drivers.SPI) *DeviceSPI {
// configures the BMI160, but it does not configure the SPI interface (it is
// assumed to be up and running).
func (d *DeviceSPI) Configure() error {
if d.configurePins == nil {
return legacy.ErrConfigBeforeInstantiated
}
d.configurePins()
d.csb.High()
d.CSB.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.CSB.High()
// The datasheet recommends doing a register read from address 0x7F to get
// SPI communication going:
// > If CSB sees a rising edge after power-up, the BMI160 interface switches
@@ -93,9 +86,9 @@ func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
data[0] = 0x80 | reg_TEMPERATURE_0
data[1] = 0
data[2] = 0
d.csb.Low()
err = d.bus.Tx(data, data)
d.csb.High()
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
if err != nil {
return
}
@@ -130,9 +123,9 @@ func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
for i := 1; i < len(data); i++ {
data[i] = 0
}
d.csb.Low()
err = d.bus.Tx(data, data)
d.csb.High()
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
if err != nil {
return
}
@@ -160,9 +153,9 @@ func (d *DeviceSPI) ReadRotation() (x int32, y int32, z int32, err error) {
for i := 1; i < len(data); i++ {
data[i] = 0
}
d.csb.Low()
err = d.bus.Tx(data, data)
d.csb.High()
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
if err != nil {
return
}
@@ -208,9 +201,9 @@ func (d *DeviceSPI) readRegister(address uint8) uint8 {
data := d.buf[:2]
data[0] = 0x80 | address
data[1] = 0
d.csb.Low()
d.bus.Tx(data, data)
d.csb.High()
d.CSB.Low()
d.Bus.Tx(data, data)
d.CSB.High()
return data[1]
}
@@ -224,7 +217,7 @@ func (d *DeviceSPI) writeRegister(address, data uint8) {
buf[0] = address
buf[1] = data
d.csb.Low()
d.bus.Tx(buf, buf)
d.csb.High()
d.CSB.Low()
d.Bus.Tx(buf, buf)
d.CSB.High()
}
+6 -23
View File
@@ -6,11 +6,9 @@
package bmp180 // import "tinygo.org/x/drivers/bmp180"
import (
"math"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// OversamplingMode is the oversampling ratio of the pressure measurement.
@@ -56,7 +54,7 @@ func New(bus drivers.I2C) Device {
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
return data[0] == CHIP_ID
}
@@ -64,7 +62,7 @@ func (d *Device) Connected() bool {
// read the calibration coefficients.
func (d *Device) Configure() {
data := make([]byte, 22)
err := legacy.ReadRegister(d.bus, uint8(d.Address), AC1_MSB, data)
err := d.bus.ReadRegister(uint8(d.Address), AC1_MSB, data)
if err != nil {
return
}
@@ -125,27 +123,12 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
return 1000 * (p + ((x1 + x2 + 3791) >> 4)), nil
}
// ReadAltitude returns the current altitude in meters based on the
// current barometric pressure and estimated pressure at sea level.
// Calculation is based on code from Adafruit BME280 library
//
// https://github.com/adafruit/Adafruit_BME280_Library
func (d *Device) ReadAltitude() (int32, error) {
mPa, err := d.ReadPressure()
if err != nil {
return 0, err
}
atmP := float32(mPa) / 100000
return int32(44330.0 * (1.0 - math.Pow(float64(atmP/SEALEVEL_PRESSURE), 0.1903))), nil
}
// rawTemp returns the sensor's raw values of the temperature
func (d *Device) rawTemp() (int32, error) {
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL, []byte{CMD_TEMP})
d.bus.WriteRegister(uint8(d.Address), REG_CTRL, []byte{CMD_TEMP})
time.Sleep(5 * time.Millisecond)
data := make([]byte, 2)
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_TEMP_MSB, data)
err := d.bus.ReadRegister(uint8(d.Address), REG_TEMP_MSB, data)
if err != nil {
return 0, err
}
@@ -161,10 +144,10 @@ func (d *Device) calculateB5(rawTemp int32) int32 {
// rawPressure returns the sensor's raw values of the pressure
func (d *Device) rawPressure(mode OversamplingMode) (int32, error) {
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL, []byte{CMD_PRESSURE + byte(mode<<6)})
d.bus.WriteRegister(uint8(d.Address), REG_CTRL, []byte{CMD_PRESSURE + byte(mode<<6)})
time.Sleep(pauseForReading(mode))
data := make([]byte, 3)
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_PRESSURE_MSB, data)
err := d.bus.ReadRegister(uint8(d.Address), REG_PRESSURE_MSB, data)
if err != nil {
return 0, err
}
-4
View File
@@ -28,7 +28,3 @@ const (
// ULTRAHIGHRESOLUTION is the highest oversampling mode of the pressure measurement.
ULTRAHIGHRESOLUTION
)
const (
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
)
+15 -15
View File
@@ -4,7 +4,6 @@ import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// OversamplingMode is the oversampling ratio of the temperature or pressure measurement.
@@ -23,7 +22,6 @@ type Filter uint
type Device struct {
bus drivers.I2C
Address uint16
buf [6]byte
cali calibrationCoefficients
Temperature Oversampling
Pressure Oversampling
@@ -66,14 +64,14 @@ func New(bus drivers.I2C) Device {
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := make([]byte, 1)
legacy.ReadRegister(d.bus, uint8(d.Address), REG_ID, data)
d.bus.ReadRegister(uint8(d.Address), REG_ID, data)
return data[0] == CHIP_ID
}
// Reset preforms complete power-on-reset procedure.
// It is required to call Configure afterwards.
func (d *Device) Reset() {
legacy.WriteRegister(d.bus, uint8(d.Address), REG_RESET, []byte{CMD_RESET})
d.bus.WriteRegister(uint8(d.Address), REG_RESET, []byte{CMD_RESET})
}
// Configure sets up the device for communication and
@@ -87,15 +85,15 @@ func (d *Device) Configure(standby Standby, filter Filter, temp Oversampling, pr
// Write the configuration (standby, filter, spi 3 wire)
config := uint(d.Standby<<5) | uint(d.Filter<<2) | 0x00
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONFIG, []byte{byte(config)})
d.bus.WriteRegister(uint8(d.Address), REG_CONFIG, []byte{byte(config)})
// Write the control (temperature oversampling, pressure oversampling,
config = uint(d.Temperature<<5) | uint(d.Pressure<<2) | uint(d.Mode)
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)})
d.bus.WriteRegister(uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)})
// Read Calibration data
data := make([]byte, 24)
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALI, data)
err := d.bus.ReadRegister(uint8(d.Address), REG_CALI, data)
if err != nil {
return
}
@@ -135,8 +133,8 @@ func (d *Device) PrintCali() {
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000).
func (d *Device) ReadTemperature() (temperature int32, err error) {
data := d.buf[:3]
if err = d.readData(REG_TEMP, data); err != nil {
data, err := d.readData(REG_TEMP, 3)
if err != nil {
return
}
@@ -159,8 +157,8 @@ func (d *Device) ReadTemperature() (temperature int32, err error) {
// ReadPressure returns the pressure in milli pascals (mPa).
func (d *Device) ReadPressure() (pressure int32, err error) {
// First 3 bytes are Pressure, last 3 bytes are Temperature
data := d.buf[:6]
if err = d.readData(REG_PRES, data); err != nil {
data, err := d.readData(REG_PRES, 6)
if err != nil {
return
}
@@ -204,22 +202,24 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
}
// readData reads n number of bytes of the specified register
func (d *Device) readData(register int, data []byte) error {
func (d *Device) readData(register int, n int) ([]byte, error) {
// If not in normal mode, set the mode to FORCED mode, to prevent incorrect measurements
// After the measurement in FORCED mode, the sensor will return to SLEEP mode
if d.Mode != MODE_NORMAL {
config := uint(d.Temperature<<5) | uint(d.Pressure<<2) | uint(MODE_FORCED)
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)})
d.bus.WriteRegister(uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)})
}
// Check STATUS register, wait if data is not available yet
status := make([]byte, 1)
for legacy.ReadRegister(d.bus, uint8(d.Address), uint8(REG_STATUS), status[0:]); status[0] != 4 && status[0] != 0; legacy.ReadRegister(d.bus, uint8(d.Address), uint8(REG_STATUS), status[0:]) {
for d.bus.ReadRegister(uint8(d.Address), uint8(REG_STATUS), status[0:]); status[0] != 4 && status[0] != 0; d.bus.ReadRegister(uint8(d.Address), uint8(REG_STATUS), status[0:]) {
time.Sleep(time.Millisecond)
}
// Read the requested register
return legacy.ReadRegister(d.bus, uint8(d.Address), uint8(register), data[:])
data := make([]byte, n)
err := d.bus.ReadRegister(uint8(d.Address), uint8(register), data[:])
return data, err
}
// convert3Bytes converts three bytes to int32
+2 -3
View File
@@ -4,7 +4,6 @@ import (
"errors"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
var (
@@ -241,10 +240,10 @@ func (d *Device) configurationError() bool {
func (d *Device) readRegister(register byte, len int) (data []byte, err error) {
data = make([]byte, len)
err = legacy.ReadRegister(d.bus, d.Address, register, data)
err = d.bus.ReadRegister(d.Address, register, data)
return
}
func (d *Device) writeRegister(register byte, data byte) error {
return legacy.WriteRegister(d.bus, d.Address, register, []byte{data})
return d.bus.WriteRegister(d.Address, register, []byte{data})
}
-256
View File
@@ -1,256 +0,0 @@
// Package bno08x provides a TinyGo driver for the Adafruit BNO08x 9-DOF IMU sensors.
//
// This driver implements the CEVA SH-2 protocol over the SHTP transport layer,
// providing access to orientation, motion, and environmental sensors.
//
// Datasheet: https://www.ceva-ip.com/wp-content/uploads/BNO080_085-Datasheet.pdf
package bno08x
import (
"time"
"tinygo.org/x/drivers/internal/pin"
)
// Buser is the interface that wraps I2C or SPI bus operations.
type Buser interface {
configure(address uint16, readChunk int) error
read(target []byte) (int, uint32, error)
write(data []byte) error
softReset() error
}
// Device represents a BNO08x sensor device.
type Device struct {
bus Buser
resetPin pin.OutputFunc
hal *hal
shtp *shtp
sh2 *sh2Protocol
queue [8]SensorValue
queueHead int
queueTail int
queueCount int
productIDs ProductIDs
lastReset bool
}
// Config holds configuration options for the device.
type Config struct {
// Address is the I2C address (used only for I2C bus).
Address uint16
// ResetPin is the optional hardware reset pin.
ResetPin pin.OutputFunc
// ReadChunk is the I2C read chunk size (used only for I2C bus).
ReadChunk int
// StartupDelay is the delay after reset (default: 100ms).
StartupDelay time.Duration
}
// Configure initializes the sensor and prepares it for use.
func (d *Device) Configure(cfg Config) error {
// Configure bus-specific settings
if err := d.bus.configure(cfg.Address, cfg.ReadChunk); err != nil {
return err
}
if cfg.ResetPin != nil {
d.resetPin = cfg.ResetPin
}
if cfg.StartupDelay <= 0 {
cfg.StartupDelay = 100 * time.Millisecond
}
d.hal = newHAL(d)
d.shtp = newSHTP(d.hal)
d.sh2 = newSH2Protocol(d)
d.queueHead = 0
d.queueTail = 0
d.queueCount = 0
d.productIDs = ProductIDs{}
d.lastReset = false
if err := d.hal.open(); err != nil {
return err
}
// Now that handlers are registered, perform reset
// Try hardware reset first if available
if d.resetPin != nil {
d.hardwareReset()
time.Sleep(cfg.StartupDelay)
} else {
// No hardware reset pin - try soft reset via bus
if err := d.bus.softReset(); err != nil {
// If that fails, try soft reset via SHTP protocol
_ = d.sh2.softReset()
time.Sleep(50 * time.Millisecond)
}
}
// Wait for reset notification by actively polling
// The sensor should send reset complete message shortly after reset
deadline := time.Now().Add(1000 * time.Millisecond)
pollCount := 0
for time.Now().Before(deadline) {
pollCount++
if err := d.service(); err != nil {
// Ignore errors during initial polling - sensor might not be ready
time.Sleep(1 * time.Millisecond)
continue
}
if d.lastReset {
break
}
time.Sleep(1 * time.Millisecond)
}
if !d.lastReset {
return errTimeout
}
// NOTE: We intentionally skip the Initialize command (sh2_initialize)
// Testing revealed that sending the Initialize command (0xF2 0x00 0x04 0x01...)
// prevents the BNO08x from sending sensor reports on channel 3.
// The sensor works correctly without this command after a soft reset.
// The Arduino library likely works because it does a hardware reset which
// may put the sensor in a different state, or their initialization sequence
// differs in a way that doesn't trigger this issue.
// Request product IDs
if err := d.sh2.requestProductIDs(); err != nil {
return err
}
// Wait for product IDs with polling delay
deadline = time.Now().Add(500 * time.Millisecond)
for time.Now().Before(deadline) {
if err := d.service(); err != nil {
time.Sleep(10 * time.Millisecond)
continue
}
if d.productIDs.NumEntries > 0 {
break
}
time.Sleep(10 * time.Millisecond)
}
if d.productIDs.NumEntries == 0 {
return errTimeout
}
return nil
}
// EnableReport enables a specific sensor report at the given interval.
func (d *Device) EnableReport(id SensorID, intervalUs uint32) error {
err := d.sh2.enableReport(id, intervalUs)
if err != nil {
return err
}
// Poll a few times to let the sensor process the command
// and potentially send acknowledgment
for i := 0; i < 10; i++ {
_ = d.service()
time.Sleep(10 * time.Millisecond)
}
return nil
}
// GetSensorConfig retrieves the current configuration for a sensor.
func (d *Device) GetSensorConfig(id SensorID) (SensorConfig, error) {
return d.sh2.getSensorConfig(id)
}
// SetSensorConfig sets the configuration for a sensor.
func (d *Device) SetSensorConfig(id SensorID, config SensorConfig) error {
return d.sh2.setSensorConfig(id, config)
}
// WasReset returns true if the sensor signaled a reset since the last call.
func (d *Device) WasReset() bool {
if d.lastReset {
d.lastReset = false
return true
}
return false
}
// GetSensorEvent retrieves the next available sensor event if present.
func (d *Device) GetSensorEvent() (SensorValue, bool) {
if d.queueCount == 0 {
if err := d.service(); err != nil {
return SensorValue{}, false
}
if d.queueCount == 0 {
return SensorValue{}, false
}
}
value := d.queue[d.queueHead]
d.queueHead = (d.queueHead + 1) % len(d.queue)
d.queueCount--
return value, true
}
// ProductIDs returns the cached product identification information.
func (d *Device) ProductIDs() ProductIDs {
return d.productIDs
}
// Service processes pending sensor data.
// This is called automatically by GetSensorEvent but can be called manually
// for more control over timing.
func (d *Device) Service() error {
return d.service()
}
func (d *Device) enqueue(value SensorValue) {
next := (d.queueTail + 1) % len(d.queue)
if d.queueCount == len(d.queue) {
// Queue full, drop oldest
d.queueHead = (d.queueHead + 1) % len(d.queue)
d.queueCount--
}
d.queue[d.queueTail] = value
d.queueTail = next
d.queueCount++
}
func (d *Device) service() error {
if d.shtp == nil {
return nil
}
for {
processed, err := d.shtp.poll()
if err != nil {
return err
}
if !processed {
break
}
}
return nil
}
func (d *Device) hardwareReset() {
if d.resetPin == nil {
return
}
d.resetPin.High()
time.Sleep(10 * time.Millisecond)
d.resetPin.Low()
time.Sleep(10 * time.Millisecond)
d.resetPin.High()
time.Sleep(10 * time.Millisecond)
}
-173
View File
@@ -1,173 +0,0 @@
package bno08x
import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/pin"
)
// I2CConfig holds I2C-specific configuration options.
type I2CConfig struct {
// Address is the I2C address (default: 0x4A).
Address uint16
// ResetPin is the optional hardware reset pin.
ResetPin pin.OutputFunc
// ReadChunk is the I2C read chunk size (default: 32 bytes).
ReadChunk int
}
const (
// DefaultAddress is the default I2C address.
DefaultAddress = 0x4A
)
// NewI2C creates a new BNO08x device using I2C communication.
func NewI2C(bus drivers.I2C) *Device {
return &Device{
bus: &I2CBus{
wire: bus,
address: DefaultAddress,
readChunk: i2cDefaultChunk,
},
}
}
// I2CBus implements the Buser interface for I2C communication.
type I2CBus struct {
wire drivers.I2C
address uint16
readChunk int
scratch []byte
header [shtpHeaderLength]byte
}
// configure sets up the I2C bus with the specified address and chunk size.
func (b *I2CBus) configure(address uint16, readChunk int) error {
if address != 0 {
b.address = address
}
if readChunk > 0 {
b.readChunk = readChunk
}
chunk := b.readChunk
if chunk < shtpHeaderLength {
chunk = shtpHeaderLength
}
b.scratch = make([]byte, chunk)
return nil
}
// read reads data from the I2C bus.
func (b *I2CBus) read(target []byte) (int, uint32, error) {
// Read SHTP header (4 bytes) to get packet length
// Use pre-allocated header buffer to avoid allocations
err := b.wire.Tx(b.address, nil, b.header[:])
if err != nil {
return 0, 0, err
}
// Parse packet length from header
packetLen := uint16(b.header[0]) | (uint16(b.header[1]) << 8)
// Check if continuation bit is set (0x8000)
// This means no data is available yet
if packetLen&continueMask != 0 {
return 0, 0, nil
}
// No continuation bit, check for actual data
if packetLen == 0 {
return 0, 0, nil
}
if int(packetLen) > len(target) {
return 0, 0, errBufferTooSmall
}
// Now read the full packet in chunks, re-reading the header in first chunk
// This follows Arduino's approach: initial header read is just to get size,
// actual packet data (including header) is read in the loop
cargoRemaining := int(packetLen)
offset := 0
firstRead := true
for cargoRemaining > 0 {
var request int
if firstRead {
// First read: get the full packet including header (up to chunkSize)
request = b.readChunk
if request > cargoRemaining {
request = cargoRemaining
}
} else {
// Subsequent reads: each chunk has a 4-byte header we need to skip
request = b.readChunk
if request > cargoRemaining+shtpHeaderLength {
request = cargoRemaining + shtpHeaderLength
}
}
// Ensure scratch buffer is large enough
if request > len(b.scratch) {
b.scratch = make([]byte, request)
}
buf := b.scratch[:request]
// Read chunk
err = b.wire.Tx(b.address, nil, buf)
if err != nil {
return 0, 0, err
}
var cargoRead int
if firstRead {
// First read: copy everything including header
cargoRead = request
copy(target[offset:], buf[:cargoRead])
firstRead = false
} else {
// Subsequent reads: skip the 4-byte header
cargoRead = request - shtpHeaderLength
copy(target[offset:], buf[shtpHeaderLength:shtpHeaderLength+cargoRead])
}
offset += cargoRead
cargoRemaining -= cargoRead
}
// Extract timestamp from the header in the target buffer
timestamp := uint32(target[2]) | (uint32(target[3]) << 8)
return int(packetLen), timestamp, nil
}
// write sends data over the I2C bus.
func (b *I2CBus) write(data []byte) error {
return b.wire.Tx(b.address, data, nil)
}
// softReset sends a soft reset command via I2C.
func (b *I2CBus) softReset() error {
// Send soft reset packet via I2C as per Adafruit implementation
// Format: [length_low, length_high, channel, sequence, command]
// This is: 5 bytes total, channel 1 (executable), command 1 (reset)
softResetPacket := []byte{5, 0, 1, 0, 1}
// Try up to 5 times
var err error
for i := 0; i < 5; i++ {
err = b.wire.Tx(b.address, softResetPacket, nil)
if err == nil {
// Success - wait for sensor to process reset
time.Sleep(300 * time.Millisecond)
return nil
}
time.Sleep(30 * time.Millisecond)
}
return err
}
-179
View File
@@ -1,179 +0,0 @@
package bno08x
// I2C and protocol constants
const (
shtpHeaderLength = 4
maxTransferOut = 256
maxTransferIn = 384
i2cDefaultChunk = 32
continueMask = 0x8000
)
// SHTP channel numbers
const (
channelCommand = 0
channelExecutable = 1
channelControl = 2
channelSensorReport = 3
channelWakeReport = 4
channelGyroRV = 5
)
// SH-2 report IDs
const (
reportProdIDReq = 0xF9
reportProdIDResp = 0xF8
reportSetFeature = 0xFD
reportGetFeature = 0xFE
reportGetFeatureResp = 0xFC
reportCommandReq = 0xF2
reportCommandResp = 0xF1
reportFRSWriteReq = 0xF7
reportFRSWriteData = 0xF6
reportFRSReadReq = 0xF4
reportFRSReadResp = 0xF3
reportBaseTimestamp = 0xFB
reportTimestampReuse = 0xFA
reportForceFlush = 0xF0
reportFlushCompleted = 0xEF
reportResetReq = 0xF1
reportResetResp = 0xF0
)
// SH-2 commands
const (
cmdErrors = 0x01
cmdCounts = 0x02
cmdTare = 0x03
cmdInitialize = 0x04
cmdFRS = 0x05
cmdDCD = 0x06
cmdMECal = 0x07
cmdProdIDReq = 0x07
cmdDCDSave = 0x09
cmdGetOscType = 0x0A
cmdClearDCDReset = 0x0B
cmdCal = 0x0C
cmdBootloader = 0x0D
cmdInteractiveZRO = 0x0E
// Command parameters
initSystem = 0x01
initUnsolicited = 0x80
countsClearCounts = 0x01
countsGetCounts = 0x00
tareTareNow = 0x00
tarePersist = 0x01
tareSetReorientation = 0x02
calStart = 0x00
calFinish = 0x01
commandParamCount = 9
responseValueCount = 11
)
// Feature report flags
const (
featChangeSensitivityRelative = 0x01
featChangeSensitivityEnabled = 0x02
featWakeEnabled = 0x04
featAlwaysOnEnabled = 0x08
)
// Scaling factors for sensor data
// These are derived from the Q-point encoding in the SH-2 specification
const (
scaleQuat = 1.0 / 16384.0 // Q14
scaleAccel = 1.0 / 256.0 // Q8
scaleGyro = 1.0 / 512.0 // Q9
scaleMag = 1.0 / 16.0 // Q4
scaleAccuracy = 1.0 / 4096.0 // Q12
scalePressure = 1.0 / 1048576.0 // Q20
scaleLight = 1.0 / 256.0 // Q8
scaleHumidity = 1.0 / 256.0 // Q8
scaleProximity = 1.0 / 16.0 // Q4
scaleTemperature = 1.0 / 128.0 // Q7
scaleAngle = 1.0 / 16.0 // Q4
scaleHeartRate = 1.0 / 16.0 // Q4
)
// Activity classifier codes (extended beyond standard SH-2)
const (
ActivityUnknown = 0
ActivityInVehicle = 1
ActivityOnBicycle = 2
ActivityOnFoot = 3
ActivityStill = 4
ActivityTilting = 5
ActivityWalking = 6
ActivityRunning = 7
ActivityOnStairs = 8
ActivityOptionCount = 9
)
// Stability classifier values
const (
StabilityUnknown = 0
StabilityOnTable = 1
StabilityStationary = 2
StabilityStable = 3
StabilityMotion = 4
)
// Tap detector flags
const (
TapX = 0x01 // 1 - X axis tapped
TapXPos = 0x02 // 2 - X positive direction
TapY = 0x04 // 4 - Y axis tapped
TapYPos = 0x08 // 8 - Y positive direction
TapZ = 0x10 // 16 - Z axis tapped
TapZPos = 0x20 // 32 - Z positive direction
TapDouble = 0x40 // 64 - Double tap occurred
)
// GUID values for SHTP
const (
guidSHTP = 0
guidExecutable = 1
guidSensorHub = 2
)
// Advertisement tags
const (
tagNull = 0
tagGUID = 1
tagMaxCargoHeaderWrite = 2
tagMaxCargoHeaderRead = 3
tagMaxTransferWrite = 4
tagMaxTransferRead = 5
tagNormalChannel = 6
tagWakeChannel = 7
tagAppName = 8
tagChannelName = 9
tagAdvCount = 10
tagAppSpecific = 0x80
tagSH2Version = 0x80
tagSH2ReportLengths = 0x81
)
// Timeouts
const (
advertTimeout = 200000 // microseconds
commandTimeout = 300000 // microseconds
)
// Executable device commands
const (
execDeviceCmdReset = 1
execDeviceCmdOn = 2
execDeviceCmdSleep = 3
)
// Executable device responses
const (
execDeviceRespResetComplete = 1
)
-316
View File
@@ -1,316 +0,0 @@
package bno08x
import "encoding/binary"
// decodeSensor decodes a sensor report payload into a SensorValue.
func decodeSensor(payload []byte, timestamp uint32) (SensorValue, bool) {
if len(payload) < 4 {
return SensorValue{}, false
}
value := SensorValue{
id: SensorID(payload[0]),
sequence: payload[1],
status: payload[2] & 0x03,
delay: payload[3],
timestamp: uint64(timestamp),
}
data := payload[4:]
switch value.id {
case SensorRawAccelerometer:
if len(data) >= 10 {
value.rawAccelerometer = RawVector3{
X: int16(binary.LittleEndian.Uint16(data[0:])),
Y: int16(binary.LittleEndian.Uint16(data[2:])),
Z: int16(binary.LittleEndian.Uint16(data[4:])),
Timestamp: binary.LittleEndian.Uint32(data[6:]),
}
}
case SensorAccelerometer:
if len(data) >= 6 {
value.accelerometer = Vector3{
X: qToFloat(data[0:], scaleAccel),
Y: qToFloat(data[2:], scaleAccel),
Z: qToFloat(data[4:], scaleAccel),
}
}
case SensorLinearAcceleration:
if len(data) >= 6 {
value.linearAcceleration = Vector3{
X: qToFloat(data[0:], scaleAccel),
Y: qToFloat(data[2:], scaleAccel),
Z: qToFloat(data[4:], scaleAccel),
}
}
case SensorGravity:
if len(data) >= 6 {
value.gravity = Vector3{
X: qToFloat(data[0:], scaleAccel),
Y: qToFloat(data[2:], scaleAccel),
Z: qToFloat(data[4:], scaleAccel),
}
}
case SensorRawGyroscope:
if len(data) >= 12 {
value.rawGyroscope = RawGyroscope{
X: int16(binary.LittleEndian.Uint16(data[0:])),
Y: int16(binary.LittleEndian.Uint16(data[2:])),
Z: int16(binary.LittleEndian.Uint16(data[4:])),
Temperature: int16(binary.LittleEndian.Uint16(data[6:])),
Timestamp: binary.LittleEndian.Uint32(data[8:]),
}
}
case SensorGyroscope:
if len(data) >= 6 {
value.gyroscope = Vector3{
X: qToFloat(data[0:], scaleGyro),
Y: qToFloat(data[2:], scaleGyro),
Z: qToFloat(data[4:], scaleGyro),
}
}
case SensorGyroscopeUncalibrated:
if len(data) >= 12 {
value.gyroscopeUncal = GyroscopeUncalibrated{
X: qToFloat(data[0:], scaleGyro),
Y: qToFloat(data[2:], scaleGyro),
Z: qToFloat(data[4:], scaleGyro),
BiasX: qToFloat(data[6:], scaleGyro),
BiasY: qToFloat(data[8:], scaleGyro),
BiasZ: qToFloat(data[10:], scaleGyro),
}
}
case SensorRawMagnetometer:
if len(data) >= 10 {
value.rawMagnetometer = RawVector3{
X: int16(binary.LittleEndian.Uint16(data[0:])),
Y: int16(binary.LittleEndian.Uint16(data[2:])),
Z: int16(binary.LittleEndian.Uint16(data[4:])),
Timestamp: binary.LittleEndian.Uint32(data[6:]),
}
}
case SensorMagneticField:
if len(data) >= 6 {
value.magneticField = Vector3{
X: qToFloat(data[0:], scaleMag),
Y: qToFloat(data[2:], scaleMag),
Z: qToFloat(data[4:], scaleMag),
}
}
case SensorMagneticFieldUncalibrated:
if len(data) >= 12 {
value.magneticFieldUncal = MagneticFieldUncalibrated{
X: qToFloat(data[0:], scaleMag),
Y: qToFloat(data[2:], scaleMag),
Z: qToFloat(data[4:], scaleMag),
BiasX: qToFloat(data[6:], scaleMag),
BiasY: qToFloat(data[8:], scaleMag),
BiasZ: qToFloat(data[10:], scaleMag),
}
}
case SensorRotationVector:
if len(data) >= 10 {
value.quaternion = Quaternion{
I: qToFloat(data[0:], scaleQuat),
J: qToFloat(data[2:], scaleQuat),
K: qToFloat(data[4:], scaleQuat),
Real: qToFloat(data[6:], scaleQuat),
}
value.quaternionAccuracy = qToFloat(data[8:], scaleAccuracy)
}
case SensorGameRotationVector:
if len(data) >= 8 {
value.quaternion = Quaternion{
I: qToFloat(data[0:], scaleQuat),
J: qToFloat(data[2:], scaleQuat),
K: qToFloat(data[4:], scaleQuat),
Real: qToFloat(data[6:], scaleQuat),
}
}
case SensorGeomagneticRotationVector:
if len(data) >= 10 {
value.quaternion = Quaternion{
I: qToFloat(data[0:], scaleQuat),
J: qToFloat(data[2:], scaleQuat),
K: qToFloat(data[4:], scaleQuat),
Real: qToFloat(data[6:], scaleQuat),
}
value.quaternionAccuracy = qToFloat(data[8:], scaleAccuracy)
}
case SensorARVRStabilizedRV:
if len(data) >= 10 {
value.quaternion = Quaternion{
I: qToFloat(data[0:], scaleQuat),
J: qToFloat(data[2:], scaleQuat),
K: qToFloat(data[4:], scaleQuat),
Real: qToFloat(data[6:], scaleQuat),
}
value.quaternionAccuracy = qToFloat(data[8:], scaleAccuracy)
}
case SensorARVRStabilizedGRV:
if len(data) >= 8 {
value.quaternion = Quaternion{
I: qToFloat(data[0:], scaleQuat),
J: qToFloat(data[2:], scaleQuat),
K: qToFloat(data[4:], scaleQuat),
Real: qToFloat(data[6:], scaleQuat),
}
}
case SensorGyroIntegratedRV:
if len(data) >= 10 {
value.quaternion = Quaternion{
I: qToFloat(data[0:], scaleQuat),
J: qToFloat(data[2:], scaleQuat),
K: qToFloat(data[4:], scaleQuat),
Real: qToFloat(data[6:], scaleQuat),
}
// Angular velocity X at data[8:10]
}
case SensorPressure:
if len(data) >= 4 {
value.pressure = float32(int32(binary.LittleEndian.Uint32(data[0:]))) * scalePressure
}
case SensorAmbientLight:
if len(data) >= 4 {
value.ambientLight = float32(int32(binary.LittleEndian.Uint32(data[0:]))) * scaleLight
}
case SensorHumidity:
if len(data) >= 2 {
value.humidity = qToFloat(data[0:], scaleHumidity)
}
case SensorProximity:
if len(data) >= 2 {
value.proximity = qToFloat(data[0:], scaleProximity)
}
case SensorTemperature:
if len(data) >= 2 {
value.temperature = qToFloat(data[0:], scaleTemperature)
}
case SensorTapDetector:
if len(data) >= 1 {
value.tapDetector = TapDetector{
Flags: data[0],
}
}
case SensorStepDetector:
if len(data) >= 4 {
value.stepDetector = StepDetector{
Latency: binary.LittleEndian.Uint32(data[0:]),
}
}
case SensorStepCounter:
if len(data) >= 8 {
value.stepCounter = StepCounter{
Count: uint16(binary.LittleEndian.Uint32(data[4:8])),
Latency: binary.LittleEndian.Uint32(data[0:4]),
}
}
case SensorSignificantMotion:
if len(data) >= 2 {
value.significantMotion = SignificantMotion{
Motion: binary.LittleEndian.Uint16(data[0:]),
}
}
case SensorStabilityClassifier:
if len(data) >= 1 {
value.stabilityClassifier = StabilityClassifier{
Classification: data[0],
}
}
case SensorStabilityDetector:
if len(data) >= 1 {
value.stabilityDetector = data[0]
}
case SensorShakeDetector:
if len(data) >= 2 {
value.shakeDetector = ShakeDetector{
Shake: binary.LittleEndian.Uint16(data[0:]),
}
}
case SensorFlipDetector:
if len(data) >= 2 {
value.flipDetector = binary.LittleEndian.Uint16(data[0:2])
}
case SensorPickupDetector:
if len(data) >= 2 {
// Pickup detected at data[0:2]
}
case SensorPersonalActivityClassifier:
if len(data) >= 16 {
value.personalActivityClassifier = PersonalActivityClassifier{
Page: data[0],
MostLikelyState: data[1],
EndOfPage: data[15],
}
for i := 0; i < 10 && i+2 < len(data); i++ {
value.personalActivityClassifier.Confidence[i] = data[2+i]
}
}
case SensorSleepDetector:
if len(data) >= 1 {
value.sleepDetector = data[0]
}
case SensorTiltDetector:
if len(data) >= 1 {
value.tiltDetector = data[0]
}
case SensorPocketDetector:
if len(data) >= 1 {
value.pocketDetector = data[0]
}
case SensorCircleDetector:
if len(data) >= 1 {
value.circleDetector = data[0]
}
case SensorHeartRateMonitor:
if len(data) >= 2 {
value.heartRateMonitor = binary.LittleEndian.Uint16(data[0:])
}
}
return value, true
}
// qToFloat converts a Q-point fixed-point value to float32.
func qToFloat(data []byte, scale float32) float32 {
if len(data) < 2 {
return 0
}
return float32(int16(binary.LittleEndian.Uint16(data))) * scale
}
-43
View File
@@ -1,43 +0,0 @@
package bno08x
import (
"time"
)
// hal implements the hardware abstraction layer for bus communication.
type hal struct {
device *Device
}
func newHAL(dev *Device) *hal {
return &hal{
device: dev,
}
}
func (h *hal) open() error {
// HAL is now open and ready for communication
// Soft reset will be sent after handlers are registered
return nil
}
func (h *hal) close() {}
func (h *hal) read(target []byte) (int, uint32, error) {
return h.device.bus.read(target)
}
func (h *hal) write(frame []byte) (int, error) {
if len(frame) > maxTransferOut {
return 0, errFrameTooLarge
}
err := h.device.bus.write(frame)
if err != nil {
return 0, err
}
return len(frame), nil
}
func (h *hal) getTimeUs() uint32 {
return uint32(time.Now().UnixNano() / 1000)
}
-387
View File
@@ -1,387 +0,0 @@
// SH-2 specification found at https://www.ceva-ip.com/wp-content/uploads/SH-2-Reference-Manual.pdf
package bno08x
import (
"encoding/binary"
"time"
)
// getReportLen returns the length in bytes of a sensor report given its ID.
// Returns 0 for unknown report IDs.
func getReportLen(reportID byte) int {
switch reportID {
case 0xF1: // FLUSH_COMPLETED
return 6
case 0xFA: // TIMESTAMP_REBASE
return 5
case 0xFB: // BASE_TIMESTAMP_REF
return 5
case 0xFC: // GET_FEATURE_RESP
return 17
case 0x01: // Accelerometer (calibrated)
return 10
case 0x02: // Gyroscope (calibrated)
return 10
case 0x03: // Magnetic field (calibrated)
return 10
case 0x04: // Linear acceleration
return 10
case 0x05: // Rotation vector
return 14
case 0x06: // Gravity
return 10
case 0x07: // Gyroscope uncalibrated
return 16
case 0x08: // Game rotation vector
return 12
case 0x09: // Geomagnetic rotation vector
return 14
case 0x0A: // Pressure
return 10
case 0x0B: // Ambient light
return 10
case 0x0C: // Humidity
return 10
case 0x0D: // Proximity
return 10
case 0x0E: // Temperature
return 10
case 0x0F: // Magnetic field uncalibrated
return 16
case 0x10: // Tap detector
return 5
case 0x11: // Step counter
return 12
case 0x12: // Significant motion
return 6
case 0x13: // Stability classifier
return 5
case 0x14: // Raw accelerometer
return 16
case 0x15: // Raw gyroscope
return 16
case 0x16: // Raw magnetometer
return 16
case 0x18: // Step detector
return 8
case 0x19: // Shake detector
return 6
case 0x1A: // Flip detector
return 6
case 0x1B: // Pickup detector
return 6
case 0x1C: // Stability detector
return 6
case 0x1E: // Personal activity classifier
return 16
default:
// For most sensor reports, they are typically 10-16 bytes
// If we don't know the exact length, return a safe default
// that covers most cases (the handler will bounds-check)
if reportID < 0xF0 {
return 10 // Most sensor reports are at least this long
}
return 0
}
}
// sh2Protocol implements the Sensor Hub 2 (SH-2) application protocol.
type sh2Protocol struct {
device *Device
transport *shtp
cmdSeq uint8
waiting bool
lastCmd uint8
pendingConfigRequest bool
pendingConfigSensor SensorID
receivedConfig SensorConfig
configReady bool
configBuf [17]byte // Reusable buffer for setSensorConfig
commandBuf [3 + commandParamCount]byte // Reusable buffer for sendCommand
}
func newSH2Protocol(device *Device) *sh2Protocol {
proto := &sh2Protocol{
device: device,
transport: device.shtp,
}
// Register handlers for each channel
device.shtp.register(channelControl, proto.handleControl)
device.shtp.register(channelSensorReport, proto.handleSensor)
device.shtp.register(channelWakeReport, proto.handleSensor)
device.shtp.register(channelGyroRV, proto.handleSensor)
device.shtp.register(channelExecutable, proto.handleExecutable)
return proto
}
// softReset sends a software reset command to the sensor.
func (s *sh2Protocol) softReset() error {
payload := []byte{execDeviceCmdReset}
return s.transport.send(channelExecutable, payload)
}
// initialize sends the initialize command to the sensor.
func (s *sh2Protocol) initialize() error {
return s.sendCommand(cmdInitialize, []byte{initSystem})
}
// requestProductIDs requests product identification information.
func (s *sh2Protocol) requestProductIDs() error {
payload := []byte{reportProdIDReq, 0x00}
return s.transport.send(channelControl, payload)
}
// enableReport enables a sensor report at the specified interval.
func (s *sh2Protocol) enableReport(id SensorID, intervalUs uint32) error {
config := SensorConfig{
ReportInterval: intervalUs,
}
return s.setSensorConfig(id, config)
}
// getSensorConfig retrieves the configuration for a sensor.
// This method sends a GET_FEATURE request and waits for the response
// by polling the device. It will timeout after approximately 1 second.
func (s *sh2Protocol) getSensorConfig(id SensorID) (SensorConfig, error) {
// Mark that we're waiting for a config response
s.pendingConfigRequest = true
s.pendingConfigSensor = id
s.configReady = false
payload := []byte{reportGetFeature, byte(id)}
err := s.transport.send(channelControl, payload)
if err != nil {
s.pendingConfigRequest = false
return SensorConfig{}, err
}
// Poll for response with timeout
maxAttempts := 100 // ~1 second with 10ms delays
for i := 0; i < maxAttempts; i++ {
// Service the device to process incoming messages
s.device.shtp.poll()
if s.configReady {
s.pendingConfigRequest = false
s.configReady = false
return s.receivedConfig, nil
}
// Small delay between polls
time.Sleep(10 * time.Millisecond)
}
s.pendingConfigRequest = false
return SensorConfig{}, errTimeout
}
// setSensorConfig configures a sensor.
func (s *sh2Protocol) setSensorConfig(id SensorID, config SensorConfig) error {
// Use pre-allocated buffer to avoid allocations
payload := s.configBuf[:]
payload[0] = reportSetFeature
payload[1] = byte(id)
// Build feature flags
var flags uint8
if config.ChangeSensitivityEnabled {
flags |= featChangeSensitivityEnabled
}
if config.ChangeSensitivityRelative {
flags |= featChangeSensitivityRelative
}
if config.WakeupEnabled {
flags |= featWakeEnabled
}
if config.AlwaysOnEnabled {
flags |= featAlwaysOnEnabled
}
payload[2] = flags
binary.LittleEndian.PutUint16(payload[3:5], config.ChangeSensitivity)
binary.LittleEndian.PutUint32(payload[5:9], config.ReportInterval)
binary.LittleEndian.PutUint32(payload[9:13], config.BatchInterval)
binary.LittleEndian.PutUint32(payload[13:17], config.SensorSpecific)
return s.transport.send(channelControl, payload)
}
// sendCommand sends a command with parameters to the sensor.
func (s *sh2Protocol) sendCommand(command byte, params []byte) error {
// Use pre-allocated buffer to avoid allocations
payload := s.commandBuf[:]
payload[0] = reportCommandReq
payload[1] = s.cmdSeq
payload[2] = command
s.cmdSeq++
s.lastCmd = command
s.waiting = true
for i := 0; i < commandParamCount && i < len(params); i++ {
payload[3+i] = params[i]
}
return s.transport.send(channelControl, payload[:3+commandParamCount])
}
// handleControl processes control channel messages.
func (s *sh2Protocol) handleControl(payload []byte, timestamp uint32) {
if len(payload) == 0 {
return
}
reportID := payload[0]
switch reportID {
case reportProdIDResp:
s.handleProdID(payload, timestamp)
case reportCommandResp:
s.handleCommandResp(payload, timestamp)
case reportGetFeatureResp:
s.handleGetFeatureResp(payload, timestamp)
case reportFRSReadResp:
// FRS (Flash Record System) read response
// Not implemented in basic version
}
}
// handleProdID processes product ID responses.
func (s *sh2Protocol) handleProdID(payload []byte, timestamp uint32) {
if len(payload) < 16 {
return
}
entry := ProductID{
ResetCause: payload[1],
VersionMajor: payload[2],
VersionMinor: payload[3],
PartNumber: binary.LittleEndian.Uint32(payload[4:8]),
BuildNumber: binary.LittleEndian.Uint32(payload[8:12]),
VersionPatch: binary.LittleEndian.Uint16(payload[12:14]),
Reserved0: payload[14],
Reserved1: payload[15],
}
// Store in first slot
s.device.productIDs.Entries[0] = entry
s.device.productIDs.NumEntries = 1
}
// handleCommandResp processes command responses.
func (s *sh2Protocol) handleCommandResp(payload []byte, timestamp uint32) {
if len(payload) < 16 {
return
}
// seq := payload[1]
command := payload[2]
// commandSeq := payload[3]
// respSeq := payload[4]
// Check if this response is for our command
if s.waiting && command == s.lastCmd {
s.waiting = false
// Status is in payload[6]
// For now, we just acknowledge receipt
}
}
// handleGetFeatureResp processes get feature responses.
func (s *sh2Protocol) handleGetFeatureResp(payload []byte, timestamp uint32) {
if len(payload) < 17 {
return
}
// Parse the response
sensorID := SensorID(payload[1])
flags := payload[2]
changeSensitivity := binary.LittleEndian.Uint16(payload[3:5])
reportInterval := binary.LittleEndian.Uint32(payload[5:9])
batchInterval := binary.LittleEndian.Uint32(payload[9:13])
sensorSpecific := binary.LittleEndian.Uint32(payload[13:17])
// If we're waiting for this sensor's config, store it
if s.pendingConfigRequest && s.pendingConfigSensor == sensorID {
s.receivedConfig = SensorConfig{
ChangeSensitivityEnabled: flags&featChangeSensitivityEnabled != 0,
ChangeSensitivityRelative: flags&featChangeSensitivityRelative != 0,
WakeupEnabled: flags&featWakeEnabled != 0,
AlwaysOnEnabled: flags&featAlwaysOnEnabled != 0,
ChangeSensitivity: changeSensitivity,
ReportInterval: reportInterval,
BatchInterval: batchInterval,
SensorSpecific: sensorSpecific,
}
s.configReady = true
}
}
// handleSensor processes sensor report messages.
// The payload can contain multiple sensor reports batched together.
func (s *sh2Protocol) handleSensor(payload []byte, timestamp uint32) {
cursor := 0
var referenceDelta uint32
for cursor < len(payload) {
if cursor >= len(payload) {
break
}
reportID := payload[cursor]
reportLen := getReportLen(reportID)
if reportLen == 0 {
// Unknown report ID
break
}
if cursor+reportLen > len(payload) {
// Not enough data for this report
break
}
// Handle special report types
switch reportID {
case 0xFB: // SENSORHUB_BASE_TIMESTAMP_REF
if reportLen >= 5 {
// Extract timebase (little-endian uint32)
timebase := binary.LittleEndian.Uint32(payload[cursor+1 : cursor+5])
referenceDelta = -timebase // Store negative for delta calculation
}
case 0xFA: // SENSORHUB_TIMESTAMP_REBASE
if reportLen >= 5 {
timebase := binary.LittleEndian.Uint32(payload[cursor+1 : cursor+5])
referenceDelta += timebase
}
case 0xF1: // SENSORHUB_FLUSH_COMPLETED
// Route to control handler
s.handleControl(payload[cursor:cursor+reportLen], timestamp)
default:
// Regular sensor report
value, ok := decodeSensor(payload[cursor:cursor+reportLen], timestamp)
if ok {
s.device.enqueue(value)
}
}
cursor += reportLen
}
} // handleExecutable processes executable channel messages.
func (s *sh2Protocol) handleExecutable(payload []byte, timestamp uint32) {
if len(payload) == 0 {
return
}
reportID := payload[0]
switch reportID {
case execDeviceRespResetComplete:
s.device.lastReset = true
}
}
-83
View File
@@ -1,83 +0,0 @@
// SHTP specification found at https://www.ceva-ip.com/wp-content/uploads/SH-2-SHTP-Reference-Manual.pdf
package bno08x
import "encoding/binary"
// shtpHandler is a callback for handling SHTP channel data.
type shtpHandler func(payload []byte, timestamp uint32)
// shtp implements the Sensor Hub Transport Protocol layer.
type shtp struct {
hal *hal
handlers map[uint8]shtpHandler
seq [8]uint8
rx [maxTransferIn]byte // Reusable receive buffer
tx [maxTransferOut]byte // Reusable transmit buffer
}
func newSHTP(hal *hal) *shtp {
return &shtp{
hal: hal,
handlers: make(map[uint8]shtpHandler),
}
}
// register registers a handler for a specific SHTP channel.
func (s *shtp) register(channel uint8, handler shtpHandler) {
if handler == nil {
delete(s.handlers, channel)
return
}
s.handlers[channel] = handler
}
// send transmits a payload on the specified channel.
func (s *shtp) send(channel uint8, payload []byte) error {
total := len(payload) + shtpHeaderLength
if total > maxTransferOut {
return errFrameTooLarge
}
// Use pre-allocated transmit buffer to avoid allocations
frame := s.tx[:total]
binary.LittleEndian.PutUint16(frame[0:2], uint16(total))
frame[2] = channel
frame[3] = s.seq[channel]
s.seq[channel]++
copy(frame[shtpHeaderLength:], payload)
_, err := s.hal.write(frame)
return err
}
// poll checks for and processes incoming SHTP packets.
// Returns true if a packet was processed, false if no data available.
func (s *shtp) poll() (bool, error) {
n, timestamp, err := s.hal.read(s.rx[:])
if err != nil {
return false, err
}
if n == 0 {
return false, nil
}
packet := s.rx[:n]
length := int(binary.LittleEndian.Uint16(packet[0:2]) & ^uint16(continueMask))
if length > n {
length = n
}
if length < shtpHeaderLength {
return false, nil
}
channel := packet[2]
// seq := packet[3] // sequence number, not currently validated
payload := packet[shtpHeaderLength:length]
if handler := s.handlers[channel]; handler != nil {
handler(payload, timestamp)
}
return true, nil
}
-572
View File
@@ -1,572 +0,0 @@
package bno08x
// SensorID identifies a specific sensor type.
type SensorID uint8
// Sensor IDs as defined in the SH-2 specification.
const (
SensorRawAccelerometer SensorID = 0x14
SensorAccelerometer SensorID = 0x01
SensorLinearAcceleration SensorID = 0x04
SensorGravity SensorID = 0x06
SensorRawGyroscope SensorID = 0x15
SensorGyroscope SensorID = 0x02
SensorGyroscopeUncalibrated SensorID = 0x07
SensorRawMagnetometer SensorID = 0x16
SensorMagneticField SensorID = 0x03
SensorMagneticFieldUncalibrated SensorID = 0x0F
SensorRotationVector SensorID = 0x05
SensorGameRotationVector SensorID = 0x08
SensorGeomagneticRotationVector SensorID = 0x09
SensorPressure SensorID = 0x0A
SensorAmbientLight SensorID = 0x0B
SensorHumidity SensorID = 0x0C
SensorProximity SensorID = 0x0D
SensorTemperature SensorID = 0x0E
SensorReserved SensorID = 0x17
SensorTapDetector SensorID = 0x10
SensorStepDetector SensorID = 0x18
SensorStepCounter SensorID = 0x11
SensorSignificantMotion SensorID = 0x12
SensorStabilityClassifier SensorID = 0x13
SensorShakeDetector SensorID = 0x19
SensorFlipDetector SensorID = 0x1A
SensorPickupDetector SensorID = 0x1B
SensorStabilityDetector SensorID = 0x1C
SensorPersonalActivityClassifier SensorID = 0x1E
SensorSleepDetector SensorID = 0x1F
SensorTiltDetector SensorID = 0x20
SensorPocketDetector SensorID = 0x21
SensorCircleDetector SensorID = 0x22
SensorHeartRateMonitor SensorID = 0x23
SensorARVRStabilizedRV SensorID = 0x28
SensorARVRStabilizedGRV SensorID = 0x29
SensorGyroIntegratedRV SensorID = 0x2A
SensorIZROMotionRequest SensorID = 0x2B
SensorMaxID SensorID = 0x2B
)
// ProductID contains firmware information from the sensor.
type ProductID struct {
ResetCause uint8
VersionMajor uint8
VersionMinor uint8
PartNumber uint32
BuildNumber uint32
VersionPatch uint16
Reserved0 uint8
Reserved1 uint8
}
// ProductIDs holds all product ID entries returned by the sensor.
type ProductIDs struct {
Entries [5]ProductID
NumEntries uint8
}
// Vector3 represents a 3D vector.
type Vector3 struct {
X float32
Y float32
Z float32
}
// Quaternion represents a quaternion in (real, i, j, k) format.
// Note: This maps to (w, x, y, z) convention where w=real, x=i, y=j, z=k.
type Quaternion struct {
Real float32
I float32
J float32
K float32
}
// RawVector3 contains raw ADC counts with timestamp.
type RawVector3 struct {
X int16
Y int16
Z int16
Timestamp uint32
}
// RawGyroscope contains raw gyro readings with temperature and timestamp.
type RawGyroscope struct {
X int16
Y int16
Z int16
Temperature int16
Timestamp uint32
}
// GyroscopeUncalibrated contains uncalibrated gyroscope data with bias.
type GyroscopeUncalibrated struct {
X float32
Y float32
Z float32
BiasX float32
BiasY float32
BiasZ float32
}
// MagneticFieldUncalibrated contains uncalibrated magnetometer data with bias.
type MagneticFieldUncalibrated struct {
X float32
Y float32
Z float32
BiasX float32
BiasY float32
BiasZ float32
}
// TapDetector contains tap/double-tap detection flags.
type TapDetector struct {
Flags uint8
}
// StepDetector contains step detection with latency.
type StepDetector struct {
Latency uint32
}
// StepCounter contains step count with latency.
type StepCounter struct {
Count uint16
Latency uint32
}
// SignificantMotion indicates significant motion was detected.
type SignificantMotion struct {
Motion uint16
}
// ActivityClassification contains activity classification data.
type ActivityClassification struct {
Page uint8
MostLikelyState uint8
Classification [10]uint8
EndOfPage uint8
}
// ShakeDetector contains shake detection data.
type ShakeDetector struct {
Shake uint16
}
// StabilityClassifier contains stability classification.
type StabilityClassifier struct {
Classification uint8
}
// PersonalActivityClassifier contains personal activity data.
type PersonalActivityClassifier struct {
Page uint8
MostLikelyState uint8
Confidence [10]uint8
EndOfPage uint8
}
// SensorValue contains decoded sensor data for all sensor types.
type SensorValue struct {
id SensorID
status uint8
sequence uint8
delay uint8
timestamp uint64
// Orientation data (quaternions)
quaternion Quaternion
quaternionAccuracy float32
// Linear measurements
accelerometer Vector3
linearAcceleration Vector3
gravity Vector3
gyroscope Vector3
gyroscopeUncal GyroscopeUncalibrated
magneticField Vector3
magneticFieldUncal MagneticFieldUncalibrated
// Raw sensor data
rawAccelerometer RawVector3
rawGyroscope RawGyroscope
rawMagnetometer RawVector3
// Environmental sensors
pressure float32 // hPa
ambientLight float32 // lux
humidity float32 // %
proximity float32 // cm
temperature float32 // °C
// Activity detection
tapDetector TapDetector
stepCounter StepCounter
stepDetector StepDetector
significantMotion SignificantMotion
shakeDetector ShakeDetector
flipDetector uint16
stabilityClassifier StabilityClassifier
stabilityDetector uint8
activityClassifier ActivityClassification
personalActivityClassifier PersonalActivityClassifier
sleepDetector uint8
tiltDetector uint8
pocketDetector uint8
circleDetector uint8
heartRateMonitor uint16
}
// SensorConfig holds configuration settings for a sensor.
type SensorConfig struct {
ChangeSensitivityEnabled bool
ChangeSensitivityRelative bool
WakeupEnabled bool
AlwaysOnEnabled bool
ChangeSensitivity uint16
ReportInterval uint32 // microseconds
BatchInterval uint32 // microseconds
SensorSpecific uint32
}
// Error represents a driver error.
type Error string
func (e Error) Error() string { return string(e) }
// Error constants.
var (
errBufferTooSmall = Error("bno08x: buffer too small")
errNoEvent = Error("bno08x: no sensor event available")
errTimeout = Error("bno08x: operation timed out")
errFrameTooLarge = Error("bno08x: frame exceeds maximum size")
errNoBus = Error("bno08x: I2C bus not configured")
errInvalidParam = Error("bno08x: invalid parameter")
errHubError = Error("bno08x: sensor hub error")
errIO = Error("bno08x: I/O error")
)
// Metadata accessor methods (always available for any sensor type)
// ID returns the sensor ID.
func (sv SensorValue) ID() SensorID {
return sv.id
}
// Status returns the sensor status flags.
func (sv SensorValue) Status() uint8 {
return sv.status
}
// Sequence returns the sequence number.
func (sv SensorValue) Sequence() uint8 {
return sv.sequence
}
// Delay returns the sensor delay value.
func (sv SensorValue) Delay() uint8 {
return sv.delay
}
// Timestamp returns the sensor timestamp.
func (sv SensorValue) Timestamp() uint64 {
return sv.timestamp
}
// Orientation data accessor methods
// Quaternion returns the quaternion value for rotation vector sensors.
// Panics if called on a sensor type that doesn't provide quaternion data.
func (sv SensorValue) Quaternion() Quaternion {
switch sv.id {
case SensorRotationVector, SensorGameRotationVector, SensorGeomagneticRotationVector,
SensorARVRStabilizedRV, SensorARVRStabilizedGRV, SensorGyroIntegratedRV:
return sv.quaternion
default:
panic("bno08x: Quaternion() called on non-rotation sensor type")
}
}
// QuaternionAccuracy returns the quaternion accuracy estimate.
// Panics if called on a sensor type that doesn't provide quaternion accuracy.
func (sv SensorValue) QuaternionAccuracy() float32 {
switch sv.id {
case SensorRotationVector, SensorGeomagneticRotationVector, SensorARVRStabilizedRV:
return sv.quaternionAccuracy
default:
panic("bno08x: QuaternionAccuracy() called on sensor type without accuracy data")
}
}
// Linear measurement accessor methods
// Accelerometer returns the accelerometer vector.
// Panics if called on a sensor type other than SensorAccelerometer.
func (sv SensorValue) Accelerometer() Vector3 {
if sv.id != SensorAccelerometer {
panic("bno08x: Accelerometer() called on non-accelerometer sensor type")
}
return sv.accelerometer
}
// LinearAcceleration returns the linear acceleration vector.
// Panics if called on a sensor type other than SensorLinearAcceleration.
func (sv SensorValue) LinearAcceleration() Vector3 {
if sv.id != SensorLinearAcceleration {
panic("bno08x: LinearAcceleration() called on wrong sensor type")
}
return sv.linearAcceleration
}
// Gravity returns the gravity vector.
// Panics if called on a sensor type other than SensorGravity.
func (sv SensorValue) Gravity() Vector3 {
if sv.id != SensorGravity {
panic("bno08x: Gravity() called on non-gravity sensor type")
}
return sv.gravity
}
// Gyroscope returns the gyroscope vector.
// Panics if called on a sensor type other than SensorGyroscope.
func (sv SensorValue) Gyroscope() Vector3 {
if sv.id != SensorGyroscope {
panic("bno08x: Gyroscope() called on non-gyroscope sensor type")
}
return sv.gyroscope
}
// GyroscopeUncal returns the uncalibrated gyroscope data.
// Panics if called on a sensor type other than SensorGyroscopeUncalibrated.
func (sv SensorValue) GyroscopeUncal() GyroscopeUncalibrated {
if sv.id != SensorGyroscopeUncalibrated {
panic("bno08x: GyroscopeUncal() called on wrong sensor type")
}
return sv.gyroscopeUncal
}
// MagneticField returns the magnetic field vector.
// Panics if called on a sensor type other than SensorMagneticField.
func (sv SensorValue) MagneticField() Vector3 {
if sv.id != SensorMagneticField {
panic("bno08x: MagneticField() called on wrong sensor type")
}
return sv.magneticField
}
// MagneticFieldUncal returns the uncalibrated magnetic field data.
// Panics if called on a sensor type other than SensorMagneticFieldUncalibrated.
func (sv SensorValue) MagneticFieldUncal() MagneticFieldUncalibrated {
if sv.id != SensorMagneticFieldUncalibrated {
panic("bno08x: MagneticFieldUncal() called on wrong sensor type")
}
return sv.magneticFieldUncal
}
// Raw sensor data accessor methods
// RawAccelerometer returns the raw accelerometer data.
// Panics if called on a sensor type other than SensorRawAccelerometer.
func (sv SensorValue) RawAccelerometer() RawVector3 {
if sv.id != SensorRawAccelerometer {
panic("bno08x: RawAccelerometer() called on wrong sensor type")
}
return sv.rawAccelerometer
}
// RawGyroscope returns the raw gyroscope data.
// Panics if called on a sensor type other than SensorRawGyroscope.
func (sv SensorValue) RawGyroscope() RawGyroscope {
if sv.id != SensorRawGyroscope {
panic("bno08x: RawGyroscope() called on wrong sensor type")
}
return sv.rawGyroscope
}
// RawMagnetometer returns the raw magnetometer data.
// Panics if called on a sensor type other than SensorRawMagnetometer.
func (sv SensorValue) RawMagnetometer() RawVector3 {
if sv.id != SensorRawMagnetometer {
panic("bno08x: RawMagnetometer() called on wrong sensor type")
}
return sv.rawMagnetometer
}
// Environmental sensor accessor methods
// Pressure returns the pressure reading in hPa.
// Panics if called on a sensor type other than SensorPressure.
func (sv SensorValue) Pressure() float32 {
if sv.id != SensorPressure {
panic("bno08x: Pressure() called on non-pressure sensor type")
}
return sv.pressure
}
// AmbientLight returns the ambient light reading in lux.
// Panics if called on a sensor type other than SensorAmbientLight.
func (sv SensorValue) AmbientLight() float32 {
if sv.id != SensorAmbientLight {
panic("bno08x: AmbientLight() called on wrong sensor type")
}
return sv.ambientLight
}
// Humidity returns the humidity reading in percent.
// Panics if called on a sensor type other than SensorHumidity.
func (sv SensorValue) Humidity() float32 {
if sv.id != SensorHumidity {
panic("bno08x: Humidity() called on non-humidity sensor type")
}
return sv.humidity
}
// Proximity returns the proximity reading in cm.
// Panics if called on a sensor type other than SensorProximity.
func (sv SensorValue) Proximity() float32 {
if sv.id != SensorProximity {
panic("bno08x: Proximity() called on non-proximity sensor type")
}
return sv.proximity
}
// Temperature returns the temperature reading in °C.
// Panics if called on a sensor type other than SensorTemperature.
func (sv SensorValue) Temperature() float32 {
if sv.id != SensorTemperature {
panic("bno08x: Temperature() called on non-temperature sensor type")
}
return sv.temperature
}
// Activity detection accessor methods
// TapDetector returns the tap detector data.
// Panics if called on a sensor type other than SensorTapDetector.
func (sv SensorValue) TapDetector() TapDetector {
if sv.id != SensorTapDetector {
panic("bno08x: TapDetector() called on wrong sensor type")
}
return sv.tapDetector
}
// StepCounter returns the step counter value.
// Panics if called on a sensor type other than SensorStepCounter.
func (sv SensorValue) StepCounter() StepCounter {
if sv.id != SensorStepCounter {
panic("bno08x: StepCounter() called on wrong sensor type")
}
return sv.stepCounter
}
// StepDetector returns the step detector data.
// Panics if called on a sensor type other than SensorStepDetector.
func (sv SensorValue) StepDetector() StepDetector {
if sv.id != SensorStepDetector {
panic("bno08x: StepDetector() called on wrong sensor type")
}
return sv.stepDetector
}
// SignificantMotion returns the significant motion data.
// Panics if called on a sensor type other than SensorSignificantMotion.
func (sv SensorValue) SignificantMotion() SignificantMotion {
if sv.id != SensorSignificantMotion {
panic("bno08x: SignificantMotion() called on wrong sensor type")
}
return sv.significantMotion
}
// ShakeDetector returns the shake detector data.
// Panics if called on a sensor type other than SensorShakeDetector.
func (sv SensorValue) ShakeDetector() ShakeDetector {
if sv.id != SensorShakeDetector {
panic("bno08x: ShakeDetector() called on wrong sensor type")
}
return sv.shakeDetector
}
// FlipDetector returns the flip detector data.
// Panics if called on a sensor type other than SensorFlipDetector.
func (sv SensorValue) FlipDetector() uint16 {
if sv.id != SensorFlipDetector {
panic("bno08x: FlipDetector() called on wrong sensor type")
}
return sv.flipDetector
}
// StabilityClassifier returns the stability classifier data.
// Panics if called on a sensor type other than SensorStabilityClassifier.
func (sv SensorValue) StabilityClassifier() StabilityClassifier {
if sv.id != SensorStabilityClassifier {
panic("bno08x: StabilityClassifier() called on wrong sensor type")
}
return sv.stabilityClassifier
}
// StabilityDetector returns the stability detector value.
// Panics if called on a sensor type other than SensorStabilityDetector.
func (sv SensorValue) StabilityDetector() uint8 {
if sv.id != SensorStabilityDetector {
panic("bno08x: StabilityDetector() called on wrong sensor type")
}
return sv.stabilityDetector
}
// ActivityClassifier returns the activity classification data.
// Note: This field appears unused in decode.go, keeping for API compatibility.
func (sv SensorValue) ActivityClassifier() ActivityClassification {
return sv.activityClassifier
}
// PersonalActivityClassifier returns the personal activity classifier data.
// Panics if called on a sensor type other than SensorPersonalActivityClassifier.
func (sv SensorValue) PersonalActivityClassifier() PersonalActivityClassifier {
if sv.id != SensorPersonalActivityClassifier {
panic("bno08x: PersonalActivityClassifier() called on wrong sensor type")
}
return sv.personalActivityClassifier
}
// SleepDetector returns the sleep detector value.
// Panics if called on a sensor type other than SensorSleepDetector.
func (sv SensorValue) SleepDetector() uint8 {
if sv.id != SensorSleepDetector {
panic("bno08x: SleepDetector() called on wrong sensor type")
}
return sv.sleepDetector
}
// TiltDetector returns the tilt detector value.
// Panics if called on a sensor type other than SensorTiltDetector.
func (sv SensorValue) TiltDetector() uint8 {
if sv.id != SensorTiltDetector {
panic("bno08x: TiltDetector() called on wrong sensor type")
}
return sv.tiltDetector
}
// PocketDetector returns the pocket detector value.
// Panics if called on a sensor type other than SensorPocketDetector.
func (sv SensorValue) PocketDetector() uint8 {
if sv.id != SensorPocketDetector {
panic("bno08x: PocketDetector() called on wrong sensor type")
}
return sv.pocketDetector
}
// CircleDetector returns the circle detector value.
// Panics if called on a sensor type other than SensorCircleDetector.
func (sv SensorValue) CircleDetector() uint8 {
if sv.id != SensorCircleDetector {
panic("bno08x: CircleDetector() called on wrong sensor type")
}
return sv.circleDetector
}
// HeartRateMonitor returns the heart rate monitor value.
// Panics if called on a sensor type other than SensorHeartRateMonitor.
func (sv SensorValue) HeartRateMonitor() uint16 {
if sv.id != SensorHeartRateMonitor {
panic("bno08x: HeartRateMonitor() called on wrong sensor type")
}
return sv.heartRateMonitor
}
+7 -7
View File
@@ -2,22 +2,22 @@
package buzzer // import "tinygo.org/x/drivers/buzzer"
import (
"time"
"machine"
"tinygo.org/x/drivers/internal/pin"
"time"
)
// Device wraps a GPIO connection to a buzzer.
type Device struct {
pin pin.OutputFunc
pin machine.Pin
High bool
BPM float64
}
// New returns a new buzzer driver given which pin to use
func New(pin pin.Output) Device {
func New(pin machine.Pin) Device {
return Device{
pin: pin.Set,
pin: pin,
High: false,
BPM: 96.0,
}
@@ -25,14 +25,14 @@ func New(pin pin.Output) Device {
// On sets the buzzer to a high state.
func (l *Device) On() (err error) {
l.pin.High()
l.pin.Set(true)
l.High = true
return
}
// Off sets the buzzer to a low state.
func (l *Device) Off() (err error) {
l.pin.Low()
l.pin.Set(false)
l.High = false
return
}
-711
View File
@@ -1,711 +0,0 @@
// Package comboat implements WiFi driver for the Aithinker-Combo-AT WiFi
// device found on the Elecrow W5 rp2040 and rp2350 devices. Ths WiFi device
// is a RTL8720d variant. The driver interface is via AT command set over UART
// (see reference docs below).
//
// NOTE: the driver doesn't support UDP/TCP server connections in STA mode,
// currently. UDP/TCP/TLS client connections are supported in STA mode.
//
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/instruction/index.html
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/command-set/index.html
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/command-examples/index.html
package comboat // import "tinygo.org/x/drivers/comboat"
import (
"bytes"
"errors"
"fmt"
"io"
"machine"
"net"
"net/netip"
"strconv"
"sync"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/netlink"
)
type Config struct {
BaudRate uint32
Uart *machine.UART
Tx machine.Pin
Rx machine.Pin
}
type socket struct {
protocol int
id string
rx chan []byte
remainder []byte
laddr netip.AddrPort // Set in Bind()
}
type device struct {
cfg *Config
uart *machine.UART
uartMu sync.Mutex
mac net.HardwareAddr
ip netip.Addr
gateway netip.Addr
buf [1500]byte
pos int
last []byte
ok chan bool
txReady chan bool
accept chan string
err chan error
sockets [8]*socket
sync.Mutex
}
func NewDevice(cfg *Config) *device {
return &device{
cfg: cfg,
ok: make(chan bool),
txReady: make(chan bool),
accept: make(chan string),
err: make(chan error),
}
}
func logDebug(msg string) {
//println("[DEBUG] " + msg)
}
func logError(msg string) {
println("[ERROR] " + msg)
}
func split(resp []byte, part int, del, on string) string {
parts := bytes.Split(resp, []byte(del))
if part >= len(parts) {
return "Split parts error getting " + on
}
return string(parts[part])
}
func (d *device) getFWVersion() string {
return split(d.last, 1, ":", "FW version")
}
func (d *device) saveMAC() {
raw := split(d.last, 1, ":", "MAC")
if len(raw) > 11 {
macStr := fmt.Sprintf("%s:%s:%s:%s:%s:%s",
raw[0:2], raw[2:4], raw[4:6],
raw[6:8], raw[8:10], raw[10:12])
d.mac, _ = net.ParseMAC(macStr)
}
}
var countryCodes = map[int]string{
1: "JP Japan",
2: "American Samoa",
3: "CA Canada",
4: "US",
5: "CN China",
6: "Hong Kong, China",
7: "Taiwan, China",
8: "MO Macau, China",
9: "IL Israel",
10: "Singapore",
11: "KR South Korea",
12: "TR Türkiye",
13: "AU Australia",
14: "ZA South Africa",
15: "BR Brazil",
}
func (d *device) getCountry() (code string) {
code = split(d.last, 1, ":", "county code")
codeNum, err := strconv.Atoi(code)
if err != nil {
return
}
if val, ok := countryCodes[codeNum]; ok {
code = val
}
return
}
func (d *device) saveIP() {
ipStr := split(d.last, 7, ",", "IP address")
gwStr := split(d.last, 8, ",", "gateway address")
d.ip, _ = netip.ParseAddr(ipStr)
d.gateway, _ = netip.ParseAddr(gwStr)
}
func (d *device) execute(cmd string, timeout int) (err error) {
logDebug("EXECUTE " + cmd)
d.uartMu.Lock()
_, err = d.uart.Write([]byte(cmd + "\r\n"))
d.uartMu.Unlock()
if err != nil {
return
}
t := time.NewTicker(time.Duration(timeout) * time.Millisecond)
defer t.Stop()
select {
case <-t.C:
return errors.New("Timed out")
case <-d.ok:
return
case err = <-d.err:
return
}
}
func (d *device) send(cmd string, timeout int) (err error) {
logDebug("EXECUTE " + cmd)
d.uartMu.Lock()
_, err = d.uart.Write([]byte(cmd + "\r\n"))
d.uartMu.Unlock()
if err != nil {
return
}
t := time.NewTicker(time.Duration(timeout) * time.Millisecond)
defer t.Stop()
select {
case <-t.C:
return errors.New("Timed out")
case <-d.txReady:
return
case err = <-d.err:
return
}
}
func (d *device) findSocket(id string) (*socket, error) {
for _, s := range d.sockets {
if s.id == id {
return s, nil
}
}
return nil, errors.New("Socket not found with id: " + id)
}
func (d *device) getSocket(sockfd int) (*socket, error) {
if sockfd < 0 || sockfd+1 > len(d.sockets) {
return nil, netdev.ErrInvalidSocketFd
}
if d.sockets[sockfd] == nil {
return nil, netdev.ErrInvalidSocketFd
}
return d.sockets[sockfd], nil
}
func (d *device) handle(event []byte) {
logDebug("GOT EVENT " + string(event))
switch {
// SocketDisconnect,<id>
case bytes.HasPrefix(event, []byte("SocketDisconnect")):
id := split(event, 1, ",", "SocketDisconnect")
s, err := d.findSocket(id)
if err == nil {
close(s.rx) // Sends io.EOF
}
// SocketSeed,<id>,<server id>
case bytes.HasPrefix(event, []byte("SocketSeed,2,1")):
//d.uart.Write([]byte("AT+SOCKET?" + "\r\n"))
}
}
func (d *device) processUART() {
if d.pos == 1 && d.buf[0] == '>' {
d.pos = 0
logDebug("GOT >")
d.txReady <- true
}
sofar := d.buf[:d.pos]
if !bytes.HasSuffix(sofar, []byte("\r\n")) {
return
}
// Strip CR/LF off end
sofar = sofar[:len(sofar)-2]
switch {
case bytes.HasPrefix(sofar, []byte("+EVENT:SocketDown")):
// +EVENT:SocketDown,<id>,<length>,<data>
parts := bytes.SplitN(sofar, []byte(","), 4)
if len(parts) != 4 {
logError("Error parsing +EVENT:SocketDown: " + string(sofar))
d.pos = 0
return
}
id := string(parts[1])
length, err := strconv.Atoi(string(parts[2]))
if err != nil {
logError("Error parsing length from: " + string(parts[2]))
d.pos = 0
return
}
if length != len(parts[3]) {
// This can happen if <data> actually contains a CR/LF.
// Return without resetting d.pos to continue reading
// in the full <data>.
return
}
s, err := d.findSocket(id)
if err != nil {
logError(err.Error())
d.pos = 0
return
}
logDebug("GOT +EVENT:SocketDown," + id + "," + string(parts[2]))
d.pos = 0
data := make([]byte, len(parts[3]))
copy(data, parts[3])
s.rx <- data
case bytes.HasPrefix(sofar, []byte("OK")):
d.pos = 0
logDebug("GOT OK")
d.ok <- true
case bytes.HasPrefix(sofar, []byte("ERROR")):
d.pos = 0
logDebug("GOT ERROR")
errStr := getErrStr(d.last)
d.err <- errors.New(errStr)
case bytes.HasPrefix(sofar, []byte("+EVENT:")):
d.pos = 0
event := sofar[len("+EVENT:"):]
d.handle(event)
default:
// Catch everything else and store in d.last
d.pos = 0
size := len(sofar)
if size > 0 {
d.last = make([]byte, size)
copy(d.last, sofar[:size])
logDebug("GOT LINE " + string(d.last))
}
}
}
func (d *device) serviceUART() {
for {
d.uartMu.Lock()
for d.uart.Buffered() > 0 {
if d.pos >= len(d.buf) {
println("Trying to write past buffer")
d.pos = 0
break
}
var err error
d.buf[d.pos], err = d.uart.ReadByte()
if err == nil {
d.pos++
d.processUART()
}
}
d.uartMu.Unlock()
time.Sleep(10 * time.Millisecond)
}
}
func (d *device) NetConnect(params *netlink.ConnectParams) error {
d.Lock()
defer d.Unlock()
d.uart = d.cfg.Uart
d.uart.Configure(machine.UARTConfig{
BaudRate: d.cfg.BaudRate,
TX: d.cfg.Tx,
RX: d.cfg.Rx,
})
go d.serviceUART()
fmt.Printf("\r\n")
fmt.Printf("TinyGo Combo-AT WiFi network device driver\r\n")
fmt.Printf("\r\n")
fmt.Printf("Driver version : %s\r\n", drivers.Version)
if len(params.Ssid) == 0 {
return netlink.ErrMissingSSID
}
// AT Test to see if device is alive
if err := d.execute("AT", 1000); err != nil {
return err
}
// Disable echo
if err := d.execute("ATE0", 1000); err != nil {
return err
}
// Get FW version
if err := d.execute("AT+GMR", 1000); err != nil {
return err
}
fmt.Printf("Combo-AT firmware version : %s\r\n", d.getFWVersion())
// Get/save MAC addresses
if err := d.execute("AT+CIPSTAMAC_DEF?", 1000); err != nil {
return err
}
d.saveMAC()
fmt.Printf("MAC address : %s\r\n", d.mac.String())
// Set country code US
if err := d.execute("AT+WCOUNTRY=4", 1000); err != nil {
return err
}
// Get country code
if err := d.execute("AT+WCOUNTRY?", 1000); err != nil {
return err
}
fmt.Printf("WiFi country code : %s\r\n", d.getCountry())
// Set Wi-Fi working mode to STA and save to flash
if err := d.execute("AT+WMODE=1,1", 1000); err != nil {
return err
}
// Connect to Wifi AP (keep trying until connected)
fmt.Printf("\r\n")
cmd := "AT+WJAP=" + params.Ssid + "," + params.Passphrase
for {
fmt.Printf("Connecting to WiFi SSID '%s'...", params.Ssid)
if err := d.execute(cmd, 20000); err != nil {
fmt.Printf("FAILED (%s)\r\n", err.Error())
continue
}
break
}
fmt.Printf("CONNECTED\r\n")
// Automatically reconnect to Wi-Fi after power on
if err := d.execute("AT+WAUTOCONN=1", 1000); err != nil {
return err
}
// Get/save IP/gateway addresses
if err := d.execute("AT+WJAP?", 1000); err != nil {
return err
}
d.saveIP()
fmt.Printf("\r\n")
fmt.Printf("DHCP-assigned IP : %s\r\n", d.ip)
fmt.Printf("DHCP-assigned gateway : %s\r\n", d.gateway)
fmt.Printf("\r\n")
// Set socket receiving mode to active
if err := d.execute("AT+SOCKETRECVCFG=1", 1000); err != nil {
return err
}
return nil
}
func (d *device) NetDisconnect() {
d.Lock()
defer d.Unlock()
// Disconnect from WiFi AP
d.execute("AT+WDISCONNECT", 1000)
}
func (d *device) NetNotify(cb func(netlink.Event)) {
fmt.Printf("\r\n%s\r\n", netlink.ErrNotSupported)
}
func (d *device) GetHardwareAddr() (net.HardwareAddr, error) {
return d.mac, nil
}
func (d *device) _getHostByName(name string) (ip netip.Addr, err error) {
if err = d.execute("AT+WDOMAIN="+name, 1000); err != nil {
return
}
ipStr := split(d.last, 1, ":", "host by name")
return netip.ParseAddr(ipStr)
}
func (d *device) GetHostByName(name string) (ip netip.Addr, err error) {
// If it's already a dotted-network address, and not a host name,
// return it
ip, err = netip.ParseAddr(name)
if err == nil {
return
}
d.Lock()
defer d.Unlock()
return d._getHostByName(name)
}
func (d *device) Addr() (netip.Addr, error) {
return d.ip, nil
}
func (d *device) Socket(domain, stype, protocol int) (int, error) {
switch domain {
case netdev.AF_INET:
default:
return -1, netdev.ErrFamilyNotSupported
}
switch {
case protocol == netdev.IPPROTO_TCP && stype == netdev.SOCK_STREAM:
case protocol == netdev.IPPROTO_TLS && stype == netdev.SOCK_STREAM:
case protocol == netdev.IPPROTO_UDP && stype == netdev.SOCK_DGRAM:
default:
return -1, netdev.ErrProtocolNotSupported
}
d.Lock()
defer d.Unlock()
// Search for empty slot in sockets array
for fd, s := range d.sockets {
if s == nil {
// Found one
d.sockets[fd] = &socket{
protocol: protocol,
rx: make(chan []byte, 10),
}
return fd, nil
}
}
return -1, netdev.ErrNoMoreSockets
}
func (d *device) Bind(sockfd int, ip netip.AddrPort) error {
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return err
}
s.laddr = ip
return nil
}
func (d *device) Connect(sockfd int, host string, ip netip.AddrPort) error {
var addr string
var cmd string
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return err
}
if host == "" {
addr = ip.Addr().String()
} else {
ip, err := d._getHostByName(host)
if err != nil {
return err
}
addr = ip.String()
}
port := strconv.Itoa(int(ip.Port()))
switch s.protocol {
case netdev.IPPROTO_UDP:
cmd = "AT+SOCKET=2," + addr + "," + port
case netdev.IPPROTO_TCP:
cmd = "AT+SOCKET=4," + addr + "," + port
case netdev.IPPROTO_TLS:
cmd = "AT+SOCKET=7," + addr + "," + port
}
if cmd == "" {
return netdev.ErrProtocolNotSupported
}
if err := d.execute(cmd, 20000); err != nil {
return err
}
s.id = split(d.last, 1, "=", "connection ID")
return nil
}
func (d *device) Listen(sockfd, backlog int) error {
// TODO Creating a TCP server socket isn't working when in STA mode,
// TODO returning error "Socket bind error".
// TODO The reference example shows a TCP server example in AP mode.
/*
var cmd string
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return err
}
port := strconv.Itoa(int(s.laddr.Port()))
switch s.protocol {
case netdev.IPPROTO_UDP:
cmd = "AT+SOCKET=1," + port
case netdev.IPPROTO_TCP:
cmd = "AT+SOCKET=3," + port
}
if cmd == "" {
return netdev.ErrProtocolNotSupported
}
if err := d.execute(cmd, 20000); err != nil {
return err
}
s.id = split(d.last, 1, "=", "connection ID")
*/
return netdev.ErrNotSupported
}
func (d *device) Accept(sockfd int) (int, netip.AddrPort, error) {
return 0, netip.AddrPort{}, netdev.ErrNotSupported
}
func (d *device) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return 0, err
}
cmd := fmt.Sprintf("AT+SOCKETSEND=%s,%d", s.id, len(buf))
if err := d.send(cmd, 1000); err != nil {
return 0, err
}
// AT+SOCKETSEND will sub-packet send data into 1024-byte chunks,
// automatically, so send the full buffer in one shot, even if it's
// bigger than 1024 bytes.
d.uartMu.Lock()
n, err := d.uart.Write(buf)
d.uartMu.Unlock()
if err != nil {
return 0, err
}
// Expecting "OK" after good send, or "ERROR"
t := time.NewTicker(time.Duration(1000) * time.Millisecond)
defer t.Stop()
select {
case <-t.C:
return 0, errors.New("Timed out")
case <-d.ok:
return n, nil
case err = <-d.err:
return 0, err
}
}
func (d *device) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return 0, err
}
// 1. Use leftover data first
if len(s.remainder) > 0 {
n := copy(buf, s.remainder)
s.remainder = s.remainder[n:]
return n, nil
}
// 2. Get new data from the channel
data, ok := <-s.rx
if !ok {
// Socket closed, return EOF
return 0, io.EOF
}
// 3. Copy data, handle leftovers
n := copy(buf, data)
if n < len(data) {
s.remainder = data[n:]
}
return n, nil
}
func (d *device) Close(sockfd int) error {
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return err
}
// Delete socket only if connection was successful (s.id is set)
if s.id != "" {
cmd := fmt.Sprintf("AT+SOCKETDEL=%s", s.id)
if err = d.execute(cmd, 1000); err != nil {
return err
}
}
d.sockets[sockfd] = nil
return nil
}
func (d *device) SetSockOpt(sockfd, level, opt int, value interface{}) error {
return netdev.ErrNotSupported
}
-86
View File
@@ -1,86 +0,0 @@
package comboat
import (
"bytes"
"strconv"
)
var errStrings = map[int]string{
// System framework related error codes
0: "success",
1: "The command is not supported (the combo framework contains the command but the current platform has not transplanted or adapted to support it)",
2: "The command parameters contain unsupported operations (the current platform only supports some operations for this command)",
3: "The instruction format is incorrect (this refers to the wrong number of parameters, for example, two parameters are required, but only one parameter is entered)",
4: "Parameter error (the content of the parameter is wrong, for example, a number between 0 and 9 is required, but 10 or xyz is passed in, which is a parameter error)",
5: "Parameter length error (command length exceeds the maximum supported length)",
31: "The current command has not ended and needs to report the status asynchronously. This value is used by the state machine to determine the use of the command and no message is returned.",
32: "Unknown error (or unhandled error type)",
// Common error codes
33: "malloc error",
34: "Failed to read buf",
35: "Failed to write buf",
36: "Configuration error (configuration error loaded from memory, for example, we set port -1 for OTA upgrade, and check port error when executing AT+OTA, then configuration error will be reported)",
37: "Failed to create task",
38: "Flash read and write failure",
39: "Serial port configuration error, unsupported baud rate",
40: "Serial port configuration error, unsupported data bits",
41: "Serial port configuration error, unsupported stop bit",
42: "Serial port configuration error, unsupported parity bit",
43: "Serial port configuration error, unsupported flow control",
44: "Serial port configuration failed",
45: "Wrong username/password",
46: "Low power mode error or unsupported low power mode",
47: "Uninitialized configuration data error (including io mapping data)",
63: "General error code (without other information)",
// Wi-Fi related error codes
64: "Wi-Fi not initialized or initialization failed",
65: "Wi-Fi mode error (unable to connect to Wi-Fi in single AP mode)",
66: "Wi-Fi connection failed",
67: "Wi-Fi connection successful, error in obtaining IP (DHCP)",
68: "Failed to obtain encryption method",
69: "The specified AP was not found.",
70: "Wi-Fi scan start failed",
71: "Wi-Fi scan timeout",
72: "Failed to enable AP hotspot",
73: "Failed to obtain the Wi-Fi information of the router or the AP information that you enabled yourself",
74: "The network card (STA/AP) is not running",
75: "Wi-Fi country code error (unsupported Wi-Fi country code)",
76: "The current network configuration mode is wrong.",
95: "Wi-Fi connection unknown error",
// Socket related error codes
96: "Failed to create socket",
97: "Socket connection failed",
98: "DNS Failure",
99: "The socket status is wrong (for example, TCP is not connected yet)",
100: "Socket type error",
101: "Socket send failed",
102: "Socket receive failed",
103: "Socket monitoring thread creation failed",
104: "Socket bind error",
105: "The current connection cannot be transparently linked (wrong socket type or number)",
106: "PING test failed (all packets lost)",
107: "Wi-Fi country code error (unsupported Wi-Fi country code)",
108: "SSL Config Error",
109: "SSL verification error (usually caused by unsupported SSL encryption type or certificate error)",
127: "Unknown socket error",
}
func getErrStr(errLine []byte) (errStr string) {
errStr = "Can't parse ERROR response"
tokens := bytes.Split(errLine, []byte(":"))
if len(tokens) > 1 {
errCode, err := strconv.Atoi(string(tokens[1]))
if err == nil {
errStr = errStrings[errCode]
}
}
return
}
-54
View File
@@ -1,54 +0,0 @@
#include <stdint.h>
#include <stdbool.h>
// Loop the given times, where one loop takes four CPU cycles.
bool tinygo_drivers_sleep(uint32_t cycles) {
// In this function, a [n] comment indicates the number of cycles an
// instruction or a set of instructions take. This is typically 1 for most
// arithmetic instructions, and a bit more for branches.
#if __ARM_ARCH_6M__ || __ARM_ARCH_7M__ || __ARM_ARCH_7EM__
// Inline assembly for Cortex-M0/M0+/M3/M4/M7.
// The Cortex-M0 (but not M0+) takes one more cycle, so is off by 12.5%.
// Others should be basically cycle-accurate (with a slight overhead to
// calculate the number of cycles). Unfortunately, there doesn't appear to
// be a preprocessor macro to detect the Cortex-M0 specifically (although we
// could rely on macros like NRF51).
// Each loop takes 8 cycles (5 nops, 1 sub, and 2 for the branch).
uint32_t loops = (cycles + 7) / 8;
__asm__ __volatile__(
"1:\n\t"
"nop\n\t" // [5] nops
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"subs %[loops], #1\n\t" // [1]
"bne 1b" // [1-4], at least 2 cycles if taken
: [loops]"+r"(loops)
);
return true;
#elif __XTENSA__
// Inline assembly for Xtensa.
// I don't know exactly how many cycles a branch takes, so I've taken a
// conservative guess and assume it takes only one cycle. In practice, it's
// probably more than that.
uint32_t loops = (cycles + 7) / 8;
__asm__ __volatile__(
"1:\n\t"
"nop\n\t" // [6] nops
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"addi %[loops], %[loops], -1\n\t" // [1]
"bnez %[loops], 1b" // [1?]
: [loops]"+r"(loops)
);
return true;
#else
// Unknown architecture, so fall back to time.Sleep.
return false;
#endif
}
-57
View File
@@ -1,57 +0,0 @@
package delay
import (
"machine"
"time"
)
/*
#include <stdint.h>
#include <stdbool.h>
bool tinygo_drivers_sleep(uint32_t ticks);
*/
import "C"
// Sleep for a very precise short duration by busy-waiting for the given time.
// This is not an efficient way to sleep: it will needlessly burn cycles while
// sleeping. But it is useful for sleeping for a very short duration, for
// example for bit-banged protocols.
//
// Longer durations (longer than a few milliseconds) will be handled by calling
// time.Sleep instead.
//
// This function should be called with a constant duration value, in which case
// the call will typically be fully inlined and only take up around nine
// instructions for the entire loop.
//
//go:inline
func Sleep(duration time.Duration) {
if time.Duration(uint32(duration)&0xff_ffff) != duration {
// This is a long duration (more than 16ms) which shouldn't be done by
// busy-waiting.
time.Sleep(duration)
return
}
// Calculate the number of cycles we should sleep:
// cycles = duration * freq / 1e9
// Avoiding a 64-bit division:
// cycles = duration * (freq/1000_000) / 1000
//
// This assumes:
// * The CPU frequency is a constant and can trivially be
// const-propagated, therefore the divide by 1000_000 is done at compile
// time.
// * The CPU frequency is a multiple of 1000_000, which is true for most
// chips (examples: 16MHz, 48MHz, 120MHz, etc).
// * The division by 1000 can be done efficiently (Cortex-M3 and up), or
// can be fully const-propagated.
// * The CPU frequency is lower than 256MHz. If it is higher, long sleep
// times (1-16ms) may not work correctly.
cycles := uint32(duration) * (machine.CPUFrequency() / 1000_000) / 1000
slept := C.tinygo_drivers_sleep(C.uint32_t(cycles))
if !slept {
// Fallback for platforms without inline assembly support.
time.Sleep(duration)
}
}
-16
View File
@@ -12,19 +12,3 @@ type Displayer interface {
// Display sends the buffer (if any) to the screen.
Display() error
}
// Rotation is how much a display has been rotated. Displays can be rotated, and
// sometimes also mirrored.
type Rotation uint8
// Clockwise rotation of the screen.
const (
Rotation0 = iota
Rotation90
Rotation180
Rotation270
Rotation0Mirror
Rotation90Mirror
Rotation180Mirror
Rotation270Mirror
)
+4 -5
View File
@@ -9,7 +9,6 @@ import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
)
// Device wraps an I2C connection to a DS1307 device.
@@ -45,7 +44,7 @@ func (d *Device) SetTime(t time.Time) error {
// ReadTime returns the date and time
func (d *Device) ReadTime() (time.Time, error) {
data := make([]byte, 8)
err := legacy.ReadRegister(d.bus, d.Address, uint8(TimeDate), data)
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
if err != nil {
return time.Time{}, err
}
@@ -106,7 +105,7 @@ func (d *Device) Read(data []uint8) (n int, err error) {
if int(d.AddressSRAM)+len(data)-1 > SRAMEndAddress {
return 0, errors.New("EOF")
}
err = legacy.ReadRegister(d.bus, d.Address, d.AddressSRAM, data)
err = d.bus.ReadRegister(d.Address, d.AddressSRAM, data)
if err != nil {
return 0, err
}
@@ -125,7 +124,7 @@ func (d *Device) SetOscillatorFrequency(sqw uint8) error {
// IsOscillatorRunning returns if the oscillator is running
func (d *Device) IsOscillatorRunning() bool {
data := []byte{0}
err := legacy.ReadRegister(d.bus, d.Address, uint8(TimeDate), data)
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
if err != nil {
return false
}
@@ -135,7 +134,7 @@ func (d *Device) IsOscillatorRunning() bool {
// SetOscillatorRunning starts/stops internal oscillator by toggling halt bit
func (d *Device) SetOscillatorRunning(running bool) error {
data := make([]byte, 3)
err := legacy.ReadRegister(d.bus, d.Address, uint8(TimeDate), data)
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
if err != nil {
return err
}
-89
View File
@@ -1,89 +0,0 @@
// Package ds18b20 provides a driver for the DS18B20 digital thermometer
//
// Datasheet:
// https://www.analog.com/media/en/technical-documentation/data-sheets/DS18B20.pdf
package ds18b20 // import "tinygo.org/x/drivers/ds18b20"
import (
"errors"
)
// Device ROM commands
const (
CONVERT_TEMPERATURE uint8 = 0x44
READ_SCRATCHPAD uint8 = 0xBE
WRITE_SCRATCHPAD uint8 = 0x4E
)
type OneWireDevice interface {
Write(uint8)
Read() uint8
Select([]uint8) error
Сrc8([]uint8) uint8
}
// Device wraps a connection to an 1-Wire devices.
type Device struct {
owd OneWireDevice
}
// Errors list
var (
errReadTemperature = errors.New("Error: DS18B20. Read temperature error: CRC mismatch.")
)
func New(owd OneWireDevice) Device {
return Device{
owd: owd,
}
}
// Configure. Initializes the device, left for compatibility reasons.
func (d Device) Configure() {}
// ThermometerResolution sets thermometer resolution from 9 to 12 bits
func (d Device) ThermometerResolution(romid []uint8, resolution uint8) {
if 9 <= resolution && resolution <= 12 {
d.owd.Select(romid)
d.owd.Write(WRITE_SCRATCHPAD) // send three data bytes to scratchpad (TH, TL, and config)
d.owd.Write(0xFF) // to TH
d.owd.Write(0x00) // to TL
d.owd.Write(((resolution - 9) << 5) | 0x1F) // to resolution config
}
}
// RequestTemperature sends request to device
func (d Device) RequestTemperature(romid []uint8) {
d.owd.Select(romid)
d.owd.Write(CONVERT_TEMPERATURE)
}
// ReadTemperatureRaw returns the raw temperature.
// ScratchPad memory map:
// byte 0: Temperature LSB
// byte 1: Temperature MSB
func (d Device) ReadTemperatureRaw(romid []uint8) ([]uint8, error) {
spb := make([]uint8, 9) // ScratchPad buffer
d.owd.Select(romid)
d.owd.Write(READ_SCRATCHPAD)
for i := 0; i < 9; i++ {
spb[i] = d.owd.Read()
}
if d.owd.Сrc8(spb) != 0 {
return nil, errReadTemperature
}
return spb[:2:2], nil
}
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
func (d Device) ReadTemperature(romid []uint8) (int32, error) {
raw, err := d.ReadTemperatureRaw(romid)
if err != nil {
return 0, err
}
t := int32(uint16(raw[0]) | uint16(raw[1])<<8)
if t&0x8000 == 0x8000 {
t -= 0x10000
}
return (t * 625 / 10), nil
}
+37 -324
View File
@@ -5,12 +5,9 @@
package ds3231 // import "tinygo.org/x/drivers/ds3231"
import (
"encoding/binary"
"errors"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/regmap"
)
type Mode uint8
@@ -19,7 +16,6 @@ type Mode uint8
type Device struct {
bus drivers.I2C
Address uint16
d regmap.Device8I2C
}
// New creates a new DS3231 connection. The I2C bus must already be
@@ -27,83 +23,75 @@ type Device struct {
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) Device {
d := Device{
return Device{
bus: bus,
Address: Address,
}
d.Configure()
return d
}
// Configure sets up the device for communication
func (d *Device) Configure() bool {
d.d.SetBus(d.bus, d.Address, binary.BigEndian)
return true
}
// IsTimeValid return true/false is the time in the device is valid
func (d *Device) IsTimeValid() bool {
status, err := d.d.Read8(REG_STATUS)
data := []byte{0}
err := d.bus.ReadRegister(uint8(d.Address), REG_STATUS, data)
if err != nil {
return false
}
return (status & (1 << OSF)) == 0x00
return (data[0] & (1 << OSF)) == 0x00
}
// IsRunning returns if the oscillator is running
func (d *Device) IsRunning() bool {
control, err := d.d.Read8(REG_CONTROL)
data := []uint8{0}
err := d.bus.ReadRegister(uint8(d.Address), REG_CONTROL, data)
if err != nil {
return false
}
return (control & (1 << EOSC)) == 0x00
return (data[0] & (1 << EOSC)) == 0x00
}
// SetRunning starts the internal oscillator
func (d *Device) SetRunning(isRunning bool) error {
control, err := d.d.Read8(REG_CONTROL)
data := []uint8{0}
err := d.bus.ReadRegister(uint8(d.Address), REG_CONTROL, data)
if err != nil {
return err
}
if isRunning {
control &^= uint8(1 << EOSC)
data[0] &^= uint8(1 << EOSC)
} else {
control |= 1 << EOSC
data[0] |= 1 << EOSC
}
return d.d.Write8(REG_CONTROL, control)
}
// SetTime sets the date and time in the DS3231. The DS3231 hardware supports
// only a 2-digit year field, so the current year will be stored as an offset
// from the year 2000, which supports the year 2000 until 2100.
//
// The DS3231 also supports a one-bit 'century' flag which is set by the chip
// when the year field rolls over from 99 to 00. The current code interprets
// this flag to be the year 2100, which appears to extend the range of years
// until the year 2200. However the DS3231 does not incorporate the 'century'
// flag in its leap year calculation, so it will incorrectly identify the year
// 2100 as a leap year, causing it to increment from 2100-02-28 to 2100-02-29
// instead of 2100-03-01.
func (d *Device) SetTime(dt time.Time) error {
status, err := d.d.Read8(REG_STATUS)
err = d.bus.WriteRegister(uint8(d.Address), REG_CONTROL, data)
if err != nil {
return err
}
status &^= 1 << OSF
if err = d.d.Write8(REG_STATUS, status); err != nil {
return nil
}
// SetTime sets the date and time in the DS3231
func (d *Device) SetTime(dt time.Time) error {
data := []byte{0}
err := d.bus.ReadRegister(uint8(d.Address), REG_STATUS, data)
if err != nil {
return err
}
data[0] &^= 1 << OSF
err = d.bus.WriteRegister(uint8(d.Address), REG_STATUS, data)
if err != nil {
return err
}
data := make([]uint8, 7)
data = make([]uint8, 7)
data[0] = uint8ToBCD(uint8(dt.Second()))
data[1] = uint8ToBCD(uint8(dt.Minute()))
data[2] = uint8ToBCD(uint8(dt.Hour()))
year := uint8(dt.Year() - 2000)
// This code interprets the centuryFlag to be the year 2100. Warning: The
// DS3231 does not incorporate the centuryFlag in its leap year calculation.
// It will increment from 2100-02-28 to 2100-02-29, which is incorrect because
// the year 2100 is not a leap year in the Gregorian calendar.
centuryFlag := uint8(0)
if year >= 100 {
year -= 100
@@ -115,16 +103,21 @@ func (d *Device) SetTime(dt time.Time) error {
data[5] = uint8ToBCD(uint8(dt.Month()) | centuryFlag)
data[6] = uint8ToBCD(year)
return d.bus.Tx(d.Address, append([]byte{REG_TIMEDATE}, data...), nil)
err = d.bus.WriteRegister(uint8(d.Address), REG_TIMEDATE, data)
if err != nil {
return err
}
return nil
}
// ReadTime returns the date and time
func (d *Device) ReadTime() (dt time.Time, err error) {
data := make([]uint8, 7)
if err = d.d.ReadData(REG_TIMEDATE, data); err != nil {
err = d.bus.ReadRegister(uint8(d.Address), REG_TIMEDATE, data)
if err != nil {
return
}
second := bcdToInt(data[0] & 0x7F)
minute := bcdToInt(data[1])
hour := hoursBCDToInt(data[2])
@@ -142,284 +135,12 @@ func (d *Device) ReadTime() (dt time.Time, err error) {
// ReadTemperature returns the temperature in millicelsius (mC)
func (d *Device) ReadTemperature() (int32, error) {
temp, err := d.d.Read16(REG_TEMP)
data := make([]uint8, 2)
err := d.bus.ReadRegister(uint8(d.Address), REG_TEMP, data)
if err != nil {
return 0, err
}
return milliCelsius(temp), nil
}
// GetSqwPinMode returns the current square wave output frequency
func (d *Device) GetSqwPinMode() SqwPinMode {
control, err := d.d.Read8(REG_CONTROL)
if err != nil {
return SQW_OFF
}
control &= 0x1C // turn off INTCON
if control&0x04 != 0 {
return SQW_OFF
}
return SqwPinMode(control)
}
// SetSqwPinMode sets the square wave output mode to the given frequency
func (d *Device) SetSqwPinMode(mode SqwPinMode) error {
control, err := d.d.Read8(REG_CONTROL)
if err != nil {
return err
}
control &^= 0x04 // turn off INTCON
control &^= 0x18 // set freq bits to 0
control |= uint8(mode)
return d.d.Write8(REG_CONTROL, control)
}
// SetAlarm1 sets alarm1 to the given time and mode
func (d *Device) SetAlarm1(dt time.Time, mode Alarm1Mode) error {
control, err := d.d.Read8(REG_CONTROL)
if err != nil {
return err
}
if control&(1<<INTCN) == 0x00 {
return errors.New("INTCN has to be disabled")
}
A1M1 := uint8((mode & 0x01) << 7)
A1M2 := uint8((mode & 0x02) << 6)
A1M3 := uint8((mode & 0x04) << 5)
A1M4 := uint8((mode & 0x08) << 4)
DY_DT := uint8((mode & 0x10) << 2)
day := dt.Day()
if DY_DT > 0 {
day = dowToDS3231(int(dt.Weekday()))
}
alarm1 := uint32(uint8ToBCD(uint8(dt.Second()))|A1M1) << 24
alarm1 |= uint32(uint8ToBCD(uint8(dt.Minute()))|A1M2) << 16
alarm1 |= uint32(uint8ToBCD(uint8(dt.Hour()))|A1M3) << 8
alarm1 |= uint32(uint8ToBCD(uint8(day)) | A1M4 | DY_DT)
if err := d.d.Write32(REG_ALARMONE, alarm1); err != nil {
return err
}
control |= AlarmFlag_Alarm1
return d.d.Write8(REG_CONTROL, control)
}
// ReadAlarm1 returns the alarm1 time
func (d *Device) ReadAlarm1() (dt time.Time, err error) {
data := make([]uint8, 4)
if err = d.d.ReadData(REG_ALARMONE, data); err != nil {
return
}
second := bcdToInt(data[0] & 0x7F)
minute := bcdToInt(data[1] & 0x7F)
hour := hoursBCDToInt(data[2] & 0x3F)
isDayOfWeek := (data[3] & 0x40) >> 6
var day int
if isDayOfWeek > 0 {
day = bcdToInt(data[3] & 0x0F)
} else {
day = bcdToInt(data[3] & 0x3F)
}
dt = time.Date(2000, 5, day, hour, minute, second, 0, time.UTC)
return
}
// SetAlarm2 sets alarm2 to the given time and mode
func (d *Device) SetAlarm2(dt time.Time, mode Alarm2Mode) error {
control, err := d.d.Read8(REG_CONTROL)
if err != nil {
return err
}
if control&(1<<INTCN) == 0x00 {
return errors.New("INTCN has to be disabled")
}
A2M2 := uint8((mode & 0x01) << 7)
A2M3 := uint8((mode & 0x02) << 6)
A2M4 := uint8((mode & 0x04) << 5)
DY_DT := uint8((mode & 0x08) << 3)
day := dt.Day()
if DY_DT > 0 {
day = dowToDS3231(int(dt.Weekday()))
}
data := make([]uint8, 4)
data[0] = uint8ToBCD(uint8(dt.Minute())) | A2M2
data[1] = uint8ToBCD(uint8(dt.Hour())) | A2M3
data[2] = uint8ToBCD(uint8(day)) | A2M4 | DY_DT
if err = d.bus.Tx(d.Address, append([]byte{REG_ALARMTWO}, data...), nil); err != nil {
return err
}
control |= AlarmFlag_Alarm2
return d.d.Write8(REG_CONTROL, control)
}
// ReadAlarm2 returns the alarm2 time
func (d *Device) ReadAlarm2() (dt time.Time, err error) {
data := make([]uint8, 3)
if err = d.d.ReadData(REG_ALARMTWO, data); err != nil {
return
}
minute := bcdToInt(data[0] & 0x7F)
hour := hoursBCDToInt(data[1] & 0x3F)
isDayOfWeek := (data[2] & 0x40) >> 6
var day int
if isDayOfWeek > 0 {
day = bcdToInt(data[2] & 0x0F)
} else {
day = bcdToInt(data[2] & 0x3F)
}
dt = time.Date(2000, 5, day, hour, minute, 0, 0, time.UTC)
return
}
// IsEnabledAlarm1 returns true when alarm1 is enabled
func (d *Device) IsEnabledAlarm1() bool {
return d.isEnabledAlarm(1)
}
// SetEnabledAlarm1 sets the enabled status of alarm1
func (d *Device) SetEnabledAlarm1(enable bool) error {
if enable {
return d.enableAlarm(1)
}
return d.disableAlarm(1)
}
// IsEnabledAlarm2 returns true when alarm2 is enabled
func (d *Device) IsEnabledAlarm2() bool {
return d.isEnabledAlarm(2)
}
// SetEnabledAlarm2 sets the enabled status of alarm2
func (d *Device) SetEnabledAlarm2(enable bool) error {
if enable {
return d.enableAlarm(2)
}
return d.disableAlarm(2)
}
// ClearAlarm1 clears status of alarm1
func (d *Device) ClearAlarm1() error {
return d.clearAlarm(1)
}
// ClearAlarm2 clears status of alarm2
func (d *Device) ClearAlarm2() error {
return d.clearAlarm(2)
}
// IsAlarm1Fired returns true when alarm1 is firing
func (d *Device) IsAlarm1Fired() bool {
return d.isAlarmFired(1)
}
// IsAlarm2Fired returns true when alarm2 is firing
func (d *Device) IsAlarm2Fired() bool {
return d.isAlarmFired(2)
}
// SetEnabled32K sets the enabled status of the 32KHz output
func (d *Device) SetEnabled32K(enable bool) error {
status, err := d.d.Read8(REG_STATUS)
if err != nil {
return err
}
if enable {
status |= 1 << EN32KHZ
} else {
status &^= 1 << EN32KHZ
}
return d.d.Write8(REG_STATUS, status)
}
// IsEnabled32K returns true when the 32KHz output is enabled
func (d *Device) IsEnabled32K() bool {
status, err := d.d.Read8(REG_STATUS)
if err != nil {
return false
}
return (status & (1 << EN32KHZ)) != 0x00
}
func (d *Device) disableAlarm(alarm_num uint8) error {
control, err := d.d.Read8(REG_CONTROL)
if err != nil {
return err
}
control &^= (1 << (alarm_num - 1))
return d.d.Write8(REG_CONTROL, control)
}
func (d *Device) enableAlarm(alarm_num uint8) error {
control, err := d.d.Read8(REG_CONTROL)
if err != nil {
return err
}
control |= (1 << (alarm_num - 1))
return d.d.Write8(REG_CONTROL, control)
}
func (d *Device) isEnabledAlarm(alarm_num uint8) bool {
control, err := d.d.Read8(REG_CONTROL)
if err != nil {
return false
}
return (control & (1 << (alarm_num - 1))) != 0x00
}
func (d *Device) clearAlarm(alarm_num uint8) error {
status, err := d.d.Read8(REG_STATUS)
if err != nil {
return err
}
status &^= (1 << (alarm_num - 1))
return d.d.Write8(REG_STATUS, status)
}
func (d *Device) isAlarmFired(alarm_num uint8) bool {
status, err := d.d.Read8(REG_STATUS)
if err != nil {
return false
}
return (status & (1 << (alarm_num - 1))) != 0x00
}
// milliCelsius converts the raw temperature bytes (msb and lsb) from the DS3231
// into a 32-bit signed integer in units of milli Celsius (1/1000 deg C).
//
// According to the DS3231 datasheet: "Temperature is represented as a 10-bit
// code with a resolution of 0.25 deg C and is accessible at location 11h and
// 12h. The temperature is encoded in two's complement format. The upper 8 bits,
// the integer portion, are at location 11h and the lower 2 bits, the fractional
// portion, are in the upper nibble at location 12h."
//
// In other words, the msb and lsb bytes should be treated as a signed 16-bit
// integer in units of (1/256 deg C). It is possible to convert this into a
// 16-bit signed integer in units of centi Celsius (1/100 deg C) with no loss of
// precision or dynamic range. But for backwards compatibility, let's instead
// convert this into a 32-bit signed integer in units of milli Celsius.
func milliCelsius(tempBytes uint16) int32 {
t256 := int16(uint16(tempBytes>>8)<<8 | uint16(tempBytes&0xFF))
t1000 := int32(t256) / 64 * 250
return t1000
return int32(data[0])*1000 + int32((data[1]>>6)*25)*10, nil
}
// uint8ToBCD converts a byte to BCD for the DS3231
@@ -444,11 +165,3 @@ func hoursBCDToInt(value uint8) (hour int) {
}
return
}
// dowToDS3231 converts the day of the week to internal DS3231 format
func dowToDS3231(d int) int {
if d == 0 {
return 7
}
return d
}
-76
View File
@@ -1,76 +0,0 @@
package ds3231
import (
"testing"
)
func TestPositiveMilliCelsius(t *testing.T) {
t1000 := milliCelsius(0)
if t1000 != 0 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b0000000001000000)
if t1000 != 250 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b0000000010000000)
if t1000 != 500 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b0000000011000000)
if t1000 != 750 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b0000000100000000)
if t1000 != 1000 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b0000001000000000)
if t1000 != 2000 {
t.Fatal(t1000)
}
// highest temperature is 127.750C
t1000 = milliCelsius(0b0111111111000000)
if t1000 != 127750 {
t.Fatal(t1000)
}
}
func TestNegativeMilliCelsius(t *testing.T) {
t1000 := milliCelsius(0b1111111111000000)
if t1000 != -250 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b1111111110000000)
if t1000 != -500 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b1111111101000000)
if t1000 != -750 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b1111111100000000)
if t1000 != -1000 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0b1111111000000000)
if t1000 != -2000 {
t.Fatal(t1000)
}
// lowest temperature is -128.000C
t1000 = milliCelsius(0b1000000000000000)
if t1000 != -128000 {
t.Fatal(t1000)
}
}
-49
View File
@@ -46,52 +46,3 @@ const (
AlarmTwo Mode = 4
ModeAlarmBoth Mode = 5
)
// SQW Pin Modes
type SqwPinMode uint8
const (
SQW_OFF SqwPinMode = 0x1C
SQW_1HZ SqwPinMode = 0x00
SQW_1KHZ SqwPinMode = 0x08
SQW_4KHZ SqwPinMode = 0x10
SQW_8KHZ SqwPinMode = 0x18
)
// Alarm1 Modes define which parts of the set alarm time has to match the current timestamp of the clock device for
// alarm1 to fire
type Alarm1Mode uint8
const (
// Alarm1 fires every second
A1_PER_SECOND Alarm1Mode = 0x0F
// Alarm1 fires when the seconds match
A1_SECOND Alarm1Mode = 0x0E
// Alarm1 fires when both seconds and minutes match
A1_MINUTE Alarm1Mode = 0x0C
// Alarm1 fires when seconds, minutes and hours match
A1_HOUR Alarm1Mode = 0x08
// Alarm1 fires when seconds, minutes, hours and the day of the month match
A1_DATE Alarm1Mode = 0x00
// Alarm1 fires when seconds, minutes, hours and the day of the week match
A1_DAY Alarm1Mode = 0x10
)
// Alarm2 Modes define which parts of the set alarm time has to match the current timestamp of the clock device for
// alarm2 to fire.
//
// Alarm2 only supports matching down to the minute unlike alarm1 which supports matching down to the second.
type Alarm2Mode uint8
const (
// Alarm2 fires every minute
A2_PER_MINUTE Alarm2Mode = 0x07
// Alarm2 fires when the minutes match
A2_MINUTE Alarm2Mode = 0x06
// Alarm2 fires when both minutes and hours match
A2_HOUR Alarm2Mode = 0x04
// Alarm2 fires when minutes, hours and the day of the month match
A2_DATE Alarm2Mode = 0x00
// Alarm2 fires when minutes, hours and the day of the week match
A2_DAY Alarm2Mode = 0x08
)
-34
View File
@@ -1,34 +0,0 @@
package encoders
type QuadratureDevice struct {
cfg QuadratureConfig
impl quadratureImpl
}
type QuadratureConfig struct {
Precision int
}
type quadratureImpl interface {
configure(cfg QuadratureConfig) error
readValue() int
writeValue(int)
}
func (enc *QuadratureDevice) Configure(cfg QuadratureConfig) error {
if cfg.Precision < 1 {
cfg.Precision = 4
}
enc.cfg = cfg
return enc.impl.configure(cfg)
}
// Position returns the stored int value for the encoder
func (enc *QuadratureDevice) Position() int {
return enc.impl.readValue() / enc.cfg.Precision
}
// SetPosition overwrites the currently stored value with the specified int value
func (enc *QuadratureDevice) SetPosition(v int) {
enc.impl.writeValue(v * enc.cfg.Precision)
}
-69
View File
@@ -1,69 +0,0 @@
//go:build tinygo && (rp2040 || rp2350 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
// Implementation based on:
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
// Note: build constraints in this file list targets that define machine.PinToggle.
// If this is supported for additional targets in the future, they can be added above.
package encoders
import (
"machine"
"runtime/volatile"
)
var (
states = []int8{0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}
)
// NewQuadratureViaInterrupt returns a rotary encoder device that uses GPIO
// interrupts and a lookup table to keep track of quadrature state changes.
//
// This constructur is only available for TinyGo targets for which machine.PinToggle
// is defined as a valid interrupt type.
func NewQuadratureViaInterrupt(pinA, pinB machine.Pin) *QuadratureDevice {
return &QuadratureDevice{impl: &quadInterruptImpl{pinA: pinA, pinB: pinB, oldAB: 0b00000011}}
}
type quadInterruptImpl struct {
pinA machine.Pin
pinB machine.Pin
// precision int
oldAB int
value volatile.Register32
}
func (enc *quadInterruptImpl) configure(cfg QuadratureConfig) error {
enc.pinA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
enc.pinA.SetInterrupt(machine.PinToggle, enc.interrupt)
enc.pinB.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
enc.pinB.SetInterrupt(machine.PinToggle, enc.interrupt)
return nil
}
func (enc *quadInterruptImpl) interrupt(pin machine.Pin) {
aHigh, bHigh := enc.pinA.Get(), enc.pinB.Get()
enc.oldAB <<= 2
if aHigh {
enc.oldAB |= 1 << 1
}
if bHigh {
enc.oldAB |= 1
}
enc.writeValue(enc.readValue() + int(states[enc.oldAB&0x0f]))
}
// readValue gets the value using volatile operations and returns it as an int
func (enc *quadInterruptImpl) readValue() int {
return int(enc.value.Get())
}
// writeValue set the value to the specified int using volatile operations
func (enc *quadInterruptImpl) writeValue(v int) {
enc.value.Set(uint32(v))
}
-225
View File
@@ -1,225 +0,0 @@
// Package ens160 provides a driver for the ScioSense ENS160 digital gas sensor.
//
// Datasheet: https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
package ens160
import (
"encoding/binary"
"errors"
"time"
"tinygo.org/x/drivers"
)
const (
defaultTimeout = 30 * time.Millisecond
shortTimeout = 1 * time.Millisecond
)
// Conversion constants for environment data compensation.
const (
kelvinOffsetMilli = 273150 // 273.15 K in milli-units
tempRawFactor = 64 // As per datasheet for TEMP_IN
humRawFactor = 512 // As per datasheet for RH_IN
milliFactor = 1000 // For converting from milli-units
roundingTerm = milliFactor / 2 // For rounding before integer division
)
// validityStrings provides human-readable descriptions for validity flags.
var validityStrings = [...]string{
ValidityNormalOperation: "normal operation",
ValidityWarmUpPhase: "warm-up phase, wait ~3 minutes for valid data",
ValidityInitialStartUpPhase: "initial start-up phase, wait ~1 hour for valid data",
ValidityInvalidOutput: "invalid output",
}
// Device wraps an I2C connection to an ENS160 device.
type Device struct {
bus drivers.I2C // I²C implementation
addr uint16 // 7bit bus address, promoted to uint16 per drivers.I2C
// shadow registers / last measurements
lastTvocPPB uint16
lastEco2PPM uint16
lastAqiUBA uint8
lastValidity uint8 // Store the latest validity status
// preallocated buffers
wbuf [5]byte // longest write: reg + 4bytes (TEMP+RH)
rbuf [5]byte // longest read: DATA burst (5bytes)
}
// New returns a new ENS160 driver.
func New(bus drivers.I2C, addr uint16) *Device {
if addr == 0 {
addr = DefaultAddress
}
return &Device{
bus: bus,
addr: addr,
lastValidity: ValidityInvalidOutput,
}
}
// Connected returns whether a ENS160 has been found.
func (d *Device) Connected() bool {
d.wbuf[0] = regPartID
err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:2])
return err == nil && d.rbuf[0] == LowPartID && d.rbuf[1] == HighPartID
}
// Configure sets up the device for reading.
func (d *Device) Configure() error {
// 1. Soft-reset. The device will automatically enter IDLE mode.
if err := d.write1(regOpMode, ModeReset); err != nil {
return err
}
time.Sleep(defaultTimeout)
// 2. Clear GPR registers, then go to STANDARD mode.
if err := d.write1(regCommand, cmdClrGPR); err != nil {
return err
}
time.Sleep(defaultTimeout)
if err := d.write1(regOpMode, ModeStandard); err != nil {
return err
}
time.Sleep(defaultTimeout)
return nil
}
// calculateTempRaw converts temperature from milli-degrees Celsius to the sensor's raw format.
func calculateTempRaw(tempMilliC int32) uint16 {
// Clip temperature
const (
minC = -40 * 1000
maxC = 85 * 1000
)
if tempMilliC < minC {
tempMilliC = minC
} else if tempMilliC > maxC {
tempMilliC = maxC
}
// Integer fixed-point conversion to format required by the sensor.
// Formula from datasheet: T_IN = (T_ambient_C + 273.15) * 64
return uint16((((tempMilliC + kelvinOffsetMilli) * tempRawFactor) + roundingTerm) / milliFactor)
}
// calculateHumRaw converts relative humidity from milli-percent to the sensor's raw format.
func calculateHumRaw(rhMilliPct int32) uint16 {
// Clip humidity
if rhMilliPct < 0 {
rhMilliPct = 0
} else if rhMilliPct > 100*1000 {
rhMilliPct = 100 * 1000
}
// Integer fixed-point conversion to format required by the sensor.
// Formula from datasheet: RH_IN = (RH_ambient_% * 512)
return uint16(((rhMilliPct * humRawFactor) + roundingTerm) / milliFactor)
}
// SetEnvDataMilli sets the ambient temperature and humidity for compensation.
//
// tempMilliC is the temperature in milli-degrees Celsius.
// rhMilliPct is the relative humidity in milli-percent.
func (d *Device) SetEnvDataMilli(tempMilliC, rhMilliPct int32) error {
tempRaw := calculateTempRaw(tempMilliC)
humRaw := calculateHumRaw(rhMilliPct)
d.wbuf[0] = regTempIn // start address (autoincrement)
binary.LittleEndian.PutUint16(d.wbuf[1:3], tempRaw)
binary.LittleEndian.PutUint16(d.wbuf[3:5], humRaw)
return d.bus.Tx(d.addr, d.wbuf[:5], nil)
}
// Update refreshes the concentration measurements.
func (d *Device) Update(which drivers.Measurement) error {
if which&drivers.Concentration == 0 {
return nil // nothing requested
}
const maxTries = 1000
var (
status uint8
validity uint8
)
var gotData bool
// Poll DEVICE_STATUS until NEWDAT or timeout
for range maxTries {
var err error
status, err = d.read1(regStatus)
if err != nil {
return err
}
if status&statusSTATER != 0 {
return errors.New("ENS160: error (STATER set)")
}
validity = (status & statusValidityMask) >> statusValidityShift
if status&statusNEWDAT != 0 {
gotData = true
break // Always break when data available
}
time.Sleep(shortTimeout)
}
if !gotData {
return errors.New("ENS160: timeout waiting for NEWDAT")
}
// Burst-read data regardless of validity state
d.wbuf[0] = regAQI
if err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:5]); err != nil {
return errors.New("ENS160: burst read failed")
}
d.lastAqiUBA = d.rbuf[0]
d.lastTvocPPB = binary.LittleEndian.Uint16(d.rbuf[1:3])
d.lastEco2PPM = binary.LittleEndian.Uint16(d.rbuf[3:5])
d.lastValidity = validity // Store the validity status
return nil
}
// TVOC returns the last totalVOC concentration in partsperbillion.
func (d *Device) TVOC() uint16 { return d.lastTvocPPB }
// ECO2 returns the last equivalent CO₂ concentration in partspermillion.
func (d *Device) ECO2() uint16 { return d.lastEco2PPM }
// AQI returns the last AirQuality Index according to UBA (15).
func (d *Device) AQI() uint8 { return d.lastAqiUBA }
// Validity returns the current operating state of the sensor.
func (d *Device) Validity() uint8 {
return d.lastValidity
}
// ValidityString returns a human-readable string describing the current validity status.
func (d *Device) ValidityString() string {
if int(d.lastValidity) < len(validityStrings) {
return validityStrings[d.lastValidity]
}
return "unknown"
}
// write1 writes a single byte to a register.
func (d *Device) write1(reg, val uint8) error {
d.wbuf[0] = reg
d.wbuf[1] = val
return d.bus.Tx(d.addr, d.wbuf[:2], nil)
}
// read1 reads a single byte from a register.
func (d *Device) read1(reg uint8) (uint8, error) {
d.wbuf[0] = reg
if err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:1]); err != nil {
return 0, err
}
return d.rbuf[0], nil
}
-54
View File
@@ -1,54 +0,0 @@
package ens160
import (
"testing"
)
func TestCalculateTempRaw(t *testing.T) {
testCases := []struct {
name string
tempMilliC int32
expectedRaw uint16
}{
{"25°C", 25000, 19082},
{"-10.5°C", -10500, 16810},
{"Min temp", -40000, 14922},
{"Below min", -50000, 14922},
{"Max temp", 85000, 22922},
{"Above max", 90000, 22922},
{"Zero", 0, 17482},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
raw := calculateTempRaw(tc.tempMilliC)
if raw != tc.expectedRaw {
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
}
})
}
}
func TestCalculateHumRaw(t *testing.T) {
testCases := []struct {
name string
rhMilliPct int32
expectedRaw uint16
}{
{"50%", 50000, 25600},
{"0%", 0, 0},
{"100%", 100000, 51200},
{"Below 0%", -10000, 0},
{"Above 100%", 110000, 51200},
{"33.3%", 33300, 17050},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
raw := calculateHumRaw(tc.rhMilliPct)
if raw != tc.expectedRaw {
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
}
})
}
}
-65
View File
@@ -1,65 +0,0 @@
package ens160
// DefaultAddress is the default I2C address for the ENS160 when the ADDR pin is
// connected to high (3.3V). When connected to low (GND), the address is 0x52.
const DefaultAddress = 0x53
// Registers
const (
regPartID = 0x00
regOpMode = 0x10
regConfig = 0x11
regCommand = 0x12
regTempIn = 0x13
regRhIn = 0x15
regStatus = 0x20
regAQI = 0x21
regTVOC = 0x22
regECO2 = 0x24
regDataT = 0x30
regDataRH = 0x32
regMISR = 0x38
regGPRWrite = 0x40
regGPRRead = 0x48
)
// Operating modes
const (
ModeDeepSleep = 0x00
ModeIdle = 0x01
ModeStandard = 0x02
ModeReset = 0xF0
)
// Status register bits
const (
statusSTATAS = 1 << 7
statusSTATER = 1 << 6
statusValidityMask = 0x0C
statusValidityShift = 2
statusNEWDAT = 1 << 1
statusNEWGPR = 1 << 0
)
// Validity flags
const (
ValidityNormalOperation = 0x00
ValidityWarmUpPhase = 0x01 // need ~3 minutes until valid data
ValidityInitialStartUpPhase = 0x02 // need ~1 hour until valid data
ValidityInvalidOutput = 0x03
)
// Commands
const (
cmdNOP = 0x00
cmdGetAppVer = 0x0E
cmdClrGPR = 0xCC
)
// Part IDs
const (
LowPartID = 0x60
HighPartID = 0x01
)
+20
View File
@@ -0,0 +1,20 @@
package espat
import (
"time"
"tinygo.org/x/drivers/net"
)
func (d *Device) ConnectToAccessPoint(ssid, pass string, timeout time.Duration) error {
if len(ssid) == 0 {
return net.ErrWiFiMissingSSID
}
d.SetWifiMode(WifiModeClient)
return d.ConnectToAP(ssid, pass, int(timeout.Seconds()))
}
func (d *Device) Disconnect() error {
return d.DisconnectFromAP()
}
+34 -296
View File
@@ -15,302 +15,41 @@
//
// AT command set:
// https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf
//
// 02/2023 sfeldma@gmail.com Heavily modified to use netdev interface
package espat // import "tinygo.org/x/drivers/espat"
import (
"errors"
"fmt"
"machine"
"net"
"net/netip"
"strconv"
"strings"
"sync"
"time"
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/net"
)
type Config struct {
// UART config
Uart *machine.UART
Tx machine.Pin
Rx machine.Pin
}
type socket struct {
inUse bool
protocol int
laddr netip.AddrPort
}
// Device wraps UART connection to the ESP8266/ESP32.
type Device struct {
cfg *Config
uart *machine.UART
bus drivers.UART
// command responses that come back from the ESP8266/ESP32
response []byte
// data received from a TCP/UDP connection forwarded by the ESP8266/ESP32
data []byte
socket socket
mu sync.Mutex
socketdata []byte
}
func NewDevice(cfg *Config) *Device {
return &Device{
cfg: cfg,
response: make([]byte, 1500),
data: make([]byte, 0, 1500),
}
// ActiveDevice is the currently configured Device in use. There can only be one.
var ActiveDevice *Device
// New returns a new espat driver. Pass in a fully configured UART bus.
func New(b drivers.UART) *Device {
return &Device{bus: b, response: make([]byte, 512), socketdata: make([]byte, 0, 1024)}
}
func (d *Device) NetConnect(params *netlink.ConnectParams) error {
if len(params.Ssid) == 0 {
return netlink.ErrMissingSSID
}
d.uart = d.cfg.Uart
d.uart.Configure(machine.UARTConfig{TX: d.cfg.Tx, RX: d.cfg.Rx})
// Connect to ESP8266/ESP32
fmt.Printf("Connecting to device...")
for i := 0; i < 5; i++ {
if d.Connected() {
break
}
time.Sleep(1 * time.Second)
}
if !d.Connected() {
fmt.Printf("FAILED\r\n")
return netlink.ErrConnectFailed
}
fmt.Printf("CONNECTED\r\n")
// Connect to Wifi AP
fmt.Printf("Connecting to Wifi SSID '%s'...", params.Ssid)
d.SetWifiMode(WifiModeClient)
err := d.ConnectToAP(params.Ssid, params.Passphrase, 10 /* secs */)
if err != nil {
fmt.Printf("FAILED\r\n")
return err
}
fmt.Printf("CONNECTED\r\n")
ip, err := d.Addr()
if err != nil {
return err
}
fmt.Printf("DHCP-assigned IP: %s\r\n", ip)
fmt.Printf("\r\n")
return nil
}
func (d *Device) NetDisconnect() {
d.DisconnectFromAP()
fmt.Printf("\r\nDisconnected from Wifi\r\n\r\n")
}
func (d *Device) NetNotify(cb func(netlink.Event)) {
fmt.Printf("\r\n%s\r\n", netlink.ErrNotSupported)
}
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
ip, err := d.GetDNS(name)
if err != nil {
return netip.Addr{}, err
}
return netip.ParseAddr(ip)
}
func (d *Device) GetHardwareAddr() (net.HardwareAddr, error) {
return net.HardwareAddr{}, netlink.ErrNotSupported
}
func (d *Device) Addr() (netip.Addr, error) {
resp, err := d.GetClientIP()
if err != nil {
return netip.Addr{}, err
}
prefix := "+CIPSTA:ip:"
for _, line := range strings.Split(resp, "\n") {
if ok := strings.HasPrefix(line, prefix); ok {
ip := line[len(prefix)+1 : len(line)-2]
return netip.ParseAddr(ip)
}
}
return netip.Addr{}, fmt.Errorf("Error getting IP address")
}
func (d *Device) Socket(domain int, stype int, protocol int) (int, error) {
switch domain {
case netdev.AF_INET:
default:
return -1, netdev.ErrFamilyNotSupported
}
switch {
case protocol == netdev.IPPROTO_TCP && stype == netdev.SOCK_STREAM:
case protocol == netdev.IPPROTO_TLS && stype == netdev.SOCK_STREAM:
case protocol == netdev.IPPROTO_UDP && stype == netdev.SOCK_DGRAM:
default:
return -1, netdev.ErrProtocolNotSupported
}
// Only supporting single connection mode, so only one socket at a time
if d.socket.inUse {
return -1, netdev.ErrNoMoreSockets
}
d.socket.inUse = true
d.socket.protocol = protocol
return 0, nil
}
func (d *Device) Bind(sockfd int, ip netip.AddrPort) error {
d.socket.laddr = ip
return nil
}
func (d *Device) Connect(sockfd int, host string, ip netip.AddrPort) error {
var err error
var addr = ip.Addr().String()
var rport = strconv.Itoa(int(ip.Port()))
var lport = strconv.Itoa(int(d.socket.laddr.Port()))
switch d.socket.protocol {
case netdev.IPPROTO_TCP:
err = d.ConnectTCPSocket(addr, rport)
case netdev.IPPROTO_UDP:
err = d.ConnectUDPSocket(addr, rport, lport)
case netdev.IPPROTO_TLS:
err = d.ConnectSSLSocket(host, rport)
}
if err != nil {
if host == "" {
return fmt.Errorf("Connect to %s timed out", ip)
} else {
return fmt.Errorf("Connect to %s:%d timed out", host, ip.Port())
}
}
return nil
}
func (d *Device) Listen(sockfd int, backlog int) error {
switch d.socket.protocol {
case netdev.IPPROTO_UDP:
default:
return netdev.ErrProtocolNotSupported
}
return nil
}
func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error) {
return -1, netip.AddrPort{}, netdev.ErrNotSupported
}
func (d *Device) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
// Check if we've timed out
if !deadline.IsZero() {
if time.Now().After(deadline) {
return -1, netdev.ErrTimeout
}
}
err := d.StartSocketSend(len(buf))
if err != nil {
return -1, err
}
n, err := d.Write(buf)
if err != nil {
return -1, err
}
_, err = d.Response(1000)
if err != nil {
return -1, err
}
return n, err
}
func (d *Device) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
d.mu.Lock()
defer d.mu.Unlock()
// Break large bufs into chunks so we don't overrun the hw queue
chunkSize := 1436
for i := 0; i < len(buf); i += chunkSize {
end := i + chunkSize
if end > len(buf) {
end = len(buf)
}
_, err := d.sendChunk(sockfd, buf[i:end], deadline)
if err != nil {
return -1, err
}
}
return len(buf), nil
}
func (d *Device) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
d.mu.Lock()
defer d.mu.Unlock()
var length = len(buf)
// Limit length read size to chunk large read requests
if length > 1436 {
length = 1436
}
for {
// Check if we've timed out
if !deadline.IsZero() {
if time.Now().After(deadline) {
return -1, netdev.ErrTimeout
}
}
n, err := d.ReadSocket(buf[:length])
if err != nil {
return -1, err
}
if n == 0 {
d.mu.Unlock()
time.Sleep(100 * time.Millisecond)
d.mu.Lock()
continue
}
return n, nil
}
}
func (d *Device) Close(sockfd int) error {
d.mu.Lock()
defer d.mu.Unlock()
d.socket.inUse = false
return d.DisconnectSocket()
}
func (d *Device) SetSockOpt(sockfd int, level int, opt int, value interface{}) error {
return netdev.ErrNotSupported
// Configure sets up the device for communication.
func (d Device) Configure() {
ActiveDevice = &d
net.ActiveDevice = ActiveDevice
}
// Connected checks if there is communication with the ESP8266/ESP32.
@@ -318,7 +57,7 @@ func (d *Device) Connected() bool {
d.Execute(Test)
// handle response here, should include "OK"
_, err := d.Response(1000)
_, err := d.Response(100)
if err != nil {
return false
}
@@ -327,12 +66,12 @@ func (d *Device) Connected() bool {
// Write raw bytes to the UART.
func (d *Device) Write(b []byte) (n int, err error) {
return d.uart.Write(b)
return d.bus.Write(b)
}
// Read raw bytes from the UART.
func (d *Device) Read(b []byte) (n int, err error) {
return d.uart.Read(b)
return d.bus.Read(b)
}
// how long in milliseconds to pause after sending AT commands
@@ -361,10 +100,9 @@ func (d Device) Set(cmd, params string) error {
// Version returns the ESP8266/ESP32 firmware version info.
func (d Device) Version() []byte {
d.Execute(Version)
r, err := d.Response(2000)
r, err := d.Response(100)
if err != nil {
//return []byte("unknown")
return []byte(err.Error())
return []byte("unknown")
}
return r
}
@@ -394,16 +132,16 @@ func (d *Device) ReadSocket(b []byte) (n int, err error) {
d.Response(300)
count := len(b)
if len(b) >= len(d.data) {
if len(b) >= len(d.socketdata) {
// copy it all, then clear socket data
count = len(d.data)
copy(b, d.data[:count])
d.data = d.data[:0]
count = len(d.socketdata)
copy(b, d.socketdata[:count])
d.socketdata = d.socketdata[:0]
} else {
// copy all we can, then keep the remaining socket data around
copy(b, d.data[:count])
copy(d.data, d.data[count:])
d.data = d.data[:len(d.data)-count]
copy(b, d.socketdata[:count])
copy(d.socketdata, d.socketdata[count:])
d.socketdata = d.socketdata[:len(d.socketdata)-count]
}
return count, nil
@@ -419,11 +157,11 @@ func (d *Device) Response(timeout int) ([]byte, error) {
retries := timeout / pause
for {
size = d.uart.Buffered()
size = d.bus.Buffered()
if size > 0 {
end += size
d.uart.Read(d.response[start:end])
d.bus.Read(d.response[start:end])
// if "+IPD" then read socket data
if strings.Contains(string(d.response[:end]), "+IPD") {
@@ -466,18 +204,18 @@ func (d *Device) parseIPD(end int) error {
val := string(d.response[s+5 : e])
// TODO: verify count
v, err := strconv.Atoi(val)
_, err := strconv.Atoi(val)
if err != nil {
// not expected data here. what to do?
return err
}
// load up the socket data
d.data = append(d.data, d.response[e+1:e+1+v]...)
d.socketdata = append(d.socketdata, d.response[e+1:end]...)
return nil
}
// IsSocketDataAvailable returns of there is socket data available
func (d *Device) IsSocketDataAvailable() bool {
return len(d.data) > 0 || d.uart.Buffered() > 0
return len(d.socketdata) > 0 || d.bus.Buffered() > 0
}
+1 -1
View File
@@ -51,7 +51,7 @@ func (d *Device) ConnectTCPSocket(addr, port string) error {
// ConnectUDPSocket creates a new UDP connection for the ESP8266/ESP32.
func (d *Device) ConnectUDPSocket(addr, sendport, listenport string) error {
protocol := "UDP"
val := "\"" + protocol + "\",\"" + addr + "\"," + sendport + "," + listenport + ",0"
val := "\"" + protocol + "\",\"" + addr + "\"," + sendport + "," + listenport + ",2"
err := d.Set(TCPConnect, val)
if err != nil {
return err
+4 -1
View File
@@ -44,7 +44,10 @@ func (d *Device) ConnectToAP(ssid, pwd string, ws int) error {
d.Set(ConnectAP, val)
_, err := d.Response(ws * 1000)
return err
if err != nil {
return err
}
return nil
}
// DisconnectFromAP disconnects the ESP8266/ESP32 from the current access point.
-42
View File
@@ -1,42 +0,0 @@
package main
import (
"image/color"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/adafruit4650"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
dev := adafruit4650.New(machine.I2C0)
err := dev.Configure()
if err != nil {
panic(err)
}
drawPlus(&dev)
drawHelloWorld(&dev)
err = dev.Display()
if err != nil {
panic(err)
}
}
func drawPlus(d drivers.Displayer) {
for i := int16(0); i < 128; i++ {
d.SetPixel(i, 32, color.RGBA{R: 1})
}
for i := int16(0); i < 64; i++ {
d.SetPixel(64, i, color.RGBA{R: 1})
}
}
func drawHelloWorld(d drivers.Displayer) {
tinyfont.WriteLine(d, &freemono.Regular9pt7b, 0, 32, "Hello World!", color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
}
-66
View File
@@ -1,66 +0,0 @@
package main
import (
"machine"
"machine/usb/hid/mouse"
"math"
"time"
"tinygo.org/x/drivers/as560x"
)
func main() {
// Let's use the AS5600 to make the world's most useless mouse with just a single X-axis & no buttons (!)
machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
SDA: machine.GPIO4,
SCL: machine.GPIO5,
})
as5600 := as560x.NewAS5600(machine.I2C0)
as5600.Configure(as560x.Config{})
mouse := mouse.New()
lastAngle := -1
for {
time.Sleep(time.Millisecond * 10)
// Get the magnet status of the AS5600
magnetDetected, magnetStrength, err := as5600.MagnetStatus()
if err != nil {
continue
}
// Get the raw angle from the AS5600
angle, _, err := as5600.RawAngle(as560x.ANGLE_NATIVE)
if err != nil {
continue
}
str := ""
if !magnetDetected {
str += "NOT "
}
str += "detected. Strength is "
switch magnetStrength {
case as560x.MagnetTooWeak:
str += "too weak"
case as560x.MagnetTooStrong:
str += "too strong"
default:
str += "ok"
}
println("Raw angle:", angle, "Magnet was", str)
if lastAngle != -1 {
diff := int(angle) - lastAngle
// correct the zero crossover glitch
if diff < -0xc00 {
diff += 0xfff
} else if diff > 0xc00 {
diff -= 0xfff
}
// debounce the noise (could use the sensor's filters/hysteresis instead?)
if math.Abs(float64(diff)) > 2 {
// move the mouse x-axis in response to the AS5600
mouse.Move(diff, 0)
}
}
lastAngle = int(angle)
}
}
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"machine"
"encoding/hex"
"time"
"tinygo.org/x/drivers/ateccx08"
)
func main() {
time.Sleep(5 * time.Second)
println("Looking for ATECCx08...")
machine.I2C0.Configure(machine.I2CConfig{})
atecc := ateccx08.New(machine.I2C0)
atecc.Configure()
if !atecc.Connected() {
for {
println("could not connect to ATECCx08")
time.Sleep(time.Second)
}
}
version, _ := atecc.Version()
println(version.String(), "started")
if !atecc.IsLocked() {
for i := 10; i > 0; i-- {
println(version.String(), "is not locked. Locking in", i, "seconds...")
time.Sleep(time.Second)
}
// locks the Configuration zone... PERMANENTLY!
atecc.Lock(0)
}
println(version.String(), "locked.")
for {
data, err := atecc.Random()
if err != nil {
println(err)
}
println(hex.EncodeToString(data[:]))
time.Sleep(500 * time.Millisecond)
}
}
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"machine"
"crypto/rand"
"encoding/hex"
"time"
"tinygo.org/x/drivers/ateccx08"
)
var atecc *ateccx08.Device
func main() {
time.Sleep(5 * time.Second)
println("Looking for ATECCx08...")
machine.I2C0.Configure(machine.I2CConfig{})
atecc = ateccx08.New(machine.I2C0)
atecc.Configure()
if !atecc.Connected() {
for {
println("could not connect to ATECCx08")
time.Sleep(time.Second)
}
}
version, _ := atecc.Version()
println(version.String(), "started")
if !atecc.IsLocked() {
for {
println(version.String(), "is not locked. Random numbers will not actually be random.")
time.Sleep(time.Second)
}
}
var result [13]byte
for {
rand.Read(result[:])
encodedString := hex.EncodeToString(result[:])
println(encodedString)
time.Sleep(500 * time.Millisecond)
}
}
+52
View File
@@ -0,0 +1,52 @@
// connects the Go crypto/rand package to the random number generation
// on the ATECCx08 cryptographic processor.
package main
import (
"crypto/rand"
"errors"
)
var (
errNoATECC = errors.New("no ATECCx08")
)
func init() {
rand.Reader = &reader{}
}
type reader struct{}
func (r *reader) Read(b []byte) (n int, err error) {
if len(b) == 0 {
return
}
if atecc == nil {
return 0, errNoATECC
}
if !atecc.IsLocked() {
panic("ATECCx08 is not locked and cannot produce random numbers!")
}
rnds, err := atecc.Random()
if err != nil {
return 0, err
}
for i := 0; i < len(b); i += 32 {
if i+32 > len(b) {
copy(b[i:], rnds[:(len(b)-i)])
break
}
copy(b[i:], rnds[:])
rnds, err = atecc.Random()
if err != nil {
return 0, err
}
}
return len(b), nil
}
-54
View File
@@ -1,54 +0,0 @@
package main
// Smoke test for the BMA421/BMA425 sensors.
// Warning: this code has _not been tested_. It's only here as a smoke test.
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/bma42x"
)
func main() {
time.Sleep(5 * time.Second)
i2cBus := machine.I2C1
i2cBus.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
SDA: machine.SDA_PIN,
SCL: machine.SCL_PIN,
})
sensor := bma42x.NewI2C(i2cBus, bma42x.Address)
err := sensor.Configure(bma42x.Config{
Device: bma42x.DeviceBMA421 | bma42x.DeviceBMA425,
Features: bma42x.FeatureStepCounting,
})
if err != nil {
println("could not configure BMA421/BMA425:", err)
return
}
if !sensor.Connected() {
println("BMA42x not connected")
return
}
for {
time.Sleep(time.Second)
err := sensor.Update(drivers.Acceleration | drivers.Temperature)
if err != nil {
println("Error reading sensor", err)
continue
}
fmt.Printf("Temperature: %.2f °C\n", float32(sensor.Temperature())/1000)
accelX, accelY, accelZ := sensor.Acceleration()
fmt.Printf("Acceleration: %.2fg %.2fg %.2fg\n", float32(accelX)/1e6, float32(accelY)/1e6, float32(accelZ)/1e6)
}
}
-3
View File
@@ -27,9 +27,6 @@ func main() {
pressure, _ := sensor.ReadPressure()
println("Pressure", float32(pressure)/100000, "hPa")
altitude, _ := sensor.ReadAltitude()
println("Altitude", altitude, "meters")
time.Sleep(2 * time.Second)
}
}
-66
View File
@@ -1,66 +0,0 @@
// Package main provides a basic example of using the BNO08x driver
// to read rotation vector (quaternion) data from the sensor.
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/bno08x"
)
func main() {
time.Sleep(2 * time.Second) // Wait for sensor to power up
// Initialize I2C bus
i2c := machine.I2C0
err := i2c.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
})
if err != nil {
println("Failed to configure I2C:", err.Error())
return
}
println("Initializing BNO08x sensor...")
// Create and configure sensor using I2C
sensor := bno08x.NewI2C(i2c)
err = sensor.Configure(bno08x.Config{})
if err != nil {
println("Failed to configure sensor:", err.Error())
return
}
println("Sensor initialized successfully")
// Enable Game Rotation Vector reports at 100Hz (10000 microseconds = 10ms interval)
// Using Game Rotation Vector (0x08) to match the working channel_debug test
err = sensor.EnableReport(bno08x.SensorGameRotationVector, 10000)
if err != nil {
println("Failed to enable game rotation vector:", err.Error())
return
}
println("Reading rotation vectors...")
println("Format: Real I J K Accuracy")
// Add a delay after enabling reports (Arduino does this)
time.Sleep(100 * time.Millisecond)
// Main loop - read and display quaternion data
for {
event, ok := sensor.GetSensorEvent()
if ok && (event.ID() == bno08x.SensorRotationVector || event.ID() == bno08x.SensorGameRotationVector) {
q := event.Quaternion()
if event.ID() == bno08x.SensorRotationVector {
println(q.Real, q.I, q.J, q.K, event.QuaternionAccuracy())
} else {
// GameRotationVector doesn't have accuracy
println(q.Real, q.I, q.J, q.K)
}
}
// Arduino uses 10ms delay in loop
time.Sleep(10 * time.Millisecond)
}
}
-17
View File
@@ -1,17 +0,0 @@
package main
import (
"time"
"tinygo.org/x/drivers/delay"
)
func main() {
time.Sleep(time.Second) // wait for a serial console
start := time.Now()
for i := 0; i < 2000; i++ {
delay.Sleep(50 * time.Microsecond)
}
duration := time.Since(start)
println("sleep of 2000*50µs (100ms) took:", duration.String())
}
-58
View File
@@ -1,58 +0,0 @@
package main
import (
"encoding/hex"
"machine"
"time"
"tinygo.org/x/drivers/onewire"
"tinygo.org/x/drivers/ds18b20"
)
func main() {
// Define pin for DS18B20
pin := machine.D2
ow := onewire.New(pin)
romIDs, err := ow.Search(onewire.SEARCH_ROM)
if err != nil {
println(err)
}
sensor := ds18b20.New(ow)
for {
time.Sleep(3 * time.Second)
println()
println("Device:", machine.Device)
println()
println("Request Temperature.")
for _, romid := range romIDs {
println("Sensor RomID: ", hex.EncodeToString(romid))
sensor.RequestTemperature(romid)
}
// wait 750ms or more for DS18B20 convert T
time.Sleep(1 * time.Second)
println()
println("Read Temperature")
for _, romid := range romIDs {
raw, err := sensor.ReadTemperatureRaw(romid)
if err != nil {
println(err)
}
println()
println("Sensor RomID: ", hex.EncodeToString(romid))
println("Temperature Raw value: ", hex.EncodeToString(raw))
t, err := sensor.ReadTemperature(romid)
if err != nil {
println(err)
}
println("Temperature in celsius milli degrees (°C/1000): ", t)
}
}
}
-74
View File
@@ -1,74 +0,0 @@
// Connects to an DS3231 I2C Real Time Clock (RTC) and sets both alarms. It then repeatedly checks
// if the alarms are firing and prints out a message if that is the case.
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/ds3231"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
rtc := ds3231.New(machine.I2C0)
rtc.Configure()
valid := rtc.IsTimeValid()
if !valid {
date := time.Date(2019, 12, 05, 20, 34, 12, 0, time.UTC)
rtc.SetTime(date)
}
// Set alarm1 so it triggers when the seconds match 59 => repeats every minute at dd:hh:mm:59
if err := rtc.SetAlarm1(time.Date(0, 0, 0, 0, 0, 59, 0, time.UTC), ds3231.A1_SECOND); err != nil {
println("Error while setting Alarm1")
}
if err := rtc.SetEnabledAlarm1(true); err != nil {
println("Error while enabling Alarm1")
}
// Set alarm2 so it triggers when the minutes match 35 => repeats every hour at dd:hh:35:ss
if err := rtc.SetAlarm2(time.Date(0, 0, 0, 0, 35, 0, 0, time.UTC), ds3231.A2_MINUTE); err != nil {
println("Error while setting Alarm2")
}
if err := rtc.SetEnabledAlarm2(true); err != nil {
println("Error while enabling Alarm2")
}
running := rtc.IsRunning()
if !running {
err := rtc.SetRunning(true)
if err != nil {
println("Error configuring RTC")
}
}
for {
dt, err := rtc.ReadTime()
if err != nil {
println("Error reading date:", err)
continue
}
a1 := rtc.IsAlarm1Fired()
a2 := rtc.IsAlarm2Fired()
println(dt.Format(time.DateTime), "A1:", a1, "A2:", a2)
if a1 {
if err := rtc.ClearAlarm1(); err != nil {
println("Error while clearing alarm1")
}
}
if a2 {
if err := rtc.ClearAlarm2(); err != nil {
println("Error while clearing alarm2")
}
}
time.Sleep(time.Second * 1)
}
}
@@ -1,11 +1,12 @@
// Connects to an DS3231 I2C Real Time Clock (RTC).
// Connects to an MAG3110 I2C magnetometer.
package main
import (
"machine"
"strconv"
"time"
"fmt"
"tinygo.org/x/drivers/ds3231"
)
@@ -25,19 +26,19 @@ func main() {
if !running {
err := rtc.SetRunning(true)
if err != nil {
println("Error configuring RTC")
fmt.Println("Error configuring RTC")
}
}
for {
dt, err := rtc.ReadTime()
if err != nil {
println("Error reading date:", err)
fmt.Println("Error reading date:", err)
} else {
println(dt.Format(time.DateTime))
fmt.Printf("Date: %d/%s/%02d %02d:%02d:%02d \r\n", dt.Year(), dt.Month(), dt.Day(), dt.Hour(), dt.Minute(), dt.Second())
}
temp, _ := rtc.ReadTemperature()
println("Temperature:", strconv.FormatFloat(float64(temp)/1000, 'f', -1, 32), "°C")
fmt.Printf("Temperature: %.2f °C \r\n", float32(temp)/1000)
time.Sleep(time.Second * 1)
}
@@ -1,28 +0,0 @@
//go:build macropad_rp2040
package main
import (
"machine"
"tinygo.org/x/drivers/encoders"
)
var (
enc = encoders.NewQuadratureViaInterrupt(machine.ROT_A, machine.ROT_B)
)
func main() {
enc.Configure(encoders.QuadratureConfig{
Precision: 4,
})
for oldValue := 0; ; {
if newValue := enc.Position(); newValue != oldValue {
println("value: ", newValue)
oldValue = newValue
}
}
}
-56
View File
@@ -1,56 +0,0 @@
// This example demonstrates ENS160 usage.
//
// Wiring:
// - VCC to 3.3V, GND to ground
// - SDA to board SDA, SCL to board SCL
package main
import (
"time"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/ens160"
)
func main() {
err := machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
})
if err != nil {
println("Failed to configure I2C:", err)
}
dev := ens160.New(machine.I2C0, ens160.DefaultAddress)
connected := dev.Connected()
if !connected {
println("ENS160 not detected")
return
}
println("ENS160 detected")
if err := dev.Configure(); err != nil {
println("Failed to configure ENS160:", err)
}
for {
err := dev.Update(drivers.Concentration)
if err != nil {
println("Error reading ENS160: %v\n", err)
time.Sleep(5 * time.Second)
continue
}
println(
"AQI:", dev.AQI(),
"TVOC:", dev.TVOC(),
"eCO2:", dev.ECO2(),
"Validity:", dev.ValidityString(),
)
time.Sleep(2 * time.Second)
}
}
+145
View File
@@ -0,0 +1,145 @@
// This is a console to a ESP8266/ESP32 running on the device UART1.
// Allows you to type AT commands from your computer via the microcontroller.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> INTERNET
//
// More information on the Espressif AT command set at:
// https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/espat"
)
// change actAsAP to true to act as an access point instead of connecting to one.
const actAsAP = false
var (
// access point info
ssid string
pass string
)
// 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 (
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
console = machine.Serial
adaptor *espat.Device
)
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
// Init esp8266
adaptor = espat.New(uart)
adaptor.Configure()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
println("Type an AT command then press enter:")
prompt()
input := make([]byte, 64)
i := 0
for {
if console.Buffered() > 0 {
data, _ := console.ReadByte()
switch data {
case 13:
// return key
console.Write([]byte("\r\n"))
// send command to ESP8266
input[i] = byte('\r')
input[i+1] = byte('\n')
adaptor.Write(input[:i+2])
// display response
r, _ := adaptor.Response(500)
console.Write(r)
// prompt
prompt()
i = 0
continue
default:
// just echo the character
console.WriteByte(data)
input[i] = data
i++
}
}
time.Sleep(10 * time.Millisecond)
}
}
func prompt() {
print("ESPAT>")
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
// provide access point
func provideAP() {
println("Starting wifi network as access point '" + ssid + "'...")
adaptor.SetWifiMode(espat.WifiModeAP)
adaptor.SetAPConfig(ssid, pass, 7, espat.WifiAPSecurityWPA2_PSK)
println("Ready.")
ip, _ := adaptor.GetAPIP()
println(ip)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+131
View File
@@ -0,0 +1,131 @@
// This is a sensor hub that uses a ESP8266/ESP32 running on the device UART1.
// It creates a UDP "server" you can use to get info to/from your computer via the microcontroller.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> INTERNET
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/net"
)
// change actAsAP to true to act as an access point instead of connecting to one.
const actAsAP = false
var (
// access point info
ssid string
pass string
)
// 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 (
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
adaptor *espat.Device
)
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
// Init esp8266
adaptor = espat.New(uart)
adaptor.Configure()
readyled := machine.LED
readyled.Configure(machine.PinConfig{Mode: machine.PinOutput})
readyled.High()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
// now make UDP connection
laddr := &net.UDPAddr{Port: 2222}
println("Loading UDP listener...")
conn, _ := net.ListenUDP("UDP", laddr)
println("Waiting for data...")
data := make([]byte, 50)
blink := true
for {
n, _ := conn.Read(data)
if n > 0 {
println(string(data[:n]))
conn.Write([]byte("hello back\r\n"))
}
blink = !blink
if blink {
readyled.High()
} else {
readyled.Low()
}
time.Sleep(500 * time.Millisecond)
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting UDP...")
conn.Close()
println("Done.")
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
// provide access point
func provideAP() {
println("Starting wifi network as access point '" + ssid + "'...")
adaptor.SetWifiMode(espat.WifiModeAP)
adaptor.SetAPConfig(ssid, pass, 7, espat.WifiAPSecurityWPA2_PSK)
println("Ready.")
ip, _ := adaptor.GetAPIP()
println(ip)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+110
View File
@@ -0,0 +1,110 @@
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
// It creates a UDP connection you can use to get info to/from your computer via the microcontroller.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/net"
)
var (
// access point info
ssid string
pass string
)
// IP address of the listener aka "hub". Replace with your own info.
const hubIP = "0.0.0.0"
// 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 (
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
adaptor *espat.Device
)
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
// Init esp8266/esp32
adaptor = espat.New(uart)
adaptor.Configure()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
// now make UDP connection
ip := net.ParseIP(hubIP)
raddr := &net.UDPAddr{IP: ip, Port: 2222}
laddr := &net.UDPAddr{Port: 2222}
println("Dialing UDP connection...")
conn, _ := net.DialUDP("udp", laddr, raddr)
for {
// send data
println("Sending data...")
conn.Write([]byte("hello\r\n"))
time.Sleep(1000 * time.Millisecond)
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting UDP...")
conn.Close()
println("Done.")
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+144
View File
@@ -0,0 +1,144 @@
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
// It creates an MQTT connection that publishes a message every second
// to an MQTT broker.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker.
//
// You must install the Paho MQTT package to build this program:
//
// go get -u github.com/eclipse/paho.mqtt.golang
package main
import (
"machine"
"math/rand"
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/net/mqtt"
)
var (
// access point info
ssid string
pass string
)
// IP address of the MQTT broker to use. Replace with your own info.
const server = "tcp://test.mosquitto.org:1883"
//const server = "ssl://test.mosquitto.org:8883"
// 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 (
uart = machine.UART2
tx = machine.PA22
rx = machine.PA23
console = machine.Serial
adaptor *espat.Device
topic = "tinygo"
)
func main() {
time.Sleep(3000 * time.Millisecond)
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
rand.Seed(time.Now().UnixNano())
// Init esp8266/esp32
adaptor = espat.New(uart)
adaptor.Configure()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
opts := mqtt.NewClientOptions()
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
println("Connecting to MQTT broker at", server)
cl := mqtt.NewClient(opts)
if token := cl.Connect(); token.Wait() && token.Error() != nil {
failMessage(token.Error().Error())
}
for {
println("Publishing MQTT message...")
data := []byte("{\"e\":[{ \"n\":\"hello\", \"v\":101 }]}")
token := cl.Publish(topic, 0, false, data)
token.Wait()
if token.Error() != nil {
println(token.Error().Error())
}
time.Sleep(1000 * time.Millisecond)
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting MQTT...")
cl.Disconnect(100)
println("Done.")
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
// Returns an int >= min, < max
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
// Generate a random string of A-Z chars with len = l
func randomString(len int) string {
bytes := make([]byte, len)
for i := 0; i < len; i++ {
bytes[i] = byte(randomInt(65, 90))
}
return string(bytes)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+165
View File
@@ -0,0 +1,165 @@
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
// It creates an MQTT connection that publishes a message every second
// to an MQTT broker.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker.
//
// You must also install the Paho MQTT package to build this program:
//
// go get -u github.com/eclipse/paho.mqtt.golang
package main
import (
"fmt"
"machine"
"math/rand"
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/net/mqtt"
)
var (
// access point info
ssid string
pass string
)
// IP address of the MQTT broker to use. Replace with your own info.
//const server = "tcp://test.mosquitto.org:1883"
const server = "ssl://test.mosquitto.org:8883"
// change these to connect to a different UART or pins for the ESP8266/ESP32
var (
// these are defaults for the Arduino Nano33 IoT.
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
console = machine.Serial
adaptor *espat.Device
cl mqtt.Client
topicTx = "tinygo/tx"
topicRx = "tinygo/rx"
)
func subHandler(client mqtt.Client, msg mqtt.Message) {
fmt.Printf("[%s] ", msg.Topic())
fmt.Printf("%s\r\n", msg.Payload())
}
func main() {
time.Sleep(3000 * time.Millisecond)
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
rand.Seed(time.Now().UnixNano())
// Init esp8266/esp32
adaptor = espat.New(uart)
adaptor.Configure()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
opts := mqtt.NewClientOptions()
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
println("Connecting to MQTT broker at", server)
cl = mqtt.NewClient(opts)
if token := cl.Connect(); token.Wait() && token.Error() != nil {
failMessage(token.Error().Error())
}
// subscribe
token := cl.Subscribe(topicRx, 0, subHandler)
token.Wait()
if token.Error() != nil {
failMessage(token.Error().Error())
}
go publishing()
select {}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting MQTT...")
cl.Disconnect(100)
println("Done.")
}
func publishing() {
for {
println("Publishing MQTT message...")
data := []byte("{\"e\":[{ \"n\":\"hello\", \"v\":101 }]}")
token := cl.Publish(topicTx, 0, false, data)
token.Wait()
if token.Error() != nil {
println(token.Error().Error())
}
time.Sleep(1000 * time.Millisecond)
}
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
// Returns an int >= min, < max
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
// Generate a random string of A-Z chars with len = l
func randomString(len int) string {
bytes := make([]byte, len)
for i := 0; i < len; i++ {
bytes[i] = byte(randomInt(65, 90))
}
return string(bytes)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+113
View File
@@ -0,0 +1,113 @@
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
// It creates a UDP connection you can use to get info to/from your computer via the microcontroller.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/net"
)
var (
// access point info
ssid string
pass string
)
// IP address of the server aka "hub". Replace with your own info.
const serverIP = "0.0.0.0"
// 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 (
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
adaptor *espat.Device
)
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
// Init esp8266/esp32
adaptor = espat.New(uart)
adaptor.Configure()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
// now make TCP connection
ip := net.ParseIP(serverIP)
raddr := &net.TCPAddr{IP: ip, Port: 8080}
laddr := &net.TCPAddr{Port: 8080}
println("Dialing TCP connection...")
conn, err := net.DialTCP("tcp", laddr, raddr)
if err != nil {
failMessage(err.Error())
}
for {
// send data
println("Sending data...")
conn.Write([]byte("hello\r\n"))
time.Sleep(1000 * time.Millisecond)
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting TCP...")
conn.Close()
println("Done.")
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
func main() {
console_example.RunFor(
flash.NewSPI(
machine.SPI1,
&machine.SPI1,
machine.SPI1_SDO_PIN,
machine.SPI1_SDI_PIN,
machine.SPI1_SCK_PIN,
+1 -1
View File
@@ -10,7 +10,7 @@ import (
func main() {
println("GPS I2C Example")
machine.I2C0.Configure(machine.I2CConfig{})
ublox := gps.NewI2CWithAddress(machine.I2C0, gps.UBLOX_I2C_ADDRESS)
ublox := gps.NewI2C(machine.I2C0)
parser := gps.NewParser()
var fix gps.Fix
for {
+6 -18
View File
@@ -8,6 +8,7 @@ import (
)
func main() {
println("GPS UART Example")
machine.UART1.Configure(machine.UARTConfig{BaudRate: 9600})
ublox := gps.NewUART(machine.UART1)
parser := gps.NewParser()
@@ -15,24 +16,14 @@ func main() {
for {
s, err := ublox.NextSentence()
if err != nil {
switch err {
case gps.ErrUnknownNMEASentence, gps.ErrInvalidNMEASentence, gps.ErrInvalidNMEASentenceLength:
continue
default:
println("sentence error:", err)
continue
}
println(err)
continue
}
fix, err = parser.Parse(s)
if err != nil {
switch err {
case gps.ErrUnknownNMEASentence, gps.ErrInvalidNMEASentence, gps.ErrInvalidNMEASentenceLength:
continue
default:
println("parse error:", err)
continue
}
println(err)
continue
}
if fix.Valid {
print(fix.Time.Format("15:04:05"))
@@ -52,10 +43,7 @@ func main() {
}
println()
} else {
if fix.Type == gps.GSV {
// GSV sentence provides satellite count even if no fix yet
println(fix.Satellites, "satellites visible")
}
println("No fix")
}
time.Sleep(200 * time.Millisecond)
}
-49
View File
@@ -1,49 +0,0 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/honeyhsc"
)
// Data taken from https://github.com/rodan/honeywell_hsc_ssc_i2c/blob/master/hsc_ssc_i2c.cpp
// these defaults are valid for the HSCMRNN030PA2A3 chip
const (
i2cAddress = 0x28
// 10%
outputMinimum = 0x666
// 90% of 2^14 - 1
outputMax = 0x399A
// min is 0 for sensors that give absolute values
pressureMin = 0
// 30psi (and we want results in millipascals)
// pressureMax = 206842.7
pressureMax = 206843 * 1000
)
func main() {
bus := machine.I2C0
err := bus.Configure(machine.I2CConfig{
Frequency: 400_000, // 100kHz minimum and 400kHz I2C maximum clock. 50 to 800 for SPI.
SDA: machine.I2C0_SDA_PIN,
SCL: machine.I2C0_SCL_PIN,
})
if err != nil {
panic(err.Error())
}
sensor := honeyhsc.NewDevI2C(bus, i2cAddress, outputMinimum, outputMax, pressureMin, pressureMax)
for {
time.Sleep(time.Second)
const measuremask = drivers.Pressure | drivers.Temperature
err := sensor.Update(measuremask)
if err != nil {
println("error updating measurements:", err.Error())
continue
}
P := sensor.Pressure()
T := sensor.Temperature()
println("pressure:", P, "temperature:", T)
}
}
+22 -23
View File
@@ -8,16 +8,15 @@ import (
"tinygo.org/x/drivers/examples/ili9341/initdisplay"
"tinygo.org/x/drivers/examples/ili9341/pyportal_boing/graphics"
"tinygo.org/x/drivers/ili9341"
"tinygo.org/x/drivers/pixel"
)
const (
BGCOLOR = pixel.RGB565BE(0x75AD)
GRIDCOLOR = pixel.RGB565BE(0x15A8)
BGSHADOW = pixel.RGB565BE(0x8552)
GRIDSHADOW = pixel.RGB565BE(0x0C60)
RED = pixel.RGB565BE(0x00F8)
WHITE = pixel.RGB565BE(0xFFFF)
BGCOLOR = 0xAD75
GRIDCOLOR = 0xA815
BGSHADOW = 0x5285
GRIDSHADOW = 0x600C
RED = 0xF800
WHITE = 0xFFFF
YBOTTOM = 123 // Ball Y coord at bottom
YBOUNCE = -3.5 // Upward velocity on ball bounce
@@ -26,7 +25,7 @@ const (
)
var (
frameBuffer = pixel.NewImage[pixel.RGB565BE](graphics.BALLWIDTH+8, graphics.BALLHEIGHT+8)
frameBuffer = [(graphics.BALLHEIGHT + 8) * (graphics.BALLWIDTH + 8) * 2]uint8{}
startTime int64
frame int64
@@ -42,7 +41,7 @@ var (
balloldy float32
// Color table for ball rotation effect
palette [16]pixel.RGB565BE
palette [16]uint16
)
var (
@@ -109,7 +108,6 @@ func main() {
width = maxx - minx + 1
height = maxy - miny + 1
buffer := frameBuffer.Rescale(int(width), int(height))
// Ball animation frame # is incremented opposite the ball's X velocity
ballframe -= ballvx * 0.5
@@ -130,7 +128,7 @@ func main() {
}
// Only the changed rectangle is drawn into the 'renderbuf' array...
var c pixel.RGB565BE //, *destPtr;
var c uint16 //, *destPtr;
bx := minx - int16(ballx) // X relative to ball bitmap (can be negative)
by := miny - int16(bally) // Y relative to ball bitmap (can be negative)
bgx := minx // X relative to background bitmap (>= 0)
@@ -151,20 +149,19 @@ func main() {
(by >= 0) && (by < graphics.BALLHEIGHT) { // inside the ball bitmap area?
// Yes, do ball compositing math...
p = graphics.Ball[int(by*(graphics.BALLWIDTH/2))+int(bx1/2)] // Get packed value (2 pixels)
var nibble uint8
if (bx1 & 1) != 0 {
nibble = p & 0xF
c = uint16(p & 0xF)
} else {
nibble = p >> 4
c = uint16(p >> 4)
} // Unpack high or low nybble
if nibble == 0 { // Outside ball - just draw grid
if c == 0 { // Outside ball - just draw grid
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
c = GRIDCOLOR
} else {
c = BGCOLOR
}
} else if nibble > 1 { // In ball area...
c = palette[nibble]
} else if c > 1 { // In ball area...
c = palette[c]
} else { // In shadow area...
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
c = GRIDSHADOW
@@ -179,7 +176,8 @@ func main() {
c = BGCOLOR
}
}
buffer.Set(x, y, c)
frameBuffer[(y*int(width)+x)*2] = byte(c >> 8)
frameBuffer[(y*int(width)+x)*2+1] = byte(c)
bx1++ // Increment bitmap position counters (X axis)
bgx1++
}
@@ -190,7 +188,7 @@ func main() {
bgy++
}
display.DrawBitmap(minx, miny, buffer)
display.DrawRGBBitmap8(minx, miny, frameBuffer[:width*height*2], width, height)
// Show approximate frame rate
frame++
@@ -207,7 +205,6 @@ func DrawBackground() {
w, h := display.Size()
byteWidth := (w + 7) / 8 // Bitmap scanline pad = whole byte
var b uint8
buffer := frameBuffer.Rescale(int(w), 1)
for j := int16(0); j < h; j++ {
for k := int16(0); k < w; k++ {
if k&7 > 0 {
@@ -216,11 +213,13 @@ func DrawBackground() {
b = graphics.Background[j*byteWidth+k/8]
}
if b&0x80 == 0 {
buffer.Set(int(k), 0, BGCOLOR)
frameBuffer[2*k] = byte(BGCOLOR >> 8)
frameBuffer[2*k+1] = byte(BGCOLOR & 0xFF)
} else {
buffer.Set(int(k), 0, GRIDCOLOR)
frameBuffer[2*k] = byte(GRIDCOLOR >> 8)
frameBuffer[2*k+1] = byte(GRIDCOLOR & 0xFF)
}
}
display.DrawBitmap(0, j, buffer)
display.DrawRGBBitmap8(0, j, frameBuffer[0:w*2], w, 1)
}
}
-29
View File
@@ -1,29 +0,0 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/ina219"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
dev := ina219.New(machine.I2C0)
dev.Configure()
for {
busVoltage, shuntVoltage, current, power, err := dev.Measurements()
if err != nil {
println("Error reading measurements", err)
}
println("Bus Voltage:", busVoltage, "V")
println("Shunt Voltage:", shuntVoltage/100, "mV")
println("Current:", current, "mA")
println("Power:", power, "mW")
time.Sleep(10 * time.Millisecond)
}
}
+3 -12
View File
@@ -14,18 +14,9 @@ func main() {
i2c.Configure(machine.I2CConfig{SCL: machine.SCL1_PIN, SDA: machine.SDA1_PIN})
accel := lis3dh.New(i2c)
err := accel.Configure(lis3dh.Config{
Address: lis3dh.Address1, // address on the Circuit Playground Express
})
for err != nil {
println("could not configure LIS3DH:", err)
time.Sleep(time.Second)
}
err = accel.SetRange(lis3dh.RANGE_2_G)
for err != nil {
println("could not set acceleration range:", err)
time.Sleep(time.Second)
}
accel.Address = lis3dh.Address1 // address on the Circuit Playground Express
accel.Configure()
accel.SetRange(lis3dh.RANGE_2_G)
println(accel.Connected())
+4 -15
View File
@@ -23,30 +23,19 @@ Builds/flashes atcmd console application with simulator instead of actual LoRa r
tinygo flash -target pico ./examples/lora/lorawan/atcmd/
```
## PyBadge with LoRa Featherwing for EU868 region
## PyBadge with LoRa Featherwing
Builds/flashes atcmd console application on PyBadge using LoRa Featherwing (RFM95/SX1276).
```
tinygo flash -target pybadge -tags featherwing -ldflags="-X main.reg=EU868" ./examples/lora/lorawan/atcmd/
tinygo flash -target pybadge -tags featherwing ./examples/lora/lorawan/atcmd/
```
## LoRa-E5 for US915 region
## LoRa-E5
Builds/flashes atcmd console application on Lora-E5 using onboard SX126x.
```
tinygo flash -target lorae5 -ldflags="-X main.reg=US915" ./examples/lora/lorawan/atcmd/
tinygo flash -target lorae5 ./examples/lora/lorawan/atcmd/
```
## Joining a Public Lorawan Network
```
AT+ID=DevEui,0101010101010101
AT+ID=AppEui,0123012301230213
AT+KEY=APPKEY,AEAEAEAEAEAEAEAAEAEAEAEAEAEAAEAE
AT+LW=NET,ON
AT+JOIN
```
AT+LW=NET,(ON|OFF) command changes Lora Sync Word to connect on public network(ON) or private networks(OFF)
-11
View File
@@ -337,17 +337,6 @@ func delay(setting string) error {
func lw(setting string) error {
cmd := "LW"
param, val, hasComma := strings.Cut(setting, ",")
if hasComma {
if param == "NET" {
if val == "ON" {
lorawan.SetPublicNetwork(true)
} else {
lorawan.SetPublicNetwork(false)
}
}
}
writeCommandOutput(cmd, setting)
return nil
-14
View File
@@ -15,7 +15,6 @@ import (
"tinygo.org/x/drivers/examples/lora/lorawan/common"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/lora/lorawan"
"tinygo.org/x/drivers/lora/lorawan/region"
)
// change these to test a different UART or pins if available
@@ -32,8 +31,6 @@ var (
defaultTimeout uint32 = 1000
)
var reg string
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
@@ -47,17 +44,6 @@ func main() {
otaa = &lorawan.Otaa{}
lorawan.UseRadio(radio)
switch reg {
case "AU915":
lorawan.UseRegionSettings(region.AU915())
case "EU868":
lorawan.UseRegionSettings(region.EU868())
case "US915":
lorawan.UseRegionSettings(region.US915())
default:
lorawan.UseRegionSettings(region.EU868())
}
for {
if uart.Buffered() > 0 {
data, _ := uart.ReadByte()
+4 -13
View File
@@ -21,24 +21,15 @@ loraConnect: Connected !
tinygo flash -target pico ./examples/lora/lorawan/basic-demo
```
## PyBadge with LoRa Featherwing for EU868 region
## PyBadge with LoRa Featherwing
```
tinygo flash -target pybadge -tags featherwing -ldflags="-X main.reg=EU868" ./examples/lora/lorawan/basic-demo
tinygo flash -target pybadge -tags featherwing ./examples/lora/lorawan/basic-demo
```
## LoRa-E5 for US915 region
## LoRa-E5
```
tinygo flash -target lorae5 -ldflags="-X main.reg=US915" ./examples/lora/lorawan/basic-demo
```
## Enable debugging
You can also enable some debug logs with ldflags :
```
$ tinygo build -ldflags="-X 'main.debug=true'" -target=lorae5
tinygo flash -target lorae5 ./examples/lora/lorawan/basic-demo
```
@@ -2,16 +2,10 @@
package main
import (
"tinygo.org/x/drivers/lora/lorawan"
)
// These are sample keys, so the example builds
// Either change here, or create a new go file and use customkeys build tag
func setLorawanKeys() {
otaa.SetAppEUI([]uint8{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
otaa.SetDevEUI([]uint8{0xB3, 0xD5, 0x41, 0x00, 0x0A, 0xF1, 0xA4, 0x45})
otaa.SetAppKey([]uint8{0x12, 0x22, 0xA3, 0xFF, 0x0C, 0x7B, 0x76, 0x7B, 0x8F, 0xD3, 0x12, 0x4F, 0xCE, 0x7A, 0x32, 0x16})
lorawan.SetPublicNetwork(true)
}
+17 -30
View File
@@ -9,12 +9,6 @@ import (
"tinygo.org/x/drivers/examples/lora/lorawan/common"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/lora/lorawan"
"tinygo.org/x/drivers/lora/lorawan/region"
)
var (
reg string
debug string
)
const (
@@ -68,40 +62,33 @@ func main() {
session = &lorawan.Session{}
otaa = &lorawan.Otaa{}
// Initial Lora modulation configuration
loraConf := lora.Config{
Freq: 868100000,
Bw: lora.Bandwidth_125_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
Preamble: 12,
Ldr: lora.LowDataRateOptimizeOff,
Iq: lora.IQStandard,
Crc: lora.CRCOn,
SyncWord: lora.SyncPublic,
LoraTxPowerDBm: 20,
}
radio.LoraConfig(loraConf)
// Connect the lorawan with the Lora Radio device.
lorawan.UseRadio(radio)
switch reg {
case "AU915":
lorawan.UseRegionSettings(region.AU915())
case "EU868":
lorawan.UseRegionSettings(region.EU868())
case "US915":
lorawan.UseRegionSettings(region.US915())
default:
lorawan.UseRegionSettings(region.EU868())
}
// Configure AppEUI, DevEUI, APPKey, and public/private Lorawan Network
// Configure AppEUI, DevEUI, APPKey
setLorawanKeys()
if debug != "" {
println("main: Network joined")
println("main: DevEui, " + otaa.GetDevEUI())
println("main: AppEui, " + otaa.GetAppEUI())
println("main: DevAddr, " + otaa.GetAppKey())
}
// Try to connect Lorawan network
if err := loraConnect(); err != nil {
failMessage(err)
}
if debug != "" {
println("main: NetID, " + otaa.GetNetID())
println("main: NwkSKey, " + session.GetNwkSKey())
println("main: AppSKey, " + session.GetAppSKey())
println("main: Done")
}
// Try to periodicaly send an uplink sample message
upCount := 1
for {

Some files were not shown because too many files have changed in this diff Show More