LCOV - code coverage report
Current view: top level - src/AH/Hardware/ExtendedInputOutput - ExtendedIOElement.hpp (source / functions) Hit Total Coverage
Test: b8a30b4b7040ae1abf162fd0a258beaa2de43626 Lines: 67 69 97.1 %
Date: 2024-12-21 21:28:55 Functions: 14 18 77.8 %
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           4 :     pin.apply(
     298             :         &ExtendedIOElement::pinMode, //
     299           0 :         [](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         100 :     pin.apply(
     305             :         &ExtendedIOElement::digitalWrite, //
     306          32 :         [](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           6 :     pin.apply(
     326             :         &ExtendedIOElement::analogWrite, //
     327           0 :         [](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 1.15