mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 850df0a25c | |||
| 5f4806f0f9 | |||
| c8e62562b5 | |||
| 6842bdb424 | |||
| d43263f764 | |||
| 6716bb6c0a | |||
| f4bccd1fed | |||
| d5aa295b76 | |||
| c6e8af3057 | |||
| 38076352eb | |||
| 086415605e | |||
| 1d0f04af6b | |||
| dc883d913d | |||
| 3bb5b4519b | |||
| c12f78eb50 | |||
| adb0c2c261 | |||
| 18f0722728 | |||
| e80a22d0ba | |||
| cc5ecafacf | |||
| 7b56e61d52 | |||
| 514b436889 | |||
| 7233452819 | |||
| 7929aa10ea | |||
| 02084fd8a5 | |||
| f822da51fe | |||
| c62d7db35d | |||
| 93372474a2 | |||
| 7dcbfbecc6 | |||
| 2e606b090a | |||
| e0cdc931e7 | |||
| 130d9de03b | |||
| 2c2f1d3db4 | |||
| 2413eb86e0 | |||
| c7555a1469 | |||
| 3fca96e0ef | |||
| c7981f72ec | |||
| 5df157230f | |||
| bcb291992c | |||
| 7b710e3a48 | |||
| dcfd9c066d | |||
| 955b3a56e8 | |||
| 21b8d953f4 | |||
| d1b917b835 | |||
| 2cd73e3204 | |||
| 3ae5895183 | |||
| d80f619c9f | |||
| c91888a099 | |||
| b4dbac3a67 | |||
| bf077c8249 | |||
| 4867abcbba | |||
| 45922f6524 | |||
| eb040dde9c | |||
| 04bfa6fa70 | |||
| 8453611d1f | |||
| d64069a517 | |||
| e4b80d8e0e | |||
| 50633f3e86 | |||
| 7e78e2c998 | |||
| 40d9287ac4 | |||
| 59aece351a | |||
| 1b81b992c2 | |||
| 506819c93c | |||
| 1d9e393948 | |||
| 9c88d1fab4 | |||
| a267fdb8ce | |||
| 500f3d9813 | |||
| fe58e9b762 | |||
| d8675515bc | |||
| 28dbbe2e65 | |||
| 78fdaad9c0 | |||
| cb49783f18 | |||
| 8534e67c83 | |||
| 95755ebae7 | |||
| f4583f5144 | |||
| 1e59a3970e |
+115
@@ -1,3 +1,118 @@
|
||||
0.10.0
|
||||
---
|
||||
- **new devices**
|
||||
- adt7410: Support for ADT7410 temperature sensor (#109)
|
||||
- ili9341: ILI9341 TFT driver (#115)
|
||||
- l293x: added support for h-bridge motor controller
|
||||
- l9110x: add support for L9110x h-bridge motor driver
|
||||
- resistive: Adding driver for four-wire resistive touchscreen (#118)
|
||||
- **enhancements**
|
||||
- st7735: added scroll functionality to st7735
|
||||
- st7735: remove default offsets
|
||||
- st7789: remove default offsets
|
||||
- ws2812: Added nrf52840 tag to ws2812
|
||||
- ws2812: work-arounds to allow Digispark to control WS2812 LEDs
|
||||
- **docs**
|
||||
- readme: update README to include list of all 44 drivers
|
||||
- wifinina: update docs and add Dockerfile to build firmware
|
||||
- wifinina: update docs and info on how to install WiFiNINA driver
|
||||
|
||||
0.9.0
|
||||
---
|
||||
- **new devices**
|
||||
- net: shared implementation of net package for serial wifi devices
|
||||
- shifter: add support for bit Parallel In Serial Out (PISO) shifter
|
||||
- stepper: add support for dual stepper motor
|
||||
- wifinina: add implementation for WiFiNINA firmware
|
||||
- **enhancements**
|
||||
- st7735: improvements in st7735 driver
|
||||
- st7789: improvements in st7789 driver
|
||||
- ws2812: add support for 120Mhz Cortex-M4
|
||||
- ws2812: added Feather M0 and Trinket M0 to build tags for WS2812
|
||||
- ws2812: add support for simulation
|
||||
- **bugfixes**
|
||||
- ws2812: fix "invalid symbol redefinition" error
|
||||
- **examples**
|
||||
- Add examples for wifinina drivers
|
||||
|
||||
0.8.0
|
||||
---
|
||||
- **new devices**
|
||||
- mcp3008: add implementation for MCP3008 ADC with SPI interface
|
||||
- semihosting: initial implementation of ARM semihosting
|
||||
- **enhancements**
|
||||
- espat: refactor response processing for greater speed and efficiency
|
||||
- espat: implement mqtt subscribe functionality via blocking select/channels (experiemental)
|
||||
- **bugfixes**
|
||||
- st7789: fix index out of bounds error
|
||||
- **examples**
|
||||
- Add espat driver example for mqtt subscribe
|
||||
|
||||
0.7.0
|
||||
---
|
||||
- **new devices**
|
||||
- veml6070: add Vishay UV light sensor
|
||||
- **enhancements**
|
||||
- lis3dh: example uses I2C1 so requires config to specify pins since they are not default
|
||||
- ssd1331: make SPI TX faster
|
||||
- st7735: make SPI Tx faster
|
||||
- **docs**
|
||||
- complete missing GoDocs for main and sub-packages
|
||||
- **core**
|
||||
- add Version string for support purposes
|
||||
- **examples**
|
||||
- Change all espat driver examples to use Arduino Nano33 IoT by default
|
||||
|
||||
0.6.0
|
||||
---
|
||||
- **new devices**
|
||||
- Support software SPI for APA102 (Itsy Bitsy M0 on-board "Dotstar" LED as example)
|
||||
|
||||
0.5.0
|
||||
---
|
||||
- **new devices**
|
||||
- LSM6DS3 accelerometer
|
||||
- **bugfixes**
|
||||
- ws2812: fix timings for the nrf51
|
||||
- **enhancements**
|
||||
- ws2812: Add build tag for Arduino Nano33 IoT
|
||||
|
||||
0.4.0
|
||||
---
|
||||
- **new devices**
|
||||
- SSD1331 TFT color display
|
||||
- ST7735 TFT color display
|
||||
- ST7789 TFT color display
|
||||
- **docs**
|
||||
- espat
|
||||
- complete list of dependencies for flashing NINA-W102 as used in Arduino Nano33 IoT board.
|
||||
|
||||
0.3.0
|
||||
---
|
||||
- **new devices**
|
||||
- Buzzer for piezo or small speaker
|
||||
- PDM MEMS microphone support using I2S interface
|
||||
- **enhancements**
|
||||
- epd2in13: added rotation
|
||||
- espat
|
||||
- add built-in support for MQTT publish using the Paho library packets, alongside some modifications needed for the AT protocol.
|
||||
- add DialTLS and Dial methods, update MQTT example to allow both MQTT and MQTTS connections
|
||||
- add example that uses MQTT publish to open server
|
||||
- add README with information on how to flash ESP32 or ESP8266 with AT command set firmware.
|
||||
- add ResolveUDPAddr and ResolveTCPAddr implementations using AT command for DNS lookup
|
||||
- change Response() method to use a passed-in timeout value instead of fixed pauses.
|
||||
- implement TCPConn using AT command set
|
||||
- improve error handling for key TCP functions
|
||||
- refactor net and tls interface compatible code into separate sub-packages
|
||||
- update MQTT example for greater stability
|
||||
- use only AT commands that work on both ESP8266 and ESP32
|
||||
- add documentation on how to use Arduino Nano33 IoT built-in WiFi NINA-W102 chip.
|
||||
- **bugfixes**
|
||||
- core: Error strings should not be capitalized (unless beginning with proper nouns or acronyms) or end with punctuation, since they are usually printed following other context.
|
||||
- docs: add note to current/future contributors to please start by opening a GH issue to avoid duplication of efforts
|
||||
- examples: typo in package name of examples
|
||||
- mpu6050: properly scale the outputs of the accel/gyro
|
||||
|
||||
0.2.0
|
||||
---
|
||||
- **new devices**
|
||||
|
||||
@@ -16,6 +16,8 @@ Please open a Github issue with your problem, and we will be happy to assist.
|
||||
|
||||
We probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
||||
|
||||
Please first open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -9,39 +9,115 @@ fmt-check:
|
||||
|
||||
smoke-test:
|
||||
@mkdir -p build
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/adxl345/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/apa102/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/at24cx/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/bh1750/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/blinkm/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/bmp180/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=bluepill ./examples/ds1307/sram/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=bluepill ./examples/ds1307/time/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/ds3231/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/easystepper/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/espat/espconsole/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/espat/esphub/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/espat/espstation/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=feather-m0 ./examples/gps/i2c/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=feather-m0 ./examples/gps/uart/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/hd44780/customchar/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/hd44780/text/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/hub75/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/lis3dh/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/mag3110/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/microbitmatrix/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/mma8653/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/mpu6050/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/pcd8544/setbuffer/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/pcd8544/setpixel/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/sht3x/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/ssd1306/i2c_128x32/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/ssd1306/spi_128x64/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/thermistor/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/vl53l1x/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/waveshare-epd/epd2in13/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/ws2812/main.go
|
||||
tinygo build -size short -o ./build/test.elf -target=trinket-m0 ./examples/bme280/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adt7410/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adxl345/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=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
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/at24cx/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bh1750/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/blinkm/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp180/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/sram/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/time/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/easystepper/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/espconsole/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/esphub/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/espstation/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=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=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=microbit ./examples/hub75/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/basic/main.go
|
||||
@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=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/mcp3008/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/microbitmatrix/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mma8653/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mpu6050/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setbuffer/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/i2c_128x32/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/spi_128x64/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1331/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7735/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/thermistor/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/fourwire/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/pyportal_touchpaint/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl53l1x/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/tcpclient/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/webclient/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/microphone/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/buzzer/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/veml6070/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l293x/simple/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l293x/speed/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l9110x/simple/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l9110x/speed/main.go
|
||||
@md5sum ./build/test.hex
|
||||
|
||||
test: clean fmt-check smoke-test
|
||||
|
||||
@@ -52,8 +52,11 @@ func main() {
|
||||
|
||||
## Currently supported devices
|
||||
|
||||
The following 44 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 |
|
||||
| [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 |
|
||||
@@ -61,21 +64,37 @@ func main() {
|
||||
| [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 |
|
||||
| [BMP180 barometer](https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf) | I2C |
|
||||
| [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | 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 |
|
||||
| [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 |
|
||||
| [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 |
|
||||
| [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 |
|
||||
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
|
||||
| [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO |
|
||||
| [Semihosting](https://wiki.segger.com/Semihosting) | Debug |
|
||||
| [Shift register](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | 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 |
|
||||
| [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 |
|
||||
| [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 |
|
||||
| [Thermistor](https://www.farnell.com/datasheets/33552.pdf) | ADC |
|
||||
| [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 |
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package adt7410
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Error uint8
|
||||
|
||||
const (
|
||||
ErrInvalidID Error = 0x1
|
||||
)
|
||||
|
||||
func (e Error) Error() string {
|
||||
switch e {
|
||||
case ErrInvalidID:
|
||||
return "Invalid chip ID"
|
||||
default:
|
||||
return "Unknown error"
|
||||
}
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
bus *machine.I2C
|
||||
buf []byte
|
||||
addr 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
|
||||
// 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 {
|
||||
return &Device{
|
||||
bus: i2c,
|
||||
buf: make([]byte, 2),
|
||||
addr: Address | (addressBits & 0x3),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
//}
|
||||
|
||||
// reset the chip
|
||||
dev.writeByte(RegReset, 0xFF)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (ºC/1000)
|
||||
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
|
||||
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
|
||||
func (d *Device) ReadTempF() float32 {
|
||||
return d.ReadTempC()*1.8 + 32.0
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (d *Device) readByte(reg uint8) byte {
|
||||
d.bus.ReadRegister(d.addr, reg, d.buf)
|
||||
return d.buf[0]
|
||||
}
|
||||
|
||||
func (d *Device) readUint16(reg uint8) uint16 {
|
||||
d.bus.ReadRegister(d.addr, reg, d.buf)
|
||||
return uint16(d.buf[0])<<8 | uint16(d.buf[1])
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package adt7410
|
||||
|
||||
const (
|
||||
// Default I2C address
|
||||
Address = 0x48
|
||||
|
||||
// Temperature Value MSB Register
|
||||
RegTempValueMSB = 0x0
|
||||
|
||||
// Temperature Value LSB Register
|
||||
RegTempValueLSB = 0x1
|
||||
|
||||
// Status Register
|
||||
RegStatus = 0x2
|
||||
|
||||
// Config Register
|
||||
RegConfig = 0x3
|
||||
|
||||
// ID Register
|
||||
RegID = 0x0B
|
||||
|
||||
// Software Reset Register
|
||||
RegReset = 0x2F
|
||||
)
|
||||
+15
-2
@@ -21,15 +21,28 @@ const (
|
||||
|
||||
// Device wraps APA102 SPI LEDs.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
bus 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 machine.SPI) Device {
|
||||
func New(b 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})
|
||||
}
|
||||
|
||||
// WriteColors writes the given RGBA color slice out using the APA102 protocol.
|
||||
// The A value (Alpha channel) is used for brightness, set to 0xff (255) for maximum.
|
||||
func (d Device) WriteColors(cs []color.RGBA) (n int, err error) {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package apa102
|
||||
|
||||
import "machine"
|
||||
|
||||
// bbSPI is a dumb bit-bang implementation of SPI protocol that is hardcoded
|
||||
// to mode 0 and ignores trying to receive data. Just enough for the APA102.
|
||||
// Note: making this unexported for now because it is probable not suitable
|
||||
// most purposes other than the APA102 package. It might be desirable to make
|
||||
// this more generic and include it in the TinyGo "machine" package instead.
|
||||
type bbSPI struct {
|
||||
SCK machine.Pin
|
||||
MOSI machine.Pin
|
||||
Delay uint32
|
||||
}
|
||||
|
||||
// Configure sets up the SCK and MOSI 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.SCK.Low()
|
||||
s.MOSI.Low()
|
||||
if s.Delay == 0 {
|
||||
s.Delay = 1
|
||||
}
|
||||
}
|
||||
|
||||
// Tx matches signature of machine.SPI.Tx() and is used to send multiple bytes.
|
||||
// The r slice is ignored and no error will ever be returned.
|
||||
func (s *bbSPI) Tx(w []byte, r []byte) error {
|
||||
s.Configure()
|
||||
for _, b := range w {
|
||||
s.Transfer(b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// delay represents a quarter of the clock cycle
|
||||
func (s *bbSPI) delay() {
|
||||
for i := uint32(0); i < s.Delay; {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer is used to send a single byte.
|
||||
func (s *bbSPI) Transfer(b byte) {
|
||||
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)
|
||||
if b&(1<<(7-i)) == 0 {
|
||||
s.MOSI.Low()
|
||||
} else {
|
||||
s.MOSI.High()
|
||||
}
|
||||
s.delay()
|
||||
|
||||
// half clock cycle low
|
||||
s.SCK.Low()
|
||||
s.delay()
|
||||
|
||||
// for actual SPI would try to read the MISO value here
|
||||
s.delay()
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Package buzzer provides a very simplistic driver for a connected buzzer or low-fidelity speaker.
|
||||
//
|
||||
package buzzer // import "tinygo.org/x/drivers/buzzer"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
// Device wraps a GPIO connection to a buzzer.
|
||||
type Device struct {
|
||||
pin machine.Pin
|
||||
High bool
|
||||
BPM float64
|
||||
}
|
||||
|
||||
// New returns a new buzzer driver given which pin to use
|
||||
func New(pin machine.Pin) Device {
|
||||
return Device{
|
||||
pin: pin,
|
||||
High: false,
|
||||
BPM: 96.0,
|
||||
}
|
||||
}
|
||||
|
||||
// On sets the buzzer to a high state.
|
||||
func (l *Device) On() (err error) {
|
||||
l.pin.Set(true)
|
||||
l.High = true
|
||||
return
|
||||
}
|
||||
|
||||
// Off sets the buzzer to a low state.
|
||||
func (l *Device) Off() (err error) {
|
||||
l.pin.Set(false)
|
||||
l.High = false
|
||||
return
|
||||
}
|
||||
|
||||
// Toggle sets the buzzer to the opposite of it's current state
|
||||
func (l *Device) Toggle() (err error) {
|
||||
if l.High {
|
||||
err = l.Off()
|
||||
} else {
|
||||
err = l.On()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Tone plays a tone of the requested frequency and duration.
|
||||
func (l *Device) Tone(hz, duration float64) (err error) {
|
||||
// calculation based off https://www.arduino.cc/en/Tutorial/Melody
|
||||
tone := (1.0 / (2.0 * hz)) * 1000000.0
|
||||
|
||||
tempo := ((60 / l.BPM) * (duration * 1000))
|
||||
|
||||
for i := 0.0; i < tempo*1000; i += tone * 2.0 {
|
||||
if err = l.On(); err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Duration(tone) * time.Microsecond)
|
||||
|
||||
if err = l.Off(); err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Duration(tone) * time.Microsecond)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package buzzer
|
||||
|
||||
const (
|
||||
Whole = 4
|
||||
Half = 2
|
||||
Quarter = 1
|
||||
Eighth = 0.500
|
||||
)
|
||||
|
||||
// The values seem like they are little off, so feel free to make corrections, as needed.
|
||||
const (
|
||||
Rest = 0
|
||||
C0 = 16.35
|
||||
Db0 = 17.32
|
||||
D0 = 18.35
|
||||
Eb0 = 19.45
|
||||
E0 = 20.60
|
||||
F0 = 21.83
|
||||
Gb0 = 23.12
|
||||
G0 = 24.50
|
||||
Ab0 = 25.96
|
||||
A0 = 27.50
|
||||
Bb0 = 29.14
|
||||
B0 = 30.87
|
||||
C1 = 32.70
|
||||
Db1 = 34.65
|
||||
D1 = 36.71
|
||||
Eb1 = 38.89
|
||||
E1 = 41.20
|
||||
F1 = 43.65
|
||||
Gb1 = 46.25
|
||||
G1 = 49.00
|
||||
Ab1 = 51.91
|
||||
A1 = 55.00
|
||||
Bb1 = 58.27
|
||||
B1 = 61.74
|
||||
C2 = 65.41
|
||||
Db2 = 69.30
|
||||
D2 = 73.42
|
||||
Eb2 = 77.78
|
||||
E2 = 82.41
|
||||
F2 = 87.31
|
||||
Gb2 = 92.50
|
||||
G2 = 98.00
|
||||
Ab2 = 103.83
|
||||
A2 = 110.00
|
||||
Bb2 = 116.54
|
||||
B2 = 123.47
|
||||
C3 = 130.81
|
||||
Db3 = 138.59
|
||||
D3 = 146.83
|
||||
Eb3 = 155.56
|
||||
E3 = 164.81
|
||||
F3 = 174.61
|
||||
Gb3 = 185.00
|
||||
G3 = 196.00
|
||||
Ab3 = 207.65
|
||||
A3 = 220.00
|
||||
Bb3 = 233.08
|
||||
B3 = 246.94
|
||||
C4 = 261.63
|
||||
Db4 = 277.18
|
||||
D4 = 293.66
|
||||
Eb4 = 311.13
|
||||
E4 = 329.63
|
||||
F4 = 349.23
|
||||
Gb4 = 369.99
|
||||
G4 = 392.00
|
||||
Ab4 = 415.30
|
||||
A4 = 440.00
|
||||
Bb4 = 466.16
|
||||
B4 = 493.88
|
||||
C5 = 523.25
|
||||
Db5 = 554.37
|
||||
D5 = 587.33
|
||||
Eb5 = 622.25
|
||||
E5 = 659.25
|
||||
F5 = 698.46
|
||||
Gb5 = 739.99
|
||||
G5 = 783.99
|
||||
Ab5 = 830.61
|
||||
A5 = 880.00
|
||||
Bb5 = 932.33
|
||||
B5 = 987.77
|
||||
C6 = 1046.50
|
||||
Db6 = 1108.73
|
||||
D6 = 1174.66
|
||||
Eb6 = 1244.51
|
||||
E6 = 1318.51
|
||||
F6 = 1396.91
|
||||
Gb6 = 1479.98
|
||||
G6 = 1567.98
|
||||
Ab6 = 1661.22
|
||||
A6 = 1760.00
|
||||
Bb6 = 1864.66
|
||||
B6 = 1975.53
|
||||
C7 = 2093.00
|
||||
Db7 = 2217.46
|
||||
D7 = 2349.32
|
||||
Eb7 = 2489.02
|
||||
E7 = 2637.02
|
||||
F7 = 2793.83
|
||||
Gb7 = 2959.96
|
||||
G7 = 3135.96
|
||||
Ab7 = 3322.44
|
||||
A7 = 3520.00
|
||||
Bb7 = 3729.31
|
||||
B7 = 3951.07
|
||||
C8 = 4186.01
|
||||
Db8 = 4434.92
|
||||
D8 = 4698.63
|
||||
Eb8 = 4978.03
|
||||
E8 = 5274.04
|
||||
F8 = 5587.65
|
||||
Gb8 = 5919.91
|
||||
G8 = 6271.93
|
||||
Ab8 = 6644.88
|
||||
A8 = 7040.00
|
||||
Bb8 = 7458.62
|
||||
B8 = 7902.13
|
||||
)
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
// Package drivers provides a collection of hardware drivers for devices that
|
||||
// can be used together with TinyGo (https://tinygo.org).
|
||||
// Package drivers provides a collection of hardware drivers for TinyGo (https://tinygo.org)
|
||||
// for devices such as sensors and displays.
|
||||
//
|
||||
// Here is an example in TinyGo that uses the BMP180 digital barometer:
|
||||
//
|
||||
@@ -9,7 +9,7 @@
|
||||
// "time"
|
||||
// "machine"
|
||||
//
|
||||
// "github.com/tinygo-org/drivers/bmp180"
|
||||
// "tinygo.org/x/drivers/bmp180"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
|
||||
+2
-2
@@ -74,7 +74,7 @@ func (d *Device) Seek(offset int64, whence int) (int64, error) {
|
||||
case 2:
|
||||
whence = SRAMEndAddress
|
||||
default:
|
||||
return 0, errors.New("Invalid starting point")
|
||||
return 0, errors.New("invalid starting point")
|
||||
}
|
||||
d.AddressSRAM = uint8(whence) + uint8(offset)
|
||||
if d.AddressSRAM > SRAMEndAddress {
|
||||
@@ -87,7 +87,7 @@ func (d *Device) Seek(offset int64, whence int) (int64, error) {
|
||||
// returns number of bytes written and error, if any
|
||||
func (d *Device) Write(data []byte) (n int, err error) {
|
||||
if int(d.AddressSRAM)+len(data)-1 > SRAMEndAddress {
|
||||
return 0, errors.New("Writing outside of SRAM")
|
||||
return 0, errors.New("writing outside of SRAM")
|
||||
}
|
||||
buffer := make([]byte, len(data)+1)
|
||||
buffer[0] = d.AddressSRAM
|
||||
|
||||
+101
-19
@@ -1,4 +1,4 @@
|
||||
// Simple driver to rotate a 4-wire stepper motor
|
||||
// Package easystepper provides a simple driver to rotate a 4-wire stepper motor.
|
||||
package easystepper // import "tinygo.org/x/drivers/easystepper"
|
||||
|
||||
import (
|
||||
@@ -10,47 +10,117 @@ import (
|
||||
type Device struct {
|
||||
pins [4]machine.Pin
|
||||
stepDelay int32
|
||||
stepNumber int32
|
||||
stepNumber uint8
|
||||
}
|
||||
|
||||
// DualDevice holds information for controlling 2 motors
|
||||
type DualDevice struct {
|
||||
devices [2]Device
|
||||
}
|
||||
|
||||
// New returns a new easystepper driver given 4 pins numbers (not pin object),
|
||||
// number of steps and rpm
|
||||
func New(pin1, pin2, pin3, pin4 machine.Pin, steps int32, rpm int32) Device {
|
||||
pin1.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
pin2.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
pin3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
pin4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
return Device{
|
||||
pins: [4]machine.Pin{pin1, pin2, pin3, pin4},
|
||||
stepDelay: 60000000 / (steps * rpm),
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures the pins of the Device
|
||||
func (d *Device) Configure() {
|
||||
for _, pin := range d.pins {
|
||||
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
}
|
||||
}
|
||||
|
||||
// NewDual returns a new dual easystepper driver given 8 pins numbers (not pin object),
|
||||
// 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{
|
||||
pins: [4]machine.Pin{pin1, pin2, pin3, pin4},
|
||||
stepDelay: 60000000 / (steps * rpm),
|
||||
}
|
||||
dual.devices[1] = Device{
|
||||
pins: [4]machine.Pin{pin5, pin6, pin7, pin8},
|
||||
stepDelay: 60000000 / (steps * rpm),
|
||||
}
|
||||
return dual
|
||||
}
|
||||
|
||||
// Configure configures the pins of the DualDevice
|
||||
func (d *DualDevice) Configure() {
|
||||
d.devices[0].Configure()
|
||||
d.devices[1].Configure()
|
||||
}
|
||||
|
||||
// Move rotates the motor the number of given steps
|
||||
// (negative steps will rotate it the opposite direction)
|
||||
func (d *Device) Move(steps int32) {
|
||||
direction := steps > 0
|
||||
if steps < 0 {
|
||||
steps = -steps - d.stepNumber
|
||||
} else {
|
||||
steps += d.stepNumber
|
||||
steps = -steps
|
||||
}
|
||||
var stepN int8
|
||||
steps += int32(d.stepNumber)
|
||||
var s int32
|
||||
for s = d.stepNumber; s < steps; s++ {
|
||||
d.stepMotor(d.stepNumber)
|
||||
for s = int32(d.stepNumber); s < steps; s++ {
|
||||
time.Sleep(time.Duration(d.stepDelay) * time.Microsecond)
|
||||
if direction {
|
||||
stepN = int8(s % 4)
|
||||
} else {
|
||||
stepN = int8((s + 2*(s%2)) % 4)
|
||||
}
|
||||
d.stepMotor(stepN)
|
||||
d.moveDirectionSteps(direction, s)
|
||||
}
|
||||
d.stepNumber = int32(stepN)
|
||||
}
|
||||
|
||||
// Off turns off all motor pins
|
||||
func (d *Device) Off() {
|
||||
for _, pin := range d.pins {
|
||||
pin.Low()
|
||||
}
|
||||
}
|
||||
|
||||
// Move rotates the motors the number of given steps
|
||||
// (negative steps will rotate it the opposite direction)
|
||||
func (d *DualDevice) Move(stepsA, stepsB int32) {
|
||||
min := uint8(1)
|
||||
max := uint8(0)
|
||||
var directions [2]bool
|
||||
var minStep int32
|
||||
|
||||
directions[0] = stepsA > 0
|
||||
directions[1] = stepsB > 0
|
||||
if stepsA < 0 {
|
||||
stepsA = -stepsA
|
||||
}
|
||||
if stepsB < 0 {
|
||||
stepsB = -stepsB
|
||||
}
|
||||
if stepsB > stepsA {
|
||||
stepsA, stepsB = stepsB, stepsA
|
||||
max, min = min, max
|
||||
}
|
||||
d.devices[0].stepMotor(d.devices[0].stepNumber)
|
||||
d.devices[1].stepMotor(d.devices[1].stepNumber)
|
||||
stepsA += int32(d.devices[max].stepNumber)
|
||||
minStep = int32(d.devices[min].stepNumber)
|
||||
for s := int32(d.devices[max].stepNumber); s < stepsA; s++ {
|
||||
time.Sleep(time.Duration(d.devices[0].stepDelay) * time.Microsecond)
|
||||
d.devices[max].moveDirectionSteps(directions[max], s)
|
||||
|
||||
if ((s * stepsB) / stepsA) > minStep {
|
||||
minStep++
|
||||
d.devices[min].moveDirectionSteps(directions[min], minStep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Off turns off all motor pins
|
||||
func (d *DualDevice) Off() {
|
||||
d.devices[0].Off()
|
||||
d.devices[1].Off()
|
||||
}
|
||||
|
||||
// stepMotor changes the pins' state to the correct step
|
||||
func (d *Device) stepMotor(step int8) {
|
||||
func (d *Device) stepMotor(step uint8) {
|
||||
switch step {
|
||||
case 0:
|
||||
d.pins[0].High()
|
||||
@@ -77,4 +147,16 @@ func (d *Device) stepMotor(step int8) {
|
||||
d.pins[3].High()
|
||||
break
|
||||
}
|
||||
d.stepNumber = step
|
||||
}
|
||||
|
||||
// moveDirectionSteps uses the direction to calculate the correct step and change the motor to it.
|
||||
// Direction true: 0, 1, 2, 3, 0, 1, 2, ...
|
||||
// Direction false: 0, 3, 2, 1, 0, 3, 2, ...
|
||||
func (d *Device) moveDirectionSteps(direction bool, step int32) {
|
||||
if direction {
|
||||
d.stepMotor(uint8(step % 4))
|
||||
} else {
|
||||
d.stepMotor(uint8((step + 2*(step%2)) % 4))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# ESP-AT Driver
|
||||
|
||||
This package provides a driver to use a separate connected WiFi processor either the ESP8266 or the ESP32 from Espressif.
|
||||
|
||||
The way this driver works is by using the UART interface to communicate with the WiFi chip using the Espressif AT command set.
|
||||
|
||||
## ESP-AT Firmware Installation
|
||||
|
||||
In order to use this driver, you must have the ESP-AT firmware installed on the ESP8266/ESP32 chip.
|
||||
|
||||
### Installing on Arduino Nano33 IoT
|
||||
|
||||
In order to install the needed firmware on the Arduino Nano33 IoT board's built-in NINA W102 chip, you will need to use the `arduino-nano33-iot` branch of this fork of the firmware:
|
||||
|
||||
https://github.com/hybridgroup/esp32-at
|
||||
|
||||
To flash this firmware on the Arduino Nano33 IoT you will need to follow the following procedure:
|
||||
|
||||
- Install _Arduino SAMD Boards_ from the Boards Manager.
|
||||
- Install _WiFiNANO_ from the Library Manager.
|
||||
- Using the normal Arduino software, load the `SerialNINAPassthrough` sketch on to the board (in File -> Examples -> WiFiNINA-> Tools).
|
||||
- Flash the NINA 102 firmware using the `make flash` command in the https://github.com/hybridgroup/esp32-at repo.
|
||||
|
||||
You only need to do this one time, and then the correct ESP-AT firmware will be on the NINA chip, and you can just flash the Arduino Nano33 IoT board using TinyGo. We should be able to remove some of these step in a future release of this software.
|
||||
|
||||
### Installing on ESP32
|
||||
|
||||
The official repository for the ESP-AT for the ESP32 processor is located here:
|
||||
|
||||
https://github.com/espressif/esp32-at
|
||||
|
||||
Your best option is to follow the instructions in the official repo.
|
||||
|
||||
### Installing on ESP8266
|
||||
|
||||
The official repository for the AT command set firmware for the ESP8266 processor is located here:
|
||||
|
||||
https://github.com/espressif/ESP8266_NONOS_SDK
|
||||
|
||||
First clone the repo:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/espressif/ESP8266_NONOS_SDK.git
|
||||
```
|
||||
|
||||
You will also need to install the Espressif `esptool` to flash this firmware on your ESP8266:
|
||||
|
||||
https://github.com/espressif/esptool
|
||||
|
||||
Once you have obtained the binary code, and installed `esptool`, you can flash the ESP8266.
|
||||
|
||||
Here is an example shell script that flashes a Wemos D1 Mini board:
|
||||
|
||||
|
||||
```python
|
||||
#!/bin/sh
|
||||
SPToolDir="$HOME/.local/lib/python2.7/site-packages"
|
||||
FirmwareDir="$HOME/Development/ESP8266_NONOS_SDK"
|
||||
cd "$SPToolDir"
|
||||
port=/dev/ttyUSB0
|
||||
if [ ! -c $port ]; then
|
||||
port=/dev/ttyUSB0
|
||||
fi
|
||||
if [ ! -c $port ]; then
|
||||
echo "No device appears to be plugged in. Stopping."
|
||||
fi
|
||||
printf "Writing AT firmware to the Wemos D1 Mini in 3..."
|
||||
sleep 1; printf "2..."
|
||||
sleep 1; printf "1..."
|
||||
sleep 1; echo "done."
|
||||
echo "Erasing the flash first"
|
||||
esptool.py --port $port erase_flash
|
||||
esptool.py --port /dev/ttyUSB0 --baud 115200 \
|
||||
write_flash -fm dio -ff 20m -fs detect \
|
||||
0x0000 "$FirmwareDir/bin/boot_v1.7.bin" \
|
||||
0x01000 "$FirmwareDir/bin/at/512+512/user1.1024.new.2.bin" \
|
||||
0x3fc000 "$FirmwareDir/bin/esp_init_data_default_v05.bin" \
|
||||
0x7e000 "$FirmwareDir/bin/blank.bin" \
|
||||
0x3fe000 "$FirmwareDir/bin/blank.bin"
|
||||
|
||||
echo "Check the boot by typing: miniterm $port 74800"
|
||||
echo " and then resetting. Use Ctrl-] to quit miniterm."
|
||||
|
||||
```
|
||||
+16
-9
@@ -42,12 +42,14 @@ const (
|
||||
Disconnect = "+CWQAP"
|
||||
|
||||
// Set softAP configuration. This also activates the ESP8266/ESP32 to act as an access point.
|
||||
// The settings will not be saved in flash memory, so they will be forgotten on next reset.
|
||||
SoftAPConfigCurrent = "+CWSAP_CUR"
|
||||
// On the ESP8266 the settings will not be saved in flash memory, so they will be forgotten on next reset.
|
||||
// On the ESP32 the settings WILL be saved in flash memory, so they will be used on next reset.
|
||||
SoftAPConfigCurrent = "+CWSAP"
|
||||
|
||||
// Set softAP configuration as saved in flash. This also activates the ESP8266/ESP32 to act as an
|
||||
// access point. The settings will be saved in flash memory, so they will be used on next reset.
|
||||
SoftAPConfigFlash = "+CWSAP_DEF"
|
||||
// Set softAP configuration. This also activates the ESP8266/ESP32 to act as an access point.
|
||||
// On the ESP8266 the settings will not be saved in flash memory, so they will be forgotten on next reset.
|
||||
// On the ESP32 the settings WILL be saved in flash memory, so they will be used on next reset.
|
||||
SoftAPConfigFlash = "+CWSAP"
|
||||
|
||||
// List station IP's connected to softAP
|
||||
ListConnectedIP = "+CWLIF"
|
||||
@@ -65,12 +67,14 @@ const (
|
||||
SetStationIP = "+CIPSTA"
|
||||
|
||||
// Set IP address of ESP8266/ESP32 when acting as access point.
|
||||
// The IP address will not be saved in flash memory, so it will be forgotten on next reset.
|
||||
SetSoftAPIPCurrent = "+CIPAP_CUR"
|
||||
// On the ESP8266 the IP address will not be saved in flash memory, so it will be forgotten on next reset.
|
||||
// On the ESP32 the IP address WILL be saved in flash memory, so it will be used on next reset.
|
||||
SetSoftAPIPCurrent = "+CIPAP"
|
||||
|
||||
// Set IP address of ESP8266/ESP32 when acting as access point.
|
||||
// The IP address will be saved in flash memory, so they will be used on next reset.
|
||||
SetSoftAPIPFlash = "+CIPAP_DEF"
|
||||
// On the ESP8266 the IP address will not be saved in flash memory, so it will be forgotten on next reset.
|
||||
// On the ESP32 the IP address WILL be saved in flash memory, so it will be used on next reset.
|
||||
SetSoftAPIPFlash = "+CIPAP"
|
||||
)
|
||||
|
||||
// TCP/IP commands
|
||||
@@ -81,6 +85,9 @@ const (
|
||||
// Establish TCP connection or register UDP port
|
||||
TCPConnect = "+CIPSTART"
|
||||
|
||||
// DNS Lookup
|
||||
TCPDNSLookup = "+CIPDOMAIN"
|
||||
|
||||
// Send Data
|
||||
TCPSend = "+CIPSEND"
|
||||
|
||||
|
||||
+70
-100
@@ -19,10 +19,13 @@
|
||||
package espat // import "tinygo.org/x/drivers/espat"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
)
|
||||
|
||||
// Device wraps UART connection to the ESP8266/ESP32.
|
||||
@@ -36,6 +39,9 @@ type Device struct {
|
||||
socketdata []byte
|
||||
}
|
||||
|
||||
// ActiveDevice is the currently configured Device in use. There can only be one.
|
||||
var ActiveDevice *Device
|
||||
|
||||
// New returns a new espat driver. Pass in a fully configured UART bus.
|
||||
func New(b machine.UART) *Device {
|
||||
return &Device{bus: b, response: make([]byte, 512), socketdata: make([]byte, 0, 1024)}
|
||||
@@ -43,6 +49,8 @@ func New(b machine.UART) *Device {
|
||||
|
||||
// Configure sets up the device for communication.
|
||||
func (d Device) Configure() {
|
||||
ActiveDevice = &d
|
||||
net.ActiveDevice = ActiveDevice
|
||||
}
|
||||
|
||||
// Connected checks if there is communication with the ESP8266/ESP32.
|
||||
@@ -50,11 +58,11 @@ func (d *Device) Connected() bool {
|
||||
d.Execute(Test)
|
||||
|
||||
// handle response here, should include "OK"
|
||||
r := d.Response()
|
||||
if strings.Contains(string(r), "OK") {
|
||||
return true
|
||||
_, err := d.Response(100)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
// Write raw bytes to the UART.
|
||||
@@ -68,7 +76,7 @@ func (d *Device) Read(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// how long in milliseconds to pause after sending AT commands
|
||||
const pause = 100
|
||||
const pause = 300
|
||||
|
||||
// Execute sends an AT command to the ESP8266/ESP32.
|
||||
func (d Device) Execute(cmd string) error {
|
||||
@@ -93,7 +101,11 @@ func (d Device) Set(cmd, params string) error {
|
||||
// Version returns the ESP8266/ESP32 firmware version info.
|
||||
func (d Device) Version() []byte {
|
||||
d.Execute(Version)
|
||||
return d.Response()
|
||||
r, err := d.Response(100)
|
||||
if err != nil {
|
||||
return []byte("unknown")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Echo sets the ESP8266/ESP32 echo setting.
|
||||
@@ -104,7 +116,7 @@ func (d Device) Echo(set bool) {
|
||||
d.Execute(EchoConfigOff)
|
||||
}
|
||||
// TODO: check for success
|
||||
d.Response()
|
||||
d.Response(100)
|
||||
}
|
||||
|
||||
// Reset restarts the ESP8266/ESP32 firmware. Due to how the baud rate changes,
|
||||
@@ -112,13 +124,13 @@ func (d Device) Echo(set bool) {
|
||||
// what you are doing when you call this.
|
||||
func (d Device) Reset() {
|
||||
d.Execute(Restart)
|
||||
d.Response()
|
||||
d.Response(100)
|
||||
}
|
||||
|
||||
// ReadSocket returns the data that has already been read in from the responses.
|
||||
func (d *Device) ReadSocket(b []byte) (n int, err error) {
|
||||
// make sure no data in buffer
|
||||
d.Response()
|
||||
d.Response(300)
|
||||
|
||||
count := len(b)
|
||||
if len(b) >= len(d.socketdata) {
|
||||
@@ -137,116 +149,74 @@ func (d *Device) ReadSocket(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// Response gets the next response bytes from the ESP8266/ESP32.
|
||||
func (d *Device) Response() []byte {
|
||||
var i, retries int
|
||||
// The call will retry for up to timeout milliseconds before returning nothing.
|
||||
func (d *Device) Response(timeout int) ([]byte, error) {
|
||||
// read data
|
||||
var size int
|
||||
var start, end int
|
||||
pause := 100 // pause to wait for 100 ms
|
||||
retries := timeout / pause
|
||||
|
||||
header := make([]byte, 2)
|
||||
for {
|
||||
for d.bus.Buffered() > 0 {
|
||||
// get the first 2 bytes
|
||||
header[0], _ = d.bus.ReadByte()
|
||||
header[1], _ = d.bus.ReadByte()
|
||||
size = d.bus.Buffered()
|
||||
|
||||
if d.isLeadingCRLF(header) {
|
||||
// skip it
|
||||
header[0], _ = d.bus.ReadByte()
|
||||
header[1], _ = d.bus.ReadByte()
|
||||
if size > 0 {
|
||||
end += size
|
||||
d.bus.Read(d.response[start:end])
|
||||
|
||||
// if "+IPD" then read socket data
|
||||
if strings.Contains(string(d.response[:end]), "+IPD") {
|
||||
// handle socket data
|
||||
return nil, d.parseIPD(end)
|
||||
}
|
||||
|
||||
if d.isIPD(header) {
|
||||
// is socket data packet
|
||||
d.parseIPD()
|
||||
} else {
|
||||
// no, so put into response
|
||||
d.response[i] = header[0]
|
||||
i++
|
||||
d.response[i] = header[1]
|
||||
i++
|
||||
// if "OK" then the command worked
|
||||
if strings.Contains(string(d.response[:end]), "OK") {
|
||||
return d.response[start:end], nil
|
||||
}
|
||||
|
||||
// read the rest of normal command response
|
||||
for d.bus.Buffered() > 0 {
|
||||
data, err := d.bus.ReadByte()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
d.response[i] = data
|
||||
i++
|
||||
// if "Error" then the command failed
|
||||
if strings.Contains(string(d.response[:end]), "ERROR") {
|
||||
return d.response[start:end], errors.New("response error:" + string(d.response[start:end]))
|
||||
}
|
||||
}
|
||||
retries++
|
||||
if retries > 2 {
|
||||
break
|
||||
|
||||
// if anything else, then keep reading data in?
|
||||
start = end
|
||||
}
|
||||
|
||||
// pause to make sure is no more data to be read
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// wait longer?
|
||||
retries--
|
||||
if retries == 0 {
|
||||
return nil, errors.New("response timeout error:" + string(d.response[start:end]))
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(pause) * time.Millisecond)
|
||||
}
|
||||
return d.response[:i]
|
||||
}
|
||||
|
||||
func (d *Device) isLeadingCRLF(b []byte) bool {
|
||||
if len(b) < 2 {
|
||||
return false
|
||||
}
|
||||
if b[0] == 13 && b[1] == 10 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (d *Device) parseIPD(end int) error {
|
||||
// find the "+IPD," to get length
|
||||
s := strings.Index(string(d.response[:end]), "+IPD,")
|
||||
|
||||
func (d *Device) isIPD(b []byte) bool {
|
||||
if len(b) < 2 {
|
||||
return false
|
||||
}
|
||||
if b[0] == '+' && b[1] == 'I' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
// find the ":"
|
||||
e := strings.Index(string(d.response[:end]), ":")
|
||||
|
||||
func (d *Device) parseIPD() bool {
|
||||
data, _ := d.bus.ReadByte()
|
||||
if data != 'P' {
|
||||
// error
|
||||
return false
|
||||
}
|
||||
data, _ = d.bus.ReadByte()
|
||||
if data != 'D' {
|
||||
// error
|
||||
return false
|
||||
}
|
||||
data, _ = d.bus.ReadByte()
|
||||
if data != ',' {
|
||||
// error
|
||||
return false
|
||||
}
|
||||
// find the data length
|
||||
val := string(d.response[s+5 : e])
|
||||
|
||||
// get the expected data length
|
||||
// skip remaining header up to the ":"
|
||||
buf := []byte{}
|
||||
data, _ = d.bus.ReadByte()
|
||||
for data != ':' {
|
||||
// put into the buffer with int value here
|
||||
buf = append(buf, data)
|
||||
|
||||
// read next value
|
||||
data, _ = d.bus.ReadByte()
|
||||
}
|
||||
|
||||
val := string(buf)
|
||||
count, err := strconv.Atoi(val)
|
||||
// TODO: verify count
|
||||
_, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
// not expected data here. what to do?
|
||||
return false
|
||||
return err
|
||||
}
|
||||
|
||||
// load up the socket data
|
||||
// only read the expected amount of data
|
||||
for m := 0; m < count; m++ {
|
||||
data, _ = d.bus.ReadByte()
|
||||
d.socketdata = append(d.socketdata, data)
|
||||
}
|
||||
|
||||
return true
|
||||
d.socketdata = append(d.socketdata, d.response[e+1:end]...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSocketDataAvailable returns of there is socket data available
|
||||
func (d *Device) IsSocketDataAvailable() bool {
|
||||
return len(d.socketdata) > 0 || d.bus.Buffered() > 0
|
||||
}
|
||||
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
package espat
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DialUDP makes a UDP network connection. raadr is the port that the messages will
|
||||
// be sent to, and laddr is the port that will be listened to in order to
|
||||
// receive incoming messages.
|
||||
func (d Device) DialUDP(network string, laddr, raddr *UDPAddr) (*SerialConn, error) {
|
||||
addr := raddr.IP.String()
|
||||
sendport := strconv.Itoa(raddr.Port)
|
||||
listenport := strconv.Itoa(laddr.Port)
|
||||
|
||||
// disconnect any old socket
|
||||
d.DisconnectSocket()
|
||||
|
||||
// connect new socket
|
||||
d.ConnectUDPSocket(addr, sendport, listenport)
|
||||
|
||||
return &SerialConn{Adaptor: &d, laddr: laddr, raddr: raddr}, nil
|
||||
}
|
||||
|
||||
// ListenUDP listens for UDP connections on the port listed in laddr.
|
||||
func (d Device) ListenUDP(network string, laddr *UDPAddr) (*SerialConn, error) {
|
||||
addr := "0"
|
||||
sendport := "0"
|
||||
listenport := strconv.Itoa(laddr.Port)
|
||||
|
||||
// disconnect any old socket
|
||||
d.DisconnectSocket()
|
||||
|
||||
// connect new socket
|
||||
d.ConnectUDPSocket(addr, sendport, listenport)
|
||||
|
||||
return &SerialConn{Adaptor: &d, laddr: laddr}, nil
|
||||
}
|
||||
|
||||
// SerialConn is a loosely net.Conn compatible intended to support
|
||||
// TCP/UDP over serial.
|
||||
type SerialConn struct {
|
||||
Adaptor *Device
|
||||
laddr *UDPAddr
|
||||
raddr *UDPAddr
|
||||
}
|
||||
|
||||
// Read reads data from the connection.
|
||||
// TODO: implement the full method functionality:
|
||||
// Read can be made to time out and return an Error with Timeout() == true
|
||||
// after a fixed time limit; see SetDeadline and SetReadDeadline.
|
||||
func (c *SerialConn) Read(b []byte) (n int, err error) {
|
||||
// read only the data that has been received via "+IPD" socket
|
||||
return c.Adaptor.ReadSocket(b)
|
||||
}
|
||||
|
||||
// Write writes data to the connection.
|
||||
// TODO: implement the full method functionality for timeouts.
|
||||
// Write can be made to time out and return an Error with Timeout() == true
|
||||
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
|
||||
func (c *SerialConn) Write(b []byte) (n int, err error) {
|
||||
// specify that is a data transfer to the
|
||||
// currently open socket, not commands to the ESP8266/ESP32.
|
||||
c.Adaptor.StartSocketSend(len(b))
|
||||
return c.Adaptor.Write(b)
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
// Currently only supports a single Read or Write operations without blocking.
|
||||
func (c *SerialConn) Close() error {
|
||||
c.Adaptor.DisconnectSocket()
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (c *SerialConn) LocalAddr() UDPAddr {
|
||||
return *c.laddr
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (c *SerialConn) RemoteAddr() UDPAddr {
|
||||
return *c.laddr
|
||||
}
|
||||
|
||||
// SetDeadline sets the read and write deadlines associated
|
||||
// with the connection. It is equivalent to calling both
|
||||
// SetReadDeadline and SetWriteDeadline.
|
||||
//
|
||||
// A deadline is an absolute time after which I/O operations
|
||||
// fail with a timeout (see type Error) instead of
|
||||
// blocking. The deadline applies to all future and pending
|
||||
// I/O, not just the immediately following call to Read or
|
||||
// Write. After a deadline has been exceeded, the connection
|
||||
// can be refreshed by setting a deadline in the future.
|
||||
//
|
||||
// An idle timeout can be implemented by repeatedly extending
|
||||
// the deadline after successful Read or Write calls.
|
||||
//
|
||||
// A zero value for t means I/O operations will not time out.
|
||||
func (c *SerialConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the deadline for future Read calls
|
||||
// and any currently-blocked Read call.
|
||||
// A zero value for t means Read will not time out.
|
||||
func (c *SerialConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the deadline for future Write calls
|
||||
// and any currently-blocked Write call.
|
||||
// Even if write times out, it may return n > 0, indicating that
|
||||
// some of the data was successfully written.
|
||||
// A zero value for t means Write will not time out.
|
||||
func (c *SerialConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The following definitions are here to support a Golang standard package
|
||||
// net-compatible interface for IP until TinyGo can compile the net package.
|
||||
|
||||
// IP is an IP address. Unlike the standard implementation, it is only
|
||||
// a buffer of bytes that contains the string form of the IP address, not the
|
||||
// full byte format used by the Go standard .
|
||||
type IP []byte
|
||||
|
||||
// UDPAddr here to serve as compatible type. until TinyGo can compile the net package.
|
||||
type UDPAddr struct {
|
||||
IP IP
|
||||
Port int
|
||||
Zone string // IPv6 scoped addressing zone; added in Go 1.1
|
||||
}
|
||||
|
||||
// ParseIP parses s as an IP address, returning the result.
|
||||
func ParseIP(s string) IP {
|
||||
return IP([]byte(s))
|
||||
}
|
||||
|
||||
// String returns the string form of the IP address ip.
|
||||
func (ip IP) String() string {
|
||||
return string(ip)
|
||||
}
|
||||
+76
-26
@@ -1,8 +1,9 @@
|
||||
package espat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -13,14 +14,37 @@ const (
|
||||
TCPTransferModeUnvarnished = 1
|
||||
)
|
||||
|
||||
// GetDNS returns the IP address for a domain name.
|
||||
func (d *Device) GetDNS(domain string) (string, error) {
|
||||
d.Set(TCPDNSLookup, "\""+domain+"\"")
|
||||
resp, err := d.Response(1000)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !strings.Contains(string(resp), ":") {
|
||||
return "", errors.New("GetDNS error:" + string(resp))
|
||||
}
|
||||
r := strings.Split(string(resp), ":")
|
||||
if len(r) != 2 {
|
||||
return "", errors.New("Invalid domain lookup result")
|
||||
}
|
||||
res := strings.Split(r[1], "\r\n")
|
||||
return res[0], nil
|
||||
}
|
||||
|
||||
// ConnectTCPSocket creates a new TCP socket connection for the ESP8266/ESP32.
|
||||
// Currently only supports single connection mode.
|
||||
func (d *Device) ConnectTCPSocket(addr, port string) error {
|
||||
protocol := "TCP"
|
||||
val := "\"" + protocol + "\",\"" + addr + "\"," + port
|
||||
d.Set(TCPConnect, val)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
d.Response()
|
||||
val := "\"" + protocol + "\",\"" + addr + "\"," + port + ",120"
|
||||
err := d.Set(TCPConnect, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, e := d.Response(3000)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -28,17 +52,41 @@ func (d *Device) ConnectTCPSocket(addr, port string) error {
|
||||
func (d *Device) ConnectUDPSocket(addr, sendport, listenport string) error {
|
||||
protocol := "UDP"
|
||||
val := "\"" + protocol + "\",\"" + addr + "\"," + sendport + "," + listenport + ",2"
|
||||
err := d.Set(TCPConnect, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, e := d.Response(3000)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnectSSLSocket creates a new SSL socket connection for the ESP8266/ESP32.
|
||||
// Currently only supports single connection mode.
|
||||
func (d *Device) ConnectSSLSocket(addr, port string) error {
|
||||
protocol := "SSL"
|
||||
val := "\"" + protocol + "\",\"" + addr + "\"," + port + ",120"
|
||||
d.Set(TCPConnect, val)
|
||||
time.Sleep(pause * time.Millisecond)
|
||||
d.Response()
|
||||
// this operation takes longer, so wait up to 6 seconds to complete.
|
||||
_, err := d.Response(6000)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisconnectSocket disconnects the ESP8266/ESP32 from the current TCP/UDP connection.
|
||||
func (d *Device) DisconnectSocket() error {
|
||||
d.Execute(TCPClose)
|
||||
time.Sleep(pause * time.Millisecond)
|
||||
d.Response()
|
||||
err := d.Execute(TCPClose)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, e := d.Response(pause)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -47,15 +95,14 @@ func (d *Device) DisconnectSocket() error {
|
||||
func (d *Device) SetMux(mode int) error {
|
||||
val := strconv.Itoa(mode)
|
||||
d.Set(TCPMultiple, val)
|
||||
time.Sleep(pause * time.Millisecond)
|
||||
d.Response()
|
||||
return nil
|
||||
_, err := d.Response(pause)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetMux returns the ESP8266/ESP32 current client TCP/UDP configuration for concurrent connections.
|
||||
func (d *Device) GetMux() ([]byte, error) {
|
||||
d.Query(TCPMultiple)
|
||||
return d.Response(), nil
|
||||
return d.Response(pause)
|
||||
}
|
||||
|
||||
// SetTCPTransferMode sets the ESP8266/ESP32 current client TCP/UDP transfer mode.
|
||||
@@ -63,15 +110,14 @@ func (d *Device) GetMux() ([]byte, error) {
|
||||
func (d *Device) SetTCPTransferMode(mode int) error {
|
||||
val := strconv.Itoa(mode)
|
||||
d.Set(TransmissionMode, val)
|
||||
time.Sleep(pause * time.Millisecond)
|
||||
d.Response()
|
||||
return nil
|
||||
_, err := d.Response(pause)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTCPTransferMode returns the ESP8266/ESP32 current client TCP/UDP transfer mode.
|
||||
func (d *Device) GetTCPTransferMode() []byte {
|
||||
func (d *Device) GetTCPTransferMode() ([]byte, error) {
|
||||
d.Query(TransmissionMode)
|
||||
return d.Response()
|
||||
return d.Response(pause)
|
||||
}
|
||||
|
||||
// StartSocketSend gets the ESP8266/ESP32 ready to receive TCP/UDP socket data.
|
||||
@@ -79,10 +125,16 @@ func (d *Device) StartSocketSend(size int) error {
|
||||
val := strconv.Itoa(size)
|
||||
d.Set(TCPSend, val)
|
||||
|
||||
// TODO: wait until ">" is received, which indicates
|
||||
// when ">" is received, it indicates
|
||||
// ready to receive data
|
||||
d.Response()
|
||||
return nil
|
||||
r, err := d.Response(2000)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(string(r), ">") {
|
||||
return nil
|
||||
}
|
||||
return errors.New("StartSocketSend error:" + string(r))
|
||||
}
|
||||
|
||||
// EndSocketSend tell the ESP8266/ESP32 the TCP/UDP socket data sending is complete,
|
||||
@@ -90,8 +142,6 @@ func (d *Device) StartSocketSend(size int) error {
|
||||
func (d *Device) EndSocketSend() error {
|
||||
d.Write([]byte("+++"))
|
||||
|
||||
// TODO: wait until ">" is received, which indicates
|
||||
// ready to receive data
|
||||
d.Response()
|
||||
return nil
|
||||
_, err := d.Response(pause)
|
||||
return err
|
||||
}
|
||||
|
||||
+42
-42
@@ -2,7 +2,6 @@ package espat
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -17,26 +16,25 @@ const (
|
||||
)
|
||||
|
||||
// GetWifiMode returns the ESP8266/ESP32 wifi mode.
|
||||
func (d *Device) GetWifiMode() []byte {
|
||||
func (d *Device) GetWifiMode() ([]byte, error) {
|
||||
d.Query(WifiMode)
|
||||
return d.Response()
|
||||
return d.Response(100)
|
||||
}
|
||||
|
||||
// SetWifiMode sets the ESP8266/ESP32 wifi mode.
|
||||
func (d *Device) SetWifiMode(mode int) error {
|
||||
val := strconv.Itoa(mode)
|
||||
d.Set(WifiMode, val)
|
||||
time.Sleep(pause * time.Millisecond)
|
||||
d.Response()
|
||||
return nil
|
||||
_, err := d.Response(pause)
|
||||
return err
|
||||
}
|
||||
|
||||
// Wifi Client
|
||||
|
||||
// GetConnectedAP returns the ESP8266/ESP32 is currently connected to as a client.
|
||||
func (d *Device) GetConnectedAP() []byte {
|
||||
func (d *Device) GetConnectedAP() ([]byte, error) {
|
||||
d.Query(ConnectAP)
|
||||
return d.Response()
|
||||
return d.Response(100)
|
||||
}
|
||||
|
||||
// ConnectToAP connects the ESP8266/ESP32 to an access point.
|
||||
@@ -44,41 +42,43 @@ func (d *Device) GetConnectedAP() []byte {
|
||||
func (d *Device) ConnectToAP(ssid, pwd string, ws int) error {
|
||||
val := "\"" + ssid + "\",\"" + pwd + "\""
|
||||
d.Set(ConnectAP, val)
|
||||
// TODO: a better way to wait for connect and check for up to ws seconds.
|
||||
time.Sleep(time.Duration(ws) * time.Second)
|
||||
d.Response()
|
||||
|
||||
_, err := d.Response(ws * 1000)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisconnectFromAP disconnects the ESP8266/ESP32 from the current access point.
|
||||
func (d *Device) DisconnectFromAP() error {
|
||||
d.Execute(Disconnect)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
d.Response()
|
||||
return nil
|
||||
_, err := d.Response(1000)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetClientIP returns the ESP8266/ESP32 current client IP addess when connected to an Access Point.
|
||||
func (d *Device) GetClientIP() string {
|
||||
func (d *Device) GetClientIP() (string, error) {
|
||||
d.Query(SetStationIP)
|
||||
return string(d.Response())
|
||||
r, err := d.Response(1000)
|
||||
return string(r), err
|
||||
}
|
||||
|
||||
// SetClientIP sets the ESP8266/ESP32 current client IP addess when connected to an Access Point.
|
||||
func (d *Device) SetClientIP(ipaddr string) []byte {
|
||||
func (d *Device) SetClientIP(ipaddr string) error {
|
||||
val := "\"" + ipaddr + "\""
|
||||
d.Set(ConnectAP, val)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
d.Response()
|
||||
return nil
|
||||
_, err := d.Response(500)
|
||||
return err
|
||||
}
|
||||
|
||||
// Access Point
|
||||
|
||||
// GetAPConfig returns the ESP8266/ESP32 current configuration when acting as an Access Point.
|
||||
func (d *Device) GetAPConfig() string {
|
||||
func (d *Device) GetAPConfig() (string, error) {
|
||||
d.Query(SoftAPConfigCurrent)
|
||||
return string(d.Response())
|
||||
r, err := d.Response(100)
|
||||
return string(r), err
|
||||
}
|
||||
|
||||
// SetAPConfig sets the ESP8266/ESP32 current configuration when acting as an Access Point.
|
||||
@@ -89,37 +89,38 @@ func (d *Device) SetAPConfig(ssid, pwd string, ch, security int) error {
|
||||
ecnval := strconv.Itoa(security)
|
||||
val := "\"" + ssid + "\",\"" + pwd + "\"," + chval + "," + ecnval
|
||||
d.Set(SoftAPConfigCurrent, val)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
d.Response()
|
||||
return nil
|
||||
_, err := d.Response(1000)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAPClients returns the ESP8266/ESP32 current clients when acting as an Access Point.
|
||||
func (d *Device) GetAPClients() string {
|
||||
func (d *Device) GetAPClients() (string, error) {
|
||||
d.Query(ListConnectedIP)
|
||||
return string(d.Response())
|
||||
r, err := d.Response(100)
|
||||
return string(r), err
|
||||
}
|
||||
|
||||
// GetAPIP returns the ESP8266/ESP32 current IP addess when configured as an Access Point.
|
||||
func (d *Device) GetAPIP() string {
|
||||
func (d *Device) GetAPIP() (string, error) {
|
||||
d.Query(SetSoftAPIPCurrent)
|
||||
return string(d.Response())
|
||||
r, err := d.Response(100)
|
||||
return string(r), err
|
||||
}
|
||||
|
||||
// SetAPIP sets the ESP8266/ESP32 current IP addess when configured as an Access Point.
|
||||
func (d *Device) SetAPIP(ipaddr string) error {
|
||||
val := "\"" + ipaddr + "\""
|
||||
d.Set(SetSoftAPIPCurrent, val)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
d.Response()
|
||||
return nil
|
||||
_, err := d.Response(500)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAPConfigFlash returns the ESP8266/ESP32 current configuration acting as an Access Point
|
||||
// from flash storage. These settings are those used after a reset.
|
||||
func (d *Device) GetAPConfigFlash() string {
|
||||
func (d *Device) GetAPConfigFlash() (string, error) {
|
||||
d.Query(SoftAPConfigFlash)
|
||||
return string(d.Response())
|
||||
r, err := d.Response(100)
|
||||
return string(r), err
|
||||
}
|
||||
|
||||
// SetAPConfigFlash sets the ESP8266/ESP32 current configuration acting as an Access Point,
|
||||
@@ -131,16 +132,16 @@ func (d *Device) SetAPConfigFlash(ssid, pwd string, ch, security int) error {
|
||||
ecnval := strconv.Itoa(security)
|
||||
val := "\"" + ssid + "\",\"" + pwd + "\"," + chval + "," + ecnval
|
||||
d.Set(SoftAPConfigFlash, val)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
d.Response()
|
||||
return nil
|
||||
_, err := d.Response(1000)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAPIPFlash returns the ESP8266/ESP32 IP address as saved to flash storage.
|
||||
// This is the IP address that will be used after a reset.
|
||||
func (d *Device) GetAPIPFlash() string {
|
||||
func (d *Device) GetAPIPFlash() (string, error) {
|
||||
d.Query(SetSoftAPIPFlash)
|
||||
return string(d.Response())
|
||||
r, err := d.Response(100)
|
||||
return string(r), err
|
||||
}
|
||||
|
||||
// SetAPIPFlash sets the ESP8266/ESP32 current IP addess when configured as an Access Point.
|
||||
@@ -148,7 +149,6 @@ func (d *Device) GetAPIPFlash() string {
|
||||
func (d *Device) SetAPIPFlash(ipaddr string) error {
|
||||
val := "\"" + ipaddr + "\""
|
||||
d.Set(SetSoftAPIPFlash, val)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
d.Response()
|
||||
return nil
|
||||
_, err := d.Response(500)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/adt7410"
|
||||
)
|
||||
|
||||
var (
|
||||
i2c = &machine.I2C0
|
||||
sensor = adt7410.New(i2c, 0)
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
i2c.Configure(machine.I2CConfig{Frequency: machine.TWI_FREQ_400KHZ})
|
||||
sensor.Configure()
|
||||
|
||||
for {
|
||||
temp := sensor.ReadTempF()
|
||||
fmt.Printf("temperature: %f\r\n", temp)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// This example demostrates how to control the "Dotstar" (APA102) LED included
|
||||
// on the Adafruit Itsy Bitsy M0 board. It implements a "rainbow effect" based
|
||||
// on the following example:
|
||||
// https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/master/CircuitPython_Essentials/CircuitPython_Internal_RGB_LED_rainbow.py
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/apa102"
|
||||
)
|
||||
|
||||
var (
|
||||
apa apa102.Device
|
||||
|
||||
led = machine.PWM{machine.LED}
|
||||
leds = make([]color.RGBA, 1)
|
||||
wheel = &Wheel{Brightness: 0x10}
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
// APA102 on Itsy Bitsy is connected to pins that require a software-based
|
||||
// SPI implementation.
|
||||
apa = apa102.NewSoftwareSPI(machine.PA00, machine.PA01, 1)
|
||||
|
||||
// Configure the regular on-board LED for PWM fading
|
||||
machine.InitPWM()
|
||||
led.Configure()
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// We'll fade the on-board LED in a goroutine to show/ensure that the APA102
|
||||
// works fine with the scheduler enabled. Comment this out to test this code
|
||||
// with the scheduler disabled.
|
||||
go func() {
|
||||
for i, brightening := uint8(0), false; ; i++ {
|
||||
if i == 0 {
|
||||
brightening = !brightening
|
||||
continue
|
||||
}
|
||||
var brightness uint16 = uint16(i) << 8
|
||||
if !brightening {
|
||||
brightness = 0xFFFF - brightness
|
||||
}
|
||||
led.Set(brightness)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
// Use the "wheel" function from Adafruit's example to cycle the APA102
|
||||
for {
|
||||
leds[0] = wheel.Next()
|
||||
apa.WriteColors(leds)
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Wheel is a port of Adafruit's Circuit Python example referenced above.
|
||||
type Wheel struct {
|
||||
Brightness uint8
|
||||
pos uint8
|
||||
}
|
||||
|
||||
// Next increments the internal state of the color and returns the new RGBA
|
||||
func (w *Wheel) Next() (c color.RGBA) {
|
||||
pos := w.pos
|
||||
if w.pos < 85 {
|
||||
c = color.RGBA{R: 0xFF - pos*3, G: pos * 3, B: 0x0, A: w.Brightness}
|
||||
} else if w.pos < 170 {
|
||||
pos -= 85
|
||||
c = color.RGBA{R: 0x0, G: 0xFF - pos*3, B: pos * 3, A: w.Brightness}
|
||||
} else {
|
||||
pos -= 170
|
||||
c = color.RGBA{R: pos * 3, G: 0x0, B: 0xFF - pos*3, A: w.Brightness}
|
||||
}
|
||||
w.pos++
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/buzzer"
|
||||
)
|
||||
|
||||
type note struct {
|
||||
tone float64
|
||||
duration float64
|
||||
}
|
||||
|
||||
func main() {
|
||||
speaker := machine.PA30
|
||||
speaker.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
speaker.Set(true)
|
||||
|
||||
bzrPin := machine.A0
|
||||
bzrPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
bzr := buzzer.New(bzrPin)
|
||||
|
||||
song := []note{
|
||||
{buzzer.C3, buzzer.Quarter},
|
||||
{buzzer.D3, buzzer.Quarter},
|
||||
{buzzer.E3, buzzer.Quarter},
|
||||
{buzzer.F3, buzzer.Quarter},
|
||||
{buzzer.G3, buzzer.Quarter},
|
||||
{buzzer.A3, buzzer.Quarter},
|
||||
{buzzer.B3, buzzer.Quarter},
|
||||
{buzzer.C3, buzzer.Quarter},
|
||||
}
|
||||
|
||||
for _, val := range song {
|
||||
bzr.Tone(val.tone, val.duration)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
func main() {
|
||||
motor := easystepper.New(machine.P13, machine.P15, machine.P14, machine.P16, 200, 75)
|
||||
motor.Configure()
|
||||
|
||||
for {
|
||||
println("CLOCKWISE")
|
||||
|
||||
@@ -23,11 +23,12 @@ const actAsAP = false
|
||||
const ssid = "YOURSSID"
|
||||
const pass = "YOURPASS"
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
uart = machine.UART1
|
||||
tx = machine.D10
|
||||
rx = machine.D11
|
||||
tx = machine.PA22
|
||||
rx = machine.PA23
|
||||
|
||||
console = machine.UART0
|
||||
|
||||
@@ -42,28 +43,20 @@ func main() {
|
||||
adaptor.Configure()
|
||||
|
||||
// first check if connected
|
||||
if adaptor.Connected() {
|
||||
if connectToESP() {
|
||||
println("Connected to wifi adaptor.")
|
||||
adaptor.Echo(false)
|
||||
console.Write([]byte("\r\n"))
|
||||
console.Write([]byte("ESP-AT console enabled.\r\n"))
|
||||
console.Write([]byte("Firmware version:\r\n"))
|
||||
console.Write(adaptor.Version())
|
||||
console.Write([]byte("\r\n"))
|
||||
|
||||
if actAsAP {
|
||||
provideAP()
|
||||
} else {
|
||||
connectToAP()
|
||||
}
|
||||
|
||||
console.Write([]byte("Type an AT command then press enter:\r\n"))
|
||||
prompt()
|
||||
connectToAP()
|
||||
} else {
|
||||
console.Write([]byte("\r\n"))
|
||||
console.Write([]byte("Unable to connect to wifi adaptor.\r\n"))
|
||||
println("")
|
||||
failMessage("Unable to connect to wifi adaptor.")
|
||||
return
|
||||
}
|
||||
|
||||
println("Type an AT command then press enter:")
|
||||
prompt()
|
||||
|
||||
input := make([]byte, 64)
|
||||
i := 0
|
||||
for {
|
||||
@@ -80,11 +73,9 @@ func main() {
|
||||
input[i+1] = byte('\n')
|
||||
adaptor.Write(input[:i+2])
|
||||
|
||||
// give the ESP8266 a chance to respond.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// display response
|
||||
console.Write(adaptor.Response())
|
||||
r, _ := adaptor.Response(500)
|
||||
console.Write(r)
|
||||
|
||||
// prompt
|
||||
prompt()
|
||||
@@ -103,27 +94,50 @@ func main() {
|
||||
}
|
||||
|
||||
func prompt() {
|
||||
console.Write([]byte("ESPAT>"))
|
||||
print("ESPAT>")
|
||||
}
|
||||
|
||||
// connect to ESP8266/ESP32
|
||||
func connectToESP() bool {
|
||||
for i := 0; i < 5; i++ {
|
||||
println("Connecting to wifi adaptor...")
|
||||
if adaptor.Connected() {
|
||||
return true
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
console.Write([]byte("Connecting to wifi network...\r\n"))
|
||||
println("Connecting to wifi network '" + ssid + "'")
|
||||
|
||||
adaptor.SetWifiMode(espat.WifiModeClient)
|
||||
adaptor.ConnectToAP(ssid, pass, 10)
|
||||
console.Write([]byte("Connected.\r\n"))
|
||||
console.Write([]byte(adaptor.GetClientIP()))
|
||||
console.Write([]byte("\r\n"))
|
||||
|
||||
println("Connected.")
|
||||
ip, err := adaptor.GetClientIP()
|
||||
if err != nil {
|
||||
failMessage(err.Error())
|
||||
}
|
||||
|
||||
println(ip)
|
||||
}
|
||||
|
||||
// provide access point
|
||||
func provideAP() {
|
||||
console.Write([]byte("Starting wifi network as access point '"))
|
||||
console.Write([]byte(ssid))
|
||||
console.Write([]byte("'...\r\n"))
|
||||
println("Starting wifi network as access point '" + ssid + "'...")
|
||||
adaptor.SetWifiMode(espat.WifiModeAP)
|
||||
adaptor.SetAPConfig(ssid, pass, 7, espat.WifiAPSecurityWPA2_PSK)
|
||||
console.Write([]byte("Ready.\r\n"))
|
||||
console.Write([]byte(adaptor.GetAPIP()))
|
||||
console.Write([]byte("\r\n"))
|
||||
println("Ready.")
|
||||
ip, _ := adaptor.GetAPIP()
|
||||
println(ip)
|
||||
}
|
||||
|
||||
func failMessage(msg string) {
|
||||
for {
|
||||
println(msg)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/espat"
|
||||
"tinygo.org/x/drivers/net"
|
||||
)
|
||||
|
||||
// change actAsAP to true to act as an access point instead of connecting to one.
|
||||
@@ -20,13 +21,12 @@ const actAsAP = false
|
||||
const ssid = "YOURSSID"
|
||||
const pass = "YOURPASS"
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
uart = machine.UART1
|
||||
tx = machine.D10
|
||||
rx = machine.D11
|
||||
|
||||
console = machine.UART0
|
||||
tx = machine.PA22
|
||||
rx = machine.PA23
|
||||
|
||||
adaptor *espat.Device
|
||||
)
|
||||
@@ -43,34 +43,29 @@ func main() {
|
||||
readyled.High()
|
||||
|
||||
// first check if connected
|
||||
if adaptor.Connected() {
|
||||
console.Write([]byte("Connected to wifi adaptor.\r\n"))
|
||||
if connectToESP() {
|
||||
println("Connected to wifi adaptor.")
|
||||
adaptor.Echo(false)
|
||||
|
||||
if actAsAP {
|
||||
provideAP()
|
||||
} else {
|
||||
connectToAP()
|
||||
}
|
||||
connectToAP()
|
||||
} else {
|
||||
console.Write([]byte("\r\n"))
|
||||
console.Write([]byte("Unable to connect to wifi adaptor.\r\n"))
|
||||
println("")
|
||||
failMessage("Unable to connect to wifi adaptor.")
|
||||
return
|
||||
}
|
||||
|
||||
// now make UDP connection
|
||||
laddr := &espat.UDPAddr{Port: 2222}
|
||||
console.Write([]byte("Loading UDP listener...\r\n"))
|
||||
conn, _ := adaptor.ListenUDP("UDP", laddr)
|
||||
laddr := &net.UDPAddr{Port: 2222}
|
||||
println("Loading UDP listener...")
|
||||
conn, _ := net.ListenUDP("UDP", laddr)
|
||||
|
||||
console.Write([]byte("Waiting for data...\r\n"))
|
||||
println("Waiting for data...")
|
||||
data := make([]byte, 50)
|
||||
blink := true
|
||||
for {
|
||||
n, _ := conn.Read(data)
|
||||
if n > 0 {
|
||||
console.Write(data[:n])
|
||||
console.Write([]byte("\r\n"))
|
||||
println(string(data[:n]))
|
||||
conn.Write([]byte("hello back\r\n"))
|
||||
}
|
||||
blink = !blink
|
||||
@@ -83,29 +78,52 @@ func main() {
|
||||
}
|
||||
|
||||
// Right now this code is never reached. Need a way to trigger it...
|
||||
console.Write([]byte("Disconnecting UDP...\r\n"))
|
||||
println("Disconnecting UDP...")
|
||||
conn.Close()
|
||||
console.Write([]byte("Done.\r\n"))
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
// connect to ESP8266/ESP32
|
||||
func connectToESP() bool {
|
||||
for i := 0; i < 5; i++ {
|
||||
println("Connecting to wifi adaptor...")
|
||||
if adaptor.Connected() {
|
||||
return true
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
console.Write([]byte("Connecting to wifi network...\r\n"))
|
||||
println("Connecting to wifi network '" + ssid + "'")
|
||||
|
||||
adaptor.SetWifiMode(espat.WifiModeClient)
|
||||
adaptor.ConnectToAP(ssid, pass, 10)
|
||||
console.Write([]byte("Connected.\r\n"))
|
||||
console.Write([]byte(adaptor.GetClientIP()))
|
||||
console.Write([]byte("\r\n"))
|
||||
|
||||
println("Connected.")
|
||||
ip, err := adaptor.GetClientIP()
|
||||
if err != nil {
|
||||
failMessage(err.Error())
|
||||
}
|
||||
|
||||
println(ip)
|
||||
}
|
||||
|
||||
// provide access point
|
||||
func provideAP() {
|
||||
console.Write([]byte("Starting wifi network as access point '"))
|
||||
console.Write([]byte(ssid))
|
||||
console.Write([]byte("'...\r\n"))
|
||||
println("Starting wifi network as access point '" + ssid + "'...")
|
||||
adaptor.SetWifiMode(espat.WifiModeAP)
|
||||
adaptor.SetAPConfig(ssid, pass, 7, espat.WifiAPSecurityWPA2_PSK)
|
||||
console.Write([]byte("Ready.\r\n"))
|
||||
console.Write([]byte(adaptor.GetAPIP()))
|
||||
console.Write([]byte("\r\n"))
|
||||
println("Ready.")
|
||||
ip, _ := adaptor.GetAPIP()
|
||||
println(ip)
|
||||
}
|
||||
|
||||
func failMessage(msg string) {
|
||||
for {
|
||||
println(msg)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/espat"
|
||||
"tinygo.org/x/drivers/net"
|
||||
)
|
||||
|
||||
// access point info
|
||||
@@ -20,13 +21,12 @@ const pass = "YOURPASS"
|
||||
// IP address of the listener aka "hub". Replace with your own info.
|
||||
const hubIP = "0.0.0.0"
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
uart = machine.UART1
|
||||
tx = machine.D10
|
||||
rx = machine.D11
|
||||
|
||||
console = machine.UART0
|
||||
tx = machine.PA22
|
||||
rx = machine.PA23
|
||||
|
||||
adaptor *espat.Device
|
||||
)
|
||||
@@ -39,45 +39,70 @@ func main() {
|
||||
adaptor.Configure()
|
||||
|
||||
// first check if connected
|
||||
if adaptor.Connected() {
|
||||
console.Write([]byte("Connected to wifi adaptor.\r\n"))
|
||||
if connectToESP() {
|
||||
println("Connected to wifi adaptor.")
|
||||
adaptor.Echo(false)
|
||||
|
||||
connectToAP()
|
||||
} else {
|
||||
console.Write([]byte("\r\n"))
|
||||
console.Write([]byte("Unable to connect to wifi adaptor.\r\n"))
|
||||
println("")
|
||||
failMessage("Unable to connect to wifi adaptor.")
|
||||
return
|
||||
}
|
||||
|
||||
// now make UDP connection
|
||||
ip := espat.ParseIP(hubIP)
|
||||
raddr := &espat.UDPAddr{IP: ip, Port: 2222}
|
||||
laddr := &espat.UDPAddr{Port: 2222}
|
||||
ip := net.ParseIP(hubIP)
|
||||
raddr := &net.UDPAddr{IP: ip, Port: 2222}
|
||||
laddr := &net.UDPAddr{Port: 2222}
|
||||
|
||||
console.Write([]byte("Dialing UDP connection...\r\n"))
|
||||
conn, _ := adaptor.DialUDP("udp", laddr, raddr)
|
||||
println("Dialing UDP connection...")
|
||||
conn, _ := net.DialUDP("udp", laddr, raddr)
|
||||
|
||||
for {
|
||||
// send data
|
||||
console.Write([]byte("Sending data...\r\n"))
|
||||
println("Sending data...")
|
||||
conn.Write([]byte("hello\r\n"))
|
||||
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Right now this code is never reached. Need a way to trigger it...
|
||||
console.Write([]byte("Disconnecting UDP...\r\n"))
|
||||
println("Disconnecting UDP...")
|
||||
conn.Close()
|
||||
console.Write([]byte("Done.\r\n"))
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
// connect to ESP8266/ESP32
|
||||
func connectToESP() bool {
|
||||
for i := 0; i < 5; i++ {
|
||||
println("Connecting to wifi adaptor...")
|
||||
if adaptor.Connected() {
|
||||
return true
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
console.Write([]byte("Connecting to wifi network...\r\n"))
|
||||
println("Connecting to wifi network '" + ssid + "'")
|
||||
|
||||
adaptor.SetWifiMode(espat.WifiModeClient)
|
||||
adaptor.ConnectToAP(ssid, pass, 10)
|
||||
console.Write([]byte("Connected.\r\n"))
|
||||
console.Write([]byte(adaptor.GetClientIP()))
|
||||
console.Write([]byte("\r\n"))
|
||||
|
||||
println("Connected.")
|
||||
ip, err := adaptor.GetClientIP()
|
||||
if err != nil {
|
||||
failMessage(err.Error())
|
||||
}
|
||||
|
||||
println(ip)
|
||||
}
|
||||
|
||||
func failMessage(msg string) {
|
||||
for {
|
||||
println(msg)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
|
||||
// It creates an MQTT connection that publishes a message every second
|
||||
// to an MQTT broker.
|
||||
//
|
||||
// In other words:
|
||||
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker.
|
||||
//
|
||||
// You must install the Paho MQTT package to build this program:
|
||||
//
|
||||
// go get -u github.com/eclipse/paho.mqtt.golang
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/espat"
|
||||
"tinygo.org/x/drivers/net/mqtt"
|
||||
)
|
||||
|
||||
// access point info
|
||||
const ssid = "YOURSSID"
|
||||
const pass = "YOURPASS"
|
||||
|
||||
// IP address of the MQTT broker to use. Replace with your own info.
|
||||
const server = "tcp://test.mosquitto.org:1883"
|
||||
|
||||
//const server = "ssl://test.mosquitto.org:8883"
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
uart = machine.UART2
|
||||
tx = machine.PA22
|
||||
rx = machine.PA23
|
||||
|
||||
console = machine.UART0
|
||||
|
||||
adaptor *espat.Device
|
||||
topic = "tinygo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
time.Sleep(3000 * time.Millisecond)
|
||||
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Init esp8266/esp32
|
||||
adaptor = espat.New(uart)
|
||||
adaptor.Configure()
|
||||
|
||||
// first check if connected
|
||||
if connectToESP() {
|
||||
println("Connected to wifi adaptor.")
|
||||
adaptor.Echo(false)
|
||||
|
||||
connectToAP()
|
||||
} else {
|
||||
println("")
|
||||
failMessage("Unable to connect to wifi adaptor.")
|
||||
return
|
||||
}
|
||||
|
||||
opts := mqtt.NewClientOptions()
|
||||
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
|
||||
|
||||
println("Connecting to MQTT broker at", server)
|
||||
cl := mqtt.NewClient(opts)
|
||||
if token := cl.Connect(); token.Wait() && token.Error() != nil {
|
||||
failMessage(token.Error().Error())
|
||||
}
|
||||
|
||||
for {
|
||||
println("Publishing MQTT message...")
|
||||
data := []byte("{\"e\":[{ \"n\":\"hello\", \"v\":101 }]}")
|
||||
token := cl.Publish(topic, 0, false, data)
|
||||
token.Wait()
|
||||
if token.Error() != nil {
|
||||
println(token.Error().Error())
|
||||
}
|
||||
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Right now this code is never reached. Need a way to trigger it...
|
||||
println("Disconnecting MQTT...")
|
||||
cl.Disconnect(100)
|
||||
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
// connect to ESP8266/ESP32
|
||||
func connectToESP() bool {
|
||||
for i := 0; i < 5; i++ {
|
||||
println("Connecting to wifi adaptor...")
|
||||
if adaptor.Connected() {
|
||||
return true
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
println("Connecting to wifi network '" + ssid + "'")
|
||||
|
||||
adaptor.SetWifiMode(espat.WifiModeClient)
|
||||
adaptor.ConnectToAP(ssid, pass, 10)
|
||||
|
||||
println("Connected.")
|
||||
ip, err := adaptor.GetClientIP()
|
||||
if err != nil {
|
||||
failMessage(err.Error())
|
||||
}
|
||||
|
||||
println(ip)
|
||||
}
|
||||
|
||||
// Returns an int >= min, < max
|
||||
func randomInt(min, max int) int {
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
||||
|
||||
// Generate a random string of A-Z chars with len = l
|
||||
func randomString(len int) string {
|
||||
bytes := make([]byte, len)
|
||||
for i := 0; i < len; i++ {
|
||||
bytes[i] = byte(randomInt(65, 90))
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func failMessage(msg string) {
|
||||
for {
|
||||
println(msg)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
|
||||
// It creates an MQTT connection that publishes a message every second
|
||||
// to an MQTT broker.
|
||||
//
|
||||
// In other words:
|
||||
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker.
|
||||
//
|
||||
// You must also install the Paho MQTT package to build this program:
|
||||
//
|
||||
// go get -u github.com/eclipse/paho.mqtt.golang
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/espat"
|
||||
"tinygo.org/x/drivers/net/mqtt"
|
||||
)
|
||||
|
||||
// access point info
|
||||
const ssid = "YOURSSID"
|
||||
const pass = "YOURPASS"
|
||||
|
||||
// IP address of the MQTT broker to use. Replace with your own info.
|
||||
//const server = "tcp://test.mosquitto.org:1883"
|
||||
|
||||
const server = "ssl://test.mosquitto.org:8883"
|
||||
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
// these are defaults for the Arduino Nano33 IoT.
|
||||
uart = machine.UART1
|
||||
tx = machine.PA22
|
||||
rx = machine.PA23
|
||||
|
||||
console = machine.UART0
|
||||
|
||||
adaptor *espat.Device
|
||||
cl mqtt.Client
|
||||
topicTx = "tinygo/tx"
|
||||
topicRx = "tinygo/rx"
|
||||
)
|
||||
|
||||
func subHandler(client mqtt.Client, msg mqtt.Message) {
|
||||
fmt.Printf("[%s] ", msg.Topic())
|
||||
fmt.Printf("%s\r\n", msg.Payload())
|
||||
}
|
||||
|
||||
func main() {
|
||||
time.Sleep(3000 * time.Millisecond)
|
||||
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Init esp8266/esp32
|
||||
adaptor = espat.New(uart)
|
||||
adaptor.Configure()
|
||||
|
||||
// first check if connected
|
||||
if connectToESP() {
|
||||
println("Connected to wifi adaptor.")
|
||||
adaptor.Echo(false)
|
||||
|
||||
connectToAP()
|
||||
} else {
|
||||
println("")
|
||||
failMessage("Unable to connect to wifi adaptor.")
|
||||
return
|
||||
}
|
||||
|
||||
opts := mqtt.NewClientOptions()
|
||||
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
|
||||
|
||||
println("Connecting to MQTT broker at", server)
|
||||
cl = mqtt.NewClient(opts)
|
||||
if token := cl.Connect(); token.Wait() && token.Error() != nil {
|
||||
failMessage(token.Error().Error())
|
||||
}
|
||||
|
||||
// subscribe
|
||||
token := cl.Subscribe(topicRx, 0, subHandler)
|
||||
token.Wait()
|
||||
if token.Error() != nil {
|
||||
failMessage(token.Error().Error())
|
||||
}
|
||||
|
||||
go publishing()
|
||||
|
||||
select {}
|
||||
|
||||
// Right now this code is never reached. Need a way to trigger it...
|
||||
println("Disconnecting MQTT...")
|
||||
cl.Disconnect(100)
|
||||
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
func publishing() {
|
||||
for {
|
||||
println("Publishing MQTT message...")
|
||||
data := []byte("{\"e\":[{ \"n\":\"hello\", \"v\":101 }]}")
|
||||
token := cl.Publish(topicTx, 0, false, data)
|
||||
token.Wait()
|
||||
if token.Error() != nil {
|
||||
println(token.Error().Error())
|
||||
}
|
||||
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// connect to ESP8266/ESP32
|
||||
func connectToESP() bool {
|
||||
for i := 0; i < 5; i++ {
|
||||
println("Connecting to wifi adaptor...")
|
||||
if adaptor.Connected() {
|
||||
return true
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
println("Connecting to wifi network '" + ssid + "'")
|
||||
|
||||
adaptor.SetWifiMode(espat.WifiModeClient)
|
||||
adaptor.ConnectToAP(ssid, pass, 10)
|
||||
|
||||
println("Connected.")
|
||||
ip, err := adaptor.GetClientIP()
|
||||
if err != nil {
|
||||
failMessage(err.Error())
|
||||
}
|
||||
|
||||
println(ip)
|
||||
}
|
||||
|
||||
// Returns an int >= min, < max
|
||||
func randomInt(min, max int) int {
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
||||
|
||||
// Generate a random string of A-Z chars with len = l
|
||||
func randomString(len int) string {
|
||||
bytes := make([]byte, len)
|
||||
for i := 0; i < len; i++ {
|
||||
bytes[i] = byte(randomInt(65, 90))
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func failMessage(msg string) {
|
||||
for {
|
||||
println(msg)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
|
||||
// It creates a UDP connection you can use to get info to/from your computer via the microcontroller.
|
||||
//
|
||||
// In other words:
|
||||
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/espat"
|
||||
"tinygo.org/x/drivers/net"
|
||||
)
|
||||
|
||||
// access point info
|
||||
const ssid = "YOURSSID"
|
||||
const pass = "YOURPASS"
|
||||
|
||||
// IP address of the server aka "hub". Replace with your own info.
|
||||
const serverIP = "0.0.0.0"
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
uart = machine.UART1
|
||||
tx = machine.PA22
|
||||
rx = machine.PA23
|
||||
|
||||
adaptor *espat.Device
|
||||
)
|
||||
|
||||
func main() {
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
|
||||
// Init esp8266/esp32
|
||||
adaptor = espat.New(uart)
|
||||
adaptor.Configure()
|
||||
|
||||
// first check if connected
|
||||
if connectToESP() {
|
||||
println("Connected to wifi adaptor.")
|
||||
adaptor.Echo(false)
|
||||
|
||||
connectToAP()
|
||||
} else {
|
||||
println("")
|
||||
failMessage("Unable to connect to wifi adaptor.")
|
||||
return
|
||||
}
|
||||
|
||||
// now make TCP connection
|
||||
ip := net.ParseIP(serverIP)
|
||||
raddr := &net.TCPAddr{IP: ip, Port: 8080}
|
||||
laddr := &net.TCPAddr{Port: 8080}
|
||||
|
||||
println("Dialing TCP connection...")
|
||||
conn, err := net.DialTCP("tcp", laddr, raddr)
|
||||
if err != nil {
|
||||
failMessage(err.Error())
|
||||
}
|
||||
|
||||
for {
|
||||
// send data
|
||||
println("Sending data...")
|
||||
conn.Write([]byte("hello\r\n"))
|
||||
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Right now this code is never reached. Need a way to trigger it...
|
||||
println("Disconnecting TCP...")
|
||||
conn.Close()
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
// connect to ESP8266/ESP32
|
||||
func connectToESP() bool {
|
||||
for i := 0; i < 5; i++ {
|
||||
println("Connecting to wifi adaptor...")
|
||||
if adaptor.Connected() {
|
||||
return true
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
println("Connecting to wifi network '" + ssid + "'")
|
||||
|
||||
adaptor.SetWifiMode(espat.WifiModeClient)
|
||||
adaptor.ConnectToAP(ssid, pass, 10)
|
||||
|
||||
println("Connected.")
|
||||
ip, err := adaptor.GetClientIP()
|
||||
if err != nil {
|
||||
failMessage(err.Error())
|
||||
}
|
||||
|
||||
println(ip)
|
||||
}
|
||||
|
||||
func failMessage(msg string) {
|
||||
for {
|
||||
println(msg)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"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,
|
||||
)
|
||||
|
||||
black = color.RGBA{0, 0, 0, 255}
|
||||
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}
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
machine.TFT_BACKLIGHT.Configure(machine.PinConfig{machine.PinOutput})
|
||||
|
||||
display.Configure(ili9341.Config{})
|
||||
width, height := display.Size()
|
||||
|
||||
display.FillScreen(black)
|
||||
machine.TFT_BACKLIGHT.High()
|
||||
|
||||
display.FillRectangle(0, 0, width/2, height/2, white)
|
||||
display.FillRectangle(width/2, 0, width/2, height/2, red)
|
||||
display.FillRectangle(0, height/2, width/2, height/2, green)
|
||||
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
|
||||
display.FillRectangle(width/4, height/4, width/2, height/2, black)
|
||||
for {
|
||||
time.Sleep(time.Hour)
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,234 @@
|
||||
// Port of Adafruit's "pyportal_boing" demo found here:
|
||||
// https://github.com/adafruit/Adafruit_ILI9341/blob/master/examples/pyportal_boing
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/examples/ili9341/pyportal_boing/graphics"
|
||||
"tinygo.org/x/drivers/ili9341"
|
||||
)
|
||||
|
||||
const (
|
||||
BGCOLOR = 0xAD75
|
||||
GRIDCOLOR = 0xA815
|
||||
BGSHADOW = 0x5285
|
||||
GRIDSHADOW = 0x600C
|
||||
RED = 0xF800
|
||||
WHITE = 0xFFFF
|
||||
|
||||
YBOTTOM = 123 // Ball Y coord at bottom
|
||||
YBOUNCE = -3.5 // Upward velocity on ball bounce
|
||||
|
||||
_debug = false
|
||||
)
|
||||
|
||||
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
|
||||
frame int64
|
||||
|
||||
// Ball coordinates are stored floating-point because screen refresh
|
||||
// is so quick, whole-pixel movements are just too fast!
|
||||
ballx float32
|
||||
bally float32
|
||||
ballvx float32
|
||||
ballvy float32
|
||||
ballframe float32
|
||||
balloldx float32
|
||||
balloldy float32
|
||||
|
||||
// Color table for ball rotation effect
|
||||
palette [16]uint16
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// configure backlight
|
||||
machine.TFT_BACKLIGHT.Configure(machine.PinConfig{machine.PinOutput})
|
||||
|
||||
// configure display
|
||||
display.Configure(ili9341.Config{})
|
||||
print("width, height == ")
|
||||
width, height := display.Size()
|
||||
println(width, height)
|
||||
|
||||
machine.TFT_BACKLIGHT.High()
|
||||
|
||||
display.SetRotation(ili9341.Rotation270)
|
||||
DrawBackground()
|
||||
|
||||
startTime = time.Now().UnixNano()
|
||||
frame = 0
|
||||
|
||||
ballx = 20.0
|
||||
bally = YBOTTOM // Current ball position
|
||||
ballvx = 0.8
|
||||
ballvy = YBOUNCE // Ball velocity
|
||||
ballframe = 3 // Ball animation frame #
|
||||
balloldx = ballx
|
||||
balloldy = bally // Prior ball position
|
||||
|
||||
for {
|
||||
|
||||
balloldx = ballx // Save prior position
|
||||
balloldy = bally
|
||||
ballx += ballvx // Update position
|
||||
bally += ballvy
|
||||
ballvy += 0.06 // Update Y velocity
|
||||
if (ballx <= 15) || (ballx >= graphics.SCREENWIDTH-graphics.BALLWIDTH) {
|
||||
ballvx *= -1 // Left/right bounce
|
||||
}
|
||||
if bally >= YBOTTOM { // Hit ground?
|
||||
bally = YBOTTOM // Clip and
|
||||
ballvy = YBOUNCE // bounce up
|
||||
}
|
||||
|
||||
// Determine screen area to update. This is the bounds of the ball's
|
||||
// prior and current positions, so the old ball is fully erased and new
|
||||
// ball is fully drawn.
|
||||
var minx, miny, maxx, maxy, width, height int16
|
||||
|
||||
// Determine bounds of prior and new positions
|
||||
minx = int16(ballx)
|
||||
if int16(balloldx) < minx {
|
||||
minx = int16(balloldx)
|
||||
}
|
||||
miny = int16(bally)
|
||||
if int16(balloldy) < miny {
|
||||
miny = int16(balloldy)
|
||||
}
|
||||
maxx = int16(ballx + graphics.BALLWIDTH - 1)
|
||||
if int16(balloldx+graphics.BALLWIDTH-1) > maxx {
|
||||
maxx = int16(balloldx + graphics.BALLWIDTH - 1)
|
||||
}
|
||||
maxy = int16(bally + graphics.BALLHEIGHT - 1)
|
||||
if int16(balloldy+graphics.BALLHEIGHT-1) > maxy {
|
||||
maxy = int16(balloldy + graphics.BALLHEIGHT - 1)
|
||||
}
|
||||
|
||||
width = maxx - minx + 1
|
||||
height = maxy - miny + 1
|
||||
|
||||
// Ball animation frame # is incremented opposite the ball's X velocity
|
||||
ballframe -= ballvx * 0.5
|
||||
if ballframe < 0 {
|
||||
ballframe += 14 // Constrain from 0 to 13
|
||||
} else if ballframe >= 14 {
|
||||
ballframe -= 14
|
||||
}
|
||||
|
||||
// Set 7 palette entries to white, 7 to red, based on frame number.
|
||||
// This makes the ball spin
|
||||
for i := 0; i < 14; i++ {
|
||||
if (int(ballframe)+i)%14 < 7 {
|
||||
palette[i+2] = WHITE
|
||||
} else {
|
||||
palette[i+2] = RED
|
||||
} // Palette entries 0 and 1 aren't used (clear and shadow, respectively)
|
||||
}
|
||||
|
||||
// Only the changed rectangle is drawn into the 'renderbuf' array...
|
||||
var c uint16 //, *destPtr;
|
||||
bx := minx - int16(ballx) // X relative to ball bitmap (can be negative)
|
||||
by := miny - int16(bally) // Y relative to ball bitmap (can be negative)
|
||||
bgx := minx // X relative to background bitmap (>= 0)
|
||||
bgy := miny // Y relative to background bitmap (>= 0)
|
||||
var bx1, bgx1 int16 // Loop counters and working vars
|
||||
var p uint8 // 'packed' value of 2 ball pixels
|
||||
var bufIdx int8 = 0
|
||||
|
||||
//tft.setAddrWindow(minx, miny, width, height)
|
||||
|
||||
for y := 0; y < int(height); y++ { // For each row...
|
||||
//destPtr = &renderbuf[bufIdx][0];
|
||||
bx1 = bx // Need to keep the original bx and bgx values,
|
||||
bgx1 = bgx // so copies of them are made here (and changed in loop below)
|
||||
for x := 0; x < int(width); x++ {
|
||||
var bgidx = int(bgy)*(graphics.SCREENWIDTH/8) + int(bgx1/8)
|
||||
if (bx1 >= 0) && (bx1 < graphics.BALLWIDTH) && // Is current pixel row/column
|
||||
(by >= 0) && (by < graphics.BALLHEIGHT) { // inside the ball bitmap area?
|
||||
// Yes, do ball compositing math...
|
||||
p = graphics.Ball[int(by*(graphics.BALLWIDTH/2))+int(bx1/2)] // Get packed value (2 pixels)
|
||||
if (bx1 & 1) != 0 {
|
||||
c = uint16(p & 0xF)
|
||||
} else {
|
||||
c = uint16(p >> 4)
|
||||
} // Unpack high or low nybble
|
||||
if c == 0 { // Outside ball - just draw grid
|
||||
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
|
||||
c = GRIDCOLOR
|
||||
} else {
|
||||
c = BGCOLOR
|
||||
}
|
||||
} else if c > 1 { // In ball area...
|
||||
c = palette[c]
|
||||
} else { // In shadow area...
|
||||
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
|
||||
c = GRIDSHADOW
|
||||
} else {
|
||||
c = BGSHADOW
|
||||
}
|
||||
}
|
||||
} else { // Outside ball bitmap, just draw background bitmap...
|
||||
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
|
||||
c = GRIDCOLOR
|
||||
} else {
|
||||
c = BGCOLOR
|
||||
}
|
||||
}
|
||||
frameBuffer[y*int(width)+x] = c
|
||||
bx1++ // Increment bitmap position counters (X axis)
|
||||
bgx1++
|
||||
}
|
||||
//tft.dmaWait(); // Wait for prior line to complete
|
||||
//tft.writePixels(&renderbuf[bufIdx][0], width, false); // Non-blocking write
|
||||
bufIdx = 1 - bufIdx
|
||||
by++ // Increment bitmap position counters (Y axis)
|
||||
bgy++
|
||||
}
|
||||
|
||||
display.DrawRGBBitmap(minx, miny, frameBuffer[:width*height], width, height)
|
||||
|
||||
// Show approximate frame rate
|
||||
frame++
|
||||
if frame&255 == 0 { // Every 256 frames...
|
||||
elapsed := (time.Now().UnixNano() - startTime) / int64(time.Second)
|
||||
if elapsed > 0 {
|
||||
println(frame/elapsed, " fps")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DrawBackground() {
|
||||
w, h := display.Size()
|
||||
byteWidth := (w + 7) / 8 // Bitmap scanline pad = whole byte
|
||||
var b uint8
|
||||
for j := int16(0); j < h; j++ {
|
||||
for k := int16(0); k < w; k++ {
|
||||
if k&7 > 0 {
|
||||
b <<= 1
|
||||
} else {
|
||||
b = graphics.Background[j*byteWidth+k/8]
|
||||
}
|
||||
if b&0x80 == 0 {
|
||||
frameBuffer[k] = BGCOLOR
|
||||
} else {
|
||||
frameBuffer[k] = GRIDCOLOR
|
||||
}
|
||||
}
|
||||
display.DrawRGBBitmap(0, j, frameBuffer[0:w], w, 1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/l293x"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wheel := l293x.New(machine.D10, machine.D11, machine.D12)
|
||||
wheel.Configure()
|
||||
|
||||
for i := 0; i <= 10; i++ {
|
||||
println("Forward")
|
||||
wheel.Forward()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
println("Backward")
|
||||
wheel.Backward()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/l293x"
|
||||
)
|
||||
|
||||
const (
|
||||
maxSpeed = 30000
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.InitPWM()
|
||||
|
||||
wheel := l293x.NewWithSpeed(machine.D10, machine.D11, machine.PWM{machine.D12})
|
||||
wheel.Configure()
|
||||
|
||||
for i := 0; i <= 10; i++ {
|
||||
println("Forward")
|
||||
var i uint16
|
||||
for i = 0; i < maxSpeed; i += 1000 {
|
||||
wheel.Forward(i)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
println("Backward")
|
||||
for i = 0; i < maxSpeed; i += 1000 {
|
||||
wheel.Backward(i)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/l9110x"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wheel := l9110x.New(machine.D10, machine.D11)
|
||||
wheel.Configure()
|
||||
|
||||
for i := 0; i <= 10; i++ {
|
||||
println("Forward")
|
||||
wheel.Forward()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
println("Backward")
|
||||
wheel.Backward()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/l9110x"
|
||||
)
|
||||
|
||||
const (
|
||||
maxSpeed = 30000
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.InitPWM()
|
||||
|
||||
wheel := l9110x.NewWithSpeed(machine.PWM{machine.D11}, machine.PWM{machine.D12})
|
||||
wheel.Configure()
|
||||
|
||||
for i := 0; i <= 10; i++ {
|
||||
println("Forward")
|
||||
var i uint16
|
||||
for i = 0; i < maxSpeed; i += 1000 {
|
||||
wheel.Forward(i)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
println("Backward")
|
||||
for i = 0; i < maxSpeed; i += 1000 {
|
||||
wheel.Backward(i)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
|
||||
println("Stop")
|
||||
wheel.Stop()
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
var i2c = machine.I2C1
|
||||
|
||||
func main() {
|
||||
i2c.Configure(machine.I2CConfig{})
|
||||
i2c.Configure(machine.I2CConfig{SCL: machine.SCL1_PIN, SDA: machine.SDA1_PIN})
|
||||
|
||||
accel := lis3dh.New(i2c)
|
||||
accel.Address = lis3dh.Address1 // address on the Circuit Playground Express
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Connects to an LSM6DS3 I2C a 6 axis Inertial Measurement Unit (IMU)
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/lsm6ds3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
accel := lsm6ds3.New(machine.I2C0)
|
||||
accel.Configure(lsm6ds3.Configuration{})
|
||||
if !accel.Connected() {
|
||||
println("LSM6DS3 not connected")
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
x, y, z := accel.ReadAcceleration()
|
||||
println("Acceleration:", float32(x)/1000000, float32(y)/1000000, float32(z)/1000000)
|
||||
x, y, z = accel.ReadRotation()
|
||||
println("Gyroscope:", float32(x)/1000000, float32(y)/1000000, float32(z)/1000000)
|
||||
x, _ = accel.ReadTemperature()
|
||||
println("Degrees C", float32(x)/1000, "\n\n")
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Connects to a MCP3008 ADC via SPI.
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/mcp3008"
|
||||
)
|
||||
|
||||
var (
|
||||
spi = machine.SPI0
|
||||
csPin = machine.D12
|
||||
)
|
||||
|
||||
func main() {
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 4000000,
|
||||
Mode: 3})
|
||||
|
||||
adc := mcp3008.New(spi, csPin)
|
||||
adc.Configure()
|
||||
|
||||
// get "CH0" aka "machine.ADC" interface to channel 0 from ADC.
|
||||
p := adc.CH0
|
||||
|
||||
for {
|
||||
val := p.Get()
|
||||
println(val)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Example using the i2s hardware interface on the Adafruit Circuit Playground Express
|
||||
// to read data from the onboard MEMS microphone.
|
||||
//
|
||||
// Uses ideas from the https://github.com/adafruit/Adafruit_CircuitPlayground repo.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/microphone"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSampleRate = 22000
|
||||
quantizeSteps = 64
|
||||
msForSPLSample = 50
|
||||
defaultSampleCountForSPL = (defaultSampleRate / 1000) * msForSPLSample
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2S0.Configure(machine.I2SConfig{
|
||||
Mode: machine.I2SModePDM,
|
||||
AudioFrequency: defaultSampleRate * quantizeSteps / 16,
|
||||
ClockSource: machine.I2SClockSourceExternal,
|
||||
Stereo: true,
|
||||
})
|
||||
|
||||
mic := microphone.New(machine.I2S0)
|
||||
mic.SampleCountForSPL = defaultSampleCountForSPL
|
||||
mic.Configure()
|
||||
|
||||
for {
|
||||
spl, maxval := mic.GetSoundPressure()
|
||||
println("C", spl, "max", maxval)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/shifter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
buttons := shifter.New(shifter.EIGHT_BITS, machine.BUTTON_LATCH, machine.BUTTON_CLK, machine.BUTTON_OUT)
|
||||
buttons.Configure()
|
||||
|
||||
for {
|
||||
// Slower
|
||||
for i := 0; i < 8; i++ {
|
||||
if buttons.Pins[i].Get() {
|
||||
println("Button", i, "pressed")
|
||||
}
|
||||
}
|
||||
|
||||
// Faster
|
||||
pressed, _ := buttons.Read8Input()
|
||||
if pressed&machine.BUTTON_LEFT_MASK > 0 {
|
||||
println("Button LEFT pressed")
|
||||
}
|
||||
if pressed&machine.BUTTON_UP_MASK > 0 {
|
||||
println("Button UP pressed")
|
||||
}
|
||||
if pressed&machine.BUTTON_DOWN_MASK > 0 {
|
||||
println("Button DOWN pressed")
|
||||
}
|
||||
if pressed&machine.BUTTON_RIGHT_MASK > 0 {
|
||||
println("Button RIGHT pressed")
|
||||
}
|
||||
if pressed&machine.BUTTON_SELECT_MASK > 0 {
|
||||
println("Button SELECT pressed")
|
||||
}
|
||||
if pressed&machine.BUTTON_START_MASK > 0 {
|
||||
println("Button START pressed")
|
||||
}
|
||||
if pressed&machine.BUTTON_A_MASK > 0 {
|
||||
println("Button A pressed")
|
||||
}
|
||||
if pressed&machine.BUTTON_B_MASK > 0 {
|
||||
println("Button B pressed")
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package i2c_128x32
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package i2c_128x64
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package ssd1331
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"image/color"
|
||||
|
||||
"tinygo.org/x/drivers/ssd1331"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 8000000,
|
||||
})
|
||||
display := ssd1331.New(machine.SPI0, machine.P6, machine.P7, machine.P8)
|
||||
display.Configure(ssd1331.Config{})
|
||||
display.SetContrast(0x30, 0x20, 0x30)
|
||||
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}
|
||||
black := color.RGBA{0, 0, 0, 255}
|
||||
|
||||
display.FillScreen(black)
|
||||
|
||||
display.FillRectangle(0, 0, width/2, height/2, white)
|
||||
display.FillRectangle(width/2, 0, width/2, height/2, red)
|
||||
display.FillRectangle(0, height/2, width/2, height/2, green)
|
||||
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
|
||||
display.FillRectangle(width/4, height/4, width/2, height/2, black)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"image/color"
|
||||
|
||||
"tinygo.org/x/drivers/st7735"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 8000000,
|
||||
})
|
||||
display := st7735.New(machine.SPI0, machine.P6, machine.P7, machine.P8, machine.P9)
|
||||
display.Configure(st7735.Config{})
|
||||
|
||||
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}
|
||||
black := color.RGBA{0, 0, 0, 255}
|
||||
|
||||
display.FillScreen(black)
|
||||
|
||||
display.FillRectangle(0, 0, width/2, height/2, white)
|
||||
display.FillRectangle(width/2, 0, width/2, height/2, red)
|
||||
display.FillRectangle(0, height/2, width/2, height/2, green)
|
||||
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
|
||||
display.FillRectangle(width/4, height/4, width/2, height/2, black)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"image/color"
|
||||
|
||||
"tinygo.org/x/drivers/st7789"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 8000000,
|
||||
Mode: 3,
|
||||
})
|
||||
display := st7789.New(machine.SPI0, machine.P6, machine.P7, machine.P8)
|
||||
display.Configure(st7789.Config{Rotation: st7789.NO_ROTATION})
|
||||
|
||||
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}
|
||||
black := color.RGBA{0, 0, 0, 255}
|
||||
|
||||
display.FillScreen(black)
|
||||
|
||||
display.FillRectangle(0, 0, width/2, height/2, white)
|
||||
display.FillRectangle(width/2, 0, width/2, height/2, red)
|
||||
display.FillRectangle(0, height/2, width/2, height/2, green)
|
||||
display.FillRectangle(width/2, height/2, width/2, height/2, blue)
|
||||
display.FillRectangle(width/4, height/4, width/2, height/2, black)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// demo of 4-wire touchscreen as described in app note:
|
||||
// http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"math"
|
||||
|
||||
"tinygo.org/x/drivers/touch"
|
||||
"tinygo.org/x/drivers/touch/resistive"
|
||||
)
|
||||
|
||||
var (
|
||||
resistiveTouch = new(resistive.FourWire)
|
||||
)
|
||||
|
||||
const (
|
||||
Xmin = 750
|
||||
Xmax = 325
|
||||
Ymin = 840
|
||||
Ymax = 240
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// configure touchscreen
|
||||
machine.InitADC()
|
||||
resistiveTouch.Configure(&resistive.FourWireConfig{
|
||||
YP: machine.TOUCH_YD, // y+
|
||||
YM: machine.TOUCH_YU, // y-
|
||||
XP: machine.TOUCH_XR, // x+
|
||||
XM: machine.TOUCH_XL, // x-
|
||||
})
|
||||
|
||||
last := touch.Point{}
|
||||
|
||||
// loop and poll for touches, including performing debouncing
|
||||
debounce := 0
|
||||
for {
|
||||
|
||||
point := resistiveTouch.ReadTouchPoint()
|
||||
touch := touch.Point{}
|
||||
if point.Z>>6 > 100 {
|
||||
touch.X = mapval(point.X>>6, Xmin, Xmax, 0, 240)
|
||||
touch.Y = mapval(point.Y>>6, Ymin, Ymax, 0, 320)
|
||||
touch.Z = point.Z >> 6 / 100
|
||||
} else {
|
||||
touch.X = 0
|
||||
touch.Y = 0
|
||||
touch.Z = 0
|
||||
}
|
||||
|
||||
if last.Z != touch.Z {
|
||||
debounce = 0
|
||||
last = touch
|
||||
} else if math.Abs(float64(touch.X-last.X)) > 4 ||
|
||||
math.Abs(float64(touch.Y-last.Y)) > 4 {
|
||||
debounce = 0
|
||||
last = touch
|
||||
} else if debounce > 1 {
|
||||
debounce = 0
|
||||
HandleTouch(last)
|
||||
} else if touch.Z > 0 {
|
||||
debounce++
|
||||
} else {
|
||||
last = touch
|
||||
debounce = 0
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// based on Arduino's "map" function
|
||||
func mapval(x int, inMin int, inMax int, outMin int, outMax int) int {
|
||||
return (x-inMin)*(outMax-outMin)/(inMax-inMin) + outMin
|
||||
}
|
||||
|
||||
func HandleTouch(touch touch.Point) {
|
||||
println("touch point:", touch.X, touch.Y, touch.Z)
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"math"
|
||||
|
||||
"tinygo.org/x/drivers/ili9341"
|
||||
"tinygo.org/x/drivers/touch"
|
||||
"tinygo.org/x/drivers/touch/resistive"
|
||||
)
|
||||
|
||||
var (
|
||||
resistiveTouch = &resistive.FourWire{}
|
||||
|
||||
display = ili9341.NewParallel(
|
||||
machine.LCD_DATA0,
|
||||
machine.TFT_WR,
|
||||
machine.TFT_DC,
|
||||
machine.TFT_CS,
|
||||
machine.TFT_RESET,
|
||||
machine.TFT_RD,
|
||||
)
|
||||
|
||||
white = color.RGBA{255, 255, 255, 255}
|
||||
black = color.RGBA{0, 0, 0, 255}
|
||||
red = color.RGBA{255, 0, 0, 255}
|
||||
green = color.RGBA{0, 255, 0, 255}
|
||||
blue = color.RGBA{0, 0, 255, 255}
|
||||
magenta = color.RGBA{255, 0, 255, 255}
|
||||
yellow = color.RGBA{255, 255, 0, 255}
|
||||
cyan = color.RGBA{0, 255, 255, 255}
|
||||
|
||||
oldColor color.RGBA
|
||||
currentColor color.RGBA
|
||||
)
|
||||
|
||||
const (
|
||||
penRadius = 3
|
||||
boxSize = 30
|
||||
|
||||
Xmin = 750
|
||||
Xmax = 325
|
||||
Ymin = 840
|
||||
Ymax = 240
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// configure backlight
|
||||
machine.TFT_BACKLIGHT.Configure(machine.PinConfig{machine.PinOutput})
|
||||
|
||||
// configure touchscreen
|
||||
machine.InitADC()
|
||||
resistiveTouch.Configure(&resistive.FourWireConfig{
|
||||
YP: machine.TOUCH_YD,
|
||||
YM: machine.TOUCH_YU,
|
||||
XP: machine.TOUCH_XR,
|
||||
XM: machine.TOUCH_XL,
|
||||
})
|
||||
|
||||
// configure display
|
||||
display.Configure(ili9341.Config{})
|
||||
|
||||
// fill the background and activate the backlight
|
||||
width, height := display.Size()
|
||||
display.FillRectangle(0, 0, width, height, black)
|
||||
machine.TFT_BACKLIGHT.High()
|
||||
|
||||
// make color selection boxes
|
||||
display.FillRectangle(0, 0, boxSize, boxSize, red)
|
||||
display.FillRectangle(boxSize, 0, boxSize, boxSize, yellow)
|
||||
display.FillRectangle(boxSize*2, 0, boxSize, boxSize, green)
|
||||
display.FillRectangle(boxSize*3, 0, boxSize, boxSize, cyan)
|
||||
display.FillRectangle(boxSize*4, 0, boxSize, boxSize, blue)
|
||||
display.FillRectangle(boxSize*5, 0, boxSize, boxSize, magenta)
|
||||
display.FillRectangle(boxSize*6, 0, boxSize, boxSize, black)
|
||||
display.FillRectangle(boxSize*7, 0, boxSize, boxSize, white)
|
||||
|
||||
// set the initial color to red and draw a box to highlight it
|
||||
oldColor = red
|
||||
currentColor = red
|
||||
display.DrawRectangle(0, 0, boxSize, boxSize, white)
|
||||
|
||||
last := touch.Point{}
|
||||
|
||||
// loop and poll for touches, including performing debouncing
|
||||
debounce := 0
|
||||
for {
|
||||
|
||||
point := resistiveTouch.ReadTouchPoint()
|
||||
touch := touch.Point{}
|
||||
if point.Z>>6 > 100 {
|
||||
rawX := mapval(point.X>>6, Xmin, Xmax, 0, 240)
|
||||
rawY := mapval(point.Y>>6, Ymin, Ymax, 0, 320)
|
||||
touch.X = rawX
|
||||
touch.Y = rawY
|
||||
touch.Z = 1
|
||||
} else {
|
||||
touch.X = 0
|
||||
touch.Y = 0
|
||||
touch.Z = 0
|
||||
}
|
||||
|
||||
if last.Z != touch.Z {
|
||||
debounce = 0
|
||||
last = touch
|
||||
} else if math.Abs(float64(touch.X-last.X)) > 4 ||
|
||||
math.Abs(float64(touch.Y-last.Y)) > 4 {
|
||||
debounce = 0
|
||||
last = touch
|
||||
} else if debounce > 1 {
|
||||
debounce = 0
|
||||
HandleTouch(last)
|
||||
} else if touch.Z > 0 {
|
||||
debounce++
|
||||
} else {
|
||||
last = touch
|
||||
debounce = 0
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// based on Arduino's "map" function
|
||||
func mapval(x int, inMin int, inMax int, outMin int, outMax int) int {
|
||||
return (x-inMin)*(outMax-outMin)/(inMax-inMin) + outMin
|
||||
}
|
||||
|
||||
func HandleTouch(touch touch.Point) {
|
||||
|
||||
if int16(touch.Y) < boxSize {
|
||||
oldColor = currentColor
|
||||
x := int16(touch.X)
|
||||
switch {
|
||||
case x < boxSize:
|
||||
currentColor = red
|
||||
case x < boxSize*2:
|
||||
currentColor = yellow
|
||||
case x < boxSize*3:
|
||||
currentColor = green
|
||||
case x < boxSize*4:
|
||||
currentColor = cyan
|
||||
case x < boxSize*5:
|
||||
currentColor = blue
|
||||
case x < boxSize*6:
|
||||
currentColor = magenta
|
||||
case x < boxSize*7:
|
||||
currentColor = black
|
||||
case x < boxSize*8:
|
||||
currentColor = white
|
||||
}
|
||||
|
||||
if oldColor == currentColor {
|
||||
return
|
||||
}
|
||||
|
||||
display.DrawRectangle((x/boxSize)*boxSize, 0, boxSize, boxSize, white)
|
||||
switch oldColor {
|
||||
case red:
|
||||
x = 0
|
||||
case yellow:
|
||||
x = boxSize
|
||||
case green:
|
||||
x = boxSize * 2
|
||||
case cyan:
|
||||
x = boxSize * 3
|
||||
case blue:
|
||||
x = boxSize * 4
|
||||
case magenta:
|
||||
x = boxSize * 5
|
||||
case black:
|
||||
x = boxSize * 6
|
||||
case white:
|
||||
x = boxSize * 7
|
||||
}
|
||||
display.FillRectangle(int16(x), 0, boxSize, boxSize, oldColor)
|
||||
|
||||
}
|
||||
|
||||
if (int16(touch.Y) - penRadius) > boxSize {
|
||||
display.FillRectangle(
|
||||
int16(touch.X), int16(touch.Y), penRadius*2, penRadius*2, currentColor)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/veml6070"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
sensor := veml6070.New(machine.I2C0)
|
||||
|
||||
if !sensor.Configure() {
|
||||
println("VEML6070 could not be configured")
|
||||
return
|
||||
}
|
||||
|
||||
println("VEML6070 configured")
|
||||
|
||||
for {
|
||||
intensity, _ := sensor.ReadUVALightIntensity()
|
||||
println("UVA light intensity:", float32(intensity)/1000.0, "W/(m*m)")
|
||||
|
||||
switch sensor.GetEstimatedRiskLevel(intensity) {
|
||||
case veml6070.UVI_RISK_LOW:
|
||||
println("UV risk level: low")
|
||||
case veml6070.UVI_RISK_MODERATE:
|
||||
println("UV risk level: moderate")
|
||||
case veml6070.UVI_RISK_HIGH:
|
||||
println("UV risk level: high")
|
||||
case veml6070.UVI_RISK_VERY_HIGH:
|
||||
println("UV risk level: very high")
|
||||
case veml6070.UVI_RISK_EXTREME:
|
||||
println("UV risk level: extreme")
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
|
||||
// It creates an MQTT connection that publishes a message every second
|
||||
// to an MQTT broker.
|
||||
//
|
||||
// In other words:
|
||||
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker.
|
||||
//
|
||||
// You must install the Paho MQTT package to build this program:
|
||||
//
|
||||
// go get -u github.com/eclipse/paho.mqtt.golang
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net/mqtt"
|
||||
"tinygo.org/x/drivers/wifinina"
|
||||
)
|
||||
|
||||
// access point info
|
||||
const ssid = ""
|
||||
const pass = ""
|
||||
|
||||
// IP address of the MQTT broker to use. Replace with your own info.
|
||||
const server = "tcp://test.mosquitto.org:1883"
|
||||
|
||||
//const server = "ssl://test.mosquitto.org:8883"
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
uart = machine.UART2
|
||||
tx = machine.NINA_TX
|
||||
rx = machine.NINA_RX
|
||||
spi = machine.NINA_SPI
|
||||
|
||||
// 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
|
||||
topic = "tinygo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
time.Sleep(3000 * time.Millisecond)
|
||||
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 8 * 1e6,
|
||||
MOSI: machine.NINA_MOSI,
|
||||
MISO: machine.NINA_MISO,
|
||||
SCK: machine.NINA_SCK,
|
||||
})
|
||||
|
||||
// Init esp8266/esp32
|
||||
adaptor.Configure()
|
||||
|
||||
connectToAP()
|
||||
|
||||
opts := mqtt.NewClientOptions()
|
||||
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
|
||||
|
||||
println("Connectng to MQTT...")
|
||||
cl := mqtt.NewClient(opts)
|
||||
if token := cl.Connect(); token.Wait() && token.Error() != nil {
|
||||
failMessage(token.Error().Error())
|
||||
}
|
||||
|
||||
for i := 0; ; i++ {
|
||||
println("Publishing MQTT message...")
|
||||
data := []byte(fmt.Sprintf(`{"e":[{"n":"hello %d","v":101}]}`, i))
|
||||
token := cl.Publish(topic, 0, false, data)
|
||||
token.Wait()
|
||||
if err := token.Error(); err != nil {
|
||||
switch t := err.(type) {
|
||||
case wifinina.Error:
|
||||
println(t.Error(), "attempting to reconnect")
|
||||
if token := cl.Connect(); token.Wait() && token.Error() != nil {
|
||||
failMessage(token.Error().Error())
|
||||
}
|
||||
default:
|
||||
println(err.Error())
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Right now this code is never reached. Need a way to trigger it...
|
||||
println("Disconnecting MQTT...")
|
||||
cl.Disconnect(100)
|
||||
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
time.Sleep(2 * time.Second)
|
||||
println("Connecting to " + ssid)
|
||||
adaptor.SetPassphrase(ssid, pass)
|
||||
for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; {
|
||||
println("Connection status: " + st.String())
|
||||
time.Sleep(1 * time.Second)
|
||||
st, _ = adaptor.GetConnectionStatus()
|
||||
}
|
||||
println("Connected.")
|
||||
time.Sleep(2 * time.Second)
|
||||
ip, _, _, err := adaptor.GetIP()
|
||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||
println(err.Error())
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
println(ip.String())
|
||||
}
|
||||
|
||||
// Returns an int >= min, < max
|
||||
func randomInt(min, max int) int {
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
||||
|
||||
// Generate a random string of A-Z chars with len = l
|
||||
func randomString(len int) string {
|
||||
bytes := make([]byte, len)
|
||||
for i := 0; i < len; i++ {
|
||||
bytes[i] = byte(randomInt(65, 90))
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func failMessage(msg string) {
|
||||
for {
|
||||
println(msg)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
|
||||
// It creates an MQTT connection that publishes a message every second
|
||||
// to an MQTT broker.
|
||||
//
|
||||
// In other words:
|
||||
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker.
|
||||
//
|
||||
// You must also install the Paho MQTT package to build this program:
|
||||
//
|
||||
// go get -u github.com/eclipse/paho.mqtt.golang
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net/mqtt"
|
||||
"tinygo.org/x/drivers/wifinina"
|
||||
)
|
||||
|
||||
// access point info
|
||||
const ssid = ""
|
||||
const pass = ""
|
||||
|
||||
// IP address of the MQTT broker to use. Replace with your own info.
|
||||
const server = "tcp://test.mosquitto.org:1883"
|
||||
|
||||
//const server = "ssl://test.mosquitto.org:8883"
|
||||
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
uart = machine.UART2
|
||||
tx = machine.NINA_TX
|
||||
rx = machine.NINA_RX
|
||||
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
|
||||
|
||||
cl mqtt.Client
|
||||
topicTx = "tinygo/tx"
|
||||
topicRx = "tinygo/rx"
|
||||
)
|
||||
|
||||
func subHandler(client mqtt.Client, msg mqtt.Message) {
|
||||
fmt.Printf("[%s] ", msg.Topic())
|
||||
fmt.Printf("%s\r\n", msg.Payload())
|
||||
}
|
||||
|
||||
func main() {
|
||||
time.Sleep(3000 * time.Millisecond)
|
||||
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 8 * 1e6,
|
||||
MOSI: machine.NINA_MOSI,
|
||||
MISO: machine.NINA_MISO,
|
||||
SCK: machine.NINA_SCK,
|
||||
})
|
||||
|
||||
// Init esp8266/esp32
|
||||
adaptor.Configure()
|
||||
|
||||
connectToAP()
|
||||
|
||||
opts := mqtt.NewClientOptions()
|
||||
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
|
||||
|
||||
println("Connecting to MQTT broker at", server)
|
||||
cl = mqtt.NewClient(opts)
|
||||
if token := cl.Connect(); token.Wait() && token.Error() != nil {
|
||||
failMessage(token.Error().Error())
|
||||
}
|
||||
|
||||
// subscribe
|
||||
token := cl.Subscribe(topicRx, 0, subHandler)
|
||||
token.Wait()
|
||||
if token.Error() != nil {
|
||||
failMessage(token.Error().Error())
|
||||
}
|
||||
|
||||
go publishing()
|
||||
|
||||
select {}
|
||||
|
||||
// Right now this code is never reached. Need a way to trigger it...
|
||||
println("Disconnecting MQTT...")
|
||||
cl.Disconnect(100)
|
||||
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
func publishing() {
|
||||
for i := 0; ; i++ {
|
||||
println("Publishing MQTT message...")
|
||||
data := []byte(fmt.Sprintf(`{"e":[{"n":"hello %d","v":101}]}`, i))
|
||||
token := cl.Publish(topicRx, 0, false, data)
|
||||
token.Wait()
|
||||
if token.Error() != nil {
|
||||
println(token.Error().Error())
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
time.Sleep(2 * time.Second)
|
||||
println("Connecting to " + ssid)
|
||||
adaptor.SetPassphrase(ssid, pass)
|
||||
for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; {
|
||||
println("Connection status: " + st.String())
|
||||
time.Sleep(1 * time.Second)
|
||||
st, _ = adaptor.GetConnectionStatus()
|
||||
}
|
||||
println("Connected.")
|
||||
time.Sleep(2 * time.Second)
|
||||
ip, _, _, err := adaptor.GetIP()
|
||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||
println(err.Error())
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
println(ip.String())
|
||||
}
|
||||
|
||||
// Returns an int >= min, < max
|
||||
func randomInt(min, max int) int {
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
||||
|
||||
// Generate a random string of A-Z chars with len = l
|
||||
func randomString(len int) string {
|
||||
bytes := make([]byte, len)
|
||||
for i := 0; i < len; i++ {
|
||||
bytes[i] = byte(randomInt(65, 90))
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func failMessage(msg string) {
|
||||
for {
|
||||
println(msg)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// This example opens a TCP connection using a device with WiFiNINA firmware
|
||||
// and sends some data, for the purpose of testing speed and connectivity.
|
||||
//
|
||||
// You can open a server to accept connections from this program using:
|
||||
//
|
||||
// nc -w 5 -lk 8080
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"machine"
|
||||
"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 serverIP = ""
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
uart = machine.UART2
|
||||
tx = machine.NINA_TX
|
||||
rx = machine.NINA_RX
|
||||
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
|
||||
)
|
||||
|
||||
var buf = &bytes.Buffer{}
|
||||
|
||||
func main() {
|
||||
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 8 * 1e6,
|
||||
MOSI: machine.NINA_MOSI,
|
||||
MISO: machine.NINA_MISO,
|
||||
SCK: machine.NINA_SCK,
|
||||
})
|
||||
|
||||
adaptor.Configure()
|
||||
|
||||
connectToAP()
|
||||
|
||||
for {
|
||||
sendBatch()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
func sendBatch() {
|
||||
|
||||
// make TCP connection
|
||||
ip := net.ParseIP(serverIP)
|
||||
raddr := &net.TCPAddr{IP: ip, Port: 8080}
|
||||
laddr := &net.TCPAddr{Port: 8080}
|
||||
|
||||
message("---------------\r\nDialing TCP connection")
|
||||
conn, err := net.DialTCP("tcp", laddr, raddr)
|
||||
for ; err != nil; conn, err = net.DialTCP("tcp", laddr, raddr) {
|
||||
message(err.Error())
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
n := 0
|
||||
w := 0
|
||||
start := time.Now()
|
||||
|
||||
// send data
|
||||
message("Sending data")
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
buf.Reset()
|
||||
fmt.Fprint(buf,
|
||||
"\r---------------------------- i == ", i, " ----------------------------"+
|
||||
"\r---------------------------- i == ", i, " ----------------------------")
|
||||
if w, err = conn.Write(buf.Bytes()); err != nil {
|
||||
println("error:", err.Error(), "\r")
|
||||
continue
|
||||
}
|
||||
n += w
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
ms := time.Now().Sub(start).Milliseconds()
|
||||
fmt.Fprint(buf, "\nWrote ", n, " bytes in ", ms, " ms\r\n")
|
||||
message(buf.String())
|
||||
|
||||
if _, err := conn.Write(buf.Bytes()); err != nil {
|
||||
println("error:", err.Error(), "\r")
|
||||
}
|
||||
|
||||
// Right now this code is never reached. Need a way to trigger it...
|
||||
println("Disconnecting TCP...")
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// This example opens a TCP connection using a device with WiFiNINA firmware
|
||||
// and sends a HTTP request to retrieve a webpage, based on the following
|
||||
// Arduino example:
|
||||
//
|
||||
// https://github.com/arduino-libraries/WiFiNINA/blob/master/examples/WiFiWebClientRepeating/
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"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 server = "tinygo.org"
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||
var (
|
||||
|
||||
// these are the default pins for the Arduino Nano33 IoT.
|
||||
uart = machine.UART2
|
||||
tx = machine.NINA_TX
|
||||
rx = machine.NINA_RX
|
||||
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
|
||||
)
|
||||
|
||||
var buf [256]byte
|
||||
|
||||
var lastRequestTime time.Time
|
||||
var conn net.Conn
|
||||
|
||||
func main() {
|
||||
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
|
||||
// Configure SPI for 8Mhz, Mode 0, MSB First
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Frequency: 8 * 1e6,
|
||||
MOSI: machine.NINA_MOSI,
|
||||
MISO: machine.NINA_MISO,
|
||||
SCK: machine.NINA_SCK,
|
||||
})
|
||||
|
||||
adaptor.Configure()
|
||||
|
||||
connectToAP()
|
||||
|
||||
for {
|
||||
loop()
|
||||
}
|
||||
println("Done.")
|
||||
}
|
||||
|
||||
func loop() {
|
||||
if conn != nil {
|
||||
for n, err := conn.Read(buf[:]); n > 0; n, err = conn.Read(buf[:]) {
|
||||
if err != nil {
|
||||
println("Read error: " + err.Error())
|
||||
} else {
|
||||
print(string(buf[0:n]))
|
||||
}
|
||||
}
|
||||
}
|
||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 10000 {
|
||||
makeHTTPRequest()
|
||||
}
|
||||
}
|
||||
|
||||
func makeHTTPRequest() {
|
||||
|
||||
var err error
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
// make TCP connection
|
||||
ip := net.ParseIP(server)
|
||||
raddr := &net.TCPAddr{IP: ip, Port: 80}
|
||||
laddr := &net.TCPAddr{Port: 8080}
|
||||
|
||||
message("\r\n---------------\r\nDialing TCP connection")
|
||||
conn, err = net.DialTCP("tcp", laddr, raddr)
|
||||
for ; err != nil; conn, err = net.DialTCP("tcp", laddr, raddr) {
|
||||
message("connection failed: " + err.Error())
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
println("Connected!\r")
|
||||
|
||||
print("Sending HTTP request...")
|
||||
fmt.Fprintln(conn, "GET / HTTP/1.1")
|
||||
fmt.Fprintln(conn, "Host:", server)
|
||||
fmt.Fprintln(conn, "User-Agent: TinyGo/0.10.0")
|
||||
fmt.Fprintln(conn, "Connection: close")
|
||||
fmt.Fprintln(conn)
|
||||
println("Sent!\r\n\r")
|
||||
|
||||
lastRequestTime = time.Now()
|
||||
}
|
||||
|
||||
func readLine(conn *net.TCPSerialConn) string {
|
||||
println("Attempting to read...\r")
|
||||
b := buf[:]
|
||||
for expiry := time.Now().Unix() + 10; time.Now().Unix() > expiry; {
|
||||
if n, err := conn.Read(b); n > 0 && err == nil {
|
||||
return string(b[0:n])
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// connect to access point
|
||||
func connectToAP() {
|
||||
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")
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// +build digispark
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
// This is the pin assignment for the Digispark only.
|
||||
// Replace neo in the code below to match the pin
|
||||
// that you are using if different.
|
||||
var neo machine.Pin = 0
|
||||
+10
-7
@@ -1,8 +1,7 @@
|
||||
// Connects to an WS2812 RGB LED strip with 10 LEDS, such as
|
||||
// on an Adafruit Circuit Playground Express board.
|
||||
// Connects to an WS2812 RGB LED strip with 10 LEDS.
|
||||
//
|
||||
// Replace machine.NEOPIXELS in the code below to match the pin
|
||||
// that you are using, if you have a different board.
|
||||
// See either the others.go or digispark.go files in this directory
|
||||
// for the neopixels pin assignments.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -13,12 +12,15 @@ import (
|
||||
"tinygo.org/x/drivers/ws2812"
|
||||
)
|
||||
|
||||
var leds [10]color.RGBA
|
||||
|
||||
func main() {
|
||||
neo := machine.NEOPIXELS
|
||||
led := machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
ws := ws2812.New(neo)
|
||||
leds := make([]color.RGBA, 10)
|
||||
rg := false
|
||||
|
||||
for {
|
||||
@@ -33,7 +35,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
ws.WriteColors(leds)
|
||||
ws.WriteColors(leds[:])
|
||||
led.Set(rg)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// +build !digispark
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
// Replace neo in the code below to match the pin
|
||||
// that you are using if different.
|
||||
var neo machine.Pin = machine.NEOPIXELS
|
||||
+1
-1
@@ -49,5 +49,5 @@ func sendCommand(gpsDevice GPSDevice, command []byte) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.New("No ACK to GPS command")
|
||||
return errors.New("no ACK to GPS command")
|
||||
}
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ func (g *GPIO) write4BitMode(data byte) {
|
||||
// Ram address can be changed by writing address in command mode
|
||||
func (g *GPIO) Read(data []byte) (n int, err error) {
|
||||
if len(data) == 0 {
|
||||
return 0, errors.New("Length greater than 0 is required")
|
||||
return 0, errors.New("length greater than 0 is required")
|
||||
}
|
||||
g.rw.High()
|
||||
g.reconfigureGPIOMode(machine.PinInput)
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// Package lis3dh provides a driver for the HD44780 LCD controller.
|
||||
// Package hd44780 provides a driver for the HD44780 LCD controller.
|
||||
//
|
||||
// Datasheet: https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
|
||||
//
|
||||
@@ -66,7 +66,7 @@ func (d *Device) Configure(cfg Config) error {
|
||||
d.width = uint8(cfg.Width)
|
||||
d.height = uint8(cfg.Height)
|
||||
if d.width == 0 || d.height == 0 {
|
||||
return errors.New("Width and height must be set")
|
||||
return errors.New("width and height must be set")
|
||||
}
|
||||
memoryMap := uint8(ONE_LINE)
|
||||
if d.height > 1 {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
TinyGo driver for TFT displays using ILI9341 driver chips.
|
||||
|
||||
These displays support 8-bit parallel, 16-bit parallel, or SPI interfaces.
|
||||
|
||||
Examples of such displays include:
|
||||
|
||||
* [Adafruit PyPortal
|
||||
](https://www.adafruit.com/product/4116)
|
||||
* [Adafruit 2.8" Touch Shield V2 (SPI)](http://www.adafruit.com/products/1651)
|
||||
* [Adafruit 2.4" TFT LCD with Touchscreen Breakout w/MicroSD Socket](https://www.adafruit.com/product/2478)
|
||||
* [2.8" TFT LCD with Touchscreen Breakout Board w/MicroSD Socket](https://www.adafruit.com/product/1770)
|
||||
* [2.2" 18-bit color TFT LCD display with microSD card breakout](https://www.adafruit.com/product/1770)
|
||||
* [TFT FeatherWing - 2.4" 320x240 Touchscreen For All Feathers](https://www.adafruit.com/product/3315)
|
||||
|
||||
Currently this driver only supports an 8-bit parallel interface using ATSAMD51
|
||||
(this is the default configuration on PyPortal). It should be relatively
|
||||
straightforward to implement a more generic SPI-based interface as well.
|
||||
Please see `parallel_atsamd51.go` for an example of what needs to be
|
||||
implemented if you are interested in contributing.
|
||||
@@ -0,0 +1,290 @@
|
||||
package ili9341
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
const _debug = false
|
||||
|
||||
type Config struct {
|
||||
Width int16
|
||||
Height int16
|
||||
Rotation Rotation
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
width int16
|
||||
height int16
|
||||
rotation Rotation
|
||||
driver driver
|
||||
|
||||
dc machine.Pin
|
||||
cs machine.Pin
|
||||
rst machine.Pin
|
||||
rd machine.Pin
|
||||
}
|
||||
|
||||
func (d *Device) Configure(config Config) {
|
||||
|
||||
if config.Width == 0 {
|
||||
config.Width = TFTWIDTH
|
||||
}
|
||||
if config.Height == 0 {
|
||||
config.Height = TFTHEIGHT
|
||||
}
|
||||
d.width = config.Width
|
||||
d.height = config.Height
|
||||
|
||||
output := machine.PinConfig{machine.PinOutput}
|
||||
|
||||
// configure chip select if there is one
|
||||
if d.cs != machine.NoPin {
|
||||
d.cs.Configure(output)
|
||||
d.cs.High() // deselect
|
||||
}
|
||||
|
||||
d.dc.Configure(output)
|
||||
d.dc.High() // data mode
|
||||
|
||||
// driver-specific configuration
|
||||
d.driver.configure(&config)
|
||||
|
||||
if d.rd != machine.NoPin {
|
||||
d.rd.Configure(output)
|
||||
d.rd.High()
|
||||
}
|
||||
|
||||
// reset the display
|
||||
if d.rst != machine.NoPin {
|
||||
// configure hardware reset if there is one
|
||||
d.rst.Configure(output)
|
||||
d.rst.High()
|
||||
delay(100)
|
||||
d.rst.Low()
|
||||
delay(100)
|
||||
d.rst.High()
|
||||
delay(200)
|
||||
} else {
|
||||
// if no hardware reset, send software reset
|
||||
d.sendCommand(SWRESET, nil)
|
||||
delay(150)
|
||||
}
|
||||
|
||||
initCmd := []byte{
|
||||
0xEF, 3, 0x03, 0x80, 0x02,
|
||||
0xCF, 3, 0x00, 0xC1, 0x30,
|
||||
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
||||
0xE8, 3, 0x85, 0x00, 0x78,
|
||||
0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
|
||||
0xF7, 1, 0x20,
|
||||
0xEA, 2, 0x00, 0x00,
|
||||
PWCTR1, 1, 0x23, // Power control VRH[5:0]
|
||||
PWCTR2, 1, 0x10, // Power control SAP[2:0];BT[3:0]
|
||||
VMCTR1, 2, 0x3e, 0x28, // VCM control
|
||||
VMCTR2, 1, 0x86, // VCM control2
|
||||
MADCTL, 1, 0x48, // Memory Access Control
|
||||
VSCRSADD, 1, 0x00, // Vertical scroll zero
|
||||
PIXFMT, 1, 0x55,
|
||||
FRMCTR1, 2, 0x00, 0x18,
|
||||
DFUNCTR, 3, 0x08, 0x82, 0x27, // Display Function Control
|
||||
0xF2, 1, 0x00, // 3Gamma Function Disable
|
||||
GAMMASET, 1, 0x01, // Gamma curve selected
|
||||
GMCTRP1, 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
|
||||
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00,
|
||||
GMCTRN1, 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
|
||||
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F,
|
||||
SLPOUT, 0x80, // Exit Sleep
|
||||
DISPON, 0x80, // Display on
|
||||
0x00, // End of list
|
||||
}
|
||||
for i, c := 0, len(initCmd); i < c; {
|
||||
cmd := initCmd[i]
|
||||
if cmd == 0x00 {
|
||||
break
|
||||
}
|
||||
x := initCmd[i+1]
|
||||
numArgs := int(x & 0x7F)
|
||||
d.sendCommand(cmd, initCmd[i+2:i+2+numArgs])
|
||||
if x&0x80 > 0 {
|
||||
delay(150)
|
||||
}
|
||||
i += numArgs + 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (x, y int16) {
|
||||
if d.rotation == 1 || d.rotation == 3 {
|
||||
return d.height, d.width
|
||||
}
|
||||
return d.width, d.height
|
||||
}
|
||||
|
||||
// SetPixel modifies the internal buffer.
|
||||
func (d *Device) SetPixel(x, y int16, c color.RGBA) {
|
||||
d.setWindow(x, y, 1, 1)
|
||||
c565 := RGBATo565(c)
|
||||
d.startWrite()
|
||||
d.driver.write16(c565)
|
||||
d.endWrite()
|
||||
}
|
||||
|
||||
// Display sends the buffer (if any) to the screen.
|
||||
func (d *Device) Display() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 ||
|
||||
x >= k || (x+w) > k || y >= i || (y+h) > i {
|
||||
return errors.New("rectangle coordinates outside display area")
|
||||
}
|
||||
d.setWindow(x, y, w, h)
|
||||
d.startWrite()
|
||||
d.driver.write16sl(data)
|
||||
d.endWrite()
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a given coordinates with a color
|
||||
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||
k, i := d.Size()
|
||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||
x >= k || (x+width) > k || y >= i || (y+height) > i {
|
||||
return errors.New("rectangle coordinates outside display area")
|
||||
}
|
||||
d.setWindow(x, y, width, height)
|
||||
c565 := RGBATo565(c)
|
||||
d.startWrite()
|
||||
d.driver.write16n(c565, int(width)*int(height))
|
||||
d.endWrite()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DrawRectangle fills a rectangle at a 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
|
||||
}
|
||||
if err := d.DrawFastHLine(x, x+w-1, y+h-1, c); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.DrawFastVLine(x, y, y+h-1, c); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.DrawFastVLine(x+w-1, y, y+h-1, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DrawFastVLine draws a vertical line faster than using SetPixel
|
||||
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) error {
|
||||
if y0 > y1 {
|
||||
y0, y1 = y1, y0
|
||||
}
|
||||
return d.FillRectangle(x, y0, 1, y1-y0+1, c)
|
||||
}
|
||||
|
||||
// DrawFastHLine draws a horizontal line faster than using SetPixel
|
||||
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) error {
|
||||
if x0 > x1 {
|
||||
x0, x1 = x1, x0
|
||||
}
|
||||
return d.FillRectangle(x0, y, x1-x0+1, 1, c)
|
||||
}
|
||||
|
||||
// FillScreen fills the screen with a given color
|
||||
func (d *Device) FillScreen(c color.RGBA) {
|
||||
if d.rotation == Rotation0 || d.rotation == Rotation180 {
|
||||
d.FillRectangle(0, 0, d.width, d.height, c)
|
||||
} else {
|
||||
d.FillRectangle(0, 0, d.height, d.width, c)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) GetRotation() Rotation {
|
||||
return d.rotation
|
||||
}
|
||||
|
||||
// SetRotation changes the rotation of the device (clock-wise)
|
||||
func (d *Device) SetRotation(rotation Rotation) {
|
||||
madctl := uint8(0)
|
||||
switch rotation % 4 {
|
||||
case 0:
|
||||
madctl = MADCTL_MX | MADCTL_BGR
|
||||
case 1:
|
||||
madctl = MADCTL_MV | MADCTL_BGR
|
||||
case 2:
|
||||
madctl = MADCTL_MY | MADCTL_BGR
|
||||
case 3:
|
||||
madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR
|
||||
}
|
||||
d.sendCommand(MADCTL, []uint8{madctl})
|
||||
d.rotation = rotation
|
||||
}
|
||||
|
||||
// setWindow prepares the screen to be modified at a given rectangle
|
||||
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),
|
||||
})
|
||||
d.sendCommand(RAMWR, nil)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func (d *Device) startWrite() {
|
||||
if d.cs != machine.NoPin {
|
||||
d.cs.Low()
|
||||
}
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func (d *Device) endWrite() {
|
||||
if d.cs != machine.NoPin {
|
||||
d.cs.High()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) sendCommand(cmd byte, data []byte) {
|
||||
d.startWrite()
|
||||
d.dc.Low()
|
||||
d.driver.write8(cmd)
|
||||
d.dc.High()
|
||||
for _, b := range data {
|
||||
d.driver.write8(b)
|
||||
}
|
||||
d.endWrite()
|
||||
}
|
||||
|
||||
type driver interface {
|
||||
configure(config *Config)
|
||||
write8(b byte)
|
||||
write16(data uint16)
|
||||
write16n(data uint16, n int)
|
||||
write16sl(data []uint16)
|
||||
}
|
||||
|
||||
func delay(m int) {
|
||||
t := time.Now().UnixNano() + int64(time.Duration(m*1000)*time.Microsecond)
|
||||
for time.Now().UnixNano() < t {
|
||||
}
|
||||
}
|
||||
|
||||
// RGBATo565 converts a color.RGBA to uint16 used in the display
|
||||
func RGBATo565(c color.RGBA) uint16 {
|
||||
r, g, b, _ := c.RGBA()
|
||||
return uint16((r & 0xF800) +
|
||||
((g & 0xFC00) >> 5) +
|
||||
((b & 0xF800) >> 11))
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// +build atsamd51
|
||||
|
||||
package ili9341
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
type parallelDriver struct {
|
||||
d0 machine.Pin
|
||||
wr machine.Pin
|
||||
|
||||
setPort *uint32
|
||||
setMask uint32
|
||||
|
||||
clrPort *uint32
|
||||
clrMask uint32
|
||||
|
||||
wrPortSet *uint32
|
||||
wrMaskSet uint32
|
||||
|
||||
wrPortClr *uint32
|
||||
wrMaskClr uint32
|
||||
}
|
||||
|
||||
func NewParallel(d0, wr, dc, cs, rst, rd machine.Pin) *Device {
|
||||
return &Device{
|
||||
dc: dc,
|
||||
cs: cs,
|
||||
rd: rd,
|
||||
rst: rst,
|
||||
driver: ¶llelDriver{
|
||||
d0: d0,
|
||||
wr: wr,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (pd *parallelDriver) configure(config *Config) {
|
||||
output := machine.PinConfig{machine.PinOutput}
|
||||
for pin := pd.d0; pin < pd.d0+8; pin++ {
|
||||
pin.Configure(output)
|
||||
pin.Low()
|
||||
}
|
||||
pd.wr.Configure(output)
|
||||
pd.wr.High()
|
||||
|
||||
pd.setPort, _ = pd.d0.PortMaskSet()
|
||||
pd.setMask = uint32(pd.d0) & 0x1f
|
||||
|
||||
pd.clrPort, _ = (pd.d0).PortMaskClear()
|
||||
pd.clrMask = 0xFF << uint32(pd.d0)
|
||||
|
||||
pd.wrPortSet, pd.wrMaskSet = pd.wr.PortMaskSet()
|
||||
pd.wrPortClr, pd.wrMaskClr = pd.wr.PortMaskClear()
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func (pd *parallelDriver) write8(b byte) {
|
||||
volatile.StoreUint32(pd.clrPort, pd.clrMask)
|
||||
volatile.StoreUint32(pd.setPort, uint32(b)<<pd.setMask)
|
||||
volatile.StoreUint32(pd.wrPortClr, pd.wrMaskClr)
|
||||
volatile.StoreUint32(pd.wrPortSet, pd.wrMaskSet)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func (pd *parallelDriver) write16(data uint16) {
|
||||
pd.write8(byte(data >> 8))
|
||||
pd.write8(byte(data))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func (pd *parallelDriver) write16n(data uint16, n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
pd.write8(byte(data >> 8))
|
||||
pd.write8(byte(data))
|
||||
}
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func (pd *parallelDriver) write16sl(data []uint16) {
|
||||
for i, c := 0, len(data); i < c; i++ {
|
||||
pd.write8(byte(data[i] >> 8))
|
||||
pd.write8(byte(data[i]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package ili9341
|
||||
|
||||
type Rotation uint8
|
||||
|
||||
const (
|
||||
|
||||
// register constants based on source:
|
||||
// https://github.com/adafruit/Adafruit_ILI9341/blob/master/Adafruit_ILI9341.h
|
||||
|
||||
TFTWIDTH = 240 ///< ILI9341 max TFT width
|
||||
TFTHEIGHT = 320 ///< ILI9341 max TFT height
|
||||
|
||||
NOP = 0x00 ///< No-op register
|
||||
SWRESET = 0x01 ///< Software reset register
|
||||
RDDID = 0x04 ///< Read display identification information
|
||||
RDDST = 0x09 ///< Read Display Status
|
||||
|
||||
SLPIN = 0x10 ///< Enter Sleep Mode
|
||||
SLPOUT = 0x11 ///< Sleep Out
|
||||
PTLON = 0x12 ///< Partial Mode ON
|
||||
NORON = 0x13 ///< Normal Display Mode ON
|
||||
|
||||
RDMODE = 0x0A ///< Read Display Power Mode
|
||||
RDMADCTL = 0x0B ///< Read Display MADCTL
|
||||
RDPIXFMT = 0x0C ///< Read Display Pixel Format
|
||||
RDIMGFMT = 0x0D ///< Read Display Image Format
|
||||
RDSELFDIAG = 0x0F ///< Read Display Self-Diagnostic Result
|
||||
|
||||
INVOFF = 0x20 ///< Display Inversion OFF
|
||||
INVON = 0x21 ///< Display Inversion ON
|
||||
GAMMASET = 0x26 ///< Gamma Set
|
||||
DISPOFF = 0x28 ///< Display OFF
|
||||
DISPON = 0x29 ///< Display ON
|
||||
|
||||
CASET = 0x2A ///< Column Address Set
|
||||
PASET = 0x2B ///< Page Address Set
|
||||
RAMWR = 0x2C ///< Memory Write
|
||||
RAMRD = 0x2E ///< Memory Read
|
||||
|
||||
PTLAR = 0x30 ///< Partial Area
|
||||
VSCRDEF = 0x33 ///< Vertical Scrolling Definition
|
||||
MADCTL = 0x36 ///< Memory Access Control
|
||||
VSCRSADD = 0x37 ///< Vertical Scrolling Start Address
|
||||
PIXFMT = 0x3A ///< COLMOD: Pixel Format Set
|
||||
|
||||
FRMCTR1 = 0xB1 ///< Frame Rate Control (In Normal Mode/Full Colors)
|
||||
FRMCTR2 = 0xB2 ///< Frame Rate Control (In Idle Mode/8 colors)
|
||||
FRMCTR3 = 0xB3 ///< Frame Rate control (In Partial Mode/Full Colors)
|
||||
INVCTR = 0xB4 ///< Display Inversion Control
|
||||
DFUNCTR = 0xB6 ///< Display Function Control
|
||||
|
||||
PWCTR1 = 0xC0 ///< Power Control 1
|
||||
PWCTR2 = 0xC1 ///< Power Control 2
|
||||
PWCTR3 = 0xC2 ///< Power Control 3
|
||||
PWCTR4 = 0xC3 ///< Power Control 4
|
||||
PWCTR5 = 0xC4 ///< Power Control 5
|
||||
VMCTR1 = 0xC5 ///< VCOM Control 1
|
||||
VMCTR2 = 0xC7 ///< VCOM Control 2
|
||||
|
||||
RDID1 = 0xDA ///< Read ID 1
|
||||
RDID2 = 0xDB ///< Read ID 2
|
||||
RDID3 = 0xDC ///< Read ID 3
|
||||
RDID4 = 0xDD ///< Read ID 4
|
||||
|
||||
GMCTRP1 = 0xE0 ///< Positive Gamma Correction
|
||||
GMCTRN1 = 0xE1 ///< Negative Gamma Correction
|
||||
//PWCTR6 0xFC
|
||||
|
||||
MADCTL_MY = 0x80 ///< Bottom to top
|
||||
MADCTL_MX = 0x40 ///< Right to left
|
||||
MADCTL_MV = 0x20 ///< Reverse Mode
|
||||
MADCTL_ML = 0x10 ///< LCD refresh Bottom to top
|
||||
MADCTL_RGB = 0x00 ///< Red-Green-Blue pixel order
|
||||
MADCTL_BGR = 0x08 ///< Blue-Green-Red pixel order
|
||||
MADCTL_MH = 0x04 ///< LCD refresh right to left
|
||||
|
||||
)
|
||||
|
||||
const (
|
||||
Rotation0 Rotation = 0
|
||||
Rotation90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||
Rotation180 Rotation = 2
|
||||
Rotation270 Rotation = 3
|
||||
)
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// Package l293x provides a driver to the L293/L293D H-bridge chip
|
||||
// typically used to control DC motors.
|
||||
//
|
||||
// Datasheet: https://www.ti.com/lit/ds/symlink/l293d.pdf
|
||||
//
|
||||
package l293x // import "tinygo.org/x/drivers/l293x"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
// Device is a motor without speed control.
|
||||
// a1 and a2 are the directional pins.
|
||||
// en is the pin turns the motor on/off.
|
||||
type Device struct {
|
||||
a1, a2 machine.Pin
|
||||
en machine.Pin
|
||||
}
|
||||
|
||||
// New returns a new Motor driver for GPIO-only operation.
|
||||
func New(direction1, direction2, enablePin machine.Pin) Device {
|
||||
return Device{
|
||||
a1: direction1,
|
||||
a2: direction2,
|
||||
en: enablePin,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures the Device.
|
||||
func (d *Device) Configure() {
|
||||
d.a1.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.a2.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.en.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
d.Stop()
|
||||
}
|
||||
|
||||
// Forward turns motor on in forward direction.
|
||||
func (d *Device) Forward() {
|
||||
d.a1.High()
|
||||
d.a2.Low()
|
||||
d.en.High()
|
||||
}
|
||||
|
||||
// Backward turns motor on in backward direction.
|
||||
func (d *Device) Backward() {
|
||||
d.a1.Low()
|
||||
d.a2.High()
|
||||
d.en.High()
|
||||
}
|
||||
|
||||
// Stop turns motor off.
|
||||
func (d *Device) Stop() {
|
||||
d.a1.Low()
|
||||
d.a2.Low()
|
||||
d.en.Low()
|
||||
}
|
||||
|
||||
// PWMDevice is a motor with speed control.
|
||||
// a1 and a2 are the directional GPIO pins.
|
||||
// en is the PWM pin that controls the motor speed.
|
||||
type PWMDevice struct {
|
||||
a1, a2 machine.Pin
|
||||
en machine.PWM
|
||||
}
|
||||
|
||||
// NewWithSpeed returns a new PWMMotor driver that uses a PWM pin to control speed.
|
||||
func NewWithSpeed(direction1, direction2 machine.Pin, speedPin machine.PWM) PWMDevice {
|
||||
return PWMDevice{
|
||||
a1: direction1,
|
||||
a2: direction2,
|
||||
en: speedPin,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures the PWMDevice.
|
||||
func (d *PWMDevice) Configure() {
|
||||
d.a1.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.a2.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.en.Configure()
|
||||
|
||||
d.Stop()
|
||||
}
|
||||
|
||||
// Forward turns motor on in forward direction at specific speed.
|
||||
func (d *PWMDevice) Forward(speed uint16) {
|
||||
d.a1.High()
|
||||
d.a2.Low()
|
||||
d.en.Set(speed)
|
||||
}
|
||||
|
||||
// Backward turns motor on in backward direction at specific speed.
|
||||
func (d *PWMDevice) Backward(speed uint16) {
|
||||
d.a1.Low()
|
||||
d.a2.High()
|
||||
d.en.Set(speed)
|
||||
}
|
||||
|
||||
// Stop turns motor off.
|
||||
func (d *PWMDevice) Stop() {
|
||||
d.a1.Low()
|
||||
d.a2.Low()
|
||||
d.en.Set(0)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Package l9110x provides a driver to the L9110/L9110S H-bridge chip
|
||||
// typically used to control DC motors.
|
||||
//
|
||||
// Datasheet: https://www.elecrow.com/download/datasheet-l9110.pdf
|
||||
//
|
||||
package l9110x // import "tinygo.org/x/drivers/l9110x"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
// Device is a motor without speed control.
|
||||
// ia and ib are the directional pins.
|
||||
type Device struct {
|
||||
ia, ib machine.Pin
|
||||
}
|
||||
|
||||
// New returns a new Motor driver for GPIO-only operation.
|
||||
func New(direction1, direction2 machine.Pin) Device {
|
||||
return Device{
|
||||
ia: direction1,
|
||||
ib: direction2,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures the Device.
|
||||
func (d *Device) Configure() {
|
||||
d.ia.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.ib.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
d.Stop()
|
||||
}
|
||||
|
||||
// Forward turns motor on in forward direction.
|
||||
func (d *Device) Forward() {
|
||||
d.ia.High()
|
||||
d.ib.Low()
|
||||
}
|
||||
|
||||
// Backward turns motor on in backward direction.
|
||||
func (d *Device) Backward() {
|
||||
d.ia.Low()
|
||||
d.ib.High()
|
||||
}
|
||||
|
||||
// Stop turns motor off.
|
||||
func (d *Device) Stop() {
|
||||
d.ia.Low()
|
||||
d.ib.Low()
|
||||
}
|
||||
|
||||
// PWMDevice is a motor with speed control.
|
||||
// ia and ib are the directional/speed PWM pins.
|
||||
type PWMDevice struct {
|
||||
ia, ib machine.PWM
|
||||
}
|
||||
|
||||
// NewWithSpeed returns a new PWMMotor driver that uses 2 PWM pins to control both direction and speed.
|
||||
func NewWithSpeed(direction1, direction2 machine.PWM) PWMDevice {
|
||||
return PWMDevice{
|
||||
ia: direction1,
|
||||
ib: direction2,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures the PWMDevice.
|
||||
func (d *PWMDevice) Configure() {
|
||||
d.ia.Configure()
|
||||
d.ib.Configure()
|
||||
|
||||
d.Stop()
|
||||
}
|
||||
|
||||
// Forward turns motor on in forward direction at specific speed.
|
||||
func (d *PWMDevice) Forward(speed uint16) {
|
||||
d.ia.Set(speed)
|
||||
d.ib.Set(0)
|
||||
}
|
||||
|
||||
// Backward turns motor on in backward direction at specific speed.
|
||||
func (d *PWMDevice) Backward(speed uint16) {
|
||||
d.ia.Set(0)
|
||||
d.ib.Set(speed)
|
||||
}
|
||||
|
||||
// Stop turns motor off.
|
||||
func (d *PWMDevice) Stop() {
|
||||
d.ia.Set(0)
|
||||
d.ib.Set(0)
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// Package lsm6ds3 implements a driver for the LSM6DS3 a 6 axis Inertial
|
||||
// Measurement Unit (IMU)
|
||||
//
|
||||
// Datasheet: https://www.st.com/resource/en/datasheet/lsm6ds3.pdf
|
||||
//
|
||||
package lsm6ds3 // import "tinygo.org/x/drivers/lsm6ds3"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
type AccelRange uint8
|
||||
type AccelSampleRate uint8
|
||||
type AccelBandwidth uint8
|
||||
|
||||
type GyroRange uint8
|
||||
type GyroSampleRate uint8
|
||||
|
||||
// Device wraps an I2C connection to a LSM6DS3 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
Address uint16
|
||||
accelRange AccelRange
|
||||
accelSampleRate AccelSampleRate
|
||||
accelBandWidth AccelBandwidth
|
||||
gyroRange GyroRange
|
||||
gyroSampleRate GyroSampleRate
|
||||
dataBufferSix []uint8
|
||||
dataBufferTwo []uint8
|
||||
}
|
||||
|
||||
// Configuration for LSM6DS3 device.
|
||||
type Configuration struct {
|
||||
AccelRange AccelRange
|
||||
AccelSampleRate AccelSampleRate
|
||||
AccelBandWidth AccelBandwidth
|
||||
GyroRange GyroRange
|
||||
GyroSampleRate GyroSampleRate
|
||||
IsPedometer bool
|
||||
ResetStepCounter bool
|
||||
}
|
||||
|
||||
// New creates a new LSM6DS3 connection. The I2C bus must already be
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2C) Device {
|
||||
return Device{bus: bus, Address: Address}
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication.
|
||||
func (d *Device) Configure(cfg Configuration) {
|
||||
if cfg.AccelRange != 0 {
|
||||
d.accelRange = cfg.AccelRange
|
||||
} else {
|
||||
d.accelRange = ACCEL_2G
|
||||
}
|
||||
|
||||
if cfg.AccelSampleRate != 0 {
|
||||
d.accelSampleRate = cfg.AccelSampleRate
|
||||
} else {
|
||||
d.accelSampleRate = ACCEL_SR_104
|
||||
}
|
||||
|
||||
if cfg.AccelBandWidth != 0 {
|
||||
d.accelBandWidth = cfg.AccelBandWidth
|
||||
} else {
|
||||
d.accelBandWidth = ACCEL_BW_100
|
||||
}
|
||||
|
||||
if cfg.GyroRange != 0 {
|
||||
d.gyroRange = cfg.GyroRange
|
||||
} else {
|
||||
d.gyroRange = GYRO_2000DPS
|
||||
}
|
||||
|
||||
if cfg.GyroSampleRate != 0 {
|
||||
d.gyroSampleRate = cfg.GyroSampleRate
|
||||
} else {
|
||||
d.gyroSampleRate = GYRO_SR_104
|
||||
}
|
||||
|
||||
d.dataBufferSix = make([]uint8, 6)
|
||||
d.dataBufferTwo = make([]uint8, 2)
|
||||
|
||||
if cfg.IsPedometer { // CONFIGURE AS PEDOMETER
|
||||
// Configure accelerometer: 2G + 26Hz
|
||||
d.bus.WriteRegister(uint8(d.Address), CTRL1_XL, []byte{uint8(ACCEL_2G) | uint8(ACCEL_SR_26)})
|
||||
|
||||
// Configure Zen_G, Yen_G, Xen_G, reset steps
|
||||
if cfg.ResetStepCounter {
|
||||
d.bus.WriteRegister(uint8(d.Address), CTRL10_C, []byte{0x3E})
|
||||
} else {
|
||||
d.bus.WriteRegister(uint8(d.Address), CTRL10_C, []byte{0x3C})
|
||||
}
|
||||
|
||||
// Enable pedometer
|
||||
d.bus.WriteRegister(uint8(d.Address), TAP_CFG, []byte{0x40})
|
||||
} else { // NORMAL USE
|
||||
// Configure accelerometer
|
||||
data := make([]uint8, 1)
|
||||
data[0] = uint8(d.accelRange) | uint8(d.accelSampleRate) | uint8(d.accelBandWidth)
|
||||
d.bus.WriteRegister(uint8(d.Address), CTRL1_XL, data)
|
||||
|
||||
// Set ODR bit
|
||||
d.bus.ReadRegister(uint8(d.Address), CTRL4_C, data)
|
||||
data[0] = data[0] &^ BW_SCAL_ODR_ENABLED
|
||||
data[0] |= BW_SCAL_ODR_ENABLED
|
||||
d.bus.WriteRegister(uint8(d.Address), CTRL4_C, data)
|
||||
|
||||
// Configure gyroscope
|
||||
data[0] = uint8(d.gyroRange) | uint8(d.gyroSampleRate)
|
||||
d.bus.WriteRegister(uint8(d.Address), CTRL2_G, data)
|
||||
}
|
||||
}
|
||||
|
||||
// Connected returns whether a LSM6DS3 has been found.
|
||||
// It does a "who am I" request and checks the response.
|
||||
func (d *Device) Connected() bool {
|
||||
data := []byte{0}
|
||||
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
|
||||
return data[0] == 0x69
|
||||
}
|
||||
|
||||
// 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 *Device) ReadAcceleration() (x int32, y int32, z int32) {
|
||||
d.bus.ReadRegister(uint8(d.Address), OUTX_L_XL, d.dataBufferSix)
|
||||
// k comes from "Table 3. Mechanical characteristics" 3 of the datasheet * 1000
|
||||
k := int32(61) // 2G
|
||||
if d.accelRange == ACCEL_4G {
|
||||
k = 122
|
||||
} else if d.accelRange == ACCEL_8G {
|
||||
k = 244
|
||||
} else if d.accelRange == ACCEL_16G {
|
||||
k = 488
|
||||
}
|
||||
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * k
|
||||
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * k
|
||||
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * k
|
||||
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 *Device) ReadRotation() (x int32, y int32, z int32) {
|
||||
d.bus.ReadRegister(uint8(d.Address), OUTX_L_G, d.dataBufferSix)
|
||||
// k comes from "Table 3. Mechanical characteristics" 3 of the datasheet * 1000
|
||||
k := int32(4375) // 125DPS
|
||||
if d.gyroRange == GYRO_250DPS {
|
||||
k = 8750
|
||||
} else if d.gyroRange == GYRO_500DPS {
|
||||
k = 17500
|
||||
} else if d.gyroRange == GYRO_1000DPS {
|
||||
k = 35000
|
||||
} else if d.gyroRange == GYRO_2000DPS {
|
||||
k = 70000
|
||||
}
|
||||
x = int32(int16((uint16(d.dataBufferSix[1])<<8)|uint16(d.dataBufferSix[0]))) * k
|
||||
y = int32(int16((uint16(d.dataBufferSix[3])<<8)|uint16(d.dataBufferSix[2]))) * k
|
||||
z = int32(int16((uint16(d.dataBufferSix[5])<<8)|uint16(d.dataBufferSix[4]))) * k
|
||||
return
|
||||
}
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (ºC/1000)
|
||||
func (d *Device) ReadTemperature() (int32, error) {
|
||||
d.bus.ReadRegister(uint8(d.Address), OUT_TEMP_L, d.dataBufferTwo)
|
||||
|
||||
// From "Table 5. Temperature sensor characteristics"
|
||||
// temp = value/16 + 25
|
||||
t := 25000 + (int32(int16((int16(d.dataBufferTwo[1])<<8)|int16(d.dataBufferTwo[0])))*125)/2
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ReadSteps returns the steps of the pedometer
|
||||
func (d *Device) ReadSteps() int32 {
|
||||
d.bus.ReadRegister(uint8(d.Address), STEP_COUNTER_L, d.dataBufferTwo)
|
||||
return int32(int16((uint16(d.dataBufferTwo[1]) << 8) | uint16(d.dataBufferTwo[0])))
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package lsm6ds3
|
||||
|
||||
// Constants/addresses used for I2C.
|
||||
|
||||
// The I2C address which this device listens to.
|
||||
const Address = 0x6A
|
||||
|
||||
const (
|
||||
WHO_AM_I = 0x0F
|
||||
STATUS = 0x1E
|
||||
CTRL1_XL = 0x10
|
||||
CTRL2_G = 0x11
|
||||
CTRL3_C = 0x12
|
||||
CTRL4_C = 0x13
|
||||
CTRL5_C = 0x14
|
||||
CTRL6_C = 0x15
|
||||
CTRL7_G = 0x16
|
||||
CTRL8_XL = 0x17
|
||||
CTRL9_XL = 0x18
|
||||
CTRL10_C = 0x19
|
||||
OUTX_L_G = 0x22
|
||||
OUTX_H_G = 0x23
|
||||
OUTY_L_G = 0x24
|
||||
OUTY_H_G = 0x25
|
||||
OUTZ_L_G = 0x26
|
||||
OUTZ_H_G = 0x27
|
||||
OUTX_L_XL = 0x28
|
||||
OUTX_H_XL = 0x29
|
||||
OUTY_L_XL = 0x2A
|
||||
OUTY_H_XL = 0x2B
|
||||
OUTZ_L_XL = 0x2C
|
||||
OUTZ_H_XL = 0x2D
|
||||
OUT_TEMP_L = 0x20
|
||||
OUT_TEMP_H = 0x21
|
||||
BW_SCAL_ODR_DISABLED = 0x00
|
||||
BW_SCAL_ODR_ENABLED = 0x80
|
||||
STEP_TIMESTAMP_L = 0x49
|
||||
STEP_TIMESTAMP_H = 0x4A
|
||||
STEP_COUNTER_L = 0x4B
|
||||
STEP_COUNTER_H = 0x4C
|
||||
STEP_COUNT_DELTA = 0x15
|
||||
TAP_CFG = 0x58
|
||||
INT1_CTRL = 0x0D
|
||||
|
||||
ACCEL_2G AccelRange = 0x00
|
||||
ACCEL_4G AccelRange = 0x08
|
||||
ACCEL_8G AccelRange = 0x0C
|
||||
ACCEL_16G AccelRange = 0x04
|
||||
|
||||
ACCEL_SR_OFF AccelSampleRate = 0x00
|
||||
ACCEL_SR_13 AccelSampleRate = 0x10
|
||||
ACCEL_SR_26 AccelSampleRate = 0x20
|
||||
ACCEL_SR_52 AccelSampleRate = 0x30
|
||||
ACCEL_SR_104 AccelSampleRate = 0x40
|
||||
ACCEL_SR_208 AccelSampleRate = 0x50
|
||||
ACCEL_SR_416 AccelSampleRate = 0x60
|
||||
ACCEL_SR_833 AccelSampleRate = 0x70
|
||||
ACCEL_SR_1666 AccelSampleRate = 0x80
|
||||
ACCEL_SR_3332 AccelSampleRate = 0x90
|
||||
ACCEL_SR_6664 AccelSampleRate = 0xA0
|
||||
ACCEL_SR_13330 AccelSampleRate = 0xB0
|
||||
|
||||
ACCEL_BW_50 AccelBandwidth = 0x03
|
||||
ACCEL_BW_100 AccelBandwidth = 0x02
|
||||
ACCEL_BW_200 AccelBandwidth = 0x01
|
||||
ACCEL_BW_400 AccelBandwidth = 0x00
|
||||
|
||||
//GYRO_125DPS GyroRange = 0x01
|
||||
GYRO_250DPS GyroRange = 0x00
|
||||
GYRO_500DPS GyroRange = 0x04
|
||||
GYRO_1000DPS GyroRange = 0x08
|
||||
GYRO_2000DPS GyroRange = 0x0C
|
||||
|
||||
GYRO_SR_OFF GyroSampleRate = 0x00
|
||||
GYRO_SR_13 GyroSampleRate = 0x10
|
||||
GYRO_SR_26 GyroSampleRate = 0x20
|
||||
GYRO_SR_52 GyroSampleRate = 0x30
|
||||
GYRO_SR_104 GyroSampleRate = 0x40
|
||||
GYRO_SR_208 GyroSampleRate = 0x50
|
||||
GYRO_SR_416 GyroSampleRate = 0x60
|
||||
GYRO_SR_833 GyroSampleRate = 0x70
|
||||
GYRO_SR_1666 GyroSampleRate = 0x80
|
||||
)
|
||||
@@ -0,0 +1,92 @@
|
||||
// Package mcp3008 implements a driver for the MCP3008 Analog to Digital Converter.
|
||||
//
|
||||
// Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf
|
||||
//
|
||||
package mcp3008 // import "tinygo.org/x/drivers/mcp3008"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
)
|
||||
|
||||
// Device wraps MCP3008 SPI ADC.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
cs machine.Pin
|
||||
tx []byte
|
||||
rx []byte
|
||||
CH0 ADCPin
|
||||
CH1 ADCPin
|
||||
CH2 ADCPin
|
||||
CH3 ADCPin
|
||||
CH4 ADCPin
|
||||
CH5 ADCPin
|
||||
CH6 ADCPin
|
||||
CH7 ADCPin
|
||||
}
|
||||
|
||||
// ADCPin is the implementation of the ADConverter interface.
|
||||
type ADCPin struct {
|
||||
machine.Pin
|
||||
d *Device
|
||||
}
|
||||
|
||||
// New returns a new MCP3008 driver. Pass in a fully configured SPI bus.
|
||||
func New(b machine.SPI, csPin machine.Pin) *Device {
|
||||
d := &Device{bus: b,
|
||||
cs: csPin,
|
||||
tx: make([]byte, 3),
|
||||
rx: make([]byte, 3),
|
||||
}
|
||||
|
||||
// setup all channels
|
||||
d.CH0 = d.GetADC(0)
|
||||
d.CH1 = d.GetADC(1)
|
||||
d.CH2 = d.GetADC(2)
|
||||
d.CH3 = d.GetADC(3)
|
||||
d.CH4 = d.GetADC(4)
|
||||
d.CH5 = d.GetADC(5)
|
||||
d.CH6 = d.GetADC(6)
|
||||
d.CH7 = d.GetADC(7)
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication
|
||||
func (d *Device) Configure() {
|
||||
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
}
|
||||
|
||||
// Read analog data from channel
|
||||
func (d *Device) Read(ch int) (uint16, error) {
|
||||
if ch < 0 || ch > 7 {
|
||||
return 0, errors.New("invalid channel for MCP3008 Read")
|
||||
}
|
||||
|
||||
return d.GetADC(ch).Get(), nil
|
||||
}
|
||||
|
||||
// GetADC returns an ADC for a specific channel.
|
||||
func (d *Device) GetADC(ch int) ADCPin {
|
||||
return ADCPin{machine.Pin(ch), d}
|
||||
}
|
||||
|
||||
// Get the current reading for a specific ADCPin.
|
||||
func (p ADCPin) Get() uint16 {
|
||||
p.d.tx[0] = 0x01
|
||||
p.d.tx[1] = byte(8+p.Pin) << 4
|
||||
p.d.tx[2] = 0x00
|
||||
|
||||
p.d.cs.Low()
|
||||
p.d.bus.Tx(p.d.tx, p.d.rx)
|
||||
|
||||
// scale result to 16bit value like other ADCs
|
||||
result := uint16((p.d.rx[1]&0x3))<<8 + uint16(p.d.rx[2])<<6
|
||||
p.d.cs.High()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Configure here just for interface compatibility.
|
||||
func (p ADCPin) Configure() {
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// Package microphone implements a driver for a PDM microphone.
|
||||
// For example, the Adafruit PDM MEMS breakout board (https://www.adafruit.com/product/3492)
|
||||
//
|
||||
// Datasheet: https://cdn-learn.adafruit.com/assets/assets/000/049/977/original/MP34DT01-M.pdf
|
||||
//
|
||||
package microphone // import "tinygo.org/x/drivers/microphone"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSampleRate = 22000
|
||||
quantizeSteps = 64
|
||||
msForSPLSample = 50
|
||||
defaultSampleCountForSPL = (defaultSampleRate / 1000) * msForSPLSample
|
||||
defaultGain = 9.0
|
||||
defaultRefLevel = 0.00002
|
||||
)
|
||||
|
||||
// Device wraps an I2S connection to a PDM microphone device.
|
||||
type Device struct {
|
||||
bus machine.I2S
|
||||
|
||||
// data buffer used for SPL sound pressure level samples
|
||||
data []int32
|
||||
|
||||
// buf buffer used for sinc filter
|
||||
buf []uint32
|
||||
|
||||
// SampleCountForSPL is number of samples aka size of data buffer to be used
|
||||
// for sound pressure level measurement.
|
||||
// Once Configure() is called, changing this value has no effect.
|
||||
SampleCountForSPL int
|
||||
|
||||
// Gain setting used to calculate sound pressure level
|
||||
Gain float64
|
||||
|
||||
// ReferenceLevel setting used to calculate sound pressure level.
|
||||
ReferenceLevel float64
|
||||
}
|
||||
|
||||
// New creates a new microphone connection. The I2S bus must already be
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus machine.I2S) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
SampleCountForSPL: defaultSampleCountForSPL,
|
||||
Gain: defaultGain,
|
||||
ReferenceLevel: defaultRefLevel,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the microphone.
|
||||
func (d *Device) Configure() {
|
||||
d.data = make([]int32, d.SampleCountForSPL)
|
||||
d.buf = make([]uint32, (quantizeSteps / 16))
|
||||
}
|
||||
|
||||
// Read the raw microphone data.
|
||||
func (d *Device) Read(r []int32) (int, error) {
|
||||
count := len(r)
|
||||
|
||||
// get the next group of samples
|
||||
machine.I2S0.Read(d.buf)
|
||||
|
||||
if len(r) > len(d.buf) {
|
||||
count = len(d.buf)
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
r[i] = int32(d.buf[i])
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ReadWithFilter reads the microphone and filters the buffer using the sinc filter.
|
||||
func (d *Device) ReadWithFilter(r []int32) (int, error) {
|
||||
// read/filter the samples
|
||||
var sum uint16
|
||||
for i := 0; i < len(r); i++ {
|
||||
|
||||
// get the next group of samples
|
||||
machine.I2S0.Read(d.buf)
|
||||
|
||||
// filter
|
||||
sum = applySincFilter(d.buf)
|
||||
|
||||
// adjust to 10 bit value
|
||||
s := int32(sum >> 6)
|
||||
|
||||
// make it close to 0-offset signed
|
||||
s -= 512
|
||||
|
||||
r[i] = s
|
||||
}
|
||||
|
||||
return len(r), nil
|
||||
}
|
||||
|
||||
// GetSoundPressure returns the sound pressure in milli-decibels.
|
||||
func (d *Device) GetSoundPressure() (int32, int32) {
|
||||
// read/filter the samples
|
||||
d.ReadWithFilter(d.data)
|
||||
|
||||
// remove offset
|
||||
var avg int32
|
||||
for i := 0; i < len(d.data); i++ {
|
||||
avg += d.data[i]
|
||||
}
|
||||
avg /= int32(len(d.data))
|
||||
|
||||
for i := 0; i < len(d.data); i++ {
|
||||
d.data[i] -= avg
|
||||
}
|
||||
|
||||
// get max value
|
||||
var maxval int32
|
||||
for i := 0; i < len(d.data); i++ {
|
||||
v := d.data[i]
|
||||
if v < 0 {
|
||||
v = -v
|
||||
}
|
||||
if maxval < v {
|
||||
maxval = v
|
||||
}
|
||||
}
|
||||
|
||||
// calculate SPL
|
||||
spl := float64(maxval) / 1023.0 * d.Gain
|
||||
spl = 20 * math.Log10(spl/d.ReferenceLevel)
|
||||
|
||||
return int32(spl * 1000), maxval
|
||||
}
|
||||
|
||||
// sinc filter for 44 khz with 64 samples
|
||||
// each value matches the corresponding bit in the 8-bit value
|
||||
// for that sample.
|
||||
//
|
||||
// For more information: https://en.wikipedia.org/wiki/Sinc_filter
|
||||
//
|
||||
var sincfilter = [quantizeSteps]uint16{
|
||||
0, 2, 9, 21, 39, 63, 94, 132,
|
||||
179, 236, 302, 379, 467, 565, 674, 792,
|
||||
920, 1055, 1196, 1341, 1487, 1633, 1776, 1913,
|
||||
2042, 2159, 2263, 2352, 2422, 2474, 2506, 2516,
|
||||
2506, 2474, 2422, 2352, 2263, 2159, 2042, 1913,
|
||||
1776, 1633, 1487, 1341, 1196, 1055, 920, 792,
|
||||
674, 565, 467, 379, 302, 236, 179, 132,
|
||||
94, 63, 39, 21, 9, 2, 0, 0,
|
||||
}
|
||||
|
||||
// applySincFilter uses the sinc filter to process a single set of sample values.
|
||||
func applySincFilter(samples []uint32) (result uint16) {
|
||||
var sample uint16
|
||||
pos := 0
|
||||
for j := 0; j < len(samples); j++ {
|
||||
// takes only the low order 16-bits
|
||||
sample = uint16(samples[j] & 0xffff)
|
||||
for i := 0; i < 16; i++ {
|
||||
if (sample & 0x1) > 0 {
|
||||
result += sincfilter[pos]
|
||||
pos++
|
||||
}
|
||||
sample >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+33
-10
@@ -39,22 +39,45 @@ func (d Device) Configure() {
|
||||
}
|
||||
|
||||
// ReadAcceleration reads the current acceleration from the device and returns
|
||||
// it.
|
||||
func (d Device) ReadAcceleration() (x int16, y int16, z int16) {
|
||||
// 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 Device) ReadAcceleration() (x int32, y int32, z int32) {
|
||||
data := make([]byte, 6)
|
||||
d.bus.ReadRegister(uint8(d.Address), ACCEL_XOUT_H, data)
|
||||
x = int16((uint16(data[0]) << 8) | uint16(data[1]))
|
||||
y = int16((uint16(data[2]) << 8) | uint16(data[3]))
|
||||
z = int16((uint16(data[4]) << 8) | uint16(data[5]))
|
||||
// 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[0])<<8)|uint16(data[1]))) * 15625 / 256
|
||||
y = int32(int16((uint16(data[2])<<8)|uint16(data[3]))) * 15625 / 256
|
||||
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 256
|
||||
return
|
||||
}
|
||||
|
||||
// ReadRotation reads the current rotation from the device and returns it.
|
||||
func (d Device) ReadRotation() (x int16, y int16, z int16) {
|
||||
// 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 Device) ReadRotation() (x int32, y int32, z int32) {
|
||||
data := make([]byte, 6)
|
||||
d.bus.ReadRegister(uint8(d.Address), GYRO_XOUT_H, data)
|
||||
x = int16((uint16(data[0]) << 8) | uint16(data[1]))
|
||||
y = int16((uint16(data[2]) << 8) | uint16(data[3]))
|
||||
z = int16((uint16(data[4]) << 8) | uint16(data[5]))
|
||||
// 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).
|
||||
// This is done in the following steps:
|
||||
// 1. Multiply by 250 * 1000_000
|
||||
// 2. Divide by 32768
|
||||
// The following calculation (x * 15625 / 2048 * 1000) is essentially the
|
||||
// same but avoids overflow. First both operations are divided by 16 leading
|
||||
// to multiply by 15625000 and divide by 2048, and then part of the multiply
|
||||
// is done after the divide instead of before.
|
||||
x = int32(int16((uint16(data[0])<<8)|uint16(data[1]))) * 15625 / 2048 * 1000
|
||||
y = int32(int16((uint16(data[2])<<8)|uint16(data[3]))) * 15625 / 2048 * 1000
|
||||
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 2048 * 1000
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net
|
||||
|
||||
type DeviceDriver interface {
|
||||
GetDNS(domain string) (string, error)
|
||||
ConnectTCPSocket(addr, port string) error
|
||||
ConnectSSLSocket(addr, port string) error
|
||||
ConnectUDPSocket(addr, sendport, listenport string) error
|
||||
DisconnectSocket() error
|
||||
StartSocketSend(size int) error
|
||||
Write(b []byte) (n int, err error)
|
||||
ReadSocket(b []byte) (n int, err error)
|
||||
IsSocketDataAvailable() bool
|
||||
|
||||
// FIXME: this is really specific to espat, and maybe shouldn't be part
|
||||
// of the driver interface
|
||||
Response(timeout int) ([]byte, error)
|
||||
}
|
||||
|
||||
var ActiveDevice DeviceDriver
|
||||
|
||||
func UseDriver(driver DeviceDriver) {
|
||||
// TODO: rethink and refactor this
|
||||
if ActiveDevice != nil {
|
||||
panic("net.ActiveDevice is already set")
|
||||
}
|
||||
ActiveDevice = driver
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
// Package mqtt is intended to provide compatible interfaces with the
|
||||
// Paho mqtt library.
|
||||
package mqtt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eclipse/paho.mqtt.golang/packets"
|
||||
"tinygo.org/x/drivers/net"
|
||||
"tinygo.org/x/drivers/net/tls"
|
||||
)
|
||||
|
||||
// NewClient will create an MQTT v3.1.1 client with all of the options specified
|
||||
// in the provided ClientOptions. The client must have the Connect method called
|
||||
// on it before it may be used. This is to make sure resources (such as a net
|
||||
// connection) are created before the application is actually ready.
|
||||
func NewClient(o *ClientOptions) Client {
|
||||
c := &mqttclient{opts: o, adaptor: o.Adaptor}
|
||||
c.msgRouter, c.stopRouter = newRouter()
|
||||
return c
|
||||
}
|
||||
|
||||
type mqttclient struct {
|
||||
adaptor net.DeviceDriver
|
||||
conn net.Conn
|
||||
connected bool
|
||||
opts *ClientOptions
|
||||
mid uint16
|
||||
inbound chan packets.ControlPacket
|
||||
stop chan struct{}
|
||||
msgRouter *router
|
||||
stopRouter chan bool
|
||||
incomingPubChan chan *packets.PublishPacket
|
||||
}
|
||||
|
||||
// AddRoute allows you to add a handler for messages on a specific topic
|
||||
// without making a subscription. For example having a different handler
|
||||
// for parts of a wildcard subscription
|
||||
func (c *mqttclient) AddRoute(topic string, callback MessageHandler) {
|
||||
return
|
||||
}
|
||||
|
||||
// IsConnected returns a bool signifying whether
|
||||
// the client is connected or not.
|
||||
func (c *mqttclient) IsConnected() bool {
|
||||
return c.connected
|
||||
}
|
||||
|
||||
// IsConnectionOpen return a bool signifying whether the client has an active
|
||||
// connection to mqtt broker, i.e not in disconnected or reconnect mode
|
||||
func (c *mqttclient) IsConnectionOpen() bool {
|
||||
return c.connected
|
||||
}
|
||||
|
||||
// Connect will create a connection to the message broker.
|
||||
func (c *mqttclient) Connect() Token {
|
||||
var err error
|
||||
|
||||
// make connection
|
||||
if strings.Contains(c.opts.Servers, "ssl://") {
|
||||
url := strings.TrimPrefix(c.opts.Servers, "ssl://")
|
||||
c.conn, err = tls.Dial("tcp", url, nil)
|
||||
if err != nil {
|
||||
return &mqtttoken{err: err}
|
||||
}
|
||||
} else if strings.Contains(c.opts.Servers, "tcp://") {
|
||||
url := strings.TrimPrefix(c.opts.Servers, "tcp://")
|
||||
c.conn, err = net.Dial("tcp", url)
|
||||
if err != nil {
|
||||
return &mqtttoken{err: err}
|
||||
}
|
||||
} else {
|
||||
// invalid protocol
|
||||
return &mqtttoken{err: errors.New("invalid protocol")}
|
||||
}
|
||||
|
||||
c.mid = 1
|
||||
c.inbound = make(chan packets.ControlPacket)
|
||||
c.stop = make(chan struct{})
|
||||
c.incomingPubChan = make(chan *packets.PublishPacket)
|
||||
c.msgRouter.matchAndDispatch(c.incomingPubChan, c.opts.Order, c)
|
||||
|
||||
// send the MQTT connect message
|
||||
connectPkt := packets.NewControlPacket(packets.Connect).(*packets.ConnectPacket)
|
||||
connectPkt.Qos = 0
|
||||
if c.opts.Username != "" {
|
||||
connectPkt.Username = c.opts.Username
|
||||
connectPkt.UsernameFlag = true
|
||||
}
|
||||
|
||||
if c.opts.Password != "" {
|
||||
connectPkt.Password = []byte(c.opts.Password)
|
||||
connectPkt.PasswordFlag = true
|
||||
}
|
||||
|
||||
connectPkt.ClientIdentifier = c.opts.ClientID
|
||||
connectPkt.ProtocolVersion = byte(c.opts.ProtocolVersion)
|
||||
connectPkt.ProtocolName = "MQTT"
|
||||
connectPkt.Keepalive = 30
|
||||
|
||||
err = connectPkt.Write(c.conn)
|
||||
if err != nil {
|
||||
return &mqtttoken{err: err}
|
||||
}
|
||||
|
||||
// TODO: handle timeout as ReadPacket blocks until it gets a packet.
|
||||
// CONNECT response.
|
||||
packet, err := packets.ReadPacket(c.conn)
|
||||
if err != nil {
|
||||
return &mqtttoken{err: err}
|
||||
}
|
||||
if packet != nil {
|
||||
ack, ok := packet.(*packets.ConnackPacket)
|
||||
if ok {
|
||||
if ack.ReturnCode != 0 {
|
||||
return &mqtttoken{err: errors.New(packet.String())}
|
||||
}
|
||||
c.connected = true
|
||||
}
|
||||
}
|
||||
|
||||
go readMessages(c)
|
||||
go processInbound(c)
|
||||
|
||||
return &mqtttoken{}
|
||||
}
|
||||
|
||||
// Disconnect will end the connection with the server, but not before waiting
|
||||
// the specified number of milliseconds to wait for existing work to be
|
||||
// completed.
|
||||
func (c *mqttclient) Disconnect(quiesce uint) {
|
||||
c.conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Publish will publish a message with the specified QoS and content
|
||||
// to the specified topic.
|
||||
// Returns a token to track delivery of the message to the broker
|
||||
func (c *mqttclient) Publish(topic string, qos byte, retained bool, payload interface{}) Token {
|
||||
if !c.IsConnected() {
|
||||
return &mqtttoken{err: errors.New("MQTT client not connected")}
|
||||
}
|
||||
|
||||
pub := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket)
|
||||
pub.Qos = qos
|
||||
pub.TopicName = topic
|
||||
switch payload.(type) {
|
||||
case string:
|
||||
pub.Payload = []byte(payload.(string))
|
||||
case []byte:
|
||||
pub.Payload = payload.([]byte)
|
||||
default:
|
||||
return &mqtttoken{err: errors.New("Unknown payload type")}
|
||||
}
|
||||
pub.MessageID = c.mid
|
||||
c.mid++
|
||||
|
||||
err := pub.Write(c.conn)
|
||||
if err != nil {
|
||||
return &mqtttoken{err: err}
|
||||
}
|
||||
|
||||
return &mqtttoken{}
|
||||
}
|
||||
|
||||
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when
|
||||
// a message is published on the topic provided.
|
||||
func (c *mqttclient) Subscribe(topic string, qos byte, callback MessageHandler) Token {
|
||||
if !c.IsConnected() {
|
||||
return &mqtttoken{err: errors.New("MQTT client not connected")}
|
||||
}
|
||||
|
||||
sub := packets.NewControlPacket(packets.Subscribe).(*packets.SubscribePacket)
|
||||
sub.Topics = append(sub.Topics, topic)
|
||||
sub.Qoss = append(sub.Qoss, qos)
|
||||
|
||||
if callback != nil {
|
||||
c.msgRouter.addRoute(topic, callback)
|
||||
}
|
||||
|
||||
sub.MessageID = c.mid
|
||||
c.mid++
|
||||
|
||||
// drop in the channel to send
|
||||
err := sub.Write(c.conn)
|
||||
if err != nil {
|
||||
return &mqtttoken{err: err}
|
||||
}
|
||||
|
||||
return &mqtttoken{}
|
||||
}
|
||||
|
||||
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to
|
||||
// be executed when a message is published on one of the topics provided.
|
||||
func (c *mqttclient) SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token {
|
||||
return &mqtttoken{}
|
||||
}
|
||||
|
||||
// Unsubscribe will end the subscription from each of the topics provided.
|
||||
// Messages published to those topics from other clients will no longer be
|
||||
// received.
|
||||
func (c *mqttclient) Unsubscribe(topics ...string) Token {
|
||||
return &mqtttoken{}
|
||||
}
|
||||
|
||||
// OptionsReader returns a ClientOptionsReader which is a copy of the clientoptions
|
||||
// in use by the client.
|
||||
func (c *mqttclient) OptionsReader() ClientOptionsReader {
|
||||
r := ClientOptionsReader{}
|
||||
return r
|
||||
}
|
||||
|
||||
func processInbound(c *mqttclient) {
|
||||
for {
|
||||
select {
|
||||
case msg := <-c.inbound:
|
||||
switch m := msg.(type) {
|
||||
case *packets.PingrespPacket:
|
||||
// TODO: handle this
|
||||
case *packets.SubackPacket:
|
||||
// TODO: handle this
|
||||
case *packets.UnsubackPacket:
|
||||
// TODO: handle this
|
||||
case *packets.PublishPacket:
|
||||
// TODO: handle Qos
|
||||
c.incomingPubChan <- m
|
||||
case *packets.PubackPacket:
|
||||
// TODO: handle this
|
||||
case *packets.PubrecPacket:
|
||||
// TODO: handle this
|
||||
case *packets.PubrelPacket:
|
||||
// TODO: handle this
|
||||
case *packets.PubcompPacket:
|
||||
// TODO: handle this
|
||||
}
|
||||
case <-c.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readMessages reads incoming messages off the wire.
|
||||
// incoming messages are then send into inbound channel.
|
||||
func readMessages(c *mqttclient) {
|
||||
var err error
|
||||
var cp packets.ControlPacket
|
||||
|
||||
PROCESS:
|
||||
for {
|
||||
if cp, err = c.ReadPacket(); err != nil {
|
||||
break PROCESS
|
||||
}
|
||||
if cp != nil {
|
||||
c.inbound <- cp
|
||||
// TODO: Notify keepalive logic that we recently received a packet
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// TODO: handle if we received an error on read.
|
||||
// If disconnect is in progress, swallow error and return
|
||||
}
|
||||
|
||||
func (c *mqttclient) ackFunc(packet *packets.PublishPacket) func() {
|
||||
return func() {
|
||||
switch packet.Qos {
|
||||
case 2:
|
||||
// pr := packets.NewControlPacket(packets.Pubrec).(*packets.PubrecPacket)
|
||||
// pr.MessageID = packet.MessageID
|
||||
// DEBUG.Println(NET, "putting pubrec msg on obound")
|
||||
// select {
|
||||
// case c.oboundP <- &PacketAndToken{p: pr, t: nil}:
|
||||
// case <-c.stop:
|
||||
// }
|
||||
// DEBUG.Println(NET, "done putting pubrec msg on obound")
|
||||
case 1:
|
||||
// pa := packets.NewControlPacket(packets.Puback).(*packets.PubackPacket)
|
||||
// pa.MessageID = packet.MessageID
|
||||
// DEBUG.Println(NET, "putting puback msg on obound")
|
||||
// persistOutbound(c.persist, pa)
|
||||
// select {
|
||||
// case c.oboundP <- &PacketAndToken{p: pa, t: nil}:
|
||||
// case <-c.stop:
|
||||
// }
|
||||
// DEBUG.Println(NET, "done putting puback msg on obound")
|
||||
case 0:
|
||||
// do nothing, since there is no need to send an ack packet back
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReadPacket tries to read the next incoming packet from the MQTT broker.
|
||||
// If there is no data yet but also is no error, it returns nil for both values.
|
||||
func (c *mqttclient) ReadPacket() (packets.ControlPacket, error) {
|
||||
// check for data first...
|
||||
if net.ActiveDevice.IsSocketDataAvailable() {
|
||||
return packets.ReadPacket(c.conn)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
// The following code is a slightly modified version of code taken from the Paho MQTT library.
|
||||
// It is here until TinyGo can compile the "net" package from the standard library, at which time
|
||||
// it can be removed.
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Seth Hoenig
|
||||
* Allan Stockdill-Mander
|
||||
* Mike Robertson
|
||||
*/
|
||||
|
||||
// Portions copyright © 2018 TIBCO Software Inc.
|
||||
|
||||
package mqtt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eclipse/paho.mqtt.golang/packets"
|
||||
"tinygo.org/x/drivers/net"
|
||||
)
|
||||
|
||||
const (
|
||||
disconnected uint32 = iota
|
||||
connecting
|
||||
reconnecting
|
||||
connected
|
||||
)
|
||||
|
||||
// Client is the interface definition for a Client as used by this
|
||||
// library, the interface is primarily to allow mocking tests.
|
||||
//
|
||||
// It is an MQTT v3.1.1 client for communicating
|
||||
// with an MQTT server using non-blocking methods that allow work
|
||||
// to be done in the background.
|
||||
// An application may connect to an MQTT server using:
|
||||
// A plain TCP socket
|
||||
// A secure SSL/TLS socket
|
||||
// A websocket
|
||||
// To enable ensured message delivery at Quality of Service (QoS) levels
|
||||
// described in the MQTT spec, a message persistence mechanism must be
|
||||
// used. This is done by providing a type which implements the Store
|
||||
// interface. For convenience, FileStore and MemoryStore are provided
|
||||
// implementations that should be sufficient for most use cases. More
|
||||
// information can be found in their respective documentation.
|
||||
// Numerous connection options may be specified by configuring a
|
||||
// and then supplying a ClientOptions type.
|
||||
type Client interface {
|
||||
// IsConnected returns a bool signifying whether
|
||||
// the client is connected or not.
|
||||
IsConnected() bool
|
||||
// IsConnectionOpen return a bool signifying wether the client has an active
|
||||
// connection to mqtt broker, i.e not in disconnected or reconnect mode
|
||||
IsConnectionOpen() bool
|
||||
// Connect will create a connection to the message broker, by default
|
||||
// it will attempt to connect at v3.1.1 and auto retry at v3.1 if that
|
||||
// fails
|
||||
Connect() Token
|
||||
// Disconnect will end the connection with the server, but not before waiting
|
||||
// the specified number of milliseconds to wait for existing work to be
|
||||
// completed.
|
||||
Disconnect(quiesce uint)
|
||||
// Publish will publish a message with the specified QoS and content
|
||||
// to the specified topic.
|
||||
// Returns a token to track delivery of the message to the broker
|
||||
Publish(topic string, qos byte, retained bool, payload interface{}) Token
|
||||
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when
|
||||
// a message is published on the topic provided, or nil for the default handler
|
||||
Subscribe(topic string, qos byte, callback MessageHandler) Token
|
||||
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to
|
||||
// be executed when a message is published on one of the topics provided, or nil for the
|
||||
// default handler
|
||||
SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token
|
||||
// Unsubscribe will end the subscription from each of the topics provided.
|
||||
// Messages published to those topics from other clients will no longer be
|
||||
// received.
|
||||
Unsubscribe(topics ...string) Token
|
||||
// AddRoute allows you to add a handler for messages on a specific topic
|
||||
// without making a subscription. For example having a different handler
|
||||
// for parts of a wildcard subscription
|
||||
AddRoute(topic string, callback MessageHandler)
|
||||
// OptionsReader returns a ClientOptionsReader which is a copy of the clientoptions
|
||||
// in use by the client.
|
||||
OptionsReader() ClientOptionsReader
|
||||
}
|
||||
|
||||
// Token defines the interface for the tokens used to indicate when
|
||||
// actions have completed.
|
||||
type Token interface {
|
||||
Wait() bool
|
||||
WaitTimeout(time.Duration) bool
|
||||
Error() error
|
||||
}
|
||||
|
||||
// MessageHandler is a callback type which can be set to be
|
||||
// executed upon the arrival of messages published to topics
|
||||
// to which the client is subscribed.
|
||||
type MessageHandler func(Client, Message)
|
||||
|
||||
// Message defines the externals that a message implementation must support
|
||||
// these are received messages that are passed to the callbacks, not internal
|
||||
// messages
|
||||
type Message interface {
|
||||
Duplicate() bool
|
||||
Qos() byte
|
||||
Retained() bool
|
||||
Topic() string
|
||||
MessageID() uint16
|
||||
Payload() []byte
|
||||
Ack()
|
||||
}
|
||||
|
||||
type message struct {
|
||||
duplicate bool
|
||||
qos byte
|
||||
retained bool
|
||||
topic string
|
||||
messageID uint16
|
||||
payload []byte
|
||||
ack func()
|
||||
}
|
||||
|
||||
func (m *message) Duplicate() bool {
|
||||
return m.duplicate
|
||||
}
|
||||
|
||||
func (m *message) Qos() byte {
|
||||
return m.qos
|
||||
}
|
||||
|
||||
func (m *message) Retained() bool {
|
||||
return m.retained
|
||||
}
|
||||
|
||||
func (m *message) Topic() string {
|
||||
return m.topic
|
||||
}
|
||||
|
||||
func (m *message) MessageID() uint16 {
|
||||
return m.messageID
|
||||
}
|
||||
|
||||
func (m *message) Payload() []byte {
|
||||
return m.payload
|
||||
}
|
||||
|
||||
func (m *message) Ack() {
|
||||
return
|
||||
}
|
||||
|
||||
func messageFromPublish(p *packets.PublishPacket, ack func()) Message {
|
||||
return &message{
|
||||
duplicate: p.Dup,
|
||||
qos: p.Qos,
|
||||
retained: p.Retain,
|
||||
topic: p.TopicName,
|
||||
messageID: p.MessageID,
|
||||
payload: p.Payload,
|
||||
ack: ack,
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOptionsReader provides an interface for reading ClientOptions after the client has been initialized.
|
||||
type ClientOptionsReader struct {
|
||||
options *ClientOptions
|
||||
}
|
||||
|
||||
// ClientOptions contains configurable options for an MQTT Client.
|
||||
type ClientOptions struct {
|
||||
Adaptor net.DeviceDriver
|
||||
|
||||
//Servers []*url.URL
|
||||
Servers string
|
||||
ClientID string
|
||||
Username string
|
||||
Password string
|
||||
//CredentialsProvider CredentialsProvider
|
||||
CleanSession bool
|
||||
Order bool
|
||||
WillEnabled bool
|
||||
WillTopic string
|
||||
WillPayload []byte
|
||||
WillQos byte
|
||||
WillRetained bool
|
||||
ProtocolVersion uint
|
||||
protocolVersionExplicit bool
|
||||
//TLSConfig *tls.Config
|
||||
KeepAlive int64
|
||||
PingTimeout time.Duration
|
||||
ConnectTimeout time.Duration
|
||||
MaxReconnectInterval time.Duration
|
||||
AutoReconnect bool
|
||||
//Store Store
|
||||
//DefaultPublishHandler MessageHandler
|
||||
//OnConnect OnConnectHandler
|
||||
//OnConnectionLost ConnectionLostHandler
|
||||
WriteTimeout time.Duration
|
||||
MessageChannelDepth uint
|
||||
ResumeSubs bool
|
||||
//HTTPHeaders http.Header
|
||||
}
|
||||
|
||||
// NewClientOptions returns a new ClientOptions struct.
|
||||
func NewClientOptions() *ClientOptions {
|
||||
return &ClientOptions{Adaptor: net.ActiveDevice, ProtocolVersion: 4}
|
||||
}
|
||||
|
||||
// AddBroker adds a broker URI to the list of brokers to be used. The format should be
|
||||
// scheme://host:port
|
||||
// Where "scheme" is one of "tcp", "ssl", or "ws", "host" is the ip-address (or hostname)
|
||||
// and "port" is the port on which the broker is accepting connections.
|
||||
//
|
||||
// Default values for hostname is "127.0.0.1", for schema is "tcp://".
|
||||
//
|
||||
// An example broker URI would look like: tcp://foobar.com:1883
|
||||
func (o *ClientOptions) AddBroker(server string) *ClientOptions {
|
||||
if len(server) > 0 && server[0] == ':' {
|
||||
server = "127.0.0.1" + server
|
||||
}
|
||||
if !strings.Contains(server, "://") {
|
||||
server = "tcp://" + server
|
||||
}
|
||||
|
||||
o.Servers = server
|
||||
return o
|
||||
}
|
||||
|
||||
// SetClientID will set the client id to be used by this client when
|
||||
// connecting to the MQTT broker. According to the MQTT v3.1 specification,
|
||||
// a client id mus be no longer than 23 characters.
|
||||
func (o *ClientOptions) SetClientID(id string) *ClientOptions {
|
||||
o.ClientID = id
|
||||
return o
|
||||
}
|
||||
|
||||
// SetUsername will set the username to be used by this client when connecting
|
||||
// to the MQTT broker. Note: without the use of SSL/TLS, this information will
|
||||
// be sent in plaintext accross the wire.
|
||||
func (o *ClientOptions) SetUsername(u string) *ClientOptions {
|
||||
o.Username = u
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPassword will set the password to be used by this client when connecting
|
||||
// to the MQTT broker. Note: without the use of SSL/TLS, this information will
|
||||
// be sent in plaintext accross the wire.
|
||||
func (o *ClientOptions) SetPassword(p string) *ClientOptions {
|
||||
o.Password = p
|
||||
return o
|
||||
}
|
||||
|
||||
// SetWill accepts a string will message to be set. When the client connects,
|
||||
// it will give this will message to the broker, which will then publish the
|
||||
// provided payload (the will) to any clients that are subscribed to the provided
|
||||
// topic.
|
||||
func (o *ClientOptions) SetWill(topic string, payload string, qos byte, retained bool) *ClientOptions {
|
||||
o.SetBinaryWill(topic, []byte(payload), qos, retained)
|
||||
return o
|
||||
}
|
||||
|
||||
// SetBinaryWill accepts a []byte will message to be set. When the client connects,
|
||||
// it will give this will message to the broker, which will then publish the
|
||||
// provided payload (the will) to any clients that are subscribed to the provided
|
||||
// topic.
|
||||
func (o *ClientOptions) SetBinaryWill(topic string, payload []byte, qos byte, retained bool) *ClientOptions {
|
||||
o.WillEnabled = true
|
||||
o.WillTopic = topic
|
||||
o.WillPayload = payload
|
||||
o.WillQos = qos
|
||||
o.WillRetained = retained
|
||||
return o
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// The following code is a slightly modified version of code taken from the Paho MQTT library.
|
||||
// It is here until TinyGo can compile the "net" package from the standard library, at which time
|
||||
// it can be removed.
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Seth Hoenig
|
||||
* Allan Stockdill-Mander
|
||||
* Mike Robertson
|
||||
*/
|
||||
|
||||
package mqtt
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"strings"
|
||||
|
||||
"github.com/eclipse/paho.mqtt.golang/packets"
|
||||
)
|
||||
|
||||
// route is a type which associates MQTT Topic strings with a
|
||||
// callback to be executed upon the arrival of a message associated
|
||||
// with a subscription to that topic.
|
||||
type route struct {
|
||||
topic string
|
||||
callback MessageHandler
|
||||
}
|
||||
|
||||
// match takes a slice of strings which represent the route being tested having been split on '/'
|
||||
// separators, and a slice of strings representing the topic string in the published message, similarly
|
||||
// split.
|
||||
// The function determines if the topic string matches the route according to the MQTT topic rules
|
||||
// and returns a boolean of the outcome
|
||||
func match(route []string, topic []string) bool {
|
||||
if len(route) == 0 {
|
||||
if len(topic) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if len(topic) == 0 {
|
||||
if route[0] == "#" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if route[0] == "#" {
|
||||
return true
|
||||
}
|
||||
|
||||
if (route[0] == "+") || (route[0] == topic[0]) {
|
||||
return match(route[1:], topic[1:])
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func routeIncludesTopic(route, topic string) bool {
|
||||
return match(routeSplit(route), strings.Split(topic, "/"))
|
||||
}
|
||||
|
||||
// removes $share and sharename when splitting the route to allow
|
||||
// shared subscription routes to correctly match the topic
|
||||
func routeSplit(route string) []string {
|
||||
var result []string
|
||||
if strings.HasPrefix(route, "$share") {
|
||||
result = strings.Split(route, "/")[2:]
|
||||
} else {
|
||||
result = strings.Split(route, "/")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// match takes the topic string of the published message and does a basic compare to the
|
||||
// string of the current Route, if they match it returns true
|
||||
func (r *route) match(topic string) bool {
|
||||
return r.topic == topic || routeIncludesTopic(r.topic, topic)
|
||||
}
|
||||
|
||||
type router struct {
|
||||
//sync.RWMutex
|
||||
routes *list.List
|
||||
defaultHandler MessageHandler
|
||||
messages chan *packets.PublishPacket
|
||||
stop chan bool
|
||||
}
|
||||
|
||||
// newRouter returns a new instance of a Router and channel which can be used to tell the Router
|
||||
// to stop
|
||||
func newRouter() (*router, chan bool) {
|
||||
router := &router{routes: list.New(), messages: make(chan *packets.PublishPacket), stop: make(chan bool)}
|
||||
stop := router.stop
|
||||
return router, stop
|
||||
}
|
||||
|
||||
// addRoute takes a topic string and MessageHandler callback. It looks in the current list of
|
||||
// routes to see if there is already a matching Route. If there is it replaces the current
|
||||
// callback with the new one. If not it add a new entry to the list of Routes.
|
||||
func (r *router) addRoute(topic string, callback MessageHandler) {
|
||||
for e := r.routes.Front(); e != nil; e = e.Next() {
|
||||
if e.Value.(*route).match(topic) {
|
||||
r := e.Value.(*route)
|
||||
r.callback = callback
|
||||
return
|
||||
}
|
||||
}
|
||||
r.routes.PushBack(&route{topic: topic, callback: callback})
|
||||
}
|
||||
|
||||
// deleteRoute takes a route string, looks for a matching Route in the list of Routes. If
|
||||
// found it removes the Route from the list.
|
||||
func (r *router) deleteRoute(topic string) {
|
||||
for e := r.routes.Front(); e != nil; e = e.Next() {
|
||||
if e.Value.(*route).match(topic) {
|
||||
r.routes.Remove(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaultHandler assigns a default callback that will be called if no matching Route
|
||||
// is found for an incoming Publish.
|
||||
func (r *router) setDefaultHandler(handler MessageHandler) {
|
||||
r.defaultHandler = handler
|
||||
}
|
||||
|
||||
// matchAndDispatch takes a channel of Message pointers as input and starts a go routine that
|
||||
// takes messages off the channel, matches them against the internal route list and calls the
|
||||
// associated callback (or the defaultHandler, if one exists and no other route matched). If
|
||||
// anything is sent down the stop channel the function will end.
|
||||
func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order bool, client *mqttclient) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case message := <-messages:
|
||||
sent := false
|
||||
m := messageFromPublish(message, client.ackFunc(message))
|
||||
handlers := []MessageHandler{}
|
||||
for e := r.routes.Front(); e != nil; e = e.Next() {
|
||||
if e.Value.(*route).match(message.TopicName) {
|
||||
if order {
|
||||
handlers = append(handlers, e.Value.(*route).callback)
|
||||
} else {
|
||||
hd := e.Value.(*route).callback
|
||||
go func() {
|
||||
hd(client, m)
|
||||
//TODO: m.Ack()
|
||||
}()
|
||||
}
|
||||
sent = true
|
||||
}
|
||||
}
|
||||
if !sent && r.defaultHandler != nil {
|
||||
if order {
|
||||
handlers = append(handlers, r.defaultHandler)
|
||||
} else {
|
||||
go func() {
|
||||
r.defaultHandler(client, m)
|
||||
//TODO: m.Ack()
|
||||
}()
|
||||
}
|
||||
}
|
||||
for _, handler := range handlers {
|
||||
func() {
|
||||
handler(client, m)
|
||||
//TODO: m.Ack()
|
||||
}()
|
||||
}
|
||||
case <-r.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package mqtt
|
||||
|
||||
import "time"
|
||||
|
||||
type mqtttoken struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (t *mqtttoken) Wait() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *mqtttoken) WaitTimeout(time.Duration) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *mqtttoken) Error() error {
|
||||
return t.err
|
||||
}
|
||||
+410
@@ -0,0 +1,410 @@
|
||||
// package net is intended to provide compatible interfaces with the
|
||||
// Go standard library's net package.
|
||||
package net
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DialUDP makes a UDP network connection. raadr is the port that the messages will
|
||||
// be sent to, and laddr is the port that will be listened to in order to
|
||||
// receive incoming messages.
|
||||
func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPSerialConn, error) {
|
||||
addr := raddr.IP.String()
|
||||
sendport := strconv.Itoa(raddr.Port)
|
||||
listenport := strconv.Itoa(laddr.Port)
|
||||
|
||||
// disconnect any old socket
|
||||
ActiveDevice.DisconnectSocket()
|
||||
|
||||
// connect new socket
|
||||
err := ActiveDevice.ConnectUDPSocket(addr, sendport, listenport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &UDPSerialConn{SerialConn: SerialConn{Adaptor: ActiveDevice}, laddr: laddr, raddr: raddr}, nil
|
||||
}
|
||||
|
||||
// ListenUDP listens for UDP connections on the port listed in laddr.
|
||||
func ListenUDP(network string, laddr *UDPAddr) (*UDPSerialConn, error) {
|
||||
addr := "0"
|
||||
sendport := "0"
|
||||
listenport := strconv.Itoa(laddr.Port)
|
||||
|
||||
// disconnect any old socket
|
||||
ActiveDevice.DisconnectSocket()
|
||||
|
||||
// connect new socket
|
||||
err := ActiveDevice.ConnectUDPSocket(addr, sendport, listenport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &UDPSerialConn{SerialConn: SerialConn{Adaptor: ActiveDevice}, laddr: laddr}, nil
|
||||
}
|
||||
|
||||
// DialTCP makes a TCP network connection. raadr is the port that the messages will
|
||||
// be sent to, and laddr is the port that will be listened to in order to
|
||||
// receive incoming messages.
|
||||
func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPSerialConn, error) {
|
||||
addr := raddr.IP.String()
|
||||
sendport := strconv.Itoa(raddr.Port)
|
||||
|
||||
// disconnect any old socket?
|
||||
//ActiveDevice.DisconnectSocket()
|
||||
|
||||
// connect new socket
|
||||
err := ActiveDevice.ConnectTCPSocket(addr, sendport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TCPSerialConn{SerialConn: SerialConn{Adaptor: ActiveDevice}, laddr: laddr, raddr: raddr}, nil
|
||||
}
|
||||
|
||||
// Dial connects to the address on the named network.
|
||||
// It tries to provide a mostly compatible interface
|
||||
// to net.Dial().
|
||||
func Dial(network, address string) (Conn, error) {
|
||||
switch network {
|
||||
case "tcp":
|
||||
raddr, err := ResolveTCPAddr(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, e := DialTCP(network, &TCPAddr{}, raddr)
|
||||
return c.opConn(), e
|
||||
case "udp":
|
||||
raddr, err := ResolveUDPAddr(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, e := DialUDP(network, &UDPAddr{}, raddr)
|
||||
return c.opConn(), e
|
||||
default:
|
||||
return nil, errors.New("invalid network for dial")
|
||||
}
|
||||
}
|
||||
|
||||
// SerialConn is a loosely net.Conn compatible implementation
|
||||
type SerialConn struct {
|
||||
Adaptor DeviceDriver
|
||||
}
|
||||
|
||||
// UDPSerialConn is a loosely net.Conn compatible intended to support
|
||||
// UDP over serial.
|
||||
type UDPSerialConn struct {
|
||||
SerialConn
|
||||
laddr *UDPAddr
|
||||
raddr *UDPAddr
|
||||
}
|
||||
|
||||
// NewUDPSerialConn returns a new UDPSerialConn/
|
||||
func NewUDPSerialConn(c SerialConn, laddr, raddr *UDPAddr) *UDPSerialConn {
|
||||
return &UDPSerialConn{SerialConn: c, raddr: raddr}
|
||||
}
|
||||
|
||||
// TCPSerialConn is a loosely net.Conn compatible intended to support
|
||||
// TCP over serial.
|
||||
type TCPSerialConn struct {
|
||||
SerialConn
|
||||
laddr *TCPAddr
|
||||
raddr *TCPAddr
|
||||
}
|
||||
|
||||
// NewTCPSerialConn returns a new TCPSerialConn/
|
||||
func NewTCPSerialConn(c SerialConn, laddr, raddr *TCPAddr) *TCPSerialConn {
|
||||
return &TCPSerialConn{SerialConn: c, raddr: raddr}
|
||||
}
|
||||
|
||||
// Read reads data from the connection.
|
||||
// TODO: implement the full method functionality:
|
||||
// Read can be made to time out and return an Error with Timeout() == true
|
||||
// after a fixed time limit; see SetDeadline and SetReadDeadline.
|
||||
func (c *SerialConn) Read(b []byte) (n int, err error) {
|
||||
// read only the data that has been received via "+IPD" socket
|
||||
return c.Adaptor.ReadSocket(b)
|
||||
}
|
||||
|
||||
// Write writes data to the connection.
|
||||
// TODO: implement the full method functionality for timeouts.
|
||||
// Write can be made to time out and return an Error with Timeout() == true
|
||||
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
|
||||
func (c *SerialConn) Write(b []byte) (n int, err error) {
|
||||
// specify that is a data transfer to the
|
||||
// currently open socket, not commands to the ESP8266/ESP32.
|
||||
err = c.Adaptor.StartSocketSend(len(b))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n, err = c.Adaptor.Write(b)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
/* TODO(bcg): this is kind of specific to espat, should maybe refactor */
|
||||
_, err = c.Adaptor.Response(1000)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
// Currently only supports a single Read or Write operations without blocking.
|
||||
func (c *SerialConn) Close() error {
|
||||
c.Adaptor.DisconnectSocket()
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (c *UDPSerialConn) LocalAddr() Addr {
|
||||
return c.laddr.opAddr()
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (c *UDPSerialConn) RemoteAddr() Addr {
|
||||
return c.laddr.opAddr()
|
||||
}
|
||||
|
||||
func (c *UDPSerialConn) opConn() Conn {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (c *TCPSerialConn) LocalAddr() Addr {
|
||||
return c.laddr.opAddr()
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (c *TCPSerialConn) RemoteAddr() Addr {
|
||||
return c.laddr.opAddr()
|
||||
}
|
||||
|
||||
func (c *TCPSerialConn) opConn() Conn {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// SetDeadline sets the read and write deadlines associated
|
||||
// with the connection. It is equivalent to calling both
|
||||
// SetReadDeadline and SetWriteDeadline.
|
||||
//
|
||||
// A deadline is an absolute time after which I/O operations
|
||||
// fail with a timeout (see type Error) instead of
|
||||
// blocking. The deadline applies to all future and pending
|
||||
// I/O, not just the immediately following call to Read or
|
||||
// Write. After a deadline has been exceeded, the connection
|
||||
// can be refreshed by setting a deadline in the future.
|
||||
//
|
||||
// An idle timeout can be implemented by repeatedly extending
|
||||
// the deadline after successful Read or Write calls.
|
||||
//
|
||||
// A zero value for t means I/O operations will not time out.
|
||||
func (c *SerialConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the deadline for future Read calls
|
||||
// and any currently-blocked Read call.
|
||||
// A zero value for t means Read will not time out.
|
||||
func (c *SerialConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the deadline for future Write calls
|
||||
// and any currently-blocked Write call.
|
||||
// Even if write times out, it may return n > 0, indicating that
|
||||
// some of the data was successfully written.
|
||||
// A zero value for t means Write will not time out.
|
||||
func (c *SerialConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveTCPAddr returns an address of TCP end point.
|
||||
//
|
||||
// The network must be a TCP network name.
|
||||
//
|
||||
func ResolveTCPAddr(network, address string) (*TCPAddr, error) {
|
||||
// TODO: make sure network is 'tcp'
|
||||
// separate domain from port, if any
|
||||
r := strings.Split(address, ":")
|
||||
addr, err := ActiveDevice.GetDNS(r[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip := IP(addr)
|
||||
if len(r) > 1 {
|
||||
port, e := strconv.Atoi(r[1])
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return &TCPAddr{IP: ip, Port: port}, nil
|
||||
}
|
||||
return &TCPAddr{IP: ip}, nil
|
||||
}
|
||||
|
||||
// ResolveUDPAddr returns an address of UDP end point.
|
||||
//
|
||||
// The network must be a UDP network name.
|
||||
//
|
||||
func ResolveUDPAddr(network, address string) (*UDPAddr, error) {
|
||||
// TODO: make sure network is 'udp'
|
||||
// separate domain from port, if any
|
||||
r := strings.Split(address, ":")
|
||||
addr, err := ActiveDevice.GetDNS(r[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip := IP(addr)
|
||||
if len(r) > 1 {
|
||||
port, e := strconv.Atoi(r[1])
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return &UDPAddr{IP: ip, Port: port}, nil
|
||||
}
|
||||
|
||||
return &UDPAddr{IP: ip}, nil
|
||||
}
|
||||
|
||||
// The following definitions are here to support a Golang standard package
|
||||
// net-compatible interface for IP until TinyGo can compile the net package.
|
||||
|
||||
// IP is an IP address. Unlike the standard implementation, it is only
|
||||
// a buffer of bytes that contains the string form of the IP address, not the
|
||||
// full byte format used by the Go standard .
|
||||
type IP []byte
|
||||
|
||||
// UDPAddr here to serve as compatible type. until TinyGo can compile the net package.
|
||||
type UDPAddr struct {
|
||||
IP IP
|
||||
Port int
|
||||
Zone string // IPv6 scoped addressing zone; added in Go 1.1
|
||||
}
|
||||
|
||||
// Network returns the address's network name, "udp".
|
||||
func (a *UDPAddr) Network() string { return "udp" }
|
||||
|
||||
func (a *UDPAddr) String() string {
|
||||
if a == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
if a.Port != 0 {
|
||||
return a.IP.String() + ":" + strconv.Itoa(a.Port)
|
||||
}
|
||||
return a.IP.String()
|
||||
}
|
||||
|
||||
func (a *UDPAddr) opAddr() Addr {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// TCPAddr here to serve as compatible type. until TinyGo can compile the net package.
|
||||
type TCPAddr struct {
|
||||
IP IP
|
||||
Port int
|
||||
Zone string // IPv6 scoped addressing zone
|
||||
}
|
||||
|
||||
// Network returns the address's network name, "tcp".
|
||||
func (a *TCPAddr) Network() string { return "tcp" }
|
||||
|
||||
func (a *TCPAddr) String() string {
|
||||
if a == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
if a.Port != 0 {
|
||||
return a.IP.String() + ":" + strconv.Itoa(a.Port)
|
||||
}
|
||||
return a.IP.String()
|
||||
}
|
||||
|
||||
func (a *TCPAddr) opAddr() Addr {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// ParseIP parses s as an IP address, returning the result.
|
||||
func ParseIP(s string) IP {
|
||||
return IP([]byte(s))
|
||||
}
|
||||
|
||||
// String returns the string form of the IP address ip.
|
||||
func (ip IP) String() string {
|
||||
return string(ip)
|
||||
}
|
||||
|
||||
// Conn is a generic stream-oriented network connection.
|
||||
// This interface is from the Go standard library.
|
||||
type Conn interface {
|
||||
// Read reads data from the connection.
|
||||
// Read can be made to time out and return an Error with Timeout() == true
|
||||
// after a fixed time limit; see SetDeadline and SetReadDeadline.
|
||||
Read(b []byte) (n int, err error)
|
||||
|
||||
// Write writes data to the connection.
|
||||
// Write can be made to time out and return an Error with Timeout() == true
|
||||
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
|
||||
Write(b []byte) (n int, err error)
|
||||
|
||||
// Close closes the connection.
|
||||
// Any blocked Read or Write operations will be unblocked and return errors.
|
||||
Close() error
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
LocalAddr() Addr
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
RemoteAddr() Addr
|
||||
|
||||
// SetDeadline sets the read and write deadlines associated
|
||||
// with the connection. It is equivalent to calling both
|
||||
// SetReadDeadline and SetWriteDeadline.
|
||||
//
|
||||
// A deadline is an absolute time after which I/O operations
|
||||
// fail with a timeout (see type Error) instead of
|
||||
// blocking. The deadline applies to all future and pending
|
||||
// I/O, not just the immediately following call to Read or
|
||||
// Write. After a deadline has been exceeded, the connection
|
||||
// can be refreshed by setting a deadline in the future.
|
||||
//
|
||||
// An idle timeout can be implemented by repeatedly extending
|
||||
// the deadline after successful Read or Write calls.
|
||||
//
|
||||
// A zero value for t means I/O operations will not time out.
|
||||
SetDeadline(t time.Time) error
|
||||
|
||||
// SetReadDeadline sets the deadline for future Read calls
|
||||
// and any currently-blocked Read call.
|
||||
// A zero value for t means Read will not time out.
|
||||
SetReadDeadline(t time.Time) error
|
||||
|
||||
// SetWriteDeadline sets the deadline for future Write calls
|
||||
// and any currently-blocked Write call.
|
||||
// Even if write times out, it may return n > 0, indicating that
|
||||
// some of the data was successfully written.
|
||||
// A zero value for t means Write will not time out.
|
||||
SetWriteDeadline(t time.Time) error
|
||||
}
|
||||
|
||||
// Addr represents a network end point address.
|
||||
type Addr interface {
|
||||
Network() string // name of the network (for example, "tcp", "udp")
|
||||
String() string // string form of address (for example, "192.0.2.1:25", "[2001:db8::1]:80")
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Package tls is intended to provide a minimal set of compatible interfaces with the
|
||||
// Go standard library's tls package.
|
||||
package tls
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
)
|
||||
|
||||
// Dial makes a TLS network connection. It tries to provide a mostly compatible interface
|
||||
// to tls.Dial().
|
||||
// Dial connects to the given network address.
|
||||
func Dial(network, address string, config *Config) (*net.TCPSerialConn, error) {
|
||||
raddr, err := net.ResolveTCPAddr(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := raddr.IP.String()
|
||||
sendport := strconv.Itoa(raddr.Port)
|
||||
|
||||
// disconnect any old socket
|
||||
net.ActiveDevice.DisconnectSocket()
|
||||
|
||||
// connect new socket
|
||||
err = net.ActiveDevice.ConnectSSLSocket(addr, sendport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return net.NewTCPSerialConn(net.SerialConn{Adaptor: net.ActiveDevice}, nil, raddr), nil
|
||||
}
|
||||
|
||||
// Config is a placeholder for future compatibility with
|
||||
// tls.Config.
|
||||
type Config struct {
|
||||
}
|
||||
+1
-1
@@ -127,7 +127,7 @@ func (d *Device) GetPixel(x int16, y int16) bool {
|
||||
func (d *Device) SetBuffer(buffer []byte) error {
|
||||
if int16(len(buffer)) != d.bufferSize {
|
||||
//return ErrBuffer
|
||||
return errors.New("Wrong size buffer")
|
||||
return errors.New("wrong size buffer")
|
||||
}
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
d.buffer[i] = buffer[i]
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Package semihosting implements parts of the ARM semihosting specification,
|
||||
// for communicating over a debug connection.
|
||||
//
|
||||
// If you want to use it in OpenOCD, you have to enable it first with the
|
||||
// following command:
|
||||
//
|
||||
// arm semihosting enable
|
||||
package semihosting
|
||||
|
||||
import (
|
||||
"device/arm"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// IOError is returned by I/O operations when they fail.
|
||||
type IOError struct {
|
||||
BytesWritten int
|
||||
}
|
||||
|
||||
func (e *IOError) Error() string {
|
||||
return "semihosting: I/O error"
|
||||
}
|
||||
|
||||
// Write writes the given data to the given file descriptor. It returns an
|
||||
// *IOError if the write was not successful.
|
||||
func Write(fd uintptr, data []byte) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
params := struct {
|
||||
fd uintptr
|
||||
data unsafe.Pointer
|
||||
len int
|
||||
}{
|
||||
fd: fd,
|
||||
data: unsafe.Pointer(&data[0]),
|
||||
len: len(data),
|
||||
}
|
||||
unwritten := arm.SemihostingCall(arm.SemihostingWrite, uintptr(unsafe.Pointer(¶ms)))
|
||||
if unwritten != 0 {
|
||||
// Error: unwritten is the number of bytes not written.
|
||||
return &IOError{
|
||||
BytesWritten: len(data) - unwritten,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package semihosting
|
||||
|
||||
// These three file descriptors are connected to the host stdin/stdout/stderr,
|
||||
// and can be used for logging.
|
||||
var (
|
||||
Stdin = File{fd: 0}
|
||||
Stdout = File{fd: 1}
|
||||
Stderr = File{fd: 2}
|
||||
)
|
||||
|
||||
// File represents a semihosting file descriptor.
|
||||
type File struct {
|
||||
fd uintptr
|
||||
}
|
||||
|
||||
// Write writes the given data buffer to the file descriptor, returning an error
|
||||
// if the write could not complete successfully.
|
||||
func (f *File) Write(buf []byte) error {
|
||||
return Write(f.fd, buf)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Package shifter is for 8bit shift register, most common are 74HC165 and 74165
|
||||
package shifter // import "tinygo.org/x/drivers/shifter"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
)
|
||||
|
||||
const (
|
||||
EIGHT_BITS NumberBit = 8
|
||||
SIXTEEN_BITS NumberBit = 16
|
||||
THIRTYTWO_BITS NumberBit = 32
|
||||
)
|
||||
|
||||
type NumberBit int8
|
||||
|
||||
// Device holds the Pins.
|
||||
type Device struct {
|
||||
latch machine.Pin
|
||||
clk machine.Pin
|
||||
out machine.Pin
|
||||
Pins []ShiftPin
|
||||
bits NumberBit
|
||||
}
|
||||
|
||||
// ShiftPin is the implementation of the ShiftPin interface.
|
||||
type ShiftPin struct {
|
||||
pin machine.Pin
|
||||
d *Device
|
||||
}
|
||||
|
||||
// New returns a new thermistor driver given an ADC pin.
|
||||
func New(numBits NumberBit, latch, clk, out machine.Pin) Device {
|
||||
return Device{
|
||||
latch: latch,
|
||||
clk: clk,
|
||||
out: out,
|
||||
Pins: make([]ShiftPin, int(numBits)),
|
||||
bits: numBits,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure here just for interface compatibility.
|
||||
func (d *Device) Configure() {
|
||||
d.latch.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.clk.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.out.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
for i := 0; i < int(d.bits); i++ {
|
||||
d.Pins[i] = d.GetShiftPin(i)
|
||||
}
|
||||
}
|
||||
|
||||
// GetShiftPin returns an ShiftPin for a specific input.
|
||||
func (d *Device) GetShiftPin(input int) ShiftPin {
|
||||
return ShiftPin{pin: machine.Pin(input), d: d}
|
||||
}
|
||||
|
||||
// Read8Input reads the 8 inputs and return an uint8
|
||||
func (d *Device) Read8Input() (uint8, error) {
|
||||
if d.bits != EIGHT_BITS {
|
||||
return 0, errors.New("wrong amount of registers")
|
||||
}
|
||||
return uint8(d.readInput(EIGHT_BITS)), nil
|
||||
}
|
||||
|
||||
// Read16Input reads the 16 inputs and return an uint16
|
||||
func (d *Device) Read16Input() (uint16, error) {
|
||||
if d.bits != SIXTEEN_BITS {
|
||||
return 0, errors.New("wrong amount of registers")
|
||||
}
|
||||
return uint16(d.readInput(SIXTEEN_BITS)), nil
|
||||
}
|
||||
|
||||
// Read32Input reads the 32 inputs and return an uint32
|
||||
func (d *Device) Read32Input() (uint32, error) {
|
||||
if d.bits != THIRTYTWO_BITS {
|
||||
return 0, errors.New("wrong amount of registers")
|
||||
}
|
||||
return d.readInput(THIRTYTWO_BITS), nil
|
||||
}
|
||||
|
||||
// Get the current reading for a specific ShiftPin.
|
||||
func (p ShiftPin) Get() bool {
|
||||
return (p.d.readInput(p.d.bits) & (1 << int(p.pin))) > 0
|
||||
}
|
||||
|
||||
// Configure here just for interface compatibility.
|
||||
func (p ShiftPin) Configure() {
|
||||
}
|
||||
|
||||
// readInput reads howMany bits from the shift register
|
||||
func (d *Device) readInput(howMany NumberBit) uint32 {
|
||||
d.latch.High()
|
||||
var data uint32
|
||||
for i := howMany - 1; i >= 0; i-- {
|
||||
d.clk.Low()
|
||||
if d.out.Get() {
|
||||
data |= 1 << i
|
||||
}
|
||||
d.clk.High()
|
||||
}
|
||||
d.latch.Low()
|
||||
return data
|
||||
}
|
||||
+1
-1
@@ -219,7 +219,7 @@ func (d *Device) GetPixel(x int16, y int16) bool {
|
||||
func (d *Device) SetBuffer(buffer []byte) error {
|
||||
if int16(len(buffer)) != d.bufferSize {
|
||||
//return ErrBuffer
|
||||
return errors.New("Wrong size buffer")
|
||||
return errors.New("wrong size buffer")
|
||||
}
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
d.buffer[i] = buffer[i]
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package ssd1331
|
||||
|
||||
// Registers
|
||||
const (
|
||||
DRAWLINE = 0x21
|
||||
DRAWRECT = 0x22
|
||||
FILL = 0x26
|
||||
SETCOLUMN = 0x15
|
||||
SETROW = 0x75
|
||||
CONTRASTA = 0x81
|
||||
CONTRASTB = 0x82
|
||||
CONTRASTC = 0x83
|
||||
MASTERCURRENT = 0x87
|
||||
SETREMAP = 0xA0
|
||||
STARTLINE = 0xA1
|
||||
DISPLAYOFFSET = 0xA2
|
||||
NORMALDISPLAY = 0xA4
|
||||
DISPLAYALLON = 0xA5
|
||||
DISPLAYALLOFF = 0xA6
|
||||
INVERTDISPLAY = 0xA7
|
||||
SETMULTIPLEX = 0xA8
|
||||
SETMASTER = 0xAD
|
||||
DISPLAYOFF = 0xAE
|
||||
DISPLAYON = 0xAF
|
||||
POWERMODE = 0xB0
|
||||
PRECHARGE = 0xB1
|
||||
CLOCKDIV = 0xB3
|
||||
PRECHARGEA = 0x8A
|
||||
PRECHARGEB = 0x8B
|
||||
PRECHARGEC = 0x8C
|
||||
PRECHARGELEVEL = 0xBB
|
||||
VCOMH = 0xBE
|
||||
)
|
||||
@@ -0,0 +1,273 @@
|
||||
// Package ssd1331 implements a driver for the SSD1331 TFT color displays.
|
||||
//
|
||||
// Datasheet: https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/
|
||||
//
|
||||
package ssd1331 // import "tinygo.org/x/drivers/ssd1331"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Model uint8
|
||||
type Rotation uint8
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
width int16
|
||||
height int16
|
||||
batchLength int16
|
||||
isBGR bool
|
||||
batchData []uint8
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
type Config struct {
|
||||
Width int16
|
||||
Height int16
|
||||
}
|
||||
|
||||
// New creates a new SSD1331 connection. The SPI wire must already be configured.
|
||||
func New(bus machine.SPI, resetPin, dcPin, csPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
return Device{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure initializes the display with default configuration
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
if cfg.Width != 0 {
|
||||
d.width = cfg.Width
|
||||
} else {
|
||||
d.width = 96
|
||||
}
|
||||
if cfg.Height != 0 {
|
||||
d.height = cfg.Height
|
||||
} else {
|
||||
d.height = 64
|
||||
}
|
||||
|
||||
d.batchLength = d.width
|
||||
if d.height > d.width {
|
||||
d.batchLength = d.height
|
||||
}
|
||||
d.batchLength += d.batchLength & 1
|
||||
d.batchData = make([]uint8, d.batchLength*2)
|
||||
|
||||
// reset the device
|
||||
d.resetPin.High()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
d.resetPin.Low()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
d.resetPin.High()
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// Initialization
|
||||
d.Command(DISPLAYOFF)
|
||||
d.Command(SETREMAP)
|
||||
d.Command(0x72) // RGB
|
||||
//d.Command(0x76) // BGR
|
||||
d.Command(STARTLINE)
|
||||
d.Command(0x0)
|
||||
d.Command(DISPLAYOFFSET)
|
||||
d.Command(0x0)
|
||||
d.Command(NORMALDISPLAY)
|
||||
d.Command(SETMULTIPLEX)
|
||||
d.Command(0x3F)
|
||||
d.Command(SETMASTER)
|
||||
d.Command(0x8E)
|
||||
d.Command(POWERMODE)
|
||||
d.Command(0x0B)
|
||||
d.Command(PRECHARGE)
|
||||
d.Command(0x31)
|
||||
d.Command(CLOCKDIV)
|
||||
d.Command(0xF0)
|
||||
d.Command(PRECHARGEA)
|
||||
d.Command(0x64)
|
||||
d.Command(PRECHARGEB)
|
||||
d.Command(0x78)
|
||||
d.Command(PRECHARGEC)
|
||||
d.Command(0x64)
|
||||
d.Command(PRECHARGELEVEL)
|
||||
d.Command(0x3A)
|
||||
d.Command(VCOMH)
|
||||
d.Command(0x3E)
|
||||
d.Command(MASTERCURRENT)
|
||||
d.Command(0x06)
|
||||
d.Command(CONTRASTA)
|
||||
d.Command(0x91)
|
||||
d.Command(CONTRASTB)
|
||||
d.Command(0x50)
|
||||
d.Command(CONTRASTC)
|
||||
d.Command(0x7D)
|
||||
d.Command(DISPLAYON)
|
||||
}
|
||||
|
||||
// Display does nothing, there's no buffer as it might be too big for some boards
|
||||
func (d *Device) Display() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPixel sets a pixel in the screen
|
||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
if x < 0 || y < 0 || x >= d.width || y >= d.height {
|
||||
return
|
||||
}
|
||||
d.FillRectangle(x, y, 1, 1, c)
|
||||
}
|
||||
|
||||
// setWindow prepares the screen to be modified at a given rectangle
|
||||
func (d *Device) setWindow(x, y, w, h int16) {
|
||||
/*d.Tx([]uint8{SETCOLUMN}, true)
|
||||
d.Tx([]uint8{uint8(x), uint8(x + w - 1)}, false)
|
||||
d.Tx([]uint8{SETROW}, true)
|
||||
d.Tx([]uint8{uint8(y), uint8(y + h - 1)}, false)*/
|
||||
d.Command(SETCOLUMN)
|
||||
d.Command(uint8(x))
|
||||
d.Command(uint8(x + w - 1))
|
||||
d.Command(SETROW)
|
||||
d.Command(uint8(y))
|
||||
d.Command(uint8(y + h - 1))
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a given coordinates with a color
|
||||
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
|
||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||
x >= d.width || (x+width) > d.width || y >= d.height || (y+height) > d.height {
|
||||
return errors.New("rectangle coordinates outside display area")
|
||||
}
|
||||
d.setWindow(x, y, width, height)
|
||||
c565 := RGBATo565(c)
|
||||
c1 := uint8(c565 >> 8)
|
||||
c2 := uint8(c565)
|
||||
|
||||
var i int16
|
||||
for i = 0; i < d.batchLength; i++ {
|
||||
d.batchData[i*2] = c1
|
||||
d.batchData[i*2+1] = c2
|
||||
}
|
||||
i = width * height
|
||||
for i > 0 {
|
||||
if i >= d.batchLength {
|
||||
d.Tx(d.batchData, false)
|
||||
} else {
|
||||
d.Tx(d.batchData[:i*2], false)
|
||||
}
|
||||
i -= d.batchLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a given coordinates with a buffer
|
||||
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
|
||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||
x >= d.width || (x+width) > d.width || y >= d.height || (y+height) > d.height {
|
||||
return errors.New("rectangle coordinates outside display area")
|
||||
}
|
||||
k := width * height
|
||||
l := int16(len(buffer))
|
||||
if k != l {
|
||||
return errors.New("buffer length does not match with rectangle size")
|
||||
}
|
||||
|
||||
d.setWindow(x, y, width, height)
|
||||
|
||||
offset := int16(0)
|
||||
for k > 0 {
|
||||
for i := int16(0); i < d.batchLength; i++ {
|
||||
if offset+i < l {
|
||||
c565 := RGBATo565(buffer[offset+i])
|
||||
c1 := uint8(c565 >> 8)
|
||||
c2 := uint8(c565)
|
||||
d.batchData[i*2] = c1
|
||||
d.batchData[i*2+1] = c2
|
||||
}
|
||||
}
|
||||
if k >= d.batchLength {
|
||||
d.Tx(d.batchData, false)
|
||||
} else {
|
||||
d.Tx(d.batchData[:k*2], false)
|
||||
}
|
||||
k -= d.batchLength
|
||||
offset += d.batchLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DrawFastVLine draws a vertical line faster than using SetPixel
|
||||
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
|
||||
if y0 > y1 {
|
||||
y0, y1 = y1, y0
|
||||
}
|
||||
d.FillRectangle(x, y0, 1, y1-y0+1, c)
|
||||
}
|
||||
|
||||
// DrawFastHLine draws a horizontal line faster than using SetPixel
|
||||
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
|
||||
if x0 > x1 {
|
||||
x0, x1 = x1, x0
|
||||
}
|
||||
d.FillRectangle(x0, y, x1-x0+1, y, c)
|
||||
}
|
||||
|
||||
// FillScreen fills the screen with a given color
|
||||
func (d *Device) FillScreen(c color.RGBA) {
|
||||
d.FillRectangle(0, 0, d.width, d.height, c)
|
||||
}
|
||||
|
||||
// SetContrast sets the three contrast values (A, B & C)
|
||||
func (d *Device) SetContrast(contrastA, contrastB, contrastC uint8) {
|
||||
d.Command(CONTRASTA)
|
||||
d.Command(contrastA)
|
||||
d.Command(CONTRASTB)
|
||||
d.Command(contrastB)
|
||||
d.Command(CONTRASTC)
|
||||
d.Command(contrastC)
|
||||
}
|
||||
|
||||
// Command sends a command to the display
|
||||
func (d *Device) Command(command uint8) {
|
||||
d.Tx([]byte{command}, true)
|
||||
}
|
||||
|
||||
// Command sends a data to the display
|
||||
func (d *Device) Data(data uint8) {
|
||||
d.Tx([]byte{data}, false)
|
||||
}
|
||||
|
||||
// Tx sends data to the display
|
||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
||||
d.dcPin.Set(!isCommand)
|
||||
d.bus.Tx(data, nil)
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
return d.width, d.height
|
||||
}
|
||||
|
||||
// IsBGR changes the color mode (RGB/BGR)
|
||||
func (d *Device) IsBGR(bgr bool) {
|
||||
d.isBGR = bgr
|
||||
}
|
||||
|
||||
// RGBATo565 converts a color.RGBA to uint16 used in the display
|
||||
func RGBATo565(c color.RGBA) uint16 {
|
||||
r, g, b, _ := c.RGBA()
|
||||
return uint16((r & 0xF800) +
|
||||
((g & 0xFC00) >> 5) +
|
||||
((b & 0xF800) >> 11))
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# ST7735 driver
|
||||
|
||||
There are multiple devices using the ST7735 chip, and there are multiple versions ST7735B, ST7735R & ST7735S. Two apparently identical displays might have different configurations. The most common issues are:
|
||||
|
||||
* Colors are inverted (black is white and viceversa), invert the colors with display.InvertColors(true)
|
||||
* Colors are not right (red is blue and viceversa, but green is ok), some displays uses BRG instead of RGB for defining colors, change the mode with display.IsBGR(true)
|
||||
* There is noise/snow/confetti in the screen, probably rows and columns offsets are wrong, configure them with st7735.Config{RowOffset:XX, ColumnOffset:YY}
|
||||
|
||||
If nothing of the above works, your device may need a different boot-up process.
|
||||
@@ -0,0 +1,59 @@
|
||||
package st7735
|
||||
|
||||
// Registers
|
||||
const (
|
||||
NOP = 0x00
|
||||
SWRESET = 0x01
|
||||
RDDID = 0x04
|
||||
RDDST = 0x09
|
||||
SLPIN = 0x10
|
||||
SLPOUT = 0x11
|
||||
PTLON = 0x12
|
||||
NORON = 0x13
|
||||
INVOFF = 0x20
|
||||
INVON = 0x21
|
||||
DISPOFF = 0x28
|
||||
DISPON = 0x29
|
||||
CASET = 0x2A
|
||||
RASET = 0x2B
|
||||
RAMWR = 0x2C
|
||||
RAMRD = 0x2E
|
||||
PTLAR = 0x30
|
||||
COLMOD = 0x3A
|
||||
MADCTL = 0x36
|
||||
MADCTL_MY = 0x80
|
||||
MADCTL_MX = 0x40
|
||||
MADCTL_MV = 0x20
|
||||
MADCTL_ML = 0x10
|
||||
MADCTL_RGB = 0x00
|
||||
MADCTL_BGR = 0x08
|
||||
MADCTL_MH = 0x04
|
||||
RDID1 = 0xDA
|
||||
RDID2 = 0xDB
|
||||
RDID3 = 0xDC
|
||||
RDID4 = 0xDD
|
||||
FRMCTR1 = 0xB1
|
||||
FRMCTR2 = 0xB2
|
||||
FRMCTR3 = 0xB3
|
||||
INVCTR = 0xB4
|
||||
DISSET5 = 0xB6
|
||||
PWCTR1 = 0xC0
|
||||
PWCTR2 = 0xC1
|
||||
PWCTR3 = 0xC2
|
||||
PWCTR4 = 0xC3
|
||||
PWCTR5 = 0xC4
|
||||
VMCTR1 = 0xC5
|
||||
PWCTR6 = 0xFC
|
||||
GMCTRP1 = 0xE0
|
||||
GMCTRN1 = 0xE1
|
||||
VSCRDEF = 0x33
|
||||
VSCRSADD = 0x37
|
||||
|
||||
GREENTAB Model = 0
|
||||
MINI80x160 Model = 1
|
||||
|
||||
NO_ROTATION Rotation = 0
|
||||
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||
ROTATION_180 Rotation = 2
|
||||
ROTATION_270 Rotation = 3
|
||||
)
|
||||
@@ -0,0 +1,415 @@
|
||||
// Package st7735 implements a driver for the ST7735 TFT displays, it comes in various screen sizes.
|
||||
//
|
||||
// Datasheet: https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/
|
||||
//
|
||||
package st7735 // import "tinygo.org/x/drivers/st7735"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Model uint8
|
||||
type Rotation uint8
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
blPin machine.Pin
|
||||
width int16
|
||||
height int16
|
||||
columnOffset int16
|
||||
rowOffset int16
|
||||
rotation Rotation
|
||||
batchLength int16
|
||||
model Model
|
||||
isBGR bool
|
||||
batchData []uint8
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
type Config struct {
|
||||
Width int16
|
||||
Height int16
|
||||
Rotation Rotation
|
||||
Model Model
|
||||
RowOffset int16
|
||||
ColumnOffset int16
|
||||
}
|
||||
|
||||
// New creates a new ST7735 connection. The SPI wire must already be configured.
|
||||
func New(bus machine.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
return Device{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
blPin: blPin,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure initializes the display with default configuration
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
d.model = cfg.Model
|
||||
if cfg.Width != 0 {
|
||||
d.width = cfg.Width
|
||||
} else {
|
||||
if d.model == MINI80x160 {
|
||||
d.width = 80
|
||||
} else {
|
||||
d.width = 128
|
||||
}
|
||||
}
|
||||
if cfg.Height != 0 {
|
||||
d.height = cfg.Height
|
||||
} else {
|
||||
d.height = 160
|
||||
}
|
||||
d.rotation = cfg.Rotation
|
||||
d.rowOffset = cfg.RowOffset
|
||||
d.columnOffset = cfg.ColumnOffset
|
||||
|
||||
d.batchLength = d.width
|
||||
if d.height > d.width {
|
||||
d.batchLength = d.height
|
||||
}
|
||||
d.batchLength += d.batchLength & 1
|
||||
d.batchData = make([]uint8, d.batchLength*2)
|
||||
|
||||
// reset the device
|
||||
d.resetPin.High()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
d.resetPin.Low()
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
d.resetPin.High()
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
|
||||
// Common initialization
|
||||
d.Command(SWRESET)
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
d.Command(SLPOUT)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
d.Command(FRMCTR1)
|
||||
d.Data(0x01)
|
||||
d.Data(0x2C)
|
||||
d.Data(0x2D)
|
||||
d.Command(FRMCTR2)
|
||||
d.Data(0x01)
|
||||
d.Data(0x2C)
|
||||
d.Data(0x2D)
|
||||
d.Command(FRMCTR3)
|
||||
d.Data(0x01)
|
||||
d.Data(0x2C)
|
||||
d.Data(0x2D)
|
||||
d.Data(0x01)
|
||||
d.Data(0x2C)
|
||||
d.Data(0x2D)
|
||||
d.Command(INVCTR)
|
||||
d.Data(0x07)
|
||||
d.Command(PWCTR1)
|
||||
d.Data(0xA2)
|
||||
d.Data(0x02)
|
||||
d.Data(0x84)
|
||||
d.Command(PWCTR2)
|
||||
d.Data(0xC5)
|
||||
d.Command(PWCTR3)
|
||||
d.Data(0x0A)
|
||||
d.Data(0x00)
|
||||
d.Command(PWCTR4)
|
||||
d.Data(0x8A)
|
||||
d.Data(0x2A)
|
||||
d.Command(PWCTR5)
|
||||
d.Data(0x8A)
|
||||
d.Data(0xEE)
|
||||
d.Command(VMCTR1)
|
||||
d.Data(0x0E)
|
||||
d.Command(COLMOD)
|
||||
d.Data(0x05)
|
||||
|
||||
if d.model == GREENTAB {
|
||||
d.InvertColors(false)
|
||||
} else if d.model == MINI80x160 {
|
||||
d.isBGR = true
|
||||
d.InvertColors(true)
|
||||
}
|
||||
|
||||
// common color adjustment
|
||||
d.Command(GMCTRP1)
|
||||
d.Data(0x02)
|
||||
d.Data(0x1C)
|
||||
d.Data(0x07)
|
||||
d.Data(0x12)
|
||||
d.Data(0x37)
|
||||
d.Data(0x32)
|
||||
d.Data(0x29)
|
||||
d.Data(0x2D)
|
||||
d.Data(0x29)
|
||||
d.Data(0x25)
|
||||
d.Data(0x2B)
|
||||
d.Data(0x39)
|
||||
d.Data(0x00)
|
||||
d.Data(0x01)
|
||||
d.Data(0x03)
|
||||
d.Data(0x10)
|
||||
d.Command(GMCTRN1)
|
||||
d.Data(0x03)
|
||||
d.Data(0x1D)
|
||||
d.Data(0x07)
|
||||
d.Data(0x06)
|
||||
d.Data(0x2E)
|
||||
d.Data(0x2C)
|
||||
d.Data(0x29)
|
||||
d.Data(0x2D)
|
||||
d.Data(0x2E)
|
||||
d.Data(0x2E)
|
||||
d.Data(0x37)
|
||||
d.Data(0x3F)
|
||||
d.Data(0x00)
|
||||
d.Data(0x00)
|
||||
d.Data(0x02)
|
||||
d.Data(0x10)
|
||||
|
||||
d.Command(NORON)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
d.Command(DISPON)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
if cfg.Model == MINI80x160 {
|
||||
d.Command(MADCTL)
|
||||
d.Data(0xC0)
|
||||
}
|
||||
|
||||
d.SetRotation(d.rotation)
|
||||
|
||||
d.blPin.High()
|
||||
}
|
||||
|
||||
// Display does nothing, there's no buffer as it might be too big for some boards
|
||||
func (d *Device) Display() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPixel sets a pixel in the screen
|
||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
w, h := d.Size()
|
||||
if x < 0 || y < 0 || x >= w || y >= h {
|
||||
return
|
||||
}
|
||||
d.FillRectangle(x, y, 1, 1, c)
|
||||
}
|
||||
|
||||
// setWindow prepares the screen to be modified at a given rectangle
|
||||
func (d *Device) setWindow(x, y, w, h int16) {
|
||||
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
|
||||
x += d.columnOffset
|
||||
y += d.rowOffset
|
||||
} else {
|
||||
x += d.rowOffset
|
||||
y += d.columnOffset
|
||||
}
|
||||
d.Tx([]uint8{CASET}, true)
|
||||
d.Tx([]uint8{uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)}, false)
|
||||
d.Tx([]uint8{RASET}, true)
|
||||
d.Tx([]uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}, false)
|
||||
d.Command(RAMWR)
|
||||
}
|
||||
|
||||
// SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display
|
||||
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
|
||||
d.Command(VSCRDEF)
|
||||
d.Tx([]uint8{
|
||||
uint8(topFixedArea >> 8), uint8(topFixedArea),
|
||||
uint8(d.height - topFixedArea - bottomFixedArea>>8), uint8(d.height - topFixedArea - bottomFixedArea),
|
||||
uint8(bottomFixedArea >> 8), uint8(bottomFixedArea)},
|
||||
false)
|
||||
}
|
||||
|
||||
// SetScroll sets the vertical scroll address of the display.
|
||||
func (d *Device) SetScroll(line int16) {
|
||||
d.Command(VSCRSADD)
|
||||
d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false)
|
||||
}
|
||||
|
||||
// SpotScroll returns the display to its normal state
|
||||
func (d *Device) StopScroll() {
|
||||
d.Command(NORON)
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a 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 ||
|
||||
x >= k || (x+width) > k || y >= i || (y+height) > i {
|
||||
return errors.New("rectangle coordinates outside display area")
|
||||
}
|
||||
d.setWindow(x, y, width, height)
|
||||
c565 := RGBATo565(c)
|
||||
c1 := uint8(c565 >> 8)
|
||||
c2 := uint8(c565)
|
||||
|
||||
for i = 0; i < d.batchLength; i++ {
|
||||
d.batchData[i*2] = c1
|
||||
d.batchData[i*2+1] = c2
|
||||
}
|
||||
i = width * height
|
||||
for i > 0 {
|
||||
if i >= d.batchLength {
|
||||
d.Tx(d.batchData, false)
|
||||
} else {
|
||||
d.Tx(d.batchData[:i*2], false)
|
||||
}
|
||||
i -= d.batchLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a given coordinates with a buffer
|
||||
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
|
||||
k, l := d.Size()
|
||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||
x >= k || (x+width) > k || y >= l || (y+height) > l {
|
||||
return errors.New("rectangle coordinates outside display area")
|
||||
}
|
||||
k = width * height
|
||||
l = int16(len(buffer))
|
||||
if k != l {
|
||||
return errors.New("buffer length does not match with rectangle size")
|
||||
}
|
||||
|
||||
d.setWindow(x, y, width, height)
|
||||
|
||||
offset := int16(0)
|
||||
for k > 0 {
|
||||
for i := int16(0); i < d.batchLength; i++ {
|
||||
if offset+i < l {
|
||||
c565 := RGBATo565(buffer[offset+i])
|
||||
c1 := uint8(c565 >> 8)
|
||||
c2 := uint8(c565)
|
||||
d.batchData[i*2] = c1
|
||||
d.batchData[i*2+1] = c2
|
||||
}
|
||||
}
|
||||
if k >= d.batchLength {
|
||||
d.Tx(d.batchData, false)
|
||||
} else {
|
||||
d.Tx(d.batchData[:k*2], false)
|
||||
}
|
||||
k -= d.batchLength
|
||||
offset += d.batchLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DrawFastVLine draws a vertical line faster than using SetPixel
|
||||
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
|
||||
if y0 > y1 {
|
||||
y0, y1 = y1, y0
|
||||
}
|
||||
d.FillRectangle(x, y0, 1, y1-y0+1, c)
|
||||
}
|
||||
|
||||
// DrawFastHLine draws a horizontal line faster than using SetPixel
|
||||
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
|
||||
if x0 > x1 {
|
||||
x0, x1 = x1, x0
|
||||
}
|
||||
d.FillRectangle(x0, y, x1-x0+1, y, c)
|
||||
}
|
||||
|
||||
// FillScreen fills the screen with a given color
|
||||
func (d *Device) FillScreen(c color.RGBA) {
|
||||
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
|
||||
d.FillRectangle(0, 0, d.width, d.height, c)
|
||||
} else {
|
||||
d.FillRectangle(0, 0, d.height, d.width, c)
|
||||
}
|
||||
}
|
||||
|
||||
// SetRotation changes the rotation of the device (clock-wise)
|
||||
func (d *Device) SetRotation(rotation Rotation) {
|
||||
madctl := uint8(0)
|
||||
switch rotation % 4 {
|
||||
case 0:
|
||||
madctl = MADCTL_MX | MADCTL_MY
|
||||
break
|
||||
case 1:
|
||||
madctl = MADCTL_MY | MADCTL_MV
|
||||
break
|
||||
case 2:
|
||||
break
|
||||
case 3:
|
||||
madctl = MADCTL_MX | MADCTL_MV
|
||||
break
|
||||
}
|
||||
if d.isBGR {
|
||||
madctl |= MADCTL_BGR
|
||||
}
|
||||
d.Command(MADCTL)
|
||||
d.Data(madctl)
|
||||
|
||||
}
|
||||
|
||||
// Command sends a command to the display
|
||||
func (d *Device) Command(command uint8) {
|
||||
d.Tx([]byte{command}, true)
|
||||
}
|
||||
|
||||
// Command sends a data to the display
|
||||
func (d *Device) Data(data uint8) {
|
||||
d.Tx([]byte{data}, false)
|
||||
}
|
||||
|
||||
// Tx sends data to the display
|
||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
||||
d.dcPin.Set(!isCommand)
|
||||
d.bus.Tx(data, nil)
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
|
||||
return d.width, d.height
|
||||
}
|
||||
return d.height, d.width
|
||||
}
|
||||
|
||||
// EnableBacklight enables or disables the backlight
|
||||
func (d *Device) EnableBacklight(enable bool) {
|
||||
if enable {
|
||||
d.blPin.High()
|
||||
} else {
|
||||
d.blPin.Low()
|
||||
}
|
||||
}
|
||||
|
||||
// InverColors inverts the colors of the screen
|
||||
func (d *Device) InvertColors(invert bool) {
|
||||
if invert {
|
||||
d.Command(INVON)
|
||||
} else {
|
||||
d.Command(INVOFF)
|
||||
}
|
||||
}
|
||||
|
||||
// IsBGR changes the color mode (RGB/BGR)
|
||||
func (d *Device) IsBGR(bgr bool) {
|
||||
d.isBGR = bgr
|
||||
}
|
||||
|
||||
// RGBATo565 converts a color.RGBA to uint16 used in the display
|
||||
func RGBATo565(c color.RGBA) uint16 {
|
||||
r, g, b, _ := c.RGBA()
|
||||
return uint16((r & 0xF800) +
|
||||
((g & 0xFC00) >> 5) +
|
||||
((b & 0xF800) >> 11))
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package st7789
|
||||
|
||||
// Registers
|
||||
const (
|
||||
NOP = 0x00
|
||||
SWRESET = 0x01
|
||||
RDDID = 0x04
|
||||
RDDST = 0x09
|
||||
SLPIN = 0x10
|
||||
SLPOUT = 0x11
|
||||
PTLON = 0x12
|
||||
NORON = 0x13
|
||||
INVOFF = 0x20
|
||||
INVON = 0x21
|
||||
DISPOFF = 0x28
|
||||
DISPON = 0x29
|
||||
CASET = 0x2A
|
||||
RASET = 0x2B
|
||||
RAMWR = 0x2C
|
||||
RAMRD = 0x2E
|
||||
PTLAR = 0x30
|
||||
COLMOD = 0x3A
|
||||
MADCTL = 0x36
|
||||
MADCTL_MY = 0x80
|
||||
MADCTL_MX = 0x40
|
||||
MADCTL_MV = 0x20
|
||||
MADCTL_ML = 0x10
|
||||
MADCTL_RGB = 0x00
|
||||
MADCTL_BGR = 0x08
|
||||
MADCTL_MH = 0x04
|
||||
RDID1 = 0xDA
|
||||
RDID2 = 0xDB
|
||||
RDID3 = 0xDC
|
||||
RDID4 = 0xDD
|
||||
FRMCTR1 = 0xB1
|
||||
FRMCTR2 = 0xB2
|
||||
FRMCTR3 = 0xB3
|
||||
INVCTR = 0xB4
|
||||
DISSET5 = 0xB6
|
||||
PWCTR1 = 0xC0
|
||||
PWCTR2 = 0xC1
|
||||
PWCTR3 = 0xC2
|
||||
PWCTR4 = 0xC3
|
||||
PWCTR5 = 0xC4
|
||||
VMCTR1 = 0xC5
|
||||
PWCTR6 = 0xFC
|
||||
GMCTRP1 = 0xE0
|
||||
GMCTRN1 = 0xE1
|
||||
|
||||
NO_ROTATION Rotation = 0
|
||||
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||
ROTATION_180 Rotation = 2
|
||||
ROTATION_270 Rotation = 3
|
||||
)
|
||||
@@ -0,0 +1,312 @@
|
||||
// Package st7789 implements a driver for the ST7789 TFT displays, it comes in various screen sizes.
|
||||
//
|
||||
// Datasheet: https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf
|
||||
//
|
||||
package st7789 // import "tinygo.org/x/drivers/st7789"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Rotation uint8
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus machine.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
blPin machine.Pin
|
||||
width int16
|
||||
height int16
|
||||
columnOffsetCfg int16
|
||||
rowOffsetCfg int16
|
||||
columnOffset int16
|
||||
rowOffset int16
|
||||
rotation Rotation
|
||||
batchLength int32
|
||||
isBGR bool
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
type Config struct {
|
||||
Width int16
|
||||
Height int16
|
||||
Rotation Rotation
|
||||
RowOffset int16
|
||||
ColumnOffset int16
|
||||
}
|
||||
|
||||
// New creates a new ST7789 connection. The SPI wire must already be configured.
|
||||
func New(bus machine.SPI, resetPin, dcPin, blPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
return Device{
|
||||
bus: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
blPin: blPin,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure initializes the display with default configuration
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
if cfg.Width != 0 {
|
||||
d.width = cfg.Width
|
||||
} else {
|
||||
d.width = 240
|
||||
}
|
||||
if cfg.Height != 0 {
|
||||
d.height = cfg.Height
|
||||
} else {
|
||||
d.height = 240
|
||||
}
|
||||
d.rotation = cfg.Rotation
|
||||
d.rowOffsetCfg = cfg.RowOffset
|
||||
d.columnOffsetCfg = cfg.ColumnOffset
|
||||
|
||||
d.batchLength = int32(d.width)
|
||||
if d.height > d.width {
|
||||
d.batchLength = int32(d.height)
|
||||
}
|
||||
d.batchLength += d.batchLength & 1
|
||||
|
||||
// reset the device
|
||||
d.resetPin.High()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
d.resetPin.Low()
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
d.resetPin.High()
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
|
||||
// Common initialization
|
||||
d.Command(SWRESET)
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
d.Command(SLPOUT)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
d.Command(COLMOD)
|
||||
d.Data(0x55)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
d.SetRotation(d.rotation)
|
||||
d.InvertColors(true)
|
||||
|
||||
d.Command(NORON)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
d.Command(DISPON)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
d.blPin.High()
|
||||
}
|
||||
|
||||
// Display does nothing, there's no buffer as it might be too big for some boards
|
||||
func (d *Device) Display() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPixel sets a pixel in the screen
|
||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
if x < 0 || y < 0 ||
|
||||
(((d.rotation == NO_ROTATION || d.rotation == ROTATION_180) && (x >= d.width || y >= d.height)) ||
|
||||
((d.rotation == ROTATION_90 || d.rotation == ROTATION_270) && (x >= d.height || y >= d.width))) {
|
||||
return
|
||||
}
|
||||
d.FillRectangle(x, y, 1, 1, c)
|
||||
}
|
||||
|
||||
// setWindow prepares the screen to be modified at a given rectangle
|
||||
func (d *Device) setWindow(x, y, w, h int16) {
|
||||
x += d.columnOffset
|
||||
y += d.rowOffset
|
||||
d.Tx([]uint8{CASET}, true)
|
||||
d.Tx([]uint8{uint8(x << 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)}, false)
|
||||
d.Tx([]uint8{RASET}, true)
|
||||
d.Tx([]uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}, false)
|
||||
d.Command(RAMWR)
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a 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 ||
|
||||
x >= k || (x+width) > k || y >= i || (y+height) > i {
|
||||
return errors.New("rectangle coordinates outside display area")
|
||||
}
|
||||
d.setWindow(x, y, width, height)
|
||||
c565 := RGBATo565(c)
|
||||
c1 := uint8(c565 >> 8)
|
||||
c2 := uint8(c565)
|
||||
|
||||
data := make([]uint8, d.batchLength*2)
|
||||
for i := int32(0); i < d.batchLength; i++ {
|
||||
data[i*2] = c1
|
||||
data[i*2+1] = c2
|
||||
}
|
||||
j := int32(width) * int32(height)
|
||||
for j > 0 {
|
||||
if j >= d.batchLength {
|
||||
d.Tx(data, false)
|
||||
} else {
|
||||
d.Tx(data[:j*2], false)
|
||||
}
|
||||
j -= d.batchLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillRectangle fills a rectangle at a given coordinates with a buffer
|
||||
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
|
||||
i, j := d.Size()
|
||||
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
|
||||
x >= i || (x+width) > i || y >= j || (y+height) > j {
|
||||
return errors.New("rectangle coordinates outside display area")
|
||||
}
|
||||
if int32(width)*int32(height) != int32(len(buffer)) {
|
||||
return errors.New("buffer length does not match with rectangle size")
|
||||
}
|
||||
d.setWindow(x, y, width, height)
|
||||
|
||||
k := int32(width) * int32(height)
|
||||
data := make([]uint8, d.batchLength*2)
|
||||
offset := int32(0)
|
||||
for k > 0 {
|
||||
for i := int32(0); i < d.batchLength; i++ {
|
||||
if offset+i < int32(len(buffer)) {
|
||||
c565 := RGBATo565(buffer[offset+i])
|
||||
c1 := uint8(c565 >> 8)
|
||||
c2 := uint8(c565)
|
||||
data[i*2] = c1
|
||||
data[i*2+1] = c2
|
||||
}
|
||||
}
|
||||
if k >= d.batchLength {
|
||||
d.Tx(data, false)
|
||||
} else {
|
||||
d.Tx(data[:k*2], false)
|
||||
}
|
||||
k -= d.batchLength
|
||||
offset += d.batchLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DrawFastVLine draws a vertical line faster than using SetPixel
|
||||
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
|
||||
if y0 > y1 {
|
||||
y0, y1 = y1, y0
|
||||
}
|
||||
d.FillRectangle(x, y0, 1, y1-y0+1, c)
|
||||
}
|
||||
|
||||
// DrawFastHLine draws a horizontal line faster than using SetPixel
|
||||
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
|
||||
if x0 > x1 {
|
||||
x0, x1 = x1, x0
|
||||
}
|
||||
d.FillRectangle(x0, y, x1-x0+1, y, c)
|
||||
}
|
||||
|
||||
// FillScreen fills the screen with a given color
|
||||
func (d *Device) FillScreen(c color.RGBA) {
|
||||
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
|
||||
d.FillRectangle(0, 0, d.width, d.height, c)
|
||||
} else {
|
||||
d.FillRectangle(0, 0, d.height, d.width, c)
|
||||
}
|
||||
}
|
||||
|
||||
// SetRotation changes the rotation of the device (clock-wise)
|
||||
func (d *Device) SetRotation(rotation Rotation) {
|
||||
madctl := uint8(0)
|
||||
switch rotation % 4 {
|
||||
case 0:
|
||||
madctl = MADCTL_MX | MADCTL_MY
|
||||
d.rowOffset = d.rowOffsetCfg
|
||||
d.columnOffset = d.columnOffsetCfg
|
||||
break
|
||||
case 1:
|
||||
madctl = MADCTL_MY | MADCTL_MV
|
||||
d.rowOffset = d.columnOffsetCfg
|
||||
d.columnOffset = d.rowOffsetCfg
|
||||
break
|
||||
case 2:
|
||||
d.rowOffset = 0
|
||||
d.columnOffset = 0
|
||||
break
|
||||
case 3:
|
||||
madctl = MADCTL_MX | MADCTL_MV
|
||||
d.rowOffset = 0
|
||||
d.columnOffset = 0
|
||||
break
|
||||
}
|
||||
if d.isBGR {
|
||||
madctl |= MADCTL_BGR
|
||||
}
|
||||
d.Command(MADCTL)
|
||||
d.Data(madctl)
|
||||
|
||||
}
|
||||
|
||||
// Command sends a command to the display
|
||||
func (d *Device) Command(command uint8) {
|
||||
d.Tx([]byte{command}, true)
|
||||
}
|
||||
|
||||
// Command sends a data to the display
|
||||
func (d *Device) Data(data uint8) {
|
||||
d.Tx([]byte{data}, false)
|
||||
}
|
||||
|
||||
// Tx sends data to the display
|
||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
||||
if isCommand {
|
||||
d.dcPin.Low()
|
||||
d.bus.Tx(data, nil)
|
||||
} else {
|
||||
d.dcPin.High()
|
||||
d.bus.Tx(data, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
|
||||
return d.width, d.height
|
||||
}
|
||||
return d.height, d.width
|
||||
}
|
||||
|
||||
// EnableBacklight enables or disables the backlight
|
||||
func (d *Device) EnableBacklight(enable bool) {
|
||||
if enable {
|
||||
d.blPin.High()
|
||||
} else {
|
||||
d.blPin.Low()
|
||||
}
|
||||
}
|
||||
|
||||
// InverColors inverts the colors of the screen
|
||||
func (d *Device) InvertColors(invert bool) {
|
||||
if invert {
|
||||
d.Command(INVON)
|
||||
} else {
|
||||
d.Command(INVOFF)
|
||||
}
|
||||
}
|
||||
|
||||
// IsBGR changes the color mode (RGB/BGR)
|
||||
func (d *Device) IsBGR(bgr bool) {
|
||||
d.isBGR = bgr
|
||||
}
|
||||
|
||||
// RGBATo565 converts a color.RGBA to uint16 used in the display
|
||||
func RGBATo565(c color.RGBA) uint16 {
|
||||
r, g, b, _ := c.RGBA()
|
||||
return uint16((r & 0xF800) +
|
||||
((g & 0xFC00) >> 5) +
|
||||
((b & 0xF800) >> 11))
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package touch
|
||||
|
||||
// Pointer is a device that is capable of reading a single touch point
|
||||
type Pointer interface {
|
||||
ReadTouchPoint() Point
|
||||
}
|
||||
|
||||
// Point represents the result of reading a single touch point from a screen.
|
||||
// X and Y are the horizontal and vertical coordinates of the touch, while Z
|
||||
// represents the touch pressure. In general, client code will want to inspect
|
||||
// the value of Z to see if it is above some threshold to determine if a touch
|
||||
// is detected at all.
|
||||
type Point struct {
|
||||
X int
|
||||
Y int
|
||||
Z int
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package resistive
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/touch"
|
||||
)
|
||||
|
||||
// FourWire represents a resistive touchscreen with a four-wire interface as
|
||||
// described in http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf
|
||||
type FourWire struct {
|
||||
yp machine.ADC
|
||||
ym machine.ADC
|
||||
xp machine.ADC
|
||||
xm machine.ADC
|
||||
|
||||
readSamples int
|
||||
}
|
||||
|
||||
// FourWireConfig is passed to the Configure method. All of the pins must be
|
||||
// specified for this to be a valid configuration. ReadSamples is optional, and
|
||||
// if not set with default to 2.
|
||||
type FourWireConfig struct {
|
||||
|
||||
// Y+ pin, must be capable of analog reads
|
||||
YP machine.Pin
|
||||
|
||||
// Y- pin, must be capable of analog reads
|
||||
YM machine.Pin
|
||||
|
||||
// X+ pin, must be capable of analog reads
|
||||
XP machine.Pin
|
||||
|
||||
// X- pin, must be capable of analog reads
|
||||
XM machine.Pin
|
||||
|
||||
// If set, each call to ReadTouchPoint() will sample the X, Y, and Z values
|
||||
// and average them. This can help smooth out spurious readings, for example
|
||||
// ones that result from the capacitance of a TFT under the touchscreen
|
||||
ReadSamples int
|
||||
}
|
||||
|
||||
// Configure should be called once before starting to read the device
|
||||
func (res *FourWire) Configure(config *FourWireConfig) error {
|
||||
|
||||
res.yp = machine.ADC{Pin: config.YP}
|
||||
res.ym = machine.ADC{Pin: config.YM}
|
||||
res.xp = machine.ADC{Pin: config.XP}
|
||||
res.xm = machine.ADC{Pin: config.XM}
|
||||
|
||||
if config.ReadSamples < 1 {
|
||||
res.readSamples = 2
|
||||
} else {
|
||||
res.readSamples = config.ReadSamples
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadTouchPoint reads a single touch.Point from the device. If the device
|
||||
// was configured with ReadSamples > 1, each value will be sampled that many
|
||||
// times and averaged to smooth over spurious results of the analog reads.
|
||||
func (res *FourWire) ReadTouchPoint() (p touch.Point) {
|
||||
p.X = int(sample(res.ReadX, res.readSamples))
|
||||
p.Y = int(sample(res.ReadY, res.readSamples))
|
||||
p.Z = int(sample(res.ReadZ, res.readSamples))
|
||||
return
|
||||
}
|
||||
|
||||
// sample the results of the provided function and average the results
|
||||
func sample(fn func() uint16, numSamples int) (v uint16) {
|
||||
sum := 0
|
||||
for n := 0; n < numSamples; n++ {
|
||||
sum += int(fn())
|
||||
}
|
||||
return uint16(sum / numSamples)
|
||||
}
|
||||
|
||||
// ReadX reads the "raw" X-value on a 16-bit scale without multiple sampling
|
||||
func (res *FourWire) ReadX() uint16 {
|
||||
res.ym.Pin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
|
||||
|
||||
res.xp.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
res.xp.Pin.High()
|
||||
|
||||
res.xm.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
res.xm.Pin.Low()
|
||||
|
||||
res.yp.Configure()
|
||||
|
||||
return 0xFFFF - res.yp.Get()
|
||||
}
|
||||
|
||||
// ReadY reads the "raw" Y-value on a 16-bit scale without multiple sampling
|
||||
func (res *FourWire) ReadY() uint16 {
|
||||
res.xm.Pin.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
|
||||
|
||||
res.yp.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
res.yp.Pin.High()
|
||||
|
||||
res.ym.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
res.ym.Pin.Low()
|
||||
|
||||
res.xp.Configure()
|
||||
|
||||
return 0xFFFF - res.xp.Get()
|
||||
}
|
||||
|
||||
// ReadZ reads the "raw" Z-value on a 16-bit scale without multiple sampling
|
||||
func (res *FourWire) ReadZ() uint16 {
|
||||
res.xp.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
res.xp.Pin.Low()
|
||||
|
||||
res.ym.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
res.ym.Pin.High()
|
||||
|
||||
res.xm.Configure()
|
||||
res.yp.Configure()
|
||||
|
||||
z1 := res.xm.Get()
|
||||
z2 := res.yp.Get()
|
||||
|
||||
return 0xFFFF - (z2 - z1)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package veml6070
|
||||
|
||||
// I2C addresses and other constants
|
||||
|
||||
const (
|
||||
ADDR_L = 0x38 // 7bit address of the VEML6070 (write, read)
|
||||
ADDR_H = 0x39 // 7bit address of the VEML6070 (read)
|
||||
)
|
||||
|
||||
// Some possible values for resistance value (in ohm) of VEML6070 calibration resistor
|
||||
const (
|
||||
RSET_240K = 240000
|
||||
RSET_270K = 270000
|
||||
RSET_300K = 300000
|
||||
RSET_600K = 600000
|
||||
)
|
||||
|
||||
// Possible values for integration time of VEML6070
|
||||
// (internally represents the config register bit mask)
|
||||
const (
|
||||
IT_HALF = 0x00
|
||||
IT_1 = 0x04
|
||||
IT_2 = 0x08
|
||||
IT_4 = 0x0C
|
||||
)
|
||||
|
||||
// Possible values for UVI (UV index) risk level estimations - the VEML6070 can
|
||||
// only estimate UVI risk levels since it can only sense UVA rays but the vendor
|
||||
// tried to come up with some coarse thresholds, from application notes
|
||||
const (
|
||||
UVI_RISK_LOW = iota
|
||||
UVI_RISK_MODERATE
|
||||
UVI_RISK_HIGH
|
||||
UVI_RISK_VERY_HIGH
|
||||
UVI_RISK_EXTREME
|
||||
)
|
||||
|
||||
// Scale factor in milliseconds / ohm to determine refresh time
|
||||
// (aka sampling time) without IT_FACTOR for any given RSET, from datasheet.
|
||||
// Note: 100.0 milliseconds are applicable for RSET=240 kOhm and IT_FACTOR=1
|
||||
const RSET_TO_REFRESHTIME_SCALE = 100.0 / RSET_240K
|
||||
|
||||
// The refresh time in milliseconds for which NORMALIZED_UVA_SENSITIVITY
|
||||
// is applicable to a step count
|
||||
const NORMALIZED_REFRESHTIME = 100.0
|
||||
|
||||
// The UVA sensitivity in mW/(m*m)/step which is applicable to a step count
|
||||
// normalized to the NORMALIZED_REFRESHTIME, from datasheet for RSET=240 kOhm
|
||||
// and IT_FACTOR=1
|
||||
const NORMALIZED_UVA_SENSITIVITY = 50.0
|
||||
|
||||
// Config register
|
||||
|
||||
// Possible values for shutdown
|
||||
const (
|
||||
CONFIG_SD_DISABLE = 0x00
|
||||
CONFIG_SD_ENABLE = 0x01
|
||||
)
|
||||
|
||||
// Enable / disable
|
||||
const (
|
||||
CONFIG_DEFAULTS = 0x02
|
||||
CONFIG_ENABLE = CONFIG_SD_DISABLE | CONFIG_DEFAULTS
|
||||
CONFIG_DISABLE = CONFIG_SD_ENABLE | CONFIG_DEFAULTS
|
||||
)
|
||||
@@ -0,0 +1,140 @@
|
||||
// Package veml6070 provides a driver for the VEML6070 digital UV light sensor
|
||||
// by Vishay.
|
||||
//
|
||||
// Datasheet:
|
||||
// https://www.vishay.com/docs/84277/veml6070.pdf
|
||||
// Application Notes:
|
||||
// https://www.vishay.com/docs/84310/designingveml6070.pdf
|
||||
//
|
||||
package veml6070 // import "tinygo.org/x/drivers/veml6070"
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"machine"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a VEML6070 device.
|
||||
type Device struct {
|
||||
bus machine.I2C
|
||||
AddressLow uint16
|
||||
AddressHigh uint16
|
||||
RSET uint32
|
||||
IT uint8
|
||||
}
|
||||
|
||||
// New creates a new VEML6070 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 machine.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
AddressLow: ADDR_L,
|
||||
AddressHigh: ADDR_H,
|
||||
RSET: RSET_240K,
|
||||
// Note: default to maximum to get as much precision as possible since
|
||||
// raw data values larger than 16 bit can hardly occur with RSET below
|
||||
// 300 kOhm in real world applications. Power saving due to shorter
|
||||
// sampling time might be a reason to reduce this.
|
||||
IT: IT_4,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication
|
||||
func (d *Device) Configure() bool {
|
||||
// save power by shutdown as early as possible, also serves as presence test
|
||||
if err := d.disable(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadUVALightIntensity returns the UVA light intensity (irradiance)
|
||||
// in milli Watt per square meter (mW/(m*m))
|
||||
func (d *Device) ReadUVALightIntensity() (uint32, error) {
|
||||
var err2 error
|
||||
|
||||
if err := d.enable(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// wait two times the refresh time to allow completion of a previous cycle
|
||||
// with old settings (worst case)
|
||||
time.Sleep(time.Duration(d.getRefreshTime()) * 2 * time.Millisecond)
|
||||
|
||||
msb, err2 := d.readData(d.AddressHigh)
|
||||
if err2 != nil {
|
||||
return 0, err2
|
||||
}
|
||||
|
||||
lsb, err2 := d.readData(d.AddressLow)
|
||||
if err2 != nil {
|
||||
return 0, err2
|
||||
}
|
||||
|
||||
if err := d.disable(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
rawData := (uint32(msb) << 8) | uint32(lsb)
|
||||
|
||||
// normalize raw data (step count sampled in d.getRefreshTime()) into the
|
||||
// linearly scaled normalized data (step count sampled in 100ms) for which
|
||||
// we know the UVA sensitivity
|
||||
normalizedData := float32(rawData) * NORMALIZED_REFRESHTIME / d.getRefreshTime()
|
||||
|
||||
// now we can calculate the absolute UVA power detected combining normalized
|
||||
// data with known UVA sensitivity for this data, from datasheet
|
||||
intensity := normalizedData * NORMALIZED_UVA_SENSITIVITY // mW/(m*m)
|
||||
|
||||
return uint32(intensity + 0.5), nil
|
||||
}
|
||||
|
||||
// GetEstimatedRiskLevel returns estimated risk level from comparing UVA light
|
||||
// intensity values in mW/(m*m) with thresholds calculated from application notes
|
||||
func (d *Device) GetEstimatedRiskLevel(intensity uint32) uint8 {
|
||||
if intensity <= 24888 {
|
||||
return UVI_RISK_LOW
|
||||
} else if intensity <= 49800 {
|
||||
return UVI_RISK_MODERATE
|
||||
} else if intensity <= 66400 {
|
||||
return UVI_RISK_HIGH
|
||||
} else if intensity <= 91288 {
|
||||
return UVI_RISK_VERY_HIGH
|
||||
} else {
|
||||
return UVI_RISK_EXTREME
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) disable() error {
|
||||
return d.bus.Tx(uint16(d.AddressLow), []byte{CONFIG_DISABLE}, nil)
|
||||
}
|
||||
|
||||
func (d *Device) enable() error {
|
||||
return d.bus.Tx(uint16(d.AddressLow), []byte{CONFIG_ENABLE | d.IT}, nil)
|
||||
}
|
||||
|
||||
func (d *Device) readData(address uint16) (byte, error) {
|
||||
data := []byte{0}
|
||||
err := machine.I2C0.Tx(address, []byte{}, data)
|
||||
return data[0], err
|
||||
}
|
||||
|
||||
// getRefreshTime returns the refresh time (aka sample time) in milliseconds
|
||||
func (d *Device) getRefreshTime() float32 {
|
||||
var it float32
|
||||
switch d.IT {
|
||||
case IT_HALF:
|
||||
it = 0.5
|
||||
case IT_1:
|
||||
it = 1
|
||||
case IT_2:
|
||||
it = 2
|
||||
case IT_4:
|
||||
it = 4
|
||||
}
|
||||
return float32(d.RSET) * RSET_TO_REFRESHTIME_SCALE * it
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package drivers
|
||||
|
||||
// Version returns a user-readable string showing the version of the drivers package for support purposes.
|
||||
// Update this value before release of new version of software.
|
||||
const Version = "0.10.0"
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Width int16
|
||||
Height int16
|
||||
Width int16 // Width is the display resolution
|
||||
Height int16
|
||||
LogicalWidth int16 // LogicalWidth must be a multiple of 8 and same size or bigger than Width
|
||||
Rotation Rotation // Rotation is clock-wise
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
@@ -22,12 +24,16 @@ type Device struct {
|
||||
dc machine.Pin
|
||||
rst machine.Pin
|
||||
busy machine.Pin
|
||||
logicalWidth int16
|
||||
width int16
|
||||
height int16
|
||||
buffer []uint8
|
||||
bufferLength uint32
|
||||
rotation Rotation
|
||||
}
|
||||
|
||||
type Rotation uint8
|
||||
|
||||
// Look up table for full updates
|
||||
var lutFullUpdate = [30]uint8{
|
||||
0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11,
|
||||
@@ -61,17 +67,23 @@ func New(bus machine.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
|
||||
|
||||
// Configure sets up the device.
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
if cfg.LogicalWidth != 0 {
|
||||
d.logicalWidth = cfg.LogicalWidth
|
||||
} else {
|
||||
d.logicalWidth = 128
|
||||
}
|
||||
if cfg.Width != 0 {
|
||||
d.width = cfg.Width
|
||||
} else {
|
||||
d.width = 128
|
||||
d.width = 122
|
||||
}
|
||||
if cfg.Height != 0 {
|
||||
d.height = cfg.Height
|
||||
} else {
|
||||
d.height = 250
|
||||
}
|
||||
d.bufferLength = (uint32(d.width) * uint32(d.height)) / 8
|
||||
d.rotation = cfg.Rotation
|
||||
d.bufferLength = (uint32(d.logicalWidth) * uint32(d.height)) / 8
|
||||
d.buffer = make([]uint8, d.bufferLength)
|
||||
for i := uint32(0); i < d.bufferLength; i++ {
|
||||
d.buffer[i] = 0xFF
|
||||
@@ -158,10 +170,11 @@ func (d *Device) SetLUT(fullUpdate bool) {
|
||||
// We use RGBA(0,0,0, 255) as white (transparent)
|
||||
// Anything else as black
|
||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
||||
x, y = d.xy(x, y)
|
||||
if x < 0 || x >= d.logicalWidth || y < 0 || y >= d.height {
|
||||
return
|
||||
}
|
||||
byteIndex := (x + y*d.width) / 8
|
||||
byteIndex := (x + y*d.logicalWidth) / 8
|
||||
if c.R == 0 && c.G == 0 && c.B == 0 { // TRANSPARENT / WHITE
|
||||
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
|
||||
} else { // WHITE / EMPTY
|
||||
@@ -171,12 +184,12 @@ func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
|
||||
// Display sends the buffer to the screen.
|
||||
func (d *Device) Display() error {
|
||||
d.setMemoryArea(0, 0, d.width-1, d.height-1)
|
||||
d.setMemoryArea(0, 0, d.logicalWidth-1, d.height-1)
|
||||
for j := int16(0); j < d.height; j++ {
|
||||
d.setMemoryPointer(0, j)
|
||||
d.SendCommand(WRITE_RAM)
|
||||
for i := int16(0); i < d.width/8; i++ {
|
||||
d.SendData(d.buffer[i+j*(d.width/8)])
|
||||
for i := int16(0); i < d.logicalWidth/8; i++ {
|
||||
d.SendData(d.buffer[i+j*(d.logicalWidth/8)])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,15 +201,28 @@ func (d *Device) Display() error {
|
||||
}
|
||||
|
||||
// DisplayRect sends only an area of the buffer to the screen.
|
||||
// The rectangle points need to be a multiple of 8 in the screen.
|
||||
// They might not work as expected if the screen is rotated.
|
||||
func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error {
|
||||
if x < 0 || y < 0 || x >= d.width || y >= d.height || width < 0 || height < 0 {
|
||||
x, y = d.xy(x, y)
|
||||
if x < 0 || y < 0 || x >= d.logicalWidth || y >= d.height || width < 0 || height < 0 {
|
||||
return errors.New("wrong rectangle")
|
||||
}
|
||||
if d.rotation == ROTATION_90 {
|
||||
width, height = height, width
|
||||
x -= width
|
||||
} else if d.rotation == ROTATION_180 {
|
||||
x -= width - 1
|
||||
y -= height - 1
|
||||
} else if d.rotation == ROTATION_270 {
|
||||
width, height = height, width
|
||||
y -= height
|
||||
}
|
||||
x &= 0xF8
|
||||
width &= 0xF8
|
||||
width = x + width // reuse variables
|
||||
if width >= d.width {
|
||||
width = d.width
|
||||
if width >= d.logicalWidth {
|
||||
width = d.logicalWidth
|
||||
}
|
||||
height = y + height
|
||||
if height > d.height {
|
||||
@@ -209,7 +235,7 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
|
||||
d.setMemoryPointer(8*x, y)
|
||||
d.SendCommand(WRITE_RAM)
|
||||
for i := int16(x); i < width; i++ {
|
||||
d.SendData(d.buffer[i+y*d.width/8])
|
||||
d.SendData(d.buffer[i+y*d.logicalWidth/8])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +248,7 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
|
||||
|
||||
// ClearDisplay erases the device SRAM
|
||||
func (d *Device) ClearDisplay() {
|
||||
d.setMemoryArea(0, 0, d.width-1, d.height-1)
|
||||
d.setMemoryArea(0, 0, d.logicalWidth-1, d.height-1)
|
||||
d.setMemoryPointer(0, 0)
|
||||
d.SendCommand(WRITE_RAM)
|
||||
for i := uint32(0); i < d.bufferLength; i++ {
|
||||
@@ -274,5 +300,28 @@ func (d *Device) ClearBuffer() {
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
return d.width, d.height
|
||||
if d.rotation == ROTATION_90 || d.rotation == ROTATION_270 {
|
||||
return d.height, d.logicalWidth
|
||||
}
|
||||
return d.logicalWidth, d.height
|
||||
}
|
||||
|
||||
// SetRotation changes the rotation (clock-wise) of the device
|
||||
func (d *Device) SetRotation(rotation Rotation) {
|
||||
d.rotation = rotation
|
||||
}
|
||||
|
||||
// xy chages the coordinates according to the rotation
|
||||
func (d *Device) xy(x, y int16) (int16, int16) {
|
||||
switch d.rotation {
|
||||
case NO_ROTATION:
|
||||
return x, y
|
||||
case ROTATION_90:
|
||||
return d.width - y - 1, x
|
||||
case ROTATION_180:
|
||||
return d.width - x - 1, d.height - y - 1
|
||||
case ROTATION_270:
|
||||
return y, d.height - x - 1
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
|
||||
@@ -23,4 +23,9 @@ const (
|
||||
SET_RAM_X_ADDRESS_COUNTER = 0x4E
|
||||
SET_RAM_Y_ADDRESS_COUNTER = 0x4F
|
||||
TERMINATE_FRAME_READ_WRITE = 0xFF
|
||||
|
||||
NO_ROTATION Rotation = 0
|
||||
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
|
||||
ROTATION_180 Rotation = 2
|
||||
ROTATION_270 Rotation = 3
|
||||
)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# docker build -t wifinina .
|
||||
# docker run wifinina -v "../build/wifinina:/src/build"
|
||||
|
||||
FROM debian:stable-slim AS esp
|
||||
WORKDIR /src
|
||||
|
||||
RUN apt-get clean && apt-get update && \
|
||||
apt-get install -y sudo wget gcc git wget libncurses-dev flex bison gperf build-essential \
|
||||
python python-pip python-setuptools python-serial python-cryptography python-future python-pyparsing make
|
||||
|
||||
RUN mkdir /src/wifinina && \
|
||||
cd /src/wifinina && \
|
||||
wget https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz && \
|
||||
mkdir -p /src/esp && \
|
||||
cd /src/esp && \
|
||||
tar -xzf /src/wifinina/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz
|
||||
|
||||
RUN cd /src/esp && \
|
||||
git clone --branch v3.3.1 --recursive https://github.com/espressif/esp-idf.git
|
||||
|
||||
FROM esp AS nina
|
||||
|
||||
RUN cd /src/esp && \
|
||||
git clone https://github.com/arduino/nina-fw.git
|
||||
|
||||
COPY ./firmware.sh /src
|
||||
RUN chmod +x /src/firmware.sh
|
||||
ENTRYPOINT ["/src/firmware.sh"]
|
||||
@@ -0,0 +1,58 @@
|
||||
# WifiNINA Driver
|
||||
|
||||
This package provides a driver to use a separate connected WiFi processor ESP32 for TCP/UDP communication.
|
||||
|
||||
The way this driver works is by using the SPI interface of your microcontroller to communicate with the WiFi chip using the Arduino SPI command set.
|
||||
|
||||
## Using the WiFiNINA Driver
|
||||
|
||||
For information on how to use this driver, please take a look at the examples located in the [examples/wifinina](../examples/wifinina) directory.
|
||||
|
||||
## WiFiNINA Firmware Installation
|
||||
|
||||
**PLEASE NOTE: New Arduino Nano33 IoT boards already have the WiFiNINA firmware pre-installed, so you should not need to install the firmware yourself.**
|
||||
|
||||
In order to use this driver, you must have the WiFiNINA firmware installed on the ESP32 chip. If it is already installed, you can just use it. You do not need to build and flash the firmware again.
|
||||
|
||||
### Building the WifiNINA firmware
|
||||
|
||||
We have provided a Dockerfile that can build the needed firmware.
|
||||
|
||||
```shell
|
||||
docker build -t wifinina ./wifinina/
|
||||
docker run -v "$(pwd)/build:/src/build" wifinina
|
||||
```
|
||||
|
||||
This will put the firmware files into the `build` directory. Now you can flash them to the ESP32 chip.
|
||||
|
||||
### Installing esptool to flash WifiNINA firmware
|
||||
|
||||
In order to flash the firmware, you need to use Python to install the `esptool` package.
|
||||
|
||||
```shell
|
||||
pip install esptool
|
||||
```
|
||||
|
||||
Once you have installed `esptool` you can follow the correct procedure for flashing your board.
|
||||
|
||||
### Installing on Arduino Nano33 IoT
|
||||
|
||||
The Arduino Nano33 IoT board has the WiFiNINA firmware flashed onto the onboard NINA-W102 chip out of the box.
|
||||
|
||||
Flashing the firmware is only necessary on the Arduino Nano33 IoT in order to upgrade or if other firmware was installed previously.
|
||||
|
||||
If you do want to install the firmware on the Arduino Nano33 IoT board's built-in NINA-W102 chip, you will need to first build the firmware as described above.
|
||||
|
||||
To flash this firmware on the Arduino Nano33 IoT you will need to follow the following procedure using the Arduino IDE software:
|
||||
|
||||
- Install _Arduino SAMD Boards_ from the Boards Manager.
|
||||
- Install _WiFiNINA_ from the Library Manager.
|
||||
- Using the normal Arduino software, load the `SerialNINAPassthrough` sketch on to the board (in File -> Examples -> WiFiNINA-> Tools).
|
||||
|
||||
Now you can flash the WifiNINA firmware using the `esptool` script:
|
||||
|
||||
```shell
|
||||
python esptool.py --chip esp32 --port /dev/ttyACM0 --baud 115200 --before no_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x1000 build/bootloader.bin 0xf000 build/phy_init_data.bin 0x30000 build/nina-fw.bin 0x8000 build/partitions.bin
|
||||
```
|
||||
|
||||
You only need to do this one time, and then the correct WiFiNINA firmware will be on the NINA chip, and you can just flash the Arduino Nano33 IoT board using TinyGo. We should be able to remove some of these steps in a future release of this software.
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
cd /src/esp/nina-fw
|
||||
export PATH=/src/esp/xtensa-esp32-elf/bin:$PATH
|
||||
export IDF_PATH=/src/esp/esp-idf
|
||||
make firmware
|
||||
cp /src/esp/nina-fw/build/bootloader/bootloader.bin /src/build/
|
||||
cp /src/esp/nina-fw/build/phy_init_data.bin /src/build/
|
||||
cp /src/esp/nina-fw/build/nina-fw.bin /src/build/
|
||||
cp /src/esp/nina-fw/build/partitions.bin /src/build/
|
||||
cd -
|
||||
@@ -0,0 +1,3 @@
|
||||
WiFiNINA protocol
|
||||
=================
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user