11#ifndef PCAL95555_HEADER_INCLUDED
16template <
typename I2cType>
19 : i2c_(bus), previous_pin_states_(0), initialized_(false),
20 a0_level_(a0_level), a1_level_(a1_level), a2_level_(a2_level),
23 address_bits_ = (a0_level ? 1 : 0) | ((a1_level ? 1 : 0) << 1) | ((a2_level ? 1 : 0) << 2);
28 callback.registered =
false;
29 callback.edge = InterruptEdge::Both;
36template <
typename I2cType>
38 : i2c_(bus), previous_pin_states_(0), initialized_(false),
41 constexpr uint8_t BASE_ADDRESS = 0x20;
42 constexpr uint8_t MAX_ADDRESS = 0x27;
43 constexpr uint8_t MAX_BITS = 0x07;
45 if (address < BASE_ADDRESS || address > MAX_ADDRESS) {
47 if (address < BASE_ADDRESS) {
48 address = BASE_ADDRESS;
50 address = MAX_ADDRESS;
66 callback.registered =
false;
67 callback.edge = InterruptEdge::Both;
74template <
typename I2cType>
82template <
typename I2cType>
87 user_variant_ = variant;
88 chip_variant_ = variant;
89 previous_pin_states_ = 0;
90 clearError(Error::I2CReadFail);
96template <
typename I2cType>
99 if (!i2c_->EnsureInitialized()) {
100 setError(Error::I2CReadFail);
101 initialized_ =
false;
107 i2c_->SetAddressPins(a0_level_, a1_level_, a2_level_);
111 uint8_t test_value = 0;
112 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_PORT_0), test_value)) {
115 setError(Error::I2CReadFail);
116 initialized_ =
false;
121 clearError(Error::I2CReadFail);
126 chip_variant_ = user_variant_;
131 if (!detectChipVariant()) {
132 setError(Error::I2CReadFail);
133 initialized_ =
false;
139 previous_pin_states_ = readPinStates();
147template <
typename I2cType>
153template <
typename I2cType>
160 for (
int attempt = 0; attempt <= retries_; ++attempt) {
161 if (i2c_->Write(dev_addr_, reg, &xfer_byte_, 1)) {
162 clearError(Error::I2CWriteFail);
166 setError(Error::I2CWriteFail);
170template <
typename I2cType>
172 for (
int attempt = 0; attempt <= retries_; ++attempt) {
173 if (i2c_->Read(dev_addr_, reg, &xfer_byte_, 1)) {
174 clearError(Error::I2CReadFail);
179 setError(Error::I2CReadFail);
184template <
typename I2cType>
186 if (!EnsureInitialized()) {
190 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_0), 0xFF);
191 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_1), 0xFF);
192 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::POLARITY_INV_0), 0x00);
193 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::POLARITY_INV_1), 0x00);
194 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_0), 0xFF);
195 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_1), 0xFF);
200 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_0), 0xFF);
201 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_1), 0xFF);
202 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_2), 0xFF);
203 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_3), 0xFF);
205 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_LATCH_0), 0x00);
206 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_LATCH_1), 0x00);
208 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_0), 0xFF);
209 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_1), 0xFF);
210 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_SELECT_0), 0xFF);
211 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_SELECT_1), 0xFF);
212 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::INT_MASK_0), 0xFF);
213 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::INT_MASK_1), 0xFF);
215 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_CONF), 0x00);
220template <
typename I2cType>
222 if (!EnsureInitialized()) {
225#if CONFIG_PCAL95555_INIT_FROM_KCONFIG
231 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_1),
237 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_1),
243 uint8_t open_drain_config =
245 writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_CONF), open_drain_config);
251static inline uint8_t updateBit(uint8_t regVal, uint8_t bit,
bool set)
noexcept {
253 return regVal | (1 << bit);
255 return regVal & ~(1 << bit);
260template <
typename I2cType>
262 uint8_t& val0, uint8_t& val1)
noexcept {
266 if (!readRegister(reg0, port0)) {
269 if (!readRegister(reg1, port1)) {
277template <
typename I2cType>
279 uint8_t val0, uint8_t val1)
noexcept {
283 if (!writeRegister(reg0, val0)) {
286 return writeRegister(reg1, val1);
289template <
typename I2cType>
291 uint8_t pin,
bool bit_value)
noexcept {
292 uint8_t reg = (pin < 8) ? reg0 : reg1;
293 uint8_t bit = pin % 8;
295 if (!readRegister(reg, val)) {
298 val = updateBit(val, bit, bit_value);
299 return writeRegister(reg, val);
302template <
typename I2cType>
304 uint16_t mask,
bool bit_value)
noexcept {
307 if (!readDualPort(reg0, reg1, val0, val1)) {
310 for (
int bit = 0; bit < 8; ++bit) {
311 if ((mask & (1U << bit)) != 0U) {
312 val0 = updateBit(val0, bit, bit_value);
314 if ((mask & (1U << (bit + 8))) != 0U) {
315 val1 = updateBit(val1, bit, bit_value);
318 return writeDualPort(reg0, reg1, val0, val1);
323template <
typename I2cType>
325 if (!EnsureInitialized()) {
329 setError(Error::InvalidPin);
332 clearError(Error::InvalidPin);
333 return modifySinglePinRegister(
static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_0),
334 static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_1),
335 pin, (dir == GPIODir::Input));
338template <
typename I2cType>
340 if (!EnsureInitialized()) {
344 setError(Error::InvalidMask);
347 clearError(Error::InvalidMask);
348 return modifyDualPortByMask(
static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_0),
349 static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_1),
350 mask, (dir == GPIODir::Input));
354template <
typename I2cType>
356 if (!EnsureInitialized()) {
361 if (!readDualPort(
static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_0),
362 static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_1), port0, port1)) {
365 for (
const auto& config : configs) {
366 if (config.first >= 16) {
367 setError(Error::InvalidPin);
370 uint8_t bit = config.first % 8;
371 if (config.first < 8) {
372 port0 = updateBit(port0, bit, (config.second == GPIODir::Input));
374 port1 = updateBit(port1, bit, (config.second == GPIODir::Input));
377 clearError(Error::InvalidPin);
378 return writeDualPort(
static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_0),
379 static_cast<uint8_t
>(Pcal95555Reg::CONFIG_PORT_1), port0, port1);
383template <
typename I2cType>
386 (void)ReadPin(pin, level);
391template <
typename I2cType>
393 if (!EnsureInitialized()) {
397 setError(Error::InvalidPin);
400 clearError(Error::InvalidPin);
401 uint8_t reg = (pin < 8) ? static_cast<uint8_t>(Pcal95555Reg::INPUT_PORT_0) :
static_cast<uint8_t
>(Pcal95555Reg::INPUT_PORT_1);
402 uint8_t bit = pin % 8;
404 if (!readRegister(reg, val)) {
407 level = (val & (1 << bit)) != 0;
412template <
typename I2cType>
414 if (!EnsureInitialized()) {
418 setError(Error::InvalidPin);
421 clearError(Error::InvalidPin);
422 return modifySinglePinRegister(
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_0),
423 static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_1),
428template <
typename I2cType>
430 if (!EnsureInitialized()) {
434 setError(Error::InvalidMask);
437 clearError(Error::InvalidMask);
438 return modifyDualPortByMask(
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_0),
439 static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_1),
443template <
typename I2cType>
445 if (!EnsureInitialized()) {
449 setError(Error::InvalidPin);
452 clearError(Error::InvalidPin);
453 uint8_t reg = (pin < 8) ? static_cast<uint8_t>(Pcal95555Reg::OUTPUT_PORT_0)
454 :
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_1);
455 uint8_t bit = pin % 8;
457 if (!readRegister(reg, val)) {
461 return writeRegister(reg, val);
465template <
typename I2cType>
467 if (!EnsureInitialized()) {
472 if (!readDualPort(
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_0),
473 static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_1), port0, port1)) {
476 for (
const auto& config : configs) {
477 if (config.first >= 16) {
478 setError(Error::InvalidPin);
481 uint8_t bit = config.first % 8;
482 if (config.first < 8) {
483 port0 = updateBit(port0, bit, config.second);
485 port1 = updateBit(port1, bit, config.second);
488 clearError(Error::InvalidPin);
489 return writeDualPort(
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_0),
490 static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_PORT_1), port0, port1);
494template <
typename I2cType>
497 if (!EnsureInitialized()) {
504 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_PORT_0), port0)) {
508 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_PORT_1), port1)) {
513 for (uint8_t pin : pins) {
514 if (results.
contains(pin))
continue;
522 uint8_t bit = pin % 8;
525 value = (port0 & (1 << bit)) != 0;
527 value = (port1 & (1 << bit)) != 0;
536template <
typename I2cType>
538 if (!EnsureInitialized() || !requireAgileIO()) {
542 setError(Error::InvalidPin);
545 clearError(Error::InvalidPin);
546 return modifySinglePinRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_0),
547 static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_1),
551template <
typename I2cType>
553 if (!EnsureInitialized() || !requireAgileIO()) {
557 setError(Error::InvalidPin);
560 clearError(Error::InvalidPin);
561 return modifySinglePinRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_SELECT_0),
562 static_cast<uint8_t
>(Pcal95555Reg::PULL_SELECT_1),
567template <
typename I2cType>
569 if (!EnsureInitialized() || !requireAgileIO()) {
574 if (!readDualPort(
static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_0),
575 static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_1), port0, port1)) {
578 for (
const auto& config : configs) {
579 if (config.first >= 16) {
580 setError(Error::InvalidPin);
583 uint8_t bit = config.first % 8;
584 if (config.first < 8) {
585 port0 = updateBit(port0, bit, config.second);
587 port1 = updateBit(port1, bit, config.second);
590 clearError(Error::InvalidPin);
591 return writeDualPort(
static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_0),
592 static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_1), port0, port1);
596template <
typename I2cType>
598 if (!EnsureInitialized() || !requireAgileIO()) {
603 if (!readDualPort(
static_cast<uint8_t
>(Pcal95555Reg::PULL_SELECT_0),
604 static_cast<uint8_t
>(Pcal95555Reg::PULL_SELECT_1), port0, port1)) {
607 for (
const auto& config : configs) {
608 if (config.first >= 16) {
609 setError(Error::InvalidPin);
612 uint8_t bit = config.first % 8;
613 if (config.first < 8) {
614 port0 = updateBit(port0, bit, config.second);
616 port1 = updateBit(port1, bit, config.second);
619 clearError(Error::InvalidPin);
620 return writeDualPort(
static_cast<uint8_t
>(Pcal95555Reg::PULL_SELECT_0),
621 static_cast<uint8_t
>(Pcal95555Reg::PULL_SELECT_1), port0, port1);
625template <
typename I2cType>
627 uint16_t& direction_mask)
noexcept {
628 if (!EnsureInitialized()) {
631 if (!requireAgileIO()) {
635 uint8_t en0 = 0, en1 = 0, sel0 = 0, sel1 = 0;
636 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_0), en0)) {
639 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_ENABLE_1), en1)) {
642 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_SELECT_0), sel0)) {
645 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::PULL_SELECT_1), sel1)) {
649 enable_mask =
static_cast<uint16_t
>((en1 << 8) | en0);
650 direction_mask =
static_cast<uint16_t
>((sel1 << 8) | sel0);
655template <
typename I2cType>
657 if (!EnsureInitialized()) {
660 if (!requireAgileIO()) {
664 setError(Error::InvalidPin);
667 clearError(Error::InvalidPin);
668 uint8_t base = (pin < 8) ? static_cast<uint8_t>(Pcal95555Reg::DRIVE_STRENGTH_0) :
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_2);
669 uint8_t index = pin % 8;
670 uint8_t reg = base + ((index >= 4) ? 1 : 0);
671 uint8_t bit = (index % 4) * 2;
673 if (!readRegister(reg, val)) {
677 val &= ~(0x3 << bit);
678 val |= (uint8_t(level) << bit);
679 return writeRegister(reg, val);
683template <
typename I2cType>
685 if (!EnsureInitialized()) {
688 if (!requireAgileIO()) {
692 uint8_t ds0 = 0, ds1 = 0, ds2 = 0, ds3 = 0;
693 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_0), ds0)) {
696 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_1), ds1)) {
699 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_2), ds2)) {
702 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_3), ds3)) {
707 for (
const auto& config : configs) {
708 uint8_t pin = config.first;
712 setError(Error::InvalidPin);
716 uint8_t index = pin % 8;
717 uint8_t reg_offset = (index >= 4) ? 1 : 0;
718 uint8_t bit = (index % 4) * 2;
720 uint8_t* reg_ptr =
nullptr;
722 reg_ptr = (reg_offset == 0) ? &ds0 : &ds1;
724 reg_ptr = (reg_offset == 0) ? &ds2 : &ds3;
728 *reg_ptr &= ~(0x3 << bit);
729 *reg_ptr |= (uint8_t(level) << bit);
732 clearError(Error::InvalidPin);
735 if (!writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_0), ds0)) {
738 if (!writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_1), ds1)) {
741 if (!writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_2), ds2)) {
744 return writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::DRIVE_STRENGTH_3), ds3);
748template <
typename I2cType>
750 if (!EnsureInitialized()) {
753 if (!requireAgileIO()) {
757 setError(Error::InvalidPin);
760 clearError(Error::InvalidPin);
765 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INT_MASK_0), mask0)) {
768 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INT_MASK_1), mask1)) {
773 uint16_t mask = (uint16_t(mask1) << 8) | mask0;
776 if (state == InterruptState::Enabled) {
777 mask &= ~(1U << pin);
783 return ConfigureInterruptMask(mask);
787template <
typename I2cType>
789 if (!EnsureInitialized()) {
792 if (!requireAgileIO()) {
798 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INT_MASK_0), mask0)) {
801 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INT_MASK_1), mask1)) {
806 uint16_t mask = (uint16_t(mask1) << 8) | mask0;
809 for (
const auto& config : configs) {
810 uint8_t pin = config.first;
814 setError(Error::InvalidPin);
819 if (state == InterruptState::Enabled) {
820 mask &= ~(1U << pin);
826 clearError(Error::InvalidPin);
829 return ConfigureInterruptMask(mask);
833template <
typename I2cType>
835 if (!EnsureInitialized()) {
838 if (!requireAgileIO()) {
841 if (!writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::INT_MASK_0), uint8_t(mask & 0xFF))) {
844 return writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::INT_MASK_1), uint8_t((mask >> 8) & 0xFF));
848template <
typename I2cType>
850 if (!EnsureInitialized()) {
853 if (!requireAgileIO()) {
856 uint8_t low_byte = 0;
857 uint8_t high_byte = 0;
858 readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INT_STATUS_0), low_byte);
859 readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INT_STATUS_1), high_byte);
860 return uint16_t(high_byte) << 8 | low_byte;
864template <
typename I2cType>
866 if (!EnsureInitialized()) {
869 if (!requireAgileIO()) {
872 uint8_t val = (port_1_open_drain ? 1 : 0) << 1 | (port_0_open_drain ? 1 : 0);
873 return writeRegister(
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_CONF), val);
877template <
typename I2cType>
879 std::function<
void(uint8_t,
bool)> callback) {
880 if (!EnsureInitialized()) {
884 setError(Error::InvalidPin);
887 clearError(Error::InvalidPin);
893 pin_callbacks_[pin].callback = callback;
894 pin_callbacks_[pin].edge = edge;
895 pin_callbacks_[pin].registered =
true;
898 uint16_t current_states = readPinStates();
899 previous_pin_states_ = current_states;
905template <
typename I2cType>
908 if (!EnsureInitialized()) {
912 setError(Error::InvalidPin);
915 clearError(Error::InvalidPin);
917 if (!pin_callbacks_[pin].registered) {
921 pin_callbacks_[pin].registered =
false;
922 pin_callbacks_[pin].callback =
nullptr;
927template <
typename I2cType>
929 irq_callback_ = callback;
933template <
typename I2cType>
935 if (!EnsureInitialized()) {
939 return i2c_->RegisterInterruptHandler([
this]() {
945template <
typename I2cType>
949 readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_PORT_0), port0);
950 readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_PORT_1), port1);
951 return static_cast<uint16_t
>((uint16_t(port1) << 8) | port0);
955template <
typename I2cType>
957 if (!EnsureInitialized()) {
960 return readPinStates();
963template <
typename I2cType>
965 uint16_t& value)
noexcept {
967 if (!EnsureInitialized()) {
972 if (!readDualPort(reg0, reg1, port0, port1)) {
975 value =
static_cast<uint16_t
>(port0) |
976 static_cast<uint16_t
>(
static_cast<uint16_t
>(port1) << 8);
980template <
typename I2cType>
982 uint16_t value)
noexcept {
983 if (!EnsureInitialized()) {
986 return writeDualPort(reg0, reg1,
static_cast<uint8_t
>(value & 0xFFU),
987 static_cast<uint8_t
>((value >> 8) & 0xFFU));
991template <
typename I2cType>
993 if (!EnsureInitialized()) {
997 uint16_t interrupt_status = 0;
1001 interrupt_status = GetInterruptStatus();
1005 uint16_t current_states = readPinStates();
1006 interrupt_status = current_states ^ previous_pin_states_;
1011 uint16_t current_states = readPinStates();
1014 if (irq_callback_) {
1015 irq_callback_(interrupt_status);
1019 for (uint8_t pin = 0; pin < 16; ++pin) {
1021 if ((interrupt_status & (1U << pin)) == 0) {
1026 if (!pin_callbacks_[pin].registered || !pin_callbacks_[pin].callback) {
1031 bool previous_state = (previous_pin_states_ & (1U << pin)) != 0;
1032 bool current_state = (current_states & (1U << pin)) != 0;
1033 bool rising_edge = !previous_state && current_state;
1034 bool falling_edge = previous_state && !current_state;
1037 bool should_call =
false;
1038 switch (pin_callbacks_[pin].edge) {
1039 case InterruptEdge::Rising:
1040 should_call = rising_edge;
1042 case InterruptEdge::Falling:
1043 should_call = falling_edge;
1045 case InterruptEdge::Both:
1046 should_call = rising_edge || falling_edge;
1052 pin_callbacks_[pin].callback(pin, current_state);
1057 previous_pin_states_ = current_states;
1060template <
typename I2cType>
1062 if (!EnsureInitialized()) {
1066 setError(Error::InvalidPin);
1069 clearError(Error::InvalidPin);
1070 return modifySinglePinRegister(
static_cast<uint8_t
>(Pcal95555Reg::POLARITY_INV_0),
1071 static_cast<uint8_t
>(Pcal95555Reg::POLARITY_INV_1),
1072 pin, (polarity == Polarity::Inverted));
1075template <
typename I2cType>
1077 if (!EnsureInitialized()) {
1081 setError(Error::InvalidMask);
1084 clearError(Error::InvalidMask);
1085 return modifyDualPortByMask(
static_cast<uint8_t
>(Pcal95555Reg::POLARITY_INV_0),
1086 static_cast<uint8_t
>(Pcal95555Reg::POLARITY_INV_1),
1087 mask, (polarity == Polarity::Inverted));
1091template <
typename I2cType>
1093 if (!EnsureInitialized()) {
1098 if (!readDualPort(
static_cast<uint8_t
>(Pcal95555Reg::POLARITY_INV_0),
1099 static_cast<uint8_t
>(Pcal95555Reg::POLARITY_INV_1), port0, port1)) {
1102 for (
const auto& config : configs) {
1103 if (config.first >= 16) {
1104 setError(Error::InvalidPin);
1107 uint8_t bit = config.first % 8;
1108 if (config.first < 8) {
1109 port0 = updateBit(port0, bit, (config.second == Polarity::Inverted));
1111 port1 = updateBit(port1, bit, (config.second == Polarity::Inverted));
1114 clearError(Error::InvalidPin);
1115 return writeDualPort(
static_cast<uint8_t
>(Pcal95555Reg::POLARITY_INV_0),
1116 static_cast<uint8_t
>(Pcal95555Reg::POLARITY_INV_1), port0, port1);
1119template <
typename I2cType>
1121 if (!EnsureInitialized() || !requireAgileIO()) {
1125 setError(Error::InvalidPin);
1128 clearError(Error::InvalidPin);
1129 return modifySinglePinRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_LATCH_0),
1130 static_cast<uint8_t
>(Pcal95555Reg::INPUT_LATCH_1),
1134template <
typename I2cType>
1136 if (!EnsureInitialized() || !requireAgileIO()) {
1140 setError(Error::InvalidMask);
1143 clearError(Error::InvalidMask);
1144 return modifyDualPortByMask(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_LATCH_0),
1145 static_cast<uint8_t
>(Pcal95555Reg::INPUT_LATCH_1),
1150template <
typename I2cType>
1152 if (!EnsureInitialized() || !requireAgileIO()) {
1157 if (!readDualPort(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_LATCH_0),
1158 static_cast<uint8_t
>(Pcal95555Reg::INPUT_LATCH_1), port0, port1)) {
1161 for (
const auto& config : configs) {
1162 if (config.first >= 16) {
1163 setError(Error::InvalidPin);
1166 uint8_t bit = config.first % 8;
1167 if (config.first < 8) {
1168 port0 = updateBit(port0, bit, config.second);
1170 port1 = updateBit(port1, bit, config.second);
1173 clearError(Error::InvalidPin);
1174 return writeDualPort(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_LATCH_0),
1175 static_cast<uint8_t
>(Pcal95555Reg::INPUT_LATCH_1), port0, port1);
1178template <
typename I2cType>
1180 return error_flags_;
1183template <
typename I2cType>
1185 return (error_flags_ &
static_cast<uint16_t
>(e)) != 0;
1188template <
typename I2cType>
1190 return error_flags_ != 0;
1193template <
typename I2cType>
1195 error_flags_ &= ~static_cast<uint16_t>(e);
1198template <
typename I2cType>
1200 error_flags_ &= ~mask;
1203template <
typename I2cType>
1205 error_flags_ |=
static_cast<uint16_t
>(error_code);
1208template <
typename I2cType>
1210 error_flags_ &= ~static_cast<uint16_t>(error_code);
1214template <
typename I2cType>
1220template <
typename I2cType>
1222 return chip_variant_;
1226template <
typename I2cType>
1229 setError(Error::UnsupportedFeature);
1237template <
typename I2cType>
1240 int saved_retries = retries_;
1247 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_PORT_0), dummy)) {
1249 retries_ = saved_retries;
1252 clearError(Error::I2CReadFail);
1256 uint8_t probe_value = 0;
1257 if (readRegister(
static_cast<uint8_t
>(Pcal95555Reg::OUTPUT_CONF), probe_value)) {
1261 if (readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_PORT_0), dummy)) {
1263 clearError(Error::I2CReadFail);
1266 clearError(Error::I2CReadFail);
1270 clearError(Error::I2CReadFail);
1273 if (readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_PORT_0), dummy)) {
1276 clearError(Error::I2CReadFail);
1279 clearError(Error::I2CReadFail);
1284 retries_ = saved_retries;
1289template <
typename I2cType>
1295template <
typename I2cType>
1297 return address_bits_;
1302template <
typename I2cType>
1304 uint8_t new_addr = calculateAddress(new_bits);
1305 bool a0_level = (new_bits & 0x01) != 0;
1306 bool a1_level = (new_bits & 0x02) != 0;
1307 bool a2_level = (new_bits & 0x04) != 0;
1310 bool gpio_set = i2c_->SetAddressPins(a0_level, a1_level, a2_level);
1313 address_bits_ = new_bits;
1314 dev_addr_ = new_addr;
1315 a0_level_ = a0_level;
1316 a1_level_ = a1_level;
1317 a2_level_ = a2_level;
1320 initialized_ =
false;
1323 uint8_t test_value = 0;
1324 if (!readRegister(
static_cast<uint8_t
>(Pcal95555Reg::INPUT_PORT_0), test_value)) {
1325 setError(Error::I2CReadFail);
1331 clearError(Error::I2CReadFail);
1332 clearError(Error::InvalidAddress);
1337 chip_variant_ = user_variant_;
1338 }
else if (!detectChipVariant()) {
1339 setError(Error::I2CReadFail);
1343 initialized_ =
true;
1348template <
typename I2cType>
1350 uint8_t new_bits = (a0_level ? 1U : 0U)
1351 | ((a1_level ? 1U : 0U) << 1)
1352 | ((a2_level ? 1U : 0U) << 2);
1353 return changeAddressImpl(
static_cast<uint8_t
>(new_bits));
1357template <
typename I2cType>
1359 constexpr uint8_t BASE_ADDRESS = 0x20;
1360 constexpr uint8_t MAX_ADDRESS = 0x27;
1362 if (address < BASE_ADDRESS || address > MAX_ADDRESS) {
1363 setError(Error::InvalidAddress);
1367 uint8_t new_bits = (address - BASE_ADDRESS) & 0x07;
1368 return changeAddressImpl(new_bits);
bool SetPullEnables(std::initializer_list< std::pair< uint8_t, bool > > configs) noexcept
Definition pcal95555.ipp:568
bool EnableMultipleInputLatches(uint16_t mask, bool enable) noexcept
Enable or disable input latch for multiple pins using a bitmask.
Definition pcal95555.ipp:1135
bool readDualPort(uint8_t reg0, uint8_t reg1, uint8_t &val0, uint8_t &val1) noexcept
Read both port registers (port0 and port1) in one call.
Definition pcal95555.ipp:261
bool writeRegister(uint8_t reg, uint8_t value) noexcept
Write a single byte to a device register with retry logic.
Definition pcal95555.ipp:154
bool EnsureInitialized() noexcept
Definition pcal95555.ipp:75
bool RegisterPinInterrupt(uint8_t pin, InterruptEdge edge, std::function< void(uint8_t pin, bool state)> callback)
Definition pcal95555.ipp:878
bool ReadDualPortRegister(uint8_t reg0, uint8_t reg1, uint16_t &value) noexcept
Read a dual-port register pair into a 16-bit pin mask.
Definition pcal95555.ipp:964
void InitFromConfig() noexcept
Initialize the device using values from Kconfig.
Definition pcal95555.ipp:221
bool initialize() noexcept
Perform actual initialization of the driver.
Definition pcal95555.ipp:97
bool SetPullEnable(uint8_t pin, bool enable) noexcept
Enable or disable the pull-up/pull-down resistor on a pin.
Definition pcal95555.ipp:537
bool SetMultipleOutputs(uint16_t mask, bool value) noexcept
Definition pcal95555.ipp:429
bool SetDriveStrength(uint8_t pin, DriveStrength level) noexcept
Configure the output drive strength for a GPIO pin.
Definition pcal95555.ipp:656
bool RegisterInterruptHandler() noexcept
Definition pcal95555.ipp:934
bool a1_level_
Definition pcal95555.hpp:1130
bool SetPinPolarity(uint8_t pin, Polarity polarity) noexcept
Configure input polarity inversion for a single pin.
Definition pcal95555.ipp:1061
uint8_t dev_addr_
Definition pcal95555.hpp:1121
bool SetPullDirections(std::initializer_list< std::pair< uint8_t, bool > > configs) noexcept
Definition pcal95555.ipp:597
bool GetPullConfiguration(uint16_t &enable_mask, uint16_t &direction_mask) noexcept
Read the current pull resistor configuration from hardware registers.
Definition pcal95555.ipp:626
void SetRetries(int retries) noexcept
Configure retry mechanism for I2C transactions.
Definition pcal95555.ipp:148
bool EnableInputLatches(std::initializer_list< std::pair< uint8_t, bool > > configs) noexcept
Definition pcal95555.ipp:1151
PinReadResult ReadPins(std::initializer_list< uint8_t > pins) noexcept
Definition pcal95555.ipp:495
uint16_t readPinStates() noexcept
Read current pin states from input port registers.
Definition pcal95555.ipp:946
bool detectChipVariant() noexcept
Detect the chip variant by probing an Agile I/O register.
Definition pcal95555.ipp:1238
bool EnableInputLatch(uint8_t pin, bool enable) noexcept
Enable or disable the input latch for a single pin.
Definition pcal95555.ipp:1120
bool HasError(Error e) const noexcept
Check if a specific error flag is set.
Definition pcal95555.ipp:1184
void ClearErrorFlags(uint16_t mask=0xFFFF) noexcept
Clear specific error flags by raw bitmask.
Definition pcal95555.ipp:1199
bool SetOutputMode(bool port_0_open_drain, bool port_1_open_drain) noexcept
Configure per-port output mode (push-pull or open-drain).
Definition pcal95555.ipp:865
bool a0_level_
Definition pcal95555.hpp:1129
bool readRegister(uint8_t reg, uint8_t &value) noexcept
Read a device register with retry logic.
Definition pcal95555.ipp:171
bool modifyDualPortByMask(uint8_t reg0, uint8_t reg1, uint16_t mask, bool bit_value) noexcept
Batch mask read-modify-write on a dual-port register pair.
Definition pcal95555.ipp:303
uint16_t GetErrorFlags() const noexcept
Retrieve currently latched error flags.
Definition pcal95555.ipp:1179
bool SetDirections(std::initializer_list< std::pair< uint8_t, GPIODir > > configs) noexcept
Definition pcal95555.ipp:355
void ClearError(Error e) noexcept
Clear a single error flag.
Definition pcal95555.ipp:1194
bool WriteDualPortRegister(uint8_t reg0, uint8_t reg1, uint16_t value) noexcept
Write a dual-port register pair from a 16-bit pin mask (no readback).
Definition pcal95555.ipp:981
bool ConfigureInterruptMask(uint16_t mask) noexcept
Definition pcal95555.ipp:834
bool a2_level_
Definition pcal95555.hpp:1131
uint8_t GetAddress() const noexcept
Get the current I2C address of the device.
Definition pcal95555.ipp:1290
bool HasAgileIO() const noexcept
Definition pcal95555.ipp:1215
bool SetDriveStrengths(std::initializer_list< std::pair< uint8_t, DriveStrength > > configs) noexcept
Definition pcal95555.ipp:684
void clearError(Error error_code) noexcept
Definition pcal95555.ipp:1209
bool SetPolarities(std::initializer_list< std::pair< uint8_t, Polarity > > configs) noexcept
Definition pcal95555.ipp:1092
uint8_t address_bits_
Definition pcal95555.hpp:1122
bool ConfigureInterrupts(std::initializer_list< std::pair< uint8_t, InterruptState > > configs) noexcept
Definition pcal95555.ipp:788
uint8_t GetAddressBits() const noexcept
Get the current A2-A0 address bits.
Definition pcal95555.ipp:1296
bool changeAddressImpl(uint8_t new_bits) noexcept
Shared implementation for ChangeAddress overloads.
Definition pcal95555.ipp:1303
ChipVariant GetChipVariant() const noexcept
Get the detected chip variant.
Definition pcal95555.ipp:1221
bool WritePin(uint8_t pin, bool value) noexcept
Write a logical level to a GPIO output pin.
Definition pcal95555.ipp:413
bool ForceMarkInitialized(ChipVariant variant) noexcept
Mark the driver initialized without the probe read.
Definition pcal95555.ipp:83
bool SetPullDirection(uint8_t pin, bool pull_up) noexcept
Select internal pull-up or pull-down resistor direction.
Definition pcal95555.ipp:552
static constexpr uint8_t calculateAddress(uint8_t bits)
Calculate I2C address from A2-A0 bits.
Definition pcal95555.hpp:1142
void SetInterruptCallback(const std::function< void(uint16_t)> &callback) noexcept
Definition pcal95555.ipp:928
bool ChangeAddress(bool a0_level, bool a1_level, bool a2_level) noexcept
Definition pcal95555.ipp:1349
PinInterruptCallback pin_callbacks_[16]
Definition pcal95555.hpp:1126
bool ReadPin(uint8_t pin) noexcept
Read the current logical level of a GPIO pin.
Definition pcal95555.ipp:384
bool SetMultipleDirections(uint16_t mask, GPIODir dir) noexcept
Set the direction for multiple GPIO pins at once using a bitmask.
Definition pcal95555.ipp:339
bool ConfigureInterrupt(uint8_t pin, InterruptState state) noexcept
Definition pcal95555.ipp:749
void setError(Error error_code) noexcept
Definition pcal95555.ipp:1204
bool SetMultiplePolarities(uint16_t mask, Polarity polarity) noexcept
Configure input polarity for multiple pins using a bitmask.
Definition pcal95555.ipp:1076
bool requireAgileIO() noexcept
Check that the chip supports Agile I/O, setting error if not.
Definition pcal95555.ipp:1227
void HandleInterrupt() noexcept
Internal handler to process an interrupt event.
Definition pcal95555.ipp:992
bool SetPinDirection(uint8_t pin, GPIODir dir) noexcept
Set the direction of a single GPIO pin.
Definition pcal95555.ipp:324
bool modifySinglePinRegister(uint8_t reg0, uint8_t reg1, uint8_t pin, bool bit_value) noexcept
Single-pin read-modify-write on a dual-port register pair.
Definition pcal95555.ipp:290
bool WritePins(std::initializer_list< std::pair< uint8_t, bool > > configs) noexcept
Definition pcal95555.ipp:466
uint16_t GetInterruptStatus() noexcept
Retrieve and clear the interrupt status.
Definition pcal95555.ipp:849
bool TogglePin(uint8_t pin) noexcept
Toggle the output state of a GPIO pin.
Definition pcal95555.ipp:444
PCAL95555(I2cType *bus, bool a0_level, bool a1_level, bool a2_level, ChipVariant variant=ChipVariant::Unknown)
Definition pcal95555.ipp:17
bool HasAnyError() const noexcept
Check if any error flag is set.
Definition pcal95555.ipp:1189
bool UnregisterPinInterrupt(uint8_t pin) noexcept
Unregister callback for a specific pin interrupt.
Definition pcal95555.ipp:906
void ResetToDefault() noexcept
Reset all registers to their power-on default state.
Definition pcal95555.ipp:185
bool writeDualPort(uint8_t reg0, uint8_t reg1, uint8_t val0, uint8_t val1) noexcept
Write both port registers (port0 and port1) in one call.
Definition pcal95555.ipp:278
uint16_t ReadAllInputs() noexcept
Definition pcal95555.ipp:956
InterruptState
Definition pcal95555.hpp:96
GPIODir
Definition pcal95555.hpp:77
DriveStrength
Definition pcal95555.hpp:83
ChipVariant
Identifies the detected or user-specified chip variant.
Definition pcal95555.hpp:203
@ PCAL9555A
NXP PCAL9555A with Agile I/O (registers 0x00-0x07 + 0x40-0x4F)
@ Unknown
Not yet detected (pre-initialization)
@ PCA9555
Standard PCA9555 (registers 0x00-0x07 only)
Polarity
Definition pcal95555.hpp:80
Error
Definition pcal95555.hpp:103
InterruptEdge
Definition pcal95555.hpp:89
Driver for the PCA9555 and PCAL9555A 16-bit I/O expanders with I²C interface.
#define CONFIG_PCAL95555_INIT_DIRECTION
Definition pcal95555_kconfig.hpp:298
#define CONFIG_PCAL95555_INIT_PULL_ENABLE
Definition pcal95555_kconfig.hpp:302
#define CONFIG_PCAL95555_INIT_OUTPUT
Definition pcal95555_kconfig.hpp:310
#define CONFIG_PCAL95555_INIT_OD_PORT1
Definition pcal95555_kconfig.hpp:317
#define CONFIG_PCAL95555_INIT_OD_PORT0
Definition pcal95555_kconfig.hpp:314
#define CONFIG_PCAL95555_INIT_PULL_UP
Definition pcal95555_kconfig.hpp:306
Fixed-capacity result container for multi-pin read operations.
Definition pcal95555.hpp:140
bool contains(uint8_t pin) const noexcept
Check whether a pin number is already stored in the result set.
Definition pcal95555.hpp:173
void push_back(uint8_t pin, bool value) noexcept
Append a pin/value pair. Silently drops entries beyond kMaxPins.
Definition pcal95555.hpp:164