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

Generated by: LCOV version 1.15