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
|