/*-----------------------------------------------------------------------------
Auteur : Rudy Naulaerts
Datum : 24/04/2025 - 08/05/2025
Cursus : Leren programmeren met Arduino - Les 10 & 11
Project: 429135433020666881
Functie: Slagboom
Tijdens het opstarten van het systeem zal gevraagd worden om een nieuwe
geheime code van 4 cijfers in te geven en dit te bevestigen door op "#"
te drukken. Deze geheime code zal dan gebruikt worden om de slagboom te
kunnen openen.
Om toegang te krijgen tot een domein dient er op een keypad een code van
4 cijfers te worden ingegeven en op "#" te worden gedrukt om deze code te
bevestigen.
Zodra er een cijfer wordt ingedrukt, heeft men gedurende een aantal
seconden de tijd om het volgende cijfer in te drukken. Wordt er tijdens
deze tijdspanne geen cijfer ingedrukt, dan begint men van vooraf aan.
Code is foutief:
Op de lcd display verschijnt een boodschap. Gedurende een ingestelde
tijd zullen de leds op de ring rood knipperen. Na de ingestelde tijd of
als op "C" wordt gedrukt kan de code opnieuw worden ingegeven. Zolang
er geen correcte code is ingegeven, zullen de leds op de ring rood
oplichten.
Code is correct:
Op de lcd display verschijnt er een boodschap en de slagboom wordt
gedurende een bepaalde tijdspanne geopend. Tijdens deze tijdspanne zal
de hendel van de servo zich van horizontale naar vertikale positie
bewegen en zullen de leds van de ring oranje oplichten en links
ronddraaien.
Zodra de slagboom geopend is, zullen de leds op de ring groen oplichten
en kan het domein worden betreden. Op de lcd display verschijnt een
aangepaste boodschap.
Wanneer er geen voorwerp meer wordt gedetecteerd door de afstandssensor
zal de slagboom gedurende een bepaalde tijdspanne worden gesloten.
Tijdens deze tijdspanne spelen zich volgende acties af:
- op de lcd display verschijnt een aangepaste boodschap
- de hendel van de servo beweegt zich van vertikale naar horizontale
positie
- de leds op de ring lichten oranje op en draaien naar rechts
Indien er tijdens het sluiten van de slagboom toch nog een object wordt
gedetecteerd door de afstandssensor, dan zal de slagboom terug geopend
worden vanaf de de positie van de slagboom wanneer de detectie heeft
plaats gevonden.
Zodra de slagboom gesloten is, kan de code opnieuw worden ingegeven.
---------------------------------------------------------------------------*/
// Bibliotheken
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <Keypad.h>
#include <LiquidCrystal_I2C.h>
#include <Servo.h>
// Vergelijkingsoperatoren
#define EQ == // gelijk aan
#define GE >= // groter dan of gelijk aan
#define GT > // groter dan
#define LE <= // kleiner dan of gelijk aan
#define LT < // kleiner dan
#define NE != // niet gelijk aan
// Instellingen componenten
#define KEYPAD_COLS 3 // aantal kolommen op keypad
#define KEYPAD_ROWS 4 // aantal rijen op keypad
#define LCD_COLS 20 // aantal kolommen op lcd display
#define LCD_I2C_ADDR 0x27 // adres lcd display
#define LCD_ROWS 2 // aantal rijen op lcd display
#define RING_BRIGHTNESS 255 // helderheid van de leds op neopixel ring
#define RING_NUM_LEDS 16 // aantal leds op neopixel ring
// Definiëren van de pinnen
#define PIN_ECHO 12 // echo pin van afstandssensor
#define PIN_RING 6 // pin van neopixel ring
#define PIN_SERVO 9 // pin van servo
#define PIN_TRIG 13 // trigger pin van afstandssensor
// Systeem statussen
#define RESET 0
#define READ_KEYPAD 1
#define CODE_CHECK 2
#define CODE_ERROR 3
#define GATE_OPEN 4
#define ENTER_PREMISE 5
#define GATE_CLOSE 6
// Definiëren van andere variabelen
#define LEFT -1 // leds op ring links laten draaien
#define RIGHT 1 // leds op ring rechts laten draaien
#define DEBUG 1 // debug mode: 0 is uit, 1 is aan
#if DEBUG
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#define floatToString(fVal, len, dec, sVal) dtostrf(fVal, len, dec, sVal)
#else
#define debug(x)
#define debugln(x)
#define floatToString(fVal, len, dec, sVal)
#endif
// Lijsten
char keypadKeys[KEYPAD_ROWS][KEYPAD_COLS] = {
{'1', '2', '3'},
{'4', '5', '6'},
{'7', '8', '9'},
{'C', '0', '#'}};
byte keypadRowPins[KEYPAD_ROWS] = {5, 4, 3, 2};
byte keypadColPins[KEYPAD_COLS] = {A3, A2, A1};
String systemStates[] = {
"Reset systeem",
"Lees keypad",
"Check code",
"Foutieve code",
"Open slagboom",
"Betreed domein",
"Sluit slagboom"};
// Variabelen
// maximum waarde systeemstatus
byte systemStateMax = sizeof(systemStates) / sizeof(systemStates[0]) - 1;
byte systemState = RESET; // systeemstatus
byte systemStatePrv = READ_KEYPAD; // vorige systeemstatus
byte lengthCode; // lengte ingegeven code
byte servoPos; // positie hendel van de servo motor
byte cursorPos; // cursor positie op lcd display
byte flashRed, flashGreen, flashBlue; // RGB waarden knipperende leds
int pixel = 0;
String secretCode = "1234"; // geheime code
String enteredCode = ""; // ingegeven code
// tijd in msec om volgend cijfer in te drukken
unsigned long codeInterval = 10000;
unsigned long codeTimer;
// tijd in msec dat foutboodschap op lcd display zichtbaar blijft
unsigned long errorInterval = 3000; // tijd in msec
unsigned long errorTimer;
// tijd in msec om leds op de ring te laten knipperen
unsigned long flashInterval = 200;
unsigned long flashTimer;
// tijd in msec om slagboom te openen en te sluiten
unsigned long gateInterval = 5000;
unsigned long gateTimer;
// tijd in msec om leds op de ring te laten draaien
unsigned long runningInterval = 50;
unsigned long runningTimer;
char message[50];
// Objecten
// instantiëring neopixel ring
Adafruit_NeoPixel ring(RING_NUM_LEDS, PIN_RING, NEO_GRB + NEO_KHZ800);
// instantiëring keypad
Keypad keypad = Keypad(makeKeymap(keypadKeys),
keypadRowPins, keypadColPins,
KEYPAD_ROWS, KEYPAD_COLS);
// instantiëring lcd display
LiquidCrystal_I2C lcd(LCD_I2C_ADDR, LCD_COLS, LCD_ROWS);
// instantiëring servo motor
Servo servo;
// Functies
void setupBegin();
void setupEnd();
void setupPinMode();
void initLcd();
void initRing();
void initServo();
void readKeypad();
void readSensor();
void resetLcdDisplay();
void resetServo();
void checkCode();
void changeState(byte newState, unsigned long timer, unsigned long interval);
void lcdShowMsg(int col0, String text0, int col1, String text1);
void flashingLight(byte &red, byte &green, byte &blue);
void runningLight(int direction, int &pixel);
void setColorRing(byte red, byte green, byte blue);
String enterSecretCode();
void setup()
{
Serial.begin(115200); // Open communicatie naar het scherm
while (!Serial) // Wacht totdat communicatie naar het scherm open is
{
}
setupBegin();
setupPinMode();
setupEnd();
secretCode = enterSecretCode();
}
void loop()
{
// Toon de systeemstatus wanneer deze wijzigt
if (systemStatePrv NE systemState)
{
systemStatePrv = systemState;
if (systemState LE systemStateMax)
{
debug("Systeemstatus: ");
debugln(systemStates[systemState]);
}
}
// Voer acties uit afhankelijk van de systeemstatus
switch (systemState)
{
case RESET: // reset het systeem
resetLcdDisplay();
setColorRing(255, 0, 0);
resetServo();
codeTimer = 0;
enteredCode = "";
systemState = READ_KEYPAD;
break;
case READ_KEYPAD: // lees de ingedrukte toets van het keypad
readKeypad();
break;
case CODE_CHECK: // controleer de ingegeven code op het keypad
checkCode();
break;
case CODE_ERROR: // ingegeven code is foutief
flashingLight(flashRed, flashGreen, flashBlue);
readKeypad();
changeState(RESET, errorTimer, errorInterval);
break;
case GATE_OPEN: // open slagboom (hendel servo vertikaal positioneren)
runningLight(LEFT, pixel);
servoPos = map(millis(), gateTimer, gateTimer + gateInterval, 90, 0);
servo.write(servoPos);
cursorPos = servoPos / 10;
lcd.setCursor(cursorPos, 1);
lcd.print(" ");
changeState(ENTER_PREMISE, gateTimer, gateInterval);
if (systemState EQ ENTER_PREMISE)
{
lcdShowMsg(1, "Gelieve domein", 2, "te betreden");
setColorRing(0, 255, 0);
}
break;
case ENTER_PREMISE: // betreed het domein
readSensor();
if (systemState EQ GATE_CLOSE)
{
lcdShowMsg(0, "Slagboom sluiten", 0, "..........");
gateTimer = millis();
}
break;
case GATE_CLOSE: // sluit slagboom (hendel servo horizontaal positioneren)
runningLight(RIGHT, pixel);
servoPos = map(millis(), gateTimer, gateTimer + gateInterval, 0, 90);
servo.write(servoPos);
cursorPos = 9 - servoPos / 10;
lcd.setCursor(cursorPos, 1);
lcd.print(" ");
readSensor();
changeState(RESET, gateTimer, gateInterval);
break;
default:
sprintf(message, "Foutieve systeemstatus %d", systemState);
debugln(message);
systemState = RESET;
break;
}
}
void setupBegin()
{
debugln("*--- Setup begin ------------------------------------------*");
initLcd();
initRing();
initServo();
}
void setupEnd()
{
debugln("*--- Setup einde ------------------------------------------*");
}
void setupPinMode()
{
debugln("Zet modus van de aangesloten pinnen");
// INPUT pinnen
pinMode(PIN_ECHO, INPUT);
// OUTPUT pinnen
pinMode(PIN_TRIG, OUTPUT);
}
void initLcd()
{
debugln("Initialisering LCD display");
lcd.init();
lcd.backlight();
}
void initRing()
{
debugln("Initialisering ring");
ring.begin();
ring.setBrightness(RING_BRIGHTNESS);
}
void initServo()
{
debugln("Initialisering servo");
servo.attach(PIN_SERVO);
}
void resetLcdDisplay()
{
debugln("Reset LCD display");
lcdShowMsg(0, "Geef code: ____", 0, "");
}
void resetServo()
{
debugln("Reset servo");
servoPos = 90;
servo.write(servoPos);
}
void readKeypad()
{
char key = keypad.getKey();
if (key EQ 'C')
{
systemState = RESET;
return;
}
if (systemState EQ CODE_ERROR)
{
return;
}
switch (key)
{
case NO_KEY:
break;
case '#':
systemState = CODE_CHECK;
break;
default:
codeTimer = millis();
cursorPos = 11;
lengthCode = enteredCode.length();
if (lengthCode GE 4)
{
lengthCode = 0;
enteredCode = "";
lcd.setCursor(cursorPos, 0);
lcd.print("____");
lcd.setCursor(cursorPos, 1);
lcd.print(" ");
}
cursorPos = cursorPos + lengthCode;
enteredCode = enteredCode + key;
lcd.setCursor(cursorPos, 0);
lcd.print("*");
if (DEBUG)
{
lcd.setCursor(cursorPos, 1);
lcd.print(key);
}
break;
}
if (codeTimer NE 0)
{
changeState(RESET, codeTimer, codeInterval);
}
}
void readSensor()
{
digitalWrite(PIN_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(PIN_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
float distance = pulseIn(PIN_ECHO, HIGH) * 0.0344 / 2; // afstand in cm
if (distance GE 300) // geen object gedetecteerd door afstandssensor
{
// De afstandssensor detecteert geen object ==> sluit de slagboom
systemState = GATE_CLOSE;
}
else
{
if (systemState EQ GATE_CLOSE)
{
// De afstandssensor detecteert een object terwijl de slagboom wordt
// gesloten ==> open de slagboom
String dots;
for (int i = 0; i LT 10 - cursorPos; i++)
{
dots += ".";
}
lcdShowMsg(0, "Slagboom openen", 0, dots);
gateTimer = millis() + (millis() - gateTimer) - gateInterval;
systemState = GATE_OPEN;
}
}
}
void checkCode()
{
if (enteredCode EQ secretCode)
{
// de ingegeven code komt overeen met de geheime code ==> open slagboom
lcdShowMsg(0, "Slagboom openen", 0, "..........");
gateTimer = millis();
pixel = 0;
systemState = GATE_OPEN;
}
else
{
// de ingegeven code verschilt van de geheime code ==> toon foutboodschap
lcdShowMsg(1, "Foutieve code", 0, "Probeer opnieuw");
errorTimer = millis();
flashTimer = 0;
flashRed = flashGreen = flashBlue = 0;
systemState = CODE_ERROR;
}
}
void flashingLight(byte &red, byte &green, byte &blue)
{
// Laat de leds op de ring knipperen waarbij de snelheid wordt bepaald door
// een bepaald tijdsinterval
if ((millis() - flashTimer) GE flashInterval)
{
flashTimer = millis();
for (int i = 0; i LT RING_NUM_LEDS; i++)
{
ring.setPixelColor(i, ring.Color(red, blue, green));
}
ring.show();
if ((red EQ 0) && (green EQ 0) && (blue EQ 0))
{
red = 255;
green = blue = 0;
}
else
{
red = green = blue = 0;
}
}
}
void runningLight(int direction, int &pixel)
{
// Laat de leds op de ring links of rechts draaien waarbij de snelheid
// wordt bepaald door een bepaald tijdsinterval
if ((millis() - runningTimer) LT runningInterval)
{
return;
}
for (int i = 0; i LT RING_NUM_LEDS; i++)
{
if (i NE pixel)
{
ring.setPixelColor(i, ring.Color(255, 165, 0));
}
else
{
ring.setPixelColor(i, ring.Color(0, 0, 0));
}
}
ring.show();
switch (direction)
{
case LEFT:
if (pixel EQ 0)
{
pixel = 16;
}
break;
case RIGHT:
if (pixel EQ 15)
{
pixel = -1;
}
break;
}
pixel = pixel + direction;
runningTimer = millis();
}
void setColorRing(byte red, byte green, byte blue)
{
sprintf(message, "Zet kleur ring (%d, %d, %d)", red, green, blue);
debugln(message);
for (int pixel = 0; pixel LT RING_NUM_LEDS; pixel++)
{
ring.setPixelColor(pixel, ring.Color(red, green, blue));
}
ring.show();
}
void lcdShowMsg(int col0, String text0, int col1, String text1)
{
// Toon een boodschap op de lcd display
lcd.clear();
lcd.setCursor(col0, 0);
lcd.print(text0);
lcd.setCursor(col1, 1);
lcd.print(text1);
}
void changeState(byte newState, unsigned long timer, unsigned long interval)
{
// Verander de systeemstatus nadat een bepaald tijdsinterval is versteken
if ((millis() - timer) GE interval)
{
systemState = newState;
}
}
String enterSecretCode()
{
debugln("Nieuwe geheime code ingeven");
bool codeOk = false;
byte startPos = 6;
enteredCode = "";
lcdShowMsg(0, "Geef geheime", 0, "code: ____");
do
{
lengthCode = enteredCode.length();
cursorPos = startPos + lengthCode;
char key = keypad.getKey();
switch (key)
{
case NO_KEY:
// Er is geen toets op keypad ingedrukt
break;
case 'C':
// Herbegin met ingegeven van de code
enteredCode = "";
lcd.setCursor(startPos, 1);
lcd.print("____");
break;
case '#':
// Indien de ingegeven code 4 cijfers lang is, accepteer dan deze code
if (lengthCode EQ 4)
{
codeOk = true;
}
break;
default:
if (lengthCode EQ 4)
// Indien de lengte van de ingegeven code reeds 4 lang is, herbegin dan
// met het ingeven van de code
{
lengthCode = 0;
enteredCode = "";
lcd.setCursor(startPos, 1);
lcd.print("____");
}
// Voeg de waarde van de ingedrukt toets toe aan de code en toon deze op
// de lcd display
enteredCode = enteredCode + key;
cursorPos = startPos + lengthCode;
lcd.setCursor(cursorPos, 1);
lcd.print(key);
break;
}
} while (!codeOk);
debug("Geheime code: ");
debugln(enteredCode);
return enteredCode;
}COLS
ROWS