// Longueur du code secret. La fonction sizeof() retourne le nombre d'octets du code
#define SECRET_LEN (sizeof(SECRET))
// Temps maximal en millisecondes alloué entre 2 activations des interrupteurs.
#define TIMEOUT_MS 5000
// Temps alloué à l'interrupteur en microsecondes pour se stabiliser afin
// d'éliminer les rebonds.
#define DEBOUNCE_US 50000UL
// Temps pendant lequel le système n'est pas disponible lorsqu'un mauvais code est entré.
#define LOCKOUT_MS 3000
// DEL OK
#define DEL_OK_PIN PB5 // D13
// DEL Erreur
#define DEL_ERR_PIN PB0 // D8
//Variable globales
// Tableau de caractères contenant le code à utiliser pour débarrer la serrure
unsigned char SECRET[] = { 0, 1, 1, 0 };
// Tableau contenant les évènements à traiter
volatile unsigned char event_queue[8] = { 2, 2, 2, 2, 2, 2, 2, 2 };
// ev_head: Caractère indiquant la position dans le tableau du prochain évènement à insérer.
// ev_tail: Caractère indiquant la position dans le tableau du prochain évènement à traiter.
volatile unsigned char ev_head = 0, ev_tail = 0;
// evenement: Caractère contenant du prochain évènement à traiter, tel que récupéré par la
// fonction ev_pop().
unsigned char evenement = 2;
/* ev_push
Description : Cette fonction insère le numéro de l'interrupteur actif
définit par le paramètre "v" dans la table des évènements
à traiter (event_queue).
Paramètre :
v : Définit le numéro de l'interrupteur actif. 1 ou 0 dans ce programme.
Valeur retournée : true si tout s'est déroulé correctement.
false si une erreur est survenue.
*/
bool ev_push(unsigned char v) {
// Calculer le prochain index à utiliser dans la table circulaire
unsigned char n = (ev_head + 1) & 0x07;
// Si la table est plaine, retourner faux pour indiquer l'erreur
if (n == ev_tail) return false;
// Sauver le numéro de l'interrupteur reçu via le paramètre "v" dans la table
event_queue[ev_head] = v;
// Sauver la nouvelle position d'insertion dans la table circulaire dans la variable "ev_head"
ev_head = n;
return true;
}
/* ev_pop
Description : Cette fonction récupère le numéro du prochain interrupteur à traiter
(le plus ancient dans la table des évènements, FIFO ou First In First Out)
et initialise la variable globale "evenement" avec cette valeur.
Paramètre :
Aucun
Valeur retournée : true si tout s'est déroulé correctement.
false si une erreur est survenue.
*/
bool ev_pop(void) {
// Si la table est vide, indiquer l'erreur (retourner faux).
if (ev_tail == ev_head) return false;
// Récupérer la valeur à la fin de la table et initialise la variable globale "evenement"
evenement = event_queue[ev_tail];
// Calculer la nouvelle position de la fin de la table
ev_tail = (ev_tail + 1) & 0x07;
// Indiquer que l'opération s'est terminée correctement.
return true;
}
// Valeur en microsecondes du dernier évènement accepté à l'entrée D2 (INT0)
volatile unsigned long last_us_int0 = 0;
// Valeur en microsecondes du dernier évènement accepté à l'entrée D3 (INT1)
volatile unsigned long last_us_int1 = 0;
/* ISR(INT0_vect)
Description : Fonction de service pour l'interruption externe INT0). Cette fonction
est exécutée lorsqu'une transition entre les niveaux haut et bas
("falling") est détectée à l'entrée "D2" du Arduino. Cela génère une
interruption. Le temps (microsecondes) entre les interruptions est
calculé et comparé à la constante DEBOUNCE_US. Les interruptions qui
se produisent trop rapidement sont considérées comme des rebonds et
sont ignorées.
Paramètre :
Aucun.
Valeur retournée : Aucune
*/
ISR(INT0_vect) {
// Obtenir le temps en microsecondes depuis la mise en route du Arduino
unsigned long now = micros();
// Si le temps depuis la dernière interruption est plus grand ou égal à la
// constante DEBOUNCE_US, insérer le numéro de l'interrupteur "0" dans la table
// des évènements à traiter.
// Mémoriser le temps de la dernière interruption provenant de l'interrupteur "0"
if (now - last_us_int0 >= DEBOUNCE_US) {
last_us_int0 = now;
// Insérer l'évènement numéro "0" dans la table circulaire
ev_push(0);
// Imprimer sur la console que l'interrupteur "0" a été activé.
Serial.print("0 - now == ");
Serial.print(now);
Serial.print("; last_us_int0 == ");
Serial.println(last_us_int0);
Serial.flush();
}
// Effacer le fanion d'interruption de INT0
EIFR = EIFR | 0x01;
}
/* ISR(INT1_vect)
Description : Fonction de service pour l'interruption externe INT1). Cette fonction
est exécutée lorsqu'une transition entre les niveaux haut et bas
("falling") est détectée à l'entrée "D3" du Arduino. Cela génère une
interruption. Le temps (microsecondes) entre les interruptions est
calculé et comparé à la constante DEBOUNCE_US. Les interruptions qui
se produisent trop rapidement sont considérées comme des rebonds et
sont ignorées.
Paramètre :
Aucun.
Valeur retournée : Aucune
*/
ISR(INT1_vect) {
unsigned long now = micros();
// Si le temps depuis la dernière interruption est plus grand ou égal à la
// constante DEBOUNCE_US, insérer le numéro de l'interrupteur "1" dans la table
// des évènements à traiter.
if (now - last_us_int1 >= DEBOUNCE_US) {
// Mémoriser le temps de la dernière interruption provenant de l'interrupteur "1"
last_us_int1 = now;
// Insérer l'évènement numéro "0" dans la table circulaire
ev_push(1);
// Imprimer sur la console que l'interrupteur "1" a été activé.
Serial.print("1 - now == ");
Serial.print(now);
Serial.print("; last_us_int1 == ");
Serial.println(last_us_int1);
Serial.flush();
}
// Effacer le fanion d'interruption de INT1
EIFR = EIFR | 0x02;
}
/* leds_init
Description : Fonction qui fait l'initialisation des ports de sorties contrôlants les DEL.
Cette fonction doit utiliser les registres afin de ne pas modifier les autres ports.
Paramètre :
Aucun.
Valeur retournée :
Aucune
*/
void leds_init(void) {
// Initialiser les ports D8 et D13 du Arduino en sorties avec les registres.
DDRB = DDRB | (0x01 | 0x20);
// Ecrire la valeur "0" sur les ports D8 et D13 du Arduino afin d'éteindre les 2 DEL.
PORTB = PORTB &~ (0x01 | 0x20);
}
/* buttons_init_intx
Description : Fonction d'initialisation des ports utilisés par les interrupteurs (D2 et D3).
Cette fonction configure les ports D2 et D3 du Arduino en entrées et active
les interruptions externes INT0 et INT1.
Paramètre :
Aucun.
Valeur retournée : Aucune
*/
void buttons_init_intx(void) {
// Configurer les ports reliés aux interrupteurs en entrées
DDRD = DDRD &~ (0x04 | 0x08);
// Activer les résistances de maintien sur ces 2 entrées
PORTD = PORTD | (0x04 | 0x08);
// Configurer les interruptions externes INT0 et INT1 afin de détecter les fronts descendants
// Registre EICRA: INT0 et INT1 front descendant
EICRA = EICRA | (0x01 | 0x02);
// Effacer les 2 fanions d'interruption de INT0 et INT1
EIFR = EIFR | (0x01 | 0x02);
// Activer les 2 interruptions externes INT0 et INT1 (ports D2 et D3)
EIMSK = EIMSK | (0x01 | 0x02);
}
/* del_ok_blink
Description : Cette fonction fait clignoter la DEL OK le nombre
de fois définit par le paramètre "n".
Paramètre :
n : Définit le nombre de fois que la DEL doit être
allumée et éteinte.
Valeur retournée : Aucune
*/
void del_ok_blink(unsigned char n) {
// Pour le nombre de fois que la DEL OK doit être allumée et éteinte
for (unsigned char i = 0; i < n; i++)
{
// Activer la DEL OK à l'aide de la constante "DEL_OK_PIN" et des registres.
PORTB = PORTB | (0X20);
// Attendre 120 millisecondes
delay(120);
// Désactiver la DEL OK à l'aide de la constante "DEL_OK_PIN" et des registres.
PORTB = PORTB &~ (0x20);
// Attendre 120 millisecondes
delay(120);
}
}
/* del_err_blink
Description : Cette fonction fait clignoter la DEL ERR le nombre de fois définit
par le paramètre "n" en utilisant les registres.
Paramètre :
n : Définit le nombre de fois que la DEL doit être allumée et éteinte.
Valeur retournée : Aucune
*/
void del_err_blink(unsigned char n) {
// Pour le nombre de fois que la DEL Erreur doit être allumée et éteinte
for (unsigned char i = 0; i < n; i++)
{
// Allumer la DEL rouge (erreur) en utilisant les registres
PORTB = PORTB | (0x01);
//Attendre 120 millisecondes
delay(120);
//Éteindre la DEL rouge (erreur) en utilisant les registres
PORTB = PORTB &~ (0x01);
//Attendre 120 millisecondes
delay(120);
}
}
/* del_ok_pulse
Description : Cette fonction fait clignoter rapidement la DEL OK une seule fois.
Paramètre :
Aucun
Valeur retournée :
Aucune
*/
void del_ok_pulse(void) {
// Allumer la DEL verte (OK) en utilisant les registres
PORTB = PORTB | (0x20);
// Attendre 40 millisecondes
delay(40);
// Éteindre la DEL verte (OK) en utilisant les registres
PORTB = PORTB &~ (0x20);
}
// Caractère non signé indiquant le nombre de clés reconnues correctement
unsigned char progress = 0;
// Entier long non signé indiquant le temps en millisecondes depuis le démarrage d'Arduino
unsigned long start_ms = 0;
// Valeur booléenne indiquant si un code est présentement entré par l'utilisateur
bool in_progress = false;
// Valeur booléenne indiquant qu'un mauvais code a été entré.
bool locked = false;
// Indique jusqu'à combien de millisecondes la serrure est désactivée (barrée)
unsigned long lock_until = 0;
/* setup
Description : Fonction de l'environnement Arduino qui fait la configuration du système.
Paramètre :
Aucun
Valeur retournée :
Aucune
*/
void setup(void) {
// Initialiser le port série de dépannage.
Serial.begin(9600);
// Initialiser les ports utilisés pour contrôler les DEL avec la fonction
leds_init();
// Configure les ports D2 et D3 du Arduino en entrées et active
// les interruptions externes INT0 et INT1 avec la fonction
buttons_init_intx();
// Activer les interruptions globales
sei();
// Attendre 300 millisecondes
delay(300);
// Inscrire sur le port série le temps d'exécution du programme
Serial.println("Main");
Serial.print("Now == ");
Serial.println(millis());
Serial.flush();
}
/* loop
Description : Fonction de l'environnement Arduino appellée en boucle.
Paramètre :
Aucun
Valeur retournée :
Aucune
*/
void loop(void) {
// Mémoriser le temps présent d'exécution pour cette boucle
unsigned long now_ms = millis();
// Si le système est désactivé à cause d'une erreur d'entrée de l'utilisateur
if (locked) {
// Si le temps de désactivation est expiré
if (now_ms >= lock_until) {
// Mettre la variable glogale "locked" à faux
locked = false;
// Faire clignoter 2 fois la DEL OK pour indiquer que le système est de nouveau opérationnel
// avec la fonction
del_ok_blink(2);
// Inscrire le message "Unlocked" sur le port série de débogage.
Serial.println("Unlocked");
}
// Sinon
} else {
// Si un évènement est disponible pour être traité avec la fonction
if (ev_pop())
{
// Si c'est la première valeur du code secret
if (!in_progress) {
// Indiquer qu'un code est en cours d'entré
in_progress = true;
// Mémoriser le temps de départ pour la prochaine entrée
start_ms = now_ms;
// indiquer qu'on a traité correctement la première valeur du code
progress = 0;
//// Inscrire le message "Start" sur le port série de débogage. I
Serial.println("Start");
Serial.flush();
;
}
// Si l'interrupteur activé correspond à celui du code secret
if (evenement == SECRET[progress]) {
// Augmenter la variable "progress" pour indiquer le prochain interrupteur à recevoir
progress++;
// Allumer brièvement la DEL OK afin d'indiquer la réception correcte d'un évènement avec la fonction
del_ok_pulse();
// Inscrire le message "OK" sur le port série de débogage.
Serial.println("OK");
// Si le code secret a été entré correctement par l'utilisateur
if (progress == SECRET_LEN) {
// Allumer 3 fois la DEL OK afin d'indiquer la réception correcte du code en entier
del_ok_blink(3);
// Mettre la variable "in_progress" à faux pour indiquer que le système est en attente
in_progress = false;
// Mettre la variable "progress" à 0 pour débuter la recherche d'un autre code éventuel
progress = 0;
// Inscrire le message "Porte débarrée" sur le port série de débogage.
Serial.println("Porte débarrée");
}
//Sinon
} else {
// Indiquer l'erreur en faisant clignoter la DEL rouge (Erreur) 2 fois
del_err_blink(2);
// Mettre la variable "in_progress" à faux pour indiquer que le système est en attente
in_progress = false;
// Mettre la variable "progress" à 0 pour débuter la recherche d'un autre code éventuel
progress = 0;
// Mettre la variable globale "locked" à vrai afin d'indiquer que le système en erreur est désactivé
locked = true;
// Initialiser la variable globale "lock_until" afin d'indiquer le temps de réactivation du système.
lock_until = now_ms + LOCKOUT_MS;
// Inscrire le message "DÉSACTIVÉ! reprise à " sur le port série de débogage.
Serial.print("DÉSACTIVÉ! reprise à ");
Serial.println(lock_until);
}
} else {
// Attendre 10 millisecondes
delay(10);
}
}
// Si un code est présentement en cours d'entrée et que le délai est expiré
if (in_progress && (now_ms - start_ms > TIMEOUT_MS)) {
//Faire clignoter la DEL d'erreur 1 fois pour indiquer l'erreur avec la fonction
del_err_blink(1);
// Mettre la variable "in_progress" à faux pour indiquer que le système est en attente
in_progress = false;
// Mettre la variable "progress" à 0 pour débuter la recherche d'un autre code éventuel
progress = 0;
// Inscrire le message "Délai d'entrée expiré!" sur le port série de débogage.
Serial.println("Délai d'entrée expiré!");
}
// Attendre 1 milliseconde
delay(1);
// } // for
return 0;
}