/*! * \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__ */