Compare commits

..

122 Commits

Author SHA1 Message Date
deadprogram 8f372935ac Release 0.35.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-21 09:44:44 +01:00
deadprogram fb3062433a license: remove year
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-21 09:44:44 +01:00
deadprogram 9fc1c0aedc examples/ws2812: update to accomodate Arduino Uno rename for TinyGo 0.41.0 release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-21 09:44:44 +01:00
nobonobo 1c10dea443 add support extended id for mcp2515 (#857)
* add support extended id for mcp2515
* fix mcp2512 example code
* revert Pin control
2026-04-20 11:51:56 +02:00
Daniel Esteban e960a6ff57 fix issue #858 2026-04-17 10:27:04 +01:00
Daniel Esteban 2673cc1e9a fix st7789 scroll on rotated displays 2026-04-16 10:28:13 +01:00
Joel Wetzell 0034fc511a fill out more constants for lora device 2026-04-11 17:29:47 +01:00
deadprogram a0c5da601f unoqmatrix: LED matrix on the Arduino Uno Q
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-04-11 17:07:17 +01:00
Carlos Henrique Guardão Gandarez 21a7d0a96a ws2812: add brightness control 2026-04-11 16:54:48 +01:00
Avinal Kumar e232a4f136 waveshare(ssd1680): Add driver for waveshare 2.9 inch v2 epaper
- Waveshare 2.9in v2 epaper uses a different IC (SSD1680) than the v1. This commit
takes the code from v1 and modifies them for v2.
- Datasheets:
    - https://files.waveshare.com/upload/7/79/2.9inch-e-paper-v2-specification.pdf
    - https://cdn-learn.adafruit.com/assets/assets/000/097/631/original/SSD1680_Datasheet.pdf?1607625960
- Code reference: https://github.com/waveshareteam/e-Paper/raw/refs/heads/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in9_V2.c
- Fixes #627

Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
Assisted-by: Claude Code
2026-04-11 16:41:34 +01:00
Carlos Henrique Guardão Gandarez 4071028e85 ws2812: add PIO support for RP2040/RP2350
Integrate PIO support directly into the existing Device.
NewWS2812() now uses PIO for hardware-timed control
on RP2040/RP2350 and falls back to bit-banging on other platforms.

No changes to the exported API surface.
2026-04-07 21:38:57 +02:00
Daniel Esteban a514169c37 fix ST7789 driver when rotated 90º 2026-03-29 20:08:37 +02:00
deadprogram b480978e1a docs: update count of drivers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-02-25 11:13:21 +01:00
deadprogram 9eb95a4651 make: add tasks for counting the number of drivers, and displaying a list of them
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-02-25 11:13:21 +01:00
deadprogram d6114c9f6d sponsorship: add explicit callout/link in README to help out TinyGo
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-02-12 13:05:25 +01:00
deadprogram 0e2fb829ef gps: improvements and corrections for config commands
This contains some improvements and corrections for the gps driver
It adds some additional functions for different modes (automobile, bike, etc)
and also ignores the return results from any config commands.

Basically due to the fact that there is a constant stream of updates
coming from NMEA messages, the results from sending any UBX commands
are getting lost. Better to just ignore them for now.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-01-14 19:55:43 +00:00
deadprogram 892265b733 gps: export some errors for checking/supression from client
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-01-08 13:05:41 +00:00
Pat Whittingslow b390e3225a Suggestions by pato for GPS UBX support (#831)
* apply suggestions by pato

* whoopsie on inverted condition
2026-01-08 13:05:41 +00:00
deadprogram 1513808425 gps: revamp validSentence() to avoid heap allocation for errors. This error can occur too frequently to allow for such allocations
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-01-08 13:05:41 +00:00
deadprogram 0a41786a77 gps: improve implementation for UBX config commands
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-01-08 13:05:41 +00:00
deadprogram c21cd39813 si5351: complete refactor for more complete interface
This completely refactors the interface and implementation
for the si5351 clock generator. The interface based on the
Arduino implementation was both somewhat hard to work with
and also missing a number of important features that are
needed to use this chip for RF communication.

Instead this new implementation draws inspiration from the
efforts of the Traquino community mostly using the rp2040
processor.

The TinyGo implementation is based on the patterns and code
in the drivers repo for other i2c devices. It also includes
some basic unit tests which are not comprehensive but at
least provide some coverage.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-01-07 19:58:27 +00:00
deadprogram a35786be70 sx127x: add functions used for FSK radio communication
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-01-03 16:49:21 +00:00
Yurii Soldak 2a42fa7cbb st7735: remove dependency on the machine package 2025-12-22 08:59:05 +00:00
deadprogram 5d96a56603 build: use the latest TinyGo release container instead of the dev container for builds.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-17 10:28:24 +00:00
deadprogram f931ad44fb Release 0.34
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-15 15:13:56 +00:00
deadprogram 50778af656 si5351: add many missing functions needed for convenient use.
This adds many missing functions needed for convenient use that are
reinterpreted from the C code in https://github.com/dmalnati/picoinf

Thank you!

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-11 17:59:07 +00:00
Mike Hughes 936a255df9 Add support for CEVA BNO08x 9DoF sensor (#809)
* Add support for CEVA BNO08x 9DoF sensor. Also includes implementation of CEVA SH-2 and SHTP protocols.

* Replace machine.I2C with drivers.I2C interface to remove dependency on machine package

* Replace Pin functionality with that provided by tinygo.org/x/drivers/internal/pin

* Unexport fields on SensorValue and replace with accessor methods. Add check for correct SensorID validation

* Add example to smoketest.sh

* Change build target for smoketest to match development environment.. Probably not important, but matches reality.

* Fix decoding of some sensor data: Step Counter, Tap Detector, Flip Detector. These are experimental.

* Refactor to allow SPI/UART etc. SPI is currently under development, but is omitted from this commit.

Example code has been moved to i2c subdirectory.

This commit introduces some major refactoring changes. It introduces a "Buser" interface and tries to remove any I2C specific code from the core. It still retains a couple of I2C specific fields in the "Config" struct ("Address" and "ReadChunk") but they are ignored in the as yet uncommited SPI code.

* Fix CRLF -> LF for gofmt

* Update smoketest to point to new example file
2025-12-10 19:07:04 +01:00
Ayke van Laethem 9ed648f4a5 scd4x: add support for SCD41 single-shot measurements
I have a SCD41, and would like to use it for single shot measurements
instead of "low power periodic" measurements to achieve much lower
average current consumption.
2025-12-04 11:25:41 +00:00
Ayke van Laethem fab688a701 scd4x: remove dead code 2025-12-01 08:56:40 +00:00
Ayke van Laethem d4654668f2 scd4x: update package to use standard methods
This deprecates the older Read* methods and uses standard Update() and
sensor data getters instead.

Apart from being more efficient, this also makes the driver more
efficient when reading multiple sensors (since the SCD4x sensor won't
get polled for new data at each Read* method call).
2025-12-01 08:56:40 +00:00
sago35 253f8e3220 pixel: add Grayscale2bit color (#817)
* pixel: add GrayScale2bit color
* pixel: fix spelling of 'GrayScale' to 'Grayscale'
2025-11-15 09:02:55 +01:00
Yurii Soldak c710cc8236 ssd1xxx: break dependency from machine package (#812)
* ssd1xxx: break dependency from machine package
* fix ssd1289 example
* simplify
2025-11-12 09:40:34 +01:00
Nicholas Wiersma 4b831af52b w5500: initial version the driver (#788)
Thank you, @nrwiersma for working on this and @soypat for review. Now merging.
2025-11-12 09:00:21 +01:00
Patricio Whittingslow 09fd01340b add simplest driver ports 2025-11-11 10:42:28 +01:00
gkits 23833e69c7 DS3231 Alarm features (#805)
* Added alarm features
* more functions
* chore: fix typo
* feat(ds3231): add Alarm2Mode type and mode consts
* docs(ds3231): add docstrings for SQW functions
* feat(ds3231): add methods for Alarm2
* fix(ds3231): use Alarm2Mode for SetAlarm2 method
* docs(ds3231): add example for alarms
* docs(ds3231): refactor alarms example
* make main function more concise to avoid llvm error for pico
* ci(ds3231): split basic and alarm tests
* style(ds3231): reorder private funcs to the bottom
* docs(ds3231): add docstring for alarm modes
* docs(ds3231): make alarm docstrings more descriptive
* chore(ds3231): fix typo in docstring
* style(ds3231): reorder public functions
* style(ds3231): reorder private methods
* chore(ds3231): add missing error handling
* feat(ds3231): use setter funcs for en/disabling instead of separate funcs
* fix(ds3231): correctly enable alarms in example
* style(ds3231): rename SetEnable32K to SetEnabled32K for consistency
* refactor(ds3231): replace legacy with regmap package
* refactor(ds3231): use Write32 instead of Tx for SetAlarm1
* chore(ds3231): remove fmt deps and and use println in examples
* refactor(ds3231): read temperature as uint16

---------

Co-authored-by: Matthias Fulz <mfulz@olznet.de>
2025-11-11 10:22:28 +01:00
sago35 744fda5eec fix: correct logic error in image size checks in pixel's tests (Monochrome) 2025-11-10 12:28:58 +01:00
sago35 027c91272e pixel: correct RGB555 to RGBA conversion logic 2025-11-10 12:28:58 +01:00
sago35 5847506ba6 Add TestImageRGB888 and TestImageRGB555 2025-11-10 12:28:58 +01:00
sago35 e35e6b8e13 fix: correct logic error in image size checks in pixel's tests 2025-11-10 12:28:58 +01:00
Ron Evans 408851a9f5 si5351: add support for si5351 (#810)
* si5351: add support for si5351

Adds support for the si5351 I2C programmable clock generator using code
from @chiefMarlin which used code from @conotto which somehow never got merged.

Thank you everyone!

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

* refactor: use regmap instead of legacy package to avoid heap allocations

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

---------

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-11-10 08:04:25 -03:00
Pat Whittingslow bd88b70511 regmap: Add Device8I2C/SPI types and their logic (#801)
* add regmap Device8I2C/SPI types and their methods
* add endianess hint
2025-11-09 14:05:30 +01:00
Yurii Soldak 34da2d208a lsm9ds1: avoid unnecessary heap allocations 2025-11-08 10:53:22 +01:00
Pat Whittingslow 5cb360a4bf Add Honeywell HSC TruStability SPI+I2C pressure sensor driver (#799)
* add honeywell pressure sensor
* apply @aykevl suggestions
2025-11-08 10:45:18 +01:00
Ayke van Laethem 51b604ce97 lis3dh: add Update and Acceleration calls
This adjusts the API to the one proposed in
https://github.com/tinygo-org/drivers/pull/345, which I think is much
better than direct ReadAcceleration etc calls.

I have also updated the code that converts raw acceleration values to
normalized values. The new code should be faster (didn't measure) and
avoids floating point math.
2025-11-08 10:34:37 +01:00
Ayke van Laethem ec680be784 lis3dh: use correct error handling and make configurable
Instead of printing an error, this driver really should be returning
errors instead. Also, `Configure` didn't have a way to actually
configure the driver. This is now added, and can be expanded in the
future.

This is a breaking change.
2025-11-08 10:34:37 +01:00
Pat Whittingslow 5fb935001e PinInput+PinOutput HAL (#753, reloaded) (#795)
* first commit: add HAL and uc8151 driver demo
* unexport drivers.PinOutput/Input HAL
* fix non-tinygo pin config build
* change of heart
* docs: corrected some comments that were not changed at the same time as recent renaming
2025-11-08 10:21:01 +01:00
Martin Heck 297ad416d3 fix: add RP2350 to quadrature_interrupt.go 2025-09-23 14:03:35 +02:00
soypat 3fa08112db add regmap package to facilitate heapless driver development 2025-09-14 08:12:32 -04:00
Bryan Souza b639f7b12e added support for P25Q16H flash chip for xiao-ble target; 2025-09-14 08:11:14 -04:00
Bryan Souza 28d625abfd added support for W25Q80DV flash chip for xiao-ble target; 2025-09-14 08:11:14 -04:00
deadprogram 228e57cf98 release: prepare for 0.33.0 drivers release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-19 21:57:30 +02:00
deadprogram 6cf1eb86e5 fix: correct smoke tests for Adafruit Seesaw
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-08-18 14:51:03 +02:00
JP Hastings-Spital 857ab80ae6 feat: add support for seesaw encoders
Adds the necessary function addresses for reading and writing encoders on a seesaw.
Also provides two helper functions to make this easier.
2025-08-18 14:15:48 +02:00
Russel Hunter Yukawa a31ba26a6c Fix gps time calculation (#785)
* Change test case to match the date patterns where the bug reproduces
* Fix RMC date and time calculation
2025-08-13 09:39:18 +02:00
Bryan Souza 303ec94529 added support for LSM303DLHC e-Compass; (#783)
fixed the spelling in the Connection error message; Initial support for LSM303DLHC added;
Added LSM303DLHC to smoketest and added an example;
Removed unnecessary comments;
fixed format error;
squashed and ready for merge;
2025-08-11 08:47:22 +02:00
Artur Nasyrov 833990f44d Add ens160 i2c driver
Driver for ENS160 sensor:
https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
2025-08-10 10:05:18 +02:00
Ayke van Laethem 28d87eb0c5 ws2812: add RP2350 support
Adding 150MHz support for the RP2350
2025-08-10 10:05:18 +02:00
Yurii Soldak ae9e8f915e ssd1306: avoid unnecessary heap allocations (#767)
* ssd1306: avoid unnecessary heap allocations

* ssd1306: extract i2c and spi bus implementations

* ssd1306: refactor tests -- show fps and heap usage

* ssd1306: bring back the lost exported methods

* Adjust examples

* Fix smoketests for ssd1306
2025-08-10 10:05:18 +02:00
JP Hastings-Spital 45fad80c3e feat: allow gps init with address
Adafruit's Mini GPS PA1010D Module works with this device driver, but requires 0x10 as the address, rather than 0x42.

This change allows the device to be initialised with whatever i2c address is needed, while maintaining backward compatibility.

Adds new constants to allow easy configuration of both the ublox device and the PA1010D.
2025-08-10 10:05:18 +02:00
Yurii Soldak 0304d30b78 lsm6ds3tr: avoid unnecessary heap allocations (#766)
* lsm6ds3tr: avoid unnecessary heap allocations
* lsm6ds3tr: use helper functions, for readability
* lsm6ds3tr: return slice of the internal buffer on readBytes
2025-08-10 10:05:18 +02:00
Ron Evans 7de0a0814e Revert "add regmap package to facilitate heapless driver development (#768)" (#776)
This reverts commit 80356fd9d9.
2025-07-14 11:16:56 -03:00
Patricio Whittingslow 80356fd9d9 add regmap package to facilitate heapless driver development (#768) 2025-07-13 09:58:49 -03:00
deadprogram c4ff8242a7 all: updates for drivers release v0.32.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-06-15 22:36:34 +02:00
soypat 82c41dbf14 add driver design pointer to CONTRIBUTING.md 2025-05-28 11:03:09 +02:00
Hikmatulloh Hari Mukti c4168864fd bmp280: remove alloc on read sensor data 2025-05-28 10:29:08 +02:00
Leon Matthews dbc9022f6a ws2812: add 200MHz support for the Cortex-M0/rp2040 2025-05-20 13:09:35 +02:00
Mateusz Nowak d41bc0b85f fix: remove time.Sleep from SSD1306 SPI transfer code 2025-05-20 12:43:13 +02:00
Craig Swank e7f90166ad tmc2209 bug fixes (#755)
* Make write buffer big enough for crc
* crc according to datasheet
* fix build
* a correct crc func already exists
2025-05-20 12:27:30 +02:00
deadprogram 156d6e7c9c chore: update all SPI usage to use either *machine.SPI or drivers.SPI to accomodate recent switch in machine package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-11 12:43:28 -07:00
deadprogram 6e04decf19 Prepare for release 0.30
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-04 10:11:02 -08:00
deadprogram afb1ea39c2 license: update year
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-03-04 10:11:02 -08:00
henryleduc 382e79a748 fix(dhcp): fix typo in DHCP error message 2025-03-04 09:25:06 -08:00
Scott Feldman 06dd60fba2 Add wifi driver comboat for Elecrow W5 rp2040 and rp2350 devices (#741)
* Add wifi driver comboat for Elecrow W5 rp2040 and rp2350 devices

Combo-AT driver uses AT command set to talk to onboard rtl8720d wifi
device.  The driver supports UDP/TCP/TLS clients in Wifi STA mode.
Support for UDP/TCP servers is not supported, currently.

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

* Add wifi driver comboat for Elecrow W5 rp2040 and rp2350 devices

Combo-AT driver uses AT command set to talk to onboard rtl8720d wifi
device.  The driver supports UDP/TCP/TLS clients in Wifi STA mode.
Support for UDP/TCP servers is not supported, currently.

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

* switch to comboat_fw build tag for examples/net's that work with comboat driver
2025-02-21 11:24:15 +01:00
Scott Feldman 27a7d5031e move to latest golang.org/x/net v0.33.0 (#732)
v0.33.0 has fixes for:

CVE-2024-45338: Non-linear parsing of case-insensitive content in golang.org/x/net/html
CVE-2023-45288: net/http, x/net/http2: close connections when receiving too many headers

Note: requires changes from tinygo PR#4685.
2025-02-18 23:34:09 +01:00
John Hobbs 6281f36662 MAX6675 example & smoke test 2025-02-13 18:26:26 +01:00
John Hobbs ccfcfa837e Add MAX6675 device 2025-02-13 18:26:26 +01:00
Amken USA 87c205fd65 Added TMC2209 support (#727)
TMC2209: Added TMC2209 support
2025-01-28 12:42:18 +01:00
Anastasios Papalyras f57b5ecee9 Add sharpmem (#724)
sharpmem: add implementation of sharpmem display driver

* Implement Configure, Clear and ClearBuffer, add some tests, add documentation/comments
* Reverse white
* Inverted bits, fix and improve ClearBuffer, cleanup
* Refine doc comment
* Driver refactor, optimizations toggle, additional tests & support for all SKUs
* Fix address overflow padding, add wire-level tests for assumed address encoding
* Minor rename
* Cleanup and doc fixes
* Add device configs
* Bounds check
* Add example and smoketest entry
* Use uf2 output file format
* Refine example
2025-01-28 12:17:12 +01:00
Amken USA 486949686c Added TMC5160 support (#725)
TMC5160: Added TMC5160 support

* Added example code for tmc5160 and smoke test
* Update go.mod
* Updated mod file
* Cleaned up commented out code and updated readme.
* ran go fmt
* Fixed setrampspeed func
* Removed spi1 pin setup in example.go. Renamed SPIComm to spicomm.go
* Removed unused test file
* Removed commented line
2025-01-18 07:49:00 +01:00
Triston Whetten 00578a3a81 Fixed bug in aht20 driver 2025-01-18 07:45:09 +01:00
ehime-iyokan 17f273243d hub75: fix data buffering 2025-01-04 16:20:10 +01:00
deadprogram 0623bb425c chore: update microphone driver to use latest i2s interface
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-18 16:42:00 +01:00
deadprogram 019bbbe5fb Prepare for release 0.29
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-30 17:02:40 +00:00
deadprogram d04e68bef8 bugfix: correct casting of return value from adc as it was always overflowing the 8 bit result
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-29 07:28:29 +01:00
Ayke van Laethem 76a4276b5d pixels: do more extensive testing for roundtrip bugs between Set and Get 2024-10-28 09:07:15 +00:00
Ayke van Laethem 7d7efe25e7 pixel: fix Monochrome setPixel
Set/setPixel and Get weren't using the same indices. I've taken the ones
used in Get and applied them to setPixel too.
This fixes testImageNoise for the monochrome image.
2024-10-28 09:07:15 +00:00
deadprogram 0186d0905d pixel: remove monochrome image from random noise test
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-28 05:58:53 +00:00
deadprogram 2f3b5ca59a pixel: correct and clarify code for monochrome get/set pixels
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-28 05:58:53 +00:00
deadprogram ecae5e28ad pixel: add NewImageFromBytes() function to allow creating image from existing slice
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-10-28 05:58:53 +00:00
Daniel Esteban bcf3b84654 Revert "pixel: add NewImageFromBytes function (#713)" (#714)
This reverts commit 4fb5d7a0e5.
2024-10-28 01:11:52 +01:00
Ron Evans 4fb5d7a0e5 pixel: add NewImageFromBytes function (#713)
* pixel: add NewImageFromBytes() function to allow creating image from existing slice

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

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

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

---------

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

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

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

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

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

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2024-04-21 12:00:03 +02:00
deadprogram a0293643b3 ssd1306: add DrawBitmap() function to complete Displayer interface
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-21 11:52:21 +02:00
deadprogram 60801ba852 pixel: add support for Monochrome types such as the SSD1306 display
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-13 15:50:30 +02:00
deadprogram a74770b2e6 rtl8720dn: implement ConnectModeAP
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-01 07:40:46 +02:00
deadprogram 3ed09d3fe1 wifinina: implement ConnectModeAP
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-04-01 07:40:46 +02:00
sago35 22581dfd58 encoders: add atsamd21, atsamd51, atsame5x 2024-02-29 15:29:41 +01:00
220 changed files with 21381 additions and 1393 deletions
+3
View File
@@ -0,0 +1,3 @@
# These are supported funding model platforms
open_collective: tinygo
+7 -6
View File
@@ -11,13 +11,12 @@ on:
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/tinygo-org/tinygo-dev:latest
container:
image: ghcr.io/tinygo-org/tinygo:latest
options: --user root
steps:
- name: Work around CVE-2022-24765
# We're not on a multi-user machine, so this is safe.
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: TinyGo version check
run: tinygo version
- name: Enforce Go Formatted Code
@@ -25,4 +24,6 @@ jobs:
- name: Run unit tests
run: make unit-test
- name: Run build and smoke tests
run: make smoke-test
run: |
go env -w GOFLAGS=-buildvcs=false
make smoke-test
+250
View File
@@ -1,3 +1,253 @@
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**
+3
View File
@@ -8,6 +8,9 @@ We would like your help to make this project better, so we appreciate any contri
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
### Driver design
Before porting or writing a driver from scratch please read **[Driver Design for TinyGo](https://tinygo.org/docs/guides/driver-design)**.
### One of the TinyGo drivers is not working as you expect
Please open a Github issue with your problem, and we will be happy to assist.
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2018-2024 The TinyGo Authors. All rights reserved.
Copyright 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
+15 -1
View File
@@ -19,10 +19,24 @@ rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst
# Recursively find all *_test.go files from cwd & reduce to unique dir names
HAS_TESTS = $(sort $(dir $(call rwildcard,,*_test.go)))
# Exclude anything we explicitly don't want to test for whatever reason
EXCLUDE_TESTS = image
EXCLUDE_TESTS = image waveshare-epd/epd2in66b
TESTS = $(filter-out $(addsuffix /%,$(EXCLUDE_TESTS)),$(HAS_TESTS))
unit-test:
@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
+27 -2
View File
@@ -3,11 +3,14 @@
[![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 102 different hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
This package provides a collection of over 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!
## Installing
```shell
@@ -16,7 +19,7 @@ go get tinygo.org/x/drivers
## How to use
Here is an example in TinyGo that uses the BMP180 digital barometer:
Here is an example in TinyGo that uses the BMP180 digital barometer. This example should work on any board that supports I2C:
```go
package main
@@ -53,6 +56,28 @@ func main() {
}
```
## Examples Using GPIO or SPI
If compiling these examples directly you are likely to need to make minor changes to the defined variables to map the pins for the board you are using. For example, this block in main.go:
```golang
var (
spi = machine.SPI0
csPin = machine.D5
)
```
It might not be obvious, but you need to change these to match how you wired your specific board. Constants are [defined for each supported microcontroller](https://tinygo.org/docs/reference/microcontrollers/).
For example, to change the definitions for use on a Raspberry Pi Pico using typical wiring, you might need to do this:
```golang
var (
spi = machine.SPI0
csPin = machine.GP17
)
```
## Contributing
Your contributions are welcome!
+2 -2
View File
@@ -29,7 +29,7 @@ func New(bus drivers.I2C) Device {
func (d *Device) Configure() {
// Check initialization state
status := d.Status()
if status&0x08 == 1 {
if status&STATUS_CALIBRATED == 1 {
// Device is initialized
return
}
@@ -69,7 +69,7 @@ func (d *Device) Read() error {
}
// If measurement complete, store values
if data[0]&0x04 != 0 && data[0]&0x80 == 0 {
if data[0]&STATUS_CALIBRATED != 0 && data[0]&STATUS_BUSY == 0 {
d.humidity = uint32(data[1])<<12 | uint32(data[2])<<4 | uint32(data[3])>>4
d.temp = (uint32(data[3])&0xF)<<16 | uint32(data[4])<<8 | uint32(data[5])
return nil
+7 -3
View File
@@ -5,9 +5,10 @@ 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 (
@@ -37,8 +38,11 @@ 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 machine.Pin, delay uint32) *Device {
return New(&bbSPI{SCK: sckPin, SDO: sdoPin, Delay: delay})
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)
}})
}
// WriteColors writes the given RGBA color slice out using the APA102 protocol.
+12 -6
View File
@@ -1,6 +1,9 @@
package apa102
import "machine"
import (
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/pin"
)
// 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.
@@ -8,15 +11,18 @@ import "machine"
// 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 machine.Pin
SDO machine.Pin
Delay uint32
SCK pin.OutputFunc
SDO pin.OutputFunc
Delay uint32
configurePins func()
}
// Configure sets up the SCK and SDO pins as outputs and sets them low
func (s *bbSPI) Configure() {
s.SCK.Configure(machine.PinConfig{Mode: machine.PinOutput})
s.SDO.Configure(machine.PinConfig{Mode: machine.PinOutput})
if s.configurePins == nil {
panic(legacy.ErrConfigBeforeInstantiated)
}
s.configurePins()
s.SCK.Low()
s.SDO.Low()
if s.Delay == 0 {
+31 -24
View File
@@ -1,31 +1,36 @@
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 machine.Pin
csb pin.OutputFunc
buf [7]byte
// SPI bus (requires chip select to be usable).
Bus drivers.SPI
bus drivers.SPI
configurePins func()
}
// NewSPI returns a new device driver. The pin and SPI interface are not
// touched, provide a fully configured SPI object and call Configure to start
// using this device.
func NewSPI(csb machine.Pin, spi drivers.SPI) *DeviceSPI {
func NewSPI(csb pin.Output, spi drivers.SPI) *DeviceSPI {
return &DeviceSPI{
CSB: csb, // chip select
Bus: spi,
csb: csb.Set, // chip select
bus: spi,
configurePins: func() {
legacy.ConfigurePinOut(csb)
},
}
}
@@ -33,9 +38,11 @@ func NewSPI(csb machine.Pin, 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 {
d.CSB.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.CSB.High()
if d.configurePins == nil {
return legacy.ErrConfigBeforeInstantiated
}
d.configurePins()
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
@@ -86,9 +93,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
}
@@ -123,9 +130,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
}
@@ -153,9 +160,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
}
@@ -201,9 +208,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]
}
@@ -217,7 +224,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()
}
+7 -8
View File
@@ -23,6 +23,7 @@ type Filter uint
type Device struct {
bus drivers.I2C
Address uint16
buf [6]byte
cali calibrationCoefficients
Temperature Oversampling
Pressure Oversampling
@@ -134,8 +135,8 @@ func (d *Device) PrintCali() {
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000).
func (d *Device) ReadTemperature() (temperature int32, err error) {
data, err := d.readData(REG_TEMP, 3)
if err != nil {
data := d.buf[:3]
if err = d.readData(REG_TEMP, data); err != nil {
return
}
@@ -158,8 +159,8 @@ func (d *Device) ReadTemperature() (temperature int32, err error) {
// ReadPressure returns the pressure in milli pascals (mPa).
func (d *Device) ReadPressure() (pressure int32, err error) {
// First 3 bytes are Pressure, last 3 bytes are Temperature
data, err := d.readData(REG_PRES, 6)
if err != nil {
data := d.buf[:6]
if err = d.readData(REG_PRES, data); err != nil {
return
}
@@ -203,7 +204,7 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
}
// readData reads n number of bytes of the specified register
func (d *Device) readData(register int, n int) ([]byte, error) {
func (d *Device) readData(register int, data []byte) error {
// If not in normal mode, set the mode to FORCED mode, to prevent incorrect measurements
// After the measurement in FORCED mode, the sensor will return to SLEEP mode
if d.Mode != MODE_NORMAL {
@@ -218,9 +219,7 @@ func (d *Device) readData(register int, n int) ([]byte, error) {
}
// Read the requested register
data := make([]byte, n)
err := legacy.ReadRegister(d.bus, uint8(d.Address), uint8(register), data[:])
return data, err
return legacy.ReadRegister(d.bus, uint8(d.Address), uint8(register), data[:])
}
// convert3Bytes converts three bytes to int32
+256
View File
@@ -0,0 +1,256 @@
// 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
@@ -0,0 +1,173 @@
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
@@ -0,0 +1,179 @@
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
@@ -0,0 +1,316 @@
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
@@ -0,0 +1,43 @@
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
@@ -0,0 +1,387 @@
// 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
@@ -0,0 +1,83 @@
// 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
@@ -0,0 +1,572 @@
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 (
"machine"
"time"
"tinygo.org/x/drivers/internal/pin"
)
// Device wraps a GPIO connection to a buzzer.
type Device struct {
pin machine.Pin
pin pin.OutputFunc
High bool
BPM float64
}
// New returns a new buzzer driver given which pin to use
func New(pin machine.Pin) Device {
func New(pin pin.Output) Device {
return Device{
pin: pin,
pin: pin.Set,
High: false,
BPM: 96.0,
}
@@ -25,14 +25,14 @@ func New(pin machine.Pin) Device {
// On sets the buzzer to a high state.
func (l *Device) On() (err error) {
l.pin.Set(true)
l.pin.High()
l.High = true
return
}
// Off sets the buzzer to a low state.
func (l *Device) Off() (err error) {
l.pin.Set(false)
l.pin.Low()
l.High = false
return
}
+711
View File
@@ -0,0 +1,711 @@
// Package comboat implements WiFi driver for the Aithinker-Combo-AT WiFi
// device found on the Elecrow W5 rp2040 and rp2350 devices. Ths WiFi device
// is a RTL8720d variant. The driver interface is via AT command set over UART
// (see reference docs below).
//
// NOTE: the driver doesn't support UDP/TCP server connections in STA mode,
// currently. UDP/TCP/TLS client connections are supported in STA mode.
//
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/instruction/index.html
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/command-set/index.html
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/command-examples/index.html
package comboat // import "tinygo.org/x/drivers/comboat"
import (
"bytes"
"errors"
"fmt"
"io"
"machine"
"net"
"net/netip"
"strconv"
"sync"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/netlink"
)
type Config struct {
BaudRate uint32
Uart *machine.UART
Tx machine.Pin
Rx machine.Pin
}
type socket struct {
protocol int
id string
rx chan []byte
remainder []byte
laddr netip.AddrPort // Set in Bind()
}
type device struct {
cfg *Config
uart *machine.UART
uartMu sync.Mutex
mac net.HardwareAddr
ip netip.Addr
gateway netip.Addr
buf [1500]byte
pos int
last []byte
ok chan bool
txReady chan bool
accept chan string
err chan error
sockets [8]*socket
sync.Mutex
}
func NewDevice(cfg *Config) *device {
return &device{
cfg: cfg,
ok: make(chan bool),
txReady: make(chan bool),
accept: make(chan string),
err: make(chan error),
}
}
func logDebug(msg string) {
//println("[DEBUG] " + msg)
}
func logError(msg string) {
println("[ERROR] " + msg)
}
func split(resp []byte, part int, del, on string) string {
parts := bytes.Split(resp, []byte(del))
if part >= len(parts) {
return "Split parts error getting " + on
}
return string(parts[part])
}
func (d *device) getFWVersion() string {
return split(d.last, 1, ":", "FW version")
}
func (d *device) saveMAC() {
raw := split(d.last, 1, ":", "MAC")
if len(raw) > 11 {
macStr := fmt.Sprintf("%s:%s:%s:%s:%s:%s",
raw[0:2], raw[2:4], raw[4:6],
raw[6:8], raw[8:10], raw[10:12])
d.mac, _ = net.ParseMAC(macStr)
}
}
var countryCodes = map[int]string{
1: "JP Japan",
2: "American Samoa",
3: "CA Canada",
4: "US",
5: "CN China",
6: "Hong Kong, China",
7: "Taiwan, China",
8: "MO Macau, China",
9: "IL Israel",
10: "Singapore",
11: "KR South Korea",
12: "TR Türkiye",
13: "AU Australia",
14: "ZA South Africa",
15: "BR Brazil",
}
func (d *device) getCountry() (code string) {
code = split(d.last, 1, ":", "county code")
codeNum, err := strconv.Atoi(code)
if err != nil {
return
}
if val, ok := countryCodes[codeNum]; ok {
code = val
}
return
}
func (d *device) saveIP() {
ipStr := split(d.last, 7, ",", "IP address")
gwStr := split(d.last, 8, ",", "gateway address")
d.ip, _ = netip.ParseAddr(ipStr)
d.gateway, _ = netip.ParseAddr(gwStr)
}
func (d *device) execute(cmd string, timeout int) (err error) {
logDebug("EXECUTE " + cmd)
d.uartMu.Lock()
_, err = d.uart.Write([]byte(cmd + "\r\n"))
d.uartMu.Unlock()
if err != nil {
return
}
t := time.NewTicker(time.Duration(timeout) * time.Millisecond)
defer t.Stop()
select {
case <-t.C:
return errors.New("Timed out")
case <-d.ok:
return
case err = <-d.err:
return
}
}
func (d *device) send(cmd string, timeout int) (err error) {
logDebug("EXECUTE " + cmd)
d.uartMu.Lock()
_, err = d.uart.Write([]byte(cmd + "\r\n"))
d.uartMu.Unlock()
if err != nil {
return
}
t := time.NewTicker(time.Duration(timeout) * time.Millisecond)
defer t.Stop()
select {
case <-t.C:
return errors.New("Timed out")
case <-d.txReady:
return
case err = <-d.err:
return
}
}
func (d *device) findSocket(id string) (*socket, error) {
for _, s := range d.sockets {
if s.id == id {
return s, nil
}
}
return nil, errors.New("Socket not found with id: " + id)
}
func (d *device) getSocket(sockfd int) (*socket, error) {
if sockfd < 0 || sockfd+1 > len(d.sockets) {
return nil, netdev.ErrInvalidSocketFd
}
if d.sockets[sockfd] == nil {
return nil, netdev.ErrInvalidSocketFd
}
return d.sockets[sockfd], nil
}
func (d *device) handle(event []byte) {
logDebug("GOT EVENT " + string(event))
switch {
// SocketDisconnect,<id>
case bytes.HasPrefix(event, []byte("SocketDisconnect")):
id := split(event, 1, ",", "SocketDisconnect")
s, err := d.findSocket(id)
if err == nil {
close(s.rx) // Sends io.EOF
}
// SocketSeed,<id>,<server id>
case bytes.HasPrefix(event, []byte("SocketSeed,2,1")):
//d.uart.Write([]byte("AT+SOCKET?" + "\r\n"))
}
}
func (d *device) processUART() {
if d.pos == 1 && d.buf[0] == '>' {
d.pos = 0
logDebug("GOT >")
d.txReady <- true
}
sofar := d.buf[:d.pos]
if !bytes.HasSuffix(sofar, []byte("\r\n")) {
return
}
// Strip CR/LF off end
sofar = sofar[:len(sofar)-2]
switch {
case bytes.HasPrefix(sofar, []byte("+EVENT:SocketDown")):
// +EVENT:SocketDown,<id>,<length>,<data>
parts := bytes.SplitN(sofar, []byte(","), 4)
if len(parts) != 4 {
logError("Error parsing +EVENT:SocketDown: " + string(sofar))
d.pos = 0
return
}
id := string(parts[1])
length, err := strconv.Atoi(string(parts[2]))
if err != nil {
logError("Error parsing length from: " + string(parts[2]))
d.pos = 0
return
}
if length != len(parts[3]) {
// This can happen if <data> actually contains a CR/LF.
// Return without resetting d.pos to continue reading
// in the full <data>.
return
}
s, err := d.findSocket(id)
if err != nil {
logError(err.Error())
d.pos = 0
return
}
logDebug("GOT +EVENT:SocketDown," + id + "," + string(parts[2]))
d.pos = 0
data := make([]byte, len(parts[3]))
copy(data, parts[3])
s.rx <- data
case bytes.HasPrefix(sofar, []byte("OK")):
d.pos = 0
logDebug("GOT OK")
d.ok <- true
case bytes.HasPrefix(sofar, []byte("ERROR")):
d.pos = 0
logDebug("GOT ERROR")
errStr := getErrStr(d.last)
d.err <- errors.New(errStr)
case bytes.HasPrefix(sofar, []byte("+EVENT:")):
d.pos = 0
event := sofar[len("+EVENT:"):]
d.handle(event)
default:
// Catch everything else and store in d.last
d.pos = 0
size := len(sofar)
if size > 0 {
d.last = make([]byte, size)
copy(d.last, sofar[:size])
logDebug("GOT LINE " + string(d.last))
}
}
}
func (d *device) serviceUART() {
for {
d.uartMu.Lock()
for d.uart.Buffered() > 0 {
if d.pos >= len(d.buf) {
println("Trying to write past buffer")
d.pos = 0
break
}
var err error
d.buf[d.pos], err = d.uart.ReadByte()
if err == nil {
d.pos++
d.processUART()
}
}
d.uartMu.Unlock()
time.Sleep(10 * time.Millisecond)
}
}
func (d *device) NetConnect(params *netlink.ConnectParams) error {
d.Lock()
defer d.Unlock()
d.uart = d.cfg.Uart
d.uart.Configure(machine.UARTConfig{
BaudRate: d.cfg.BaudRate,
TX: d.cfg.Tx,
RX: d.cfg.Rx,
})
go d.serviceUART()
fmt.Printf("\r\n")
fmt.Printf("TinyGo Combo-AT WiFi network device driver\r\n")
fmt.Printf("\r\n")
fmt.Printf("Driver version : %s\r\n", drivers.Version)
if len(params.Ssid) == 0 {
return netlink.ErrMissingSSID
}
// AT Test to see if device is alive
if err := d.execute("AT", 1000); err != nil {
return err
}
// Disable echo
if err := d.execute("ATE0", 1000); err != nil {
return err
}
// Get FW version
if err := d.execute("AT+GMR", 1000); err != nil {
return err
}
fmt.Printf("Combo-AT firmware version : %s\r\n", d.getFWVersion())
// Get/save MAC addresses
if err := d.execute("AT+CIPSTAMAC_DEF?", 1000); err != nil {
return err
}
d.saveMAC()
fmt.Printf("MAC address : %s\r\n", d.mac.String())
// Set country code US
if err := d.execute("AT+WCOUNTRY=4", 1000); err != nil {
return err
}
// Get country code
if err := d.execute("AT+WCOUNTRY?", 1000); err != nil {
return err
}
fmt.Printf("WiFi country code : %s\r\n", d.getCountry())
// Set Wi-Fi working mode to STA and save to flash
if err := d.execute("AT+WMODE=1,1", 1000); err != nil {
return err
}
// Connect to Wifi AP (keep trying until connected)
fmt.Printf("\r\n")
cmd := "AT+WJAP=" + params.Ssid + "," + params.Passphrase
for {
fmt.Printf("Connecting to WiFi SSID '%s'...", params.Ssid)
if err := d.execute(cmd, 20000); err != nil {
fmt.Printf("FAILED (%s)\r\n", err.Error())
continue
}
break
}
fmt.Printf("CONNECTED\r\n")
// Automatically reconnect to Wi-Fi after power on
if err := d.execute("AT+WAUTOCONN=1", 1000); err != nil {
return err
}
// Get/save IP/gateway addresses
if err := d.execute("AT+WJAP?", 1000); err != nil {
return err
}
d.saveIP()
fmt.Printf("\r\n")
fmt.Printf("DHCP-assigned IP : %s\r\n", d.ip)
fmt.Printf("DHCP-assigned gateway : %s\r\n", d.gateway)
fmt.Printf("\r\n")
// Set socket receiving mode to active
if err := d.execute("AT+SOCKETRECVCFG=1", 1000); err != nil {
return err
}
return nil
}
func (d *device) NetDisconnect() {
d.Lock()
defer d.Unlock()
// Disconnect from WiFi AP
d.execute("AT+WDISCONNECT", 1000)
}
func (d *device) NetNotify(cb func(netlink.Event)) {
fmt.Printf("\r\n%s\r\n", netlink.ErrNotSupported)
}
func (d *device) GetHardwareAddr() (net.HardwareAddr, error) {
return d.mac, nil
}
func (d *device) _getHostByName(name string) (ip netip.Addr, err error) {
if err = d.execute("AT+WDOMAIN="+name, 1000); err != nil {
return
}
ipStr := split(d.last, 1, ":", "host by name")
return netip.ParseAddr(ipStr)
}
func (d *device) GetHostByName(name string) (ip netip.Addr, err error) {
// If it's already a dotted-network address, and not a host name,
// return it
ip, err = netip.ParseAddr(name)
if err == nil {
return
}
d.Lock()
defer d.Unlock()
return d._getHostByName(name)
}
func (d *device) Addr() (netip.Addr, error) {
return d.ip, nil
}
func (d *device) Socket(domain, stype, protocol int) (int, error) {
switch domain {
case netdev.AF_INET:
default:
return -1, netdev.ErrFamilyNotSupported
}
switch {
case protocol == netdev.IPPROTO_TCP && stype == netdev.SOCK_STREAM:
case protocol == netdev.IPPROTO_TLS && stype == netdev.SOCK_STREAM:
case protocol == netdev.IPPROTO_UDP && stype == netdev.SOCK_DGRAM:
default:
return -1, netdev.ErrProtocolNotSupported
}
d.Lock()
defer d.Unlock()
// Search for empty slot in sockets array
for fd, s := range d.sockets {
if s == nil {
// Found one
d.sockets[fd] = &socket{
protocol: protocol,
rx: make(chan []byte, 10),
}
return fd, nil
}
}
return -1, netdev.ErrNoMoreSockets
}
func (d *device) Bind(sockfd int, ip netip.AddrPort) error {
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return err
}
s.laddr = ip
return nil
}
func (d *device) Connect(sockfd int, host string, ip netip.AddrPort) error {
var addr string
var cmd string
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return err
}
if host == "" {
addr = ip.Addr().String()
} else {
ip, err := d._getHostByName(host)
if err != nil {
return err
}
addr = ip.String()
}
port := strconv.Itoa(int(ip.Port()))
switch s.protocol {
case netdev.IPPROTO_UDP:
cmd = "AT+SOCKET=2," + addr + "," + port
case netdev.IPPROTO_TCP:
cmd = "AT+SOCKET=4," + addr + "," + port
case netdev.IPPROTO_TLS:
cmd = "AT+SOCKET=7," + addr + "," + port
}
if cmd == "" {
return netdev.ErrProtocolNotSupported
}
if err := d.execute(cmd, 20000); err != nil {
return err
}
s.id = split(d.last, 1, "=", "connection ID")
return nil
}
func (d *device) Listen(sockfd, backlog int) error {
// TODO Creating a TCP server socket isn't working when in STA mode,
// TODO returning error "Socket bind error".
// TODO The reference example shows a TCP server example in AP mode.
/*
var cmd string
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return err
}
port := strconv.Itoa(int(s.laddr.Port()))
switch s.protocol {
case netdev.IPPROTO_UDP:
cmd = "AT+SOCKET=1," + port
case netdev.IPPROTO_TCP:
cmd = "AT+SOCKET=3," + port
}
if cmd == "" {
return netdev.ErrProtocolNotSupported
}
if err := d.execute(cmd, 20000); err != nil {
return err
}
s.id = split(d.last, 1, "=", "connection ID")
*/
return netdev.ErrNotSupported
}
func (d *device) Accept(sockfd int) (int, netip.AddrPort, error) {
return 0, netip.AddrPort{}, netdev.ErrNotSupported
}
func (d *device) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return 0, err
}
cmd := fmt.Sprintf("AT+SOCKETSEND=%s,%d", s.id, len(buf))
if err := d.send(cmd, 1000); err != nil {
return 0, err
}
// AT+SOCKETSEND will sub-packet send data into 1024-byte chunks,
// automatically, so send the full buffer in one shot, even if it's
// bigger than 1024 bytes.
d.uartMu.Lock()
n, err := d.uart.Write(buf)
d.uartMu.Unlock()
if err != nil {
return 0, err
}
// Expecting "OK" after good send, or "ERROR"
t := time.NewTicker(time.Duration(1000) * time.Millisecond)
defer t.Stop()
select {
case <-t.C:
return 0, errors.New("Timed out")
case <-d.ok:
return n, nil
case err = <-d.err:
return 0, err
}
}
func (d *device) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return 0, err
}
// 1. Use leftover data first
if len(s.remainder) > 0 {
n := copy(buf, s.remainder)
s.remainder = s.remainder[n:]
return n, nil
}
// 2. Get new data from the channel
data, ok := <-s.rx
if !ok {
// Socket closed, return EOF
return 0, io.EOF
}
// 3. Copy data, handle leftovers
n := copy(buf, data)
if n < len(data) {
s.remainder = data[n:]
}
return n, nil
}
func (d *device) Close(sockfd int) error {
d.Lock()
defer d.Unlock()
s, err := d.getSocket(sockfd)
if err != nil {
return err
}
// Delete socket only if connection was successful (s.id is set)
if s.id != "" {
cmd := fmt.Sprintf("AT+SOCKETDEL=%s", s.id)
if err = d.execute(cmd, 1000); err != nil {
return err
}
}
d.sockets[sockfd] = nil
return nil
}
func (d *device) SetSockOpt(sockfd, level, opt int, value interface{}) error {
return netdev.ErrNotSupported
}
+86
View File
@@ -0,0 +1,86 @@
package comboat
import (
"bytes"
"strconv"
)
var errStrings = map[int]string{
// System framework related error codes
0: "success",
1: "The command is not supported (the combo framework contains the command but the current platform has not transplanted or adapted to support it)",
2: "The command parameters contain unsupported operations (the current platform only supports some operations for this command)",
3: "The instruction format is incorrect (this refers to the wrong number of parameters, for example, two parameters are required, but only one parameter is entered)",
4: "Parameter error (the content of the parameter is wrong, for example, a number between 0 and 9 is required, but 10 or xyz is passed in, which is a parameter error)",
5: "Parameter length error (command length exceeds the maximum supported length)",
31: "The current command has not ended and needs to report the status asynchronously. This value is used by the state machine to determine the use of the command and no message is returned.",
32: "Unknown error (or unhandled error type)",
// Common error codes
33: "malloc error",
34: "Failed to read buf",
35: "Failed to write buf",
36: "Configuration error (configuration error loaded from memory, for example, we set port -1 for OTA upgrade, and check port error when executing AT+OTA, then configuration error will be reported)",
37: "Failed to create task",
38: "Flash read and write failure",
39: "Serial port configuration error, unsupported baud rate",
40: "Serial port configuration error, unsupported data bits",
41: "Serial port configuration error, unsupported stop bit",
42: "Serial port configuration error, unsupported parity bit",
43: "Serial port configuration error, unsupported flow control",
44: "Serial port configuration failed",
45: "Wrong username/password",
46: "Low power mode error or unsupported low power mode",
47: "Uninitialized configuration data error (including io mapping data)",
63: "General error code (without other information)",
// Wi-Fi related error codes
64: "Wi-Fi not initialized or initialization failed",
65: "Wi-Fi mode error (unable to connect to Wi-Fi in single AP mode)",
66: "Wi-Fi connection failed",
67: "Wi-Fi connection successful, error in obtaining IP (DHCP)",
68: "Failed to obtain encryption method",
69: "The specified AP was not found.",
70: "Wi-Fi scan start failed",
71: "Wi-Fi scan timeout",
72: "Failed to enable AP hotspot",
73: "Failed to obtain the Wi-Fi information of the router or the AP information that you enabled yourself",
74: "The network card (STA/AP) is not running",
75: "Wi-Fi country code error (unsupported Wi-Fi country code)",
76: "The current network configuration mode is wrong.",
95: "Wi-Fi connection unknown error",
// Socket related error codes
96: "Failed to create socket",
97: "Socket connection failed",
98: "DNS Failure",
99: "The socket status is wrong (for example, TCP is not connected yet)",
100: "Socket type error",
101: "Socket send failed",
102: "Socket receive failed",
103: "Socket monitoring thread creation failed",
104: "Socket bind error",
105: "The current connection cannot be transparently linked (wrong socket type or number)",
106: "PING test failed (all packets lost)",
107: "Wi-Fi country code error (unsupported Wi-Fi country code)",
108: "SSL Config Error",
109: "SSL verification error (usually caused by unsupported SSL encryption type or certificate error)",
127: "Unknown socket error",
}
func getErrStr(errLine []byte) (errStr string) {
errStr = "Can't parse ERROR response"
tokens := bytes.Split(errLine, []byte(":"))
if len(tokens) > 1 {
errCode, err := strconv.Atoi(string(tokens[1]))
if err == nil {
errStr = errStrings[errCode]
}
}
return
}
+2 -2
View File
@@ -19,7 +19,7 @@ type OneWireDevice interface {
Write(uint8)
Read() uint8
Select([]uint8) error
Сrc8([]uint8, int) uint8
Сrc8([]uint8) uint8
}
// Device wraps a connection to an 1-Wire devices.
@@ -69,7 +69,7 @@ func (d Device) ReadTemperatureRaw(romid []uint8) ([]uint8, error) {
for i := 0; i < 9; i++ {
spb[i] = d.owd.Read()
}
if d.owd.Сrc8(spb, 8) != spb[8] {
if d.owd.Сrc8(spb) != 0 {
return nil, errReadTemperature
}
return spb[:2:2], nil
+288 -36
View File
@@ -5,10 +5,12 @@
package ds3231 // import "tinygo.org/x/drivers/ds3231"
import (
"encoding/binary"
"errors"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/internal/regmap"
)
type Mode uint8
@@ -17,6 +19,7 @@ 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
@@ -24,54 +27,50 @@ type Device struct {
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) Device {
return Device{
d := 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 {
data := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data)
status, err := d.d.Read8(REG_STATUS)
if err != nil {
return false
}
return (data[0] & (1 << OSF)) == 0x00
return (status & (1 << OSF)) == 0x00
}
// IsRunning returns if the oscillator is running
func (d *Device) IsRunning() bool {
data := []uint8{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
control, err := d.d.Read8(REG_CONTROL)
if err != nil {
return false
}
return (data[0] & (1 << EOSC)) == 0x00
return (control & (1 << EOSC)) == 0x00
}
// SetRunning starts the internal oscillator
func (d *Device) SetRunning(isRunning bool) error {
data := []uint8{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
control, err := d.d.Read8(REG_CONTROL)
if err != nil {
return err
}
if isRunning {
data[0] &^= uint8(1 << EOSC)
control &^= uint8(1 << EOSC)
} else {
data[0] |= 1 << EOSC
control |= 1 << EOSC
}
err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
if err != nil {
return err
}
return nil
return d.d.Write8(REG_CONTROL, control)
}
// SetTime sets the date and time in the DS3231. The DS3231 hardware supports
@@ -86,18 +85,16 @@ func (d *Device) SetRunning(isRunning bool) error {
// 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 {
data := []byte{0}
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data)
status, err := d.d.Read8(REG_STATUS)
if err != nil {
return err
}
data[0] &^= 1 << OSF
err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_STATUS, data)
if err != nil {
status &^= 1 << OSF
if err = d.d.Write8(REG_STATUS, status); 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()))
@@ -118,21 +115,16 @@ func (d *Device) SetTime(dt time.Time) error {
data[5] = uint8ToBCD(uint8(dt.Month()) | centuryFlag)
data[6] = uint8ToBCD(year)
err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_TIMEDATE, data)
if err != nil {
return err
}
return nil
return d.bus.Tx(d.Address, append([]byte{REG_TIMEDATE}, data...), nil)
}
// ReadTime returns the date and time
func (d *Device) ReadTime() (dt time.Time, err error) {
data := make([]uint8, 7)
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_TIMEDATE, data)
if err != nil {
if err = d.d.ReadData(REG_TIMEDATE, data); err != nil {
return
}
second := bcdToInt(data[0] & 0x7F)
minute := bcdToInt(data[1])
hour := hoursBCDToInt(data[2])
@@ -150,12 +142,264 @@ func (d *Device) ReadTime() (dt time.Time, err error) {
// ReadTemperature returns the temperature in millicelsius (mC)
func (d *Device) ReadTemperature() (int32, error) {
data := make([]uint8, 2)
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_TEMP, data)
temp, err := d.d.Read16(REG_TEMP)
if err != nil {
return 0, err
}
return milliCelsius(data[0], data[1]), nil
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
@@ -172,8 +416,8 @@ func (d *Device) ReadTemperature() (int32, error) {
// 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(msb uint8, lsb uint8) int32 {
t256 := int16(uint16(msb)<<8 | uint16(lsb))
func milliCelsius(tempBytes uint16) int32 {
t256 := int16(uint16(tempBytes>>8)<<8 | uint16(tempBytes&0xFF))
t1000 := int32(t256) / 64 * 250
return t1000
}
@@ -200,3 +444,11 @@ 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
}
+13 -13
View File
@@ -5,71 +5,71 @@ import (
)
func TestPositiveMilliCelsius(t *testing.T) {
t1000 := milliCelsius(0, 0)
t1000 := milliCelsius(0)
if t1000 != 0 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0, 0b01000000)
t1000 = milliCelsius(0b0000000001000000)
if t1000 != 250 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0, 0b10000000)
t1000 = milliCelsius(0b0000000010000000)
if t1000 != 500 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0, 0b11000000)
t1000 = milliCelsius(0b0000000011000000)
if t1000 != 750 {
t.Fatal(t1000)
}
t1000 = milliCelsius(1, 0b00000000)
t1000 = milliCelsius(0b0000000100000000)
if t1000 != 1000 {
t.Fatal(t1000)
}
t1000 = milliCelsius(2, 0b00000000)
t1000 = milliCelsius(0b0000001000000000)
if t1000 != 2000 {
t.Fatal(t1000)
}
// highest temperature is 127.750C
t1000 = milliCelsius(0x7f, 0b11000000)
t1000 = milliCelsius(0b0111111111000000)
if t1000 != 127750 {
t.Fatal(t1000)
}
}
func TestNegativeMilliCelsius(t *testing.T) {
t1000 := milliCelsius(0xff, 0b11000000)
t1000 := milliCelsius(0b1111111111000000)
if t1000 != -250 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0xff, 0b10000000)
t1000 = milliCelsius(0b1111111110000000)
if t1000 != -500 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0xff, 0b01000000)
t1000 = milliCelsius(0b1111111101000000)
if t1000 != -750 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0xff, 0b00000000)
t1000 = milliCelsius(0b1111111100000000)
if t1000 != -1000 {
t.Fatal(t1000)
}
t1000 = milliCelsius(0xfe, 0b00000000)
t1000 = milliCelsius(0b1111111000000000)
if t1000 != -2000 {
t.Fatal(t1000)
}
// lowest temperature is -128.000C
t1000 = milliCelsius(0x80, 0b00000000)
t1000 = milliCelsius(0b1000000000000000)
if t1000 != -128000 {
t.Fatal(t1000)
}
+49
View File
@@ -46,3 +46,52 @@ 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
)
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build tinygo && (rp2040 || stm32 || k210 || esp32c3 || nrf || (avr && (atmega328p || atmega328pb)))
//go:build tinygo && (rp2040 || rp2350 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
// Implementation based on:
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
+225
View File
@@ -0,0 +1,225 @@
// Package ens160 provides a driver for the ScioSense ENS160 digital gas sensor.
//
// Datasheet: https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
package ens160
import (
"encoding/binary"
"errors"
"time"
"tinygo.org/x/drivers"
)
const (
defaultTimeout = 30 * time.Millisecond
shortTimeout = 1 * time.Millisecond
)
// Conversion constants for environment data compensation.
const (
kelvinOffsetMilli = 273150 // 273.15 K in milli-units
tempRawFactor = 64 // As per datasheet for TEMP_IN
humRawFactor = 512 // As per datasheet for RH_IN
milliFactor = 1000 // For converting from milli-units
roundingTerm = milliFactor / 2 // For rounding before integer division
)
// validityStrings provides human-readable descriptions for validity flags.
var validityStrings = [...]string{
ValidityNormalOperation: "normal operation",
ValidityWarmUpPhase: "warm-up phase, wait ~3 minutes for valid data",
ValidityInitialStartUpPhase: "initial start-up phase, wait ~1 hour for valid data",
ValidityInvalidOutput: "invalid output",
}
// Device wraps an I2C connection to an ENS160 device.
type Device struct {
bus drivers.I2C // I²C implementation
addr uint16 // 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
@@ -0,0 +1,54 @@
package ens160
import (
"testing"
)
func TestCalculateTempRaw(t *testing.T) {
testCases := []struct {
name string
tempMilliC int32
expectedRaw uint16
}{
{"25°C", 25000, 19082},
{"-10.5°C", -10500, 16810},
{"Min temp", -40000, 14922},
{"Below min", -50000, 14922},
{"Max temp", 85000, 22922},
{"Above max", 90000, 22922},
{"Zero", 0, 17482},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
raw := calculateTempRaw(tc.tempMilliC)
if raw != tc.expectedRaw {
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
}
})
}
}
func TestCalculateHumRaw(t *testing.T) {
testCases := []struct {
name string
rhMilliPct int32
expectedRaw uint16
}{
{"50%", 50000, 25600},
{"0%", 0, 0},
{"100%", 100000, 51200},
{"Below 0%", -10000, 0},
{"Above 100%", 110000, 51200},
{"33.3%", 33300, 17050},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
raw := calculateHumRaw(tc.rhMilliPct)
if raw != tc.expectedRaw {
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
}
})
}
}
+65
View File
@@ -0,0 +1,65 @@
package ens160
// DefaultAddress is the default I2C address for the ENS160 when the ADDR pin is
// connected to high (3.3V). When connected to low (GND), the address is 0x52.
const DefaultAddress = 0x53
// Registers
const (
regPartID = 0x00
regOpMode = 0x10
regConfig = 0x11
regCommand = 0x12
regTempIn = 0x13
regRhIn = 0x15
regStatus = 0x20
regAQI = 0x21
regTVOC = 0x22
regECO2 = 0x24
regDataT = 0x30
regDataRH = 0x32
regMISR = 0x38
regGPRWrite = 0x40
regGPRRead = 0x48
)
// Operating modes
const (
ModeDeepSleep = 0x00
ModeIdle = 0x01
ModeStandard = 0x02
ModeReset = 0xF0
)
// Status register bits
const (
statusSTATAS = 1 << 7
statusSTATER = 1 << 6
statusValidityMask = 0x0C
statusValidityShift = 2
statusNEWDAT = 1 << 1
statusNEWGPR = 1 << 0
)
// Validity flags
const (
ValidityNormalOperation = 0x00
ValidityWarmUpPhase = 0x01 // need ~3 minutes until valid data
ValidityInitialStartUpPhase = 0x02 // need ~1 hour until valid data
ValidityInvalidOutput = 0x03
)
// Commands
const (
cmdNOP = 0x00
cmdGetAppVer = 0x0E
cmdClrGPR = 0xCC
)
// Part IDs
const (
LowPartID = 0x60
HighPartID = 0x01
)
+1 -2
View File
@@ -122,7 +122,7 @@ func (d *Device) NetDisconnect() {
}
func (d *Device) NetNotify(cb func(netlink.Event)) {
// Not supported
fmt.Printf("\r\n%s\r\n", netlink.ErrNotSupported)
}
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
@@ -473,7 +473,6 @@ func (d *Device) parseIPD(end int) error {
}
// load up the socket data
//d.data = append(d.data, d.response[e+1:end]...)
d.data = append(d.data, d.response[e+1:e+1+v]...)
return nil
}
+66
View File
@@ -0,0 +1,66 @@
// 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)
}
}
+74
View File
@@ -0,0 +1,74 @@
// 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)
}
}
@@ -3,10 +3,9 @@ package main
import (
"machine"
"strconv"
"time"
"fmt"
"tinygo.org/x/drivers/ds3231"
)
@@ -26,19 +25,19 @@ func main() {
if !running {
err := rtc.SetRunning(true)
if err != nil {
fmt.Println("Error configuring RTC")
println("Error configuring RTC")
}
}
for {
dt, err := rtc.ReadTime()
if err != nil {
fmt.Println("Error reading date:", err)
println("Error reading date:", err)
} else {
fmt.Printf("Date: %d/%s/%02d %02d:%02d:%02d \r\n", dt.Year(), dt.Month(), dt.Day(), dt.Hour(), dt.Minute(), dt.Second())
println(dt.Format(time.DateTime))
}
temp, _ := rtc.ReadTemperature()
fmt.Printf("Temperature: %.2f °C \r\n", float32(temp)/1000)
println("Temperature:", strconv.FormatFloat(float64(temp)/1000, 'f', -1, 32), "°C")
time.Sleep(time.Second * 1)
}
+56
View File
@@ -0,0 +1,56 @@
// This example demonstrates ENS160 usage.
//
// Wiring:
// - VCC to 3.3V, GND to ground
// - SDA to board SDA, SCL to board SCL
package main
import (
"time"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/ens160"
)
func main() {
err := machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
})
if err != nil {
println("Failed to configure I2C:", err)
}
dev := ens160.New(machine.I2C0, ens160.DefaultAddress)
connected := dev.Connected()
if !connected {
println("ENS160 not detected")
return
}
println("ENS160 detected")
if err := dev.Configure(); err != nil {
println("Failed to configure ENS160:", err)
}
for {
err := dev.Update(drivers.Concentration)
if err != nil {
println("Error reading ENS160: %v\n", err)
time.Sleep(5 * time.Second)
continue
}
println(
"AQI:", dev.AQI(),
"TVOC:", dev.TVOC(),
"eCO2:", dev.ECO2(),
"Validity:", dev.ValidityString(),
)
time.Sleep(2 * time.Second)
}
}
+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.NewI2C(machine.I2C0)
ublox := gps.NewI2CWithAddress(machine.I2C0, gps.UBLOX_I2C_ADDRESS)
parser := gps.NewParser()
var fix gps.Fix
for {
+18 -6
View File
@@ -8,7 +8,6 @@ import (
)
func main() {
println("GPS UART Example")
machine.UART1.Configure(machine.UARTConfig{BaudRate: 9600})
ublox := gps.NewUART(machine.UART1)
parser := gps.NewParser()
@@ -16,14 +15,24 @@ func main() {
for {
s, err := ublox.NextSentence()
if err != nil {
println(err)
continue
switch err {
case gps.ErrUnknownNMEASentence, gps.ErrInvalidNMEASentence, gps.ErrInvalidNMEASentenceLength:
continue
default:
println("sentence error:", err)
continue
}
}
fix, err = parser.Parse(s)
if err != nil {
println(err)
continue
switch err {
case gps.ErrUnknownNMEASentence, gps.ErrInvalidNMEASentence, gps.ErrInvalidNMEASentenceLength:
continue
default:
println("parse error:", err)
continue
}
}
if fix.Valid {
print(fix.Time.Format("15:04:05"))
@@ -43,7 +52,10 @@ func main() {
}
println()
} else {
println("No fix")
if fix.Type == gps.GSV {
// GSV sentence provides satellite count even if no fix yet
println(fix.Satellites, "satellites visible")
}
}
time.Sleep(200 * time.Millisecond)
}
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/honeyhsc"
)
// Data taken from https://github.com/rodan/honeywell_hsc_ssc_i2c/blob/master/hsc_ssc_i2c.cpp
// these defaults are valid for the HSCMRNN030PA2A3 chip
const (
i2cAddress = 0x28
// 10%
outputMinimum = 0x666
// 90% of 2^14 - 1
outputMax = 0x399A
// min is 0 for sensors that give absolute values
pressureMin = 0
// 30psi (and we want results in millipascals)
// pressureMax = 206842.7
pressureMax = 206843 * 1000
)
func main() {
bus := machine.I2C0
err := bus.Configure(machine.I2CConfig{
Frequency: 400_000, // 100kHz minimum and 400kHz I2C maximum clock. 50 to 800 for SPI.
SDA: machine.I2C0_SDA_PIN,
SCL: machine.I2C0_SCL_PIN,
})
if err != nil {
panic(err.Error())
}
sensor := honeyhsc.NewDevI2C(bus, i2cAddress, outputMinimum, outputMax, pressureMin, pressureMax)
for {
time.Sleep(time.Second)
const measuremask = drivers.Pressure | drivers.Temperature
err := sensor.Update(measuremask)
if err != nil {
println("error updating measurements:", err.Error())
continue
}
P := sensor.Pressure()
T := sensor.Temperature()
println("pressure:", P, "temperature:", T)
}
}
+29
View File
@@ -0,0 +1,29 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/ina219"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
dev := ina219.New(machine.I2C0)
dev.Configure()
for {
busVoltage, shuntVoltage, current, power, err := dev.Measurements()
if err != nil {
println("Error reading measurements", err)
}
println("Bus Voltage:", busVoltage, "V")
println("Shunt Voltage:", shuntVoltage/100, "mV")
println("Current:", current, "mA")
println("Power:", power, "mW")
time.Sleep(10 * time.Millisecond)
}
}
+12 -3
View File
@@ -14,9 +14,18 @@ func main() {
i2c.Configure(machine.I2CConfig{SCL: machine.SCL1_PIN, SDA: machine.SDA1_PIN})
accel := lis3dh.New(i2c)
accel.Address = lis3dh.Address1 // address on the Circuit Playground Express
accel.Configure()
accel.SetRange(lis3dh.RANGE_2_G)
err := accel.Configure(lis3dh.Config{
Address: lis3dh.Address1, // address on the Circuit Playground Express
})
for err != nil {
println("could not configure LIS3DH:", err)
time.Sleep(time.Second)
}
err = accel.SetRange(lis3dh.RANGE_2_G)
for err != nil {
println("could not set acceleration range:", err)
time.Sleep(time.Second)
}
println(accel.Connected())
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/lsm303dlhc"
)
func main() {
// LSM303DLHC is connected to the I2C0 bus on Adafruit Feather M4 via pins: 20(SDA) and 21(SCL).
machine.I2C0.Configure(machine.I2CConfig{})
sensor := lsm303dlhc.New(machine.I2C0)
//default settings
err := sensor.Configure(lsm303dlhc.Configuration{
AccelPowerMode: lsm303dlhc.ACCEL_POWER_NORMAL,
AccelRange: lsm303dlhc.ACCEL_RANGE_2G,
AccelDataRate: lsm303dlhc.ACCEL_DATARATE_100HZ,
MagPowerMode: lsm303dlhc.MAG_POWER_NORMAL,
MagSystemMode: lsm303dlhc.MAG_SYSTEM_CONTINUOUS,
MagDataRate: lsm303dlhc.MAG_DATARATE_10HZ,
})
if err != nil {
for {
println("Failed to configure", err.Error())
time.Sleep(time.Second)
}
}
for {
accel_x, accel_y, accel_z, err := sensor.ReadAcceleration()
if err != nil {
println("Failed to read accel", err.Error())
}
println("ACCEL_X:", accel_x, " ACCEL_Y:", accel_y, " ACCEL_Z:", accel_z)
mag_x, mag_y, mag_z, err := sensor.ReadMagneticField()
if err != nil {
println("Failed to read mag", err.Error())
}
println("MAG_X:", mag_x, " MAG_Y:", mag_y, " MAG_Z:", mag_z)
pitch, roll, _ := sensor.ReadPitchRoll()
println("Pitch:", float32(pitch), " Roll:", float32(roll))
heading, _ := sensor.ReadCompass()
println("Heading:", float32(heading), "degrees")
temp, _ := sensor.ReadTemperature()
println("Temperature:", float32(temp)/1000, "*C")
println("\n")
time.Sleep(time.Millisecond * 250)
}
}
+34
View File
@@ -0,0 +1,34 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/max6675"
)
// example for reading temperature from a thermocouple
func main() {
// Pins are for an Adafruit Feather nRF52840 Express
machine.D5.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.D5.High()
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 1_000_000,
SCK: machine.SPI0_SCK_PIN,
SDI: machine.SPI0_SDI_PIN,
})
thermocouple := max6675.NewDevice(machine.SPI0, machine.D5)
for {
temp, err := thermocouple.Read()
if err != nil {
println(err)
return
}
fmt.Printf("%0.02f C : %0.02f F\n", temp, (temp*9/5)+32)
time.Sleep(time.Second)
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ func main() {
SDI: machine.SPI0_SDI_PIN,
Mode: 0})
can := mcp2515.New(spi, csPin)
can.Configure()
can.Configure(mcp2515.Configuration{})
err := can.Begin(mcp2515.CAN500kBps, mcp2515.Clock8MHz)
if err != nil {
failMessage(err.Error())
+48
View File
@@ -0,0 +1,48 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/mcp9808"
)
func main() {
//tinygo monitor
time.Sleep(time.Millisecond * 5000)
//Configure I2C (in this case, I2C0 on RPI Pico), and wire the module accordingly
machine.I2C0.Configure(machine.I2CConfig{
SCL: machine.GP1,
SDA: machine.GP0,
})
//Create sensor
sensor := mcp9808.New(machine.I2C0)
if !sensor.Connected() {
println("MCP9808 not found")
return
} else {
println("MCP9808 found")
}
time.Sleep(time.Millisecond * 1000)
//Set resolution
sensor.SetResolution(mcp9808.Maximum)
time.Sleep(time.Millisecond * 1000)
//Read temp.
temp, err := sensor.ReadTemperature()
if err != nil {
println("MCP9808 error reading temperature")
println(err.Error())
return
} else {
fmt.Printf("Temperature: %.2f \n", temp)
}
return
}
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build ninafw || wioterminal
//go:build ninafw || wioterminal || comboat_fw
package main
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build ninafw || wioterminal
//go:build ninafw || wioterminal || comboat_fw
package main
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build ninafw || wioterminal
//go:build ninafw || wioterminal || comboat_fw
package main
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build ninafw || wioterminal
//go:build ninafw || wioterminal || comboat_fw
package main
+1 -1
View File
@@ -4,7 +4,7 @@
// Note: It may be necessary to increase the stack size when using
// paho.mqtt.golang. Use the -stack-size=4KB command line option.
//go:build ninafw || wioterminal || challenger_rp2040
//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw
package main
+1 -1
View File
@@ -4,7 +4,7 @@
// Note: It may be necessary to increase the stack size when using
// paho.mqtt.golang. Use the -stack-size=4KB command line option.
//go:build ninafw || wioterminal || challenger_rp2040
//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw
package main
+1 -1
View File
@@ -3,7 +3,7 @@
// It creates a UDP connection to request the current time and parse the
// response from a NTP server. The system time is set to NTP time.
//go:build ninafw || wioterminal || challenger_rp2040
//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw
package main
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build ninafw || wioterminal
//go:build ninafw || wioterminal || comboat_fw
package main
+1 -1
View File
@@ -31,7 +31,7 @@
// func. This forces segments to connect and run concurrently, which is a good
// test of the underlying driver's ability to handle concurrent connections.
//go:build ninafw || wioterminal
//go:build ninafw || wioterminal || comboat_fw
package main
+1 -1
View File
@@ -4,7 +4,7 @@
//
// nc -lk 8080
//go:build ninafw || wioterminal || challenger_rp2040
//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw
package main
+1 -1
View File
@@ -5,7 +5,7 @@
//
// nc -lk 8080
//go:build ninafw || wioterminal || challenger_rp2040 || pico
//go:build ninafw || wioterminal || challenger_rp2040 || comboat_fw
package main
+1 -1
View File
@@ -5,7 +5,7 @@
//
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
//go:build ninafw || wioterminal
//go:build ninafw || wioterminal || comboat_fw
package main
+1 -1
View File
@@ -17,7 +17,7 @@
// }
// ---------------------------------------------------------------------------
//go:build ninafw || wioterminal
//go:build ninafw || wioterminal || comboat_fw
package main
+1 -1
View File
@@ -6,7 +6,7 @@
// Note: It may be necessary to increase the stack size when using
// "golang.org/x/net/websocket". Use the -stack-size=4KB command line option.
//go:build ninafw || wioterminal
//go:build ninafw || wioterminal || comboat_fw
package main
+28
View File
@@ -0,0 +1,28 @@
// Connects to a pcf8591 ADC via I2C.
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/pcf8591"
)
var (
i2c = machine.I2C0
)
func main() {
i2c.Configure(machine.I2CConfig{})
adc := pcf8591.New(i2c)
adc.Configure()
// get "CH0" aka "machine.ADC" interface to channel 0 from ADC.
p := adc.CH0
for {
val := p.Get()
println(val)
time.Sleep(50 * time.Millisecond)
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
func init() {
spi = &machine.SPI0
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
func init() {
spi = &machine.SPI1
spi = machine.SPI1
sckPin = machine.SDCARD_SCK_PIN
sdoPin = machine.SDCARD_SDO_PIN
sdiPin = machine.SDCARD_SDI_PIN
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
func init() {
spi = &machine.SPI0
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
func init() {
spi = &machine.SDCARD_SPI
spi = machine.SDCARD_SPI
sckPin = machine.SDCARD_SCK_PIN
sdoPin = machine.SDCARD_SDO_PIN
sdiPin = machine.SDCARD_SDI_PIN
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
func init() {
spi = &machine.SPI0
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
func init() {
spi = &machine.SPI0
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
func init() {
spi = &machine.SPI2
spi = machine.SPI2
sckPin = machine.SCK2
sdoPin = machine.SDO2
sdiPin = machine.SDI2
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/seesaw"
)
// example reading the position of a rotary encoder (4991) powered by a seesaw
// https://learn.adafruit.com/adafruit-i2c-qt-rotary-encoder/arduino
func main() {
// This assumes you are using an Adafruit QT Py RP2040 for its Stemma QT connector
// https://www.adafruit.com/product/4900
i2c := machine.I2C1
i2c.Configure(machine.I2CConfig{
SCL: machine.I2C1_QT_SCL_PIN,
SDA: machine.I2C1_QT_SDA_PIN,
})
dev := seesaw.New(i2c)
dev.Address = 0x36
for {
time.Sleep(time.Second)
pos, err := dev.GetEncoderPosition(0, false)
if err != nil {
println(err)
continue
}
println(pos)
}
}
+19 -13
View File
@@ -25,19 +25,25 @@ func main() {
return
}
println("setting to 0°")
s.SetMicroseconds(1000)
time.Sleep(3 * time.Second)
println("setting to 45°")
s.SetMicroseconds(1500)
time.Sleep(3 * time.Second)
println("setting to 90°")
s.SetMicroseconds(2000)
time.Sleep(3 * time.Second)
for {
time.Sleep(time.Second)
println("setting to 0°")
s.SetAngle(0)
time.Sleep(3 * time.Second)
println("setting to 45°")
s.SetAngle(45)
time.Sleep(3 * time.Second)
println("setting to 90°")
s.SetAngle(90)
time.Sleep(3 * time.Second)
println("setting to 135°")
s.SetAngle(135)
time.Sleep(3 * time.Second)
println("setting to 180°")
s.SetAngle(180)
time.Sleep(3 * time.Second)
}
}
+90
View File
@@ -0,0 +1,90 @@
package main
import (
"image/color"
"machine"
"math/rand/v2"
"time"
"tinygo.org/x/drivers/sharpmem"
)
var (
// example wiring using a nice!view and nice!nano:
// (view) (nano)
// MOSI --> P0.24
// SCK ---> P0.22
// GND ---> GND
// VCC ---> 3.3V
// CS ----> P0.06
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN // SCK
sdoPin = machine.SPI0_SDO_PIN // MOSI
sdiPin = machine.SPI0_SDI_PIN // (any pin)
csPin = machine.P0_06 // CS
)
func main() {
time.Sleep(time.Second)
err := spi.Configure(machine.SPIConfig{
Frequency: 2000000,
SCK: sckPin,
SDO: sdoPin,
SDI: sdiPin,
Mode: 0,
LSBFirst: true,
})
if err != nil {
println("spi.Configure() failed, error:", err.Error())
return
}
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
display := sharpmem.New(spi, csPin)
cfg := sharpmem.ConfigLS011B7DH03
display.Configure(cfg)
// clear the display before first use
err = display.Clear()
if err != nil {
println("display.Clear() failed, error:", err.Error())
return
}
// random boxes pop into and out of existence
for {
x0 := int16(rand.IntN(int(cfg.Width - 7)))
y0 := int16(rand.IntN(int(cfg.Height - 7)))
for x2 := int16(0); x2 < 16; x2++ {
x2 := x2
c := color.RGBA{R: 255, G: 255, B: 255, A: 255}
if x2 >= 8 {
// effectively erases the box after it showed up
x2 = x2 - 8
c = color.RGBA{R: 0, G: 0, B: 0, A: 255}
}
for x := int16(0); x < x2; x++ {
for y := int16(0); y < 8; y++ {
display.SetPixel(x0+x, y0+y, c)
}
}
err = display.Display()
if err != nil {
println("display.Display() failed, error:", err.Error())
continue
}
time.Sleep(33 * time.Millisecond)
}
}
}
+88
View File
@@ -0,0 +1,88 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/si5351"
)
// Simple demo of the SI5351 clock generator.
// This is like the Arduino library example:
// https://github.com/adafruit/Adafruit_Si5351_Library/blob/master/examples/si5351/si5351.ino
// Which will configure the chip with:
// - PLL A at 900mhz
// - PLL B at 616.66667mhz
// - Clock 0 at 112.5mhz, using PLL A as a source divided by 8
// - Clock 1 at 13.5531mhz, using PLL B as a source divided by 45.5
// - Clock 2 at 10.76khz, using PLL B as a source divided by 900 and further divided with an R divider of 64.
func main() {
time.Sleep(5 * time.Second)
println("Si5351 Clockgen Test")
println()
// Configure I2C bus
machine.I2C0.Configure(machine.I2CConfig{})
// Create driver instance
clockgen := si5351.New(machine.I2C0)
// Initialize device
cnf := si5351.Config{
Capacitance: si5351.CrystalLoad10PF,
}
if err := clockgen.Configure(cnf); err != nil {
println("Failed to configure Si5351:", err.Error())
return
}
println("Si5351 configured")
// Now configure the clock outputs.
clockgen.SetFrequency(si5351.Clock0, 112_500_000)
println("Clock 0: 112.5mhz")
// Next configure clock 1 for 13.5531mhz (616.6667mhz / 45.5).
// This uses fractional division.
clockgen.SetFrequency(si5351.Clock1, 13_553_125)
println("Clock 1: 13.5531mhz")
// Finally configure clock 2 to output of 10.706khz.
clockgen.SetFrequency(si5351.Clock2, 10_706)
println("Clock 2: 10.706khz")
// After configuring the clocks enable the outputs.
clockgen.EnableOutput(si5351.Clock0, true)
clockgen.EnableOutput(si5351.Clock1, true)
clockgen.EnableOutput(si5351.Clock2, true)
println("All outputs enabled")
time.Sleep(time.Second)
clockgen.EnableOutput(si5351.Clock0, false)
clockgen.EnableOutput(si5351.Clock1, false)
clockgen.EnableOutput(si5351.Clock2, false)
println("All outputs disabled for 5 seconds")
time.Sleep(5 * time.Second)
// Now turn clock outputs on and off repeatedly
on := false
for {
if on {
println("Setting clock outputs off")
clockgen.EnableOutput(si5351.Clock0, false)
clockgen.EnableOutput(si5351.Clock1, false)
clockgen.EnableOutput(si5351.Clock2, false)
on = false
} else {
println("Setting clock outputs on")
clockgen.EnableOutput(si5351.Clock0, true)
clockgen.EnableOutput(si5351.Clock1, true)
clockgen.EnableOutput(si5351.Clock2, true)
on = true
}
time.Sleep(1 * time.Second)
}
}
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"machine"
"math/rand"
"tinygo.org/x/drivers/internal/pin"
"tinygo.org/x/drivers/ssd1289"
)
@@ -16,7 +17,7 @@ func main() {
//consider creating a more efficient bus implementation that uses
//your microcontrollers built in "ports"
//see rp2040bus.go for an example for the rapsberry pi pico
bus := ssd1289.NewPinBus([16]machine.Pin{
bus := ssd1289.NewPinBus([16]pin.Output{
machine.GP4, //DB0
machine.GP5, //DB1
machine.GP6, //DB2
-51
View File
@@ -1,51 +0,0 @@
package main
import (
"machine"
"image/color"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: ssd1306.Address_128_32,
Width: 128,
Height: 32,
})
display.ClearDisplay()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 127 {
deltaX = -deltaX
}
if y == 0 || y == 31 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
-60
View File
@@ -1,60 +0,0 @@
// This example shows how to use 128x64 display over I2C
// Tested on Seeeduino XIAO Expansion Board https://wiki.seeedstudio.com/Seeeduino-XIAO-Expansion-Board/
//
// According to manual, I2C address of the display is 0x78, but that's 8-bit address.
// TinyGo operates on 7-bit addresses and respective 7-bit address would be 0x3C, which we use below.
//
// To learn more about different types of I2C addresses, please see following page
// https://www.totalphase.com/support/articles/200349176-7-bit-8-bit-and-10-bit-I2C-Slave-Addressing
package main
import (
"machine"
"image/color"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: 0x3C,
Width: 128,
Height: 64,
})
display.ClearDisplay()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 127 {
deltaX = -deltaX
}
if y == 0 || y == 63 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
+59
View File
@@ -0,0 +1,59 @@
package main
// This example shows how to use SSD1306 OLED display driver over I2C and SPI.
//
// Check the `newSSD1306Display()` functions for I2C and SPI initializations.
import (
"runtime"
"image/color"
"time"
)
func main() {
display := newSSD1306Display()
display.ClearDisplay()
w, h := display.Size()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
traceTime := time.Now().UnixMilli() + 1000
frames := 0
ms := runtime.MemStats{}
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == w-1 {
deltaX = -deltaX
}
if y == 0 || y == h-1 {
deltaY = -deltaY
}
frames++
now := time.Now().UnixMilli()
if now >= traceTime {
runtime.ReadMemStats(&ms)
println("TS", now, "| FPS", frames, "| HeapInuse", ms.HeapInuse)
traceTime = now + 1000
frames = 0
}
}
}
+38
View File
@@ -0,0 +1,38 @@
//go:build xiao_ble
// This initializes SSD1306 OLED display driver over I2C.
//
// Seeed XIAO BLE board + SSD1306 128x32 I2C OLED display.
//
// Wiring:
// - XIAO GND -> OLED GND
// - XIAO 3v3 -> OLED VCC
// - XIAO D4 (SDA) -> OLED SDA
// - XIAO D5 (SCL) -> OLED SCK
//
// For your case:
// - Connect the display to I2C pins on your board.
// - Adjust I2C address and display size as needed.
package main
import (
"machine"
"tinygo.org/x/drivers/ssd1306"
)
func newSSD1306Display() *ssd1306.Device {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
SDA: machine.SDA0_PIN,
SCL: machine.SCL0_PIN,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: ssd1306.Address_128_32, // or ssd1306.Address
Width: 128,
Height: 32, // or 64
})
return display
}
+27
View File
@@ -0,0 +1,27 @@
//go:build thumby
// This initializes SSD1306 OLED display driver over SPI.
//
// Thumby board has a tiny built-in 72x40 display.
//
// As the display is built-in, no wiring is needed.
package main
import (
"machine"
"tinygo.org/x/drivers/ssd1306"
)
func newSSD1306Display() *ssd1306.Device {
machine.SPI0.Configure(machine.SPIConfig{})
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
display.Configure(ssd1306.Config{
Width: 72,
Height: 40,
ResetCol: ssd1306.ResetValue{28, 99},
ResetPage: ssd1306.ResetValue{0, 5},
})
return display
}
+40
View File
@@ -0,0 +1,40 @@
//go:build xiao_rp2040
// This initializes SSD1306 OLED display driver over SPI.
//
// Seeed XIAO RP2040 board + SSD1306 128x64 SPI OLED display.
//
// Wiring:
// - XIAO GND -> OLED GND
// - XIAO 3v3 -> OLED VCC
// - XIAO D8 (SCK) -> OLED D0
// - XIAO D10 (SDO) -> OLED D1
// - XIAO D4 -> OLED RES
// - XIAO D5 -> OLED DC
// - XIAO D6 -> OLED CS
//
// For your case:
// - Connect the display to SPI pins on your board.
// - Adjust RES, DC and CS pins as needed.
// - Adjust SPI frequency as needed.
// - Adjust display size as needed.
package main
import (
"machine"
"tinygo.org/x/drivers/ssd1306"
)
func newSSD1306Display() *ssd1306.Device {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 50 * machine.MHz,
})
display := ssd1306.NewSPI(machine.SPI0, machine.D5, machine.D4, machine.D6)
display.Configure(ssd1306.Config{
Width: 128,
Height: 64,
})
return display
}
-48
View File
@@ -1,48 +0,0 @@
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
})
display := ssd1306.NewSPI(machine.SPI0, machine.P8, machine.P7, machine.P9)
display.Configure(ssd1306.Config{
Width: 128,
Height: 64,
})
display.ClearDisplay()
x := int16(64)
y := int16(32)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 127 {
deltaX = -deltaX
}
if y == 0 || y == 63 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
-50
View File
@@ -1,50 +0,0 @@
// This example using the SSD1306 OLED display over SPI on the Thumby board
// A very tiny 72x40 display.
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{})
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
display.Configure(ssd1306.Config{
Width: 72,
Height: 40,
ResetCol: ssd1306.ResetValue{28, 99},
ResetPage: ssd1306.ResetValue{0, 5},
})
display.ClearDisplay()
x := int16(36)
y := int16(20)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 71 {
deltaX = -deltaX
}
if y == 0 || y == 39 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"machine"
"tinygo.org/x/drivers/tmc2209"
)
func main() {
uart := machine.UART0
comm := tmc2209.NewUARTComm(*uart, 0)
// Create an instance of the TMC2209 with UART communication
tmc := tmc2209.NewTMC2209(comm, 0x00) // Replace 0x00 with the appropriate address
// Set up the TMC2209 driver
err := tmc.Setup()
if err != nil {
println("Failed to set up TMC2209: ", err)
}
// Write to a register (example: setting a register value)
err = tmc.WriteRegister(0x10, 0x12345678) // Replace 0x10 with the register address and 0x12345678 with the value
if err != nil {
println("Failed to write register:", err)
}
// Read from a register (example: reading a register value)
value, err := tmc.ReadRegister(0x10)
if err != nil {
println("Failed to read register: ", err)
}
// Output the read value
println("Register value: ", value)
}
+61
View File
@@ -0,0 +1,61 @@
// Connects to SPI1 on a RP2040 (Pico)
package main
import (
"machine"
"tinygo.org/x/drivers/tmc5160"
)
func main() {
// Step 1. Setup your protocol. SPI setup shown below
spi := machine.SPI1
spi.Configure(machine.SPIConfig{
Frequency: 12000000, // Upto 12 MHZ is pretty stable. Reduce to 5 or 6 Mhz if you are experiencing issues
Mode: 3,
LSBFirst: false,
})
// Step 2. Set up all associated Pins
csPin0 := machine.GPIO13
csPin0.Configure(machine.PinConfig{Mode: machine.PinOutput})
enn0 := machine.GPIO18
enn0.Configure(machine.PinConfig{Mode: machine.PinOutput})
// csPins is a map of all chip select pins in a multi driver setup.
//Only one pin csPin0 mapped to "0"is shown in this example, but add more mappings as required
csPins := map[uint8]machine.Pin{0: csPin0}
//bind csPin to driverAdddress
driverAddress := uint8(0) // Let's assume we are working with driver at address 0x01
// Step 3. Bind the communication interface to the protocol
comm := tmc5160.NewSPIComm(spi, csPins)
// Step 4. Define your stepper like this below
//stepper := tmc5160.NewStepper(angle , gearRatio vSupply rCoil , lCoil , iPeak , rSense , mSteps, fclk )
stepper := tmc5160.NewDefaultStepper() // Default Stepper should be used only for testing.
// Step 5. Instantiate your driver
driver := tmc5160.NewDriver(
comm,
driverAddress,
enn0,
stepper)
// Setting and getting mode
rampMode := tmc5160.NewRAMPMODE(comm, driverAddress)
err := rampMode.SetMode(tmc5160.PositioningMode)
if err != nil {
return
}
mode, err := rampMode.GetMode()
if err != nil {
println("Error getting mode:", err)
} else {
println("Current Mode:", mode)
}
// Read GCONF register
GCONF := tmc5160.NewGCONF()
gconfVal, err := driver.ReadRegister(tmc5160.GCONF)
// Uppack the register to get all the bits and bytes of the register
GCONF.Unpack(gconfVal)
//E.g. MultiStepFlit is retrieved from the GCONF register
println("GCONF:MultiStepFlit:", GCONF.MultistepFilt)
}
+69
View File
@@ -0,0 +1,69 @@
// Capacitive touch sensing example.
//
// This capacitive touch sensor works by charging a normal GPIO pin, then slowly
// discharging it through a 1MΩ resistor and seeing how long it takes to go from
// high to low.
//
// Use as follows:
// - Change touchPin below as needed.
// - Connect this pin to some metal surface, like a piece of aluminimum foil.
// Make sure this surface is covered (using paper, Scotch tape, etc).
// - Also connect this same pin to ground through a 1MΩ resistor.
//
// This sensor is very sensitive to noise on the power source, so you should
// probably try to limit it by running from a battery for example. Especially
// phone chargers can produce a lot of noise.
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/touch/capacitive"
)
const touchPin = machine.GP16 // Raspberry Pi Pico
func main() {
time.Sleep(time.Second * 2)
println("start")
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led.Low()
// Configure the array of GPIO pins used for capacitive touch sensing.
// We're using only one pin.
array := capacitive.NewArray([]machine.Pin{touchPin})
// Use a dynamic threshold, meaning the GPIO pin is automatically calibrated
// and re-calibrated to adjust for varying environments (e.g. changing
// humidity).
array.SetDynamicThreshold(100)
wasTouching := false
for i := uint32(0); ; i++ {
// Update the GPIO pin. This must be called very often.
array.Update()
touching := array.Touching(0)
// Indicate whether the pin is touched via the LED.
led.Set(touching)
// Print something when the touch state changed.
if wasTouching != touching {
wasTouching = touching
if touching {
println(" touch!")
} else {
println(" release!")
}
}
// Print the current value, as a debugging aid. It's not really meant to
// be used directly.
if i%128 == 32 {
println("touch value:", array.RawValue(0))
}
}
}
+40 -12
View File
@@ -3,7 +3,9 @@ package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/uc8151"
)
@@ -14,30 +16,56 @@ func main() {
led = machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 12000000,
Frequency: 12 * machine.MHz,
SCK: machine.EPD_SCK_PIN,
SDO: machine.EPD_SDO_PIN,
})
display = uc8151.New(machine.SPI0, machine.EPD_CS_PIN, machine.EPD_DC_PIN, machine.EPD_RESET_PIN, machine.EPD_BUSY_PIN)
display.Configure(uc8151.Config{
Rotation: uc8151.ROTATION_270,
Speed: uc8151.MEDIUM,
Blocking: true,
Rotation: drivers.Rotation270,
Speed: uc8151.TURBO,
FlickerFree: true,
Blocking: false,
})
black := color.RGBA{1, 1, 1, 255}
display.ClearBuffer()
display.Display()
for i := int16(0); i < 37; i++ {
for j := int16(0); j < 16; j++ {
if (i+j)%2 == 0 {
showRect(i*8, j*8, 8, 8, black)
display.ClearDisplay()
mod := int16(1)
for {
// checkerboard
for i := int16(0); i < 11; i++ {
if mod == 1 {
mod = 0
} else {
mod = 1
}
display.ClearBuffer()
for i := int16(0); i < 37; i++ {
for j := int16(0); j < 16; j++ {
if (i+j)%2 == mod {
showRect(i*8, j*8, 8, 8, black)
}
}
}
display.Display()
time.Sleep(500 * time.Millisecond)
}
// moving line
for i := int16(16); i < 21; i++ {
display.ClearBuffer()
for j := int16(0); j < 16; j++ {
if (i+j)%2 == 0 {
showRect(i*8, j*8, 8, 8, black)
}
display.Display()
time.Sleep(250 * time.Millisecond)
}
}
}
display.Display()
}
func showRect(x int16, y int16, w int16, h int16, c color.RGBA) {
+54
View File
@@ -0,0 +1,54 @@
package main
import (
"machine"
"image/color"
"math/rand"
"tinygo.org/x/drivers/unoqmatrix"
)
var on = color.RGBA{255, 255, 255, 255}
func main() {
display := unoqmatrix.NewFromBasePin(machine.PF0)
display.ClearDisplay()
w, h := display.Size()
x := int16(0)
y := int16(0)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
if pixel.R != 0 || pixel.G != 0 || pixel.B != 0 {
display.ClearDisplay()
x = 1 + int16(rand.Int31n(3))
y = 1 + int16(rand.Int31n(3))
deltaX = 1
deltaY = 1
if rand.Int31n(2) == 0 {
deltaX = -1
}
if rand.Int31n(2) == 0 {
deltaY = -1
}
}
display.SetPixel(x, y, on)
x += deltaX
y += deltaY
if x == 0 || x == w-1 {
deltaX = -deltaX
}
if y == 0 || y == h-1 {
deltaY = -deltaY
}
display.Display()
}
}
+37
View File
@@ -0,0 +1,37 @@
package main
import (
"machine"
"net"
"net/netip"
"time"
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/w5500"
)
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 33 * machine.MHz,
})
machine.GPIO17.Configure(machine.PinConfig{Mode: machine.PinOutput})
eth := w5500.New(machine.SPI0, machine.GPIO17)
eth.Configure(w5500.Config{
MAC: net.HardwareAddr{0xee, 0xbe, 0xe9, 0xa9, 0xb6, 0x4f},
IP: netip.AddrFrom4([4]byte{192, 168, 1, 2}),
SubnetMask: netip.AddrFrom4([4]byte{255, 255, 255, 0}),
Gateway: netip.AddrFrom4([4]byte{192, 168, 1, 1}),
})
netdev.UseNetdev(eth)
for {
if eth.LinkStatus() != w5500.LinkStatusUp {
println("Waiting for link to be up")
time.Sleep(1 * time.Second)
continue
}
break
}
}
+36
View File
@@ -0,0 +1,36 @@
package main
import (
"image/color"
"machine"
"tinygo.org/x/drivers/waveshare-epd/epd1in54"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/gophers"
)
var (
spi0 = machine.SPI0
cs = machine.D10
dc = machine.D9
rst = machine.D6
busy = machine.D5
black = color.RGBA{R: 1, G: 1, B: 1, A: 255}
)
func main() {
display := epd1in54.New(spi0, cs, dc, rst, busy)
display.LDirInit(epd1in54.Config{})
display.Clear()
display.ClearBuffer()
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 150, 0, "A B C", black, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 100, 0, "D E F", black, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 50, 0, "G H I", black, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 0, 0, "J K L", black, tinyfont.ROTATION_90)
display.Display()
display.Sleep()
}
+84
View File
@@ -0,0 +1,84 @@
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/waveshare-epd/epd2in66b"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)
var (
black = color.RGBA{0, 0, 0, 0xff}
white = color.RGBA{0xff, 0xff, 0xff, 0xff}
red = color.RGBA{0xff, 0, 0, 0xff}
)
func main() {
machine.Serial.Configure(machine.UARTConfig{})
time.Sleep(2 * time.Second)
machine.SPI1.Configure(machine.SPIConfig{
Frequency: epd2in66b.Baudrate,
})
println("started")
// in case you have a Pico module, you can directly use
// dev, err := epd2in66b.NewPicoModule()
display := epd2in66b.New(machine.SPI1)
cfg := epd2in66b.Config{
DataPin: machine.GP8,
ChipSelectPin: machine.GP9,
ResetPin: machine.GP12,
BusyPin: machine.GP13,
}
err := display.Configure(cfg)
if err != nil {
panic(err)
}
err = display.Reset()
if err != nil {
panic(err)
}
println("draw checkerboard")
drawCheckerBoard(&display)
println("draw 'hello'")
tinyfont.WriteLineRotated(&display, &freemono.Bold24pt7b, 40, 10, "Hello!", white, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &freemono.Bold12pt7b, 10, 10, "tinygo rocks", white, tinyfont.ROTATION_90)
err = display.Display()
if err != nil {
panic(err)
}
}
func drawCheckerBoard(display drivers.Displayer) {
s := 8
width, height := display.Size()
for x := 0; x <= int(width)-s; x += s {
for y := 0; y <= int(height)-s; y += s {
c := red
if (x/s)%2 == (y/s)%2 {
c = black
}
showRect(display, x, y, s, s, c)
}
}
}
func showRect(display drivers.Displayer, x int, y int, w int, h int, c color.RGBA) {
for i := x; i < x+w; i++ {
for j := y; j < y+h; j++ {
display.SetPixel(int16(i), int16(j), c)
}
}
}
+137
View File
@@ -0,0 +1,137 @@
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/waveshare-epd/epd2in9v2"
)
var display epd2in9v2.Device
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 12000000,
SCK: machine.EPD_SCK_PIN,
SDO: machine.EPD_SDO_PIN,
})
display = epd2in9v2.New(
machine.SPI0,
machine.EPD_CS_PIN,
machine.EPD_DC_PIN,
machine.EPD_RESET_PIN,
machine.EPD_BUSY_PIN,
)
display.Configure(epd2in9v2.Config{
Rotation: epd2in9v2.ROTATION_270,
Speed: epd2in9v2.SPEED_DEFAULT,
Blocking: true,
})
black := color.RGBA{0, 0, 0, 255}
white := color.RGBA{255, 255, 255, 255}
// --- Step 1: clear to white ---
println("epd2in9v2: clearing display")
display.ClearBuffer()
display.Display()
time.Sleep(2 * time.Second)
// --- Step 2: full refresh checkerboard ---
println("epd2in9v2: drawing checkerboard (full refresh)")
w, h := display.Size()
for i := int16(0); i < w/8; i++ {
for j := int16(0); j < h/8; j++ {
if (i+j)%2 == 0 {
fillRect(i*8, j*8, 8, 8, black)
}
}
}
display.Display()
time.Sleep(2 * time.Second)
// --- Step 3: fast refresh - draw border and diagonal cross ---
println("epd2in9v2: switching to fast refresh")
display.SetSpeed(epd2in9v2.SPEED_FAST)
display.ClearBuffer()
for x := int16(0); x < w; x++ {
display.SetPixel(x, 0, black)
display.SetPixel(x, h-1, black)
}
for y := int16(0); y < h; y++ {
display.SetPixel(0, y, black)
display.SetPixel(w-1, y, black)
}
for i := int16(0); i < w && i < h; i++ {
display.SetPixel(i, i*h/w, black)
display.SetPixel(w-1-i, i*h/w, black)
}
display.Display()
time.Sleep(2 * time.Second)
// --- Step 4: partial refresh counter ---
println("epd2in9v2: partial refresh demo")
display.SetSpeed(epd2in9v2.SPEED_DEFAULT)
display.ClearBuffer()
println("epd2in9v2: setting base image")
display.DisplayWithBase()
for count := 0; count < 10; count++ {
cx := int16(120)
cy := int16(50)
fillRect(cx, cy, 60, 20, white)
digit := int16(count % 10)
drawDigit(cx+22, cy+2, digit, black)
display.DisplayPartial()
time.Sleep(500 * time.Millisecond)
}
time.Sleep(2 * time.Second)
// --- Step 5: sleep ---
println("epd2in9v2: entering deep sleep")
display.ClearBuffer()
display.Display()
display.Sleep()
println("epd2in9v2: done, you can remove power")
}
func fillRect(x, y, w, h int16, c color.RGBA) {
for i := x; i < x+w; i++ {
for j := y; j < y+h; j++ {
display.SetPixel(i, j, c)
}
}
}
// drawDigit draws a simple 3x5-pixel-block digit (each block 4x3 px) at position (x,y).
func drawDigit(x, y, digit int16, c color.RGBA) {
segments := [10][5]uint8{
{0x7, 0x5, 0x5, 0x5, 0x7}, // 0
{0x2, 0x2, 0x2, 0x2, 0x2}, // 1
{0x7, 0x1, 0x7, 0x4, 0x7}, // 2
{0x7, 0x1, 0x7, 0x1, 0x7}, // 3
{0x5, 0x5, 0x7, 0x1, 0x1}, // 4
{0x7, 0x4, 0x7, 0x1, 0x7}, // 5
{0x7, 0x4, 0x7, 0x5, 0x7}, // 6
{0x7, 0x1, 0x1, 0x1, 0x1}, // 7
{0x7, 0x5, 0x7, 0x5, 0x7}, // 8
{0x7, 0x5, 0x7, 0x1, 0x7}, // 9
}
if digit < 0 || digit > 9 {
return
}
for row := int16(0); row < 5; row++ {
for col := int16(0); col < 3; col++ {
if segments[digit][row]&(0x4>>uint(col)) != 0 {
fillRect(x+col*4, y+row*3, 4, 3, c)
}
}
}
}
+6 -5
View File
@@ -1,10 +1,11 @@
//go:build arduino
//go:build arduino || arduino_uno
package main
import "machine"
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo = machine.D2
var led = machine.LED
func init() {
// Replace neo in the code below to match the pin
// that you are using if different.
neo = machine.D2
}
+6 -5
View File
@@ -4,8 +4,9 @@ package main
import "machine"
// This is the pin assignment for the Digispark only.
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = 0
var led = machine.LED
func init() {
// This is the pin assignment for the Digispark only.
// Replace neo and led in the code below to match the pin
// that you are using if different.
neo = machine.Pin(0)
}
+4 -4
View File
@@ -12,11 +12,12 @@ import (
"tinygo.org/x/drivers/ws2812"
)
var leds [10]color.RGBA
var (
neo machine.Pin
leds [10]color.RGBA
)
func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
ws := ws2812.NewWS2812(neo)
@@ -35,7 +36,6 @@ func main() {
}
ws.WriteColors(leds[:])
led.Set(rg)
time.Sleep(100 * time.Millisecond)
}
}
+6 -5
View File
@@ -1,10 +1,11 @@
//go:build !digispark && !arduino && !qtpy && !m5stamp_c3 && !thingplus_rp2040
//go:build !digispark && !arduino && !arduino_uno
package main
import "machine"
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.WS2812
var led = machine.LED
func init() {
// Replace neo in the code below to match the pin
// that you are using if different.
neo = machine.WS2812
}
-10
View File
@@ -1,10 +0,0 @@
//go:build qtpy || m5stamp_c3
package main
import "machine"
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.WS2812
var led = machine.NoPin
-12
View File
@@ -1,12 +0,0 @@
//go:build thingplus_rp2040
package main
import "machine"
// This is the pin assignment for the internal neopixel of the
// Sparkfun thingplus rp2040.
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.GPIO8
var led = machine.LED
+41
View File
@@ -46,6 +46,10 @@ var DefaultDeviceIdentifier = DeviceIdentifierFunc(func(id JedecID) Attrs {
return GD25Q16C()
case 0xC84017:
return GD25Q64C()
case 0x856015:
return P25Q16H()
case 0xEF4014:
return W25Q80DV()
case 0xEF4015:
return W25Q16JVIQ()
case 0xEF4016:
@@ -239,6 +243,24 @@ func GD25Q64C() Attrs {
}
}
// Settings for the Puya P25Q16H 2MiB SPI flash.
// Datasheet: https://files.seeedstudio.com/wiki/github_weiruanexample/Flash_P25Q16H-UXH-IR_Datasheet.pdf
func P25Q16H() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0x85, 0x60, 0x15},
MaxClockSpeedMHz: 55,
QuadEnableBitMask: 0x02,
HasSectorProtection: true,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: true,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q16JV-IQ 2MiB SPI flash. Note that JV-IM has a
// different .memory_type (0x70) Datasheet:
// https://www.winbond.com/resource-files/w25q16jv%20spi%20revf%2005092017.pdf
@@ -380,6 +402,25 @@ func W25Q80DL() Attrs {
TotalSize: 1 << 20, // 1 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x60, 0x14},
MaxClockSpeedMHz: 80,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: false,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q80DV 2MiB SPI flash.
// Datasheet:
// https://www.winbond.com/resource-files/w25q80dv%20dl_revh_10022015.pdf
func W25Q80DV() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x40, 0x14},
MaxClockSpeedMHz: 104,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
+5 -1
View File
@@ -1,3 +1,6 @@
// Guarded because still unsure of how to deal with interrupt drivers.
//go:build tinygo
// Package ft6336 provides a driver for the FT6336 I2C Self-Capacitive touch
// panel controller.
//
@@ -80,7 +83,8 @@ func (d *Device) Read() []byte {
func (d *Device) ReadTouchPoint() touch.Point {
d.Read()
z := 0xFFFFF
if d.buf[0] == 0 {
switch d.buf[0] {
case 0, 255:
z = 0
}
+8 -4
View File
@@ -1,20 +1,24 @@
module tinygo.org/x/drivers
go 1.18
go 1.22.1
toolchain go1.23.1
require (
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/frankban/quicktest v1.10.2
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/orsinium-labs/tinymath v1.1.0
github.com/soypat/natiu-mqtt v0.5.1
golang.org/x/net v0.7.0
github.com/tinygo-org/pio v0.3.0
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
golang.org/x/net v0.33.0
tinygo.org/x/tinyfont v0.3.0
tinygo.org/x/tinyterm v0.1.0
)
require (
github.com/google/go-cmp v0.5.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.1.0 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
)

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