diff --git a/src/libDevCom/CMakeLists.txt b/src/libDevCom/CMakeLists.txt index 87d8cca9d43c44d15bbf0d0ae36c2b5311f1b746..cdfc2e2edec3f8822636c0c1c452d83108ddc6da 100644 --- a/src/libDevCom/CMakeLists.txt +++ b/src/libDevCom/CMakeLists.txt @@ -15,6 +15,7 @@ target_sources(DevCom I2CCom.cpp I2CDevComuino.cpp PCA9548ACom.cpp + PCA9547Com.cpp SPICom.cpp diff --git a/src/libDevCom/PCA9547Com.cpp b/src/libDevCom/PCA9547Com.cpp new file mode 100644 index 0000000000000000000000000000000000000000..17caadf288c3eca7dbb1aeb7eaaaab1295014ebb --- /dev/null +++ b/src/libDevCom/PCA9547Com.cpp @@ -0,0 +1,319 @@ +#include "PCA9547Com.h" + +#include "DeviceComRegistry.h" +#include "Logger.h" +REGISTER_DEVCOM(PCA9547, I2CCom) + +#include "ComIOException.h" +#include "ScopeLock.h" + +PCA9547Com::PCA9547Com(uint8_t deviceAddr, uint8_t channel, + std::shared_ptr<I2CCom> com) + : I2CCom(deviceAddr), + m_com(com), + m_muxAddr(com->deviceAddr()), + m_channel(channel) {} + +PCA9547Com::~PCA9547Com() {} + +void PCA9547Com::write_reg32(uint32_t address, uint32_t data) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + try { + m_com->write_reg32(address, data); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } +} + +void PCA9547Com::write_reg16(uint32_t address, uint16_t data) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + try { + m_com->write_reg16(address, data); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } +} + +void PCA9547Com::write_reg8(uint32_t address, uint8_t data) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + try { + m_com->write_reg8(address, data); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } +} + +void PCA9547Com::write_reg32(uint32_t data) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + try { + m_com->write_reg32(data); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } +} + +void PCA9547Com::write_reg16(uint16_t data) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + try { + m_com->write_reg16(data); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } +} + +void PCA9547Com::write_reg8(uint8_t data) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + try { + m_com->write_reg8(data); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } +} + +void PCA9547Com::write_block(uint32_t address, + const std::vector<uint8_t>& data) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + try { + m_com->write_block(address, data); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } +} + +void PCA9547Com::write_block(const std::vector<uint8_t>& data) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + try { + m_com->write_block(data); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } +} + +uint32_t PCA9547Com::read_reg32(uint32_t address) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + uint32_t data = 0; + try { + data = m_com->read_reg32(address); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } + return data; +} + +uint32_t PCA9547Com::read_reg24(uint32_t address) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + uint32_t data = 0; + try { + data = m_com->read_reg24(address); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } + return data; +} + +uint16_t PCA9547Com::read_reg16(uint32_t address) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + uint16_t data = 0; + try { + data = m_com->read_reg16(address); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } + return data; +} + +uint8_t PCA9547Com::read_reg8(uint32_t address) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + uint8_t data = 0; + try { + data = m_com->read_reg8(address); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } + return data; +} + +uint32_t PCA9547Com::read_reg32() { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + uint32_t data = 0; + try { + data = m_com->read_reg32(); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } + return data; +} + +uint32_t PCA9547Com::read_reg24() { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + uint32_t data = 0; + try { + data = m_com->read_reg24(); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } + return data; +} + +uint16_t PCA9547Com::read_reg16() { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + uint16_t data = 0; + try { + data = m_com->read_reg16(); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } + return data; +} + +uint8_t PCA9547Com::read_reg8() { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + uint8_t data = 0; + try { + data = m_com->read_reg8(); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } + return data; +} + +void PCA9547Com::read_block(uint32_t address, std::vector<uint8_t>& data) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + try { + m_com->read_block(address, data); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } +} + +void PCA9547Com::read_block(std::vector<uint8_t>& data) { + ScopeLock lock(m_com); + + m_com->write_reg8(0xF8 + m_channel); + + m_com->setDeviceAddr(deviceAddr()); + try { + m_com->read_block(data); + restore_com(); + } catch (const ComIOException& e) { + restore_com(); + throw; + } +} + +void PCA9547Com::lock() { + logger(logDEBUG4) << __PRETTY_FUNCTION__ << " -> PCA9547Com locked!"; + m_com->lock(); +} + +void PCA9547Com::unlock() { + logger(logDEBUG4) << __PRETTY_FUNCTION__ << " -> PCA9547Com unlocked!"; + m_com->unlock(); +} + +void PCA9547Com::restore_com() { + m_com->setDeviceAddr(m_muxAddr); + m_com->write_reg8(0); +} diff --git a/src/libDevCom/PCA9547Com.h b/src/libDevCom/PCA9547Com.h new file mode 100644 index 0000000000000000000000000000000000000000..37e320e24e09f3f8452a963a9205010f84576fee --- /dev/null +++ b/src/libDevCom/PCA9547Com.h @@ -0,0 +1,109 @@ +#ifndef PCA9547COM_H +#define PCA9547COM_H + +#include <memory> + +#include "I2CCom.h" + +//! \brief PCA9547 Low Voltage 8-Channel I2C Switch with Reset +/** + * [Datasheet](https://www.nxp.com/docs/en/data-sheet/PCA9547.pdf) + * + * Implements I2C communicatoin with a device that is connected to the + * manager using the PCA9547 multiplexer. The I2C calls are forwarded + * to right device using the internal `com` object as follows: + * + * 1. Enable only PCA9547's `channel` output + * 2. Change `com` target device address to `deviceAddr` + * 3. Perform requested I2C operation using `com` + * 4. Restore `com` target device address to PCA9547 + * 5. Disable all PCA9547's channels + * + * The implementation assumes that the state of the PCA9547 control + * register can be changed externally (ie: in parallel process). This + * is why it always enables the desired channel. + * + * All channels are disabled at the end in case of parallel PCA9547 + * devices. This way only a single multiplexed I2C bus is available + * for any communication request to any parallel PCA9547 device. + * + * The internal `com` object should target the PCA9547 device. The + * device address should not be changed after creating the object, as + * it is cached by the constructor. + */ +class PCA9547Com : public I2CCom { + public: + /** + * \param deviceAddr Target device address to which all I2C calls are + * forwarded \param channel Output channel to which the device is connected + * to \param com Internal `com` device targettign the PCA9547. + */ + PCA9547Com(uint8_t deviceAddr, uint8_t channel, + std::shared_ptr<I2CCom> com); + virtual ~PCA9547Com(); + + /** Write commands @{ */ + + virtual void write_reg32(uint32_t address, uint32_t data); + virtual void write_reg16(uint32_t address, uint16_t data); + virtual void write_reg8(uint32_t address, uint8_t data); + + virtual void write_reg32(uint32_t data); + virtual void write_reg16(uint16_t data); + virtual void write_reg8(uint8_t data); + + virtual void write_block(uint32_t address, + const std::vector<uint8_t>& data); + virtual void write_block(const std::vector<uint8_t>& data); + + /** @} */ + + /** Read commands @{ */ + + virtual uint32_t read_reg32(uint32_t address); + virtual uint32_t read_reg24(uint32_t address); + virtual uint16_t read_reg16(uint32_t address); + virtual uint8_t read_reg8(uint32_t address); + + virtual uint32_t read_reg32(); + virtual uint32_t read_reg24(); + virtual uint16_t read_reg16(); + virtual uint8_t read_reg8(); + + virtual void read_block(uint32_t address, std::vector<uint8_t>& data); + virtual void read_block(std::vector<uint8_t>& data); + + /** @} */ + + /** Request exclusive access to device (if m_com is DevComuino) + * + * If a single hardware bus is used to connect multiple devices, + * the access to all of them should be locked to remove changes + * of cross-talk. + * + * Throw `std::runtime_error` on error. + * + */ + virtual void lock(); + + /** Release exclusive access to device (if m_com is DevComuino) + * + * Throw `std::runtime_error` on error. + * + */ + virtual void unlock(); + + private: + std::shared_ptr<I2CCom> m_com; + uint8_t m_muxAddr; + uint8_t m_channel; + + //! Restore multiplexer communication into default state + /** + * Sets the `m_com` device address to the mux address + * and disables all of the channels. + */ + void restore_com(); +}; + +#endif // PCA9547COM_H