540 lines
17 KiB
C

/*!
* \file onewire_uart.c
* \brief
* A target independent 1-wire implementation using a microprocessor's uart
* for bit timing
*
* Copyright (C) 2015 Choutouridis Christos (http://www.houtouridis.net)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "onewire_uart.h"
/*
* ========= Private helper macros ===========
*/
/*!
* Clear a virtual 64bit unsigned register.
* \note
* We use a uint8_t array[8] to represent that kind
* of data for this module
* \param _reg Pointer to register location. Usual the name of
* The array
*/
#define _clear_u64(_reg_) \
memset ((void*)(_reg_), 0, 8*sizeof(uint8_t))
/*!
* Read a bit value of a virtual 64bit unsigned register.
* \note
* We use a uint8_t array[8] to represent that kind
* of data for this module
* \param _reg Pointer to register location. Usual the name of
* The array
* \param _bit The bit location we want to read
*/
#define _read_bit_u64(_reg_, _bit_) \
(_reg_[(_bit_)/8] & (1<<((_bit_)%8))) ? 1:0
/*!
* Write/modify a bit value from a virtual 64bit unsigned register.
* \note
* We use a uint8_t array[8] to represent that kind
* of data for this module
* \param _reg Pointer to register location. Usual the name of
* The array
* \param _bit The bit location we want to set
* \param _v The value we want to set
* \arg 0 Set to zero
* \arg !0 Set to One
*/
#define _write_bit_u64(_reg_, _bit_, _v_) \
do { \
if (_v_) _reg_[(_bit_)/8] |= 1 << ((_bit_)%8); \
else _reg_[(_bit_)/8] &= ~(1 << ((_bit_)%8)); \
} while (0)
/*
* ============= Private functions ===========
*/
/* Data manipulation functions */
static int _cmp_u64 (uint8_t *reg1, uint8_t *reg2);
/* Set functions */
static drv_status_en _set_baudrate (ow_uart_t *ow, ow_uart_state_en st);
/* Bus functions */
static uint8_t _write_bit (ow_uart_t *ow, uint8_t b);
static uint8_t _read_bit (ow_uart_t *ow);
/*!
* \brief
* Compare two 64bit virtual unsigned registers
* \note
* We use a uint8_t array[8] to represent that kind
* of data for this module
* \param reg1 Pointer to first register location. Usual the name of
* the array
* \param reg1 Pointer to 2nd register location. Usual the name of
* the array
* \return The comparison result
* \arg 0 Registers are equal
* \arg 1 Register 1 is larger than register 2
* \arg -1 Register 2 is larger than register 1
*/
static int _cmp_u64 (uint8_t *reg1, uint8_t *reg2) {
int i;
for (i=7 ; i>=0 ; --i) {
if (reg1[i] > reg2[i]) return 1; /* reg1 > reg2 */
else if (reg1[i] < reg2[i]) return -1; /* reg1 < reg2 */
}
return 0; /* reg1 equal reg2 */
}
/*!
* \brief
* Set UART Baudrate and handle all function calls and data
* manipulation
* \param ow Pointer to select 1-Wire structure for the operation.
* \param st The 1-Wire operational state \sa ow_uart_state_en
* \return The status of the operation
* \arg DRV_ERROR Could not set baudrate (callback pointer function error)
* \arg DRV_READY Success
*/
static drv_status_en _set_baudrate (ow_uart_t *ow, ow_uart_state_en st)
{
uint32_t st_br;
/* Get the desired baudrate */
switch (st) {
case OWS_RESET: st_br = ow->baudrate.reset; break;
default:
case OWS_OPER: st_br = ow->baudrate.oper; break;
}
if (ow->baudrate.current != st_br) {
if (ow->io.br (st_br) != DRV_READY) return DRV_ERROR;
ow->baudrate.current = st_br;
}
return DRV_READY;
}
/*!
* \brief
* Send a 1-Wire write bit
*
* --- --------------------------------------
* Write 1 \ /
* ----
* RS: | | | | | | | | | | |
* bit: st 0 1 2 3 4 5 6 7 st
* < ------------- 87/11 usec ------------->
* 8 bits, no parity, 1 stop
* standard: BR: 115200 Overdrive: BR: 921600
* TX: 0xFF
* RX: 0xFF
*
* --- ------
* Write 0 \ /
* -------------------------------------
* RS: | | | | | | | | | | |
* bit: st 0 1 2 3 4 5 6 7 st
* < ------------- 87/11 usec ------------->
* 8 bits, no parity, 1 stop
* standard: BR: 115200 Overdrive: BR: 921600
* TX: 0x00
* RX: 0x00
*
* \param ow Pointer to select 1-Wire structure for the operation.
* \param b The bit to send
* \return Status of the operation
* \arg 0 Success
* \arg 1 Fail
*/
static uint8_t _write_bit (ow_uart_t *ow, uint8_t b)
{
uint8_t w;
uint16_t r;
/*
* Make sure we are at the right baudrate
*/
if (_set_baudrate (ow, OWS_OPER) != DRV_READY)
return 1;
/* Select frame to send and check the bus by evaluating the return value */
w = (b) ? 0xFF : 0x00;
r = ow->io.rw (w);
if (r != w) return 1;
else return 0;
}
/*!
* \brief
* Read a bit from the 1-Wire bus, return it and provide
* the recovery time.
*
* --- - - - - - - - - - - - ------
* Read \ / X X X X X X X X X X X X X X X /
* ---- - - - - - - - - - - -
* RS: | | | | | | | | | | |
* bit: st 0 1 2 3 4 5 6 7 st
* < ------------- 87/11 usec ------------->
* ^
* |
* Master sample
*
* 8 bits, no parity, 1 stop
* standard: BR: 115200 Overdrive: BR: 921600
* TX: 0xFF
* RX: {1 - 0xFF, 0 - [0x00 - 0xFE] }
*
* \return The answer
* \arg 0 Read 0
* \arg 1 Read 1 (This is also returned on transition error).
*/
static uint8_t _read_bit (ow_uart_t *ow)
{
uint16_t r;
/*
* Make sure we are at the right baudrate
*/
if (_set_baudrate (ow, OWS_OPER) != DRV_READY)
return 1;
/* Send frame for read */
r = ow->io.rw (0xFF);
/* Dispatch answer */
if (r < 0xFF) return 0;
else return 1;
}
/*
* ============= PUBLIC 1-Wire API =============
*/
/*
* Link and Glue functions
*/
/*!
* \brief link driver's UART read-write function
* \param ow pointer to select 1-Wire structure for the operation.
* \param tx ow_uart_rx_ft pointer to drivers UART rx function
*/
void ow_uart_link_rw (ow_uart_t *ow, ow_uart_rw_ft rw) {
ow->io.rw = (ow_uart_rw_ft)((rw != 0) ? rw : 0);
}
/*!
* \brief link driver's UART baudrate function
* \param ow pointer to select 1-Wire structure for the operation.
* \param tx ow_uart_tx_ft pointer to drivers UART baudrate function
*/
void ow_uart_link_br (ow_uart_t *ow, ow_uart_br_ft br) {
ow->io.br = (ow_uart_br_ft)((br != 0) ? br : 0);
}
/*
* Set functions
*/
/*!
* \brief set 1-wire timing mode and update baudrate table.
* If the owt parameter is not a valid ow_uart_timing_en
* then set timings to OW_STANDTARD.
* \param ow pointer to select 1-Wire structure for the operation.
* \param owt Timing type
* \arg OWT_STANDARD Use standard timing
* \arg OWT_OVERDRIVE Use overdrive timing
*/
void ow_uart_set_timing (ow_uart_t *ow, uint32_t owt) {
ow->timing = (_ow_uart_is_timings(owt)) ? owt : OW_UART_T_STANDARD;
switch (owt) {
case OW_UART_T_STANDARD: _ow_baudrate_standard (ow->baudrate); break;
case OW_UART_T_OVERDRIVE: _ow_baudrate_overdrive (ow->baudrate); break;
}
}
/*
* User Functions
*/
/*!
* \brief
* De-Initialize the 1-Wire interface and leave bus pin in input state
* \param ow pointer to select 1-Wire structure for the operation.
* \return none
*/
void ow_uart_deinit (ow_uart_t *ow)
{
// Clear data
memset ((void*)ow, 0, sizeof (ow_uart_t));
/*!<
* This leaves the status = DRV_NOINIT
*/
}
/*!
* \brief
* Initialize the 1-Wire interface and leave bus high
* \param ow pointer to select 1-Wire structure for the operation.
* \return The driver status after init.
* \arg DRV_READY
* \arg DRV_ERROR
*/
drv_status_en ow_uart_init (ow_uart_t *ow)
{
// Check requirements
if (!ow->io.rw) return ow->status = DRV_ERROR;
if (!ow->io.br) return ow->status = DRV_ERROR;
// Init the bus
ow->status = DRV_BUSY;
if ( _ow_uart_is_timings(ow->timing) != 1) {
ow->timing = OW_UART_T_STANDARD;
_ow_baudrate_standard (ow->baudrate);
}
switch (ow->timing) {
case OW_UART_T_STANDARD: _ow_baudrate_standard (ow->baudrate); break;
case OW_UART_T_OVERDRIVE: _ow_baudrate_overdrive (ow->baudrate); break;
}
if (_set_baudrate (ow, OWS_RESET) != DRV_READY)
return ow->status = DRV_ERROR;
return ow->status = DRV_READY;
}
/*!
* \brief
* Generate a 1-wire reset
*
* --- ---- - - - -------
* Reset \ / \ X X X /
* -------------------- - - - -
* RS: | | | | | | | | | | |
* bit: st 0 1 2 3 4 5 6 7 st
* < ---------- 1024/174 usec ------------->
*
* 8 bits, no parity, 1 stop
* Standard: Overdrive :
* BR: 9600, BR: 57600
* TX: 0xF0, TX: 0xF8
* RX: 0xF0 not present RX: 0xF8 not present
* less if present less if present
*
* \note Does not handle alarm presence from DS2404/DS1994
* \param None
* \return The status of the operation
* \arg DRV_ERROR Error, callback baudrate error or bus error
* \arg DRV_NODEV If no presence detect was found
* \arg DRV_READY Otherwise
*/
drv_status_en ow_uart_reset (ow_uart_t *ow)
{
uint8_t w;
uint16_t r;
/*
* Make sure we are at the write baudrate
*/
if (_set_baudrate (ow, OWS_RESET) != DRV_READY)
return DRV_ERROR;
/* Select frame to send */
w = (ow->timing == OW_UART_T_OVERDRIVE) ? 0xF8 : 0xF0;
r = ow->io.rw (w);
r = ow->io.rw (w); // Send it twice to make sure
if (w>r) return DRV_READY;
else if (w==r) return DRV_NODEV;
else return DRV_ERROR;
}
/*!
* \brief
* Read a byte from 1-Wire bus
* \param ow pointer to select 1-Wire structure for the operation.
* \return The byte received.
*/
uint8_t ow_uart_rx (ow_uart_t *ow)
{
uint8_t i;
byte_t byte;
for (i=8, byte=0 ; i>0 ; --i) {
byte >>= 1; /* shift bits to right as LSB comes first */
if (_read_bit (ow) != 0) byte |= 0x80; /* Mask bit to MSB */
}
return byte;
}
/*!
* \brief
* Write a byte to 1-Wire bus
* \param ow pointer to select 1-Wire structure for the operation.
* \param b: The byte to write
* \return none
*/
void ow_uart_tx (ow_uart_t *ow, byte_t byte)
{
uint8_t i;
for (i=8 ; i>0 ; --i) {
_write_bit (ow, byte & 0x01); /* Send LSB */
byte >>= 1; /* shift bits to right */
}
}
/*!
* \brief
* Write a byte to 1-Wire bus and read the response
* \param ow Pointer to select 1-Wire structure for the operation.
* \param byte The byte to write
* \return The byte received.
*/
uint8_t ow_uart_rw (ow_uart_t *ow, byte_t byte)
{
uint8_t i;
byte_t ret;
for (i=8, ret=0 ; i>0 ; --i) {
ret >>= 1; /* shift read bits to right as LSB comes first */
if ((byte & 0x01) != 0) ret |= (_read_bit (ow) != 0) ? 0x80 : 0x0;
else _write_bit (ow, 0);
byte >>= 1; /* shift bits to right */
/*!<
* If the bit is 1 we use the read sequence, as it has the same
* waveform with write-1 and we get the slave response
* If the bit is 0, we can not read the slave response so we just write-0
*/
}
return byte;
}
/*!
* \brief
* 1-Wire search algorithm based on maxim-ic application note 187
*
* \param ow Pointer to select 1-Wire structure for the operation.
* \param romid Pointer to romid to return. If the search is success
* this points to and 64bit long array with ROM ID
* \return The status of the search
* \arg DRV_NODEV (-1) Search was failed, No device found
* \arg DRV_READY (1) Search is complete, all ROM IDs was found. This was the last
* \arg DRV_BUSY (2) Search is succeed, plus there are more ROM IDs to found
* \arg DRV_ERROR (3) Search failed, Reading error
*/
drv_status_en ow_uart_search (ow_uart_t *ow, uint8_t *romid)
{
static uint8_t dec[8]; /*!<
* Hold the algorithm's select bit when a discrepancy
* is detected. We use this variable to navigate to the
* ROM tree as we store the path we take each time (0-1).
* Each bit represent a bit position in the ROM ID.
*/
static uint8_t pos[8]; /*!<
* Hold the discrepancy position. We use this variable to
* navigate to the ROM tree as we store the crossroads(1) we encounter.
* Each bit represent a bit position in the ROM ID.
*/
uint8_t i, cur[8]; /* Current pass bit position, in a pos[8] like representation of it */
uint8_t b, b1, b2; /* bit helper vars */
if (ow_uart_reset (ow) != DRV_READY)
return DRV_NODEV;
ow_uart_tx (ow, 0xF0); /* Issue search command */
for (i=0 ; i<64 ; ++i) {
/* Get response pair bits */
b1 = _read_bit (ow);
b2 = _read_bit (ow);
switch (b1 | (b2<<1)) {
case 0x00: /* 00 - We have discrepancy */
_write_bit_u64 (cur, i, 1);
switch (_cmp_u64 (pos, cur)) {
default:
case -1: b = 0;
_write_bit_u64 (pos, i, 1); break;
case 0: b = 1;
_write_bit_u64 (dec, i, 1); break;
case 1: b = _read_bit_u64 (dec, i); break;
/*<
* -1) pos < cur: This discrepancy is the most far for now.
* Mark position and select 0.
* 0) pos == cur: This was the last discrepancy in the last pass.
* Select the other branch this time (1).
* 1) pos > cur: We had a discrepancy in a MSB than that, in a previous pass.
* Continue with the last pass decision.
*/
}
/* Send selection and update romid */
_write_bit (ow, b);
_write_bit_u64 (romid, i, b);
break;
case 0x01: /* 01 - All bits of all ROMs are 1s */
_write_bit (ow, 1);
_write_bit_u64 (romid, i, 1); break;
case 0x02: /* 10 - All bits of all ROMs are 0s */
_write_bit (ow, 0);
_write_bit_u64 (romid, i, 0); break;
default:
case 0x03: /* 11 - No device on the bus */
_clear_u64 (romid);
return DRV_NODEV;
} /* switch (b1 | (b2<<1)) */
} /* for */
switch (_cmp_u64 (dec, pos)) {
case -1: return DRV_BUSY;
case 0: _clear_u64 (dec);
_clear_u64 (pos);
return DRV_READY;
default:
case 1: _clear_u64 (dec);
_clear_u64 (pos);
return DRV_ERROR;
/*<
* -1) des < pos: We have more leafs(ROMs) to found
* 0) des == pos: We have found all the leafs(ROMs)
* 1) des > pos: We have more decision that discrepancies ?!?!?, Error.
*/
}
}
#undef _clear_u64
#undef _read_bit_u64
#undef _write_bit_u64
#undef _ow_is_timings