LCOV - code coverage report
Current view: top level - src/AH/Hardware/ExtendedInputOutput - ExtendedIOElement.hpp (source / functions) Hit Total Coverage
Test: ffed98f648fe78e7aa7bdd228474317d40dadbec Lines: 66 68 97.1 %
Date: 2022-05-28 15:22:59 Functions: 14 18 77.8 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.15