diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..6dca202 --- /dev/null +++ b/.clang-format @@ -0,0 +1,10 @@ +Language: Cpp +BasedOnStyle: Google +SeparateDefinitionBlocks: Always +EmptyLineBeforeAccessModifier: LogicalBlock +ColumnLimit: 120 +ReflowComments: true +InsertBraces: true +IndentPPDirectives: BeforeHash +AllowShortFunctionsOnASingleLine: Empty +AllowShortBlocksOnASingleLine: Empty diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7902ec1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,33 @@ +name: build + +on: + push: + paths-ignore: + - "README.md" + branches: + - "*" + pull_request: + branches: + - "*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check conventional commits + uses: webiny/action-conventional-commits@v1.1.0 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Install dependencies + run: pip install --upgrade platformio + + - name: Build + run: | + pio run diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..36547ad --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,44 @@ +name: release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install platformio + + - name: Build + if: success() + run: | + pio run + + - name: Prepare files + run: | + set -x + mkdir -p /tmp/dist + + for path in ./.pio/build/*/; do + name="$(basename "${path}")" + mv "./.pio/build/$name/firmware.hex" "/tmp/dist/$name.hex" + done + + - name: Create release + uses: softprops/action-gh-release@v1 + with: + files: /tmp/dist/*.hex diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b8da3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.pio +.vscode \ No newline at end of file diff --git a/README.md b/README.md index 8ff2a5e..909bd3d 100644 --- a/README.md +++ b/README.md @@ -1 +1,12 @@ -# ds2482-bridge +# DS248x serial bridge + +[![Framework Badge Arduino](https://img.shields.io/badge/framework-arduino-00979C.svg)](https://arduino.cc) + +Simple FW for reading Dallas temperature sensors via DS248x with output as JSON + +Output example +``` +{"0x28FF5A1A31180210":23.06,"0x28FF9725311801EE":23.12} +``` + +> All credits for DS2482 library goes to https://github.com/cybergibbons/DS2482_OneWire/ \ No newline at end of file diff --git a/lib/OneWire.cpp b/lib/OneWire.cpp new file mode 100644 index 0000000..1714eec --- /dev/null +++ b/lib/OneWire.cpp @@ -0,0 +1,412 @@ +#include "OneWire.h" + +#include + + +// Constructor with no parameters for compatability with OneWire lib +OneWire::OneWire() { + // Address is determined by two pins on the DS2482 AD1/AD0 + // Pass 0b00, 0b01, 0b10 or 0b11 + mAddress = 0x18; + mError = 0; + Wire.begin(); +} + +OneWire::OneWire(uint8_t address) { + // Address is determined by two pins on the DS2482 AD1/AD0 + // Pass 0b00, 0b01, 0b10 or 0b11 + mAddress = 0x18 | address; + mError = 0; + Wire.begin(); +} + +uint8_t OneWire::getAddress() { + return mAddress; +} + +uint8_t OneWire::getError() { + return mError; +} + +// Helper functions to make dealing with I2C side easier +void OneWire::begin() { + Wire.beginTransmission(mAddress); +} + +uint8_t OneWire::end() { + return Wire.endTransmission(); +} + +void OneWire::writeByte(uint8_t data) { + Wire.write(data); +} + +uint8_t OneWire::readByte() { + Wire.requestFrom(mAddress, 1u); + return Wire.read(); +} + +// Simply starts and ends an Wire transmission +// If no devices are present, this returns false +uint8_t OneWire::checkPresence() { + begin(); + return !end() ? true : false; +} + +// Performs a global reset of device state machine logic. Terminates any ongoing 1-Wire communication. +void OneWire::deviceReset() { + begin(); + write(DS2482_COMMAND_RESET); + end(); +} + +// Sets the read pointer to the specified register. Overwrites the read pointer position of any 1-Wire communication +// command in progress. +void OneWire::setReadPointer(uint8_t readPointer) { + begin(); + writeByte(DS2482_COMMAND_SRP); + writeByte(readPointer); + end(); +} + +// Read the status register +uint8_t OneWire::readStatus() { + setReadPointer(DS2482_POINTER_STATUS); + return readByte(); +} + +// Read the data register +uint8_t OneWire::readData() { + setReadPointer(DS2482_POINTER_DATA); + return readByte(); +} + +// Read the config register +uint8_t OneWire::readConfig() { + setReadPointer(DS2482_POINTER_CONFIG); + return readByte(); +} + +void OneWire::setStrongPullup() { + writeConfig(readConfig() | DS2482_CONFIG_SPU); +} + +void OneWire::clearStrongPullup() { + writeConfig(readConfig() & !DS2482_CONFIG_SPU); +} + +// Churn until the busy bit in the status register is clear +uint8_t OneWire::waitOnBusy() { + uint8_t status; + + for (int i = 1000; i > 0; i--) { + status = readStatus(); + if (!(status & DS2482_STATUS_BUSY)) { + break; + } + delayMicroseconds(20); + } + + // if we have reached this point and we are still busy, there is an error + if (status & DS2482_STATUS_BUSY) { + mError = DS2482_ERROR_TIMEOUT; + } + + // Return the status so we don't need to explicitly do it again + return status; +} + +// Write to the config register +void OneWire::writeConfig(uint8_t config) { + waitOnBusy(); + begin(); + writeByte(DS2482_COMMAND_WRITECONFIG); + // Write the 4 bits and the complement 4 bits + writeByte(config | (~config) << 4); + end(); + + // This should return the config bits without the complement + if (readByte() != config) { + mError = DS2482_ERROR_CONFIG; + } +} + +// Generates a 1-Wire reset/presence-detect cycle (Figure 4) at the 1-Wire line. The state +// of the 1-Wire line is sampled at tSI and tMSP and the result is reported to the host +// processor through the Status Register, bits PPD and SD. +uint8_t OneWire::wireReset() { + waitOnBusy(); + // Datasheet warns that reset with SPU set can exceed max ratings + clearStrongPullup(); + + waitOnBusy(); + + begin(); + writeByte(DS2482_COMMAND_RESETWIRE); + end(); + + uint8_t status = waitOnBusy(); + + if (status & DS2482_STATUS_SD) { + mError = DS2482_ERROR_SHORT; + } + + return (status & DS2482_STATUS_PPD) ? true : false; +} + +// Writes a single data byte to the 1-Wire line. +void OneWire::wireWriteByte(uint8_t data, uint8_t power) { + waitOnBusy(); + if (power) { + setStrongPullup(); + } + begin(); + writeByte(DS2482_COMMAND_WRITEBYTE); + writeByte(data); + end(); +} + +// Generates eight read-data time slots on the 1-Wire line and stores result in the Read Data Register. +uint8_t OneWire::wireReadByte() { + waitOnBusy(); + begin(); + writeByte(DS2482_COMMAND_READBYTE); + end(); + waitOnBusy(); + return readData(); +} + +// Generates a single 1-Wire time slot with a bit value ā€œVā€ as specified by the bit byte at the 1-Wire line +// (see Table 2). A V value of 0b generates a write-zero time slot (Figure 5); a V value of 1b generates a +// write-one time slot, which also functions as a read-data time slot (Figure 6). In either case, the logic +// level at the 1-Wire line is tested at tMSR and SBR is updated. +void OneWire::wireWriteBit(uint8_t data, uint8_t power) { + waitOnBusy(); + if (power) { + setStrongPullup(); + } + begin(); + writeByte(DS2482_COMMAND_SINGLEBIT); + writeByte(data ? 0x80 : 0x00); + end(); +} + +// As wireWriteBit +uint8_t OneWire::wireReadBit() { + wireWriteBit(1); + uint8_t status = waitOnBusy(); + return status & DS2482_STATUS_SBR ? 1 : 0; +} + +// 1-Wire skip +void OneWire::wireSkip() { + wireWriteByte(WIRE_COMMAND_SKIP); +} + +void OneWire::wireSelect(const uint8_t rom[8]) { + wireWriteByte(WIRE_COMMAND_SELECT); + for (int i = 0; i < 8; i++) { + wireWriteByte(rom[i]); + } +} + +// 1-Wire reset seatch algorithm +void OneWire::wireResetSearch() { + searchLastDiscrepancy = 0; + searchLastDeviceFlag = 0; + + for (int i = 0; i < 8; i++) { + searchAddress[i] = 0; + } +} + +// Set the channel on the DS2482-800 +uint8_t OneWire::setChannel(uint8_t ch) { + uint8_t w[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; + uint8_t r[] = {0xb8, 0xb1, 0xaa, 0xa3, 0x9c, 0x95, 0x8e, 0x87}; + waitOnBusy(); + begin(); + writeByte(0xc3); + writeByte(w[ch]); + end(); + waitOnBusy(); + return readByte() == r[ch]; +} + +// Perform a search of the 1-Wire bus +uint8_t OneWire::wireSearch(uint8_t *address) { + uint8_t direction; + uint8_t last_zero = 0; + + if (searchLastDeviceFlag) { + return 0; + } + + if (!wireReset()) { + return 0; + } + + waitOnBusy(); + + wireWriteByte(WIRE_COMMAND_SEARCH); + + for (uint8_t i = 0; i < 64; i++) { + int searchByte = i / 8; + int searchBit = 1 << i % 8; + + if (i < searchLastDiscrepancy) { + direction = searchAddress[searchByte] & searchBit; + } else { + direction = i == searchLastDiscrepancy; + } + + waitOnBusy(); + begin(); + writeByte(DS2482_COMMAND_TRIPLET); + writeByte(direction ? 0x80 : 0x00); + end(); + + uint8_t status = waitOnBusy(); + + uint8_t id = status & DS2482_STATUS_SBR; + uint8_t comp_id = status & DS2482_STATUS_TSB; + direction = status & DS2482_STATUS_DIR; + + if (id && comp_id) { + return 0; + } else { + if (!id && !comp_id && !direction) { + last_zero = i; + } + } + + if (direction) { + searchAddress[searchByte] |= searchBit; + } else { + searchAddress[searchByte] &= ~searchBit; + } + } + + searchLastDiscrepancy = last_zero; + + if (!last_zero) { + searchLastDeviceFlag = 1; + } + + for (uint8_t i = 0; i < 8; i++) { + address[i] = searchAddress[i]; + } + + return 1; +} + +#if ONEWIRE_CRC8_TABLE +// This table comes from Dallas sample code where it is freely reusable, +// though Copyright (C) 2000 Dallas Semiconductor Corporation +static const uint8_t PROGMEM dscrc_table[] = { + 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, 157, 195, 33, 127, 252, 162, + 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, + 128, 222, 60, 98, 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, 70, 24, + 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, 219, 133, 103, 57, 186, 228, 6, 88, + 25, 71, 165, 251, 120, 38, 196, 154, 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, + 122, 36, 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, 140, 210, 48, 110, + 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, + 111, 49, 178, 236, 14, 80, 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, + 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, 202, 148, 118, 40, 171, 245, + 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, + 244, 170, 72, 22, 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168, 116, 42, + 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53}; + +// +// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM +// and the registers. (note: this might better be done without to +// table, it would probably be smaller and certainly fast enough +// compared to all those delayMicrosecond() calls. But I got +// confused, so I use this table from the examples.) +// +uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) { + uint8_t crc = 0; + + while (len--) { + crc = pgm_read_byte(dscrc_table + (crc ^ *addr++)); + } + return crc; +} +#else +// +// Compute a Dallas Semiconductor 8 bit CRC directly. +// this is much slower, but much smaller, than the lookup table. +// +uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) { + uint8_t crc = 0; + + while (len--) { + uint8_t inbyte = *addr++; + for (uint8_t i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) { + crc ^= 0x8C; + } + inbyte >>= 1; + } + } + return crc; +} +#endif + +// **************************************** +// These are here to mirror the functions in the original OneWire +// **************************************** + +// This is a lazy way of getting compatibility with DallasTemperature +// Not all functions are implemented, only those used in DallasTemeperature +void OneWire::reset_search() { + wireResetSearch(); +} + +uint8_t OneWire::search(uint8_t *newAddr) { + return wireSearch(newAddr); +} + +// Perform a 1-Wire reset cycle. Returns 1 if a device responds +// with a presence pulse. Returns 0 if there is no device or the +// bus is shorted or otherwise held low for more than 250uS +uint8_t OneWire::reset(void) { + return wireReset(); +} + +// Issue a 1-Wire rom select command, you do the reset first. +void OneWire::select(const uint8_t rom[8]) { + wireSelect(rom); +} + +// Issue a 1-Wire rom skip command, to address all on bus. +void OneWire::skip(void) { + wireSkip(); +} + +// Write a byte. +// Ignore the power bit +void OneWire::write(uint8_t v, uint8_t power) { + wireWriteByte(v, power); +} + +// Read a byte. +uint8_t OneWire::read(void) { + return wireReadByte(); +} + +// Read a bit. +uint8_t OneWire::read_bit(void) { + return wireReadBit(); +} + +// Write a bit. +void OneWire::write_bit(uint8_t v) { + wireWriteBit(v); +} + +// **************************************** +// End mirrored functions +// **************************************** diff --git a/lib/OneWire.h b/lib/OneWire.h new file mode 100644 index 0000000..36fc37f --- /dev/null +++ b/lib/OneWire.h @@ -0,0 +1,100 @@ +#ifndef __ONEWIRE_H__ +#define __ONEWIRE_H__ + +#include +#include +#include + +// Chose between a table based CRC (flash expensive, fast) +// or a computed CRC (smaller, slow) +#define ONEWIRE_CRC8_TABLE 1 + +#define DS2482_COMMAND_RESET 0xF0 // Device reset + +#define DS2482_COMMAND_SRP 0xE1 // Set read pointer +#define DS2482_POINTER_STATUS 0xF0 +#define DS2482_STATUS_BUSY (1 << 0) +#define DS2482_STATUS_PPD (1 << 1) +#define DS2482_STATUS_SD (1 << 2) +#define DS2482_STATUS_LL (1 << 3) +#define DS2482_STATUS_RST (1 << 4) +#define DS2482_STATUS_SBR (1 << 5) +#define DS2482_STATUS_TSB (1 << 6) +#define DS2482_STATUS_DIR (1 << 7) +#define DS2482_POINTER_DATA 0xE1 +#define DS2482_POINTER_CONFIG 0xC3 +#define DS2482_CONFIG_APU (1 << 0) +#define DS2482_CONFIG_SPU (1 << 2) +#define DS2482_CONFIG_1WS (1 << 3) + +#define DS2482_COMMAND_WRITECONFIG 0xD2 +#define DS2482_COMMAND_RESETWIRE 0xB4 +#define DS2482_COMMAND_WRITEBYTE 0xA5 +#define DS2482_COMMAND_READBYTE 0x96 +#define DS2482_COMMAND_SINGLEBIT 0x87 +#define DS2482_COMMAND_TRIPLET 0x78 + +#define WIRE_COMMAND_SKIP 0xCC +#define WIRE_COMMAND_SELECT 0x55 +#define WIRE_COMMAND_SEARCH 0xF0 + +#define DS2482_ERROR_TIMEOUT (1 << 0) +#define DS2482_ERROR_SHORT (1 << 1) +#define DS2482_ERROR_CONFIG (1 << 2) + +class OneWire { + public: + OneWire(); + OneWire(uint8_t address); + + uint8_t getAddress(); + uint8_t getError(); + uint8_t checkPresence(); + + void deviceReset(); + void setReadPointer(uint8_t readPointer); + uint8_t readStatus(); + uint8_t readData(); + uint8_t waitOnBusy(); + uint8_t readConfig(); + void writeConfig(uint8_t config); + void setStrongPullup(); + uint8_t setChannel(uint8_t ch); + void clearStrongPullup(); + uint8_t wireReset(); + void wireWriteByte(uint8_t data, uint8_t power = 0); + uint8_t wireReadByte(); + void wireWriteBit(uint8_t data, uint8_t power = 0); + uint8_t wireReadBit(); + void wireSkip(); + void wireSelect(const uint8_t rom[8]); + void wireResetSearch(); + uint8_t wireSearch(uint8_t *address); + + // emulation of original OneWire library + void reset_search(); + uint8_t search(uint8_t *newAddr); + static uint8_t crc8(const uint8_t *addr, uint8_t len); + uint8_t reset(void); + void select(const uint8_t rom[8]); + void skip(void); + void write(uint8_t v, uint8_t power = 0); + uint8_t read(void); + uint8_t read_bit(void); + void write_bit(uint8_t v); + + private: + void begin(); + uint8_t end(); + void writeByte(uint8_t); + uint8_t readByte(); + + uint8_t mAddress; + uint8_t mError; + + uint8_t searchAddress[8]; + uint8_t searchLastDiscrepancy; + uint8_t searchLastDeviceFlag; +}; + +#endif diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..649f25a --- /dev/null +++ b/platformio.ini @@ -0,0 +1,28 @@ +[common] +build_flags = + '-D ONEWIRE_CRC8_TABLE=1' +lib_deps = + ./lib + +[env:ATtiny1614] +platform = atmelmegaavr +framework = arduino +board = ATtiny1614 +board_build.f_cpu = 8000000L +board_hardware.oscillator = internal +board_hardware.bod = 4.3v +build_flags = + ${common.build_flags} +lib_deps = + ${common.lib_deps} + + +[env:UNO] +platform = atmelavr +board = uno +framework = arduino +monitor_speed = 115200 +build_flags = + ${common.build_flags} +lib_deps = + ${common.lib_deps} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..627c480 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,120 @@ +#include +#include +#include + +OneWire oneWire; +uint8_t rom[8] = {0}; +uint8_t data[9] = {0}; + +void (*resetFunc)(void) = 0; + +void printAddress(uint8_t deviceAddress[]) { + Serial.print(F("\"0x")); + for (uint8_t i = 0; i < 8; i++) { + // zero pad the address + if (deviceAddress[i] < 16) { + Serial.print(F("0")); + } + Serial.print(deviceAddress[i], HEX); + } + Serial.print(F("\"")); +} + +void checkPresence() { + if (!oneWire.checkPresence()) { + Serial.println(F("{\"error\": \"no_device\"}")); + delay(5000); + resetFunc(); + } +} + +void setup() { + Serial.begin(115200); + + checkPresence(); + + oneWire.deviceReset(); +} + +void loop() { + checkPresence(); + Serial.print("{"); + bool first = true; + + for (int i = 0; i < 1; i++) { + oneWire.setChannel(i); + + if (!oneWire.reset()) { + continue; + } + + oneWire.skip(); + + // start conversion + oneWire.write(0x44, true); + + // wait for conversion + delay(750); + + oneWire.reset(); + + while (oneWire.wireSearch(rom)) { + // Read Scratchpad + oneWire.write(0xBE); + + for (uint8_t i = 0; i < 9; i++) { + data[i] = oneWire.read(); + } + + // check CRC + if (!OneWire::crc8(data, 8)) { + continue; + } + + int16_t raw = (data[1] << 8) | data[0]; + + switch (rom[0]) { + case 0x10: { // DS18S20 + raw = raw << 3; + + if (data[7] == 0x10) { + raw = (raw & 0xFFF0) + 12 - data[6]; + } + } break; + + case 0x28: { // DS18B20 + + char cfg = (data[4] & 0x60); // default is 12 bit resolution + + if (cfg == 0x00) { // 9 bit resolution + raw &= ~7; + + } else if (cfg == 0x20) { // 10 bit res + raw &= ~3; + + } else if (cfg == 0x40) { // 11 bit res + raw &= ~1; + } + } + + default: + break; + } + + // output data + if (!first) { + Serial.print(F(",")); + } + + first = false; + + printAddress(rom); + Serial.print(F(":")); + Serial.print(((float)raw) / 16, 2); + } + + oneWire.reset_search(); + } + Serial.println(F("}")); + delay(20000); +}