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
|