//
// Programmbeispiel mit Speicherobjekt
//
#include "type_traits.h"
#include <Streaming.h>
Print &cout {Serial};
constexpr uint8_t SELECT_PINS[] {2, 3, 4};
constexpr uint8_t ANALOG_PINS[] {A0, A1, A2};
constexpr uint8_t NUM_CHANNELS_TOTAL {22};
constexpr uint8_t NUM_CANNELS_PER_DEVICE {8};
constexpr uint8_t NUM_DEVICES {sizeof(ANALOG_PINS) / sizeof(ANALOG_PINS[0])};
//
// Klasse zum Speichern von Daten, die über einen oder mehrere Multiplexer ausgelesen werden
//
template <typename T, size_t SIZE> class SegmentedArray {
static_assert(is_signed<T>(), "signed integer expected!");
public:
SegmentedArray(const size_t row, const size_t colsPerRow) : row {row}, colsPerRow {colsPerRow} {}
// Indexoperator für veränderliche Vektoren. Eindimensionaler Zugriff
T &operator[](size_t index) {
return (index < SIZE) ? pStore[index] : error = -1; // -1 wenn falscher Kanal (Speicherüberlauf abfangen)
}
// Indexoperator für NICHT veränderliche Vektoren. Eindimensionaler Zugriff
const T &operator[](size_t index) const { return (index < SIZE) ? pStore[index] : error = -1; }
// Zweidimensionaler Zugriff (veränderlich)
T &operator()(const size_t row, const size_t column) {
size_t index = colsPerRow * row + column;
return (index < SIZE) ? pStore[index] : error = -1; // -1 wenn falscher Kanal (Speicherüberlauf abfangen)
}
// Zweidimensionaler Zugriff (const nicht veränderlich)
const T &operator()(const size_t row, const size_t column) const {
size_t index = colsPerRow * row + column;
return (index < SIZE) ? pStore[index] : error = -1; // -1 wenn falscher Kanal (Speicherüberlauf abfangen)
}
void clearAll() { memset(pStore, 0, bytes); }
public:
const size_t size {SIZE}; // Größe des Arrays
const size_t bytes {sizeof(T) * SIZE}; // Größe in Bytes
private:
const size_t row;
const size_t colsPerRow;
T error {-1};
T pStore[SIZE] {};
};
SegmentedArray<int, NUM_CHANNELS_TOTAL> dataStore(NUM_DEVICES, NUM_CANNELS_PER_DEVICE); // Speicherinstanz erzeugen
//////////////////////////////////////////////////////////////////////////////
/// @brief Setzen der Steuerpins für die Kanalauswahl eines Multiplexer ICs
///
/// @param pins[N] Array mit den Select Pins zum Einstellen des Auslesekanals. N wird automatich ermittelt
/// @param channel Kanalnummer die eingestellt werden soll
//////////////////////////////////////////////////////////////////////////////
template <size_t N> void setChannel(const uint8_t (&pins)[N], uint8_t channel) {
for (size_t i = 0; i < N; ++i) { digitalWrite(pins[i], bitRead(channel, i)); }
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Auslesen eines analogen Signals aus einem gegebenen Kanal eines Multiplexer ICs
/// Sind mehrere Ics vorhanden, deren Kanal über die gleichen Steuerpins parallel geschaltet
/// wird, so wird er für jeden IC sukzessive ausgelesen.
/// Der ausgelesene Wert wird in einem in einem eindimensionalen Array gespeichert.
/// Bei mehreren ICs ist die Speicherposition (index) im Array = Ports pro Device * IC-Nummer + Kanalnummer
/// Beispiel Multiplexer mit 8 Ports: Kanal 5 des ersten ICs = 8 * 0 + 5 = 5
/// Kanal 5 des dritten ICs = 8 * 2 + 5 = 21
///
/// @tparam analogPins Array analoge Pinnummern (Elementanzahl N wird beim compilieren ermittelt)
/// @param storage Speicherobjekt für die ausgelesenen Daten
/// @param channel Kanal der ausgelesen werden soll
//////////////////////////////////////////////////////////////////////////////
template <size_t N, typename T, size_t SIZE>
void readData(const uint8_t (&analogPins)[N], SegmentedArray<T, SIZE> &storage, uint8_t channel) {
uint8_t numDevice {0};
for (auto pin : analogPins) {
storage(numDevice, channel) = analogRead(pin);
++numDevice;
}
}
void setup() {
Serial.begin(115200);
for (auto pin : SELECT_PINS) { pinMode(pin, OUTPUT); } // Selectpins auf OUTPUT konfigurieren
cout << "Start...\n" << "Arrayelemente: " << dataStore.size << " Bytes: " << dataStore.bytes << endl;
delay(5000);
}
void loop() {
// Die CD4051 Ports auslesen und in einem Array speichern
for (size_t channel = 0; channel < NUM_CANNELS_PER_DEVICE; ++channel) {
setChannel(SELECT_PINS, channel);
readData(ANALOG_PINS, dataStore, channel);
}
// Gespeicherte Daten auf serieller Konsole ausgeben (eindimensionaler Zugriff)
uint8_t devNr {0};
for (size_t i = 0; i < NUM_CHANNELS_TOTAL + 2; ++i) {
if (!(i % 8)) {
delay(500);
++devNr;
cout << "\nDevice Nr.: " << devNr << " Data: ";
}
cout << _WIDTH(dataStore[i], 4) << " ";
}
cout << endl;
delay(5000);
}
//
// Programmbeispiel ohne Speicherobjekt
//
/*
#include <Streaming.h>
Print &cout {Serial};
constexpr uint8_t SELECT_PINS[] {2, 3, 4};
constexpr uint8_t ANALOG_PINS[] {A0, A1, A2};
constexpr uint8_t NUM_PORTS_TOTAL {22};
constexpr uint8_t NUM_PORTS_DEVICE {8};
constexpr uint8_t NUM_DEVICES {sizeof(ANALOG_PINS) / sizeof(ANALOG_PINS[0])};
// constexpr uint8_t NUM_DEVICES {NUM_PORTS_TOTAL / NUM_PORTS_DEVICE + (NUM_PORTS_TOTAL % NUM_PORTS_DEVICE > 0 ? 1 : 0)};
int dataStore[NUM_PORTS_TOTAL];
//////////////////////////////////////////////////////////////////////////////
/// @brief Setzen der Steuerpins für die Kanalauswahl eines Multiplexer ICs
///
/// @tparam pins[N] Array mit den Select Pins zum Einstellen des Auslesekanals. N wird automatich ermittelt
/// @param channel Kanalnummer die eingestellt werden soll
//////////////////////////////////////////////////////////////////////////////
template <size_t N> void setChannel(const uint8_t (&pins)[N], uint8_t channel) {
for (size_t i = 0; i < N; ++i) { digitalWrite(pins[i], bitRead(channel, i)); }
}
//////////////////////////////////////////////////////////////////////////////
/// @brief Auslesen eines analogen Signals aus einem gegebenen Kanal eines Multiplexer ICs
/// Sind mehrere Ics vorhanden, deren Kanal über die gleichen Steuerpins parallel geschaltet
/// wird, so wird er für jeden IC sukzessive ausgelesen.
/// Der ausgelesene Wert wird in einem eindimensionalen Array gespeichert.
/// Bei mehreren ICs ist die Speicherposition (index) im Array = Ports pro Device * IC-Nummer + Kanalnummer
/// Beispiel Multiplexer mit 8 Ports: Kanal 5 des ersten ICs = 8 * 0 + 5 = 5
/// Kanal 5 des dritten ICs = 8 * 2 + 5 = 21
///
/// @tparam analogPins Array analoge Pinnummern (Elementanzahl N wird beim compilieren ermittelt)
/// @tparam storage Speicherarray (Typ und Anzahl Elemente M werden beim compilieren ermittelt)
/// @param channel Kanal der ausgelesen werden soll
/// @param numPortsDevice Anzahl ansteuerbare Kanäle pro IC
//////////////////////////////////////////////////////////////////////////////
template <size_t N, typename T, size_t M>
void readData(const uint8_t (&analogPins)[N], const T (&storage)[M], size_t channel, const uint8_t numPortsDevice) {
uint8_t numDevice {0};
for (auto pin : analogPins) {
uint8_t storeIndex = numPortsDevice * numDevice + channel;
if (storeIndex < M) { dataStore[storeIndex] = analogRead(pin); }
++numDevice;
}
}
void setup() {
Serial.begin(115200);
for (auto pin : SELECT_PINS) { pinMode(pin, OUTPUT); } // Selectpins auf OUTPUT konfigurieren
}
void loop() {
// Die CD4051 Ports auslesen und in einem Array speichern
for (size_t channel = 0; channel < NUM_PORTS_DEVICE; ++channel) {
setChannel(SELECT_PINS, channel);
readData(ANALOG_PINS, dataStore, channel, NUM_PORTS_DEVICE);
}
// Gespeicherte Daten auf serieller Konsole ausgeben
uint8_t devNum {0};
for (size_t i = 0; i < (sizeof(dataStore) / sizeof(dataStore[0])); ++i) {
if (!(i % 8)) { ++devNum; }
cout << "Mux Nr.: " << devNum << " Speicher Position: " << _WIDTH(i, 2) << " Wert: " << _WIDTH(dataStore[i], 4)
<< endl;
delay(500); // Nur zur Verlangsamung der Ausgabe. Im Echtbetrieb muss das weg!
}
delay(5000);
}
*/
Mux Nr. 1
Mux Nr. 2
Mux Nr. 3