diff --git a/README.md b/README.md index d2973e0..806083d 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ The TWI library is an abstract interface for I2C device drivers. The library includes a hardware and software bus manager, and example device drivers for I2C Humidity and Temperature Sensor (Si70XX), -Remote 8-bit I/O expander (PCF8574/PCF8574A), and Digital Pressure -Sensor (BMP085). +Remote 8-bit I/O expander (PCF8574/PCF8574A), Digital Pressure +Sensor (BMP085), and Single/Multi-Channel 1-Wire Master (DS2482). The software bus manager implementation of the TWI interface uses the [Arduino-GPIO](https://github.com/mikaelpatel/Arduino-GPIO) @@ -20,7 +20,7 @@ Device driver mutex allows a task to complete a device driver function in a synchronized manner when using the [Arduino-Scheduler](https://github.com/mikaelpatel/Arduino-Scheduler). -Version: 1.8 +Version: 1.9 ## Classes @@ -31,11 +31,13 @@ Version: 1.8 * [Digital Pressure Sensor, BMP085](./src/Driver/BMP085.h) * [Humidity and Temperature Sensor, Si70XX](./src/Driver/Si70XX.h) * [Remote 8-bit I/O expander, PCF8574](./src/Driver/PCF8574.h) +* [Single/Multi-Channel 1-Wire Master, DS2482-100/800](./src/Driver/DS2482.h) ## Example Sketches * [Scanner](./examples/Scanner) * [BMP085](./examples/BMP085) +* [DS2482](./examples/DS2482) * [PCF8574](./examples/PCF8574) * [Si7021](./examples/Si7021) diff --git a/examples/DS2482/DS2482.ino b/examples/DS2482/DS2482.ino new file mode 100644 index 0000000..1359513 --- /dev/null +++ b/examples/DS2482/DS2482.ino @@ -0,0 +1,186 @@ +#include "GPIO.h" +#include "TWI.h" +#include "Driver/DS2482.h" + +// Configure: Software or Hardware TWI +// #define USE_SOFTWARE_TWI +#if defined(USE_SOFTWARE_TWI) +#include "Software/TWI.h" +#if defined(SAM) +Software::TWI twi; +#else +Software::TWI twi; +#endif +#else +#include "Hardware/TWI.h" +Hardware::TWI twi; +#endif + +DS2482 owi(twi); + +const size_t ROM_MAX = 8; +const uint8_t CHARBITS = 8; + +// 1-Wire ROM and DS18B20 commands +enum { + SEARCH_ROM = 0xF0, + READ_ROM = 0x33, + MATCH_ROM = 0x55, + SKIP_ROM = 0xCC, + ALARM_SEARCH = 0xEC, + CONVERT_T = 0x44, + READ_SCRATCHPAD = 0xBE +}; + +// DS18B20 scratchpad +struct scratchpad_t { + int16_t temperature; + int8_t high_trigger; + int8_t low_trigger; + uint8_t configuration; + uint8_t reserved[3]; + uint8_t crc; +}; + +// 1-Wire CRC calculation +uint8_t one_wire_crc_update(uint8_t crc, uint8_t data) +{ + crc = crc ^ data; + for (uint8_t i = 0; i < 8; i++) { + if (crc & 0x01) + crc = (crc >> 1) ^ 0x8C; + else + crc >>= 1; + } + return (crc); +} + +// Check assertion to be true, otherwise print line and expression +#define ASSERT(expr) \ + do { \ + if (!(expr)) { \ + Serial.print(__LINE__); \ + Serial.println(F(":assert:" #expr)); \ + Serial.flush(); \ + exit(0); \ + } \ + } while (0) + + +// Print and evaluate expression +#define TRACE(expr) \ + do { \ + Serial.print(#expr "="); \ + Serial.println(expr); \ + } while (0) + + +void setup() +{ + Serial.begin(57600); + while (!Serial); + + // Reset and configure the TWI 1-Wire Master + ASSERT(owi.device_reset()); + ASSERT(owi.write_configuration()); + + // Read and print registers + uint8_t config; + ASSERT(owi.set_read_pointer(owi.CONFIGURATION_REGISTER, config)); + TRACE(config); + + uint8_t data; + ASSERT(owi.set_read_pointer(owi.READ_DATA_REGISTER, data)); + TRACE(data); + + uint8_t status; + ASSERT(owi.set_read_pointer(owi.STATUS_REGISTER, status)); + TRACE(status); + +#if defined(DS2482_800) + uint8_t channel; + ASSERT(owi.set_read_pointer(owi.CHANNEL_SELECTION_REGISTER, channel)); + TRACE(channel); +#endif + + // Read and print rom from device on 1-Wire bus + uint8_t rom[ROM_MAX] = { 0 }; + uint8_t crc = 0; + ASSERT(owi.one_wire_reset()); + ASSERT(owi.one_wire_write_byte(READ_ROM)); + Serial.print(F("read_rom=")); + for (size_t i = 0; i < sizeof(rom); i++) { + owi.one_wire_read_byte(rom[i]); + if (rom[i] < 0x10) Serial.print(0); + Serial.print(rom[i], HEX); + crc = one_wire_crc_update(crc, rom[i]); + } + Serial.println(); + ASSERT(crc == 0); + + // Search device on 1-Wire bus and print rom + uint8_t bits = 0; + uint8_t ix = 0; + uint8_t value = 0; + uint8_t dir = 0; + int8_t res = 0; + bool id; + bool nid; + crc = 0; + ASSERT(owi.one_wire_reset()); + ASSERT(owi.one_wire_write_byte(SEARCH_ROM)); + Serial.print(F("search_rom=")); + do { + res = owi.one_wire_triplet(dir); + if (res < 0) break; + id = (res & 1) != 0; + nid = (res & 2) != 0; + value = (value >> 1); + if (dir) value |= 0x80; + bits += 1; + if (bits == CHARBITS) { + rom[ix] = value; + if (rom[ix] < 0x10) Serial.print(0); + Serial.print(rom[ix], HEX); + crc = one_wire_crc_update(crc, rom[ix]); + ix += 1; + bits = 0; + value = 0; + } + } while (id != nid); + Serial.println(); + ASSERT(crc == 0); +} + +void loop() +{ + // Convert and read temperature + ASSERT(owi.one_wire_reset()); + ASSERT(owi.one_wire_write_byte(SKIP_ROM)); + ASSERT(owi.one_wire_write_byte(CONVERT_T)); + delay(750); + + ASSERT(owi.one_wire_reset()); + ASSERT(owi.one_wire_write_byte(SKIP_ROM)); + ASSERT(owi.one_wire_write_byte(READ_SCRATCHPAD)); + + // Print scatchpad and calculate check sum + scratchpad_t scratchpad; + uint8_t* p = (uint8_t*) &scratchpad; + uint8_t crc = 0; + Serial.print(F("read_scratchpad=")); + for (size_t i = 0; i < sizeof(scratchpad); i++) { + ASSERT(owi.one_wire_read_byte(p[i])); + if (i == sizeof(scratchpad) - 1) Serial.print(F(",crc=")); + if (p[i] < 0x10) Serial.print('0'); + Serial.print(p[i], HEX); + crc = one_wire_crc_update(crc, p[i]); + } + ASSERT(crc == 0); + + // Print temperature (convert from fixed to floating point number) + Serial.print(','); + float temperature = scratchpad.temperature * 0.0625; + TRACE(temperature); + delay(2000); +} diff --git a/library.properties b/library.properties index 1e93d0e..1132484 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=Arduino-TWI -version=1.8 +version=1.9 author=Mikael Patel maintainer=Mikael Patel sentence=Two-Wire Interface (TWI) library for Arduino. -paragraph=The TWI library is an abstract interface for I2C device drivers. The library includes a hardware and software implementation, and example device drivers for I2C Humidity and Temperature Sensor (Si70XX), Remote 8-bit I/O expander (PCF8574/PCF8574A), and Digital Pressure Sensor (BMP085). +paragraph=The TWI library is an abstract interface for I2C device drivers. The library includes a hardware and software implementation, and example device drivers for I2C Humidity and Temperature Sensor (Si70XX), Remote 8-bit I/O expander (PCF8574/PCF8574A), Digital Pressure Sensor (BMP085), and Single/Multi-Channel 1-Wire Master (DS2482). category=Communication url=https://github.com/mikaelpatel/Arduino-TWI architectures=avr,sam diff --git a/mainpage.dox b/mainpage.dox index 6f259ed..531fff8 100644 --- a/mainpage.dox +++ b/mainpage.dox @@ -4,9 +4,10 @@ The TWI library is an abstract interface for I2C device drivers. The library includes a Hardware::TWI and Software::TWI bus manager implementation, and example device drivers for I2C Humidity and Temperature Sensor (Si70XX), Remote 8-bit I/O expander (PCF8574 and -PCF8574A), and and Digital Pressure Sensor (BMP085). +PCF8574A), Digital Pressure Sensor (BMP085), and Single/Multi-Channel +1-Wire Master (DS2482). -Version: 1.8 +Version: 1.9 */ /** @page License diff --git a/src/Driver/DS2482.h b/src/Driver/DS2482.h new file mode 100644 index 0000000..c97aa1b --- /dev/null +++ b/src/Driver/DS2482.h @@ -0,0 +1,413 @@ +/** + * @file DS2482.h + * @version 1.0 + * + * @section License + * Copyright (C) 2017, Mikael Patel + * + * This library is free hardware; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Hardware Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#ifndef DS2482_H +#define DS2482_H + +#include "TWI.h" + +/** + * TWI Device Driver for DS2482, Single-Channel 1-Wire Master, TWI to + * OWI Bridge Device. + */ +class DS2482 : protected TWI::Device { +public: + /** + * Construct one wire bus manager for DS2482. + * @param[in] twi bus manager. + * @param[in] subaddr sub-address for device (0..3). + */ + DS2482(TWI& twi, uint8_t subaddr = 0) : + TWI::Device(twi, 0x18 | (subaddr & 0x03)) + { + } + + /** + * Reset the one wire bus and check that at least one device is + * presence. + * @return true(1) if successful otherwise false(0). + */ + bool one_wire_reset() + { + status_t status; + uint8_t cmd; + int count; + bool res = false; + + // Issue one wire reset command + cmd = ONE_WIRE_RESET; + if (!TWI::Device::acquire()) return (false); + count = TWI::Device::write(&cmd, sizeof(cmd)); + if (count != sizeof(cmd)) goto error; + if (one_wire_await(status)) res = status.PPD; + + error: + if (!TWI::Device::release()) return (false); + return (res); + } + + /** + * Read a single bit from one wire bus. Returns value and true(1) if + * successful otherwise false(0). + * @param[out] value bit read. + * @return true(1) if successful otherwise false(0). + */ + bool one_wire_read_bit(bool& value) + { + status_t status; + uint8_t cmd[2]; + int count; + bool res = false; + + // Issue one wire single bit command with read data time slot + cmd[0] = ONE_WIRE_SINGLE_BIT; + cmd[1] = 0x80; + if (!TWI::Device::acquire()) return (false); + count = TWI::Device::write(cmd, sizeof(cmd)); + if (count != sizeof(cmd)) goto error; + + // Wait for one wire operation to complete + res = one_wire_await(status); + value = status.SBR; + + error: + if (!TWI::Device::release()) return (false); + return (res); + } + + /** + * Write a single bit to one wire bus. Returns true if successful + * otherwise false. + * @param[in] value bit to write. + * @return bool. + */ + bool one_wire_write_bit(bool value) + { + status_t status; + uint8_t cmd[2]; + int count; + bool res = false; + + // Issue one wire single bit command with given data + cmd[0] = ONE_WIRE_SINGLE_BIT; + cmd[1] = (value ? 0x80 : 0x00); + if (!TWI::Device::acquire()) return (false); + count = TWI::Device::write(cmd, sizeof(cmd)); + if (count != sizeof(cmd)) goto error; + res = one_wire_await(status); + + error: + if (!TWI::Device::release()) return (false); + return (res); + } + + /** + * Read byte (8-bits) from one wire bus. Returns value and true(1) + * if successful otherwise false(0). + * @param[out] value bit read. + * @return true(1) if successful otherwise false(0). + */ + virtual bool one_wire_read_byte(uint8_t& value) + { + status_t status; + uint8_t cmd; + int count; + + // Issue one wire read byte command + cmd = ONE_WIRE_READ_BYTE; + if (!TWI::Device::acquire()) return (false); + count = TWI::Device::write(&cmd, sizeof(cmd)); + if (count != sizeof(cmd)) goto error; + + // Wait for one wire operation to complete + if (!one_wire_await(status)) goto error; + if (!TWI::Device::release()) return (false); + + // Read data register value + return (set_read_pointer(READ_DATA_REGISTER, value)); + + error: + TWI::Device::release(); + return (false); + } + + /** + * Write byte (8-bits) to one wire bus. Returns true(1) if + * successful otherwise false(0). + * @param[in] value byte write. + * @return true(1) if successful otherwise false(0). + */ + bool one_wire_write_byte(uint8_t value) + { + status_t status; + uint8_t cmd[2]; + int count; + bool res = false; + + // Issue one wire write byte command with given data + cmd[0] = ONE_WIRE_WRITE_BYTE; + cmd[1] = value; + if (!TWI::Device::acquire()) return (res); + count = TWI::Device::write(cmd, sizeof(cmd)); + if (count != sizeof(cmd)) goto error; + res = one_wire_await(status); + + error: + if (!TWI::Device::release()) return (false); + return (res); + } + + /** + * Search (rom and alarm) support function. Reads 2-bits and writes + * given direction 1-bit value when discrepancy 0b00 read. Writes + * one(1) when 0b01 read, zero(0) on 0b10. Reading 0b11 is an error + * state. + * @param[in,out] dir bit to write when discrepancy read. + * @return 2-bits read and bit written. + */ + int8_t one_wire_triplet(uint8_t& dir) + { + status_t status; + uint8_t cmd[2]; + int count; + + // Issue one wire single bit command with given data + cmd[0] = ONE_WIRE_TRIPLET; + cmd[1] = (dir ? 0x80 : 0x00); + if (!TWI::Device::acquire()) return (-1); + count = TWI::Device::write(cmd, sizeof(cmd)); + if (count != sizeof(cmd)) goto error; + + // Wait for one wire operation to complete + if (!one_wire_await(status)) goto error; + if (!TWI::Device::release()) return (-1); + dir = status.DIR; + return ((status >> 5) & 0x3); + + error: + TWI::Device::release(); + return (-1); + } + + /** + * Global reset of device state machine logic. Returns true if + * successful otherwise false. + * @return bool. + */ + bool device_reset() + { + status_t status; + uint8_t cmd; + int count; + + // Issue device reset command + cmd = DEVICE_RESET; + if (!TWI::Device::acquire()) return (false); + count = TWI::Device::write(&cmd, sizeof(cmd)); + if (count != sizeof(cmd)) goto error; + + // Check status register for device reset + count = TWI::Device::read(&status, sizeof(status)); + if (!TWI::Device::release()) return (false); + return ((count == sizeof(status)) && status.RST); + + error: + TWI::Device::release(); + return (false); + } + + /** + * Configure one wire bus master with given parameters. Returns true + * if successful otherwise false. + * @param[in] apu active pull-up (default true). + * @param[in] spu strong pull-up (default false). + * @param[in] iws one wire speed (default false). + * @return bool. + */ + bool write_configuration(bool apu = true, bool spu = false, bool iws = false) + { + config_t config; + status_t status; + uint8_t cmd[2]; + int count; + + // Set configuration bit-fields + config.APU = apu; + config.SPU = spu; + config.IWS = iws; + config.COMP = ~config; + + // Issue write configuration command with given setting + cmd[0] = WRITE_CONGIFURATION; + cmd[1] = config; + if (!TWI::Device::acquire()) return (false); + count = TWI::Device::write(cmd, sizeof(cmd)); + if (count != sizeof(cmd)) goto error; + + // Read status and check configuration + count = TWI::Device::read(&status, sizeof(status)); + if (!TWI::Device::release()) return (false); + return ((count == sizeof(status)) && !status.RST); + + error: + TWI::Device::release(); + return (false); + } + + /** + * Device Registers, pp. 5. Valid Pointer Codes, pp. 10. + */ + enum Register { + STATUS_REGISTER = 0xf0, + READ_DATA_REGISTER = 0xe1, + CHANNEL_SELECTION_REGISTER = 0xd2, + CONFIGURATION_REGISTER = 0xc3 + } __attribute__((packed)); + + /** + * Set the read pointer to the specified register. Return register + * value or negative error code. + * @param[in] addr register address. + * @param[out] value read. + * @return true(1) if successful otherwise false(0). + */ + bool set_read_pointer(Register addr, uint8_t& value) + { + uint8_t cmd[2]; + int count; + bool res = false; + + // Issue set read pointer command with given pointer + cmd[0] = SET_READ_POINTER; + cmd[1] = (uint8_t) addr; + if (!TWI::Device::acquire()) return (false); + count = TWI::Device::write(cmd, sizeof(cmd)); + if (count != sizeof(cmd)) goto error; + + // Read register value + count = TWI::Device::read(&value, sizeof(value)); + res = (count == sizeof(value)); + + error: + if (!TWI::Device::release()) return (false); + return (res); + } + + /** + * Select given channel (DS2482-800). Return true if successful + * otherwise false. + * @param[in] chan channel number (0..7). + * @return bool. + */ + bool channel_select(uint8_t chan) + { + uint8_t cmd[2]; + int count; + + // Check channel number + if (chan > 7) return (false); + + // Issue channel select command with channel code + cmd[0] = CHANNEL_SELECT; + cmd[1] = (~chan << 4) | chan; + if (!TWI::Device::acquire()) return (false); + count = TWI::Device::write(cmd, sizeof(cmd)); + if (!TWI::Device::release()) return (false); + return (count == sizeof(cmd)); + } + +protected: + /** + * Function Commands, pp. 9-15. + */ + enum { + DEVICE_RESET = 0xf0, //!< Device Reset. + SET_READ_POINTER = 0xe1, //!< Set Read Pointer. + WRITE_CONGIFURATION = 0xd2, //!< Write Configuration. + CHANNEL_SELECT = 0xc3, //!< Channel Select. + ONE_WIRE_RESET = 0xb4, //!< 1-Wire Reset. + ONE_WIRE_SINGLE_BIT = 0x87, //!< 1-Wire Single Bit. + ONE_WIRE_WRITE_BYTE = 0xa5, //!< 1-Wire Write Byte. + ONE_WIRE_READ_BYTE = 0x96, //!< 1-Wire Read Byte. + ONE_WIRE_TRIPLET = 0x78 //!< 1-Wire Triplet. + } __attribute__((packed)); + + /** + * Status Register, bit-fields, pp. 8-9. + */ + union status_t { + uint8_t as_uint8; //!< Unsigned byte access. + struct { //!< Bitfield access (little endian). + uint8_t IWB:1; //!< 1-Wire Busy. + uint8_t PPD:1; //!< Presence-Pulse Detect. + uint8_t SD:1; //!< Short Detected. + uint8_t LL:1; //!< Logic Level. + uint8_t RST:1; //!< Device Reset. + uint8_t SBR:1; //!< Single Bit Result. + uint8_t TSB:1; //!< Triplet Second Bit. + uint8_t DIR:1; //!< Branch Direction Taken. + }; + operator uint8_t() + { + return (as_uint8); + } + }; + + /** + * Configuration Register, bit-fields, pp. 5-6. + */ + union config_t { + uint8_t as_uint8; //!< Unsigned byte access. + struct { //!< Bitfield access (little endian). + uint8_t APU:1; //!< Active Pullup. + uint8_t ZERO:1; //!< Always Zero(0). + uint8_t SPU:1; //!< Strong Pullup. + uint8_t IWS:1; //!< 1-Wire Speed. + uint8_t COMP:4; //!< Complement of lower 4-bits. + }; + operator uint8_t() + { + return (as_uint8); + } + config_t() + { + as_uint8 = 0; + } + }; + + /** Number of one-wire polls */ + static const int POLL_MAX = 20; + + /** + * Wait for the one wire operation to complete. Poll the device + * status. + * @param[out] status device status on completion. + * @return true(1) if successful otherwise false(0). + */ + bool one_wire_await(status_t& status) + { + // Wait for one wire operation to complete + for (int i = 0; i < POLL_MAX; i++) { + int count = TWI::Device::read(&status, sizeof(status)); + if (count == sizeof(status) && !status.IWB) return (true); + } + return (false); + } + +}; +#endif