Compare commits

...

73 Commits

Author SHA1 Message Date
deadprogram 27ef18930e Prepare for drivers release 0.15.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-03-06 13:41:58 +01:00
deadprogram e9a6d96ddd docs: update count of supported drivers to add latest contribution
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-03-05 09:26:56 +01:00
pleomaxx3002 9a7cb1a22f DHTXX driver (#235)
dhtXX: add new driver for dht thermometer
2021-03-05 09:24:45 +01:00
deadprogram d170ec8d81 docs: add missing new drivers added since last release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-03-05 00:36:50 +01:00
deadprogram 0fc2d28ca8 docs: update year in license to 2021
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-03-05 00:09:22 +01:00
Nick Craig-Wood ef34c13cc1 hd44780: add a mode to work with boards where the RW pin is grounded
On some HD44780 boards (eg the Keyestudio LCD1602 expansion shield),
the RW pin isn't brought out and is permanently grounded.

This means that the board can't be read from, and in particular the
busy status can't be read.

This patch adapts the package to work with boards like these.

To signal this to the package, set the RW pin to machine.NoPin.

The package will then disallow all reading and use adjustable timing
based writing. The timing can be adjusted the configuration.
2021-02-23 12:28:38 +01:00
deadprogram c64d7920dc adc: update drivers with ADC to use new config struct
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-02-01 14:35:51 +01:00
deadprogram 008157b6c9 st7789: correct errors on various godoc comments
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-31 16:57:21 +01:00
deadprogram 0ed9683a52 st7789: add scrolling functions to match st7735
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-31 16:57:21 +01:00
deadprogram 01acd977f3 microbitmatrix: refactor to eliminate duplicate code with microbit v1/v2
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-28 10:09:01 +01:00
deadprogram 231ec57202 microbitmatrix: matrix now working on microbit v2
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-28 10:09:01 +01:00
Ayke van Laethem 5d3ad4ba52 all: fix main package in examples
The package with the main function should always have the name main.
This was not the case in three packages.

This was silently allowed before, but since a TinyGo change
(https://github.com/tinygo-org/tinygo/pull/1592) this now results in a
linker failure.

Perhaps this should result in a better error message in TinyGo. However,
the example code also needs to be fixed, so hence this PR.
2021-01-27 23:50:58 +01:00
Roger Peppe 8cb226938b mcp23017: use new tester package
Also change the `tester` package slightly to use an exposed `Registers`
array rather adding yet another accessor method to retrieve a register
value. This seems to me more transparent and "obvious" - we aren't trying
to hide the fact that there's just a simple memory store there.
Also unexport the `assertRegisterRange` method which was never intended
to be part of the public API.
2021-01-20 12:56:22 +01:00
Roger Peppe ce5e443084 mcp23017: implement pin toggling
Also add an example for using multiple devices.
2021-01-20 12:56:22 +01:00
Roger Peppe edf9ba92be mcp23017: new driver for MCP23017 (I2C port expander) 2021-01-20 12:56:22 +01:00
Austen LeBeau c1c05cbef7 Add bmp388 support (#219)
bmp388: add support
2021-01-08 11:32:49 +01:00
Daniel Esteban c338348d2b Better interface "ReadTime" instead of "Time" for DS1307 2020-12-22 08:16:13 +01:00
deadprogram b6aa674b2a test: run unit tests against i2c drivers and any spi drivers without direct gpio
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-15 06:08:59 +01:00
deadprogram cc7079b0cd drivers/flash: restore previous calls directly to machine package until we implement SetClockSpeed()
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-15 06:08:59 +01:00
deadprogram 42a907035b spi: incorporate change from GH issue feedback
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-15 06:08:59 +01:00
deadprogram 46c9ba9595 spi: remove machine.SPI and replace with drivers.SPI interface for almost all SPI drivers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-15 06:08:59 +01:00
Ayke van Laethem df64ce8f50 ws2812: make AVR support more robust
* Merge AVR support for Digispark and non-Digispark
* Fix the error "inline assembly requires more registers than available"
* Use a single file for all AVR targets, in a similar style as the
  Xtensa support.
2020-10-16 18:36:32 +02:00
deadprogram e376785596 Prepare for Drivers release 0.14.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-09-17 10:09:25 +02:00
Amblyopius 4c98d0bc34 - Fix for RAMWR bug:
It can not be skipped as you may call setwindow even if device is not ready for data write to buffer.
Data can only be written if last command was Memory Write or Write Memory Continue which was not guaranteed
There's no performance loss as having the exact same window on a consecutive call is extremely unlikely
- Fixed some typos and added a few missing comments to please go-lint
2020-09-11 11:31:07 +02:00
Ayke van Laethem 65f8299153 ws2812: add support for ESP8266
This patch adds support for the ESP8266 chip by adding support for 80MHz
operation. This should also work when the ESP32 is changed to 80MHz, but
I didn't test it (as there is not currently a way to do that).

Verified on a NodeMCU dev board with an ESP8266.
2020-09-09 22:43:35 +02:00
BCG a689aef543 Renamed NewSpi() to NewSPI() in accordance with Go naming conventions 2020-09-07 09:11:25 +02:00
Ayke van Laethem 7cd7df7bb8 ws2812: add support for the Xtensa architecture
This allows WS2812 LEDs to be controlled by an ESP32. Right now it only
supports 160MHz operation, which is the default with current ESP32
support in TinyGo.
2020-09-05 14:01:22 +02:00
deadprogram 705f474897 adt7410: add connection test and for that matter connection method
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-09-01 13:08:28 +02:00
Jason Striegel ce9c93f228 Add support for setting framerate, vsync pause, and reading scanline data.
- FrameRate option sets device framerates from 39-111Hz
- VSyncLines option adjusts device vsync pause using st7789 "porch control" feature
- Added Rx(cmd, bytes[]) to support retrieving scanline timing data over SPI
- Added Sync functions to support syncronization of animation to vertical scanline timing
- Minor adjustments to Configure to clear screen memory before display is visible
2020-09-01 11:26:06 +02:00
deadprogram db02cbb8a4 i2c: switch all i2c drivers definitions to use i2c bus interface type instead of machine package concrete type
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-29 17:19:27 +02:00
deadprogram a07b20f2f9 tester: correct interface definition for I2C Tx function
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-27 12:17:47 +02:00
deadprogram 4ad210060f lis2mdl: better examples showing how to create unit tests
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-27 12:15:57 +02:00
deadprogram 2ae950e96d tester: improve API surface and implement one more test function in lis2mdl driver
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-27 12:15:57 +02:00
deadprogram dd34b83e9f lis2mdl: starting point for adding unit tests
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-27 12:15:57 +02:00
deadprogram ce81b66fe2 tester: add very basic mock structs for testing i2c devices, based on work done by @rogpeppe
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-27 12:15:57 +02:00
Tim Boldt b1ae52d1b9 Add Waveshare 4.2in B/W e-paper driver (#183)
* waveshare-epd/epd4in2: Add Waveshare 4.2in e-paper driver
2020-08-25 18:57:13 +02:00
sago35 794a9c202f Fix smoke-test unless avr-gcc installed 2020-08-24 13:49:20 +02:00
deadprogram 38f97a8b45 docs: replace README badge for godocs with pkgdocs
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-21 16:17:38 +02:00
deadprogram 35a146d60e lis2mdl: turn on read mode on every read, to ensure that magnetometer data is updated
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-21 10:12:16 +02:00
deadprogram 4f82c06df9 gps: add speed and heading to fix, as parsed from RMC NMEA sentence
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-21 07:24:41 +02:00
Ron Evans af4efceac1 lis2mdl: add LIS2MDL magnetometer (#187)
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-20 13:21:39 +02:00
Ron Evans 5df96c8138 gps: improvements and bugfixes (#186)
* gps: buxfixes and refactoring of API to separate device from parser

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

* gps: simplify time parser

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

* gps: add support for RMC sentences

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

* gps: small renaming to remove reduntant use of word GPS

Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-20 11:22:07 +02:00
Jason Striegel 88aeec9f69 Fix setWindow bug, add CS pin for Clue compatibility. (#180)
* Fix setWindow bug, add CS pin for Clue compatibility.

- corrected bit shift in setWindow that broke high addresses
- added csPin to constructor (will now work with adafruit clue)
- small adjustments and comments to init routine based on working arduino driver

* Update main.go

Updated test example with CS pin.

* Reverting unnecessary (no-effect) change in setRotation.
2020-08-12 08:31:43 +02:00
deadprogram 06b2023b25 Prepare for release v0.13.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-04 16:44:20 +02:00
ardnew 8163dec7c3 ili9341: cache address window to prevent sending unnecessary commands (#171) 2020-08-03 15:58:16 +02:00
sago35 5a75c9c403 Add ILI9341 TFT driver (SPI) for ATSAMD2x (#174)
* ILI9341: Add ILI9341 SPI support for for ATSAMD2x
2020-08-03 12:42:17 +02:00
deadprogram 75b8a75b4b docs: reorder to correct alpha and adjust count of supported drivers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-03 12:06:17 +02:00
deadprogram 857e45f18d modules: update go version and dependency
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-03 11:59:35 +02:00
Ayke van Laethem 9130e61c55 bmi160: add initial support 2020-07-31 19:31:52 +02:00
Alan Wang 68963a1b42 Add Hd44780i2c driver (#173)
* hd44780i2c: add support for hd44780i2c LCD display.
2020-07-31 19:25:56 +02:00
deadprogram 74c9ff4c76 all: changeover to eliminate all direct use of master/slave terminology
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-07-30 12:03:09 +02:00
Alan Wang de27fae9a2 Update ws2812_avr_16m.go 2020-07-16 15:50:07 +02:00
Alan Wang 5ebc7cb09e bmp180: fix temperature type conversion 2020-07-07 23:47:38 +02:00
Yannis Huber 07ca36ac5a tmp102: add Connected func to check for device 2020-07-01 19:25:15 +02:00
Alan Wang 39f44ef478 lsm303agr (#162)
* lsm303agr: add support for lsm303agr digital compass
2020-07-01 16:59:25 +02:00
Daniel Esteban 61874ea928 added custom import path (#161) 2020-06-25 17:11:49 +02:00
BCG f5e81e6a01 Added smoke tests for UDP functionality 2020-06-21 16:21:06 +02:00
BCG 511a3282b7 Added UDP support 2020-06-21 16:21:06 +02:00
Nerdmeister 1d09194bbc Added support for the Bosch BMP280 temperature and pressure sensor. (#158)
* bmp280: Added support for the Bosch BMP280 temperature and pressure sensor.
2020-06-21 16:16:29 +02:00
sago35 941ea4e28b ILI9341 TFT driver (SPI) (#153)
* ili9341: Add spidriver
2020-06-15 18:46:13 +02:00
Ayke van Laethem 21ba9392e2 ci: support Go modules
Since TinyGo started supporting Go modules, there was an error in CI.
The commit 7967e82fed tries to fix that,
but I think the underlying issue is that we're checking out in GOROOT,
which is definitely not a supported configuration.

I think the best solution is to just switch to using Go modules, by
adding a go.mod file in the root. I've set it to Go version 1.13 as that
is the first Go version that supports number literals, but it could be
set to any supported Go version (1.11-1.14).

Since we use Go modules, the location of the drivers checkout should not
matter so I've removed it. This fixes the error on CircleCI.
2020-06-05 08:18:43 +02:00
deadprogram 7967e82fed build: try vendor in working directory to match expected module path
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-05-28 19:13:46 +02:00
Ayke van Laethem 91539d9ef8 apa102: avoid creating garbage
Avoid creating unnecessary garbage in the following ways:

  * Use the Transfer method instead of the Tx method for single byte
    transfers.
  * Use a statically allocated buffer for the fixed start-of-frame
    sequence.

Running this for a few minutes did not cause the garbage collector to
run. Previously, it would run every second or so in a
persistence-of-vision application.
2020-05-26 19:50:31 +02:00
Alexander Bloss c3f3af5ffa Fix DrawFastHLine for ST77xx, SSD1331 and SSD1351
DrawFastHLine uses FillRectangle(x,y,width,height,c), so height must be 1 to draw a horizontal line
2020-05-16 18:54:41 +02:00
sago35 41694085a1 Improve performance of ILI9341 on ATSAMD5X 2020-05-06 13:25:43 +02:00
Yannis Huber fd9b1ba89b Add SSD1351 OLED display driver (#146)
* ssd1351: Add SSD1351 driver
2020-04-24 07:30:50 +02:00
Ron Evans 9f23761c5e Updates for v0.12.0 release
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-04-13 21:21:45 +02:00
Ron Evans 2ea620026b docs: rearrange list of drivers to be in alpha order
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-04-12 16:34:44 +02:00
Daniel Esteban 04be2320b7 Added HC-SR04 ultrasonic distance sensor. (#143)
* Added HC-SR04 ultrasonic distance sensor.
2020-04-12 16:29:46 +02:00
BCG b1529dcf7a Low-level IO driver for serial flash memory via SPI and QSPI (#124)
* QSPI/SPI: flash memory functions
2020-04-11 17:59:58 +02:00
Ron Evans 1987f424ad mqtt: reduce use of goroutines in router to not start a new goroutine for each invocation of each callback
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-04-11 16:58:37 +02:00
Yannis Huber 6f213e97c3 Add driver for TMP102 low-power digital temperature sensor (#141)
* tmp102: add driver and example
2020-04-03 13:11:46 +02:00
Daniel Esteban ebceed6014 AMG88xx thermal camera module 2020-03-17 12:11:03 +01:00
149 changed files with 9283 additions and 707 deletions
+3 -1
View File
@@ -6,13 +6,15 @@ jobs:
build:
docker:
- image: tinygo/tinygo-dev
working_directory: /usr/local/go/src/tinygo.org/x/drivers
steps:
- checkout
- run: tinygo version
- run:
name: "Enforce Go Formatted Code"
command: make fmt-check
- run:
name: "Run unit tests"
command: make unit-test
- run:
name: "Run build and smoke tests"
command: make smoke-test
+95
View File
@@ -1,3 +1,98 @@
0.15.0
---
- **new devices**
- dht: add DHTXX thermometer
- mcp23017: new driver for MCP23017 (I2C port expander)
- bmp388: Add bmp388 support (#219)
- **enhancements**
- hd44780: add a mode to work with boards where the RW pin is grounded
- st7789: add scrolling functions to match st7735
- microbitmatrix: matrix now working on microbit v2
- ds1307: Better interface "ReadTime" instead of "Time"
- ws2812: make AVR support more robust
- **bugfixes**
- all: fix main package in examples
- **core**
- adc: update all drivers with ADC to use new config struct
- spi: remove machine.SPI and replace with drivers.SPI interface for almost all SPI drivers
- **testing**
- test: run unit tests against i2c drivers and any spi drivers without direct gpio
- **docs**
- st7789: correct errors on various godoc comments
0.14.0
---
- **new devices**
- lis2mdl: add LIS2MDL magnetometer (#187)
- waveshare: add Waveshare 4.2in B/W e-paper driver (#183)
- **enhancements**
- adt7410: add connection test and for that matter connection method
- gps
- add speed and heading to fix, as parsed from RMC NMEA sentence
- improvements and bugfixes (#186)
- ili9341
- add support for setting framerate, vsync pause, and reading scanline data.
- renamed NewSpi() to NewSPI() in accordance with Go naming conventions
- ws2812
- add support for ESP8266
- add support for ESP32
- **bugfixes**
- ili9341
- rix setWindow bug, add CS pin for Clue compatibility. (#180)
- bugfix for RAMWR bug
- lis2mdl: turn on read mode on every read, to ensure that magnetometer data is updated
- **core**
- i2c
- switch all i2c drivers definitions to use i2c bus interface type instead of machine package concrete type
- correct interface definition for I2C Tx function
- **testing**
- fix smoke-test unless avr-gcc installed
- add very basic mock structs for testing i2c devices, based on work done by @rogpeppe
- improve API surface and implement one more test function in lis2mdl driver
- **docs**
- replace README badge for godocs with pkgdocs
0.13.0
---
- **new devices**
- bmi160: add initial support
- bmp280: added support for the Bosch BMP280 temperature and pressure sensor. (#158)
- lsm303agr: add lsm303agr (#162)
- ssd1351: add SSD1351 OLED display driver (#146)
- **enhancements**
- hd44780: add Hd44780i2c driver (#173)
- ili9341
- add ILI9341 TFT driver (SPI) for ATSAMD2x (#174)
- cache address window to prevent sending unnecessary commands (#171)
- ILI9341 TFT driver (SPI) (#153)
- improve performance of ILI9341 on ATSAMD5X
- ST77xx: fix DrawFastHLine for ST77xx, SSD1331 and SSD1351 DrawFastHLine uses FillRectangle(x,y,width,height,c), so height must be 1 to draw a horizontal line
- tmp102: add Connected func to check for device
- wifinina: added UDP support
- ws2812: update ws2812_avr_16m.go
- **bugfixes**
- apa102: avoid creating garbage
- bmp180: fix temperature type conversion
- **core**
- all
- added custom import path (#161)
- changeover to eliminate all direct use of master/slave terminology
- build: try vendor in working directory to match expected module path
- ci: support Go modules
- modules: update go version and dependency
- **docs**
- docs: reorder to correct alpha and adjust count of supported drivers
0.12.0
---
- **new devices**
- hcsr04: Added HC-SR04 ultrasonic distance sensor. (#143)
- spi/qspi: Low-level IO driver for serial flash memory via SPI and QSPI (#124)
- tmp102: TMP102 low-power digital temperature sensor (#141)
- amg88xx: AMG88xx thermal camera module
- **bugfixes**
- mqtt: reduce use of goroutines in router to not start a new goroutine for each invocation of each callback
0.11.0
---
- **new devices**
+1 -1
View File
@@ -20,7 +20,7 @@ Please first open a Github issue. We want to help, and also make sure that there
## How to use our Github repository
The `master` branch of this repo will always have the latest released version of the TinyGo drivers. All of the active development work for the next release will take place in the `dev` branch. The TinyGo drivers will use semantic versioning and will create a tag/release for each release.
The `release` branch of this repo will always have the latest released version of the TinyGo drivers. All of the active development work for the next release will take place in the `dev` branch. The TinyGo drivers will use semantic versioning and will create a tag/release for each release.
Here is how to contribute back some code or documentation:
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2018-2020 The TinyGo Authors. All rights reserved.
Copyright (c) 2018-2021 The TinyGo Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
+56 -3
View File
@@ -13,6 +13,8 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adxl345/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/amg88xx
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/itsybitsy-m0/main.go
@@ -23,8 +25,14 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/blinkm/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmi160/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp180/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp280/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bmp388/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/sram/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/time/main.go
@@ -39,26 +47,46 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/espstation/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/flash/console/spi
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/flash/console/qspi
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/i2c/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/uart/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/hcsr04/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hd44780/customchar/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hd44780/text/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/hd44780i2c/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hub75/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/basic/main.go
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/basic
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/scroll/main.go
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/ili9341/basic
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/pyportal_boing
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/scroll
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/ili9341/scroll
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/lsm303agr/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/lsm6ds3/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mag3110/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017-multiple/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp3008/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/microbitmatrix/main.go
@@ -97,14 +125,24 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd4in2/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/ntpclient/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/udpstation/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/tcpclient/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/webclient/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
@md5sum ./build/test.hex
ifneq ($(AVR), 0)
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
@md5sum ./build/test.hex
endif
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/microphone/main.go
@@ -123,5 +161,20 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=nucleo-f103rb ./examples/shiftregister/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=hifive1b ./examples/ssd1351/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go
@md5sum ./build/test.hex
test: clean fmt-check smoke-test
DRIVERS = $(wildcard */)
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
unit-test:
@go test -v $(addprefix ./,$(TESTS))
test: clean fmt-check unit-test smoke-test
+19 -7
View File
@@ -1,9 +1,9 @@
# TinyGo Drivers
[![GoDoc](https://godoc.org/tinygo.org/x/drivers?status.svg)](https://godoc.org/tinygo.org/x/drivers) [![CircleCI](https://circleci.com/gh/tinygo-org/drivers/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/drivers/tree/dev)
[![PkgGoDev](https://pkg.go.dev/badge/tinygo.org/x/drivers)](https://pkg.go.dev/tinygo.org/x/drivers) [![CircleCI](https://circleci.com/gh/tinygo-org/drivers/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/drivers/tree/dev)
This package provides a collection of hardware drivers for devices that can be used together with [TinyGo](https://tinygo.org).
This package provides a collection of hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
## Installing
@@ -52,35 +52,42 @@ func main() {
## Currently supported devices
The following 45 devices are supported.
The following 56 devices are supported.
| Device Name | Interface Type |
|----------|-------------|
| [ADT7410 I2C Temperature Sensor](https://www.analog.com/media/en/technical-documentation/data-sheets/ADT7410.pdf) | I2C |
| [ADXL345 accelerometer](http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf) | I2C |
| [AMG88xx 8x8 Thermal camera sensor](https://cdn-learn.adafruit.com/assets/assets/000/043/261/original/Grid-EYE_SPECIFICATIONS%28Reference%29.pdf) | I2C |
| [APA102 RGB LED](https://cdn-shop.adafruit.com/product-files/2343/APA102C.pdf) | SPI |
| [AT24CX 2-wire serial EEPROM](https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf) | I2C |
| [BBC micro:bit LED matrix](https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf) | GPIO |
| [BH1750 ambient light sensor](https://www.mouser.com/ds/2/348/bh1750fvi-e-186247.pdf) | I2C |
| [BlinkM RGB LED](http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf) | I2C |
| [BME280 humidity/pressure sensor](https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf) | I2C |
| [BMI160 accelerometer/gyroscope](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi160-ds000.pdf) | SPI |
| [BMP180 barometer](https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf) | I2C |
| [BMP280 temperature/barometer](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf) | I2C |
| [BMP388 pressure sensor](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf) | I2C |
| [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | GPIO |
| [DHTXX thermometer and humidity sensor](https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf) | GPIO |
| [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C |
| [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C |
| ["Easystepper" stepper motor controller](https://en.wikipedia.org/wiki/Stepper_motor) | GPIO |
| [ESP32 as WiFi Coprocessor with Arduino nina-fw](https://github.com/arduino/nina-fw) | SPI |
| [ESP8266/ESP32 AT Command set for WiFi/TCP/UDP](https://github.com/espressif/esp32-at) | UART |
| [GPS module](https://www.u-blox.com/en/product/neo-6-series) | I2C/UART |
| [HD44780 LCD controller](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) | GPIO |
| [HC-SR04 Ultrasonic distance sensor](https://cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf) | GPIO |
| [HD44780 LCD controller](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) | GPIO/I2C |
| [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI |
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
| [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM |
| [L9110x motor driver](https://www.elecrow.com/download/datasheet-l9110.pdf) | GPIO/PWM |
| [LIS2MDL magnetometer](https://www.st.com/resource/en/datasheet/lis2mdl.pdf) | I2C |
| [LIS3DH accelerometer](https://www.st.com/resource/en/datasheet/lis3dh.pdf) | I2C |
| [LSM6DS3 accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3.pdf) | I2C |
| [MAG3110 magnetometer](https://www.nxp.com/docs/en/data-sheet/MAG3110.pdf) | I2C |
| [MCP23017 port expander](https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf) | I2C |
| [MCP3008 analog to digital converter (ADC)](http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf) | SPI |
| [BBC micro:bit LED matrix](https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf) | GPIO |
| [Microphone - PDM](https://cdn-learn.adafruit.com/assets/assets/000/049/977/original/MP34DT01-M.pdf) | I2S/PDM |
| [MMA8653 accelerometer](https://www.nxp.com/docs/en/data-sheet/MMA8653FC.pdf) | I2C |
| [MPU6050 accelerometer/gyroscope](https://store.invensense.com/datasheets/invensense/MPU-6050_DataSheet_V3%204.pdf) | I2C |
@@ -90,15 +97,20 @@ The following 45 devices are supported.
| [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO |
| [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO |
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Humidity/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
| [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI |
| [SSD1306 OLED display](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) | I2C / SPI |
| [SSD1331 TFT color display](https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/) | SPI |
| [SSD1351 OLED display](https://download.mikroe.com/documents/datasheets/ssd1351-revision-1.3.pdf) | SPI |
| [ST7735 TFT color display](https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/) | SPI |
| [ST7789 TFT color display](https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf) | SPI |
| [Stepper motor "Easystepper" controller](https://en.wikipedia.org/wiki/Stepper_motor) | GPIO |
| [Thermistor](https://www.farnell.com/datasheets/33552.pdf) | ADC |
| [TMP102 I2C Temperature Sensor](https://download.mikroe.com/documents/datasheets/tmp102-data-sheet.pdf) | I2C |
| [VEML6070 UV light sensor](https://www.vishay.com/docs/84277/veml6070.pdf) | I2C |
| [VL53L1X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl53l1x.pdf) | I2C |
| [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI |
| [Waveshare 2.13" (B & C) e-paper display](https://www.waveshare.com/w/upload/d/d3/2.13inch-e-paper-b-Specification.pdf) | SPI |
| [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI |
| [Waveshare 4.2" e-paper B/W display](https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf) | SPI |
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
## Contributing
+30 -28
View File
@@ -1,8 +1,13 @@
package adt7410
// Package adt7410 provides a driver for the adt7410 I2C Temperature Sensor.
//
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADT7410.pdf
//
package adt7410 // import "tinygo.org/x/drivers/adt7410"
import (
"machine"
"time"
"tinygo.org/x/drivers"
)
type Error uint8
@@ -21,40 +26,37 @@ func (e Error) Error() string {
}
type Device struct {
bus *machine.I2C
buf []byte
addr uint8
bus drivers.I2C
buf []byte
Address uint8
}
// New returns ADT7410 device for the provided I2C bus and address. The ADT7410
// has a default address of 0x48 (1001000). The last 2 bits of the address
// New returns ADT7410 device for the provided I2C bus using default address.
// of 0x48 (1001000). To use multiple ADT7410 devices, the last 2 bits of the address
// can be set using by connecting to the A1 and A0 pins to VDD or GND (for a
// total of up to 4 devices on a I2C bus). Also note that 10k pullups are
// recommended for the SDA and SCL lines.
func New(i2c *machine.I2C, addressBits uint8) *Device {
func New(i2c drivers.I2C) *Device {
return &Device{
bus: i2c,
buf: make([]byte, 2),
addr: Address | (addressBits & 0x3),
bus: i2c,
buf: make([]byte, 2),
Address: Address,
}
}
func (dev *Device) Configure() (err error) {
// verify the chip ID
// TODO: According to datasheet, the check below should work; however
// this does not seem to be working right, but is not exactly
// necessary, so can revisit later to see if there is a bug
//id := dev.ReadByte(RegID) & 0xF8
//if id != 0xC8 {
// err = ErrInvalidID
//}
// Configure the ADT7410 device.
func (d *Device) Configure() (err error) {
// reset the chip
dev.writeByte(RegReset, 0xFF)
d.writeByte(RegReset, 0xFF)
time.Sleep(10 * time.Millisecond)
return
}
// Connected returns whether sensor has been found.
func (d *Device) Connected() bool {
data := []byte{0}
d.bus.ReadRegister(uint8(d.Address), RegID, data)
return data[0]&0xF8 == 0xC8
}
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
@@ -62,13 +64,13 @@ func (d *Device) ReadTemperature() (temperature int32, err error) {
return (int32(d.readUint16(RegTempValueMSB)) * 1000) / 128, nil
}
// ReadTempC returns the value in the temperature value register, in Celcius
// ReadTempC returns the value in the temperature value register, in Celsius.
func (d *Device) ReadTempC() float32 {
t := d.readUint16(RegTempValueMSB)
return float32(int(t)) / 128.0
}
// ReadTempF returns the value in the temperature value register, in Fahrenheit
// ReadTempF returns the value in the temperature value register, in Fahrenheit.
func (d *Device) ReadTempF() float32 {
return d.ReadTempC()*1.8 + 32.0
}
@@ -76,15 +78,15 @@ func (d *Device) ReadTempF() float32 {
func (d *Device) writeByte(reg uint8, data byte) {
d.buf[0] = reg
d.buf[1] = data
d.bus.Tx(uint16(d.addr), d.buf, nil)
d.bus.Tx(uint16(d.Address), d.buf, nil)
}
func (d *Device) readByte(reg uint8) byte {
d.bus.ReadRegister(d.addr, reg, d.buf)
d.bus.ReadRegister(d.Address, reg, d.buf)
return d.buf[0]
}
func (d *Device) readUint16(reg uint8) uint16 {
d.bus.ReadRegister(d.addr, reg, d.buf)
d.bus.ReadRegister(d.Address, reg, d.buf)
return uint16(d.buf[0])<<8 | uint16(d.buf[1])
}
+49
View File
@@ -0,0 +1,49 @@
package adt7410
import (
"testing"
qt "github.com/frankban/quicktest"
"tinygo.org/x/drivers/tester"
)
func TestDefaultI2CAddress(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
dev := New(bus)
c.Assert(dev.Address, qt.Equals, uint8(Address))
}
func TestWhoAmI(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice(c, Address)
copy(fake.Registers[:], defaultRegisters())
bus.AddDevice(fake)
dev := New(bus)
c.Assert(dev.Connected(), qt.Equals, true)
fake.Registers[RegID] = 0x99
c.Assert(dev.Connected(), qt.Equals, false)
}
// defaultRegisters returns the default values for all of the device's registers.
// see table 22 on page 27 of the datasheet.
func defaultRegisters() []uint8 {
return []uint8{
RegTempValueMSB: 0,
RegTempValueLSB: 0,
RegStatus: 0,
RegConfig: 0,
RegTHIGHMsbReg: 0x20,
RegTHIGHLsbReg: 0,
RegTLOWMsbReg: 0x05,
RegTLOWLsbReg: 0,
RegTCRITMsbReg: 0x49,
RegTCRITLsbReg: 0x80,
RegTHYSTReg: 0x05,
RegID: 0xC8,
RegReset: 0,
}
}
+48 -2
View File
@@ -1,8 +1,33 @@
package adt7410
// 0x00 Temperature value most significant byte 0x00
// 0x01 Temperature value least significant byte 0x00
// 0x02 Status 0x00
// 0x03 Configuration 0x00
// 0x04 THIGH setpoint most significant byte 0x20 (64°C)
// 0x05 THIGH setpoint least significant byte 0x00 (64°C)
// 0x06 TLOW setpoint most significant byte 0x05 (10°C)
// 0x07 TLOW setpoint least significant byte 0x00 (10°C)
// 0x08 TCRIT setpoint most significant byte 0x49 (147°C)
// 0x09 TCRIT setpoint least significant byte 0x80 (147°C)
// 0x0A THYST setpoint 0x05 (5°C)
// 0x0B ID 0xCX
// 0x0C Reserved 0xXX
// 0x0D Reserved 0xXX
// 0x2E Reserved 0xXX
// 0x2F Software reset 0xXX
const (
// Default I2C address
// Address is default I2C address.
Address = 0x48
// Address1 is for first device, aka the default.
Address1 = Address
// Address2 is for second device.
Address2 = 0x49
// Address3 is for third device.
Address3 = 0x4A
// Address4 is for fourth device.
Address4 = 0x4B
// Temperature Value MSB Register
RegTempValueMSB = 0x0
@@ -16,7 +41,28 @@ const (
// Config Register
RegConfig = 0x3
// ID Register
// THIGH setpoint most significant byte 0x20 (64°C)
RegTHIGHMsbReg = 0x4
// THIGH setpoint least significant byte 0x00 (64°C)
RegTHIGHLsbReg = 0x5
// TLOW setpoint most significant byte 0x05 (10°C)
RegTLOWMsbReg = 0x6
// TLOW setpoint least significant byte 0x00 (10°C)
RegTLOWLsbReg = 0x7
// TCRIT setpoint most significant byte 0x49 (147°C)
RegTCRITMsbReg = 0x8
// TCRIT setpoint least significant byte 0x80 (147°C)
RegTCRITLsbReg = 0x9
// THYST setpoint 0x05 (5°C)
RegTHYSTReg = 0xA
// ID Register (0xCx)
RegID = 0x0B
// Software Reset Register
+3 -5
View File
@@ -6,9 +6,7 @@
//
package adxl345 // import "tinygo.org/x/drivers/adxl345"
import (
"machine"
)
import "tinygo.org/x/drivers"
type Range uint8
type Rate uint8
@@ -40,7 +38,7 @@ type bwRate struct {
// Device wraps an I2C connection to a ADXL345 device.
type Device struct {
bus machine.I2C
bus drivers.I2C
Address uint16
powerCtl powerCtl
dataFormat dataFormat
@@ -52,7 +50,7 @@ type Device struct {
//
// This function only creates the Device object, it does not init the device.
// To do that you must call the Configure() method on the Device before using it.
func New(bus machine.I2C) Device {
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
powerCtl: powerCtl{
+159
View File
@@ -0,0 +1,159 @@
// Package amg88xx provides a driver for the AMG88XX Thermal Camera
//
// Datasheet:
// https://cdn-learn.adafruit.com/assets/assets/000/043/261/original/Grid-EYE_SPECIFICATIONS%28Reference%29.pdf
package amg88xx // import "tinygo.org/x/drivers/amg88xx"
import (
"time"
"tinygo.org/x/drivers"
)
// Device wraps an I2C connection to a AMG88xx device.
type Device struct {
bus drivers.I2C
Address uint16
data []uint8
interruptMode InterruptMode
interruptEnable uint8
}
type InterruptMode uint8
type Config struct {
}
// New creates a new AMG88xx connection. The I2C bus must already be
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: AddressHigh,
}
}
// Configure sets up the device for communication
func (d *Device) Configure(cfg Config) {
d.data = make([]uint8, 128)
d.SetPCTL(NORMAL_MODE)
d.SetReset(INITIAL_RESET)
d.SetFrameRate(FPS_10)
time.Sleep(100 * time.Millisecond)
}
// ReadPixels returns the 64 values (8x8 grid) of the sensor converted to millicelsius
func (d *Device) ReadPixels(buffer *[64]int16) {
d.bus.ReadRegister(uint8(d.Address), PIXEL_OFFSET, d.data)
for i := 0; i < 64; i++ {
buffer[i] = int16((uint16(d.data[2*i+1]) << 8) | uint16(d.data[2*i]))
if (buffer[i] & (1 << 11)) > 0 { // temperature negative
buffer[i] &= ^(1 << 11)
buffer[i] = -buffer[i]
}
buffer[i] *= PIXEL_TEMP_CONVERSION
}
}
// SetPCTL sets the PCTL
func (d *Device) SetPCTL(pctl uint8) {
d.bus.WriteRegister(uint8(d.Address), PCTL, []byte{pctl})
}
// SetReset sets the reset value
func (d *Device) SetReset(rst uint8) {
d.bus.WriteRegister(uint8(d.Address), RST, []byte{rst})
}
// SetFrameRate configures the frame rate
func (d *Device) SetFrameRate(framerate uint8) {
d.bus.WriteRegister(uint8(d.Address), FPSC, []byte{framerate & 0x01})
}
// SetMovingAverageMode sets the moving average mode
func (d *Device) SetMovingAverageMode(mode bool) {
var value uint8
if mode {
value = 1
}
d.bus.WriteRegister(uint8(d.Address), AVE, []byte{value << 5})
}
// SetInterruptLevels sets the interrupt levels
func (d *Device) SetInterruptLevels(high int16, low int16) {
d.SetInterruptLevelsHysteresis(high, low, (high*95)/100)
}
// SetInterruptLevelsHysteresis sets the interrupt levels with hysteresis
func (d *Device) SetInterruptLevelsHysteresis(high int16, low int16, hysteresis int16) {
high = high / PIXEL_TEMP_CONVERSION
if high < -4095 {
high = -4095
}
if high > 4095 {
high = 4095
}
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8(high & 0xFF)})
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8((high & 0xFF) >> 4)})
low = low / PIXEL_TEMP_CONVERSION
if low < -4095 {
low = -4095
}
if low > 4095 {
low = 4095
}
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8(low & 0xFF)})
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8((low & 0xFF) >> 4)})
hysteresis = hysteresis / PIXEL_TEMP_CONVERSION
if hysteresis < -4095 {
hysteresis = -4095
}
if hysteresis > 4095 {
hysteresis = 4095
}
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8(hysteresis & 0xFF)})
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8((hysteresis & 0xFF) >> 4)})
}
// EnableInterrupt enables the interrupt pin on the device
func (d *Device) EnableInterrupt() {
d.interruptEnable = 1
d.bus.WriteRegister(uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
}
// DisableInterrupt disables the interrupt pin on the device
func (d *Device) DisableInterrupt() {
d.interruptEnable = 0
d.bus.WriteRegister(uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
}
// SetInterruptMode sets the interrupt mode
func (d *Device) SetInterruptMode(mode InterruptMode) {
d.interruptMode = mode
d.bus.WriteRegister(uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
}
// GetInterrupt reads the state of the triggered interrupts
func (d *Device) GetInterrupt() []uint8 {
data := make([]uint8, 8)
d.bus.ReadRegister(uint8(d.Address), INT_OFFSET, data)
return data
}
// ClearInterrupt clears any triggered interrupts
func (d *Device) ClearInterrupt() {
d.SetReset(FLAG_RESET)
}
// ReadThermistor reads the onboard thermistor
func (d *Device) ReadThermistor() int16 {
data := make([]uint8, 2)
d.bus.ReadRegister(uint8(d.Address), TTHL, data)
return (int16((uint16(data[1])<<8)|uint16(data[0])) * THERMISTOR_CONVERSION) / 10
}
+46
View File
@@ -0,0 +1,46 @@
package amg88xx
// The I2C address which this device listens to.
const AddressHigh = 0x69
const AddressLow = 0x68
const (
PCTL = 0x00
RST = 0x01
FPSC = 0x02
INTC = 0x03
STAT = 0x04
SCLR = 0x05
AVE = 0x07
INTHL = 0x08
INTHH = 0x09
INTLL = 0x0A
INTLH = 0x0B
IHYSL = 0x0C
IHYSH = 0x0D
TTHL = 0x0E
TTHH = 0x0F
INT_OFFSET = 0x010
PIXEL_OFFSET = 0x80
// power modes
NORMAL_MODE = 0x00
SLEEP_MODE = 0x01
STAND_BY_60 = 0x20
STAND_BY_10 = 0x21
// resets
FLAG_RESET = 0x30
INITIAL_RESET = 0x3F
// frame rates
FPS_10 = 0x00
FPS_1 = 0x01
// interrupt modes
DIFFERENCE InterruptMode = 0x00
ABSOLUTE_VALUE InterruptMode = 0x01
PIXEL_TEMP_CONVERSION = 250
THERMISTOR_CONVERSION = 625
)
+20 -23
View File
@@ -6,6 +6,8 @@ package apa102 // import "tinygo.org/x/drivers/apa102"
import (
"image/color"
"machine"
"tinygo.org/x/drivers"
)
const (
@@ -19,28 +21,23 @@ const (
GRB
)
var startFrame = []byte{0x00, 0x00, 0x00, 0x00}
// Device wraps APA102 SPI LEDs.
type Device struct {
bus SPI
bus drivers.SPI
Order int
}
// The SPI interface specifies the minimum functionality that a bus
// implementation needs to provide for use by the APA102 driver. Hardware
// SPI from the TinyGo "machine" package implements this already.
type SPI interface {
Tx(w, r []byte) error
}
// New returns a new APA102 driver. Pass in a fully configured SPI bus.
func New(b SPI) Device {
func New(b drivers.SPI) Device {
return Device{bus: b, Order: BGR}
}
// NewSoftwareSPI returns a new APA102 driver that will use a software based
// implementation of the SPI protocol.
func NewSoftwareSPI(sckPin, mosiPin machine.Pin, delay uint32) Device {
return New(&bbSPI{SCK: sckPin, MOSI: mosiPin, Delay: delay})
func NewSoftwareSPI(sckPin, sdoPin machine.Pin, delay uint32) Device {
return New(&bbSPI{SCK: sckPin, SDO: sdoPin, Delay: delay})
}
// WriteColors writes the given RGBA color slice out using the APA102 protocol.
@@ -51,22 +48,22 @@ func (d Device) WriteColors(cs []color.RGBA) (n int, err error) {
// write data
for _, c := range cs {
// brightness is scaled to 5 bit value
d.bus.Tx([]byte{0xe0 | (c.A >> 3)}, nil)
d.bus.Transfer(0xe0 | (c.A >> 3))
// set the colors
switch d.Order {
case BRG:
d.bus.Tx([]byte{c.B}, nil)
d.bus.Tx([]byte{c.R}, nil)
d.bus.Tx([]byte{c.G}, nil)
d.bus.Transfer(c.B)
d.bus.Transfer(c.R)
d.bus.Transfer(c.G)
case GRB:
d.bus.Tx([]byte{c.G}, nil)
d.bus.Tx([]byte{c.R}, nil)
d.bus.Tx([]byte{c.B}, nil)
d.bus.Transfer(c.G)
d.bus.Transfer(c.R)
d.bus.Transfer(c.B)
case BGR:
d.bus.Tx([]byte{c.B}, nil)
d.bus.Tx([]byte{c.G}, nil)
d.bus.Tx([]byte{c.R}, nil)
d.bus.Transfer(c.B)
d.bus.Transfer(c.G)
d.bus.Transfer(c.R)
}
}
@@ -86,7 +83,7 @@ func (d Device) Write(buf []byte) (n int, err error) {
// startFrame sends the start bytes for a strand of LEDs.
func (d Device) startFrame() {
d.bus.Tx([]byte{0x00, 0x00, 0x00, 0x00}, nil)
d.bus.Tx(startFrame, nil)
}
// endFrame sends the end frame marker with one extra bit per LED so
@@ -94,6 +91,6 @@ func (d Device) startFrame() {
// See https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/
func (d Device) endFrame(count int) {
for i := 0; i < count/16; i++ {
d.bus.Tx([]byte{0xff}, nil)
d.bus.Transfer(0xff)
}
}
+13 -11
View File
@@ -9,16 +9,16 @@ import "machine"
// this more generic and include it in the TinyGo "machine" package instead.
type bbSPI struct {
SCK machine.Pin
MOSI machine.Pin
SDO machine.Pin
Delay uint32
}
// Configure sets up the SCK and MOSI pins as outputs and sets them low
// 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.MOSI.Configure(machine.PinConfig{Mode: machine.PinOutput})
s.SDO.Configure(machine.PinConfig{Mode: machine.PinOutput})
s.SCK.Low()
s.MOSI.Low()
s.SDO.Low()
if s.Delay == 0 {
s.Delay = 1
}
@@ -41,19 +41,20 @@ func (s *bbSPI) delay() {
}
}
// Transfer is used to send a single byte.
func (s *bbSPI) Transfer(b byte) {
// Transfer matches signature of machine.SPI.Transfer() and is used to send a
// single byte. The received data is ignored and no error will ever be returned.
func (s *bbSPI) Transfer(b byte) (byte, error) {
for i := uint8(0); i < 8; i++ {
// half clock cycle high to start
s.SCK.High()
s.delay()
// write the value to MOSI (MSB first)
// write the value to SDO (MSB first)
if b&(1<<(7-i)) == 0 {
s.MOSI.Low()
s.SDO.Low()
} else {
s.MOSI.High()
s.SDO.High()
}
s.delay()
@@ -61,8 +62,9 @@ func (s *bbSPI) Transfer(b byte) {
s.SCK.Low()
s.delay()
// for actual SPI would try to read the MISO value here
// for actual SPI would try to read the SDI value here
s.delay()
}
return 0, nil
}
+4 -3
View File
@@ -6,13 +6,14 @@ package at24cx // import "tinygo.org/x/drivers/at24cx"
import (
"errors"
"machine"
"time"
"tinygo.org/x/drivers"
)
// Device wraps an I2C connection to a DS3231 device.
type Device struct {
bus machine.I2C
bus drivers.I2C
Address uint16
pageSize uint16
currentRAMAddress uint16
@@ -30,7 +31,7 @@ type Config struct {
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2C) Device {
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
+3 -3
View File
@@ -8,7 +8,7 @@ package bh1750 // import "tinygo.org/x/drivers/bh1750"
import (
"time"
"machine"
"tinygo.org/x/drivers"
)
// SamplingMode is the sampling's resolution of the measurement
@@ -16,7 +16,7 @@ type SamplingMode byte
// Device wraps an I2C connection to a bh1750 device.
type Device struct {
bus machine.I2C
bus drivers.I2C
Address uint16
mode SamplingMode
}
@@ -25,7 +25,7 @@ type Device struct {
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2C) Device {
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
+4 -5
View File
@@ -1,15 +1,14 @@
// Package blinkm implements a driver for the BlinkM I2C RGB LED.
//
// Datasheet: http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf
//
package blinkm // import "tinygo.org/x/drivers/blinkm"
import (
"machine"
)
import "tinygo.org/x/drivers"
// Device wraps an I2C connection to a BlinkM device.
type Device struct {
bus machine.I2C
bus drivers.I2C
Address uint16
}
@@ -17,7 +16,7 @@ type Device struct {
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2C) Device {
func New(bus drivers.I2C) Device {
return Device{bus, Address}
}
+4 -3
View File
@@ -7,8 +7,9 @@
package bme280
import (
"machine"
"math"
"tinygo.org/x/drivers"
)
// calibrationCoefficients reads at startup and stores the calibration coefficients
@@ -35,7 +36,7 @@ type calibrationCoefficients struct {
// Device wraps an I2C connection to a BME280 device.
type Device struct {
bus machine.I2C
bus drivers.I2C
Address uint16
calibrationCoefficients calibrationCoefficients
}
@@ -44,7 +45,7 @@ type Device struct {
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2C) Device {
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
+204
View File
@@ -0,0 +1,204 @@
package bmi160
import (
"machine"
"time"
"tinygo.org/x/drivers"
)
// DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is
// also an I2C interface, but it is not yet supported.
type DeviceSPI struct {
// Chip select pin
CSB machine.Pin
// SPI bus (requires chip select to be usable).
Bus drivers.SPI
}
// NewSPI returns a new device driver. The pin and SPI interface are not
// touched, provide a fully configured SPI object and call Configure to start
// using this device.
func NewSPI(csb machine.Pin, spi drivers.SPI) *DeviceSPI {
return &DeviceSPI{
CSB: csb, // chip select
Bus: spi,
}
}
// Configure configures the BMI160 for use. It configures the CSB pin and
// 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()
// 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
// > to SPI until a reset or the next power-up occurs. Therefore, a CSB
// > rising edge is needed before starting the SPI communication. Hence, it
// > is recommended to perform a SPI single read access to the ADDRESS 0x7F
// > before the actual communication in order to use the SPI interface.
d.readRegister(0x7F)
// Power up the accelerometer. 0b0001_00nn is the command format, with 0b01
// indicating normal mode.
d.runCommand(0b0001_0001)
// Power up the gyroscope. 0b0001_01nn is the command format, with 0b01
// indicating normal mode.
d.runCommand(0b0001_0101)
// Wait until the device is fully initialized. Even after the command has
// finished, the gyroscope may not be fully powered on. Therefore, wait
// until we get an expected value.
// This takes 30ms or so.
for {
// Wait for the acc_pmu_status and gyr_pmu_status to both be 0b01.
if d.readRegister(reg_PMU_STATUS) == 0b0001_0100 {
break
}
}
return nil
}
// Connected check whether the device appears to be properly connected. It reads
// the CHIPID, which must be 0xD1 for the BMI160.
func (d *DeviceSPI) Connected() bool {
return d.readRegister(reg_CHIPID) == 0xD1
}
// Reset restores the device to the state after power up. This can be useful to
// easily disable the accelerometer and gyroscope to reduce current consumption.
func (d *DeviceSPI) Reset() error {
d.runCommand(0xB6) // softreset
return nil
}
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000).
func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
data := []byte{0x80 | reg_TEMPERATURE_0, 0, 0}
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
if err != nil {
return
}
rawTemperature := int16(uint16(data[1]) | uint16(data[2])<<8)
// 0x0000 is 23°C
// 0x7fff is ~87°C
// We use 0x8000 instead of 0x7fff to make the formula easier. The result
// should be near identical and shouldn't affect the result too much (the
// temperature sensor has an offset of around 2°C so isn't very reliable).
// So the formula is as follows:
// 1. Scale from 0x0000..0x8000 to 0..(87-23).
// rawTemperature * (87-23) / 0x8000
// 2. Convert to centidegrees.
// rawTemperature * 1000 * (87-23) / 0x8000
// 3. Add 23°C offset.
// rawTemperature * 1000 * (87-23) / 0x8000 + 23000
// 4. Simplify.
// rawTemperature * 1000 * 64 / 0x8000 + 23000
// rawTemperature * 64000 / 0x8000 + 23000
// rawTemperature * 125 / 64 + 23000
temperature = int32(rawTemperature)*125/64 + 23000
return
}
// ReadAcceleration reads the current acceleration from the device and returns
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
data := []byte{0x80 | reg_ACC_XL, 0, 0, 0, 0, 0, 0}
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
if err != nil {
return
}
// Now do two things:
// 1. merge the two values to a 16-bit number (and cast to a 32-bit integer)
// 2. scale the value to bring it in the -1000000..1000000 range.
// This is done with a trick. What we do here is essentially multiply by
// 1000000 and divide by 16384 to get the original scale, but to avoid
// overflow we do it at 1/64 of the value:
// 1000000 / 64 = 15625
// 16384 / 64 = 256
x = int32(int16(uint16(data[1])|uint16(data[2])<<8)) * 15625 / 256
y = int32(int16(uint16(data[3])|uint16(data[4])<<8)) * 15625 / 256
z = int32(int16(uint16(data[5])|uint16(data[6])<<8)) * 15625 / 256
return
}
// ReadRotation reads the current rotation from the device and returns it in
// µ°/s (micro-degrees/sec). This means that if you were to do a complete
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *DeviceSPI) ReadRotation() (x int32, y int32, z int32, err error) {
data := []byte{0x80 | reg_GYR_XL, 0, 0, 0, 0, 0, 0}
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
if err != nil {
return
}
// First the value is converted from a pair of bytes to a signed 16-bit
// value and then to a signed 32-bit value to avoid integer overflow.
// Then the value is scaled to µ°/s (micro-degrees per second).
// The default is 2000°/s full scale range for -32768..32767.
// The formula works as follows (taking X as an example):
// 1. Scale from 32768 to 2000. This means that it is in °/s units.
// rawX * 2000 / 32768
// 2. Scale to µ°/s by multiplying by 1e6.
// rawX * 1e6 * 2000 / 32768
// 3. Simplify.
// rawX * 2e9 / 32768
// rawX * 1953125 / 32
rawX := int32(int16(uint16(data[1]) | uint16(data[2])<<8))
rawY := int32(int16(uint16(data[3]) | uint16(data[4])<<8))
rawZ := int32(int16(uint16(data[5]) | uint16(data[6])<<8))
x = int32(int64(rawX) * 1953125 / 32)
y = int32(int64(rawY) * 1953125 / 32)
z = int32(int64(rawZ) * 1953125 / 32)
return
}
// runCommand runs a BMI160 command through the CMD register. It waits for the
// command to complete before returning.
func (d *DeviceSPI) runCommand(command uint8) {
d.writeRegister(reg_CMD, command)
for {
response := d.readRegister(reg_CMD)
if response == 0 {
return // command was completed
}
}
}
// readRegister reads from a single BMI160 register. It should only be used for
// single register reads, not for reading multiple registers at once.
func (d *DeviceSPI) readRegister(address uint8) uint8 {
// I don't know why but it appears necessary to sleep for a bit here.
time.Sleep(time.Millisecond)
data := []byte{0x80 | address, 0}
d.CSB.Low()
d.Bus.Tx(data, data)
d.CSB.High()
return data[1]
}
// writeRegister writes a single byte BMI160 register. It should only be used
// for writing to a single register.
func (d *DeviceSPI) writeRegister(address, data uint8) {
// I don't know why but it appears necessary to sleep for a bit here.
time.Sleep(time.Millisecond)
d.CSB.Low()
d.Bus.Tx([]byte{address, data}, []byte{0, 0})
d.CSB.High()
}
+44
View File
@@ -0,0 +1,44 @@
package bmi160
const (
reg_CHIPID = 0x00
reg_ERR_REG = 0x02
reg_PMU_STATUS = 0x03
reg_MAG_XL = 0x04
reg_MAG_XH = 0x05
reg_MAG_YL = 0x06
reg_MAG_YH = 0x07
reg_MAG_ZL = 0x08
reg_MAG_ZH = 0x09
reg_RHALL_L = 0x0A
reg_RHALL_H = 0x0B
reg_GYR_XL = 0x0C
reg_GYR_XH = 0x0D
reg_GYR_YL = 0x0E
reg_GYR_YH = 0x0F
reg_GYR_ZL = 0x10
reg_GYR_ZH = 0x11
reg_ACC_XL = 0x12
reg_ACC_XH = 0x13
reg_ACC_YL = 0x14
reg_ACC_YH = 0x15
reg_ACC_ZL = 0x16
reg_ACC_ZH = 0x17
reg_SENSORTIME_0 = 0x18
reg_SENSORTIME_1 = 0x19
reg_SENSORTIME_2 = 0x1A
reg_STATUS = 0x1B
reg_INT_STATUS_0 = 0x1C
reg_INT_STATUS_1 = 0x1D
reg_INT_STATUS_2 = 0x1E
reg_INT_STATUS_3 = 0x1F
reg_TEMPERATURE_0 = 0x20
reg_TEMPERATURE_1 = 0x21
reg_FIFO_LENGTH_0 = 0x22
reg_FIFO_LENGTH_1 = 0x23
reg_FIFO_DATA = 0x24
// ...
reg_CMD = 0x7E
)
+7 -7
View File
@@ -9,7 +9,7 @@ package bmp180 // import "tinygo.org/x/drivers/bmp180"
import (
"time"
"machine"
"tinygo.org/x/drivers"
)
// OversamplingMode is the oversampling ratio of the pressure measurement.
@@ -32,7 +32,7 @@ type calibrationCoefficients struct {
// Device wraps an I2C connection to a BMP180 device.
type Device struct {
bus machine.I2C
bus drivers.I2C
Address uint16
mode OversamplingMode
calibrationCoefficients calibrationCoefficients
@@ -43,7 +43,7 @@ type Device struct {
//
// This function only creates the Device object, it does not initialize the device.
// You must call Configure() first in order to use the device itself.
func New(bus machine.I2C) Device {
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
@@ -125,7 +125,7 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
}
// rawTemp returns the sensor's raw values of the temperature
func (d *Device) rawTemp() (int16, error) {
func (d *Device) rawTemp() (int32, error) {
d.bus.WriteRegister(uint8(d.Address), REG_CTRL, []byte{CMD_TEMP})
time.Sleep(5 * time.Millisecond)
data := make([]byte, 2)
@@ -133,12 +133,12 @@ func (d *Device) rawTemp() (int16, error) {
if err != nil {
return 0, err
}
return readInt(data[0], data[1]), nil
return int32(uint16(data[0])<<8 | uint16(data[1])), nil
}
// calculateB5 calculates intermediate value B5 as per page 15 of datasheet
func (d *Device) calculateB5(rawTemp int16) int32 {
x1 := (int32(rawTemp) - int32(d.calibrationCoefficients.ac6)) * int32(d.calibrationCoefficients.ac5) >> 15
func (d *Device) calculateB5(rawTemp int32) int32 {
x1 := (rawTemp - int32(d.calibrationCoefficients.ac6)) * int32(d.calibrationCoefficients.ac5) >> 15
x2 := int32(d.calibrationCoefficients.mc) << 11 / (x1 + int32(d.calibrationCoefficients.md))
return x1 + x2
}
+244
View File
@@ -0,0 +1,244 @@
package bmp280
import (
"time"
"tinygo.org/x/drivers"
)
// OversamplingMode is the oversampling ratio of the temperature or pressure measurement.
type Oversampling uint
// Mode is the Power Mode.
type Mode uint
// Standby is the inactive period between the reads when the sensor is in normal power mode.
type Standby uint
// Filter unwanted changes in measurement caused by external (environmental) or internal changes (IC).
type Filter uint
// Device wraps an I2C connection to a BMP280 device.
type Device struct {
bus drivers.I2C
Address uint16
cali calibrationCoefficients
Temperature Oversampling
Pressure Oversampling
Mode Mode
Standby Standby
Filter Filter
}
type calibrationCoefficients struct {
// Temperature compensation
t1 uint16
t2 int16
t3 int16
// Pressure compensation
p1 uint16
p2 int16
p3 int16
p4 int16
p5 int16
p6 int16
p7 int16
p8 int16
p9 int16
}
// New creates a new BMP280 connection. The I2C bus must already be
// configured.
//
// This function only creates the Device object, it does not initialize the device.
// You must call Configure() first in order to use the device itself.
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
}
}
// Connected returns whether a BMP280 has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := make([]byte, 1)
d.bus.ReadRegister(uint8(d.Address), REG_ID, data)
return data[0] == CHIP_ID
}
// Reset preforms complete power-on-reset procedure.
// It is required to call Configure afterwards.
func (d *Device) Reset() {
d.bus.WriteRegister(uint8(d.Address), REG_RESET, []byte{CMD_RESET})
}
// Configure sets up the device for communication and
// read the calibration coefficients.
func (d *Device) Configure(standby Standby, filter Filter, temp Oversampling, pres Oversampling, mode Mode) {
d.Standby = standby
d.Filter = filter
d.Temperature = temp
d.Pressure = pres
d.Mode = mode
// Write the configuration (standby, filter, spi 3 wire)
config := uint(d.Standby<<5) | uint(d.Filter<<2) | 0x00
d.bus.WriteRegister(uint8(d.Address), REG_CONFIG, []byte{byte(config)})
// Write the control (temperature oversampling, pressure oversampling,
config = uint(d.Temperature<<5) | uint(d.Pressure<<2) | uint(d.Mode)
d.bus.WriteRegister(uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)})
// Read Calibration data
data := make([]byte, 24)
err := d.bus.ReadRegister(uint8(d.Address), REG_CALI, data)
if err != nil {
return
}
// Datasheet: 3.11.2 Trimming parameter readout
d.cali.t1 = readUintLE(data[0], data[1])
d.cali.t2 = readIntLE(data[2], data[3])
d.cali.t3 = readIntLE(data[4], data[5])
d.cali.p1 = readUintLE(data[6], data[7])
d.cali.p2 = readIntLE(data[8], data[9])
d.cali.p3 = readIntLE(data[10], data[11])
d.cali.p4 = readIntLE(data[12], data[13])
d.cali.p5 = readIntLE(data[14], data[15])
d.cali.p6 = readIntLE(data[16], data[17])
d.cali.p7 = readIntLE(data[18], data[19])
d.cali.p8 = readIntLE(data[20], data[21])
d.cali.p9 = readIntLE(data[22], data[23])
}
// PrintCali prints the Calibration information.
func (d *Device) PrintCali() {
println("T1:", d.cali.t1)
println("T2:", d.cali.t2)
println("T3:", d.cali.t3)
println("P1:", d.cali.p1)
println("P2:", d.cali.p2)
println("P3:", d.cali.p3)
println("P4:", d.cali.p4)
println("P5:", d.cali.p5)
println("P6:", d.cali.p6)
println("P7:", d.cali.p7)
println("P8:", d.cali.p8)
println("P9:", d.cali.p9, "\n")
}
// 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 {
return
}
rawTemp := convert3Bytes(data[0], data[1], data[2])
// Datasheet: 8.2 Compensation formula in 32 bit fixed point
// Temperature compensation
var1 := ((rawTemp >> 3) - int32(d.cali.t1<<1)) * int32(d.cali.t2) >> 11
var2 := (((rawTemp >> 4) - int32(d.cali.t1)) * ((rawTemp >> 4) - int32(d.cali.t1)) >> 12) *
int32(d.cali.t3) >> 14
tFine := var1 + var2
// Convert from degrees to milli degrees by multiplying by 10.
// Will output 30250 milli degrees celsius for 30.25 degrees celsius
temperature = 10 * ((tFine*5 + 128) >> 8)
return
}
// 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 {
return
}
rawTemp := convert3Bytes(data[3], data[4], data[5])
// Datasheet: 8.2 Compensation formula in 32 bit fixed point
// Calculate tFine (temperature), used for the Pressure compensation
var1 := ((rawTemp >> 3) - int32(d.cali.t1<<1)) * int32(d.cali.t2) >> 11
var2 := (((rawTemp >> 4) - int32(d.cali.t1)) * ((rawTemp >> 4) - int32(d.cali.t1)) >> 12) *
int32(d.cali.t3) >> 14
tFine := var1 + var2
rawPres := convert3Bytes(data[0], data[1], data[2])
// Datasheet: 8.2 Compensation formula in 32 bit fixed point
// Pressure compensation
var1 = (tFine >> 1) - 64000
var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * int32(d.cali.p6)
var2 = var2 + ((var1 * int32(d.cali.p5)) << 1)
var2 = (var2 >> 2) + (int32(d.cali.p4) << 16)
var1 = (((int32(d.cali.p3) * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) +
((int32(d.cali.p2) * var1) >> 1)) >> 18
var1 = ((32768 + var1) * int32(d.cali.p1)) >> 15
if var1 == 0 {
return 0, nil
}
p := uint32(((1048576 - rawPres) - (var2 >> 12)) * 3125)
if p < 0x80000000 {
p = (p << 1) / uint32(var1)
} else {
p = (p / uint32(var1)) * 2
}
var1 = (int32(d.cali.p9) * int32(((p>>3)*(p>>3))>>13)) >> 12
var2 = (int32(p>>2) * int32(d.cali.p8)) >> 13
return 1000 * (int32(p) + ((var1 + var2 + int32(d.cali.p7)) >> 4)), nil
}
// readData reads n number of bytes of the specified register
func (d *Device) readData(register int, n int) ([]byte, error) {
// If not in normal mode, set the mode to FORCED mode, to prevent incorrect measurements
// After the measurement in FORCED mode, the sensor will return to SLEEP mode
if d.Mode != MODE_NORMAL {
config := uint(d.Temperature<<5) | uint(d.Pressure<<2) | uint(MODE_FORCED)
d.bus.WriteRegister(uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)})
}
// Check STATUS register, wait if data is not available yet
status := make([]byte, 1)
for d.bus.ReadRegister(uint8(d.Address), uint8(REG_STATUS), status[0:]); status[0] != 4 && status[0] != 0; d.bus.ReadRegister(uint8(d.Address), uint8(REG_STATUS), status[0:]) {
time.Sleep(time.Millisecond)
}
// Read the requested register
data := make([]byte, n)
err := d.bus.ReadRegister(uint8(d.Address), uint8(register), data[:])
return data, err
}
// convert3Bytes converts three bytes to int32
func convert3Bytes(msb byte, b1 byte, lsb byte) int32 {
return int32(((((uint32(msb) << 8) | uint32(b1)) << 8) | uint32(lsb)) >> 4)
}
// readUint converts two bytes to uint16
func readUint(msb byte, lsb byte) uint16 {
return (uint16(msb) << 8) | uint16(lsb)
}
// readUintLE converts two little endian bytes to uint16
func readUintLE(msb byte, lsb byte) uint16 {
temp := readUint(msb, lsb)
return (temp >> 8) | (temp << 8)
}
// readIntLE converts two little endian bytes to int16
func readIntLE(msb byte, lsb byte) int16 {
return int16(readUintLE(msb, lsb))
}
+56
View File
@@ -0,0 +1,56 @@
// Package bmp280 provides a driver for the BMP280 digital temperature & pressure sensor by Bosch.
//
// Datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
package bmp280
// The I2C address which this device listens to.
const Address = 0x77
// Registers
const (
REG_ID = 0xD0 // WHO_AM_I
REG_RESET = 0xE0
REG_STATUS = 0xF3
REG_CTRL_MEAS = 0xF4
REG_CONFIG = 0xF5
REG_TEMP = 0xFA
REG_PRES = 0xF7
REG_CALI = 0x88
CHIP_ID = 0x58
CMD_RESET = 0xB6
)
const (
SAMPLING_SKIPPED Oversampling = iota
SAMPLING_1X
SAMPLING_2X
SAMPLING_4X
SAMPLING_8X
SAMPLING_16X
)
const (
MODE_SLEEP Mode = 0x00
MODE_FORCED Mode = 0x01
MODE_NORMAL Mode = 0x03
)
const (
STANDBY_1MS Standby = iota
STANDBY_63MS
STANDBY_125MS
STANDBY_250MS
STANDBY_500MS
STANDBY_1000MS
STANDBY_2000MS
STANDBY_4000MS
)
const (
FILTER_OFF Filter = iota
FILTER_2X
FILTER_4X
FILTER_8X
FILTER_16X
)
+249
View File
@@ -0,0 +1,249 @@
package bmp388
import (
"errors"
"tinygo.org/x/drivers"
)
var (
errConfigWrite = errors.New("bmp388: failed to configure sensor, check connection")
errConfig = errors.New("bmp388: there is a problem with the configuration, try reducing ODR")
errCaliRead = errors.New("bmp388: failed to read calibration coefficient register")
errSoftReset = errors.New("bmp388: failed to perform a soft reset")
errNotConnected = errors.New("bmp388: not connected")
)
type Oversampling byte
type Mode byte
type OutputDataRate byte
type FilterCoefficient byte
// Config contains settings for filtering, sampling, and modes of operation
type Config struct {
Pressure Oversampling
Temperature Oversampling
Mode Mode
ODR OutputDataRate
IIR FilterCoefficient
}
// Device wraps the I2C connection and configuration values for the BMP388
type Device struct {
bus drivers.I2C
Address uint8
cali calibrationCoefficients
Config Config
}
type calibrationCoefficients struct {
// Temperature compensation
t1 uint16
t2 uint16
t3 int8
// Pressure compensation
p1 int16
p2 int16
p3 int8
p4 int8
p5 uint16
p6 uint16
p7 int8
p8 int8
p9 int16
p10 int8
p11 int8
}
// New returns a bmp388 struct with the default I2C address. Configure must also be called after instanting
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
}
}
// Configure can enable settings on the BMP388 and reads the calibration coefficients
func (d *Device) Configure(config Config) (err error) {
d.Config = config
if d.Config == (Config{}) {
d.Config.Mode = Normal
}
// Turning on the pressure and temperature sensors and setting the measurement mode
err = d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
// Configure the oversampling, output data rate, and iir filter coefficient settings
err = d.writeRegister(RegOSR, byte(d.Config.Pressure|d.Config.Temperature<<3))
err = d.writeRegister(RegODR, byte(d.Config.ODR))
err = d.writeRegister(RegIIR, byte(d.Config.IIR<<1))
if err != nil {
return errConfigWrite
}
// Check if there is a problem with the given configuration
if d.configurationError() {
return errConfig
}
// Reading the builtin calibration coefficients and parsing them per the datasheet. The compensation formula given
// in the datasheet is implemented in floating point
buffer, err := d.readRegister(RegCali, 21)
if err != nil {
return errCaliRead
}
d.cali.t1 = uint16(buffer[1])<<8 | uint16(buffer[0])
d.cali.t2 = uint16(buffer[3])<<8 | uint16(buffer[2])
d.cali.t3 = int8(buffer[4])
d.cali.p1 = int16(buffer[6])<<8 | int16(buffer[5])
d.cali.p2 = int16(buffer[8])<<8 | int16(buffer[7])
d.cali.p3 = int8(buffer[9])
d.cali.p4 = int8(buffer[10])
d.cali.p5 = uint16(buffer[12])<<8 | uint16(buffer[11])
d.cali.p6 = uint16(buffer[14])<<8 | uint16(buffer[13])
d.cali.p7 = int8(buffer[15])
d.cali.p8 = int8(buffer[16])
d.cali.p9 = int16(buffer[18])<<8 | int16(buffer[17])
d.cali.p10 = int8(buffer[19])
d.cali.p11 = int8(buffer[20])
return nil
}
// Read the temperature registers and compute a compensation value for the temperature and pressure compensation
// calculations. This is not the temperature itself.
func (d *Device) tlinCompensate() (int64, error) {
rawTemp, err := d.readSensorData(RegTemp)
if err != nil {
return 0, err
}
// pulled from C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
partialData1 := rawTemp - (256 * int64(d.cali.t1))
partialData2 := int64(d.cali.t2) * partialData1
partialData3 := (partialData1 * partialData1)
partialData4 := partialData3 * int64(d.cali.t3)
partialData5 := (partialData2 * 262144) + partialData4
return partialData5 / 4294967296, nil
}
// ReadTemperature returns the temperature in centicelsius, i.e 2426 / 100 = 24.26 C
func (d *Device) ReadTemperature() (int32, error) {
tlin, err := d.tlinCompensate()
if err != nil {
return 0, err
}
temp := (tlin * 25) / 16384
return int32(temp), nil
}
// ReadPressure returns the pressure in centipascals, i.e 10132520 / 100 = 101325.20 Pa
func (d *Device) ReadPressure() (int32, error) {
tlin, err := d.tlinCompensate()
if err != nil {
return 0, err
}
rawPress, err := d.readSensorData(RegPress)
if err != nil {
return 0, err
}
// code pulled from bmp388 C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
partialData1 := tlin * tlin
partialData2 := partialData1 / 64
partialData3 := (partialData2 * tlin) / 256
partialData4 := (int64(d.cali.p8) * partialData3) / 32
partialData5 := (int64(d.cali.p7) * partialData1) * 16
partialData6 := (int64(d.cali.p6) * tlin) * 4194304
offset := (int64(d.cali.p5) * 140737488355328) + partialData4 + partialData5 + partialData6
partialData2 = (int64(d.cali.p4) * partialData3) / 32
partialData4 = (int64(d.cali.p3) * partialData1) * 4
partialData5 = (int64(d.cali.p2) - 16384) * tlin * 2097152
sensitivity := ((int64(d.cali.p1) - 16384) * 70368744177664) + partialData2 + partialData4 + partialData5
partialData1 = (sensitivity / 16777216) * rawPress
partialData2 = int64(d.cali.p10) * tlin
partialData3 = partialData2 + (65536 * int64(d.cali.p9))
partialData4 = (partialData3 * rawPress) / 8192
// dividing by 10 followed by multiplying by 10
// To avoid overflow caused by (pressure * partial_data4)
partialData5 = (rawPress * (partialData4 / 10)) / 512
partialData5 = partialData5 * 10
partialData6 = (int64)(uint64(rawPress) * uint64(rawPress))
partialData2 = (int64(d.cali.p11) * partialData6) / 65536
partialData3 = (partialData2 * rawPress) / 128
partialData4 = (offset / 4) + partialData1 + partialData5 + partialData3
compPress := ((uint64(partialData4) * 25) / uint64(1099511627776))
return int32(compPress), nil
}
// SoftReset commands the BMP388 to reset of all user configuration settings
func (d *Device) SoftReset() error {
err := d.writeRegister(RegCmd, SoftReset)
if err != nil {
return errSoftReset
}
return nil
}
// Connected tries to reach the bmp388 and check its chip id register. Returns true if it was able to successfully
// communicate over i2c and returns the correct value
func (d *Device) Connected() bool {
data, err := d.readRegister(RegChipId, 1)
return err == nil && data[0] == ChipId // returns true if i2c comm was good and response equals 0x50
}
// SetMode changes the run mode of the sensor, NORMAL is the one to use for most cases. Use FORCED if you plan to take
// measurements infrequently and want to conserve power. SLEEP will of course put the sensor to sleep
func (d *Device) SetMode(mode Mode) error {
d.Config.Mode = mode
return d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
}
func (d *Device) readSensorData(register byte) (data int64, err error) {
if !d.Connected() {
return 0, errNotConnected
}
// put the sensor back into forced mode to get a reading, the sensor goes back to sleep after taking one read in
// forced mode
if d.Config.Mode != Normal {
err = d.SetMode(Forced)
if err != nil {
return
}
}
bytes, err := d.readRegister(register, 3)
if err != nil {
return
}
data = int64(bytes[2])<<16 | int64(bytes[1])<<8 | int64(bytes[0])
return
}
// configurationError checks the register error for the configuration error bit. The bit is cleared on read by the bmp.
func (d *Device) configurationError() bool {
data, err := d.readRegister(RegErr, 1)
return err == nil && (data[0]&0x04) != 0
}
func (d *Device) readRegister(register byte, len int) (data []byte, err error) {
data = make([]byte, len)
err = d.bus.ReadRegister(d.Address, register, data)
return
}
func (d *Device) writeRegister(register byte, data byte) error {
return d.bus.WriteRegister(d.Address, register, []byte{data})
}
+84
View File
@@ -0,0 +1,84 @@
// Package bmp388 provides a driver for Bosch's BMP388 digital temperature & pressure sensor.
// The datasheet can be found here: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf
package bmp388
const Address byte = 0x77 // default I2C address
const (
RegChipId byte = 0x00 // useful for checking the connection
RegCali byte = 0x31 // pressure & temperature compensation calibration coefficients
RegPress byte = 0x04 // start of pressure data registers
RegTemp byte = 0x07 // start of temperature data registers
RegPwrCtrl byte = 0x1B // measurement mode & pressure/temperature sensor power register
RegOSR byte = 0x1C // oversampling settings register
RegODR byte = 0x1D //
RegCmd byte = 0x7E // miscellaneous command register
RegStat byte = 0x03 // sensor status register
RegErr byte = 0x02 // error status register
RegIIR byte = 0x1F
)
const (
ChipId byte = 0x50 // correct response if reading from chip id register
PwrPress byte = 0x01 // power on pressure sensor
PwrTemp byte = 0x02 // power on temperature sensor
SoftReset byte = 0xB6 // command to reset all user configuration
DRDYPress byte = 0x20 // for checking if pressure data is ready
DRDYTemp byte = 0x40 // for checking if pressure data is ready
)
// The difference between forced and normal mode is the bmp388 goes to sleep after taking a measurement in forced mode.
// Set it to forced if you intend to take measurements sporadically and want to save power. The driver will handle
// waking the sensor up when the sensor is in forced mode.
const (
Normal Mode = 0x30
Forced Mode = 0x16
Sleep Mode = 0x00
)
// Increasing sampling rate increases precision but also the wait time for measurements. The datasheet has a table of
// suggested values for oversampling, output data rates, and iir filter coefficients by use case.
const (
Sampling1X Oversampling = iota
Sampling2X
Sampling4X
Sampling8X
Sampling16X
Sampling32X
)
// Output data rates in Hz. If increasing the sampling rates you need to decrease the output data rates, else the bmp388
// will freeze and Configure() will return a configuration error message. In that case keep decreasing the data rate
// until the bmp is happy
const (
Odr200 OutputDataRate = iota
Odr100
Odr50
Odr25
Odr12p5
Odr6p25
Odr3p1
Odr1p5
Odr0p78
Odr0p39
Odr0p2
Odr0p1
Odr0p05
Odr0p02
Odr0p01
Odr0p006
Odr0p003
Odr0p0015
)
// IIR filter coefficients, higher values means steadier measurements but slower reaction times
const (
Coeff0 FilterCoefficient = iota
Coeff1
Coeff3
Coeff7
Coeff15
Coeff31
Coeff63
Coeff127
)
+116
View File
@@ -0,0 +1,116 @@
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
package dht // import "tinygo.org/x/drivers/dht"
import (
"encoding/binary"
"machine"
"time"
)
// enum type for device type
type DeviceType uint8
// DeviceType specific parsing of information received from the sensor
func (d DeviceType) extractData(buf []byte) (temp int16, hum uint16) {
if d == DHT11 {
temp = int16(buf[2])
if buf[3]&0x80 > 0 {
temp = -1 - temp
}
temp *= 10
temp += int16(buf[3] & 0x0f)
hum = 10*uint16(buf[0]) + uint16(buf[1])
} else {
hum = binary.LittleEndian.Uint16(buf[0:2])
temp = int16(buf[3])<<8 + int16(buf[2]&0x7f)
if buf[2]&0x80 > 0 {
temp = -temp
}
}
return
}
// Celsius and Fahrenheit temperature scales
type TemperatureScale uint8
func (t TemperatureScale) convertToFloat(temp int16) float32 {
if t == C {
return float32(temp) / 10
} else {
// Fahrenheit
return float32(temp)*(9.0/50.) + 32.
}
}
// All functions return ErrorCode instance as error. This class can be used for more efficient error processing
type ErrorCode uint8
const (
startTimeout = time.Millisecond * 200
startingLow = time.Millisecond * 20
DHT11 DeviceType = iota
DHT22
C TemperatureScale = iota
F
ChecksumError ErrorCode = iota
NoSignalError
NoDataError
UpdateError
UninitializedDataError
)
// error interface implementation for ErrorCode
func (e ErrorCode) Error() string {
switch e {
case ChecksumError:
// DHT returns ChecksumError if all the data from the sensor was received, but the checksum does not match.
return "checksum mismatch"
case NoSignalError:
// DHT returns NoSignalError if there was no reply from the sensor. Check sensor connection or the correct pin
// sis chosen,
return "no signal"
case NoDataError:
// DHT returns NoDataError if the connection was successfully initialized, but not all 40 bits from
// the sensor is received
return "no data"
case UpdateError:
// DHT returns UpdateError if ReadMeasurements function is called before time specified in UpdatePolicy or
// less than 2 seconds after past measurement
return "cannot update now"
case UninitializedDataError:
// DHT returns UninitializedDataError if user attempts to access data before first measurement
return "no measurements done"
}
// should never be reached
return "unknown error"
}
// Update policy of the DHT device. UpdateTime cannot be shorter than 2 seconds. According to dht specification sensor
// will return undefined data if update requested less than 2 seconds before last usage
type UpdatePolicy struct {
UpdateTime time.Duration
UpdateAutomatically bool
}
var (
// timeout counter equal to number of ticks per 1 millisecond
timeout counter
)
func init() {
timeout = cyclesPerMillisecond()
}
func cyclesPerMillisecond() counter {
freq := machine.CPUFrequency()
freq /= 1000
return counter(freq)
}
+6
View File
@@ -0,0 +1,6 @@
// +build mimxrt1062 stm32f405 atsamd51 stm32f103xx k210 stm32f407
package dht // import "tinygo.org/x/drivers/dht"
// This file provides a definition of the counter for boards with frequency higher than 2^8 ticks per millisecond (>64MHz)
type counter uint32
+6
View File
@@ -0,0 +1,6 @@
// +build arduino atmega1284p nrf52840 digispark nrf52 arduino_nano nrf51 atsamd21 fe310 arduino_nano33 circuitplay_express arduino_mega2560
package dht // import "tinygo.org/x/drivers/dht"
// This file provides a definition of the counter for boards with frequency lower than 2^8 ticks per millisecond (<64MHz)
type counter uint16
+218
View File
@@ -0,0 +1,218 @@
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
package dht // import "tinygo.org/x/drivers/dht"
import (
"machine"
"time"
)
// DummyDevice provides a basic interface for DHT devices.
type DummyDevice interface {
ReadMeasurements() error
Measurements() (temperature int16, humidity uint16, err error)
Temperature() (int16, error)
TemperatureFloat(scale TemperatureScale) (float32, error)
Humidity() (uint16, error)
HumidityFloat() (float32, error)
}
// Basic implementation of the DummyDevice
// This implementation takes measurements from sensor only with ReadMeasurements function
// and does not provide a protection from too frequent calls for measurements.
// Since taking measurements from the sensor is time consuming procedure and blocks interrupts,
// user can avoid any hidden calls to the sensor.
type device struct {
pin machine.Pin
measurements DeviceType
initialized bool
temperature int16
humidity uint16
}
// ReadMeasurements reads data from the sensor.
// According to documentation pin should be always, but the t *device restores pin to the state before call.
func (t *device) ReadMeasurements() error {
// initial waiting
state := powerUp(t.pin)
defer t.pin.Set(state)
err := t.read()
if err == nil {
t.initialized = true
}
return err
}
// Getter for temperature. Temperature method returns temperature as it is sent by device.
// The temperature is measured temperature in Celsius multiplied by 10.
// If no successful measurements for this device was performed, returns UninitializedDataError.
func (t *device) Temperature() (int16, error) {
if !t.initialized {
return 0, UninitializedDataError
}
return t.temperature, nil
}
// Getter for temperature. TemperatureFloat returns temperature in a given scale.
// If no successful measurements for this device was performed, returns UninitializedDataError.
func (t *device) TemperatureFloat(scale TemperatureScale) (float32, error) {
if !t.initialized {
return 0, UninitializedDataError
}
return scale.convertToFloat(t.temperature), nil
}
// Getter for humidity. Humidity returns humidity as it is sent by device.
// The humidity is measured in percentages multiplied by 10.
// If no successful measurements for this device was performed, returns UninitializedDataError.
func (t *device) Humidity() (uint16, error) {
if !t.initialized {
return 0, UninitializedDataError
}
return t.humidity, nil
}
// Getter for humidity. HumidityFloat returns humidity in percentages.
// If no successful measurements for this device was performed, returns UninitializedDataError.
func (t *device) HumidityFloat() (float32, error) {
if !t.initialized {
return 0, UninitializedDataError
}
return float32(t.humidity) / 10., nil
}
// Perform initialization of the communication protocol.
// Device lowers the voltage on pin for startingLow=20ms and starts listening for response
// Section 5.2 in [1]
func initiateCommunication(p machine.Pin) {
// Send low signal to the device
p.Configure(machine.PinConfig{Mode: machine.PinOutput})
p.Low()
time.Sleep(startingLow)
// Set pin to high and wait for reply
p.High()
p.Configure(machine.PinConfig{Mode: machine.PinInput})
}
// Measurements returns both measurements: temperature and humidity as they sent by the device.
// If no successful measurements for this device was performed, returns UninitializedDataError.
func (t *device) Measurements() (temperature int16, humidity uint16, err error) {
if !t.initialized {
return 0, 0, UninitializedDataError
}
temperature = t.temperature
humidity = t.humidity
err = nil
return
}
// Main routine that performs communication with the sensor
func (t *device) read() error {
// initialize loop variables
// buffer for the data sent by the sensor. Sensor sends 40 bits = 5 bytes
bufferData := [5]byte{}
buf := bufferData[:]
// We perform measurements of the signal from the sensor by counting low and high cycles.
// The bit is determined by the relative length of the high signal to low signal.
// For 1, high signal will be longer than low, for 0---low is longer.
// See section 5.3 [1]
signalsData := [80]counter{}
signals := signalsData[:]
// Start communication protocol with sensor
initiateCommunication(t.pin)
// Wait for sensor's response and abort if sensor does not reply
err := waitForDataTransmission(t.pin)
if err != nil {
return err
}
// count low and high cycles for sensor's reply
receiveSignals(t.pin, signals)
// process received signals and store the result in the buffer. Abort if data transmission was interrupted and not
// all 40 bits were received
err = t.extractData(signals[:], buf)
if err != nil {
return err
}
// Compute checksum and compare it to the one in data. Abort if checksum is incorrect
if !isValid(buf[:]) {
return ChecksumError
}
// Extract temperature and humidity data from buffer
t.temperature, t.humidity = t.measurements.extractData(buf)
return nil
}
// receiveSignals counts number of low and high cycles. The execution is time critical, so the function disables
// interrupts
func receiveSignals(pin machine.Pin, result []counter) {
i := uint8(0)
machine.UART1.Interrupt.Disable()
defer machine.UART1.Interrupt.Enable()
for ; i < 40; i++ {
result[i*2] = expectChange(pin, false)
result[i*2+1] = expectChange(pin, true)
}
}
// extractData process signal counters and transforms them into bits.
// if any of the bits were not received (timed-out), returns NoDataError
func (t *device) extractData(signals []counter, buf []uint8) error {
for i := uint8(0); i < 40; i++ {
lowCycle := signals[i*2]
highCycle := signals[i*2+1]
if lowCycle == timeout || highCycle == timeout {
return NoDataError
}
byteN := i >> 3
buf[byteN] <<= 1
if highCycle > lowCycle {
buf[byteN] |= 1
}
}
return nil
}
// waitForDataTransmission waits for reply from the sensor.
// If no reply received, returns NoSignalError.
// For more details, see section 5.2 in [1]
func waitForDataTransmission(p machine.Pin) error {
// wait for thermometer to pull down
if expectChange(p, true) == timeout {
return NoSignalError
}
//wait for thermometer to pull up
if expectChange(p, false) == timeout {
return NoSignalError
}
// wait for thermometer to pull down and start sending the data
if expectChange(p, true) == timeout {
return NoSignalError
}
return nil
}
// Constructor function for a DummyDevice implementation.
// This device provides full control to the user.
// It does not do any hidden measurements calls and does not check
// for 2 seconds delay between measurements.
func NewDummyDevice(pin machine.Pin, deviceType DeviceType) DummyDevice {
pin.High()
return &device{
pin: pin,
measurements: deviceType,
initialized: false,
temperature: 0,
humidity: 0,
}
}
+154
View File
@@ -0,0 +1,154 @@
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
//
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
package dht // import "tinygo.org/x/drivers/dht"
import (
"machine"
"time"
)
// Device interface provides main functionality of the DHTXX sensors.
type Device interface {
DummyDevice
Configure(policy UpdatePolicy)
}
// managedDevice struct provides time control and optional automatic data retrieval from the sensor.
// It delegates all the functionality to device
type managedDevice struct {
t device
lastUpdate time.Time
policy UpdatePolicy
}
// Measurements returns both measurements: temperature and humidity as they sent by the device.
// Depending on the UpdatePolicy of the device may update cached measurements.
func (m *managedDevice) Measurements() (temperature int16, humidity uint16, err error) {
err = m.checkForUpdateOnDataRequest()
if err != nil {
return 0, 0, err
}
return m.t.Measurements()
}
// Getter for temperature. Temperature method returns temperature as it is sent by device.
// The temperature is measured temperature in Celsius multiplied by 10.
// Depending on the UpdatePolicy of the device may update cached measurements.
func (m *managedDevice) Temperature() (temp int16, err error) {
err = m.checkForUpdateOnDataRequest()
if err != nil {
return 0, err
}
temp, err = m.t.Temperature()
return
}
func (m *managedDevice) checkForUpdateOnDataRequest() (err error) {
// update if necessary
if m.policy.UpdateAutomatically {
err = m.ReadMeasurements()
}
// ignore error if the data was updated recently
// interface comparison does not work in tinygo. Therefore need to cast to explicit type
if code, ok := err.(ErrorCode); ok && code == UpdateError {
err = nil
}
// add error if the data is not initialized
if !m.t.initialized {
err = UninitializedDataError
}
return err
}
// Getter for temperature. TemperatureFloat returns temperature in a given scale.
// Depending on the UpdatePolicy of the device may update cached measurements.
func (m *managedDevice) TemperatureFloat(scale TemperatureScale) (float32, error) {
err := m.checkForUpdateOnDataRequest()
if err != nil {
return 0, err
}
return m.t.TemperatureFloat(scale)
}
// Getter for humidity. Humidity returns humidity as it is sent by device.
// The humidity is measured in percentages multiplied by 10.
// Depending on the UpdatePolicy of the device may update cached measurements.
func (m *managedDevice) Humidity() (hum uint16, err error) {
err = m.checkForUpdateOnDataRequest()
if err != nil {
return 0, err
}
return m.t.Humidity()
}
// Getter for humidity. HumidityFloat returns humidity in percentages.
// Depending on the UpdatePolicy of the device may update cached measurements.
func (m *managedDevice) HumidityFloat() (float32, error) {
err := m.checkForUpdateOnDataRequest()
if err != nil {
return 0, err
}
return m.t.HumidityFloat()
}
// ReadMeasurements reads data from the sensor.
// The function will return UpdateError if it is called more frequently than specified in UpdatePolicy
func (m *managedDevice) ReadMeasurements() (err error) {
timestamp := time.Now()
if !m.t.initialized || timestamp.Sub(m.lastUpdate) > m.policy.UpdateTime {
err = m.t.ReadMeasurements()
} else {
err = UpdateError
}
if err == nil {
m.lastUpdate = timestamp
}
return
}
// Configure configures UpdatePolicy for Device.
// Configure checks for policy.UpdateTime and prevent from updating more frequently than specified in [1][2]
// to prevent undefined behaviour of the sensor.
func (m *managedDevice) Configure(policy UpdatePolicy) {
if policy.UpdateAutomatically && policy.UpdateTime < time.Second*2 {
policy.UpdateTime = time.Second * 2
}
m.policy = policy
}
// Constructor of the Device implementation.
// This implementation updates data every 2 seconds during data access.
func New(pin machine.Pin, deviceType DeviceType) Device {
pin.High()
return &managedDevice{
t: device{
pin: pin,
measurements: deviceType,
initialized: false,
},
lastUpdate: time.Time{},
policy: UpdatePolicy{
UpdateTime: time.Second * 2,
UpdateAutomatically: true,
},
}
}
// Constructor of the Device implementation with given UpdatePolicy
func NewWithPolicy(pin machine.Pin, deviceType DeviceType, updatePolicy UpdatePolicy) Device {
pin.High()
result := &managedDevice{
t: device{
pin: pin,
measurements: deviceType,
initialized: false,
},
lastUpdate: time.Time{},
}
result.Configure(updatePolicy)
return result
}
+34
View File
@@ -0,0 +1,34 @@
package dht // import "tinygo.org/x/drivers/dht"
import (
"machine"
"time"
)
// Check if the pin is disabled
func powerUp(p machine.Pin) bool {
state := p.Get()
if !state {
p.High()
time.Sleep(startTimeout)
}
return state
}
func expectChange(p machine.Pin, oldState bool) counter {
cnt := counter(0)
for ; p.Get() == oldState && cnt != timeout; cnt++ {
}
return cnt
}
func checksum(buf []uint8) uint8 {
return buf[4]
}
func computeChecksum(buf []uint8) uint8 {
return buf[0] + buf[1] + buf[2] + buf[3]
}
func isValid(buf []uint8) bool {
return checksum(buf) == computeChecksum(buf)
}
+5 -5
View File
@@ -9,18 +9,18 @@ import (
"errors"
"time"
"machine"
"tinygo.org/x/drivers"
)
// Device wraps an I2C connection to a DS1307 device.
type Device struct {
bus machine.I2C
bus drivers.I2C
Address uint8
AddressSRAM uint8
}
// New creates a new DS1307 connection. I2C bus must be already configured.
func New(bus machine.I2C) Device {
func New(bus drivers.I2C) Device {
return Device{bus: bus,
Address: uint8(I2CAddress),
AddressSRAM: SRAMBeginAddres,
@@ -42,8 +42,8 @@ func (d *Device) SetTime(t time.Time) error {
return err
}
// Time returns the time and date
func (d *Device) Time() (time.Time, error) {
// ReadTime returns the date and time
func (d *Device) ReadTime() (time.Time, error) {
data := make([]byte, 8)
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
if err != nil {
+4 -3
View File
@@ -5,15 +5,16 @@
package ds3231 // import "tinygo.org/x/drivers/ds3231"
import (
"machine"
"time"
"tinygo.org/x/drivers"
)
type Mode uint8
// Device wraps an I2C connection to a DS3231 device.
type Device struct {
bus machine.I2C
bus drivers.I2C
Address uint16
}
@@ -21,7 +22,7 @@ type Device struct {
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2C) Device {
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: Address,
+2 -4
View File
@@ -18,8 +18,7 @@ type DualDevice struct {
devices [2]Device
}
// New returns a new easystepper driver given 4 pins numbers (not pin object),
// number of steps and rpm
// New returns a new easystepper driver given 4 pins, number of steps and rpm
func New(pin1, pin2, pin3, pin4 machine.Pin, steps int32, rpm int32) Device {
return Device{
pins: [4]machine.Pin{pin1, pin2, pin3, pin4},
@@ -34,8 +33,7 @@ func (d *Device) Configure() {
}
}
// NewDual returns a new dual easystepper driver given 8 pins numbers (not pin object),
// number of steps and rpm
// NewDual returns a new dual easystepper driver given 8 pins, number of steps and rpm
func NewDual(pin1, pin2, pin3, pin4, pin5, pin6, pin7, pin8 machine.Pin, steps int32, rpm int32) DualDevice {
var dual DualDevice
dual.devices[0] = Device{
+1 -1
View File
@@ -10,7 +10,7 @@ import (
var (
i2c = &machine.I2C0
sensor = adt7410.New(i2c, 0)
sensor = adt7410.New(i2c)
)
func main() {
File diff suppressed because one or more lines are too long
+56
View File
@@ -0,0 +1,56 @@
package main
import (
"image/color"
"machine"
"tinygo.org/x/drivers/st7735"
"tinygo.org/x/drivers/amg88xx"
)
func main() {
machine.SPI1.Configure(machine.SPIConfig{
SCK: machine.SPI1_SCK_PIN,
SDO: machine.SPI1_SDO_PIN,
SDI: machine.SPI1_SDI_PIN,
Frequency: 8000000,
})
machine.I2C0.Configure(machine.I2CConfig{SCL: machine.SCL_PIN, SDA: machine.SDA_PIN})
display := st7735.New(machine.SPI1, machine.TFT_RST, machine.TFT_DC, machine.TFT_CS, machine.TFT_LITE)
display.Configure(st7735.Config{
Rotation: st7735.ROTATION_90,
})
display.FillScreen(color.RGBA{0, 0, 0, 255})
camera := amg88xx.New(machine.I2C0)
camera.Configure(amg88xx.Config{})
var data [64]int16
var value int16
for {
// get the values of the sensor in millicelsius
camera.ReadPixels(&data)
for j := int16(0); j < 8; j++ {
for i := int16(0); i < 8; i++ {
value = data[63-(i+j*8)]
// treat anything below 18°C as 18°C
if value < 18000 {
value = 0
} else {
value = (value - 18000) / 36
// our color array only have 433 values, avoid getting a value that doesn't exist
if value > 432 {
value = 432
}
}
// show the image on the PyBadge's display
display.FillRectangle(16+i*16, j*16, 16, 16, colors[value])
}
}
}
}
+46
View File
@@ -0,0 +1,46 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/bmi160"
)
func main() {
time.Sleep(5 * time.Second)
machine.SPI0.Configure(machine.SPIConfig{})
sensor := bmi160.NewSPI(machine.A5, machine.SPI0)
sensor.Configure()
if !sensor.Connected() {
println("BMI160 not connected")
return
}
for {
time.Sleep(time.Second)
t, err := sensor.ReadTemperature()
if err != nil {
println("Error reading temperature", err)
continue
}
fmt.Printf("Temperature: %.2f °C\n", float32(t)/1000)
accelX, accelY, accelZ, err := sensor.ReadAcceleration()
if err != nil {
println("Error reading acceleration", err)
continue
}
fmt.Printf("Acceleration: %.2fg %.2fg %.2fg\n", float32(accelX)/1e6, float32(accelY)/1e6, float32(accelZ)/1e6)
gyroX, gyroY, gyroZ, err := sensor.ReadRotation()
if err != nil {
println("Error reading rotation", err)
continue
}
fmt.Printf("Rotation: %.2f°/s %.2f°/s %.2f°/s\n", float32(gyroX)/1e6, float32(gyroY)/1e6, float32(gyroZ)/1e6)
}
}
+44
View File
@@ -0,0 +1,44 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/bmp280"
)
func main() {
time.Sleep(5 * time.Second)
machine.I2C0.Configure(machine.I2CConfig{})
sensor := bmp280.New(machine.I2C0)
sensor.Configure(bmp280.STANDBY_125MS, bmp280.FILTER_4X, bmp280.SAMPLING_16X, bmp280.SAMPLING_16X, bmp280.MODE_FORCED)
connected := sensor.Connected()
if !connected {
println("\nBMP280 Sensor not detected\n")
return
}
println("\nBMP280 Sensor detected\n")
println("Calibration:")
sensor.PrintCali()
for {
t, err := sensor.ReadTemperature()
if err != nil {
println("Error reading temperature")
}
// Temperature in degrees Celsius
fmt.Printf("Temperature: %.2f °C\n", float32(t)/1000)
p, err := sensor.ReadPressure()
if err != nil {
println("Error reading pressure")
}
// Pressure in hectoPascal
fmt.Printf("Pressure: %.2f hPa\n", float32(p)/100000)
time.Sleep(5 * time.Second)
}
}
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/bmp388"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
sensor := bmp388.New(machine.I2C0)
if !sensor.Connected() {
println("Uh oh, BMP388 not detected")
return
}
// The accuracy of the sensor can be increased, at the cost of a slower output rate. Table 9 in Section 3.5 of the
// datasheet has recommended settings for common use cases. If increasing the sampling rate, the output data rate
// (ODR) will likely have to be decreased. Configure() will return an error if there's a problem with the
// configuration settings - keep decreasing the ODR and cycling the power to the sensor until it is happy.
err := sensor.Configure(bmp388.Config{
Pressure: bmp388.Sampling8X,
Temperature: bmp388.Sampling2X,
ODR: bmp388.Odr25,
IIR: bmp388.Coeff0,
Mode: bmp388.Normal,
})
// This is also fine
// err := sensor.Configure(bmp388.BMP388Config{})
if err != nil {
println(err)
}
for {
temp, err := sensor.ReadTemperature() // returns the temperature in centicelsius
press, err := sensor.ReadPressure() // returns the pressure in centipascals
if err != nil {
println(err)
} else {
println("Temperature: " + strconv.FormatInt(int64(temp), 10) + " cC")
println("Pressure: " + strconv.FormatInt(int64(press), 10) + " cPa\n")
}
time.Sleep(time.Second)
}
}
+23
View File
@@ -0,0 +1,23 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/dht"
)
func main() {
pin := machine.D6
dhtSensor := dht.New(pin, dht.DHT11)
for {
temp, hum, err := dhtSensor.Measurements()
if err != nil {
fmt.Printf("Temperature: %02d.%d°C, Humidity: %02d.%d%%\n", temp/10, temp%10, hum/10, hum%10)
} else {
fmt.Printf("Could not take measurements from the sensor: %s\n", err.Error())
}
// Measurements cannot be updated only 2 seconds. More frequent calls will return the same value
time.Sleep(time.Second * 2)
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ func main() {
rtc.SetTime(time.Date(2019, 5, 15, 20, 34, 12, 0, time.UTC))
for {
t, err := rtc.Time()
t, err := rtc.ReadTime()
if err != nil {
println("Error reading date:", err)
break
+259
View File
@@ -0,0 +1,259 @@
package console_example
import (
"fmt"
"io"
"machine"
"os"
"strconv"
"strings"
"tinygo.org/x/drivers/flash"
)
const consoleBufLen = 64
const storageBufLen = 512
var (
debug = false
input [consoleBufLen]byte
store [storageBufLen]byte
console = machine.UART0
dev *flash.Device
commands map[string]cmdfunc = map[string]cmdfunc{
"": cmdfunc(noop),
"erase": cmdfunc(erase),
"lsblk": cmdfunc(lsblk),
"write": cmdfunc(write),
"xxd": cmdfunc(xxd),
}
)
type cmdfunc func(argv []string)
const (
StateInput = iota
StateEscape
StateEscBrc
StateCSI
)
func RunFor(device *flash.Device) {
dev = device
dev.Configure(&flash.DeviceConfig{
Identifier: flash.DefaultDeviceIdentifier,
})
prompt()
var state = StateInput
for i := 0; ; {
if console.Buffered() > 0 {
data, _ := console.ReadByte()
if debug {
fmt.Printf("\rdata: %x\r\n\r", data)
prompt()
console.Write(input[:i])
}
switch state {
case StateInput:
switch data {
case 0x8:
fallthrough
case 0x7f: // this is probably wrong... works on my machine tho :)
// backspace
if i > 0 {
i -= 1
console.Write([]byte{0x8, 0x20, 0x8})
}
case 13:
// return key
console.Write([]byte("\r\n"))
runCommand(string(input[:i]))
prompt()
i = 0
continue
case 27:
// escape
state = StateEscape
default:
// anything else, just echo the character if it is printable
if strconv.IsPrint(rune(data)) {
if i < (consoleBufLen - 1) {
console.WriteByte(data)
input[i] = data
i++
}
}
}
case StateEscape:
switch data {
case 0x5b:
state = StateEscBrc
default:
state = StateInput
}
default:
// TODO: handle escape sequences
state = StateInput
}
}
}
}
func runCommand(line string) {
argv := strings.SplitN(strings.TrimSpace(line), " ", -1)
cmd := argv[0]
cmdfn, ok := commands[cmd]
if !ok {
println("unknown command: " + line)
return
}
cmdfn(argv)
}
func noop(argv []string) {}
func lsblk(argv []string) {
attrs := dev.Attrs()
status1, _ := dev.ReadStatus()
status2, _ := dev.ReadStatus2()
serialNumber1, _ := dev.ReadSerialNumber()
fmt.Printf(
"\n-------------------------------------\r\n"+
" Device Information: \r\n"+
"-------------------------------------\r\n"+
" JEDEC ID: %v\r\n"+
" Serial: %v\r\n"+
" Status 1: %02x\r\n"+
" Status 2: %02x\r\n"+
" \r\n"+
" Max clock speed (MHz): %d\r\n"+
" Has Sector Protection: %t\r\n"+
" Supports Fast Reads: %t\r\n"+
" Supports QSPI Reads: %t\r\n"+
" Supports QSPI Write: %t\r\n"+
" Write Status Split: %t\r\n"+
" Single Status Byte: %t\r\n"+
"-------------------------------------\r\n\r\n",
attrs.JedecID,
serialNumber1,
status1,
status2,
attrs.MaxClockSpeedMHz,
attrs.HasSectorProtection,
attrs.SupportsFastRead,
attrs.SupportsQSPI,
attrs.SupportsQSPIWrites,
attrs.WriteStatusSplit,
attrs.SingleStatusByte,
)
}
func erase(argv []string) {
if len(argv) < 3 {
println("usage: erase <chip|block|sector> <bytes>")
return
}
var err error
var addr uint64 = 0x0
if addr, err = strconv.ParseUint(argv[2], 16, 32); err != nil {
println("Invalid address: " + err.Error() + "\r\n")
return
}
if argv[1] == "block" {
if err = dev.EraseBlock(uint32(addr)); err != nil {
println("Block erase error: " + err.Error() + "\r\n")
}
} else if argv[1] == "sector" {
if err = dev.EraseSector(uint32(addr)); err != nil {
println("Sector erase error: " + err.Error() + "\r\n")
}
} else if argv[1] == "chip" {
if err = dev.EraseAll(); err != nil {
println("Chip erase error: " + err.Error() + "\r\n")
}
} else {
println("usage: erase <chip|block|sector> <bytes>")
}
}
func write(argv []string) {
if len(argv) < 3 {
println("usage: write <hex offset> <bytes>")
}
var err error
var addr uint64 = 0x0
if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil {
println("Invalid address: " + err.Error() + "\r\n")
return
}
buf := []byte(argv[2])
if _, err = dev.WriteAt(buf, int64(addr)); err != nil {
println("Write error: " + err.Error() + "\r\n")
}
}
func xxd(argv []string) {
var err error
var addr uint64 = 0x0
var size int = 64
switch len(argv) {
case 3:
if size, err = strconv.Atoi(argv[2]); err != nil {
println("Invalid size argument: " + err.Error() + "\r\n")
return
}
if size > storageBufLen || size < 1 {
fmt.Printf("Size of hexdump must be greater than 0 and less than %d\r\n", storageBufLen)
return
}
fallthrough
case 2:
if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil {
println("Invalid address: " + err.Error() + "\r\n")
return
}
fallthrough
case 1:
// no args supplied, so nothing to do here, just use the defaults
default:
println("usage: xxd <hex address, ex: 0xA0> <size of hexdump in bytes>\r\n")
return
}
buf := store[0:size]
dev.ReadAt(buf, int64(addr))
xxdfprint(os.Stdout, uint32(addr), buf)
}
func xxdfprint(w io.Writer, offset uint32, b []byte) {
var l int
var buf16 = make([]byte, 16)
for i, c := 0, len(b); i < c; i += 16 {
l = i + 16
if l >= c {
l = c
}
fmt.Fprintf(w, "%08x: % x ", offset+uint32(i), b[i:l])
for j, n := 0, l-i; j < 16; j++ {
if j >= n || !strconv.IsPrint(rune(b[i+j])) {
buf16[j] = '.'
} else {
buf16[j] = b[i+j]
}
}
console.Write(buf16)
println()
}
}
func prompt() {
print("==> ")
}
+21
View File
@@ -0,0 +1,21 @@
package main
import (
"machine"
"tinygo.org/x/drivers/examples/flash/console"
"tinygo.org/x/drivers/flash"
)
func main() {
console_example.RunFor(
flash.NewQSPI(
machine.QSPI_CS,
machine.QSPI_SCK,
machine.QSPI_DATA0,
machine.QSPI_DATA1,
machine.QSPI_DATA2,
machine.QSPI_DATA3,
),
)
}
+20
View File
@@ -0,0 +1,20 @@
package main
import (
"machine"
console_example "tinygo.org/x/drivers/examples/flash/console"
"tinygo.org/x/drivers/flash"
)
func main() {
console_example.RunFor(
flash.NewSPI(
&machine.SPI1,
machine.SPI1_SDO_PIN,
machine.SPI1_SDI_PIN,
machine.SPI1_SCK_PIN,
machine.SPI1_CS_PIN,
),
)
}
+27 -6
View File
@@ -1,8 +1,8 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/gps"
)
@@ -11,19 +11,40 @@ func main() {
println("GPS I2C Example")
machine.I2C0.Configure(machine.I2CConfig{})
ublox := gps.NewI2C(&machine.I2C0)
parser := gps.Parser(ublox)
parser := gps.NewParser()
var fix gps.Fix
for {
fix = parser.NextFix()
s, err := ublox.NextSentence()
if err != nil {
println(err)
continue
}
fix, err = parser.Parse(s)
if err != nil {
println(err)
continue
}
if fix.Valid {
print(fix.Time.Format("15:04:05"))
print(", lat=", fmt.Sprintf("%f", fix.Latitude))
print(", long=", fmt.Sprintf("%f", fix.Longitude))
print(", altitude:=", fix.Altitude)
print(", lat=")
print(fix.Latitude)
print(", long=")
print(fix.Longitude)
print(", altitude=", fix.Altitude)
print(", satellites=", fix.Satellites)
if fix.Speed != 0 {
print(", speed=")
print(fix.Speed)
}
if fix.Heading != 0 {
print(", heading=")
print(fix.Heading)
}
println()
} else {
println("No fix")
}
time.Sleep(200 * time.Millisecond)
}
}
+27 -6
View File
@@ -1,8 +1,8 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/gps"
)
@@ -11,19 +11,40 @@ func main() {
println("GPS UART Example")
machine.UART1.Configure(machine.UARTConfig{BaudRate: 9600})
ublox := gps.NewUART(&machine.UART1)
parser := gps.Parser(ublox)
parser := gps.NewParser()
var fix gps.Fix
for {
fix = parser.NextFix()
s, err := ublox.NextSentence()
if err != nil {
println(err)
continue
}
fix, err = parser.Parse(s)
if err != nil {
println(err)
continue
}
if fix.Valid {
print(fix.Time.Format("15:04:05"))
print(", lat=", fmt.Sprintf("%f", fix.Latitude))
print(", long=", fmt.Sprintf("%f", fix.Longitude))
print(", altitude:=", fix.Altitude)
print(", lat=")
print(fix.Latitude)
print(", long=")
print(fix.Longitude)
print(", altitude=", fix.Altitude)
print(", satellites=", fix.Satellites)
if fix.Speed != 0 {
print(", speed=")
print(fix.Speed)
}
if fix.Heading != 0 {
print(", heading=")
print(fix.Heading)
}
println()
} else {
println("No fix")
}
time.Sleep(200 * time.Millisecond)
}
}
+20
View File
@@ -0,0 +1,20 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/hcsr04"
)
func main() {
sensor := hcsr04.New(machine.D10, machine.D9)
sensor.Configure()
println("Ultrasonic starts")
for {
println("Distance:", sensor.ReadDistance(), "mm")
time.Sleep(100 * time.Millisecond)
}
}
+60
View File
@@ -0,0 +1,60 @@
package main
import (
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/hd44780i2c"
)
func main() {
// Note: most HD44780 LCD modules requires 5V power, however some variations
// use 3.3V (and may be damaged by 5V).
machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
lcd := hd44780i2c.New(machine.I2C0, 0x27) // some modules have address 0x3F
lcd.Configure(hd44780i2c.Config{
Width: 16, // required
Height: 2, // required
CursorOn: true,
CursorBlink: true,
})
lcd.Print([]byte(" TinyGo\n LCD Test "))
// CGRAM address 0x0-0x7 can be used to store 8 custom characters
lcd.CreateCharacter(0x0, []byte{0x00, 0x11, 0x0E, 0x1F, 0x15, 0x1F, 0x1F, 0x1F})
lcd.Print([]byte{0x0})
// You can use https://maxpromer.github.io/LCD-Character-Creator/
// to crete your own characters.
time.Sleep(time.Millisecond * 7000)
for i := 0; i < 5; i++ {
lcd.BacklightOn(false)
time.Sleep(time.Millisecond * 250)
lcd.BacklightOn(true)
time.Sleep(time.Millisecond * 250)
}
lcd.CursorOn(false)
lcd.CursorBlink(false)
i := 0
for {
lcd.ClearDisplay()
lcd.SetCursor(2, 1)
lcd.Print([]byte(strconv.FormatInt(int64(i), 10)))
i++
time.Sleep(time.Millisecond * 100)
}
}
+29
View File
@@ -0,0 +1,29 @@
// +build atsamd21
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewSPI(
machine.SPI0,
machine.D0,
machine.D1,
machine.D2,
)
backlight = machine.D3
)
func init() {
machine.SPI0.Configure(machine.SPIConfig{
SCK: machine.SPI0_SCK_PIN,
SDO: machine.SPI0_SDO_PIN,
SDI: machine.SPI0_SDI_PIN,
Frequency: 24000000,
})
}
+2 -11
View File
@@ -9,15 +9,6 @@ import (
)
var (
display = ili9341.NewParallel(
machine.LCD_DATA0,
machine.TFT_WR,
machine.TFT_DC,
machine.TFT_CS,
machine.TFT_RESET,
machine.TFT_RD,
)
black = color.RGBA{0, 0, 0, 255}
white = color.RGBA{255, 255, 255, 255}
red = color.RGBA{255, 0, 0, 255}
@@ -27,13 +18,13 @@ var (
func main() {
machine.TFT_BACKLIGHT.Configure(machine.PinConfig{machine.PinOutput})
backlight.Configure(machine.PinConfig{machine.PinOutput})
display.Configure(ili9341.Config{})
width, height := display.Size()
display.FillScreen(black)
machine.TFT_BACKLIGHT.High()
backlight.High()
display.FillRectangle(0, 0, width/2, height/2, white)
display.FillRectangle(width/2, 0, width/2, height/2, red)
+22
View File
@@ -0,0 +1,22 @@
// +build pyportal
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewParallel(
machine.LCD_DATA0,
machine.TFT_WR,
machine.TFT_DC,
machine.TFT_CS,
machine.TFT_RESET,
machine.TFT_RD,
)
backlight = machine.TFT_BACKLIGHT
)
+29
View File
@@ -0,0 +1,29 @@
// +build wioterminal
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewSPI(
machine.SPI3,
machine.LCD_DC,
machine.LCD_SS_PIN,
machine.LCD_RESET,
)
backlight = machine.LCD_BACKLIGHT
)
func init() {
machine.SPI3.Configure(machine.SPIConfig{
SCK: machine.LCD_SCK_PIN,
SDO: machine.LCD_SDO_PIN,
SDI: machine.LCD_SDI_PIN,
Frequency: 40000000,
})
}
+2 -11
View File
@@ -25,15 +25,6 @@ const (
)
var (
display = ili9341.NewParallel(
machine.LCD_DATA0,
machine.TFT_WR,
machine.TFT_DC,
machine.TFT_CS,
machine.TFT_RESET,
machine.TFT_RD,
)
frameBuffer = [(graphics.BALLHEIGHT + 8) * (graphics.BALLWIDTH + 8)]uint16{}
startTime int64
@@ -56,7 +47,7 @@ var (
func main() {
// configure backlight
machine.TFT_BACKLIGHT.Configure(machine.PinConfig{machine.PinOutput})
backlight.Configure(machine.PinConfig{machine.PinOutput})
// configure display
display.Configure(ili9341.Config{})
@@ -64,7 +55,7 @@ func main() {
width, height := display.Size()
println(width, height)
machine.TFT_BACKLIGHT.High()
backlight.High()
display.SetRotation(ili9341.Rotation270)
DrawBackground()
@@ -0,0 +1,22 @@
// +build pyportal
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewParallel(
machine.LCD_DATA0,
machine.TFT_WR,
machine.TFT_DC,
machine.TFT_CS,
machine.TFT_RESET,
machine.TFT_RD,
)
backlight = machine.TFT_BACKLIGHT
)
@@ -0,0 +1,29 @@
// +build wioterminal
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewSPI(
machine.SPI3,
machine.LCD_DC,
machine.LCD_SS_PIN,
machine.LCD_RESET,
)
backlight = machine.LCD_BACKLIGHT
)
func init() {
machine.SPI3.Configure(machine.SPIConfig{
SCK: machine.LCD_SCK_PIN,
SDO: machine.LCD_SDO_PIN,
SDI: machine.LCD_SDI_PIN,
Frequency: 40000000,
})
}
+29
View File
@@ -0,0 +1,29 @@
// +build atsamd21
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewSPI(
machine.SPI0,
machine.D0,
machine.D1,
machine.D2,
)
backlight = machine.D3
)
func init() {
machine.SPI0.Configure(machine.SPIConfig{
SCK: machine.SPI0_SCK_PIN,
SDO: machine.SPI0_SDO_PIN,
SDI: machine.SPI0_SDI_PIN,
Frequency: 24000000,
})
}
+2 -11
View File
@@ -9,15 +9,6 @@ import (
)
var (
display = ili9341.NewParallel(
machine.LCD_DATA0,
machine.TFT_WR,
machine.TFT_DC,
machine.TFT_CS,
machine.TFT_RESET,
machine.TFT_RD,
)
red = color.RGBA{255, 0, 0, 255}
blue = color.RGBA{0, 0, 255, 255}
green = color.RGBA{0, 255, 0, 255}
@@ -27,13 +18,13 @@ var (
func main() {
machine.TFT_BACKLIGHT.Configure(machine.PinConfig{machine.PinOutput})
backlight.Configure(machine.PinConfig{machine.PinOutput})
display.Configure(ili9341.Config{})
width, height := display.Size()
display.FillScreen(black)
machine.TFT_BACKLIGHT.High()
backlight.High()
display.FillRectangle(0, 0, width/2, height/2, white)
display.FillRectangle(width/2, 0, width/2, height/2, red)
+22
View File
@@ -0,0 +1,22 @@
// +build pyportal
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewParallel(
machine.LCD_DATA0,
machine.TFT_WR,
machine.TFT_DC,
machine.TFT_CS,
machine.TFT_RESET,
machine.TFT_RD,
)
backlight = machine.TFT_BACKLIGHT
)
+29
View File
@@ -0,0 +1,29 @@
// +build wioterminal
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewSPI(
machine.SPI3,
machine.LCD_DC,
machine.LCD_SS_PIN,
machine.LCD_RESET,
)
backlight = machine.LCD_BACKLIGHT
)
func init() {
machine.SPI3.Configure(machine.SPIConfig{
SCK: machine.LCD_SCK_PIN,
SDO: machine.LCD_SDO_PIN,
SDI: machine.LCD_SDI_PIN,
Frequency: 40000000,
})
}
+29
View File
@@ -0,0 +1,29 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/lis2mdl"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
compass := lis2mdl.New(machine.I2C0)
if !compass.Connected() {
for {
println("LIS2MDL not connected!")
time.Sleep(1 * time.Second)
}
}
compass.Configure(lis2mdl.Configuration{}) //default settings
for {
heading := compass.ReadCompass()
println("Heading:", heading)
time.Sleep(time.Millisecond * 100)
}
}
+39
View File
@@ -0,0 +1,39 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/lsm303agr"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
accel_mag := lsm303agr.New(machine.I2C0)
if !accel_mag.Connected() {
println("LSM303AGR/MAG not connected!")
return
}
accel_mag.Configure(lsm303agr.Configuration{}) //default settings
for {
accel_x, accel_y, accel_z := accel_mag.ReadAcceleration()
pitch, roll := accel_mag.ReadPitchRoll()
mag_x, mag_y, mag_z := accel_mag.ReadMagneticField()
heading := accel_mag.ReadCompass()
temp, _ := accel_mag.ReadTemperature()
println("ACCEL_X:", accel_x, " ACCEL_Y:", accel_y, " ACCEL_Z:", accel_z)
println("MAG_X:", mag_x, " MAG_Y:", mag_y, " MAG_Z:", mag_z)
println("Pitch:", pitch, " Roll:", roll)
println("Heading:", heading)
println("Temperature:", temp/1000)
println("\n")
time.Sleep(time.Millisecond * 100)
}
}
+45
View File
@@ -0,0 +1,45 @@
// This example demonstrates putting several mcp23017 devices together into
// a single virtual I/O array.
package main
import (
"machine"
"tinygo.org/x/drivers/mcp23017"
)
func main() {
err := machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
if err != nil {
panic(err)
}
// Assume the devices are at addresses 0x20, 0x21
dev, err := mcp23017.NewI2CDevices(machine.I2C0, 0x20, 0x21)
if err != nil {
panic(err)
}
// Configure pin 0 for input and all the others for output.
if err := dev.SetModes([]mcp23017.PinMode{
mcp23017.Input | mcp23017.Pullup,
mcp23017.Output,
}); err != nil {
panic(err)
}
input := dev.Pin(0)
// Make a mask that represents all the output pins.
// Note that this leverages the driver behaviour which replicates the highest bit in
// the last slice element (1 in this case) to all other pins
outputMask := mcp23017.PinSlice{^mcp23017.Pins(1 << 0)} // All except pin 0
inputVal, err := input.Get()
if err != nil {
panic(err)
}
println("input value: ", inputVal)
// Set the values of all the output pins.
err = dev.SetPins(mcp23017.PinSlice{0b1011011_01101110, 0b11111101_11100110}, outputMask)
if err != nil {
panic(err)
}
}
+39
View File
@@ -0,0 +1,39 @@
package main
import (
"machine"
"tinygo.org/x/drivers/mcp23017"
)
func main() {
err := machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
if err != nil {
panic(err)
}
dev, err := mcp23017.NewI2C(machine.I2C0, 0x20)
if err != nil {
panic(err)
}
// Configure pin 0 for input and all the others for output.
if err := dev.SetModes([]mcp23017.PinMode{
mcp23017.Input | mcp23017.Pullup,
mcp23017.Output,
}); err != nil {
panic(err)
}
input := dev.Pin(0)
outputMask := ^mcp23017.Pins(1 << 0) // All except pin 0
inputVal, err := input.Get()
if err != nil {
panic(err)
}
println("input value: ", inputVal)
// Set the values of all the output pins.
err = dev.SetPins(0b1011011_01101110, outputMask)
if err != nil {
panic(err)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
package ssd1331
package main
import (
"machine"
+36
View File
@@ -0,0 +1,36 @@
package main
import (
"machine"
"image/color"
"tinygo.org/x/drivers/ssd1351"
)
func main() {
machine.SPI1.Configure(machine.SPIConfig{
Frequency: 2000000,
})
display := ssd1351.New(machine.SPI1, machine.D18, machine.D17, machine.D16, machine.D4, machine.D19)
display.Configure(ssd1351.Config{
Width: 96,
Height: 96,
ColumnOffset: 16,
})
width, height := display.Size()
white := color.RGBA{255, 255, 255, 255}
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
green := color.RGBA{0, 255, 0, 255}
display.FillRectangle(0, 0, width, height/4, white)
display.FillRectangle(0, height/4, width, height/4, red)
display.FillRectangle(0, height/2, width, height/4, green)
display.FillRectangle(0, 3*height/4, width, height/4, blue)
display.Display()
}
+28 -3
View File
@@ -9,12 +9,37 @@ import (
)
func main() {
// Example configuration for Adafruit Clue
// machine.SPI1.Configure(machine.SPIConfig{
// Frequency: 8000000,
// SCK: machine.TFT_SCK,
// SDO: machine.TFT_SDO,
// SDI: machine.TFT_SDO,
// Mode: 0,
// })
// display := st7789.New(machine.SPI1,
// machine.TFT_RESET,
// machine.TFT_DC,
// machine.TFT_CS,
// machine.TFT_LITE)
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
Mode: 3,
Mode: 0,
})
display := st7789.New(machine.SPI0,
machine.P6, // TFT_RESET
machine.P7, // TFT_DC
machine.P8, // TFT_CS
machine.P9) // TFT_LITE
display.Configure(st7789.Config{
Rotation: st7789.NO_ROTATION,
RowOffset: 80,
FrameRate: st7789.FRAMERATE_111,
VSyncLines: st7789.MAX_VSYNC_SCANLINES,
})
display := st7789.New(machine.SPI0, machine.P6, machine.P7, machine.P8)
display.Configure(st7789.Config{Rotation: st7789.NO_ROTATION})
width, height := display.Size()
+28
View File
@@ -0,0 +1,28 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/tmp102"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
thermo := tmp102.New(machine.I2C0)
thermo.Configure(tmp102.Config{})
for {
temp, _ := thermo.ReadTemperature()
print(fmt.Sprintf("%.2f°C\r\n", float32(temp)/1000.0))
time.Sleep(time.Millisecond * 1000)
}
}
+56
View File
@@ -0,0 +1,56 @@
package main
import (
"machine"
"image/color"
"time"
"tinygo.org/x/drivers/waveshare-epd/epd4in2"
)
var display epd4in2.Device
func main() {
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 8000000,
Mode: 0,
})
display = epd4in2.New(machine.SPI0, machine.P6, machine.P7, machine.P8, machine.P9)
display.Configure(epd4in2.Config{})
black := color.RGBA{1, 1, 1, 255}
display.ClearBuffer()
println("Clear the display")
display.ClearDisplay()
display.WaitUntilIdle()
println("Waiting for 2 seconds")
time.Sleep(2 * time.Second)
// Show a checkered board
for i := int16(0); i < 16; i++ {
for j := int16(0); j < 25; j++ {
if (i+j)%2 == 0 {
showRect(i*8, j*10, 8, 10, black)
}
}
}
println("Show checkered board")
display.Display()
display.WaitUntilIdle()
println("Waiting for 2 seconds")
time.Sleep(2 * time.Second)
println("You could remove power now")
}
func showRect(x int16, y int16, w int16, h int16, c color.RGBA) {
for i := x; i < x+w; i++ {
for j := y; j < y+h; j++ {
display.SetPixel(i, j, c)
}
}
}
+8 -11
View File
@@ -41,15 +41,7 @@ var (
spi = machine.NINA_SPI
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor = &wifinina.Device{
SPI: spi,
CS: machine.NINA_CS,
ACK: machine.NINA_ACK,
GPIO0: machine.NINA_GPIO0,
RESET: machine.NINA_RESETN,
}
console = machine.UART0
adaptor *wifinina.Device
topic = "tinygo"
)
@@ -62,12 +54,17 @@ func main() {
// Configure SPI for 8Mhz, Mode 0, MSB First
spi.Configure(machine.SPIConfig{
Frequency: 8 * 1e6,
MOSI: machine.NINA_MOSI,
MISO: machine.NINA_MISO,
SDO: machine.NINA_SDO,
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
// Init esp8266/esp32
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
connectToAP()
+8 -11
View File
@@ -40,15 +40,7 @@ var (
spi = machine.NINA_SPI
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor = &wifinina.Device{
SPI: spi,
CS: machine.NINA_CS,
ACK: machine.NINA_ACK,
GPIO0: machine.NINA_GPIO0,
RESET: machine.NINA_RESETN,
}
console = machine.UART0
adaptor *wifinina.Device
cl mqtt.Client
topicTx = "tinygo/tx"
@@ -69,12 +61,17 @@ func main() {
// Configure SPI for 8Mhz, Mode 0, MSB First
spi.Configure(machine.SPIConfig{
Frequency: 8 * 1e6,
MOSI: machine.NINA_MOSI,
MISO: machine.NINA_MISO,
SDO: machine.NINA_SDO,
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
// Init esp8266/esp32
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
connectToAP()
+163
View File
@@ -0,0 +1,163 @@
// This is an example of using the wifinina driver to implement a NTP client.
// It creates a UDP connection to request the current time and parse the
// response from a NTP server.
package main
import (
"errors"
"fmt"
"machine"
"runtime"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/wifinina"
)
// access point info
const ssid = ""
const pass = ""
// IP address of the server aka "hub". Replace with your own info.
const ntpHost = "129.6.15.29"
const NTP_PACKET_SIZE = 48
var (
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor *wifinina.Device
b = make([]byte, NTP_PACKET_SIZE)
)
func main() {
// Init esp32
// Configure SPI for 8Mhz, Mode 0, MSB First
machine.NINA_SPI.Configure(machine.SPIConfig{
Frequency: 8 * 1e6,
SDO: machine.NINA_SDO,
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
// these are the default pins for the Arduino Nano33 IoT.
adaptor = wifinina.New(machine.NINA_SPI,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
// connect to access point
connectToAP()
// now make UDP connection
ip := net.ParseIP(ntpHost)
raddr := &net.UDPAddr{IP: ip, Port: 123}
laddr := &net.UDPAddr{Port: 2390}
conn, err := net.DialUDP("udp", laddr, raddr)
if err != nil {
for {
time.Sleep(time.Second)
println(err)
}
}
for {
// send data
println("Requesting NTP time...")
t, err := getCurrentTime(conn)
if err != nil {
message("Error getting current time: %v", err)
} else {
message("NTP time: %v", t)
}
runtime.AdjustTimeOffset(-1 * int64(time.Since(t)))
for i := 0; i < 10; i++ {
message("Current time: %v", time.Now())
time.Sleep(1 * time.Second)
}
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting UDP...")
conn.Close()
println("Done.")
}
func getCurrentTime(conn *net.UDPSerialConn) (time.Time, error) {
if err := sendNTPpacket(conn); err != nil {
return time.Time{}, err
}
clearBuffer()
for now := time.Now(); time.Since(now) < time.Second; {
time.Sleep(5 * time.Millisecond)
if n, err := conn.Read(b); err != nil {
return time.Time{}, fmt.Errorf("error reading UDP packet: %w", err)
} else if n == 0 {
continue // no packet received yet
} else if n != NTP_PACKET_SIZE {
if n != NTP_PACKET_SIZE {
return time.Time{}, fmt.Errorf("expected NTP packet size of %d: %d", NTP_PACKET_SIZE, n)
}
}
return parseNTPpacket(), nil
}
return time.Time{}, errors.New("no packet received after 1 second")
}
func sendNTPpacket(conn *net.UDPSerialConn) error {
clearBuffer()
b[0] = 0b11100011 // LI, Version, Mode
b[1] = 0 // Stratum, or type of clock
b[2] = 6 // Polling Interval
b[3] = 0xEC // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
b[12] = 49
b[13] = 0x4E
b[14] = 49
b[15] = 52
if _, err := conn.Write(b); err != nil {
return err
}
return nil
}
func parseNTPpacket() time.Time {
// the timestamp starts at byte 40 of the received packet and is four bytes,
// this is NTP time (seconds since Jan 1 1900):
t := uint32(b[40])<<24 | uint32(b[41])<<16 | uint32(b[42])<<8 | uint32(b[43])
const seventyYears = 2208988800
return time.Unix(int64(t-seventyYears), 0)
}
func clearBuffer() {
for i := range b {
b[i] = 0
}
}
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
message("Connecting to " + ssid)
adaptor.SetPassphrase(ssid, pass)
for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; {
message("Connection status: " + st.String())
time.Sleep(1 * time.Second)
st, _ = adaptor.GetConnectionStatus()
}
message("Connected.")
time.Sleep(2 * time.Second)
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message(ip.String())
}
func message(format string, args ...interface{}) {
println(fmt.Sprintf(format, args...), "\r")
}
+8 -11
View File
@@ -35,15 +35,7 @@ var (
spi = machine.NINA_SPI
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor = &wifinina.Device{
SPI: spi,
CS: machine.NINA_CS,
ACK: machine.NINA_ACK,
GPIO0: machine.NINA_GPIO0,
RESET: machine.NINA_RESETN,
}
console = machine.UART0
adaptor *wifinina.Device
)
var buf = &bytes.Buffer{}
@@ -55,11 +47,16 @@ func main() {
// Configure SPI for 8Mhz, Mode 0, MSB First
spi.Configure(machine.SPIConfig{
Frequency: 8 * 1e6,
MOSI: machine.NINA_MOSI,
MISO: machine.NINA_MISO,
SDO: machine.NINA_SDO,
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
connectToAP()
+98
View File
@@ -0,0 +1,98 @@
// This is a sensor station that uses a ESP32 running nina-fw over SPI.
// It creates a UDP connection you can use to get info to/from your computer via the microcontroller.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> SPI <--> ESP32
//
package main
import (
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/wifinina"
)
// access point info
const ssid = ""
const pass = ""
// IP address of the server aka "hub". Replace with your own info.
const hubIP = ""
var (
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor *wifinina.Device
)
func main() {
// Init esp8266/esp32
// Configure SPI for 8Mhz, Mode 0, MSB First
machine.NINA_SPI.Configure(machine.SPIConfig{
Frequency: 8 * 1e6,
SDO: machine.NINA_SDO,
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
// these are the default pins for the Arduino Nano33 IoT.
// change these to connect to a different UART or pins for the ESP8266/ESP32
adaptor = wifinina.New(machine.NINA_SPI,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
// connect to access point
connectToAP()
// now make UDP connection
ip := net.ParseIP(hubIP)
raddr := &net.UDPAddr{IP: ip, Port: 2222}
laddr := &net.UDPAddr{Port: 2222}
println("Dialing UDP connection...")
conn, _ := net.DialUDP("udp", laddr, raddr)
for {
// send data
println("Sending data...")
for i := 0; i < 25; i++ {
conn.Write([]byte("hello " + strconv.Itoa(i) + "\r\n"))
}
time.Sleep(1000 * time.Millisecond)
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting UDP...")
conn.Close()
println("Done.")
}
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
message("Connecting to " + ssid)
adaptor.SetPassphrase(ssid, pass)
for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; {
message("Connection status: " + st.String())
time.Sleep(1 * time.Second)
st, _ = adaptor.GetConnectionStatus()
}
message("Connected.")
time.Sleep(2 * time.Second)
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message(ip.String())
}
func message(msg string) {
println(msg, "\r")
}
+8 -11
View File
@@ -33,15 +33,7 @@ var (
spi = machine.NINA_SPI
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor = &wifinina.Device{
SPI: spi,
CS: machine.NINA_CS,
ACK: machine.NINA_ACK,
GPIO0: machine.NINA_GPIO0,
RESET: machine.NINA_RESETN,
}
console = machine.UART0
adaptor *wifinina.Device
)
var buf [256]byte
@@ -56,11 +48,16 @@ func main() {
// Configure SPI for 8Mhz, Mode 0, MSB First
spi.Configure(machine.SPIConfig{
Frequency: 8 * 1e6,
MOSI: machine.NINA_MOSI,
MISO: machine.NINA_MISO,
SDO: machine.NINA_SDO,
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
connectToAP()
+9
View File
@@ -0,0 +1,9 @@
// +build arduino
package main
import "machine"
// Replace neo in the code below to match the pin
// that you are using if different.
var neo = machine.D2
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !digispark
// +build !digispark,!arduino
package main
+448
View File
@@ -0,0 +1,448 @@
package flash
import "time"
// A DeviceIdentifier can be passed to the Configure() method of a flash Device
// in order provide a means of discovery of device-specific attributes based on
// the JEDEC ID read from the device.
type DeviceIdentifier interface {
// Identify returns an Attrs struct based on the provided JEDEC ID
Identify(id JedecID) Attrs
}
// DeviceIdentifierFunc is a functional Identifier implementation
type DeviceIdentifierFunc func(id JedecID) Attrs
// Identify implements the Identifier interface
func (fn DeviceIdentifierFunc) Identify(id JedecID) Attrs {
return fn(id)
}
// DefaultDeviceIndentifier is a DeviceIdentifier that is capable of recognizing
// JEDEC IDs for all of the known memory devices in this package. If you are
// have no way to be sure about the type of memory device that might be on a
// board you are targeting, this can be a good starting point to use. The
// downside of using this function is that it will prevent the compiler from
// being able to mark any of the functions for the various devices as unused,
// resulting in larger code size. If code size is a concern, and if you know
// ahead of time you are only dealing with a limited set of memory devices, it
// might be worthwhile to use your own implementation of a DeviceIdentifier
// that only references those devices, so that more methods are marked unused.
var DefaultDeviceIdentifier = DeviceIdentifierFunc(func(id JedecID) Attrs {
switch id.Uint32() {
case 0x010617:
return S25FL064L()
case 0x014015:
return S25FL216K()
case 0x1F4501:
return AT25DF081A()
case 0xC22015:
return MX25L1606()
case 0xC22016:
return MX25L3233F()
case 0xC22817:
return MX25R6435F()
case 0xC84015:
return GD25Q16C()
case 0xC84017:
return GD25Q64C()
case 0xEF4015:
return W25Q16JVIQ()
case 0xEF4016:
return W25Q32FV()
case 0xEF4017:
return W25Q64JVIQ()
case 0xEF4018:
return W25Q128JVSQ()
case 0xEF6014:
return W25Q80DL()
case 0xEF6015:
return W25Q16FW()
case 0xEF6016:
return W25Q32BV()
case 0xEF7015:
return W25Q16JVIM()
case 0xEF7016:
return W25Q32JVIM()
case 0xEF7017:
return W25Q64JVIM()
case 0xEF7018:
return W25Q128JVPM()
default:
return Attrs{JedecID: id}
}
})
// Settings for the Cypress (was Spansion) S25FL064L 8MiB SPI flash.
// Datasheet: http://www.cypress.com/file/316661/download
func S25FL064L() Attrs {
return Attrs{
TotalSize: 1 << 23, // 8 MiB
StartUp: 300 * time.Microsecond,
JedecID: JedecID{0x01, 0x60, 0x17},
MaxClockSpeedMHz: 108,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Cypress (was Spansion) S25FL116K 2MiB SPI flash.
// Datasheet: http://www.cypress.com/file/196886/download
func S25FL116K() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 10000 * time.Microsecond,
JedecID: JedecID{0x01, 0x40, 0x15},
MaxClockSpeedMHz: 108,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: false,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Cypress (was Spansion) S25FL216K 2MiB SPI flash.
// Datasheet: http://www.cypress.com/file/197346/download
func S25FL216K() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 10000 * time.Microsecond,
JedecID: JedecID{0x01, 0x40, 0x15},
MaxClockSpeedMHz: 65,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: false,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Adesto Tech AT25DF081A 1MiB SPI flash. Its on the SAMD21
// Xplained board.
// Datasheet: https://www.adestotech.com/wp-content/uploads/doc8715.pdf
func AT25DF081A() Attrs {
return Attrs{
TotalSize: 1 << 20, // 1 MiB
StartUp: 10000 * time.Microsecond,
JedecID: JedecID{0x1F, 0x45, 0x01},
MaxClockSpeedMHz: 85,
QuadEnableBitMask: 0x00,
HasSectorProtection: true,
SupportsFastRead: true,
SupportsQSPI: false,
SupportsQSPIWrites: false,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Macronix MX25L1606 2MiB SPI flash.
// Datasheet:
func MX25L1606() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB,
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xC2, 0x20, 0x15},
MaxClockSpeedMHz: 8,
QuadEnableBitMask: 0x40,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: true,
}
}
// Settings for the Macronix MX25L3233F 4MiB SPI flash.
// Datasheet:
// http://www.macronix.com/Lists/Datasheet/Attachments/7426/MX25L3233F,%203V,%2032Mb,%20v1.6.pdf
func MX25L3233F() Attrs {
return Attrs{
TotalSize: 1 << 22, // 4 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xC2, 0x20, 0x16},
MaxClockSpeedMHz: 133,
QuadEnableBitMask: 0x40,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Macronix MX25R6435F 8MiB SPI flash.
// Datasheet:
// http://www.macronix.com/Lists/Datasheet/Attachments/7428/MX25R6435F,%20Wide%20Range,%2064Mb,%20v1.4.pdf
// By default its in lower power mode which can only do 8mhz. In high power mode
// it can do 80mhz.
func MX25R6435F() Attrs {
return Attrs{
TotalSize: 1 << 23, // 8 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xC2, 0x28, 0x17},
MaxClockSpeedMHz: 8,
QuadEnableBitMask: 0x40,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: true,
}
}
// Settings for the Gigadevice GD25Q16C 2MiB SPI flash.
// Datasheet: http://www.gigadevice.com/datasheet/gd25q16c/
func GD25Q16C() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xC8, 0x40, 0x15},
MaxClockSpeedMHz: 104,
QuadEnableBitMask: 0x02,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Gigadevice GD25Q64C 8MiB SPI flash.
// Datasheet: http://www.elm-tech.com/en/products/spi-flash-memory/gd25q64/gd25q64.pdf
func GD25Q64C() Attrs {
return Attrs{
TotalSize: 1 << 23, // 8 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xC8, 0x40, 0x17},
MaxClockSpeedMHz: 104,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
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
func W25Q16JVIQ() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x40, 0x15},
MaxClockSpeedMHz: 133,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q16FW 2MiB SPI flash.
// Datasheet:
// https://www.winbond.com/resource-files/w25q16fw%20revj%2005182017%20sfdp.pdf
func W25Q16FW() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x60, 0x15},
MaxClockSpeedMHz: 133,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q16JV-IM 2MiB SPI flash. Note that JV-IQ has a
// different .memory_type (0x40) Datasheet:
// https://www.winbond.com/resource-files/w25q16jv%20spi%20revf%2005092017.pdf
func W25Q16JVIM() Attrs {
return Attrs{
TotalSize: 1 << 21, // 2 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x70, 0x15},
MaxClockSpeedMHz: 133,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q32BV 4MiB SPI flash.
// Datasheet:
// https://www.winbond.com/resource-files/w25q32bv_revi_100413_wo_automotive.pdf
func W25Q32BV() Attrs {
return Attrs{
TotalSize: 1 << 22, // 4 MiB
StartUp: 10000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x60, 0x16},
MaxClockSpeedMHz: 104,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: false,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q32JV-IM 4MiB SPI flash.
// Datasheet:
// https://www.winbond.com/resource-files/w25q32jv%20revg%2003272018%20plus.pdf
func W25Q32JVIM() Attrs {
return Attrs{
TotalSize: 1 << 22, // 4 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x70, 0x16},
MaxClockSpeedMHz: 133,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q64JV-IM 8MiB SPI flash. Note that JV-IQ has a
// different .memory_type (0x40) Datasheet:
// http://www.winbond.com/resource-files/w25q64jv%20revj%2003272018%20plus.pdf
func W25Q64JVIM() Attrs {
return Attrs{
TotalSize: 1 << 23, // 8 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x70, 0x17},
MaxClockSpeedMHz: 133,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q64JV-IQ 8MiB SPI flash. Note that JV-IM has a
// different .memory_type (0x70) Datasheet:
// http://www.winbond.com/resource-files/w25q64jv%20revj%2003272018%20plus.pdf
func W25Q64JVIQ() Attrs {
return Attrs{
TotalSize: 1 << 23, // 8 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x40, 0x17},
MaxClockSpeedMHz: 133,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q80DL 1MiB SPI flash.
// Datasheet:
// https://www.winbond.com/resource-files/w25q80dv%20dl_revh_10022015.pdf
func W25Q80DL() Attrs {
return Attrs{
TotalSize: 1 << 20, // 1 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x60, 0x14},
MaxClockSpeedMHz: 104,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: false,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q128JV-SQ 16MiB SPI flash. Note that JV-IM has a
// different .memory_type (0x70) Datasheet:
// https://www.winbond.com/resource-files/w25q128jv%20revf%2003272018%20plus.pdf
func W25Q128JVSQ() Attrs {
return Attrs{
TotalSize: 1 << 24, // 16 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x40, 0x18},
MaxClockSpeedMHz: 133,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q128JV-PM 16MiB SPI flash. Note that JV-IM has a
// different .memory_type (0x70) Datasheet:
// https://www.winbond.com/resource-files/w25q128jv%20revf%2003272018%20plus.pdf
func W25Q128JVPM() Attrs {
return Attrs{
TotalSize: 1 << 24, // 16 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x70, 0x18},
MaxClockSpeedMHz: 133,
QuadEnableBitMask: 0x02,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: true,
SupportsQSPIWrites: true,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
// Settings for the Winbond W25Q32FV 4MiB SPI flash.
// Datasheet:http://www.winbond.com/resource-files/w25q32fv%20revj%2006032016.pdf?__locale=en
func W25Q32FV() Attrs {
return Attrs{
TotalSize: 1 << 22, // 4 MiB
StartUp: 5000 * time.Microsecond,
JedecID: JedecID{0xEF, 0x40, 0x16},
MaxClockSpeedMHz: 104,
QuadEnableBitMask: 0x00,
HasSectorProtection: false,
SupportsFastRead: true,
SupportsQSPI: false,
SupportsQSPIWrites: false,
WriteStatusSplit: false,
SingleStatusByte: false,
}
}
+407
View File
@@ -0,0 +1,407 @@
package flash
import (
"time"
)
const (
// BlockSize is the number of bytes in a block for most/all NOR flash memory
BlockSize = 64 * 1024
// SectorSize is the number of bytes in a sector for most/all NOR flash memory
SectorSize = 4 * 1024
// PageSize is the number of bytes in a page for most/all NOR flash memory
PageSize = 256
)
// Device represents a NOR flash memory device accessible using SPI
type Device struct {
trans transport
attrs Attrs
}
// DeviceConfig contains the parameters that can be set when configuring a
// flash memory device.
type DeviceConfig struct {
Identifier DeviceIdentifier
}
// JedecID encapsules the ID values that unique identify a flash memory device.
type JedecID struct {
ManufID uint8
MemType uint8
Capacity uint8
}
// Uint32 returns the JEDEC ID packed into a uint32
func (id JedecID) Uint32() uint32 {
return uint32(id.ManufID)<<16 | uint32(id.MemType)<<8 | uint32(id.Capacity)
}
// SerialNumber represents a serial number read from a flash memory device
type SerialNumber uint64
// Attrs represent the differences in hardware characteristics and capabilities
// of various SPI flash memory devices.
type Attrs struct {
// TotalSize is the number of bytes that the flash device can store
TotalSize uint32
// StartUp is the duration of time between when the device is reset and when
// it is ready to operation
StartUp time.Duration
// Three response bytes to 0x9f JEDEC ID command.
JedecID
// Max clock speed for all operations and the fastest read mode.
MaxClockSpeedMHz uint8
// Bitmask for Quad Enable bit if present. 0x00 otherwise. This is for the
// highest byte in the status register.
QuadEnableBitMask uint8
HasSectorProtection bool
// Supports the 0x0b fast read command with 8 dummy cycles.
SupportsFastRead bool
// Supports the fast read, quad output command 0x6b with 8 dummy cycles.
SupportsQSPI bool
// Supports the quad input page program command 0x32. This is known as 1-1-4
// because it only uses all four lines for data.
SupportsQSPIWrites bool
// Requires a separate command 0x31 to write to the second byte of the status
// register. Otherwise two byte are written via 0x01.
WriteStatusSplit bool
// True when the status register is a single byte. This implies the Quad
// Enable bit is in the first byte and the Read Status Register 2 command
// (0x35) is unsupported.
SingleStatusByte bool
}
// Configure sets up the device and the underlying transport mechanism. The
// DeviceConfig argument allows the caller to specify an instance of the
// DeviceIdentifier interface that, if provided, will be used to retrieve the
// attributes of the device based on the JEDEC ID.
func (dev *Device) Configure(config *DeviceConfig) (err error) {
dev.trans.configure(config)
var id JedecID
if id, err = dev.ReadJEDEC(); err != nil {
return err
}
// try to ascertain the vendor-specific attributes of the chip using the
// provided Identifier
if config.Identifier != nil {
dev.attrs = config.Identifier.Identify(id)
} else {
dev.attrs = Attrs{JedecID: id}
}
// We don't know what state the flash is in so wait for any remaining
// writes and then reset.
// The write in progress bit should be low.
for s, err := dev.ReadStatus(); (s & 0x01) > 0; s, err = dev.ReadStatus() {
if err != nil {
return err
}
}
// The suspended write/erase bit should be low.
for s, err := dev.ReadStatus2(); (s & 0x80) > 0; s, err = dev.ReadStatus2() {
if err != nil {
return err
}
}
// perform device reset
if err := dev.trans.runCommand(cmdEnableReset); err != nil {
return err
}
if err := dev.trans.runCommand(cmdReset); err != nil {
return err
}
// Wait for the reset - 30us by default
time.Sleep(30 * time.Microsecond)
// Speed up to max device frequency
// I propose a check here for max frequency, but not put that functionality directly into the driver.
// Either that or we have to change the signature of the SPI interface in the machine package itself.
if dev.attrs.MaxClockSpeedMHz > 0 {
err := dev.trans.setClockSpeed(uint32(dev.attrs.MaxClockSpeedMHz) * 1e6)
if err != nil {
return err
}
}
// Enable Quad Mode if available
if dev.trans.supportQuadMode() && dev.attrs.QuadEnableBitMask > 0 {
// Verify that QSPI mode is enabled.
var status byte
if dev.attrs.SingleStatusByte {
status, err = dev.ReadStatus()
} else {
status, err = dev.ReadStatus2()
}
if err != nil {
return err
}
// Check and set the quad enable bit.
if status&dev.attrs.QuadEnableBitMask == 0 {
if err := dev.WriteEnable(); err != nil {
return err
}
fullStatus := []byte{0x00, dev.attrs.QuadEnableBitMask}
if dev.attrs.WriteStatusSplit {
err = dev.trans.writeCommand(cmdWriteStatus2, fullStatus[1:])
} else if dev.attrs.SingleStatusByte {
err = dev.trans.writeCommand(cmdWriteStatus, fullStatus[1:])
} else {
err = dev.trans.writeCommand(cmdWriteStatus, fullStatus)
}
if err != nil {
return err
}
}
}
// disable sector protection if the chip has it
if dev.attrs.HasSectorProtection {
if err := dev.WriteEnable(); err != nil {
return err
}
if err := dev.trans.writeCommand(cmdWriteStatus, []byte{0x00}); err != nil {
return err
}
}
// write disable
if err := dev.trans.runCommand(cmdWriteDisable); err != nil {
return err
}
return dev.WaitUntilReady()
}
// Attrs returns the attributes of the device determined from the most recent
// call to Configure(). If no call to Configure() has been made, this will be
// the zero value of the Attrs struct.
func (dev *Device) Attrs() Attrs {
return dev.attrs
}
// ReadJEDEC reads the JEDEC ID from the device; this ID can then be used to
// ascertain the attributes of the chip from a list of known devices.
func (dev *Device) ReadJEDEC() (JedecID, error) {
jedecID := make([]byte, 3)
if err := dev.trans.readCommand(cmdReadJedecID, jedecID); err != nil {
return JedecID{}, err
}
return JedecID{jedecID[0], jedecID[1], jedecID[2]}, nil
}
// ReadSerialNumber reads the serial numbers from the connected device.
// TODO: maybe check if byte order / endianess is correct, probably is not
func (dev *Device) ReadSerialNumber() (SerialNumber, error) {
sn := make([]byte, 12)
if err := dev.trans.readCommand(0x4B, sn); err != nil {
return 0, err
}
return SerialNumber(uint64(sn[11]) | uint64(sn[10])<<0x8 |
uint64(sn[9])<<0x10 | uint64(sn[8])<<0x18 | uint64(sn[7])<<0x20 |
uint64(sn[6])<<0x28 | uint64(sn[5])<<0x30 | uint64(sn[4])<<0x38), nil
}
// Size returns the size of this memory, in bytes.
func (dev *Device) Size() int64 {
if dev.attrs.TotalSize < 1 {
// in case a DeviceIdentifier function wasn't used, use the capacity
// specified in the JEDEC ID instead
return int64(dev.attrs.Capacity)
}
return int64(dev.attrs.TotalSize)
}
// ReadAt satisfies the io.ReaderAt interface, and fills the provided buffer
// with memory read from the device starting at the provided address.
func (dev *Device) ReadAt(buf []byte, addr int64) (int, error) {
if err := dev.WaitUntilReady(); err != nil {
return 0, err
}
if err := dev.trans.readMemory(uint32(addr), buf); err != nil {
return 0, err
}
return len(buf), nil
}
// WriteAt satisfies the io.WriterAt interface and writes data to the device,
// one page at a time, starting at the provided address. This method assumes
// that the destination is already erased.
func (dev *Device) WriteAt(buf []byte, addr int64) (n int, err error) {
remain := uint32(len(buf))
idx := uint32(0)
loc := uint32(addr)
for remain > 0 {
if err = dev.WaitUntilReady(); err != nil {
return
}
if err = dev.WriteEnable(); err != nil {
return
}
leftOnPage := PageSize - (loc & (PageSize - 1))
toWrite := remain
if leftOnPage < remain {
toWrite = leftOnPage
}
if err = dev.trans.writeMemory(loc, buf[idx:idx+toWrite]); err != nil {
return
}
idx += toWrite
loc += toWrite
remain -= toWrite
}
return len(buf) - int(remain), nil
}
// WriteBlockSize returns the block size in which data can be written to
// memory. It can be used by a client to optimize writes, non-aligned writes
// should always work correctly.
// For SPI NOR flash this is the page size, usually/always 256.
func (dev *Device) WriteBlockSize() int64 {
return PageSize
}
// EraseBlockSize returns the smallest erasable area on this particular chip
// in bytes. This is used for the block size in EraseBlocks.
// For SPI NOR flash this is the sector size, usually/always 4096.
func (dev *Device) EraseBlockSize() int64 {
return SectorSize
}
// EraseBlocks erases the given number of blocks. An implementation may
// transparently coalesce ranges of blocks into larger bundles if the chip
// supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks.
func (dev *Device) EraseBlocks(start, len int64) error {
// TODO: maybe combine sector erase operations into block erase operations
for i := start; i < start+len; i++ {
if err := dev.EraseSector(uint32(i)); err != nil {
return err
}
}
return nil
}
func (dev *Device) WriteEnable() error {
return dev.trans.runCommand(cmdWriteEnable)
}
// EraseBlock erases a block of memory at the specified index
func (dev *Device) EraseBlock(blockNumber uint32) error {
if err := dev.WaitUntilReady(); err != nil {
return err
}
if err := dev.WriteEnable(); err != nil {
return err
}
return dev.trans.eraseCommand(cmdEraseBlock, blockNumber*BlockSize)
}
// EraseSector erases a sector of memory at the given index
func (dev *Device) EraseSector(sectorNumber uint32) error {
if err := dev.WaitUntilReady(); err != nil {
return err
}
if err := dev.WriteEnable(); err != nil {
return err
}
return dev.trans.eraseCommand(cmdEraseSector, sectorNumber*SectorSize)
}
// EraseChip erases the entire flash memory chip
func (dev *Device) EraseAll() error {
if err := dev.WaitUntilReady(); err != nil {
return err
}
if err := dev.WriteEnable(); err != nil {
return err
}
return dev.trans.runCommand(cmdEraseChip)
}
// ReadStatus reads the value from status register 1 of the device
func (dev *Device) ReadStatus() (status byte, err error) {
buf := make([]byte, 1)
err = dev.trans.readCommand(cmdReadStatus, buf)
return buf[0], err
}
// ReadStatus2 reads the value from status register 2 of the device
func (dev *Device) ReadStatus2() (status byte, err error) {
buf := make([]byte, 1)
err = dev.trans.readCommand(cmdReadStatus2, buf)
return buf[0], err
}
// WaitUntilReady queries the status register until the device is ready for the
// next operation.
func (dev *Device) WaitUntilReady() error {
expire := time.Now().UnixNano() + int64(1*time.Second)
for s, err := dev.ReadStatus(); (s & 0x03) > 0; s, err = dev.ReadStatus() {
if err != nil {
return err
}
if time.Now().UnixNano() > expire {
return ErrWaitExpired
}
}
return nil
}
const (
cmdRead = 0x03 // read memory using single-bit transfer
cmdQuadRead = 0x6B // read with 1 line address, 4 line data
cmdReadJedecID = 0x9F // read the JEDEC ID from the device
cmdPageProgram = 0x02 // write a page of memory using single-bit transfer
cmdQuadPageProgram = 0x32 // write with 1 line address, 4 line data
cmdReadStatus = 0x05 // read status register 1
cmdReadStatus2 = 0x35 // read status register 2
cmdWriteStatus = 0x01 // write status register 1
cmdWriteStatus2 = 0x31 // write status register 2
cmdEnableReset = 0x66 // enable reset
cmdReset = 0x99 // perform reset
cmdWriteEnable = 0x06 // write-enable memory
cmdWriteDisable = 0x04 // write-protect memory
cmdEraseSector = 0x20 // erase a sector of memory
cmdEraseBlock = 0xD8 // erase a block of memory
cmdEraseChip = 0xC7 // erase the entire chip
)
type Error uint8
const (
_ = iota
ErrInvalidClockSpeed Error = iota
ErrInvalidAddrRange
ErrWaitExpired
)
func (err Error) Error() string {
switch err {
case ErrInvalidClockSpeed:
return "flash: invalid clock speed"
case ErrInvalidAddrRange:
return "flash: invalid address range"
case ErrWaitExpired:
return "flash: wait until ready expired"
default:
return "flash: unspecified error"
}
}
+247
View File
@@ -0,0 +1,247 @@
// +build atsamd51
package flash
import (
"device/sam"
"machine"
"runtime/volatile"
"unsafe"
)
// NewQSPI returns a pointer to a flash device that uses the QSPI peripheral to
// communicate with a serial memory chip.
func NewQSPI(cs, sck, d0, d1, d2, d3 machine.Pin) *Device {
return &Device{
trans: &qspiTransport{
cs: cs,
sck: sck,
d0: d0,
d1: d1,
d2: d2,
d3: d3,
},
}
}
// QSPI address space on SAMD51 is 0x04000000 to 0x05000000
const (
// Low address of the QSPI address space on SAMD51
qspi_AHB_LO = 0x04000000
// High address of the QSPI address space on SAMD51
qspi_AHB_HI = 0x05000000
// Instruction frame for running sending a command to the device
iframeRunCommand = 0x0 |
sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI |
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
sam.QSPI_INSTRFRAME_INSTREN |
(sam.QSPI_INSTRFRAME_TFRTYPE_READ << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
// Instruction frame for running a command that returns data
iframeReadCommand = 0x0 |
sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI |
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
sam.QSPI_INSTRFRAME_INSTREN |
sam.QSPI_INSTRFRAME_DATAEN |
(sam.QSPI_INSTRFRAME_TFRTYPE_READ << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
// Instruction frame to set up the device to read from memory
iframeReadMemory = 0x0 |
sam.QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT |
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
sam.QSPI_INSTRFRAME_INSTREN |
sam.QSPI_INSTRFRAME_DATAEN |
sam.QSPI_INSTRFRAME_ADDREN |
(8 << sam.QSPI_INSTRFRAME_DUMMYLEN_Pos) |
(sam.QSPI_INSTRFRAME_TFRTYPE_READMEMORY << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
// Instruction frame for running a command that requires parameter data
iframeWriteCommand = 0x0 |
sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI |
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
sam.QSPI_INSTRFRAME_INSTREN |
(sam.QSPI_INSTRFRAME_TFRTYPE_WRITE << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
// Instruction frame to set up the device for writing to memory
iframeWriteMemory = 0x0 |
sam.QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT |
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
sam.QSPI_INSTRFRAME_INSTREN |
sam.QSPI_INSTRFRAME_ADDREN |
sam.QSPI_INSTRFRAME_DATAEN |
(sam.QSPI_INSTRFRAME_TFRTYPE_WRITEMEMORY << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
// Instruction frame for running an erase command that requires and address
iframeEraseCommand = 0x0 |
sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI |
sam.QSPI_INSTRFRAME_ADDRLEN_24BITS |
sam.QSPI_INSTRFRAME_INSTREN |
sam.QSPI_INSTRFRAME_ADDREN |
(sam.QSPI_INSTRFRAME_TFRTYPE_WRITE << sam.QSPI_INSTRFRAME_TFRTYPE_Pos)
)
type qspiTransport struct {
cs machine.Pin
sck machine.Pin
d0 machine.Pin
d1 machine.Pin
d2 machine.Pin
d3 machine.Pin
}
func (q qspiTransport) configure(config *DeviceConfig) {
// enable main clocks
sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_QSPI_)
sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_QSPI_)
sam.MCLK.AHBMASK.ClearBits(sam.MCLK_AHBMASK_QSPI_2X_)
sam.QSPI.CTRLA.SetBits(sam.QSPI_CTRLA_SWRST)
// enable all pins to be PinCom
q.d0.Configure(machine.PinConfig{Mode: machine.PinCom})
q.d1.Configure(machine.PinConfig{Mode: machine.PinCom})
q.d2.Configure(machine.PinConfig{Mode: machine.PinCom})
q.d3.Configure(machine.PinConfig{Mode: machine.PinCom})
q.cs.Configure(machine.PinConfig{Mode: machine.PinCom})
q.sck.Configure(machine.PinConfig{Mode: machine.PinCom})
// start out with 4Mhz
// can ignore the error, 4Mhz is always a valid speed
_ = q.setClockSpeed(4e6)
// configure the CTRLB register
sam.QSPI.CTRLB.Set(sam.QSPI_CTRLB_MODE_MEMORY |
(sam.QSPI_CTRLB_DATALEN_8BITS << sam.QSPI_CTRLB_DATALEN_Pos) |
(sam.QSPI_CTRLB_CSMODE_LASTXFER << sam.QSPI_CTRLB_CSMODE_Pos))
// enable the peripheral
sam.QSPI.CTRLA.SetBits(sam.QSPI_CTRLA_ENABLE)
}
func (q qspiTransport) supportQuadMode() bool {
return true
}
func (q qspiTransport) setClockSpeed(hz uint32) error {
// The clock speed for the QSPI peripheral is controlled by a divider, so
// we can't set the requested speed exactly. Instead we will increment the
// divider until the speed is less than or equal to the speed requested.
for div, freq := uint32(1), machine.CPUFrequency(); div < 256; div++ {
if freq/div <= hz {
sam.QSPI.BAUD.Set(div << sam.QSPI_BAUD_BAUD_Pos)
return nil
}
}
return ErrInvalidClockSpeed
}
func (q qspiTransport) runCommand(cmd byte) (err error) {
q.runInstruction(cmd, iframeRunCommand)
q.endTransfer()
return
}
func (q qspiTransport) readCommand(cmd byte, buf []byte) (err error) {
q.disableAndClearCache()
q.runInstruction(cmd, iframeReadCommand)
q.readInto(buf, 0)
q.endTransfer()
q.enableCache()
return
}
func (q qspiTransport) readMemory(addr uint32, buf []byte) (err error) {
if (addr + uint32(len(buf))) > (qspi_AHB_HI - qspi_AHB_LO) {
return ErrInvalidAddrRange
}
q.disableAndClearCache()
q.runInstruction(cmdQuadRead, iframeReadMemory)
q.readInto(buf, addr)
q.endTransfer()
q.enableCache()
return
}
func (q qspiTransport) writeCommand(cmd byte, data []byte) (err error) {
var dataen uint32
if len(data) > 0 {
dataen = sam.QSPI_INSTRFRAME_DATAEN
}
q.disableAndClearCache()
q.runInstruction(cmd, iframeWriteCommand|dataen)
q.writeFrom(data, 0)
q.endTransfer()
q.enableCache()
return
}
func (q qspiTransport) writeMemory(addr uint32, data []byte) (err error) {
if (addr + uint32(len(data))) > (qspi_AHB_HI - qspi_AHB_LO) {
return ErrInvalidAddrRange
}
q.disableAndClearCache()
q.runInstruction(cmdQuadPageProgram, iframeWriteMemory)
q.writeFrom(data, addr)
q.endTransfer()
q.enableCache()
return
}
func (q qspiTransport) eraseCommand(cmd byte, addr uint32) (err error) {
q.disableAndClearCache()
sam.QSPI.INSTRADDR.Set(addr)
q.runInstruction(cmd, iframeEraseCommand)
q.endTransfer()
q.enableCache()
return
}
func (q qspiTransport) runInstruction(cmd byte, iframe uint32) {
sam.QSPI.INSTRCTRL.Set(uint32(cmd))
sam.QSPI.INSTRFRAME.Set(iframe)
sam.QSPI.INSTRFRAME.Get() // dummy read for synchronization, as per datasheet
}
func (q qspiTransport) enableCache() {
sam.CMCC.CTRL.SetBits(sam.CMCC_CTRL_CEN)
}
func (q qspiTransport) disableAndClearCache() {
sam.CMCC.CTRL.ClearBits(sam.CMCC_CTRL_CEN)
for sam.CMCC.SR.HasBits(sam.CMCC_SR_CSTS) {
}
sam.CMCC.MAINT0.SetBits(sam.CMCC_MAINT0_INVALL)
}
func (q qspiTransport) endTransfer() {
sam.QSPI.CTRLA.Set(sam.QSPI_CTRLA_ENABLE | sam.QSPI_CTRLA_LASTXFER)
for !sam.QSPI.INTFLAG.HasBits(sam.QSPI_INTFLAG_INSTREND) {
}
sam.QSPI.INTFLAG.Set(sam.QSPI_INTFLAG_INSTREND)
}
func (q qspiTransport) readInto(buf []byte, addr uint32) {
var ptr = qspi_AHB_LO + uintptr(addr)
for i := range buf {
buf[i] = volatile.LoadUint8((*uint8)(unsafe.Pointer(ptr)))
ptr++
}
/* // NB(bcg): for some reason this reads data that results from commands in
// a different byte order than the loop above, but works fine for reading
// from memory. Oddly, the above loop seems to work fine in both cases.
ln := len(buf)
sl := (*[1 << 28]byte)(unsafe.Pointer(uintptr(qspi_AHB_LO + addr)))[:ln:ln]
copy(buf, sl)
*/
}
func (q qspiTransport) writeFrom(buf []byte, addr uint32) {
var ptr = qspi_AHB_LO + uintptr(addr)
for i := range buf {
volatile.StoreUint8((*uint8)(unsafe.Pointer(ptr)), buf[i])
ptr++
}
}
+156
View File
@@ -0,0 +1,156 @@
package flash
import (
"machine"
)
type transport interface {
configure(config *DeviceConfig)
supportQuadMode() bool
setClockSpeed(hz uint32) (err error)
runCommand(cmd byte) (err error)
readCommand(cmd byte, rsp []byte) (err error)
writeCommand(cmd byte, data []byte) (err error)
eraseCommand(cmd byte, address uint32) (err error)
readMemory(addr uint32, rsp []byte) (err error)
writeMemory(addr uint32, data []byte) (err error)
}
// NewSPI returns a pointer to a flash device that uses a SPI peripheral to
// communicate with a serial memory chip.
func NewSPI(spi *machine.SPI, sdo, sdi, sck, cs machine.Pin) *Device {
return &Device{
trans: &spiTransport{
spi: spi,
sdo: sdo,
sdi: sdi,
sck: sck,
ss: cs,
},
}
}
type spiTransport struct {
spi *machine.SPI
sdo machine.Pin
sdi machine.Pin
sck machine.Pin
ss machine.Pin
}
func (tr *spiTransport) configure(config *DeviceConfig) {
// Configure spi bus
tr.setClockSpeed(5000000)
// Configure chip select pin
tr.ss.Configure(machine.PinConfig{Mode: machine.PinOutput})
tr.ss.High()
}
func (tr *spiTransport) setClockSpeed(hz uint32) error {
// TODO: un-hardcode this max speed; it is probably a sensible
// default maximum for atsamd and nrf at least
if hz > 24*1e6 {
hz = 24 * 1e6
}
tr.spi.Configure(machine.SPIConfig{
Frequency: hz,
SDI: tr.sdi,
SDO: tr.sdo,
SCK: tr.sck,
LSBFirst: false,
Mode: 0,
})
return nil
}
func (tr *spiTransport) supportQuadMode() bool {
return false
}
func (tr *spiTransport) runCommand(cmd byte) (err error) {
tr.ss.Low()
_, err = tr.spi.Transfer(byte(cmd))
tr.ss.High()
return
}
func (tr *spiTransport) readCommand(cmd byte, rsp []byte) (err error) {
tr.ss.Low()
if _, err := tr.spi.Transfer(byte(cmd)); err == nil {
err = tr.readInto(rsp)
}
tr.ss.High()
return
}
func (tr *spiTransport) readCommandByte(cmd byte) (rsp byte, err error) {
tr.ss.Low()
if _, err := tr.spi.Transfer(byte(cmd)); err == nil {
rsp, err = tr.spi.Transfer(0xFF)
}
tr.ss.High()
return
}
func (tr *spiTransport) writeCommand(cmd byte, data []byte) (err error) {
tr.ss.Low()
if _, err := tr.spi.Transfer(byte(cmd)); err == nil {
err = tr.writeFrom(data)
}
tr.ss.High()
return
}
func (tr *spiTransport) eraseCommand(cmd byte, address uint32) (err error) {
tr.ss.Low()
err = tr.sendAddress(cmd, address)
tr.ss.High()
return
}
func (tr *spiTransport) readMemory(addr uint32, rsp []byte) (err error) {
tr.ss.Low()
if err = tr.sendAddress(cmdRead, addr); err == nil {
err = tr.readInto(rsp)
}
tr.ss.High()
return
}
func (tr *spiTransport) writeMemory(addr uint32, data []byte) (err error) {
tr.ss.Low()
if err = tr.sendAddress(cmdPageProgram, addr); err == nil {
err = tr.writeFrom(data)
}
tr.ss.High()
return
}
func (tr *spiTransport) sendAddress(cmd byte, addr uint32) error {
_, err := tr.spi.Transfer(byte(cmd))
if err == nil {
_, err = tr.spi.Transfer(byte((addr >> 16) & 0xFF))
}
if err == nil {
_, err = tr.spi.Transfer(byte((addr >> 8) & 0xFF))
}
if err == nil {
_, err = tr.spi.Transfer(byte(addr & 0xFF))
}
return err
}
func (tr *spiTransport) readInto(rsp []byte) (err error) {
for i, c := 0, len(rsp); i < c && err == nil; i++ {
rsp[i], err = tr.spi.Transfer(0xFF)
}
return
}
func (tr *spiTransport) writeFrom(data []byte) (err error) {
for i, c := 0, len(data); i < c && err == nil; i++ {
_, err = tr.spi.Transfer(data[i])
}
return
}
+8
View File
@@ -0,0 +1,8 @@
module tinygo.org/x/drivers
go 1.15
require (
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/frankban/quicktest v1.10.2
)
+13
View File
@@ -0,0 +1,13 @@
github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk=
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+33 -21
View File
@@ -3,24 +3,32 @@ package gps // import "tinygo.org/x/drivers/gps"
import (
"encoding/hex"
"errors"
"machine"
"strings"
"time"
"tinygo.org/x/drivers"
)
var (
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
)
// Device wraps a connection to a GPS device.
type GPSDevice struct {
type Device struct {
buffer []byte
bufIdx int
sentence strings.Builder
uart *machine.UART
bus *machine.I2C
bus drivers.I2C
address uint16
}
// NewUART creates a new UART GPS connection. The UART must already be configured.
func NewUART(uart *machine.UART) GPSDevice {
return GPSDevice{
func NewUART(uart *machine.UART) Device {
return Device{
uart: uart,
buffer: make([]byte, bufferSize),
bufIdx: bufferSize,
@@ -29,8 +37,8 @@ func NewUART(uart *machine.UART) GPSDevice {
}
// NewI2C creates a new I2C GPS connection.
func NewI2C(bus *machine.I2C) GPSDevice {
return GPSDevice{
func NewI2C(bus drivers.I2C) Device {
return Device{
bus: bus,
address: I2C_ADDRESS,
buffer: make([]byte, bufferSize),
@@ -39,17 +47,17 @@ func NewI2C(bus *machine.I2C) GPSDevice {
}
}
// ReadNextSentence returns the next valid NMEA sentence from the GPS device.
func (gps *GPSDevice) NextSentence() (sentence string) {
// NextSentence returns the next valid NMEA sentence from the GPS device.
func (gps *Device) NextSentence() (sentence string, err error) {
sentence = gps.readNextSentence()
for !validSentence(sentence) {
sentence = gps.readNextSentence()
if err = validSentence(sentence); err != nil {
return "", err
}
return sentence
return sentence, nil
}
// readNextSentence returns the next sentence from the GPS device.
func (gps *GPSDevice) readNextSentence() (sentence string) {
func (gps *Device) readNextSentence() (sentence string) {
gps.sentence.Reset()
var b byte = ' '
@@ -69,7 +77,7 @@ func (gps *GPSDevice) readNextSentence() (sentence string) {
return sentence
}
func (gps *GPSDevice) readNextByte() (b byte) {
func (gps *Device) readNextByte() (b byte) {
gps.bufIdx += 1
if gps.bufIdx >= bufferSize {
gps.fillBuffer()
@@ -77,7 +85,7 @@ func (gps *GPSDevice) readNextByte() (b byte) {
return gps.buffer[gps.bufIdx]
}
func (gps *GPSDevice) fillBuffer() {
func (gps *Device) fillBuffer() {
if gps.uart != nil {
gps.uartFillBuffer()
} else {
@@ -85,7 +93,7 @@ func (gps *GPSDevice) fillBuffer() {
}
}
func (gps *GPSDevice) uartFillBuffer() {
func (gps *Device) uartFillBuffer() {
for gps.uart.Buffered() < bufferSize {
time.Sleep(100 * time.Millisecond)
}
@@ -93,7 +101,7 @@ func (gps *GPSDevice) uartFillBuffer() {
gps.bufIdx = 0
}
func (gps *GPSDevice) i2cFillBuffer() {
func (gps *Device) i2cFillBuffer() {
for gps.available() < bufferSize {
time.Sleep(100 * time.Millisecond)
}
@@ -102,7 +110,7 @@ func (gps *GPSDevice) i2cFillBuffer() {
}
// Available returns how many bytes of GPS data are currently available.
func (gps *GPSDevice) available() (available int) {
func (gps *Device) available() (available int) {
var lengthBytes [2]byte
gps.bus.Tx(gps.address, []byte{BYTES_AVAIL_REG}, lengthBytes[0:2])
available = int(lengthBytes[0])*256 + int(lengthBytes[1])
@@ -110,7 +118,7 @@ func (gps *GPSDevice) available() (available int) {
}
// WriteBytes sends data/commands to the GPS device
func (gps *GPSDevice) WriteBytes(bytes []byte) {
func (gps *Device) WriteBytes(bytes []byte) {
if gps.uart != nil {
gps.uart.Write(bytes)
} else {
@@ -119,14 +127,18 @@ func (gps *GPSDevice) WriteBytes(bytes []byte) {
}
// validSentence checks if a sentence has been received uncorrupted
func validSentence(sentence string) bool {
func validSentence(sentence string) error {
if len(sentence) < 4 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
return false
return errInvalidNMEASentenceLength
}
var cs byte = 0
for i := 1; i < len(sentence)-3; i++ {
cs ^= sentence[i]
}
checksum := hex.EncodeToString([]byte{cs})
return (checksum[0] == sentence[len(sentence)-2]) && (checksum[1] == sentence[len(sentence)-1])
if (checksum[0] != sentence[len(sentence)-2]) || (checksum[1] != sentence[len(sentence)-1]) {
return errInvalidNMEAChecksum
}
return nil
}
+120 -63
View File
@@ -1,92 +1,131 @@
package gps
import (
"errors"
"strconv"
"strings"
"time"
)
type GPSParser struct {
gpsDevice GPSDevice
var (
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
)
// Parser for GPS NMEA sentences.
type Parser struct {
}
// fix is a GPS location fix
// Fix is a GPS location fix
type Fix struct {
Valid bool
Time time.Time
Latitude float32
Longitude float32
Altitude int32
// Valid if the fix was valid.
Valid bool
// Time that the fix was taken, in UTC time.
Time time.Time
// Latitude is the decimal latitude. Negative numbers indicate S.
Latitude float32
// Longitude is the decimal longitude. Negative numbers indicate E.
Longitude float32
// Altitude is only returned for GGA sentences.
Altitude int32
// Satellites is the number of visible satellites, but is only returned for GGA sentences.
Satellites int16
// Speed based on reported movement. Only returned for RMC sentences.
Speed float32
// Heading based on reported movement. Only returned for RMC sentences.
Heading float32
}
func Parser(gpsDevice GPSDevice) GPSParser {
return GPSParser{
gpsDevice: gpsDevice,
// NewParser returns a GPS NMEA Parser.
func NewParser() Parser {
return Parser{}
}
// Parse parses a NMEA sentence looking for fix info.
func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
if sentence == "" {
err = errEmptyNMEASentence
return
}
}
// NextFix returns the next GPS location Fix from the GPS device
func (parser *GPSParser) NextFix() (fix Fix) {
var ggaSentence = nextGGA(parser.gpsDevice)
var ggaFields = strings.Split(ggaSentence, ",")
fix.Altitude = findAltitude(ggaFields)
fix.Satellites = findSatellites(ggaFields)
fix.Longitude = findLongitude(ggaFields)
fix.Latitude = findLatitude(ggaFields)
fix.Time = findTime(ggaFields)
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
return fix
}
// nextGGA returns the next GGA type sentence from the GPS device
// $--GGA,,,,,,,,,,,,,,*hh
func nextGGA(gpsDevice GPSDevice) (sentence string) {
for {
sentence = gpsDevice.NextSentence()
if sentence[3:6] == "GGA" {
return sentence
typ := sentence[3:6]
switch typ {
case "GGA":
fields := strings.Split(sentence, ",")
if len(fields) != 15 {
err = errInvalidGGASentence
return
}
fix.Altitude = findAltitude(fields[9])
fix.Satellites = findSatellites(fields[7])
fix.Longitude = findLongitude(fields[4], fields[5])
fix.Latitude = findLatitude(fields[2], fields[3])
fix.Time = findTime(fields[1])
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
case "RMC":
fields := strings.Split(sentence, ",")
if len(fields) != 13 {
err = errInvalidRMCSentence
return
}
fix.Longitude = findLongitude(fields[5], fields[6])
fix.Latitude = findLatitude(fields[3], fields[4])
fix.Time = findTime(fields[1])
fix.Speed = findSpeed(fields[7])
fix.Heading = findHeading(fields[8])
fix.Valid = (len(fields[2]) > 0 && fields[2][0:1] == "A")
default:
err = errUnknownNMEASentence
}
return
}
// findTime returns the time from a GGA sentence:
// findTime returns the time from an NMEA sentence:
// $--GGA,hhmmss.ss,,,,,,,,,,,,,*xx
func findTime(ggaFields []string) time.Time {
if len(ggaFields) < 1 || len(ggaFields[1]) < 6 {
func findTime(val string) time.Time {
if len(val) < 6 {
return time.Time{}
}
ts := strings.Builder{}
ts.WriteString(ggaFields[1][0:2])
ts.WriteString(":")
ts.WriteString(ggaFields[1][2:4])
ts.WriteString(":")
ts.WriteString(ggaFields[1][4:6])
var t, _ = time.Parse("15:04:05", ts.String())
h, _ := strconv.ParseInt(val[0:2], 10, 8)
m, _ := strconv.ParseInt(val[2:4], 10, 8)
s, _ := strconv.ParseInt(val[4:6], 10, 8)
ms, _ := strconv.ParseInt(val[7:10], 10, 16)
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
return t
}
// findAltitude returns the altitude from a GGA sentence:
// findAltitude returns the altitude from an NMEA sentence:
// $--GGA,,,,,,,,,25.8,,,,,*63
func findAltitude(ggaFields []string) int32 {
if len(ggaFields) > 8 && len(ggaFields[9]) > 0 {
var v, _ = strconv.ParseFloat(ggaFields[9], 32)
func findAltitude(val string) int32 {
if len(val) > 0 {
var v, _ = strconv.ParseFloat(val, 32)
return int32(v)
}
return -99999
}
// findLatitude returns the Latitude from a GGA sentence:
// findLatitude returns the Latitude from an NMEA sentence:
// $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh
func findLatitude(ggaFields []string) float32 {
if len(ggaFields) > 2 && len(ggaFields[2]) > 8 {
var dd = ggaFields[2][0:2]
var mm = ggaFields[2][2:]
func findLatitude(val, hemi string) float32 {
if len(val) > 8 {
var dd = val[0:2]
var mm = val[2:]
var d, _ = strconv.ParseFloat(dd, 32)
var m, _ = strconv.ParseFloat(mm, 32)
var v = float32(d + (m / 60))
if ggaFields[3] == "S" {
if hemi == "S" {
v *= -1
}
return v
@@ -94,16 +133,16 @@ func findLatitude(ggaFields []string) float32 {
return 0.0
}
// findLatitude returns the longitude from a GGA sentence:
// findLatitude returns the longitude from an NMEA sentence:
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
func findLongitude(ggaFields []string) float32 {
if len(ggaFields) > 4 && len(ggaFields[4]) > 8 {
var ddd = ggaFields[4][0:3]
var mm = ggaFields[4][3:]
func findLongitude(val, hemi string) float32 {
if len(val) > 8 {
var ddd = val[0:3]
var mm = val[3:]
var d, _ = strconv.ParseFloat(ddd, 32)
var m, _ = strconv.ParseFloat(mm, 32)
var v = float32(d + (m / 60))
if ggaFields[5] == "W" {
if hemi == "W" {
v *= -1
}
return v
@@ -111,14 +150,32 @@ func findLongitude(ggaFields []string) float32 {
return 0.0
}
// findSatellites returns the satellites from a GGA sentence:
// findSatellites returns the satellites from an NMEA sentence:
// $--GGA,,,,,,,nn,,,,,,,*hh
func findSatellites(ggaFields []string) (n int16) {
if len(ggaFields) > 6 && len(ggaFields[7]) > 0 {
var nn = ggaFields[7]
func findSatellites(val string) (n int16) {
if len(val) > 0 {
var nn = val
var v, _ = strconv.ParseInt(nn, 10, 32)
n = int16(v)
return n
}
return 0
}
// findSpeed returns the speed from an RMC NMEA sentence.
func findSpeed(val string) float32 {
if len(val) > 0 {
var v, _ = strconv.ParseFloat(val, 32)
return float32(v)
}
return 0
}
// findHeading returns the speed from an RMC NMEA sentence.
func findHeading(val string) float32 {
if len(val) > 0 {
var v, _ = strconv.ParseFloat(val, 32)
return float32(v)
}
return 0
}
+1 -1
View File
@@ -13,5 +13,5 @@ const (
)
const (
bufferSize = 32
bufferSize = 100
)
+11 -11
View File
@@ -24,25 +24,25 @@ var cfg_gnss_cmd = [...]byte{
0x01, 0x01, 0x06, 0x08, 0x0E, 0x00, 0x00, 0x00,
0x01, 0x01, 0xFC, 0x11}
func FlightMode(gpsDevice GPSDevice) (err error) {
err = sendCommand(gpsDevice, flight_mode_cmd[:])
func FlightMode(d Device) (err error) {
err = sendCommand(d, flight_mode_cmd[:])
return err
}
func SetCfgGNSS(gpsDevice GPSDevice) (err error) {
err = sendCommand(gpsDevice, cfg_gnss_cmd[:])
func SetCfgGNSS(d Device) (err error) {
err = sendCommand(d, cfg_gnss_cmd[:])
return err
}
func sendCommand(gpsDevice GPSDevice, command []byte) (err error) {
gpsDevice.WriteBytes(command)
func sendCommand(d Device, command []byte) (err error) {
d.WriteBytes(command)
start := time.Now()
for time.Now().Sub(start) < 1000 {
if gpsDevice.readNextByte() == '\n' {
if gpsDevice.readNextByte() == 0xB5 {
gpsDevice.readNextByte()
if gpsDevice.readNextByte() == 0x05 {
if gpsDevice.readNextByte() == 0x01 {
if d.readNextByte() == '\n' {
if d.readNextByte() == 0xB5 {
d.readNextByte()
if d.readNextByte() == 0x05 {
if d.readNextByte() == 0x01 {
return
}
}
+81
View File
@@ -0,0 +1,81 @@
// Package hcsr04 provides a driver for the HC-SR04 ultrasonic distance sensor
//
// Datasheet:
// https://cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf
package hcsr04
import (
"machine"
"time"
)
const TIMEOUT = 23324 // max sensing distance (4m)
// Device holds the pins
type Device struct {
trigger machine.Pin
echo machine.Pin
}
// New returns a new ultrasonic driver given 2 pins
func New(trigger, echo machine.Pin) Device {
return Device{
trigger: trigger,
echo: echo,
}
}
// Configure configures the pins of the Device
func (d *Device) Configure() {
d.trigger.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.echo.Configure(machine.PinConfig{Mode: machine.PinInput})
}
// ReadDistance returns the distance of the object in mm
func (d *Device) ReadDistance() int32 {
pulse := d.ReadPulse()
// sound speed is 343000 mm/s
// pulse is roundtrip measured in microseconds
// distance = velocity * time
// 2 * distance = 343000 * (pulse/1000000)
return (pulse * 1715) / 10000 //mm
}
// ReadPulse returns the time of the pulse (roundtrip) in microseconds
func (d *Device) ReadPulse() int32 {
t := time.Now()
d.trigger.Low()
time.Sleep(2 * time.Microsecond)
d.trigger.High()
time.Sleep(10 * time.Microsecond)
d.trigger.Low()
i := uint8(0)
for {
if d.echo.Get() {
t = time.Now()
break
}
i++
if i > 10 {
if time.Since(t).Microseconds() > TIMEOUT {
return 0
}
i = 0
}
}
i = 0
for {
if !d.echo.Get() {
return int32(time.Since(t).Microseconds())
}
i++
if i > 10 {
if time.Since(t).Microseconds() > TIMEOUT {
return 0
}
i = 0
}
}
return 0
}
+11 -1
View File
@@ -57,9 +57,16 @@ func (g *GPIO) SetCommandMode(set bool) {
}
}
// WriteOnly is true if you passed rw in as machine.NoPin
func (g *GPIO) WriteOnly() bool {
return g.rw == machine.NoPin
}
// Write writes len(data) bytes from data to display driver
func (g *GPIO) Write(data []byte) (n int, err error) {
g.rw.Low()
if !g.WriteOnly() {
g.rw.Low()
}
for _, d := range data {
g.write(d)
n++
@@ -89,6 +96,9 @@ func (g *GPIO) Read(data []byte) (n int, err error) {
if len(data) == 0 {
return 0, errors.New("length greater than 0 is required")
}
if g.WriteOnly() {
return 0, errors.New("Read not supported if RW not wired")
}
g.rw.High()
g.reconfigureGPIOMode(machine.PinInput)
for i := 0; i < len(data); i++ {
+60 -9
View File
@@ -11,9 +11,23 @@ import (
"time"
)
const (
// These are the default execution times for the Clear and
// Home commands and everything else.
//
// These are used if RW is passed as machine.NoPin and ignored
// otherwise.
//
// They are set conservatively here and can be tweaked in the
// Config structure.
DefaultClearHomeTime = 80 * time.Millisecond
DefaultInstrExecTime = 80 * time.Microsecond
)
type Buser interface {
io.ReadWriter
SetCommandMode(set bool)
WriteOnly() bool
}
type Device struct {
@@ -28,6 +42,9 @@ type Device struct {
cursor cursor
busyStatus []byte
clearHomeTime time.Duration // time clear/home instructions might take
instrExecTime time.Duration // time all other instructions might take
}
type cursor struct {
@@ -35,14 +52,18 @@ type cursor struct {
}
type Config struct {
Width int16
Height int16
CursorBlink bool
CursorOnOff bool
Font uint8
Width int16
Height int16
CursorBlink bool
CursorOnOff bool
Font uint8
ClearHomeTime time.Duration // time clear/home instructions might take - use 0 for the default
InstrExecTime time.Duration // time all other instructions might take - use 0 for the default
}
// NewGPIO4Bit returns 4bit data length HD44780 driver. Datapins are LCD DB pins starting from DB4 to DB7
//
// If your device has RW set permanently to ground then pass in rw as machine.NoPin
func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
const fourBitMode = 4
if len(dataPins) != fourBitMode {
@@ -52,6 +73,8 @@ func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error)
}
// NewGPIO8Bit returns 8bit data length HD44780 driver. Datapins are LCD DB pins starting from DB0 to DB7
//
// If your device has RW set permanently to ground then pass in rw as machine.NoPin
func NewGPIO8Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
const eightBitMode = 8
if len(dataPins) != eightBitMode {
@@ -68,6 +91,8 @@ func (d *Device) Configure(cfg Config) error {
if d.width == 0 || d.height == 0 {
return errors.New("width and height must be set")
}
d.clearHomeTime = cfg.ClearHomeTime
d.instrExecTime = cfg.InstrExecTime
memoryMap := uint8(ONE_LINE)
if d.height > 1 {
memoryMap = TWO_LINE
@@ -186,7 +211,7 @@ func (d *Device) SendCommand(command byte) {
d.bus.SetCommandMode(true)
d.bus.Write([]byte{command})
for d.Busy() {
for d.busy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
}
}
@@ -195,7 +220,7 @@ func (d *Device) sendData(data byte) {
d.bus.SetCommandMode(false)
d.bus.Write([]byte{data})
for d.Busy() {
for d.busy(false) {
}
}
@@ -207,13 +232,39 @@ func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) {
}
}
// Busy returns true when hd447890 is busy
func (d *Device) Busy() bool {
// busy returns true when hd447890 is busy
// or after the timeout specified
func (d *Device) busy(longDelay bool) bool {
if d.bus.WriteOnly() {
// Can't read busy flag if write only, so sleep a bit then return
if longDelay {
// Note that we sleep like this so the default
// time.Sleep is time.Sleep(constant) as
// time.Sleep(variable) doesn't seem to work on AVR yet
if d.clearHomeTime != 0 {
time.Sleep(d.clearHomeTime)
} else {
time.Sleep(DefaultClearHomeTime)
}
} else {
if d.instrExecTime != 0 {
time.Sleep(d.instrExecTime)
} else {
time.Sleep(DefaultInstrExecTime)
}
}
return false
}
d.bus.SetCommandMode(true)
d.bus.Read(d.busyStatus)
return (d.busyStatus[0] & BUSY) > 0
}
// Busy returns true when hd447890 is busy
func (d *Device) Busy() bool {
return d.busy(false)
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return int16(d.width), int16(d.height)
+243
View File
@@ -0,0 +1,243 @@
// Package hd44780i2c implements a driver for the Hitachi HD44780 LCD display module
// with an I2C adapter.
//
// Datasheet: https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
//
package hd44780i2c
import (
"errors"
"time"
"tinygo.org/x/drivers"
)
// Device wraps an I2C connection to a HD44780 I2C LCD with related data.
type Device struct {
bus drivers.I2C
addr uint8
width uint8
height uint8
cursor cursor
backlight uint8
displayfunction uint8
displaycontrol uint8
displaymode uint8
}
type cursor struct {
x, y uint8
}
// Config for HD44780 I2C LCD.
type Config struct {
Width uint8
Height uint8
Font uint8
CursorOn bool
CursorBlink bool
}
// New creates a new HD44780 I2C LCD connection. The I2C bus must already be
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C, addr uint8) Device {
if addr == 0 {
addr = 0x27
}
return Device{
bus: bus,
addr: addr,
}
}
// Configure sets up the display. Display itself and backlight is default on.
func (d *Device) Configure(cfg Config) error {
if cfg.Width == 0 || cfg.Height == 0 {
return errors.New("width and height must be set")
}
d.width = uint8(cfg.Width)
d.height = uint8(cfg.Height)
delayms(50)
d.backlight = BACKLIGHT_ON
d.expanderWrite(0)
delayms(1000)
d.write4bits(0x03 << 4)
delayus(4500)
d.write4bits(0x03 << 4)
delayus(4500)
d.write4bits(0x03 << 4)
delayus(150)
d.write4bits(0x02 << 4)
d.displayfunction = DATA_LENGTH_4BIT | ONE_LINE | FONT_5X8
if d.height > 1 {
d.displayfunction |= TWO_LINE
}
if cfg.Font != 0 && d.height == 1 {
d.displayfunction |= FONT_5X10
}
d.sendCommand(FUNCTION_MODE | d.displayfunction)
d.displaycontrol = DISPLAY_ON | CURSOR_OFF | CURSOR_BLINK_OFF
if cfg.CursorOn {
d.displaycontrol |= CURSOR_ON
}
if cfg.CursorBlink {
d.displaycontrol |= CURSOR_BLINK_ON
}
d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol)
d.ClearDisplay()
d.displaymode = CURSOR_INCREASE | DISPLAY_NO_SHIFT
d.sendCommand(ENTRY_MODE | d.displaymode)
d.Home()
return nil
}
// ClearDisplay clears all texts on the display.
func (d *Device) ClearDisplay() {
d.sendCommand(DISPLAY_CLEAR)
d.cursor.x = 0
d.cursor.y = 0
delayus(2000)
}
// Home sets the cursor back to position (0, 0).
func (d *Device) Home() {
d.sendCommand(CURSOR_HOME)
d.cursor.x = 0
d.cursor.y = 0
delayus(2000)
}
// SetCursor sets the cursor to a specific position (x, y).
//
// if y (row) is set larger than actual rows, it would be set to 0.
func (d *Device) SetCursor(x, y uint8) {
rowOffset := []uint8{0x0, 0x40, 0x14, 0x54}
if y > (d.height - 1) {
y = 0
}
d.cursor.x = x
d.cursor.y = y
d.sendCommand(DDRAM_SET | (x + (rowOffset[y])))
}
// Print prints text on the display (started from current cursor position).
//
// It would automatically break to new line when the text is too long.
// You can also use \n as line breakers.
func (d *Device) Print(data []byte) {
for _, chr := range data {
if chr == '\n' {
d.newLine()
} else {
d.cursor.x++
if d.cursor.x >= d.width {
d.newLine()
}
d.sendData(uint8(rune(chr)))
}
}
}
// CreateCharacter crates custom characters (using data parameter)
// and stores it under CGRAM address (using cgramAddr, 0x0-0x7).
func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) {
cgramAddr &= 0x7
d.sendCommand(CGRAM_SET | cgramAddr<<3)
for _, dd := range data {
d.sendData(dd)
}
d.SetCursor(d.cursor.x, d.cursor.y)
}
// DisplayOn turns on/off the display.
func (d *Device) DisplayOn(option bool) {
if option {
d.displaycontrol |= DISPLAY_ON
} else {
d.displaycontrol &= ^uint8(DISPLAY_ON)
}
d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol)
}
// CursorOn display/hides the cursor.
func (d *Device) CursorOn(option bool) {
if option {
d.displaycontrol |= CURSOR_ON
} else {
d.displaycontrol &= ^uint8(CURSOR_ON)
}
d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol)
}
// CursorBlink turns on/off the blinking cursor mode.
func (d *Device) CursorBlink(option bool) {
if option {
d.displaycontrol |= CURSOR_BLINK_ON
} else {
d.displaycontrol &= ^uint8(CURSOR_BLINK_ON)
}
d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol)
}
// BacklightOn turns on/off the display backlight.
func (d *Device) BacklightOn(option bool) {
if option {
d.backlight = BACKLIGHT_ON
} else {
d.backlight = BACKLIGHT_OFF
}
d.expanderWrite(0)
}
func (d *Device) newLine() {
d.cursor.x = 0
d.cursor.y++
d.SetCursor(d.cursor.x, d.cursor.y)
}
func delayms(t uint16) {
time.Sleep(time.Millisecond * time.Duration(t))
}
func delayus(t uint16) {
time.Sleep(time.Microsecond * time.Duration(t))
}
func (d *Device) expanderWrite(value uint8) {
d.bus.Tx(uint16(d.addr), []uint8{value | d.backlight}, nil)
}
func (d *Device) pulseEnable(value uint8) {
d.expanderWrite(value | En)
delayus(1)
d.expanderWrite(value & ^uint8(En))
delayus(50)
}
func (d *Device) write4bits(value uint8) {
d.expanderWrite(value)
d.pulseEnable(value)
}
func (d *Device) write(value uint8, mode uint8) {
d.write4bits(uint8(value&0xf0) | mode)
d.write4bits(uint8((value<<4)&0xf0) | mode)
}
func (d *Device) sendCommand(value uint8) {
d.write(value, 0)
}
func (d *Device) sendData(value uint8) {
d.write(value, Rs)
}
+44
View File
@@ -0,0 +1,44 @@
package hd44780i2c
const (
// commands
DISPLAY_CLEAR = 0x01
CURSOR_HOME = 0x02
ENTRY_MODE = 0x04
DISPLAY_ON_OFF = 0x08
CURSOR_DISPLAY_SHIFT = 0x10
FUNCTION_MODE = 0x20
CGRAM_SET = 0x40
DDRAM_SET = 0x80
// flags for display entry mode
// CURSOR_DECREASE = 0x00
CURSOR_INCREASE = 0x02
// DISPLAY_SHIFT = 0x01
DISPLAY_NO_SHIFT = 0x00
// flags for display on/off control
DISPLAY_ON = 0x04
DISPLAY_OFF = 0x00
CURSOR_ON = 0x02
CURSOR_OFF = 0x00
CURSOR_BLINK_ON = 0x01
CURSOR_BLINK_OFF = 0x00
// flags for function set
// DATA_LENGTH_8BIT = 0x10
DATA_LENGTH_4BIT = 0x00
TWO_LINE = 0x08
ONE_LINE = 0x00
FONT_5X10 = 0x04
FONT_5X8 = 0x00
// flags for backlight control
BACKLIGHT_ON = 0x08
BACKLIGHT_OFF = 0x00
En = 0x04 // Enable bit
// Rw = 0x02 // Read/Write bit
Rs = 0x01 // Register select bit
)
+4 -2
View File
@@ -9,6 +9,8 @@ import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
)
type Config struct {
@@ -21,7 +23,7 @@ type Config struct {
}
type Device struct {
bus machine.SPI
bus drivers.SPI
a machine.Pin
b machine.Pin
c machine.Pin
@@ -52,7 +54,7 @@ type Device struct {
}
// New returns a new HUB75 driver. Pass in a fully configured SPI bus.
func New(b machine.SPI, latPin, oePin, aPin, bPin, cPin, dPin machine.Pin) Device {
func New(b drivers.SPI, latPin, oePin, aPin, bPin, cPin, dPin machine.Pin) Device {
aPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
bPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
cPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
+9
View File
@@ -0,0 +1,9 @@
package drivers
// I2C represents an I2C bus. It is notably implemented by the
// machine.I2C type.
type I2C interface {
ReadRegister(addr uint8, r uint8, buf []byte) error
WriteRegister(addr uint8, r uint8, buf []byte) error
Tx(addr uint16, w, r []byte) error
}
+32 -13
View File
@@ -19,12 +19,16 @@ type Device struct {
rotation Rotation
driver driver
x0, x1 int16 // cached address window; prevents useless/expensive
y0, y1 int16 // syscalls to PASET and CASET
dc machine.Pin
cs machine.Pin
rst machine.Pin
rd machine.Pin
}
// Configure prepares display for use
func (d *Device) Configure(config Config) {
if config.Width == 0 {
@@ -37,6 +41,10 @@ func (d *Device) Configure(config Config) {
d.height = config.Height
d.rotation = config.Rotation
// try to pick an initial cache miss for one of the points
d.x0, d.x1 = -(d.width + 1), d.x0
d.y0, d.y1 = -(d.height + 1), d.y0
output := machine.PinConfig{machine.PinOutput}
// configure chip select if there is one
@@ -138,6 +146,7 @@ func (d *Device) Display() error {
return nil
}
// DrawRGBBitmap copies an RGB bitmap to the internal buffer at given coordinates
func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
k, i := d.Size()
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
@@ -151,7 +160,7 @@ func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
return nil
}
// FillRectangle fills a rectangle at a given coordinates with a color
// FillRectangle fills a rectangle at given coordinates with a color
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
k, i := d.Size()
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
@@ -166,7 +175,7 @@ func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
return nil
}
// DrawRectangle fills a rectangle at a given coordinates with a color
// DrawRectangle draws a rectangle at given coordinates with a color
func (d *Device) DrawRectangle(x, y, w, h int16, c color.RGBA) error {
if err := d.DrawFastHLine(x, x+w-1, y, c); err != nil {
return err
@@ -208,6 +217,7 @@ func (d *Device) FillScreen(c color.RGBA) {
}
}
// GetRotation returns the current rotation of the device
func (d *Device) GetRotation() Rotation {
return d.rotation
}
@@ -229,7 +239,8 @@ func (d *Device) SetRotation(rotation Rotation) {
d.rotation = rotation
}
// SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display
// SetScrollArea sets an area to scroll with fixed top/bottom or left/right parts of the display
// Rotation affects scroll direction
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
d.sendCommand(VSCRDEF, []uint8{
uint8(topFixedArea >> 8), uint8(topFixedArea),
@@ -244,7 +255,7 @@ func (d *Device) SetScroll(line int16) {
d.sendCommand(VSCRSADD, []uint8{uint8(line >> 8), uint8(line)})
}
// SpotScroll returns the display to its normal state
// StopScroll returns the display to its normal state
func (d *Device) StopScroll() {
d.sendCommand(NORON, nil)
}
@@ -253,12 +264,20 @@ func (d *Device) StopScroll() {
func (d *Device) setWindow(x, y, w, h int16) {
//x += d.columnOffset
//y += d.rowOffset
d.sendCommand(CASET, []uint8{
uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1),
})
d.sendCommand(PASET, []uint8{
uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1),
})
x1 := x + w - 1
if x != d.x0 || x1 != d.x1 {
d.sendCommand(CASET, []uint8{
uint8(x >> 8), uint8(x), uint8(x1 >> 8), uint8(x1),
})
d.x0, d.x1 = x, x1
}
y1 := y + h - 1
if y != d.y0 || y1 != d.y1 {
d.sendCommand(PASET, []uint8{
uint8(y >> 8), uint8(y), uint8(y1 >> 8), uint8(y1),
})
d.y0, d.y1 = y, y1
}
d.sendCommand(RAMWR, nil)
}
@@ -281,15 +300,15 @@ func (d *Device) sendCommand(cmd byte, data []byte) {
d.dc.Low()
d.driver.write8(cmd)
d.dc.High()
for _, b := range data {
d.driver.write8(b)
}
d.driver.write8sl(data)
d.endWrite()
}
type driver interface {
configure(config *Config)
write8(b byte)
write8n(b byte, n int)
write8sl(b []byte)
write16(data uint16)
write16n(data uint16, n int)
write16sl(data []uint16)
+28 -6
View File
@@ -5,14 +5,14 @@ package ili9341
import (
"machine"
"runtime/volatile"
"unsafe"
)
type parallelDriver struct {
d0 machine.Pin
wr machine.Pin
setPort *uint32
setMask uint32
setPort *uint8
clrPort *uint32
clrMask uint32
@@ -46,8 +46,12 @@ func (pd *parallelDriver) configure(config *Config) {
pd.wr.Configure(output)
pd.wr.High()
pd.setPort, _ = pd.d0.PortMaskSet()
pd.setMask = uint32(pd.d0) & 0x1f
// Calculates the address of the OUT register from the OUTSET register and obtains an address that allows 8-bit access.
// OUT : offset = 0x10
// OUTSET : offset = 0x18
setPort, _ := pd.d0.PortMaskSet()
setMask := uint32(pd.d0) & 0x1f
pd.setPort = (*uint8)(unsafe.Pointer(uintptr(unsafe.Pointer(setPort)) - uintptr(8) + uintptr(setMask/8)))
pd.clrPort, _ = (pd.d0).PortMaskClear()
pd.clrMask = 0xFF << uint32(pd.d0)
@@ -58,12 +62,30 @@ func (pd *parallelDriver) configure(config *Config) {
//go:inline
func (pd *parallelDriver) write8(b byte) {
volatile.StoreUint32(pd.clrPort, pd.clrMask)
volatile.StoreUint32(pd.setPort, uint32(b)<<pd.setMask)
volatile.StoreUint8(pd.setPort, uint8(b))
pd.wrx()
}
//go:inline
func (pd *parallelDriver) wrx() {
volatile.StoreUint32(pd.wrPortClr, pd.wrMaskClr)
volatile.StoreUint32(pd.wrPortSet, pd.wrMaskSet)
}
//go:inline
func (pd *parallelDriver) write8n(b byte, n int) {
for i := 0; i < n; i++ {
pd.write8(b)
}
}
//go:inline
func (pd *parallelDriver) write8sl(b []byte) {
for i := 0; i < len(b); i++ {
pd.write8(b[i])
}
}
//go:inline
func (pd *parallelDriver) write16(data uint16) {
pd.write8(byte(data >> 8))
+116
View File
@@ -0,0 +1,116 @@
// +build atsamd21
package ili9341
import (
"device/sam"
"machine"
)
type spiDriver struct {
bus machine.SPI
}
func NewSPI(bus machine.SPI, dc, cs, rst machine.Pin) *Device {
return &Device{
dc: dc,
cs: cs,
rst: rst,
rd: machine.NoPin,
driver: &spiDriver{
bus: bus,
},
}
}
func (pd *spiDriver) configure(config *Config) {
}
func (pd *spiDriver) write8(b byte) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPI_CTRLB_RXEN)
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b))
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPI_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_CTRLB) {
}
}
func (pd *spiDriver) write8n(b byte, n int) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPI_CTRLB_RXEN)
for i, c := 0, n; i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPI_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_CTRLB) {
}
}
func (pd *spiDriver) write8sl(b []byte) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPI_CTRLB_RXEN)
for i, c := 0, len(b); i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b[i]))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPI_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_CTRLB) {
}
}
func (pd *spiDriver) write16(data uint16) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPI_CTRLB_RXEN)
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data)))
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPI_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_CTRLB) {
}
}
func (pd *spiDriver) write16n(data uint16, n int) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPI_CTRLB_RXEN)
for i := 0; i < n; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data)))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPI_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_CTRLB) {
}
}
func (pd *spiDriver) write16sl(data []uint16) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPI_CTRLB_RXEN)
for i, c := 0, len(data); i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data[i] >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data[i])))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPI_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_CTRLB) {
}
}
+116
View File
@@ -0,0 +1,116 @@
// +build atsamd51
package ili9341
import (
"device/sam"
"machine"
)
type spiDriver struct {
bus machine.SPI
}
func NewSPI(bus machine.SPI, dc, cs, rst machine.Pin) *Device {
return &Device{
dc: dc,
cs: cs,
rst: rst,
rd: machine.NoPin,
driver: &spiDriver{
bus: bus,
},
}
}
func (pd *spiDriver) configure(config *Config) {
}
func (pd *spiDriver) write8(b byte) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b))
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
}
func (pd *spiDriver) write8n(b byte, n int) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i, c := 0, n; i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
}
func (pd *spiDriver) write8sl(b []byte) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i, c := 0, len(b); i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(b[i]))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
}
func (pd *spiDriver) write16(data uint16) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data)))
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
}
func (pd *spiDriver) write16n(data uint16, n int) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i := 0; i < n; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data)))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
}
func (pd *spiDriver) write16sl(data []uint16) {
pd.bus.Bus.CTRLB.ClearBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for i, c := 0, len(data); i < c; i++ {
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data[i] >> 8)))
for !pd.bus.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) {
}
pd.bus.Bus.DATA.Set(uint32(uint8(data[i])))
}
pd.bus.Bus.CTRLB.SetBits(sam.SERCOM_SPIM_CTRLB_RXEN)
for pd.bus.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_CTRLB) {
}
}
+126
View File
@@ -0,0 +1,126 @@
// Package lis2mdl implements a driver for the LIS2MDL,
// a magnetic sensor which is included on BBC micro:bit v1.5.
//
// Datasheet: https://www.st.com/resource/en/datasheet/lis2mdl.pdf
//
package lis2mdl // import "tinygo.org/x/drivers/lis2mdl"
import (
"math"
"time"
"tinygo.org/x/drivers"
)
// Device wraps an I2C connection to a LIS2MDL device.
type Device struct {
bus drivers.I2C
Address uint8
PowerMode uint8
SystemMode uint8
DataRate uint8
}
// Configuration for LIS2MDL device.
type Configuration struct {
PowerMode uint8
SystemMode uint8
DataRate uint8
}
// New creates a new LIS2MDL connection. The I2C bus must already be
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) Device {
return Device{bus: bus, Address: ADDRESS}
}
// Connected returns whether LIS2MDL sensor has been found.
func (d *Device) Connected() bool {
data := []byte{0}
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
return data[0] == 0x40
}
// Configure sets up the LIS2MDL device for communication.
func (d *Device) Configure(cfg Configuration) {
if cfg.PowerMode != 0 {
d.PowerMode = cfg.PowerMode
} else {
d.PowerMode = POWER_NORMAL
}
if cfg.DataRate != 0 {
d.DataRate = cfg.DataRate
} else {
d.DataRate = DATARATE_100HZ
}
if cfg.SystemMode != 0 {
d.SystemMode = cfg.SystemMode
} else {
d.SystemMode = SYSTEM_CONTINUOUS
}
cmd := []byte{0}
// reset
cmd[0] = byte(1 << 5)
d.bus.WriteRegister(uint8(d.Address), CFG_REG_A, cmd)
time.Sleep(100 * time.Millisecond)
// reboot
cmd[0] = byte(1 << 6)
d.bus.WriteRegister(uint8(d.Address), CFG_REG_A, cmd)
time.Sleep(100 * time.Millisecond)
// bdu
cmd[0] = byte(1 << 4)
d.bus.WriteRegister(uint8(d.Address), CFG_REG_C, cmd)
// Temperature compensation is on for magnetic sensor (0x80)
cmd[0] = byte(0x80)
d.bus.WriteRegister(uint8(d.Address), CFG_REG_A, cmd)
// speed
cmd[0] = byte(0x80 | d.DataRate)
d.bus.WriteRegister(uint8(d.Address), CFG_REG_A, cmd)
}
// ReadMagneticField reads the current magnetic field from the device and returns
// it in mG (milligauss). 1 mG = 0.1 µT (microtesla).
func (d *Device) ReadMagneticField() (x int32, y int32, z int32) {
// turn back on read mode, even though it is supposed to be continuous?
cmd := []byte{0}
cmd[0] = byte(0x80 | d.PowerMode<<4 | d.DataRate<<2 | d.SystemMode)
d.bus.WriteRegister(uint8(d.Address), CFG_REG_A, cmd)
time.Sleep(10 * time.Millisecond)
data := make([]byte, 6)
d.bus.ReadRegister(uint8(d.Address), OUTX_L_REG, data)
x = int32(int16((uint16(data[0]) << 8) | uint16(data[1])))
y = int32(int16((uint16(data[2]) << 8) | uint16(data[3])))
z = int32(int16((uint16(data[4]) << 8) | uint16(data[5])))
return
}
// ReadCompass reads the current compass heading from the device and returns
// it in degrees. When the z axis is pointing straight to Earth and
// the y axis is pointing to North, the heading would be zero.
//
// However, the heading may be off due to electronic compasses would be effected
// by strong magnetic fields and require constant calibration.
func (d *Device) ReadCompass() (h int32) {
x, y, _ := d.ReadMagneticField()
xf, yf := float64(x)*0.15, float64(y)*0.15
rh := (math.Atan2(yf, xf) * 180) / math.Pi
if rh < 0 {
rh = 360 + rh
}
return int32(rh)
}
+59
View File
@@ -0,0 +1,59 @@
package lis2mdl
import (
"testing"
qt "github.com/frankban/quicktest"
"tinygo.org/x/drivers/tester"
)
func TestDefaultI2CAddress(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
dev := New(bus)
c.Assert(dev.Address, qt.Equals, uint8(ADDRESS))
}
func TestWhoAmI(t *testing.T) {
c := qt.New(t)
bus := tester.NewI2CBus(c)
fake := tester.NewI2CDevice(c, ADDRESS)
copy(fake.Registers[:], defaultRegisters())
bus.AddDevice(fake)
dev := New(bus)
c.Assert(dev.Connected(), qt.Equals, true)
fake.Registers[WHO_AM_I] = 0x99
c.Assert(dev.Connected(), qt.Equals, false)
}
// defaultRegisters returns the default values for all of the device's registers.
// see table 22 on page 27 of the datasheet.
func defaultRegisters() []uint8 {
return []uint8{
OFFSET_X_REG_L: 0,
OFFSET_X_REG_H: 0,
OFFSET_Y_REG_L: 0,
OFFSET_Y_REG_H: 0,
OFFSET_Z_REG_L: 0,
OFFSET_Z_REG_H: 0,
WHO_AM_I: 0x40,
CFG_REG_A: 0x03,
CFG_REG_B: 0,
CFG_REG_C: 0,
INT_CRTL_REG: 0xE0,
INT_SOURCE_REG: 0,
INT_THS_L_REG: 0,
INT_THS_H_REG: 0,
STATUS_REG: 0,
OUTX_L_REG: 0,
OUTX_H_REG: 0,
OUTY_L_REG: 0,
OUTY_H_REG: 0,
OUTZ_L_REG: 0,
OUTZ_H_REG: 0,
TEMP_OUT_L_REG: 0,
TEMP_OUT_H_REG: 0,
}
}

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