From 5cc21329a6eb9a439b98cb0665da394aff9f595d Mon Sep 17 00:00:00 2001 From: Daniel Esteban Date: Sat, 22 Apr 2023 17:55:37 +0200 Subject: [PATCH] first implementation of 1-wire protocol (#505) onewire: initial implementation for protocol and ds18b20 device --- Makefile | 2 + README.md | 6 +- ds18b20/ds18b20.go | 89 ++++++++++++++++ examples/ds18b20/main.go | 58 +++++++++++ examples/onewire/main.go | 40 ++++++++ onewire/onewire.go | 213 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 ds18b20/ds18b20.go create mode 100644 examples/ds18b20/main.go create mode 100644 examples/onewire/main.go create mode 100644 onewire/onewire.go diff --git a/Makefile b/Makefile index df60b5d..a71bae0 100644 --- a/Makefile +++ b/Makefile @@ -249,6 +249,8 @@ endif @md5sum ./build/test.uf2 tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go @md5sum ./build/test.uf2 + tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ds18b20/main.go + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/lora/lorawan/atcmd/ @md5sum ./build/test.hex tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/as560x/main.go diff --git a/README.md b/README.md index 571b252..4beceb0 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ func main() { ## Currently supported devices -The following 90 devices are supported. +The following 92 devices are supported. | Device Name | Interface Type | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| @@ -76,6 +76,7 @@ The following 90 devices are supported. | [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 | +| [DS18B20 digital thermometer](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C | | [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C | | [ESP32 as WiFi Coprocessor with Arduino nina-fw](https://github.com/arduino/nina-fw) | SPI | | [ESP8266/ESP32 AT Command set for WiFi/TCP/UDP](https://github.com/espressif/esp32-at) | UART | @@ -110,6 +111,7 @@ The following 90 devices are supported. | [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 | +| [One Wire bus system](https://en.wikipedia.org/wiki/1-Wire) | 1-wire | | [P1AM-100 Base Controller](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) | SPI | | [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI | | [PCF8563 real time clock](https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf) | I2C | @@ -121,7 +123,7 @@ The following 90 devices are supported. | [Servo](https://learn.sparkfun.com/tutorials/hobby-servo-tutorial/all) | PWM | | [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO | | [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO | -| [SH1106 OLED display](https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf) | I2C / SPI | +| [SH1106 OLED display](https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf) | I2C / SPI | | [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C | | [SHTC3 Digital Humidity Sensor (RH/T)](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHTC3_Datasheet.pdf) | I2C | | [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI | diff --git a/ds18b20/ds18b20.go b/ds18b20/ds18b20.go new file mode 100644 index 0000000..3553661 --- /dev/null +++ b/ds18b20/ds18b20.go @@ -0,0 +1,89 @@ +// Package ds18b20 provides a driver for the DS18B20 digital thermometer +// +// Datasheet: +// https://www.analog.com/media/en/technical-documentation/data-sheets/DS18B20.pdf +package ds18b20 // import "tinygo.org/x/drivers/ds18b20" + +import ( + "errors" +) + +// Device ROM commands +const ( + CONVERT_TEMPERATURE uint8 = 0x44 + READ_SCRATCHPAD uint8 = 0xBE + WRITE_SCRATCHPAD uint8 = 0x4E +) + +type OneWireDevice interface { + Write(uint8) + Read() uint8 + Select([]uint8) error + Сrc8([]uint8, int) uint8 +} + +// Device wraps a connection to an 1-Wire devices. +type Device struct { + owd OneWireDevice +} + +// Errors list +var ( + errReadTemperature = errors.New("Error: DS18B20. Read temperature error: CRC mismatch.") +) + +func New(owd OneWireDevice) Device { + return Device{ + owd: owd, + } +} + +// Configure. Initializes the device, left for compatibility reasons. +func (d Device) Configure() {} + +// ThermometerResolution sets thermometer resolution from 9 to 12 bits +func (d Device) ThermometerResolution(romid []uint8, resolution uint8) { + if 9 <= resolution && resolution <= 12 { + d.owd.Select(romid) + d.owd.Write(WRITE_SCRATCHPAD) // send three data bytes to scratchpad (TH, TL, and config) + d.owd.Write(0xFF) // to TH + d.owd.Write(0x00) // to TL + d.owd.Write(((resolution - 9) << 5) | 0x1F) // to resolution config + } +} + +// RequestTemperature sends request to device +func (d Device) RequestTemperature(romid []uint8) { + d.owd.Select(romid) + d.owd.Write(CONVERT_TEMPERATURE) +} + +// ReadTemperatureRaw returns the raw temperature. +// ScratchPad memory map: +// byte 0: Temperature LSB +// byte 1: Temperature MSB +func (d Device) ReadTemperatureRaw(romid []uint8) ([]uint8, error) { + spb := make([]uint8, 9) // ScratchPad buffer + d.owd.Select(romid) + d.owd.Write(READ_SCRATCHPAD) + for i := 0; i < 9; i++ { + spb[i] = d.owd.Read() + } + if d.owd.Сrc8(spb, 8) != spb[8] { + return nil, errReadTemperature + } + return spb[:2:2], nil +} + +// ReadTemperature returns the temperature in celsius milli degrees (°C/1000) +func (d Device) ReadTemperature(romid []uint8) (int32, error) { + raw, err := d.ReadTemperatureRaw(romid) + if err != nil { + return 0, err + } + t := int32(uint16(raw[0]) | uint16(raw[1])<<8) + if t&0x8000 == 0x8000 { + t -= 0x10000 + } + return (t * 625 / 10), nil +} diff --git a/examples/ds18b20/main.go b/examples/ds18b20/main.go new file mode 100644 index 0000000..ae45a78 --- /dev/null +++ b/examples/ds18b20/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "encoding/hex" + "machine" + "time" + + "tinygo.org/x/drivers/onewire" + + "tinygo.org/x/drivers/ds18b20" +) + +func main() { + // Define pin for DS18B20 + pin := machine.D2 + + ow := onewire.New(pin) + romIDs, err := ow.Search(onewire.SEARCH_ROM) + if err != nil { + println(err) + } + sensor := ds18b20.New(ow) + + for { + time.Sleep(3 * time.Second) + + println() + println("Device:", machine.Device) + + println() + println("Request Temperature.") + for _, romid := range romIDs { + println("Sensor RomID: ", hex.EncodeToString(romid)) + sensor.RequestTemperature(romid) + } + + // wait 750ms or more for DS18B20 convert T + time.Sleep(1 * time.Second) + + println() + println("Read Temperature") + for _, romid := range romIDs { + raw, err := sensor.ReadTemperatureRaw(romid) + if err != nil { + println(err) + } + println() + println("Sensor RomID: ", hex.EncodeToString(romid)) + println("Temperature Raw value: ", hex.EncodeToString(raw)) + + t, err := sensor.ReadTemperature(romid) + if err != nil { + println(err) + } + println("Temperature in celsius milli degrees (°C/1000): ", t) + } + } +} diff --git a/examples/onewire/main.go b/examples/onewire/main.go new file mode 100644 index 0000000..6322b84 --- /dev/null +++ b/examples/onewire/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "encoding/hex" + "time" + "tinygo.org/x/drivers/onewire" +) + +func main() { + + pin := machine.D2 + + ow := onewire.New(pin) + + for { + time.Sleep(3 * time.Second) + + println() + println("Device:", machine.Device) + + romIDs, err := ow.Search(onewire.SEARCH) + if err != nil { + println(err) + } + for _, romid := range romIDs { + println(hex.EncodeToString(romid)) + } + + if len(romIDs) == 1 { + // only 1 device on bus + r, err := ow.ReadAddress() + if err != nil { + println(err) + } + println(hex.EncodeToString(r)) + + } + + } +} diff --git a/onewire/onewire.go b/onewire/onewire.go new file mode 100644 index 0000000..5a51cf7 --- /dev/null +++ b/onewire/onewire.go @@ -0,0 +1,213 @@ +// Package wire implements the Dallas Semiconductor Corp.'s 1-wire bus system. +// +// Wikipedia: https://en.wikipedia.org/wiki/1-Wire +package onewire // import "tinygo.org/x/drivers/onewire" + +import ( + "errors" + "machine" + "time" +) + +// OneWire ROM commands +const ( + READ_ROM uint8 = 0x33 + MATCH_ROM uint8 = 0x55 + SKIP_ROM uint8 = 0xCC + SEARCH_ROM uint8 = 0xF0 +) + +// Device wraps a connection to an 1-Wire devices. +type Device struct { + p machine.Pin +} + +// Config wraps a configuration to an 1-Wire devices. +type Config struct{} + +// Errors list +var ( + errNoPresence = errors.New("Error: OneWire. No devices on the bus.") + errReadAddress = errors.New("Error: OneWire. Read address error: CRC mismatch.") +) + +// New creates a new GPIO 1-Wire connection. +// The pin must be pulled up to the VCC via a resistor greater than 500 ohms (default 4.7k). +func New(p machine.Pin) Device { + return Device{ + p: p, + } +} + +// Configure initializes the protocol. +func (d *Device) Configure(config Config) {} + +// Reset pull DQ line low, then up. +func (d Device) Reset() error { + d.p.Configure(machine.PinConfig{Mode: machine.PinOutput}) + time.Sleep(480 * time.Microsecond) + d.p.Configure(machine.PinConfig{Mode: machine.PinInput}) + time.Sleep(70 * time.Microsecond) + precence := d.p.Get() + time.Sleep(410 * time.Microsecond) + if precence { + return errNoPresence + } + return nil +} + +// WriteBit transmits a bit to 1-Wire bus. +func (d Device) WriteBit(data uint8) { + d.p.Configure(machine.PinConfig{Mode: machine.PinOutput}) + if data&1 == 1 { // Send '1' + time.Sleep(5 * time.Microsecond) + d.p.Configure(machine.PinConfig{Mode: machine.PinInput}) + time.Sleep(60 * time.Microsecond) + } else { // Send '0' + time.Sleep(60 * time.Microsecond) + d.p.Configure(machine.PinConfig{Mode: machine.PinInput}) + time.Sleep(5 * time.Microsecond) + } +} + +// Write transmits a byte as bit array to 1-Wire bus. (LSB first) +func (d Device) Write(data uint8) { + for i := 0; i < 8; i++ { + d.WriteBit(data) + data >>= 1 + } +} + +// ReadBit receives a bit from 1-Wire bus. +func (d Device) ReadBit() (data uint8) { + d.p.Configure(machine.PinConfig{Mode: machine.PinOutput}) + time.Sleep(3 * time.Microsecond) + d.p.Configure(machine.PinConfig{Mode: machine.PinInput}) + time.Sleep(8 * time.Microsecond) + if d.p.Get() { + data = 1 + } + time.Sleep(60 * time.Microsecond) + return data +} + +// Read receives a byte from 1-Wire bus. (LSB first) +func (d Device) Read() (data uint8) { + for i := 0; i < 8; i++ { + data >>= 1 + data |= d.ReadBit() << 7 + } + return data +} + +// ReadAddress receives a 64-bit unique ROM ID from Device. (LSB first) +// Note: use this if there is only one slave device on the bus. +func (d Device) ReadAddress() ([]uint8, error) { + var romid = make([]uint8, 8) + if err := d.Reset(); err != nil { + return nil, err + } + d.Write(READ_ROM) + for i := 0; i < 8; i++ { + romid[i] = d.Read() + } + if d.Сrc8(romid, 7) != romid[7] { + return nil, errReadAddress + } + return romid, nil +} + +// Select selects the address of the device for communication +func (d Device) Select(romid []uint8) error { + if err := d.Reset(); err != nil { + return err + } + if len(romid) == 0 { + d.Write(SKIP_ROM) + return nil + } + d.Write(MATCH_ROM) + for i := 0; i < 8; i++ { + d.Write(romid[i]) + } + return nil +} + +// Search searches for all devices on the bus. +// Note: max 32 slave devices per bus +func (d Device) Search(cmd uint8) ([][]uint8, error) { + var ( + bit, bit_c uint8 = 0, 0 + bitOffset uint8 = 0 + lastZero uint8 = 0 + lastFork uint8 = 0 + lastAddress = make([]uint8, 8) + romIDs = make([][]uint8, 32) // + romIndex uint8 = 0 + ) + + for i := range romIDs { + romIDs[i] = make([]uint8, 8) + } + + for ok := true; ok; ok = (lastFork != 0) { + if err := d.Reset(); err != nil { + return nil, err + } + + // send search command to bus + d.Write(cmd) + + lastZero = 0 + + for bitOffset = 0; bitOffset < 64; bitOffset++ { + bit = d.ReadBit() // read first address bit + bit_c = d.ReadBit() // read second (complementary) address bit + + if bit == 1 && bit_c == 1 { // no device + return nil, errNoPresence + } + + if bit == 0 && bit_c == 0 { // collision + if bitOffset == lastFork { + bit = 1 + } + if bitOffset < lastFork { + bit = (lastAddress[bitOffset>>3] >> (bitOffset & 0x07)) & 1 + } + if bit == 0 { + lastZero = bitOffset + } + } + + if bit == 0 { + lastAddress[bitOffset>>3] &= ^(1 << (bitOffset & 0x07)) + } else { + lastAddress[bitOffset>>3] |= (1 << (bitOffset & 0x07)) + } + d.WriteBit(bit) + } + lastFork = lastZero + copy(romIDs[romIndex], lastAddress) + romIndex++ + } + return romIDs[:romIndex:romIndex], nil +} + +// Crc8 compute a Dallas Semiconductor 8 bit CRC. +func (d Device) Сrc8(buffer []uint8, size int) (crc uint8) { + // Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0 + // Tiny 2x16 entry CRC table created by Arjen Lentz + // See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table + crc8_table := [...]uint8{ + 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, + 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, + 0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8, + 0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74, + } + for i := 0; i < size; i++ { + crc = buffer[i] ^ crc // just re-using crc as intermediate + crc = crc8_table[crc&0x0f] ^ crc8_table[16+((crc>>4)&0x0f)] + } + return crc +}