/*!
* \file /utl/com/_1wire.h
* \brief An 1-wire interface implementation
*
* Copyright (C) 2018 Christos Choutouridis
*
* 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 .
*
*/
#ifndef __utl_com_1wire_h__
#define __utl_com_1wire_h__
#include
#include
#include
#include
namespace utl {
/*!
* \ingroup Communication
* \brief Abstract base class interface for 1-wire bus
*/
//!@{
/*!
* \name Friend API to provide common functionality to all specializations
*/
namespace _1wire_i_det {
template byte_t _touch (_T& obj, byte_t out, typename _T::Speed s);
template void _match (_T& obj, _1wire_id_t& id, typename _T::Speed s);
template void _match_n_ovdr (_T& obj, _1wire_id_t& id);
template void _skip (_T& obj, typename _T::Speed s);
template void _skip_n_ovdr (_T& obj);
template _1wire_id_t _first (_T& obj, typename _T::Speed s, bool alarm);
template _1wire_id_t _next (_T& obj, typename _T::Speed s, bool alarm);
}
/*!
* \brief
* Template base class for 1-wire communication interface using CRTP
* \param impl_t The CRTP type (the derived/implementation class typename).
*/
template
class _1wire_i {
_CRTP_IMPL(impl_t);
public:
using type = _1wire_i; //!< Export type as identity meta-function
//! 1-wire bus speed
enum class Speed { STD =0, OVDR };
/*!
* \name Object lifetime
*/
//!@{
protected:
_1wire_i () = default; //!< Allow constructor from derived only
~_1wire_i () = default; //!< Allow destructor from derived only
_1wire_i (const _1wire_i&) = delete; //!< No copies
_1wire_i& operator= (const _1wire_i&) = delete;
//!@}
/*!
* \name Implementation requirements
* \note
* In order for the implementation to have the following as private members too
* it need to declare this class as friend
*/
//!@{
private:
Speed speed () const { return impl().speed(); } //!< Get the 1-wire bus speed
void speed (Speed s) { return impl().speed(s); } //!< Set the 1-wire bus speed
//! Write a bit to the 1-Wire bus, return response/write status and provide the recovery time.
bool bit (bool b) { return impl().bit(b); }
bool _reset (Speed s){ return impl()._reset(s); } //!< Generate a 1-wire reset and return the operation status
//!@}
/*!
* \name Friends api to provide functionality to all class specializations
*/
//!@{
private:
enum Command {
CMD_READ_ROM = 0x33,
CMD_OVDR_SKIP = 0x3C,
CMD_MATCH = 0x55,
CMD_OVDR_MATCH = 0x69,
CMD_SKIP = 0xCC,
CMD_ALARM_SEARCH = 0xEC,
CMD_SEARCH_ROM = 0xF0
};
template friend byte_t _1wire_i_det::_touch (_T&, byte_t, typename _T::Speed);
template friend void _1wire_i_det::_match (_T& obj, _1wire_id_t& id, typename _T::Speed s);
template friend void _1wire_i_det::_match_n_ovdr (_T& obj, _1wire_id_t& id);
template friend void _1wire_i_det::_skip (_T& obj, typename _T::Speed s);
template friend void _1wire_i_det::_skip_n_ovdr (_T& obj);
template friend _1wire_id_t _1wire_i_det::_first (_T&, typename _T::Speed, bool);
template friend _1wire_id_t _1wire_i_det::_next (_T&, typename _T::Speed, bool);
//!@}
/*!
* \name User functionality provided by the interface
*/
//!@{
public:
/*!
* \brief
* Generate a 1-wire reset
* \param s Bus speed
* \return The status of the operation
* \arg 0 Fail
* \arg 1 Success
*/
bool reset (Speed s = Speed::STD) {
return _reset (s);
}
/*!
* Transmit a byte to 1-Wire bus and read the response
* \param out The byte to write
* \param s Bus speed to use
* \return The byte received.
*/
byte_t tx_data (byte_t out, Speed s = Speed::STD) {
return _1wire_i_det::_touch (*this, out, s);
}
/*!
* Transmit a number of bytes to 1-wire bus and read the response
* \param out Pointer to data to transmit
* \param in Pointer to data to store
* \param n Number of bytes
* \param s Speed to use
* \return The number of transmitted bytes
*/
size_t tx_data (const byte_t *out, byte_t *in, size_t n, Speed s = Speed::STD);
/*!
* Receive a byte from 1-Wire bus while transmitting 0xFF
* \param s Bus speed to use
* \return The byte received.
*/
byte_t rx_data (Speed s = Speed::STD) {
return _1wire_i_det::_touch (*this, 0xFF, s);
}
/*!
* Receive a number of bytes from 1-wire bus while transmitting 0xFFs
* \param in Pointer to data to store
* \param n Number of bytes
* \param s Speed to use
* \return The number of transmitted bytes
*/
size_t rx_data (byte_t *in, size_t n, Speed s = Speed::STD);
/*!
* Send match rom command
* \param id The ID to select on the bus
* \param s The speed to use for the command
*/
void match (_1wire_id_t& id, Speed s = Speed::STD) {
_1wire_i_det::_match (*this, id, s);
}
/*!
* Match and overdrive sequence
* \param obj The object from which we call private members
* \param id The ID to select on the bus
*/
void match_n_ovdr (_1wire_id_t& id) {
_1wire_i_det::_match_n_ovdr (*this, id);
}
/*!
* Send skip command to the bus
* \param id The ID to select on the bus
*/
void skip (Speed s = Speed::STD) {
_1wire_i_det::_skip (*this, s);
}
/*!
* Send the Skip and Overdrive sequence
*/
void skip_n_ovdr () {
_1wire_i_det::_skip_n_ovdr (*this);
}
/*!
* \brief
* 'first' operation, to search on the 1-Wire for the first device.
* This is performed by setting dec_, pos_ and cur_ to zero and then doing the search.
* \param s The bus speed
* \param alarm If set, search for alarm devices
* \return ID The romID
* \arg nullDev Indicate no [more] device[s]
*/
_1wire_id_t first (Speed s = Speed::STD, bool alarm =false) {
return _1wire_i_det::_first (*this, s, alarm);
}
/*!
* \brief
* 'next' operation, to search on the 1-Wire for the next device.
* This search is usually performed after a 'first' operation or another 'next' operation.
* Based on maxim-ic application note 187.
* \param s The bus speed
* \param alarm If set, search for alarm devices
* \return ID The romID
* \arg nullDev Indicate no [more] device[s]
*/
_1wire_id_t next (Speed s = Speed::STD, bool alarm =false) {
return _1wire_i_det::_next (*this, s, alarm);
}
//!@}
private:
_1wire_id_t dec_ {_1wire_id_t::nullDev()}; /*!<
* 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.
*/
_1wire_id_t pos_ {_1wire_id_t::nullDev()}; /*!<
* 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.
*/
_1wire_id_t cur_ {_1wire_id_t::nullDev()}; //! Current rom discrepancy state
};
template
size_t _1wire_i<_I>::tx_data (const byte_t *out, byte_t *in, size_t n, Speed s){
for (size_t nn {n} ; nn ; --nn)
*in++ = tx_data (*out++, s);
return n;
}
template
size_t _1wire_i<_I>::rx_data(byte_t *in, size_t n, Speed s) {
for (size_t nn {n} ; nn ; --nn)
*in++ = tx_data (0xFF, s);
return n;
}
/*!
* \brief
* A virtual base class implementation
* \param impl_t = virtual_tag
*/
template <>
class _1wire_i {
public:
using type = _1wire_i; //!< Export type as identity meta-function
//! 1-wire bus speed
enum class Speed { STD =0, OVDR };
/*!
* \name Object lifetime
*/
//!@{
protected:
_1wire_i () = default; //!< Allow constructor from derived only
_1wire_i (const type&) = delete; //!< No copies
type& operator= (const type&) = delete;
public:
virtual ~_1wire_i () = default; //!< Virtual default destructor
//!@}
/*!
* \name Implementation requirements
*/
//!@{
private:
virtual Speed speed () const =0; //!< Get the 1-wire bus speed
virtual void speed (Speed) =0; //!< Set the 1-wire bus speed
virtual bool bit () =0; //!< Read a bit from the 1-Wire bus, return it and provide the recovery time.
virtual bool bit (bool) =0; //!< Write a bit to the 1-Wire bus, return write status and provide the recovery time.
virtual bool _reset (Speed) =0; //!< Generate a 1-wire reset and return the operation status
//!@}
/*!
* \name Friends api to provide functionality to all class specializations
*/
//!@{
private:
enum Command {
CMD_READ_ROM = 0x33,
CMD_OVDR_SKIP = 0x3C,
CMD_MATCH = 0x55,
CMD_OVDR_MATCH = 0x69,
CMD_SKIP = 0xCC,
CMD_ALARM_SEARCH = 0xEC,
CMD_SEARCH_ROM = 0xF0
};
template friend byte_t _1wire_i_det::_touch (_T&, byte_t, typename _T::Speed);
template friend void _1wire_i_det::_match (_T& obj, _1wire_id_t& id, typename _T::Speed s);
template friend void _1wire_i_det::_match_n_ovdr (_T& obj, _1wire_id_t& id);
template friend void _1wire_i_det::_skip (_T& obj, typename _T::Speed s);
template friend void _1wire_i_det::_skip_n_ovdr (_T& obj);
template friend _1wire_id_t _1wire_i_det::_first (_T&, typename _T::Speed, bool);
template friend _1wire_id_t _1wire_i_det::_next (_T&, typename _T::Speed, bool);
//!@}
/*!
* \name User functionality provided by the interface
*/
//!@{
public:
/*!
* \brief
* Generate a 1-wire reset
* \param s Bus speed
* \return The status of the operation
* \arg 0 Fail
* \arg 1 Success
*/
bool reset (Speed s = Speed::STD) {
return _reset (s);
}
/*!
* Transmit a byte to 1-Wire bus and read the response
* \param out The byte to write
* \param s Bus speed to use
* \return The byte received.
*/
byte_t tx_data (byte_t out, Speed s = Speed::STD) {
return _1wire_i_det::_touch (*this, out, s);
}
/*!
* Transmit a number of bytes to 1-wire bus and read the response
* \param out Pointer to data to transmit
* \param in Pointer to data to store
* \param n Number of bytes
* \param s Speed to use
* \return The number of transmitted bytes
*/
size_t tx_data (const byte_t *out, byte_t *in, size_t n, Speed s = Speed::STD);
/*!
* Receive a byte from 1-Wire bus while transmitting 0xFF
* \param s Bus speed to use
* \return The byte received.
*/
byte_t rx_data (Speed s = Speed::STD) {
return _1wire_i_det::_touch (*this, 0xFF, s);
}
/*!
* Receive a number of bytes from 1-wire bus while transmitting 0xFFs
* \param in Pointer to data to store
* \param n Number of bytes
* \param s Speed to use
* \return The number of transmitted bytes
*/
size_t rx_data (byte_t *in, size_t n, Speed s = Speed::STD);
/*!
* Send match rom command
* \param id The ID to select on the bus
* \param s The speed to use for the command
*/
void match (_1wire_id_t& id, Speed s = Speed::STD) {
_1wire_i_det::_match (*this, id, s);
}
/*!
* Match and overdrive sequence
* \param obj The object from which we call private members
* \param id The ID to select on the bus
*/
void match_n_ovdr (_1wire_id_t& id) {
_1wire_i_det::_match_n_ovdr (*this, id);
}
/*!
* Send skip command to the bus
* \param id The ID to select on the bus
*/
void skip (Speed s = Speed::STD) {
_1wire_i_det::_skip (*this, s);
}
/*!
* Send the Skip and Overdrive sequence
*/
void skip_n_ovdr () {
_1wire_i_det::_skip_n_ovdr (*this);
}
/*!
* \brief
* 'first' operation, to search on the 1-Wire for the first device.
* This is performed by setting dec_, pos_ and cur_ to zero and then doing the search.
* \param s The bus speed
* \param alarm If set, search for alarm devices
* \return ID The romID
* \arg nullDev Indicate no [more] device[s]
*/
_1wire_id_t first (Speed s = Speed::STD, bool alarm =false) {
return _1wire_i_det::_first (*this, s, alarm);
}
/*!
* \brief
* 'next' operation, to search on the 1-Wire for the next device.
* This search is usually performed after a 'first' operation or another 'next' operation.
* Based on maxim-ic application note 187.
* \param s The bus speed
* \param alarm If set, search for alarm devices
* \return ID The romID
* \arg nullDev Indicate no [more] device[s]
*/
_1wire_id_t next (Speed s = Speed::STD, bool alarm =false) {
return _1wire_i_det::_next (*this, s, alarm);
}
//!@}
private:
_1wire_id_t dec_ {_1wire_id_t::nullDev()}; /*!<
* 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.
*/
_1wire_id_t pos_ {_1wire_id_t::nullDev()}; /*!<
* 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.
*/
_1wire_id_t cur_ {_1wire_id_t::nullDev()}; //! Current rom discrepancy state
};
size_t _1wire_i::tx_data (const byte_t *out, byte_t *in, size_t n, Speed s){
for (size_t nn {n} ; nn ; --nn)
*in++ = tx_data (*out++, s);
return n;
}
size_t _1wire_i::rx_data(byte_t *in, size_t n, Speed s) {
for (size_t nn {n} ; nn ; --nn)
*in++ = tx_data (0xFF, s);
return n;
}
/*!
* \name Friend API to provide common functionality to all specializations
*/
//!@{
namespace _1wire_i_det {
/*!
* \brief
* Write a byte to 1-Wire bus and read the response
* \param obj The object from which we call private members
* \param b The byte to write
* \param s Bus speed to use
* \return The byte received.
*/
template
byte_t _touch (_T& obj, byte_t out, typename _T::Speed s) {
byte_t ret {0};
// Select speed once
if (obj.speed () != s)
obj.speed (s);
for (uint8_t i =8; i>0 ; --i) {
ret >>= 1;
ret |= (obj.bit (out & 0x01)) ? 0x80 : 0x00;
out >>= 1;
/*^
* We shift bits to right as LSB comes first and mask it to MSB
* If we need to read we have to write 1
*/
}
return ret;
}
/*!
* Send match rom command
* \param obj The object from which we call private members
* \param id The ID to select on the bus
* \param s The speed to use for the command
*/
template
void _match (_T& obj, _1wire_id_t& id, typename _T::Speed s) {
_touch (obj, (s == _T::Speed::STD) ? _T::CMD_MATCH : _T::CMD_OVDR_MATCH, s);
for (uint8_t& b : id)
_touch (obj, b, s);
}
/*!
* Match and overdrive sequence
* \param obj The object from which we call private members
* \param id The ID to select on the bus
*/
template
void _match_n_ovdr (_T& obj, _1wire_id_t& id) {
_touch (obj, _T::CMD_MATCH, _T::Speed::STD);
for (uint8_t& b : id)
_touch (obj, b, _T::Speed::OVDR);
}
/*!
* Send skip command to the bus
* \param obj The object from which we call private members
* \param id The ID to select on the bus
*/
template
void _skip (_T& obj, typename _T::Speed s) {
_touch (obj, (s == _T::Speed::STD) ? _T::CMD_SKIP : _T::CMD_SKIP, s);
}
/*!
* Send the Skip and Overdrive sequence
* \param obj The object from which we call private members
*/
template
void _skip_n_ovdr (_T& obj) {
_touch (obj, _T::CMD_OVDR_SKIP, _T::Speed::STD);
}
/*!
* \brief
* 'first' operation, to search on the 1-Wire for the first device.
* This is performed by setting dec_, pos_ and cur_ to zero and then doing the search.
* \param obj The object from which we call private members
* \param s The bus speed
* \return ID The romID
* \arg nullDev Indicate no [more] device[s]
*/
template
_1wire_id_t _first (_T& obj, typename _T::Speed s, bool alarm) {
obj.dec_ = obj.pos_ = obj.cur_ = _1wire_id_t::nullDev();
return _next (obj, s, alarm);
}
/*!
* \brief
* 'next' operation, to search on the 1-Wire for the next device.
* This search is usually performed after a 'first' operation or another 'next' operation.
* Based on maxim-ic application note 187.
* \param obj The object from which we call private members
* \param s The bus speed
* \return The romID
* \arg nullDev Indicate no [more] device[s]
*/
template
_1wire_id_t _next (_T& obj, typename _T::Speed s, bool alarm) {
uint8_t b, bxx; // bit helper vars
uint8_t i;
_1wire_id_t ID;
do {
if ((obj.pos_ != _1wire_id_t::nullDev()) && (obj.dec_ >= obj.pos_)) {
// dec_ == pos_: We have found all the leafs, already
// dec_ > pos_ : Error
ID = _1wire_id_t::nullDev();
break;
}
if (!obj.reset (s)) {
ID = _1wire_id_t::nullDev();
break;
}
// Issue search command
if (alarm) obj.tx_data (_T::CMD_ALARM_SEARCH, s);
else obj.tx_data (_T::CMD_SEARCH_ROM, s);
// Select speed once
if (obj.speed () != s)
obj.speed (s);
// traverse entire RomID from LSB to MSB
for (i =0 ; i<64 ; ++i) {
// Get response pair bits
bxx = obj.bit (1); // bit
bxx <<= 1;
bxx |= obj.bit (1); // complementary bit
if (bxx == 0x00) {
// 00 - We have discrepancy
obj.cur_.bit (i, 1);
switch (_1wire_id_t::compare (obj.pos_, obj.cur_)) {
default:
case -1:
// pos_ < cur_: This discrepancy is the most far for now. Mark position and select 0.
b = 0;
//dec_.bit (i, (b = 0)); //<-- Its already 0
obj.pos_.bit (i, 1);
break;
case 0:
// pos_ == cur_: This was the last discrepancy in the last pass.
// Select the other branch this time (1)
obj.dec_.bit (i, (b = 1));
break;
case 1:
// pos_ > cur_: We had a discrepancy in a MSB than that, in a previous pass.
// Continue with the last pass decision.
b = obj.dec_.bit (i);
break;
}
// Send selection and update romid
obj.bit (b);
ID.bit (i, b);
}
else if (bxx == 0x01) {
// 01 - All bits of all ROMs are 0s
obj.bit (0);
ID.bit (i, 0);
}
else if (bxx == 0x02) {
// 10 - All bits of all ROMs are 1s
obj.bit (1);
ID.bit (i, 1);
}
else {
// 11 - No device on the bus
ID = _1wire_id_t::nullDev();
break;
}
} // for
if (i == 64 && obj.pos_ == _1wire_id_t::nullDev()) {
// Mark done with only one device
obj.pos_.bit (0, 1);
obj.dec_.bit (0, 1);
}
} while (0);
return ID;
}
} // namespace _1wire_i_det
//!@}
#if defined _utl_have_concepts
/*!
* \name 1-wire type interface concept
*/
template
concept bool _1wire_c = requires (T t, typename T::Speed s, _1wire_id_t id) {
// Object type
requires not_::value>::value;
requires not_::value>::value;
// Members
// typename T::Speed;
// typename T::Command;
// Methods
{t.reset(s)} -> bool;
{t.tx_data(1)} -> byte_t;
{t.rx_data(s)} -> byte_t;
t.match(id, s);
t.match_n_ovdr(id);
t.skip(s);
t.skip_n_ovdr();
{t.first(s)} -> _1wire_id_t;
{t.next(s)} -> _1wire_id_t;
};
#else
namespace _1wire_i_det {
using std::declval;
template using try_reset_t = decltype (declval<_Tp>().reset (declval()));
template using try_rx1_t = decltype (declval<_Tp>().rx_data (declval()));
template using try_tx1_t = decltype (declval<_Tp>().tx_data (declval(), declval()));
template using try_match_t = decltype (declval<_Tp>().match (declval<_1wire_id_t&>(), declval()));
template using try_match_n_ovdr_t = decltype (declval<_Tp>().match_n_ovdr (declval<_1wire_id_t&>()));
template using try_skip_t = decltype (declval<_Tp>().skip (declval()));
template using try_skip_n_ovdr_t = decltype (declval<_Tp>().skip_n_ovdr ());
template using try_first_t = decltype (declval<_Tp>().first (declval()));
template using try_next_t = decltype (declval<_Tp>().next (declval()));
//! Primary template to catch any non 1-wire interface types
template
struct is_1wire_ : false_ {};
//! template to catch a proper 1-wire interface type
template
struct is_1wire_ <_Tp,
void_t <
// typename _Tp::Speed,
// typename _Tp::Command,
use_if_same_t , bool>,
use_if_same_t , byte_t>,
//use_if_same_t , size_t>,
use_if_same_t , byte_t>,
//use_if_same_t , size_t>,
use_if_same_t , void>,
use_if_same_t , void>,
use_if_same_t , void>,
use_if_same_t , void>,
use_if_same_t , _1wire_id_t>,
use_if_same_t , _1wire_id_t>
> //!^ SFINAE may apply
> : true_ {};
} // namespace _1wire_i_det
/*!
* Value meta-programming function for 1-wire interface checking
* \param _Tp Type to check
* \return True if _Tp is a 1-wire interface
*/
template
constexpr bool _1wire_c = _1wire_i_det::is_1wire_<_Tp>::value;
#endif
//!@}
} //namespace utl
#endif /* __utl_com_1wire_h__ */