Om het allemaal netjes te houden (en voor jou waarschijnlijk gecompliceerder
), gaan we arrays van structs gebruiken.
Stel je een ladenkast voor. Iedere lade heeft een nummer (de Arduino pin) op de voorkant en heeft een aantal rijen (van voor naar achter) met vakjes (van links naar rechts).
Er zijn twee vakjes in iedere rij, de tijd dat een LED aan moet en de tijd dat dezelfde LED uit moet. Je kunt een rij nu 'vertalen' naar een struct.
struct TIMING
{
uint32_t timeOn;
uint32_t timeOff;
};
En je kunt nu een array maken die alle aan/uit tijden voor een (1) LED bevat; dat zijn dus een aantal rijen in een lade). Onderstaand geeft aan dat een LED om 5 in de morgen wordt ingeschakeld, uitgeschakeld om 8 in de morgen, ingeschakeld om 6 in de avond en uitgeschakeld om 8 in de avond.
TIMING t[] = {
{(5 * oneHour), (8 * oneHour)},
{(18 * oneHour), (20 * oneHour)}
};
Nu hebben we dus de inhoud van een lade met rijen van aan/uit tijden.
In de volgende stap voegen we het nummer van de pin toe met behulp van een andere struct
struct LIGHTS
{
const uint8_t pin;
const TIMING t[4];
};
Voor deze opzet is het aantal aan/uit tijden beperkt tot 4; je kunt het uitbreiden naar behoefte. Er is een betere oplossing als het aantal tijden niet hetzelfde is voor de verschillende LEDs.
Je kunt nu een array van LIGHTS structs maken waar.
LIGHTS lightTiming[] = {
{8, {{(1 * oneHour), (2 * oneHour)}, {(3 * oneHour), (4 * oneHour)}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}}},
{9, {{(1 * oneHour), (3 * oneHour)}, {(5 * oneHour) + (7 * oneMinute), (20 * oneHour)}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}}},
{10, {{(23 * oneHour), (1 * oneHour)}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}}},
};
Dit toont een voorbeeld voor 3 LEDs. Het eerste nummer is de pin (8/9/10), wat daarop volgt zijn de aan/uit tijden. In het voorbeeld ben je beperkt to 4 tijd blokken (zoals gezegd kun je dit uitbreiden). Aan/uit tijden die je niet gebruikt zijn ingevuld met {0xFFFFFFFF, 0xFFFFFFFF}
; dit kunnen we later gebruiken om deze over te slaan.
Je kunt extra rijen elementen toevoegen aan de lightTiming array; simpel kopieren en plakken en de niewe regel aanpassen.
Onderstaand (onderdeel van de) setup() functie laat zien hoe je door dit alless heen kunt gaan. Het gebruikt twee tellers, een (1) voor de lightTiming array (ltCnt) en een (1) voor de timing array (tCnt) in de lightTiming.
Serial.begin(115200);
// toon de configuratie
for (uint8_t ltCnt = 0; ltCnt < NUMELEMENTS(lightTiming); ltCnt++)
{
Serial.print(F("Lade "));
Serial.println(ltCnt);
Serial.print(F("\tPin "));
Serial.println(lightTiming[ltCnt].pin);
Serial.println(F("\tTijden"));
for (uint8_t tCnt = 0; tCnt < NUMELEMENTS(lightTiming[ltCnt].t); tCnt++)
{
if (lightTiming[ltCnt].t[tCnt].timeOn == 0xFFFFFFFF)
{
Serial.println(F("\t\tGeen tijden"));
}
else
{
Serial.print(F("\t\tAan "));
Serial.print(lightTiming[ltCnt].t[tCnt].timeOn);
Serial.print(F("\tUit "));
Serial.println(lightTiming[ltCnt].t[tCnt].timeOff);
}
}
}
// zet pinnen als uitgang
for (uint32_t ltCnt = 0; ltCnt < NUMELEMENTS(lightTiming); ltCnt++)
{
pinMode(lightTiming[ltCnt].pin, OUTPUT);
digitalWrite(lightTiming[ltCnt].pin, LOW);
// debugging
Serial.print(F("Pin "));
Serial.print(lightTiming[ltCnt].pin);
Serial.print(F(" output"));
}
Met een punt kun je de velden in een struct aanspreken. Bv lightTiming[ltCnt].t[tCnt].timeOn
.
lightTiming[ltCnt]
is een element in het lightTiming array.
t
is het veld t (hetgeen een array is) en t[tCnt]
is een element in dat array.
timeOn
is het veld in t[tCnt]
.
NUMELEMENTS is een macro die het aantal elementen in een array berekent; het maakt niet uit wat voor type array het is (character, int, float, struct, class, ...). Plaats het volgende boven aan je programma.
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))
Ik heb midnight veranderd van een lokale statische variabele naar een globale variable (gedeclareerd voor setup()) en die nu in setup() de waarde van millis() gegeven. De code ziet er nu zo uit
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))
// model tijden
const uint32_t oneMinute = 1000UL;
const uint32_t oneHour = 60000UL;
const uint32_t oneDay = 1440000UL;
struct TIMING
{
const uint32_t timeOn;
const uint32_t timeOff;
};
struct LIGHTS
{
const uint8_t pin;
const TIMING t[4];
};
LIGHTS lightTiming[] = {
{8, {{(1 * oneHour), (2 * oneHour)}, {(3 * oneHour), (4 * oneHour)}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}}},
{9, {{(1 * oneHour), (3 * oneHour)}, {(5 * oneHour) + (7 * oneMinute), (20 * oneHour)}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}}},
{10, {{(23 * oneHour), (1 * oneHour)}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}}},
};
// middernacht
uint32_t midnight;
void setup()
{
Serial.begin(115200);
// toon de configuratie
for (uint8_t ltCnt = 0; ltCnt < NUMELEMENTS(lightTiming); ltCnt++)
{
Serial.print(F("Lade "));
Serial.println(ltCnt);
Serial.print(F("\tPin "));
Serial.println(lightTiming[ltCnt].pin);
Serial.println(F("\tTijden"));
for (uint8_t tCnt = 0; tCnt < NUMELEMENTS(lightTiming[ltCnt].t); tCnt++)
{
if (lightTiming[ltCnt].t[tCnt].timeOn == 0xFFFFFFFF)
{
Serial.println(F("\t\tGeen tijden"));
}
else
{
Serial.print(F("\t\tAan "));
Serial.print(lightTiming[ltCnt].t[tCnt].timeOn);
Serial.print(F("\tUit "));
Serial.println(lightTiming[ltCnt].t[tCnt].timeOff);
}
}
}
// zet pinnen als uitgang
for (uint32_t ltCnt = 0; ltCnt < NUMELEMENTS(lightTiming); ltCnt++)
{
pinMode(lightTiming[ltCnt].pin, OUTPUT);
digitalWrite(lightTiming[ltCnt].pin, LOW);
// debugging
Serial.print(F("Pin "));
Serial.print(lightTiming[ltCnt].pin);
Serial.println(F(" output"));
}
// middernacht
midnight = millis();
}
void loop()
{
// test voor nieuwe dag
if (millis() - midnight >= oneDay)
{
midnight = millis();
Serial.println(F("Een dag voorbij"));
}
}
Dit doet nog niet wat je wilt. Daarvoor gaan we een functie schrijven die door alle laden en alle rijen in een lade loopt.
void lightControl()
{
uint32_t timeAfterMidnight = millis() - midnight;
// loop door alle laden (LEDs)
for (uint8_t ltCnt = 0; ltCnt < NUMELEMENTS(lightTiming); ltCnt++)
{
// bijhouden of LED aan of uit moet; default uit
bool lightOn = false;
// loop door alle lightTimings rijen in een lade
for (uint8_t tCnt = 0; tCnt < NUMELEMENTS(lightTiming[ltCnt].t); tCnt++)
{
}
}
}
Het gebruik van een for-loop is hier acceptabel omdat er geen delays gebruikt gaan worden die kunnen resulteren in lange vertragingen.
In de binnenste for-loop gaan we de tijden controleren om te zien of voor een bepaalde LED timeAfterMidnight in een gegeven aan/uit blok valt. Indien ja, dan wordt een variable gezet om aan te geven dat de LED aan moet. Er zijn drie situaties
- Een tijd blok is leeg (0xFFFFFFFF).
- En tijd blok valt in een dag.
- Een tijd blok begint voor middernacht maar eindigt na middernacht.
// loop door alle lightTimings rijen in een lade
for (uint8_t tCnt = 0; tCnt < NUMELEMENTS(lightTiming[ltCnt].t); tCnt++)
{
// overslaan indien niet gespecificeerd
if (lightTiming[ltCnt].t[tCnt].timeOn == 0xFFFFFFFF)
{
// volgende lightTiming rij in de lade
continue;
}
// controleren of de huidige tijd in een gespecificeerde aan/uit tijd valt
// aan tijd kleiner dan uit tijd
if (lightTiming[ltCnt].t[tCnt].timeOn < lightTiming[ltCnt].t[tCnt].timeOff)
{
}
// (2) aan tijd groter dan uit tijd; bv 23 tot 1
else
{
}
}
Voor (2) hierboven gebruiken we
if (timeAfterMidnight >= lightTiming[ltCnt].t[tCnt].timeOn && timeAfterMidnight < lightTiming[ltCnt].t[tCnt].timeOff)
{
// debugging
if (digitalRead(lightTiming[ltCnt].pin) == LOW)
{
Serial.print(F("Pin "));
Serial.print(lightTiming[ltCnt].pin);
Serial.print(F(" aan tussen "));
Serial.print(lightTiming[ltCnt].t[tCnt].timeOn);
Serial.print(F(" en "));
Serial.println(lightTiming[ltCnt].t[tCnt].timeOff);
}
// aangeven dat LED moet worden ingeschakeld
lightOn = true;
}
Om te voorkomen dat de seriele monitor als een idioot gaat scrollen wordt er alleen geprint als er een verandering in de LED zal gaan optreden (van uit naar aan).
De code voor (3) hierboven is practisch hetzelfde; ik laat het aan jou over om het verschil te vinden en te begrijpen in de volledige sketch die later volgt
Zodra we door alle rijen zijn gelopen in een lade weten we of een LED aan of uit moet zijn. We kunnen dat nu gebruiken om te schakelen
// indien de LED aan moet zijn
if (lightOn == true)
{
// schakel LED aan
digitalWrite(lightTiming[ltCnt].pin, HIGH);
// geen debugging
}
else
{
// debugging
if (digitalRead(lightTiming[ltCnt].pin) == HIGH)
{
Serial.print(F("Pin "));
Serial.print(lightTiming[ltCnt].pin);
Serial.println(F(" uit "));
}
// schakel LED uit
digitalWrite(lightTiming[ltCnt].pin, LOW);
}
Deze niewe functie moet je nu vanuit loop() aanroepen
void loop()
{
// test voor nieuwe dag
if (millis() - midnight >= oneDay)
{
midnight = millis();
Serial.println(F("Een dag voorbij"));
}
// bestuur de LEDs
lightControl();
}
Onderstaand de volledige sketch. Ik heb het getest met 2.4 minuten voor een dag (in plaats van 24 minuten) en dat lijkt goed te gaan.
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))
// model tijden
const uint32_t oneMinute = 1000UL;
const uint32_t oneHour = 60000UL;
const uint32_t oneDay = 1440000UL;
struct TIMING
{
const uint32_t timeOn;
const uint32_t timeOff;
};
struct LIGHTS
{
const uint8_t pin;
const TIMING t[4];
};
LIGHTS lightTiming[] = {
{8, {{(1 * oneHour), (2 * oneHour)}, {(3 * oneHour), (4 * oneHour)}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}}},
{9, {{(1 * oneHour), (3 * oneHour)}, {(5 * oneHour) + (7 * oneMinute), (20 * oneHour)}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}}},
{10, {{(23 * oneHour), (1 * oneHour)}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}, {0xFFFFFFFF, 0xFFFFFFFF}}},
};
// middernacht
uint32_t midnight;
void setup()
{
Serial.begin(115200);
// toon de configuratie
for (uint8_t ltCnt = 0; ltCnt < NUMELEMENTS(lightTiming); ltCnt++)
{
Serial.print(F("Lade "));
Serial.println(ltCnt);
Serial.print(F("\tPin "));
Serial.println(lightTiming[ltCnt].pin);
Serial.println(F("\tTijden"));
for (uint8_t tCnt = 0; tCnt < NUMELEMENTS(lightTiming[ltCnt].t); tCnt++)
{
if (lightTiming[ltCnt].t[tCnt].timeOn == 0xFFFFFFFF)
{
Serial.println(F("\t\tGeen tijden"));
}
else
{
Serial.print(F("\t\tAan "));
Serial.print(lightTiming[ltCnt].t[tCnt].timeOn);
Serial.print(F("\tUit "));
Serial.println(lightTiming[ltCnt].t[tCnt].timeOff);
}
}
}
// zet pinnen als uitgang
for (uint32_t ltCnt = 0; ltCnt < NUMELEMENTS(lightTiming); ltCnt++)
{
pinMode(lightTiming[ltCnt].pin, OUTPUT);
digitalWrite(lightTiming[ltCnt].pin, LOW);
// debugging
Serial.print(F("Pin "));
Serial.print(lightTiming[ltCnt].pin);
Serial.println(F(" output"));
}
// middernacht
midnight = millis();
}
void loop()
{
// test voor nieuwe dag
if (millis() - midnight >= oneDay)
{
midnight = millis();
Serial.println(F("Een dag voorbij"));
}
lightControl();
}
void lightControl()
{
uint32_t timeAfterMidnight = millis() - midnight;
// loop door alle laden (LEDs)
for (uint8_t ltCnt = 0; ltCnt < NUMELEMENTS(lightTiming); ltCnt++)
{
// bijhouden of LED aan of uit moet; default uit
bool lightOn = false;
// loop door alle lightTimings rijen in een lade
for (uint8_t tCnt = 0; tCnt < NUMELEMENTS(lightTiming[ltCnt].t); tCnt++)
{
// overslaan indien niet gespecificeerd
if (lightTiming[ltCnt].t[tCnt].timeOn == 0xFFFFFFFF)
{
// volgende lightTiming rij in de lade
continue;
}
// controleren of de huidige tijd in een gespecificeerde aan/uit tijd valt
// aan tijd kleiner dan uit tijd
if (lightTiming[ltCnt].t[tCnt].timeOn < lightTiming[ltCnt].t[tCnt].timeOff)
{
if (timeAfterMidnight >= lightTiming[ltCnt].t[tCnt].timeOn && timeAfterMidnight < lightTiming[ltCnt].t[tCnt].timeOff)
{
// debugging
if (digitalRead(lightTiming[ltCnt].pin) == LOW)
{
Serial.print(F("Pin "));
Serial.print(lightTiming[ltCnt].pin);
Serial.print(F(" aan tussen "));
Serial.print(lightTiming[ltCnt].t[tCnt].timeOn);
Serial.print(F(" en "));
Serial.println(lightTiming[ltCnt].t[tCnt].timeOff);
}
// aangeven dat LED moet worden ingeschakeld
lightOn = true;
}
}
// (2) aan tijd groter dan uit tijd; bv 23 tot 1
else
{
if (timeAfterMidnight >= lightTiming[ltCnt].t[tCnt].timeOn || timeAfterMidnight < lightTiming[ltCnt].t[tCnt].timeOff)
{
if (digitalRead(lightTiming[ltCnt].pin) == LOW)
{
// debugging
Serial.print(F("Pin "));
Serial.print(lightTiming[ltCnt].pin);
Serial.print(F(" aan tussen "));
Serial.print(lightTiming[ltCnt].t[tCnt].timeOn);
Serial.print(F(" en "));
Serial.println(lightTiming[ltCnt].t[tCnt].timeOff);
}
// aangeven dat LED moet worden ingeschakeld
lightOn = true;
}
}
}
// indien de LED aan moet zijn
if (lightOn == true)
{
// schakel LED aan
digitalWrite(lightTiming[ltCnt].pin, HIGH);
}
else
{
// debugging
if (digitalRead(lightTiming[ltCnt].pin) == HIGH)
{
Serial.print(F("Pin "));
Serial.print(lightTiming[ltCnt].pin);
Serial.println(F(" uit "));
}
// schakel LED uit
digitalWrite(lightTiming[ltCnt].pin, LOW);
}
}
}
Ik hoop dat deze uitleg duidelijk is. Als je vragen hebt, laat maar horen.