LCOV - code coverage report
Current view: top level - src/AH/Hardware/ExtendedInputOutput - ExtendedIOElement.hpp (source / functions) Coverage Total Hit
Test: 73449d9b107c772cf65493691543348214e5d5eb Lines: 100.0 % 69 69
Test Date: 2026-06-06 17:44:35 Functions: 100.0 % 18 18
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /* ✔ */
       2              : 
       3              : #pragma once
       4              : 
       5              : #include "ExtendedInputOutput.hpp"
       6              : #include <AH/Containers/Updatable.hpp>
       7              : #include <AH/Hardware/Hardware-Types.hpp>
       8              : 
       9              : BEGIN_AH_NAMESPACE
      10              : 
      11              : /**
      12              :  * @brief   An abstract base class for Extended Input/Output elements.
      13              :  *
      14              :  * The limited number of IO pins of the Arduino can be extended by
      15              :  * adding multiplexers, shift registers, IO expanders, etc.  
      16              :  * ExtendedIOElement serves as a base class for all these expanders.
      17              :  *
      18              :  * The pins of each extended IO element are mapped to a pin number greater than
      19              :  * the greatest Arduino pin number, represented by the @ref ExtIO::pin_t type.  
      20              :  * You can supply this pin number to the IO functions in the @ref ExtIO
      21              :  * namespace.  
      22              :  * If the pin number corresponds to an actual Arduino pin, the default Arduino
      23              :  * IO function (`digitalRead`, `digitalWrite`, ...) will be called.  
      24              :  * If the pin number does not correspond to a valid Arduino pin, it is an
      25              :  * extended IO pin number, so the extended IO element that this pin belongs to
      26              :  * is looked up, and the IO function for this element is executed with the
      27              :  * correct pin number.
      28              :  *
      29              :  * For example:
      30              :  * Imagine an Arduino with 20 pins (e.g. the Arduino UNO).
      31              :  * Pins 0 - 19 will correspond to the Arduino pins, and
      32              :  * `ExtIO::digitalRead(pin)` will have the exact same effect as
      33              :  * the standard `digitalRead(pin)` function (albeit a little slower).  
      34              :  * Now, we'll add two 8-channel analog multiplexers, let's call them
      35              :  * `mux1` and `mux2`.  
      36              :  * The first pin (pin 0) of `mux1` will be extended IO pin number 20,
      37              :  * the last pin (pin 7) of `mux1` will be extended IO pin number 27, etc.
      38              :  * The first pin of `mux2` will be extended IO pin number 28, you get the idea.
      39              :  * If you now call `ExtIO::digitalRead(mux1.pin(7))` or
      40              :  * `ExtIO::digitalRead(27)`, both will be translated to `mux1.digitalRead(7)`.
      41              :  *
      42              :  * The number of extended IO elements is limited only by the size of
      43              :  * `pin_t`. However, looking up the extended IO element for a given
      44              :  * extended IO pin number uses linear search, so that might add
      45              :  * some noticeable overhead for large pin numbers.  
      46              :  * 
      47              :  * The design here is a compromise: saving a pointer to each extended IO element
      48              :  * to find it directly would be faster than having to search all elements each
      49              :  * time. On the other hand, it would require each `pin_t` variable to be at
      50              :  * least one byte larger. Since almost all other classes in this library store
      51              :  * pin variables, the memory penalty would be too large, especially on AVR
      52              :  * microcontrollers.  
      53              :  * Another reason to do it this way, is that this approach is still fast enough
      54              :  * to make sure it is not noticeable to human users.  
      55              :  * If you need faster extended GPIO access, you can use the
      56              :  * @ref ExtIO::CachedExtIOPin class.
      57              :  */
      58              : class ExtendedIOElement : public UpdatableCRTP<ExtendedIOElement> {
      59              :   protected:
      60              :     /**
      61              :      * @brief   Create an ExtendedIOElement with the given number of pins.
      62              :      * 
      63              :      * @param   length
      64              :      *          The number of pins this element has.
      65              :      */
      66              :     ExtendedIOElement(pin_int_t length);
      67              : 
      68              :     /// Copying not allowed.
      69              :     ExtendedIOElement(const ExtendedIOElement &) = delete;
      70              :     /// Copying not allowed.
      71              :     ExtendedIOElement &operator=(const ExtendedIOElement &) = delete;
      72              : 
      73              :     /// Move constructor.
      74              :     ExtendedIOElement(ExtendedIOElement &&) = default;
      75              :     /// Move assignment.
      76              :     ExtendedIOElement &operator=(ExtendedIOElement &&) = delete;
      77              : 
      78              :   public:
      79              :     /** 
      80              :      * @brief   Set the mode of a given pin.
      81              :      * 
      82              :      * @note    This function might not be implemented by all subclasses.  
      83              :      *          Some extended IO types, such as shift registers, can only be 
      84              :      *          used as outputs.  
      85              :      *          On others, it might be implemented, but it could impact all pins
      86              :      *          of the IO element. For example, enabling the internal pull-up 
      87              :      *          resistor on an analog multiplexer affects all pins of the mux.
      88              :      * 
      89              :      * @param   pin
      90              :      *          The (zero-based) pin of this IO element.
      91              :      * @param   mode
      92              :      *          The mode to set the pin to (e.g. `INPUT`, `OUTPUT` or 
      93              :      *          `INPUT_PULLUP`).
      94              :      */
      95            1 :     virtual void pinMode(pin_int_t pin, PinMode_t mode) {
      96            1 :         pinModeBuffered(pin, mode);
      97            1 :         updateBufferedOutputs();
      98            1 :     }
      99              : 
     100              :     /**
     101              :      * @brief   Set the mode of a given pin in the software buffer.
     102              :      * The buffer is written to the ExtIO device when @ref updateBufferedOutputs
     103              :      * is called.
     104              :      * @copydetails pinMode
     105              :      */
     106              :     virtual void pinModeBuffered(pin_int_t pin, PinMode_t mode) = 0;
     107              : 
     108              :     /** 
     109              :      * @brief   Set the output of the given pin to the given state.
     110              :      * 
     111              :      * @param   pin
     112              :      *          The (zero-based) pin of this IO element.
     113              :      * @param   state
     114              :      *          The new state to set the pin to.
     115              :      */
     116            1 :     virtual void digitalWrite(pin_int_t pin, PinStatus_t state) {
     117            1 :         digitalWriteBuffered(pin, state);
     118            1 :         updateBufferedOutputs();
     119            1 :     }
     120              : 
     121              :     /**
     122              :      * @brief   Set the output of a given pin in the software buffer.
     123              :      * The buffer is written to the ExtIO device when @ref updateBufferedOutputs
     124              :      * is called.
     125              :      * @copydetails digitalWrite
     126              :      */
     127              :     virtual void digitalWriteBuffered(pin_int_t pin, PinStatus_t state) = 0;
     128              : 
     129              :     /** 
     130              :      * @brief   Read the state of the given pin.
     131              :      * 
     132              :      * @param   pin
     133              :      *          The (zero-based) pin of this IO element.
     134              :      * @return  The state of the given pin.
     135              :      */
     136            1 :     virtual PinStatus_t digitalRead(pin_int_t pin) {
     137            1 :         updateBufferedInputs();
     138            1 :         return digitalReadBuffered(pin);
     139              :     }
     140              : 
     141              :     /** 
     142              :      * @brief   Read the state of the given pin from the software buffer.
     143              :      * To update the buffer, you have to call @ref updateBufferedInputs first.
     144              :      * @copydetails digitalRead
     145              :      */
     146              :     virtual PinStatus_t digitalReadBuffered(pin_int_t pin) = 0;
     147              : 
     148              :     /**
     149              :      * @brief   Write an analog (or PWM) value to the given pin.
     150              :      * 
     151              :      * @param   pin 
     152              :      *          The (zero-based) pin of this IO element.
     153              :      * @param   val 
     154              :      *          The new analog value to set the pin to.
     155              :      */
     156            1 :     virtual void analogWrite(pin_int_t pin, analog_t val) {
     157            1 :         analogWriteBuffered(pin, val);
     158            1 :         updateBufferedOutputs();
     159            1 :     }
     160              : 
     161              :     /**
     162              :      * @brief   Write an analog (or PWM) value to the software buffer given pin.
     163              :      * The buffer is written to the ExtIO device when @ref updateBufferedOutputs
     164              :      * is called.
     165              :      * @copydetails analogWrite
     166              :      */
     167              :     virtual void analogWriteBuffered(pin_int_t pin, analog_t val) = 0;
     168              : 
     169              :     /**
     170              :      * @brief   Read the analog value of the given pin.
     171              :      * 
     172              :      * @param   pin 
     173              :      *          The (zero-based) pin of this IO element.
     174              :      * @return  The new analog value of pin.
     175              :      */
     176            1 :     virtual analog_t analogRead(pin_int_t pin) {
     177            1 :         updateBufferedInputs();
     178            1 :         return analogReadBuffered(pin);
     179              :     }
     180              : 
     181              :     /** 
     182              :      * @brief   Read the analog value of the given pin from the software buffer.
     183              :      * To update the buffer, you have to call @ref updateBufferedInputs first.
     184              :      * @copydetails analogRead
     185              :      */
     186              :     virtual analog_t analogReadBuffered(pin_int_t pin) = 0;
     187              : 
     188              :     /**
     189              :      * @brief   Initialize the extended IO element.
     190              :      */
     191              :     virtual void begin() = 0;
     192              : 
     193              :     /**
     194              :      * @brief   Initialize all extended IO elements.
     195              :      */
     196              :     static void beginAll();
     197              : 
     198              :     /**
     199              :      * @brief   Write the internal state to the physical outputs.
     200              :      */
     201              :     virtual void updateBufferedOutputs() = 0;
     202              : 
     203              :     /**
     204              :      * @brief   Write the internal states to the physical outputs for all 
     205              :      *          extended IO elements.
     206              :      */
     207              :     static void updateAllBufferedOutputs();
     208              : 
     209              :     /** 
     210              :      * @brief   Read the physical state into the input buffers.
     211              :      */
     212              :     virtual void updateBufferedInputs() = 0;
     213              : 
     214              :     /** 
     215              :      * @brief   Read the physical state into the input buffers for all extended
     216              :      *          IO elements.
     217              :      */
     218              :     static void updateAllBufferedInputs();
     219              : 
     220              :     /**
     221              :      * @brief   Get the extended IO pin number of a given physical pin of this
     222              :      *          extended IO element.
     223              :      * @param   pin
     224              :      *          The zero-based physical pin number of this IO element.
     225              :      * @return  The global, unique extended IO pin number for the given pin.
     226              :      */
     227              :     pin_t pin(pin_int_t pin) const;
     228              : 
     229              :     /**
     230              :      * @brief   Get the extended IO pin number of a given physical pin of this
     231              :      *          extended IO element.  
     232              :      *          It is alias for `ExtendedIOElement::pin`.
     233              :      * @param   pin
     234              :      *          The zero-based physical pin number of this IO element.
     235              :      * @return  The global, unique extended IO pin number for the given pin.
     236              :      */
     237              :     pin_t operator[](pin_int_t pin) const;
     238              : 
     239              :     /**
     240              :      * @brief Get the number of pins this IO element has.
     241              :      * 
     242              :      * @return The number of pins this IO element has. 
     243              :      */
     244              :     pin_int_t getLength() const;
     245              : 
     246              :     /**
     247              :      * @brief   Get the largest global extended IO pin number that belongs to
     248              :      *          this extended IO element.
     249              :      */
     250              :     pin_t getEnd() const;
     251              : 
     252              :     /**
     253              :      * @brief   Get the smallest global extended IO pin number that belongs to
     254              :      *          this extended IO element.
     255              :      */
     256              :     pin_t getStart() const;
     257              : 
     258              :     /**
     259              :      * @brief   Get the list of all Extended IO elements.
     260              :      */
     261              :     static DoublyLinkedList<ExtendedIOElement> &getAll();
     262              : 
     263              :   private:
     264              :     const pin_int_t length;
     265              :     const pin_t start;
     266              :     const pin_t end;
     267              :     static pin_t offset;
     268              : };
     269              : 
     270              : namespace ExtIO {
     271              : 
     272              : struct CachedExtIOPin {
     273           22 :     explicit CachedExtIOPin(pin_t pin)
     274           33 :         : element(pin == NO_PIN || isNativePin(pin) ? nullptr
     275           11 :                                                     : getIOElementOfPin(pin)),
     276           22 :           elementPin(element ? pin - element->getStart() : pin.pin) {}
     277              : 
     278              :     template <class FRet, class... FArgs, class Fallback>
     279              :     FRet __attribute__((always_inline))
     280              :     apply(FRet (ExtendedIOElement::*func)(pin_int_t, FArgs...),
     281              :           Fallback &&fallback, FArgs... args) {
     282           59 :         if (element != nullptr)
     283           22 :             return (element->*func)(elementPin, args...);
     284           37 :         else if (elementPin != NO_PIN_INT)
     285           32 :             return fallback(arduino_pin_cast(elementPin), args...);
     286              :         else
     287            5 :             return static_cast<FRet>(0);
     288              :     }
     289              : 
     290              :     ExtendedIOElement *element;
     291              :     pin_int_t elementPin;
     292              : };
     293              : 
     294              : /// An ExtIO version of the Arduino function
     295              : /// @see    ExtendedIOElement::pinMode
     296            2 : inline void pinMode(CachedExtIOPin pin, PinMode_t mode) {
     297            2 :     pin.apply(
     298              :         &ExtendedIOElement::pinMode, //
     299            2 :         [](ArduinoPin_t p, PinMode_t m) { ::pinMode(p, m); }, mode);
     300            2 : }
     301              : /// An ExtIO version of the Arduino function
     302              : /// @see    ExtendedIOElement::digitalWrite
     303           50 : inline void digitalWrite(CachedExtIOPin pin, PinStatus_t val) {
     304           50 :     pin.apply(
     305              :         &ExtendedIOElement::digitalWrite, //
     306           82 :         [](ArduinoPin_t p, PinStatus_t v) { ::digitalWrite(p, v); }, val);
     307           50 : }
     308              : /// An ExtIO version of the Arduino function
     309              : /// @see    ExtendedIOElement::digitalRead
     310            2 : inline PinStatus_t digitalRead(CachedExtIOPin pin) {
     311            4 :     return pin.apply(&ExtendedIOElement::digitalRead, //
     312            4 :                      [](ArduinoPin_t p) { return ::digitalRead(p); });
     313              : }
     314              : 
     315              : /// An ExtIO version of the Arduino function
     316              : /// @see    ExtendedIOElement::analogRead
     317            2 : inline analog_t analogRead(CachedExtIOPin pin) {
     318            4 :     return pin.apply(&ExtendedIOElement::analogRead, //
     319            4 :                      [](ArduinoPin_t p) { return ::analogRead(p); });
     320              : }
     321              : /// An ExtIO version of the Arduino function
     322              : /// @see    ExtendedIOElement::analogWrite
     323            3 : inline void analogWrite(CachedExtIOPin pin, analog_t val) {
     324              : #ifndef ESP32
     325            3 :     pin.apply(
     326              :         &ExtendedIOElement::analogWrite, //
     327            3 :         [](ArduinoPin_t p, analog_t v) { ::analogWrite(p, v); }, val);
     328              : #else
     329              :     pin.apply(
     330              :         &ExtendedIOElement::analogWrite, //
     331              :         [](ArduinoPin_t, analog_t) {}, val);
     332              : #endif
     333            3 : }
     334              : /// An ExtIO version of the Arduino function
     335              : /// @see    ExtendedIOElement::analogWrite
     336            2 : inline void analogWrite(CachedExtIOPin pin, int val) {
     337            2 :     return analogWrite(pin, static_cast<analog_t>(val));
     338              : }
     339              : 
     340              : /// An ExtIO version of the Arduino function
     341            6 : inline void shiftOut(CachedExtIOPin dataPin, CachedExtIOPin clockPin,
     342              :                      BitOrder_t bitOrder, uint8_t val) {
     343            6 :     if (dataPin.elementPin == NO_PIN || clockPin.elementPin == NO_PIN)
     344            1 :         return;
     345              :     // Native version
     346            5 :     if (dataPin.element == nullptr && clockPin.element == nullptr) {
     347            1 :         ::shiftOut(arduino_pin_cast(dataPin.elementPin),
     348            1 :                    arduino_pin_cast(clockPin.elementPin), bitOrder, val);
     349              :     }
     350              :     // ExtIO version
     351            4 :     else if (dataPin.element != nullptr && clockPin.element != nullptr) {
     352            2 :         const auto dataEl = dataPin.element;
     353            2 :         const auto dataPinN = dataPin.elementPin;
     354            2 :         const auto clockEl = clockPin.element;
     355            2 :         const auto clockPinN = clockPin.elementPin;
     356           18 :         for (uint8_t i = 0; i < 8; i++) {
     357           16 :             uint8_t mask = bitOrder == LSBFIRST ? (1 << i) : (1 << (7 - i));
     358           16 :             dataEl->digitalWrite(dataPinN, (val & mask) ? HIGH : LOW);
     359           16 :             clockEl->digitalWrite(clockPinN, HIGH);
     360           16 :             clockEl->digitalWrite(clockPinN, LOW);
     361              :         }
     362            2 :     }
     363              :     // Mixed version (slow)
     364              :     else {
     365           18 :         for (uint8_t i = 0; i < 8; i++) {
     366           16 :             uint8_t mask = bitOrder == LSBFIRST ? (1 << i) : (1 << (7 - i));
     367           16 :             digitalWrite(dataPin, (val & mask) ? HIGH : LOW);
     368           16 :             digitalWrite(clockPin, HIGH);
     369           16 :             digitalWrite(clockPin, LOW);
     370              :         }
     371              :     }
     372              : }
     373              : 
     374              : /// A buffered ExtIO version of the Arduino function
     375              : /// @see    ExtendedIOElement::pinModeBuffered
     376              : inline void pinModeBuffered(CachedExtIOPin pin, PinMode_t mode) {
     377              :     pin.apply(
     378              :         &ExtendedIOElement::pinModeBuffered, //
     379              :         [](ArduinoPin_t p, PinMode_t m) { ::pinMode(p, m); }, mode);
     380              : }
     381              : /// A buffered ExtIO version of the Arduino function
     382              : /// @see    ExtendedIOElement::digitalWriteBuffered
     383              : inline void digitalWriteBuffered(CachedExtIOPin pin, PinStatus_t val) {
     384              :     pin.apply(
     385              :         &ExtendedIOElement::digitalWriteBuffered, //
     386              :         [](ArduinoPin_t p, PinStatus_t v) { ::digitalWrite(p, v); }, val);
     387              : }
     388              : /// A buffered ExtIO version of the Arduino function
     389              : /// @see    ExtendedIOElement::digitalReadBuffered
     390              : inline PinStatus_t digitalReadBuffered(CachedExtIOPin pin) {
     391              :     return pin.apply(&ExtendedIOElement::digitalReadBuffered, //
     392              :                      [](ArduinoPin_t p) { return ::digitalRead(p); });
     393              : }
     394              : 
     395              : /// A buffered ExtIO version of the Arduino function
     396              : /// @see    ExtendedIOElement::analogReadBuffered
     397              : inline analog_t analogReadBuffered(CachedExtIOPin pin) {
     398              :     return pin.apply(&ExtendedIOElement::analogReadBuffered, //
     399              :                      [](ArduinoPin_t p) { return ::analogRead(p); });
     400              : }
     401              : /// A buffered ExtIO version of the Arduino function
     402              : /// @see    ExtendedIOElement::analogWriteBuffered
     403              : inline void analogWriteBuffered(CachedExtIOPin pin, analog_t val) {
     404              : #ifndef ESP32
     405              :     pin.apply(
     406              :         &ExtendedIOElement::analogWriteBuffered, //
     407              :         [](ArduinoPin_t p, analog_t v) { ::analogWrite(p, v); }, val);
     408              : #else
     409              :     pin.apply(
     410              :         &ExtendedIOElement::analogWriteBuffered, //
     411              :         [](ArduinoPin_t, analog_t) {}, val);
     412              : #endif
     413              : }
     414              : /// A buffered ExtIO version of the Arduino function
     415              : /// @see    ExtendedIOElement::analogWriteBuffered
     416              : inline void analogWriteBuffered(CachedExtIOPin pin, int val) {
     417              :     return analogWrite(pin, static_cast<analog_t>(val));
     418              : }
     419              : } // namespace ExtIO
     420              : 
     421              : using ExtIO::CachedExtIOPin;
     422              : 
     423              : END_AH_NAMESPACE
        

Generated by: LCOV version 2.4-beta