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()
|