diff --git a/units/1wire/_ow_checksum.c b/units/1wire/_ow_checksum.c new file mode 100644 index 0000000..bdd68a6 --- /dev/null +++ b/units/1wire/_ow_checksum.c @@ -0,0 +1,34 @@ +// +// Created by MightyPork on 2018/02/01. +// + +#include "platform.h" +#include "_ow_checksum.h" + +static inline uint8_t crc8_bits(uint8_t data) +{ + uint8_t crc = 0; + if(data & 1) crc ^= 0x5e; + if(data & 2) crc ^= 0xbc; + if(data & 4) crc ^= 0x61; + if(data & 8) crc ^= 0xc2; + if(data & 0x10) crc ^= 0x9d; + if(data & 0x20) crc ^= 0x23; + if(data & 0x40) crc ^= 0x46; + if(data & 0x80) crc ^= 0x8c; + return crc; +} + +static uint8_t crc8_add(uint8_t cksum, uint8_t byte) +{ + return crc8_bits(byte ^ cksum); +} + +uint8_t ow_checksum(const uint8_t *buff, uint16_t len) +{ + uint8_t cksum = 0; + for(uint16_t i = 0; i < len; i++) { + cksum = crc8_add(cksum, buff[i]); + } + return cksum; +} diff --git a/units/1wire/_ow_checksum.h b/units/1wire/_ow_checksum.h new file mode 100644 index 0000000..24dc461 --- /dev/null +++ b/units/1wire/_ow_checksum.h @@ -0,0 +1,23 @@ +// +// Created by MightyPork on 2018/02/01. +// + +#ifndef GEX_F072_OW_CHECKSUM_H +#define GEX_F072_OW_CHECKSUM_H + +#include + +/** + * Compute a 1-wire type checksum. + * If the buffer includes the checksum, the result should be 0. + * + * (this function may be used externally, or you can delete the implementation + * from the c file if another implementation is already available) + * + * @param[in] buf - buffer of bytes to verify + * @param[in] len - buffer length + * @return checksum + */ +uint8_t ow_checksum(const uint8_t *buf, uint16_t len); + +#endif //GEX_F072_OW_CHECKSUM_H diff --git a/units/1wire/_ow_commands.h b/units/1wire/_ow_commands.h index 4270bde..4283b33 100644 --- a/units/1wire/_ow_commands.h +++ b/units/1wire/_ow_commands.h @@ -5,10 +5,6 @@ #ifndef GEX_F072_OW_COMMANDS_H #define GEX_F072_OW_COMMANDS_H -#ifndef OW_INTERNAL -#error bad include! -#endif - #define OW_ROM_SEARCH 0xF0 #define OW_ROM_READ 0x33 #define OW_ROM_MATCH 0x55 diff --git a/units/1wire/_ow_internal.h b/units/1wire/_ow_internal.h index 0b89a7f..135c629 100644 --- a/units/1wire/_ow_internal.h +++ b/units/1wire/_ow_internal.h @@ -9,7 +9,7 @@ #error bad include! #endif -#include "_ow_commands.h" +#include "_ow_search.h" /** Private data structure */ struct priv { @@ -24,63 +24,9 @@ struct priv { bool busy; // flag used when the timer is running uint32_t busyStart; TF_ID busyRequestId; + struct ow_search_state searchState; }; // Prototypes -/** - * Reset the 1-wire bus - */ -bool ow_reset(Unit *unit); - -/** - * Write a bit to the 1-wire bus - */ -void ow_write_bit(Unit *unit, bool bit); - -/** - * Read a bit from the 1-wire bus - */ -bool ow_read_bit(Unit *unit); - -/** - * Write a byte to the 1-wire bus - */ -void ow_write_u8(Unit *unit, uint8_t byte); - -/** - * Write a halfword to the 1-wire bus - */ -void ow_write_u16(Unit *unit, uint16_t halfword); - -/** - * Write a word to the 1-wire bus - */ -void ow_write_u32(Unit *unit, uint32_t word); - -/** - * Write a doubleword to the 1-wire bus - */ -void ow_write_u64(Unit *unit, uint64_t dword); - -/** - * Read a byte form the 1-wire bus - */ -uint8_t ow_read_u8(Unit *unit); - -/** - * Read a halfword form the 1-wire bus - */ -uint16_t ow_read_u16(Unit *unit); - -/** - * Read a word form the 1-wire bus - */ -uint32_t ow_read_u32(Unit *unit); - -/** - * Read a doubleword form the 1-wire bus - */ -uint64_t ow_read_u64(Unit *unit); - #endif //GEX_F072_OW_INTERNAL_H diff --git a/units/1wire/_ow_ll.c b/units/1wire/_ow_low_level.c similarity index 99% rename from units/1wire/_ow_ll.c rename to units/1wire/_ow_low_level.c index 4e7913e..16e2194 100644 --- a/units/1wire/_ow_ll.c +++ b/units/1wire/_ow_low_level.c @@ -5,10 +5,10 @@ // #include "platform.h" -#include "unit_base.h" #define OW_INTERNAL #include "_ow_internal.h" +#include "_ow_low_level.h" static inline void ow_pull_high(Unit *unit) { diff --git a/units/1wire/_ow_low_level.h b/units/1wire/_ow_low_level.h new file mode 100644 index 0000000..cddf896 --- /dev/null +++ b/units/1wire/_ow_low_level.h @@ -0,0 +1,67 @@ +// +// Created by MightyPork on 2018/02/01. +// + +#ifndef GEX_F072_OW_LOW_LEVEL_H +#define GEX_F072_OW_LOW_LEVEL_H + +#include "platform.h" +#include "unit_base.h" +#include "_ow_low_level.h" + +/** + * Reset the 1-wire bus + */ +bool ow_reset(Unit *unit); + +/** + * Write a bit to the 1-wire bus + */ +void ow_write_bit(Unit *unit, bool bit); + +/** + * Read a bit from the 1-wire bus + */ +bool ow_read_bit(Unit *unit); + +/** + * Write a byte to the 1-wire bus + */ +void ow_write_u8(Unit *unit, uint8_t byte); + +/** + * Write a halfword to the 1-wire bus + */ +void ow_write_u16(Unit *unit, uint16_t halfword); + +/** + * Write a word to the 1-wire bus + */ +void ow_write_u32(Unit *unit, uint32_t word); + +/** + * Write a doubleword to the 1-wire bus + */ +void ow_write_u64(Unit *unit, uint64_t dword); + +/** + * Read a byte form the 1-wire bus + */ +uint8_t ow_read_u8(Unit *unit); + +/** + * Read a halfword form the 1-wire bus + */ +uint16_t ow_read_u16(Unit *unit); + +/** + * Read a word form the 1-wire bus + */ +uint32_t ow_read_u32(Unit *unit); + +/** + * Read a doubleword form the 1-wire bus + */ +uint64_t ow_read_u64(Unit *unit); + +#endif //GEX_F072_OW_LOW_LEVEL_H diff --git a/units/1wire/_ow_search.c b/units/1wire/_ow_search.c new file mode 100644 index 0000000..bd0e2ee --- /dev/null +++ b/units/1wire/_ow_search.c @@ -0,0 +1,121 @@ +// +// Created by MightyPork on 2018/02/01. +// + +#include "platform.h" +#include "unit_1wire.h" + +#define OW_INTERNAL +#include "_ow_search.h" +#include "_ow_internal.h" +#include "_ow_low_level.h" +#include "_ow_checksum.h" +#include "_ow_commands.h" + +void ow_search_init(Unit *unit, uint8_t command, bool test_checksums) +{ + if (unit->driver != &UNIT_1WIRE) + trap("Wrong unit type - %s", unit->driver->name); + + assert_param(command == OW_ROM_SEARCH || command == OW_ROM_ALM_SEARCH); + + struct priv *priv = unit->data; + struct ow_search_state *state = &priv->searchState; + + state->prev_last_fork = 64; + memset(state->prev_code, 0, 8); + state->status = OW_SEARCH_MORE; + state->command = command; + state->first = true; + state->test_checksums = test_checksums; +} + +uint16_t ow_search_run(Unit *unit, ow_romcode_t *codes, uint16_t capacity) +{ + if (unit->driver != &UNIT_1WIRE) + trap("Wrong unit type - %s", unit->driver->name); + + assert_param(codes); + + struct priv *priv = unit->data; + struct ow_search_state *state = &priv->searchState; + + if (state->status != OW_SEARCH_MORE) return 0; + + uint16_t found_devices = 0; + + while (found_devices < capacity) { + uint8_t index = 0; + ow_romcode_t code = {}; + int8_t last_fork = -1; + + // Start a new transaction. Devices respond to reset + if (!ow_reset(unit)) { + state->status = OW_SEARCH_FAILED; + goto done; + } + // Send the search command (SEARCH_ROM, SEARCH_ALARM) + ow_write_u8(unit, state->command); + + uint8_t *code_byte = &code[0]; + + bool p, n; + while (index != 64) { + // Read a bit and its complement + p = ow_read_bit(unit); + n = ow_read_bit(unit); + + if (!p && !n) { + // A fork: there are devices on the bus with different bit value + // (the bus is open-drain, in both cases one device pulls it low) + if ((found_devices > 0 || !state->first) && index < state->prev_last_fork) { + // earlier than the last fork, take the same turn as before + p = ow_code_getbit(state->prev_code, index); + if (!p) last_fork = index; // remember for future runs, 1 not explored yet + } + else if (index == state->prev_last_fork) { + p = 1; // both forks are now exhausted + } + else { // a new fork + last_fork = index; + } + } + else if (p && n) { + // No devices left connected - this doesn't normally happen + state->status = OW_SEARCH_FAILED; + goto done; + } + + // All devices have a matching bit here, or it was resolved in a fork + if (p) *code_byte |= (1 << (index & 7)); + ow_write_bit(unit, p); + + index++; + if((index & 7) == 0) { + code_byte++; + } + } + + memcpy(state->prev_code, code, 8); + + if (!state->test_checksums || 0 == ow_checksum(code, 8)) { + // Record a found address + memcpy(codes[found_devices], code, 8); + found_devices++; + } + + // Stop condition + if (last_fork == -1) { + state->status = OW_SEARCH_DONE; + goto done; + } + + state->prev_last_fork = last_fork; + } + +done: + state->first = false; + return found_devices; +} + + diff --git a/units/1wire/_ow_search.h b/units/1wire/_ow_search.h new file mode 100644 index 0000000..4a9f4d1 --- /dev/null +++ b/units/1wire/_ow_search.h @@ -0,0 +1,81 @@ +// +// Created by MightyPork on 2018/02/01. +// + +#ifndef GEX_F072_OW_SEARCH_H +#define GEX_F072_OW_SEARCH_H + +#ifndef OW_INTERNAL +#error bad include! +#endif + +#include +#include +#include "unit_base.h" + +// -------------------------------------------------------------------------------------- + +/** + * Data type holding a romcode + */ +typedef uint8_t ow_romcode_t[8]; + +/** + * Get a single bit from a romcode + */ +#define ow_code_getbit(code, index) (bool)((code)[(index) >> 3] & (1 << ((index) & 7))) + +/** + * Convert to unsigned 64-bit integer + * (works only on little-endian systems - eg. OK on x86/x86_64, not on PowerPC) + */ +#define ow_romcode_to_u64(code) (*((uint64_t *) (void *)(code))) + +/** + * States of the search algorithm + */ +enum ow_search_result { + OW_SEARCH_DONE = 0, + OW_SEARCH_MORE = 1, + OW_SEARCH_FAILED = 2, +}; + +/** + * Internal state of the search algorithm. + * Check status to see if more remain to be read or an error occurred. + */ +struct ow_search_state { + int8_t prev_last_fork; + ow_romcode_t prev_code; + uint8_t command; + enum ow_search_result status; + bool first; + bool test_checksums; +}; + +/** + * Init the search algorithm state structure + * + * @param[out] state - inited struct + * @param[in] command - command to send for requesting the search (e.g. SEARCH_ROM) + * @param[in] test_checksums - verify checksums of all read romcodes + */ +void ow_search_init(Unit *unit, uint8_t command, bool test_checksums); + +/** + * Perform a search of the 1-wire bus, with a state struct pre-inited + * using ow_search_init(). + * + * Romcodes are stored in the provided array in a numerically ascending order. + * + * This function may be called repeatedly to retrieve more addresses than could fit + * in the address buffer. + * + * @param[in,out] state - search state, used for multiple calls with limited buffer size + * @param[out] codes - buffer for found romcodes + * @param[in] capacity - buffer capacity + * @return number of romcodes found. Search status is stored in state->status + */ +uint16_t ow_search_run(Unit *unit, ow_romcode_t *codes, uint16_t capacity); + +#endif //GEX_F072_OW_SEARCH_H diff --git a/units/1wire/unit_1wire.c b/units/1wire/unit_1wire.c index 8353bba..f5ea092 100644 --- a/units/1wire/unit_1wire.c +++ b/units/1wire/unit_1wire.c @@ -10,6 +10,10 @@ // 1WIRE master #define OW_INTERNAL #include "_ow_internal.h" +#include "_ow_commands.h" +#include "_ow_search.h" +#include "_ow_checksum.h" +#include "_ow_low_level.h" // ------------------------------------------------------------------------ @@ -176,7 +180,8 @@ enum PinCmd_ { CMD_CHECK_PRESENCE = 0, // simply tests that any devices are attached CMD_SEARCH_ADDR = 1, // perform a scan of the bus, retrieving all found device ROMs CMD_SEARCH_ALARM = 2, // like normal scan, but retrieve only devices with alarm - CMD_READ_ADDR = 3, // read the ROM code from a single device (for single-device bus) + CMD_SEARCH_CONTINUE = 3, // continue the previously started scan, retrieving more devices + CMD_READ_ADDR = 4, // read the ROM code from a single device (for single-device bus) CMD_SKIP_WRITE = 10, // write multiple bytes using the SKIP_ROM command CMD_SKIP_READ = 11, // write and read multiple bytes using the SKIP_ROM command @@ -213,14 +218,38 @@ static error_t U1WIRE_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, if (priv->busy) return E_BUSY; + uint8_t search_cmd = OW_ROM_SEARCH; switch (command) { + case CMD_SEARCH_ALARM: + search_cmd = OW_ROM_ALM_SEARCH; + // pass-through case CMD_SEARCH_ADDR: - // TODO - return E_NOT_IMPLEMENTED; + ow_search_init(unit, search_cmd, true); + // pass through + case CMD_SEARCH_CONTINUE:; + ow_romcode_t *codes = (void *) unit_tmp512; + uint16_t count = ow_search_run(unit, codes, UNIT_TMP_LEN/8); + + if (priv->searchState.status == OW_SEARCH_FAILED) { + return E_HW_FAULT; + } - case CMD_SEARCH_ALARM: - // TODO - return E_NOT_IMPLEMENTED; + // First byte of the response is a flag whether there are more devices + // to be found using CMD_SEARCH_CONTINUE + uint8_t status_code = (uint8_t) (priv->searchState.status == OW_SEARCH_MORE); + + TF_Msg msg = { + .frame_id = frame_id, + .type = MSG_SUCCESS, + .len = (TF_LEN) (count * 8 + 1), + }; + TF_Respond_Multipart(comm, &msg); + TF_Multipart_Payload(comm, &status_code, 1); + // the codes are back-to-back stored inside the buffer, we send it directly + // (it's already little-endian, as if built by PayloadBuilder) + TF_Multipart_Payload(comm, (uint8_t *) unit_tmp512, (uint32_t) (count * 8)); + TF_Multipart_Close(comm); + return E_SUCCESS; /** Simply check presence of any devices on the bus. Responds with SUCCESS or HW_TIMEOUT */ case CMD_CHECK_PRESENCE: @@ -234,7 +263,7 @@ static error_t U1WIRE_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, case CMD_READ_ADDR: // reset presence = ow_reset(unit); - if (!presence) return E_HW_TIMEOUT; + if (!presence) return E_HW_FAULT; // command ow_write_u8(unit, OW_ROM_READ); @@ -259,7 +288,7 @@ static error_t U1WIRE_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, case CMD_SKIP_WRITE: // reset presence = ow_reset(unit); - if (!presence) return E_HW_TIMEOUT; + if (!presence) return E_HW_FAULT; // MATCH_ROM+addr, or SKIP_ROM cmd_match_skip(unit, command, pp); @@ -282,7 +311,7 @@ static error_t U1WIRE_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, case CMD_SKIP_READ:; // reset presence = ow_reset(unit); - if (!presence) return E_HW_TIMEOUT; + if (!presence) return E_HW_FAULT; // MATCH_ROM+addr, or SKIP_ROM cmd_match_skip(unit, command, pp); diff --git a/utils/error.h b/utils/error.h index 0089155..bfe5ee1 100644 --- a/utils/error.h +++ b/utils/error.h @@ -21,6 +21,7 @@ X(MALFORMED_COMMAND, NULL) \ X(NOT_APPLICABLE, NULL) \ X(HW_TIMEOUT, NULL) \ + X(HW_FAULT, NULL) \ X(NO_SUCH_UNIT, NULL) \ X(PROTOCOL_BREACH, NULL) /* eating with the wrong spoon */ \ X(BAD_UNIT_TYPE, NULL) \