SimpleCharliePlexer.hpp
/**
* SimpleCharliePlexer.hpp
*
* Klasse SimpleCharliePlexer - Zeigt das Prinzip von CharliePlexing.
*
* $Id: SimpleCharliePlexer.hpp 15125 2022-04-10 08:05:56Z martin $
*
* (C) 2022 by Martin Pischky (martin@pischky.de)
*
*/
#ifndef SIMPLE_CHARLIE_PLEXER_HPP_
#define SIMPLE_CHARLIE_PLEXER_HPP_
#include <cstdint> // uint8_t, uint16_t
#include <bitset> // std::bitset
using std::uint8_t;
using std::bitset;
/**
* Abstrakte Klasse mit dem Algorithmus für das Charlieplexen.
*
* Es werden die Anzahl der benutzen Pins (N) und die Anzahl der tatsächlichen
* benutzen LED-Positionen (M) angegeben.
*
* Die LEDs werden über "setStatus" gesteuert. Eine LED die leuchten
* soll muss im Parameter gesetzt sein.
*
* Die Methode "cycle" ist im Hauptprogramm (loop) ohne weitere Verzögerungen
* zyklisch aufzurufen.
*
* Wie funktioniert das:
*
* Mit N Port-Pins kann man N*(N-1) LEDs ansteuern. Dabei
* sind immer zwei Ports mit jeweils L und H anzusteuern so das jeweils nur eine
* LED leuchtet. Es sind alle Kombinationen zu durchlaufen, nur müssen die
* Ports paarweise verschieden sein. Das ist hier als zwei geschachtelten
* FOR-Schleifen implementiert. Bei jedem Durchlauf wird in "status" geschaut ob
* die LED einzuschalten wird oder dunkel bleibt. Dann wird dieser Zustand
* einige Zeit angezeigt.
*
* Sind weniger als N*(N-1) anzusteuern können wir die Scheifen auch vorzeitig
* abbrechen. Damit sparen wir etwas Zeit und können den Strom durch die LDEs
* etwas kleiner wählen.
*
* Das ganze ist insofern dumm, das durch die delayMicros-Routine alle
* Rechenzeit verbraucht wird. Irgendwer muss ja auch noch auf DCC oder
* LocoNet-Meldungen regieren und "status" ändern. Sonst ist das angezeigte
* Signalbild ziemlich langweilig.
*
* In der Praxis wird man also die Schleifen aufbrechen müssen und jeweils
* nur einen inneren Durchlauf ausführen. Dazu muss man i,j und k global
* speichern. Dann kann man die Routine zeitgesteuert regelmäßig aufrufen.
* Hierfür bietet sich der Timer-Interrupt an.
*/
template <uint8_t N, uint8_t M>
class SimpleCharliePlexer { // @suppress("Class has a virtual method and non-virtual destructor")
static_assert(M <= N*(N-1), "Number of controlled LEDs to high");
public:
using LedStatus = bitset<M>;
static constexpr uint8_t NUM_PINS = N;
static constexpr uint8_t NUM_LEDS = M;
void setStatus(const LedStatus& s) {
status = s;
}
const LedStatus& getStatus() const {
return status;
}
void cycle() {
uint8_t k = 0;
for (uint8_t i = 0; i < N; ++i) {
for (uint8_t j = 0; j < N; ++j) {
if (i == j) continue;
this->setAllTristate();
if (status[k]) {
this->setHigh(i);
this->setLow(j);
}
this->delayMicros(10000/M); // Frequenz: 100Hz
k++;
if (k == M) goto exit_outer_loop;
}
}
exit_outer_loop:
this->setAllTristate();
}
/** Alle LEDs abschalten */
void setAllTristate() {
for (uint8_t i = 0; i < N; ++i) this->setTristate(i);
}
private:
LedStatus status = {0};
protected:
/** Port auf TRISTATE schalten */
virtual void setTristate(uint8_t portNr) = 0;
/** Port auf HIGH schalten */
virtual void setHigh(uint8_t portNr) = 0;
/** Port auf LOW schalten */
virtual void setLow(uint8_t portNr) = 0;
/** Verzögerung der Programmausführung um angegebene Microsekunden*/
virtual void delayMicros(uint16_t us) = 0;
// eclipse CodAn meckert hier einen fehlenen Destruktor an:
//~SimpleCharliePlexer() {};
};
#endif // SIMPLE_CHARLIE_PLEXER_HPP_