From 0ed9683a52a935a05e38ebdc43d15533b1518fd4 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 31 Jan 2021 11:09:42 +0100 Subject: [PATCH 01/10] st7789: add scrolling functions to match st7735 Signed-off-by: deadprogram --- st7789/registers.go | 2 ++ st7789/st7789.go | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/st7789/registers.go b/st7789/registers.go index cdea194..8d40547 100644 --- a/st7789/registers.go +++ b/st7789/registers.go @@ -50,6 +50,8 @@ const ( GMCTRP1 = 0xE0 GMCTRN1 = 0xE1 GSCAN = 0x45 + VSCRDEF = 0x33 + VSCRSADD = 0x37 NO_ROTATION Rotation = 0 ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation diff --git a/st7789/st7789.go b/st7789/st7789.go index 9158c5c..cfb7236 100644 --- a/st7789/st7789.go +++ b/st7789/st7789.go @@ -435,6 +435,27 @@ func (d *Device) IsBGR(bgr bool) { d.isBGR = bgr } +// 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) +} + // RGBATo565 converts a color.RGBA to uint16 used in the display func RGBATo565(c color.RGBA) uint16 { r, g, b, _ := c.RGBA() From 008157b6c9c0a0bf5f55db621ca692dca1d7f09c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 31 Jan 2021 15:14:51 +0100 Subject: [PATCH 02/10] st7789: correct errors on various godoc comments Signed-off-by: deadprogram --- st7789/st7789.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/st7789/st7789.go b/st7789/st7789.go index cfb7236..ad60627 100644 --- a/st7789/st7789.go +++ b/st7789/st7789.go @@ -16,8 +16,10 @@ import ( "tinygo.org/x/drivers" ) +// Rotation controls the rotation used by the display. type Rotation uint8 +// FrameRate controls the frame rate used by the display. type FrameRate uint8 // Device wraps an SPI connection. @@ -278,7 +280,7 @@ func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error { return nil } -// FillRectangle fills a rectangle at a given coordinates with a buffer +// FillRectangleWithBuffer fills buffer with a rectangle at a given coordinates. func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error { i, j := d.Size() if x < 0 || y < 0 || width <= 0 || height <= 0 || @@ -370,12 +372,12 @@ func (d *Device) SetRotation(rotation Rotation) { d.Data(madctl) } -// Command sends a command to the display +// Command sends a command to the display. func (d *Device) Command(command uint8) { d.Tx([]byte{command}, true) } -// Command sends a data to the display +// Data sends data to the display. func (d *Device) Data(data uint8) { d.Tx([]byte{data}, false) } @@ -393,13 +395,13 @@ func (d *Device) Tx(data []byte, isCommand bool) { } // Rx reads data from the display -func (d *Device) Rx(command uint8, read_bytes []byte) { +func (d *Device) Rx(command uint8, data []byte) { d.dcPin.Low() d.csPin.Low() d.bus.Transfer(command) d.dcPin.High() - for i := range read_bytes { - read_bytes[i], _ = d.bus.Transfer(0xFF) + for i := range data { + data[i], _ = d.bus.Transfer(0xFF) } d.csPin.High() } @@ -421,7 +423,7 @@ func (d *Device) EnableBacklight(enable bool) { } } -// InverColors inverts the colors of the screen +// InvertColors inverts the colors of the screen func (d *Device) InvertColors(invert bool) { if invert { d.Command(INVON) @@ -435,7 +437,7 @@ func (d *Device) IsBGR(bgr bool) { d.isBGR = bgr } -// SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display +// SetScrollArea sets an area to scroll with fixed top and bottom parts of the display. func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) { d.Command(VSCRDEF) d.Tx([]uint8{ @@ -451,7 +453,7 @@ func (d *Device) SetScroll(line int16) { d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false) } -// SpotScroll returns the display to its normal state +// StopScroll returns the display to its normal state. func (d *Device) StopScroll() { d.Command(NORON) } From c64d7920dccc03398e1b4eb6be82f45619b40f54 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Mon, 1 Feb 2021 12:43:40 +0100 Subject: [PATCH 03/10] adc: update drivers with ADC to use new config struct Signed-off-by: deadprogram --- thermistor/thermistor.go | 2 +- touch/resistive/fourwire.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/thermistor/thermistor.go b/thermistor/thermistor.go index 1d6aede..92175d8 100644 --- a/thermistor/thermistor.go +++ b/thermistor/thermistor.go @@ -57,7 +57,7 @@ func New(pin machine.Pin) Device { // Configure configures the ADC pin used for the thermistor. func (d *Device) Configure() { - d.adc.Configure() + d.adc.Configure(machine.ADCConfig{}) } // ReadTemperature returns the temperature in celsius milli degrees (°C/1000) diff --git a/touch/resistive/fourwire.go b/touch/resistive/fourwire.go index 9f467d1..1903792 100644 --- a/touch/resistive/fourwire.go +++ b/touch/resistive/fourwire.go @@ -86,7 +86,7 @@ func (res *FourWire) ReadX() uint16 { res.xm.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) res.xm.Pin.Low() - res.yp.Configure() + res.yp.Configure(machine.ADCConfig{}) return 0xFFFF - res.yp.Get() } @@ -101,7 +101,7 @@ func (res *FourWire) ReadY() uint16 { res.ym.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) res.ym.Pin.Low() - res.xp.Configure() + res.xp.Configure(machine.ADCConfig{}) return 0xFFFF - res.xp.Get() } @@ -114,8 +114,8 @@ func (res *FourWire) ReadZ() uint16 { res.ym.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) res.ym.Pin.High() - res.xm.Configure() - res.yp.Configure() + res.xm.Configure(machine.ADCConfig{}) + res.yp.Configure(machine.ADCConfig{}) z1 := res.xm.Get() z2 := res.yp.Get() From ef34c13cc12543f385ca916e3e93c6d083129b17 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 17 Feb 2021 21:30:45 +0000 Subject: [PATCH 04/10] hd44780: add a mode to work with boards where the RW pin is grounded On some HD44780 boards (eg the Keyestudio LCD1602 expansion shield), the RW pin isn't brought out and is permanently grounded. This means that the board can't be read from, and in particular the busy status can't be read. This patch adapts the package to work with boards like these. To signal this to the package, set the RW pin to machine.NoPin. The package will then disallow all reading and use adjustable timing based writing. The timing can be adjusted the configuration. --- hd44780/gpio.go | 12 +++++++- hd44780/hd44780.go | 69 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/hd44780/gpio.go b/hd44780/gpio.go index e3f3a67..0549ab7 100644 --- a/hd44780/gpio.go +++ b/hd44780/gpio.go @@ -57,9 +57,16 @@ func (g *GPIO) SetCommandMode(set bool) { } } +// WriteOnly is true if you passed rw in as machine.NoPin +func (g *GPIO) WriteOnly() bool { + return g.rw == machine.NoPin +} + // Write writes len(data) bytes from data to display driver func (g *GPIO) Write(data []byte) (n int, err error) { - g.rw.Low() + if !g.WriteOnly() { + g.rw.Low() + } for _, d := range data { g.write(d) n++ @@ -89,6 +96,9 @@ func (g *GPIO) Read(data []byte) (n int, err error) { if len(data) == 0 { return 0, errors.New("length greater than 0 is required") } + if g.WriteOnly() { + return 0, errors.New("Read not supported if RW not wired") + } g.rw.High() g.reconfigureGPIOMode(machine.PinInput) for i := 0; i < len(data); i++ { diff --git a/hd44780/hd44780.go b/hd44780/hd44780.go index 0440a9d..c06b4bc 100644 --- a/hd44780/hd44780.go +++ b/hd44780/hd44780.go @@ -11,9 +11,23 @@ import ( "time" ) +const ( + // These are the default execution times for the Clear and + // Home commands and everything else. + // + // These are used if RW is passed as machine.NoPin and ignored + // otherwise. + // + // They are set conservatively here and can be tweaked in the + // Config structure. + DefaultClearHomeTime = 80 * time.Millisecond + DefaultInstrExecTime = 80 * time.Microsecond +) + type Buser interface { io.ReadWriter SetCommandMode(set bool) + WriteOnly() bool } type Device struct { @@ -28,6 +42,9 @@ type Device struct { cursor cursor busyStatus []byte + + clearHomeTime time.Duration // time clear/home instructions might take + instrExecTime time.Duration // time all other instructions might take } type cursor struct { @@ -35,14 +52,18 @@ type cursor struct { } type Config struct { - Width int16 - Height int16 - CursorBlink bool - CursorOnOff bool - Font uint8 + Width int16 + Height int16 + CursorBlink bool + CursorOnOff bool + Font uint8 + ClearHomeTime time.Duration // time clear/home instructions might take - use 0 for the default + InstrExecTime time.Duration // time all other instructions might take - use 0 for the default } // NewGPIO4Bit returns 4bit data length HD44780 driver. Datapins are LCD DB pins starting from DB4 to DB7 +// +// If your device has RW set permanently to ground then pass in rw as machine.NoPin func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) { const fourBitMode = 4 if len(dataPins) != fourBitMode { @@ -52,6 +73,8 @@ func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) } // NewGPIO8Bit returns 8bit data length HD44780 driver. Datapins are LCD DB pins starting from DB0 to DB7 +// +// If your device has RW set permanently to ground then pass in rw as machine.NoPin func NewGPIO8Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) { const eightBitMode = 8 if len(dataPins) != eightBitMode { @@ -68,6 +91,8 @@ func (d *Device) Configure(cfg Config) error { if d.width == 0 || d.height == 0 { return errors.New("width and height must be set") } + d.clearHomeTime = cfg.ClearHomeTime + d.instrExecTime = cfg.InstrExecTime memoryMap := uint8(ONE_LINE) if d.height > 1 { memoryMap = TWO_LINE @@ -186,7 +211,7 @@ func (d *Device) SendCommand(command byte) { d.bus.SetCommandMode(true) d.bus.Write([]byte{command}) - for d.Busy() { + for d.busy(command == DISPLAY_CLEAR || command == CURSOR_HOME) { } } @@ -195,7 +220,7 @@ func (d *Device) sendData(data byte) { d.bus.SetCommandMode(false) d.bus.Write([]byte{data}) - for d.Busy() { + for d.busy(false) { } } @@ -207,13 +232,39 @@ func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) { } } -// Busy returns true when hd447890 is busy -func (d *Device) Busy() bool { +// busy returns true when hd447890 is busy +// or after the timeout specified +func (d *Device) busy(longDelay bool) bool { + if d.bus.WriteOnly() { + // Can't read busy flag if write only, so sleep a bit then return + if longDelay { + // Note that we sleep like this so the default + // time.Sleep is time.Sleep(constant) as + // time.Sleep(variable) doesn't seem to work on AVR yet + if d.clearHomeTime != 0 { + time.Sleep(d.clearHomeTime) + } else { + time.Sleep(DefaultClearHomeTime) + } + } else { + if d.instrExecTime != 0 { + time.Sleep(d.instrExecTime) + } else { + time.Sleep(DefaultInstrExecTime) + } + } + return false + } d.bus.SetCommandMode(true) d.bus.Read(d.busyStatus) return (d.busyStatus[0] & BUSY) > 0 } +// Busy returns true when hd447890 is busy +func (d *Device) Busy() bool { + return d.busy(false) +} + // Size returns the current size of the display. func (d *Device) Size() (w, h int16) { return int16(d.width), int16(d.height) From 0fc2d28ca89e9c6c098a9114c226c89216ec4945 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 5 Mar 2021 00:09:22 +0100 Subject: [PATCH 05/10] docs: update year in license to 2021 Signed-off-by: deadprogram --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d1f0233..7634817 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2020 The TinyGo Authors. All rights reserved. +Copyright (c) 2018-2021 The TinyGo Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are From d170ec8d8138a3c55a960695000e24fb08613795 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 5 Mar 2021 00:36:50 +0100 Subject: [PATCH 06/10] docs: add missing new drivers added since last release Signed-off-by: deadprogram --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0bd1625..161606e 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ func main() { ## Currently supported devices -The following 53 devices are supported. +The following 55 devices are supported. | Device Name | Interface Type | |----------|-------------| @@ -68,6 +68,7 @@ The following 53 devices are supported. | [BMI160 accelerometer/gyroscope](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi160-ds000.pdf) | SPI | | [BMP180 barometer](https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf) | I2C | | [BMP280 temperature/barometer](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf) | I2C | +| [BMP388 pressure sensor](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf) | I2C | | [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | GPIO | | [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 | @@ -84,6 +85,7 @@ The following 53 devices are supported. | [LIS3DH accelerometer](https://www.st.com/resource/en/datasheet/lis3dh.pdf) | I2C | | [LSM6DS3 accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3.pdf) | I2C | | [MAG3110 magnetometer](https://www.nxp.com/docs/en/data-sheet/MAG3110.pdf) | I2C | +| [MCP23017 port expander](https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf) | I2C | | [MCP3008 analog to digital converter (ADC)](http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf) | SPI | | [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 | From 9a7cb1a22fe4976a2bcf4209df5328a219e78898 Mon Sep 17 00:00:00 2001 From: pleomaxx3002 Date: Fri, 5 Mar 2021 09:24:45 +0100 Subject: [PATCH 07/10] DHTXX driver (#235) dhtXX: add new driver for dht thermometer --- Makefile | 4 +- README.md | 1 + dht/constants.go | 116 ++++++++++++++++++++ dht/highfreq.go | 6 + dht/lowfreq.go | 6 + dht/thermometer.go | 218 +++++++++++++++++++++++++++++++++++++ dht/timesafethermometer.go | 154 ++++++++++++++++++++++++++ dht/util.go | 34 ++++++ examples/dht/main.go | 23 ++++ 9 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 dht/constants.go create mode 100644 dht/highfreq.go create mode 100644 dht/lowfreq.go create mode 100644 dht/thermometer.go create mode 100644 dht/timesafethermometer.go create mode 100644 dht/util.go create mode 100644 examples/dht/main.go diff --git a/Makefile b/Makefile index 50efd99..3d6459b 100644 --- a/Makefile +++ b/Makefile @@ -165,11 +165,13 @@ endif @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go + @md5sum ./build/test.hex DRIVERS = $(wildcard */) NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \ hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \ - hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x + hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS)) unit-test: diff --git a/README.md b/README.md index 161606e..46c4d3d 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ The following 55 devices are supported. | [BMP280 temperature/barometer](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf) | I2C | | [BMP388 pressure sensor](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf) | I2C | | [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | GPIO | +| [DHTXX thermometer and humidity sensor](https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf) | GPIO | | [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C | | [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C | | [ESP32 as WiFi Coprocessor with Arduino nina-fw](https://github.com/arduino/nina-fw) | SPI | diff --git a/dht/constants.go b/dht/constants.go new file mode 100644 index 0000000..8bed1a3 --- /dev/null +++ b/dht/constants.go @@ -0,0 +1,116 @@ +// Package dht provides a driver for DHTXX family temperature and humidity sensors. +// +// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf +// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf +// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library + +package dht // import "tinygo.org/x/drivers/dht" + +import ( + "encoding/binary" + "machine" + "time" +) + +// enum type for device type +type DeviceType uint8 + +// DeviceType specific parsing of information received from the sensor +func (d DeviceType) extractData(buf []byte) (temp int16, hum uint16) { + if d == DHT11 { + temp = int16(buf[2]) + if buf[3]&0x80 > 0 { + temp = -1 - temp + } + temp *= 10 + temp += int16(buf[3] & 0x0f) + hum = 10*uint16(buf[0]) + uint16(buf[1]) + } else { + hum = binary.LittleEndian.Uint16(buf[0:2]) + temp = int16(buf[3])<<8 + int16(buf[2]&0x7f) + if buf[2]&0x80 > 0 { + temp = -temp + } + } + return +} + +// Celsius and Fahrenheit temperature scales +type TemperatureScale uint8 + +func (t TemperatureScale) convertToFloat(temp int16) float32 { + if t == C { + return float32(temp) / 10 + } else { + // Fahrenheit + return float32(temp)*(9.0/50.) + 32. + } +} + +// All functions return ErrorCode instance as error. This class can be used for more efficient error processing +type ErrorCode uint8 + +const ( + startTimeout = time.Millisecond * 200 + startingLow = time.Millisecond * 20 + + DHT11 DeviceType = iota + DHT22 + + C TemperatureScale = iota + F + + ChecksumError ErrorCode = iota + NoSignalError + NoDataError + UpdateError + UninitializedDataError +) + +// error interface implementation for ErrorCode +func (e ErrorCode) Error() string { + switch e { + case ChecksumError: + // DHT returns ChecksumError if all the data from the sensor was received, but the checksum does not match. + return "checksum mismatch" + case NoSignalError: + // DHT returns NoSignalError if there was no reply from the sensor. Check sensor connection or the correct pin + // sis chosen, + return "no signal" + case NoDataError: + // DHT returns NoDataError if the connection was successfully initialized, but not all 40 bits from + // the sensor is received + return "no data" + case UpdateError: + // DHT returns UpdateError if ReadMeasurements function is called before time specified in UpdatePolicy or + // less than 2 seconds after past measurement + return "cannot update now" + case UninitializedDataError: + // DHT returns UninitializedDataError if user attempts to access data before first measurement + return "no measurements done" + } + // should never be reached + return "unknown error" +} + +// Update policy of the DHT device. UpdateTime cannot be shorter than 2 seconds. According to dht specification sensor +// will return undefined data if update requested less than 2 seconds before last usage +type UpdatePolicy struct { + UpdateTime time.Duration + UpdateAutomatically bool +} + +var ( + // timeout counter equal to number of ticks per 1 millisecond + timeout counter +) + +func init() { + timeout = cyclesPerMillisecond() +} + +func cyclesPerMillisecond() counter { + freq := machine.CPUFrequency() + freq /= 1000 + return counter(freq) +} diff --git a/dht/highfreq.go b/dht/highfreq.go new file mode 100644 index 0000000..2eb9de3 --- /dev/null +++ b/dht/highfreq.go @@ -0,0 +1,6 @@ +// +build mimxrt1062 stm32f405 atsamd51 stm32f103xx k210 stm32f407 + +package dht // import "tinygo.org/x/drivers/dht" + +// This file provides a definition of the counter for boards with frequency higher than 2^8 ticks per millisecond (>64MHz) +type counter uint32 diff --git a/dht/lowfreq.go b/dht/lowfreq.go new file mode 100644 index 0000000..ea97468 --- /dev/null +++ b/dht/lowfreq.go @@ -0,0 +1,6 @@ +// +build arduino atmega1284p nrf52840 digispark nrf52 arduino_nano nrf51 atsamd21 fe310 arduino_nano33 circuitplay_express arduino_mega2560 + +package dht // import "tinygo.org/x/drivers/dht" + +// This file provides a definition of the counter for boards with frequency lower than 2^8 ticks per millisecond (<64MHz) +type counter uint16 diff --git a/dht/thermometer.go b/dht/thermometer.go new file mode 100644 index 0000000..2ee3d93 --- /dev/null +++ b/dht/thermometer.go @@ -0,0 +1,218 @@ +// Package dht provides a driver for DHTXX family temperature and humidity sensors. +// +// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf +// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf +// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library + +package dht // import "tinygo.org/x/drivers/dht" + +import ( + "machine" + "time" +) + +// DummyDevice provides a basic interface for DHT devices. +type DummyDevice interface { + ReadMeasurements() error + Measurements() (temperature int16, humidity uint16, err error) + Temperature() (int16, error) + TemperatureFloat(scale TemperatureScale) (float32, error) + Humidity() (uint16, error) + HumidityFloat() (float32, error) +} + +// Basic implementation of the DummyDevice +// This implementation takes measurements from sensor only with ReadMeasurements function +// and does not provide a protection from too frequent calls for measurements. +// Since taking measurements from the sensor is time consuming procedure and blocks interrupts, +// user can avoid any hidden calls to the sensor. +type device struct { + pin machine.Pin + + measurements DeviceType + initialized bool + + temperature int16 + humidity uint16 +} + +// ReadMeasurements reads data from the sensor. +// According to documentation pin should be always, but the t *device restores pin to the state before call. +func (t *device) ReadMeasurements() error { + // initial waiting + state := powerUp(t.pin) + defer t.pin.Set(state) + err := t.read() + if err == nil { + t.initialized = true + } + return err +} + +// Getter for temperature. Temperature method returns temperature as it is sent by device. +// The temperature is measured temperature in Celsius multiplied by 10. +// If no successful measurements for this device was performed, returns UninitializedDataError. +func (t *device) Temperature() (int16, error) { + if !t.initialized { + return 0, UninitializedDataError + } + return t.temperature, nil +} + +// Getter for temperature. TemperatureFloat returns temperature in a given scale. +// If no successful measurements for this device was performed, returns UninitializedDataError. +func (t *device) TemperatureFloat(scale TemperatureScale) (float32, error) { + if !t.initialized { + return 0, UninitializedDataError + } + return scale.convertToFloat(t.temperature), nil +} + +// Getter for humidity. Humidity returns humidity as it is sent by device. +// The humidity is measured in percentages multiplied by 10. +// If no successful measurements for this device was performed, returns UninitializedDataError. +func (t *device) Humidity() (uint16, error) { + if !t.initialized { + return 0, UninitializedDataError + } + return t.humidity, nil +} + +// Getter for humidity. HumidityFloat returns humidity in percentages. +// If no successful measurements for this device was performed, returns UninitializedDataError. +func (t *device) HumidityFloat() (float32, error) { + if !t.initialized { + return 0, UninitializedDataError + } + return float32(t.humidity) / 10., nil +} + +// Perform initialization of the communication protocol. +// Device lowers the voltage on pin for startingLow=20ms and starts listening for response +// Section 5.2 in [1] +func initiateCommunication(p machine.Pin) { + // Send low signal to the device + p.Configure(machine.PinConfig{Mode: machine.PinOutput}) + p.Low() + time.Sleep(startingLow) + // Set pin to high and wait for reply + p.High() + p.Configure(machine.PinConfig{Mode: machine.PinInput}) +} + +// Measurements returns both measurements: temperature and humidity as they sent by the device. +// If no successful measurements for this device was performed, returns UninitializedDataError. +func (t *device) Measurements() (temperature int16, humidity uint16, err error) { + if !t.initialized { + return 0, 0, UninitializedDataError + } + temperature = t.temperature + humidity = t.humidity + err = nil + return +} + +// Main routine that performs communication with the sensor +func (t *device) read() error { + // initialize loop variables + + // buffer for the data sent by the sensor. Sensor sends 40 bits = 5 bytes + bufferData := [5]byte{} + buf := bufferData[:] + + // We perform measurements of the signal from the sensor by counting low and high cycles. + // The bit is determined by the relative length of the high signal to low signal. + // For 1, high signal will be longer than low, for 0---low is longer. + // See section 5.3 [1] + signalsData := [80]counter{} + signals := signalsData[:] + + // Start communication protocol with sensor + initiateCommunication(t.pin) + // Wait for sensor's response and abort if sensor does not reply + err := waitForDataTransmission(t.pin) + if err != nil { + return err + } + // count low and high cycles for sensor's reply + receiveSignals(t.pin, signals) + + // process received signals and store the result in the buffer. Abort if data transmission was interrupted and not + // all 40 bits were received + err = t.extractData(signals[:], buf) + if err != nil { + return err + } + // Compute checksum and compare it to the one in data. Abort if checksum is incorrect + if !isValid(buf[:]) { + return ChecksumError + } + + // Extract temperature and humidity data from buffer + t.temperature, t.humidity = t.measurements.extractData(buf) + return nil +} + +// receiveSignals counts number of low and high cycles. The execution is time critical, so the function disables +// interrupts +func receiveSignals(pin machine.Pin, result []counter) { + i := uint8(0) + machine.UART1.Interrupt.Disable() + defer machine.UART1.Interrupt.Enable() + for ; i < 40; i++ { + result[i*2] = expectChange(pin, false) + result[i*2+1] = expectChange(pin, true) + } +} + +// extractData process signal counters and transforms them into bits. +// if any of the bits were not received (timed-out), returns NoDataError +func (t *device) extractData(signals []counter, buf []uint8) error { + for i := uint8(0); i < 40; i++ { + lowCycle := signals[i*2] + highCycle := signals[i*2+1] + if lowCycle == timeout || highCycle == timeout { + return NoDataError + } + byteN := i >> 3 + buf[byteN] <<= 1 + if highCycle > lowCycle { + buf[byteN] |= 1 + } + } + return nil +} + +// waitForDataTransmission waits for reply from the sensor. +// If no reply received, returns NoSignalError. +// For more details, see section 5.2 in [1] +func waitForDataTransmission(p machine.Pin) error { + // wait for thermometer to pull down + if expectChange(p, true) == timeout { + return NoSignalError + } + //wait for thermometer to pull up + if expectChange(p, false) == timeout { + return NoSignalError + } + // wait for thermometer to pull down and start sending the data + if expectChange(p, true) == timeout { + return NoSignalError + } + return nil +} + +// Constructor function for a DummyDevice implementation. +// This device provides full control to the user. +// It does not do any hidden measurements calls and does not check +// for 2 seconds delay between measurements. +func NewDummyDevice(pin machine.Pin, deviceType DeviceType) DummyDevice { + pin.High() + return &device{ + pin: pin, + measurements: deviceType, + initialized: false, + temperature: 0, + humidity: 0, + } +} diff --git a/dht/timesafethermometer.go b/dht/timesafethermometer.go new file mode 100644 index 0000000..3c4ff47 --- /dev/null +++ b/dht/timesafethermometer.go @@ -0,0 +1,154 @@ +// Package dht provides a driver for DHTXX family temperature and humidity sensors. +// +// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf +// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf +// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library + +package dht // import "tinygo.org/x/drivers/dht" + +import ( + "machine" + "time" +) + +// Device interface provides main functionality of the DHTXX sensors. +type Device interface { + DummyDevice + Configure(policy UpdatePolicy) +} + +// managedDevice struct provides time control and optional automatic data retrieval from the sensor. +// It delegates all the functionality to device +type managedDevice struct { + t device + lastUpdate time.Time + policy UpdatePolicy +} + +// Measurements returns both measurements: temperature and humidity as they sent by the device. +// Depending on the UpdatePolicy of the device may update cached measurements. +func (m *managedDevice) Measurements() (temperature int16, humidity uint16, err error) { + err = m.checkForUpdateOnDataRequest() + if err != nil { + return 0, 0, err + } + return m.t.Measurements() +} + +// Getter for temperature. Temperature method returns temperature as it is sent by device. +// The temperature is measured temperature in Celsius multiplied by 10. +// Depending on the UpdatePolicy of the device may update cached measurements. +func (m *managedDevice) Temperature() (temp int16, err error) { + err = m.checkForUpdateOnDataRequest() + if err != nil { + return 0, err + } + temp, err = m.t.Temperature() + return +} + +func (m *managedDevice) checkForUpdateOnDataRequest() (err error) { + // update if necessary + if m.policy.UpdateAutomatically { + err = m.ReadMeasurements() + } + // ignore error if the data was updated recently + // interface comparison does not work in tinygo. Therefore need to cast to explicit type + if code, ok := err.(ErrorCode); ok && code == UpdateError { + err = nil + } + // add error if the data is not initialized + if !m.t.initialized { + err = UninitializedDataError + } + return err +} + +// Getter for temperature. TemperatureFloat returns temperature in a given scale. +// Depending on the UpdatePolicy of the device may update cached measurements. +func (m *managedDevice) TemperatureFloat(scale TemperatureScale) (float32, error) { + err := m.checkForUpdateOnDataRequest() + if err != nil { + return 0, err + } + return m.t.TemperatureFloat(scale) +} + +// Getter for humidity. Humidity returns humidity as it is sent by device. +// The humidity is measured in percentages multiplied by 10. +// Depending on the UpdatePolicy of the device may update cached measurements. +func (m *managedDevice) Humidity() (hum uint16, err error) { + err = m.checkForUpdateOnDataRequest() + if err != nil { + return 0, err + } + return m.t.Humidity() +} + +// Getter for humidity. HumidityFloat returns humidity in percentages. +// Depending on the UpdatePolicy of the device may update cached measurements. +func (m *managedDevice) HumidityFloat() (float32, error) { + err := m.checkForUpdateOnDataRequest() + if err != nil { + return 0, err + } + return m.t.HumidityFloat() +} + +// ReadMeasurements reads data from the sensor. +// The function will return UpdateError if it is called more frequently than specified in UpdatePolicy +func (m *managedDevice) ReadMeasurements() (err error) { + timestamp := time.Now() + if !m.t.initialized || timestamp.Sub(m.lastUpdate) > m.policy.UpdateTime { + err = m.t.ReadMeasurements() + } else { + err = UpdateError + } + if err == nil { + m.lastUpdate = timestamp + } + return +} + +// Configure configures UpdatePolicy for Device. +// Configure checks for policy.UpdateTime and prevent from updating more frequently than specified in [1][2] +// to prevent undefined behaviour of the sensor. +func (m *managedDevice) Configure(policy UpdatePolicy) { + if policy.UpdateAutomatically && policy.UpdateTime < time.Second*2 { + policy.UpdateTime = time.Second * 2 + } + m.policy = policy +} + +// Constructor of the Device implementation. +// This implementation updates data every 2 seconds during data access. +func New(pin machine.Pin, deviceType DeviceType) Device { + pin.High() + return &managedDevice{ + t: device{ + pin: pin, + measurements: deviceType, + initialized: false, + }, + lastUpdate: time.Time{}, + policy: UpdatePolicy{ + UpdateTime: time.Second * 2, + UpdateAutomatically: true, + }, + } +} + +// Constructor of the Device implementation with given UpdatePolicy +func NewWithPolicy(pin machine.Pin, deviceType DeviceType, updatePolicy UpdatePolicy) Device { + pin.High() + result := &managedDevice{ + t: device{ + pin: pin, + measurements: deviceType, + initialized: false, + }, + lastUpdate: time.Time{}, + } + result.Configure(updatePolicy) + return result +} diff --git a/dht/util.go b/dht/util.go new file mode 100644 index 0000000..0581c73 --- /dev/null +++ b/dht/util.go @@ -0,0 +1,34 @@ +package dht // import "tinygo.org/x/drivers/dht" + +import ( + "machine" + "time" +) + +// Check if the pin is disabled +func powerUp(p machine.Pin) bool { + state := p.Get() + if !state { + p.High() + time.Sleep(startTimeout) + } + return state +} + +func expectChange(p machine.Pin, oldState bool) counter { + cnt := counter(0) + for ; p.Get() == oldState && cnt != timeout; cnt++ { + } + return cnt +} + +func checksum(buf []uint8) uint8 { + return buf[4] +} +func computeChecksum(buf []uint8) uint8 { + return buf[0] + buf[1] + buf[2] + buf[3] +} + +func isValid(buf []uint8) bool { + return checksum(buf) == computeChecksum(buf) +} diff --git a/examples/dht/main.go b/examples/dht/main.go new file mode 100644 index 0000000..5600e6b --- /dev/null +++ b/examples/dht/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "machine" + "time" + "tinygo.org/x/drivers/dht" +) + +func main() { + pin := machine.D6 + dhtSensor := dht.New(pin, dht.DHT11) + for { + temp, hum, err := dhtSensor.Measurements() + if err != nil { + fmt.Printf("Temperature: %02d.%d°C, Humidity: %02d.%d%%\n", temp/10, temp%10, hum/10, hum%10) + } else { + fmt.Printf("Could not take measurements from the sensor: %s\n", err.Error()) + } + // Measurements cannot be updated only 2 seconds. More frequent calls will return the same value + time.Sleep(time.Second * 2) + } +} From e9a6d96ddd07b36bce9aa6accba838c06f62c0d5 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 5 Mar 2021 09:26:56 +0100 Subject: [PATCH 08/10] docs: update count of supported drivers to add latest contribution Signed-off-by: deadprogram --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 46c4d3d..83b38ca 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ func main() { ## Currently supported devices -The following 55 devices are supported. +The following 56 devices are supported. | Device Name | Interface Type | |----------|-------------| From 27ef18930e42eba441ab69c02b6515ae5f38eb48 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 6 Mar 2021 13:41:58 +0100 Subject: [PATCH 09/10] Prepare for drivers release 0.15.0 Signed-off-by: deadprogram --- CHANGELOG.md | 22 ++++++++++++++++++++++ README.md | 2 +- version.go | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89a7443..ff86190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +0.15.0 +--- +- **new devices** + - dht: add DHTXX thermometer + - mcp23017: new driver for MCP23017 (I2C port expander) + - bmp388: Add bmp388 support (#219) +- **enhancements** + - hd44780: add a mode to work with boards where the RW pin is grounded + - st7789: add scrolling functions to match st7735 + - microbitmatrix: matrix now working on microbit v2 + - ds1307: Better interface "ReadTime" instead of "Time" + - ws2812: make AVR support more robust +- **bugfixes** + - all: fix main package in examples +- **core** + - adc: update all drivers with ADC to use new config struct + - spi: remove machine.SPI and replace with drivers.SPI interface for almost all SPI drivers +- **testing** + - test: run unit tests against i2c drivers and any spi drivers without direct gpio +- **docs** + - st7789: correct errors on various godoc comments + 0.14.0 --- - **new devices** diff --git a/README.md b/README.md index 83b38ca..f9e5400 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![PkgGoDev](https://pkg.go.dev/badge/tinygo.org/x/drivers)](https://pkg.go.dev/tinygo.org/x/drivers) [![CircleCI](https://circleci.com/gh/tinygo-org/drivers/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/drivers/tree/dev) -This package provides a collection of hardware drivers for devices that can be used together with [TinyGo](https://tinygo.org). +This package provides a collection of hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org). ## Installing diff --git a/version.go b/version.go index 950c2e9..f75dab0 100644 --- a/version.go +++ b/version.go @@ -2,4 +2,4 @@ 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.14.0" +const Version = "0.15.0" From 5741ceb9d179dab8b88e290fda2ad5486345303c Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 6 Mar 2021 13:51:27 +0100 Subject: [PATCH 10/10] Prepare for drivers release 0.15.1 to get tag correct Signed-off-by: deadprogram --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index f75dab0..8f73943 100644 --- a/version.go +++ b/version.go @@ -2,4 +2,4 @@ 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.15.0" +const Version = "0.15.1"