Création d’une librairie de gestion des boutons

1. Objectif

L’objectif de ce tutoriel sera double :

  1. Comment créer et mettre en place une librairie que l’on pourra réutiliser dans nos projets
  2. Créer une librairie de gestion des boutons

En effet, il est souvent souhaitable de transférer une partie de code qui nous semble fini et surtout réutilisable. Il existe en C++ la possibilité de réaliser des librairies, nous en consommons tous qui peuvent être téléchargé et installé dans l’IDE arduino. Nous allons donc voir comment réaliser une librairie et comment l’installer sur notre environnement de travail Arduino.

Afin de remplir ce premier objectif, nous allons réaliser une librairie concrète. Dans nos projets Arduino, nous utilisons souvent de boutons poussoir, il en existe de différentes formes. En informatique, nous pouvons utiliser ce que l’on appel des événements, ces événements permettent de lancer des actions (vos méthodes) quand des états interviennent (Etat de la souris par exemple). Il existe tout une gamme d’événements dont nous détaillerons la liste pour des boutons. L’objectif est donc de réaliser une librairie de gestion de boutons. Il sera possible de gérer plusieurs boutons.

A propos du code source :

Vous remarquerez que j’attache la plus grande importance à commenter mon code. Un code bien commenté, c’est un code mieux compris. Malgrès tout dans ce tutoriel, je détaillerai certaines notions clés.

1. Pré requis

a. Connaissance utile :

Nous allons ici utiliser des notions avancé de programmation. En C++ nous réalisons de la programmation objet. Ici, il sera utile d’avoir les notions de base de la programmation objet. Savoir la différence entre private, public, ..  En effet, nottre librairie va nous permettre de déclarer un objet ManageButton qui permettra la gestion des boutons.

 

3. Matériel utilisé

  • 1 Arduino Nano
  • 1 bouton poussoir
  • 1 résistances de 10K Ohms.
  • Quelques cables : 2 rouges, 1 bleu et 1 noir.

4. Schéma

5. La librairie button

Le but étant de réaliser une librairie, nous allons dans un premier temps expliquer comment cela se passe en C++, comment il faut découper son code, puis dans un second temps nous verrons ce qui doit être produit dans la librairie button.

a. Notions sur les librairies

une librairie est stocké dans 2 fichiers dont les extensions sont respectivement .h et .cpp.

1.Le fichier .h

Un fichier .h contient la description de la librairie également appelé les prototypes de nos objets. On peut y ajouter les inclusions d’autres librairies, la déclaration de constantes, d’énumération, de structures et d’objets. Pour la déclaration d’objets, seront décrit les prototypes (Signature) de chacun méthode, propriété et variable membre. Celles ci, regroupé sous différents label suivant la porté des méthodes. Pour rappel :

  • private signifie que la porté se limite à l’intérieur de l’objet (Les variables ou méthodes ne seront connu que dans l’objet lui même)
  • protected signifie que la porté se limite à la class ainsi qu’à ses enfants (S’il y a héritage).
  • public signifie que la porté sera globale.

Vous noterez que je nomme toujours mes variables avec des préfixes :

  • m_ pour les membres
  • p_ pour les paramètres de méthodes
  • v_ pour les variables locales.

Cette notation me permet de produire un code plus lisible et plus simple à comprendre. En effet, du premier coup d’œil on peut identifier la porté d’une variable.

2. Le fichier .cpp

Le fichier .cpp est tout simplement l’implémentation des différentes définitions réalisées dans le fichier .h

b.Contenu de la librairie button

Il y a pas mal de chose à dire ici. Je vais essayer de décrire l’essentiel et de ne pas vous collez un mal de crâne ;). Néanmoins, pour une bonne compréhension, nous devons voir et expliquer ce que l’on souhaite réaliser. Parler un peu d’algorithmie puisqu’il va falloir coder ces concepts.

1. Cahier des charges

La librairie devra permettre de gérer un ou plusieurs boutons poussoirs dont on souhaite pouvoir connaitre l’état. Evidemment un bouton ne possède que deux états de base :

  • relâché (release)
  • enfoncé (press)

Néanmoins, en informatique on connait d’autres états de boutons qui sont lié à l’enchaînement de ces deux états de base : Le clic, le double clic, … Nous souhaitons donc pouvoir gérer tous ces autres états sachant que certains se combinerons : Si je press et que je relache rapidement un bouton, l’état de relâchement sera actionné mais également l’état de clic. Cela veut dire qu’instantanément, un bouton peut avoir plusieurs états.

Voici la liste des états que l’on souhaite avoir :

  • Change : permet de savoir si l’état du bouton change.
  • Up : permet de savoir si le bouton est relâché.
  • Press : permet de savoir si le bouton est en cours pression.
  • Release : permet de savoir s’il n’y a aucune activité sur le bouton.
  • Clic : permet de savoir si le bouton a été pressé puis relâché dans un temps imparti.
  • Double clic : permet de savoir si le bouton a réalisé 2 clic successivement dans un temps imparti
  • clic moyen : permet de savoir si le bouton a réalisé un clic dont on est resté appuyé un temps imparti supérieur au temps du clic et inférieur au temps du clic long
  • clic long : permet de savoir si le bouton a réalisé un clic dont on est resté appuyé un temps imparti supérieur au temps du clic moyen

Nous voyons ici qu’il y a des notions de temporalité qui on leur importance. En effet, en fonction de temps de pression les états peuvent être différents. Il faut également noter qu’un état en lui même se doit de durer un certain temps.

2. Algorithmie

a. Les états

Afin de gérer les états multiples d’un bouton (également simultanément), nous allons utiliser directement les notions les plus fondamentales. En effet, un octet est composé de bits. Nous pouvons stocker sur un octet différents états simultanément en choisissant les bits sur lesquels on stock un état.

Extrait de button.h permettant de gérer les états
/*
Bit 0 -> 0 : release, 1 : OnPressed.
Bit 1 -> 0 : release, 1 : OnUp.
Bit 2 -> 0 : ras, 1 : OnClick.
Bit 3 -> 0 : ras, 1 : OnDblClick.
Bit 4 -> 0 : ras, 1 : OnMediumClick.
Bit 5 -> 0 : ras, 1 : OnLongClick.
Bit 6 -> 0 : Pas de changement, 1 : changement d'état(s)
*/
enum mask
{
	PRESSEDMASK = 1,
	UPMASK = 2,
	CLICKMASK = 4,
	DBLCLICKMASK = 8,
	MEDIUMCLICKMASK = 16,
	LONGCLICKMASK = 32,
	CHANGEMASK = 64
};

Ensuite, en utilisant des masques, il est possible de savoir si l’état est activé ou non.

Extrait de button.cpp qui permet savoir si un état est activé ou non
bool ManageButton::HasStatus(byte p_status, byte p_mask)
{
	return (p_status & p_mask) == p_mask;
}

Il est également possible modifier les états avec ces mêmes masques

Extrait de button.cpp permet de modifier les états.
void ManageButton::SetToStatus(byte &p_status, byte p_mask)
{
	p_status = p_status | p_mask;
}

void ManageButton::UnSetToStatus(byte &p_status, byte p_mask)
{
	p_status = p_status & (255 ^ p_mask);
}

b. L’automate.

Nous avons vu qu’une notion temporelle intervient. Il faut stocker des états intermédiaires nous permettant d’atteindre d’autres états. En effet, pour réaliser  un double clic par exemple, il faut avoir déjà effectué un clic qu’il faut avoir mémorisé. Nous avons vu que dans le cahier des charges ces états, que l’on va appeler événements seront déclenché selon des temporalités établi.

Extrait de button.h, liste des temporalités
#define LISTENDELAY			10		// Délai minimum pour réaliser des actions lié au bouton.
#define REBOUNDDELAY		400		// Délai minimun pour annuler l'action précédente.
#define MEDIUMREBOUNDDELAY	1000	// Délai médian pour une action médian.
#define LONGREBOUNDDELAY	3000	// Délai long pour une action longue.
#define DBLCLICKDELAY		600		// Délai pour réaliser un double clic.

afin de gérer cet enchaînement d’état, nous devons utiliser un automate (vous pouvez aller voir ce lien pour en comprendre un peu plus). Cet automate nous permettra de garantir l’enchaînement des actions et donc la cohérence des différents états mis en place. Cette automate sera stocké dans une méthode appelé Listen puisqu’elle sera la méthode a appeler dans le loop afin « d’écouter » les différents bouton.

Je vous passe l’explication détaillé de la méthode Listen (C’est celle ci qui peut vous donner un bon mal de crâne).

3.Contenu du fichier .h

Vous allez retrouver les différents points déjà abordé auxquels vont venir s’ajouter la structure de bouton. En effet comme nous devons gérer un automate et des notions de temporalité, il nous faut pour chaque bouton stocker différentes informations qui seront dans cette structure.

Dans les méthodes, il nous une méthode Add afin de rajouter des boutons.

Nous avons également un ensemble de méthode On et Is qui permettent de savoir si un état est actionné ou non. Ces méthodes sont rendus simplissime par le fait que l’ensemble des états sont stockés dans l’octet qui est modifié dans la méthode Listen.

librairie button.h
/* Button.h
Par Sébastien DELAPORTE Mai 2016
licence GNU GPL https://fr.wikipedia.org/wiki/Licence_publique_g%C3%A9n%C3%A9rale_GNU
*/
#if defined(ARDUINO) && ARDUINO >= 100
	#include "arduino.h"
#else
	#include "WProgram.h"
#endif

#define LISTENDELAY			10		// Délai minimum pour réaliser des actions lié au bouton.
#define REBOUNDDELAY		400		// Délai minimun pour annuler l'action précédente.
#define MEDIUMREBOUNDDELAY	1000	// Délai médian pour une action médian.
#define LONGREBOUNDDELAY	3000	// Délai long pour une action longue.
#define DBLCLICKDELAY		600		// Délai pour réaliser un double clic.

/*
Bit 0 -> 0 : release, 1 : OnPressed.
Bit 1 -> 0 : release, 1 : OnUp.
Bit 2 -> 0 : ras, 1 : OnClick.
Bit 3 -> 0 : ras, 1 : OnDblClick.
Bit 4 -> 0 : ras, 1 : OnMediumClick.
Bit 5 -> 0 : ras, 1 : OnLongClick.
Bit 6 -> 0 : Pas de changement, 1 : changement d'état(s)
*/
enum mask
{
	PRESSEDMASK = 1,
	UPMASK = 2,
	CLICKMASK = 4,
	DBLCLICKMASK = 8,
	MEDIUMCLICKMASK = 16,
	LONGCLICKMASK = 32,
	CHANGEMASK = 64
};

// Structure d'un bouton
struct Button {							
	byte Pin;							// Pin sur lequel le bouton est cablé.
	byte Status;						// Statut courant
	bool IsPreviousClick;				// Avons nous déjà un click pour réaliser une double ?
	byte CurrentValue;					// Valeur capturée du bouton.
	unsigned int PressTime;				// Temps auquel le bouton est pressé.
	unsigned int ReleaseTime;			// Temps auquel le bouton est relaché.
};

class ManageButton
{
private:
	unsigned long int m_currentTime;	// Délai en milliseconde depuis le démarrage de l'arduino, capturé lors de l'écoute du statut du bouton.
	unsigned long int m_previousTime;	// Délai en milliseconde depuis le démarrage de l'arduino, capturé lors de la précédente écoute du statut du bouton.

	Button *m_btnList;					// Liste des boutons.
	byte m_nbButton;					// Nombre de bouton
	
	void SetToStatus(byte &p_status, byte p_mask);		// Ajoute au statut p_status le mask p_mask.
	void UnSetToStatus(byte &p_status, byte p_mask);	// Retire au statut p_status le mask p_mask.
	bool HasStatus(byte p_status, byte p_mask);			// Vérifie si le statut p_status possède le mask p_mask.
public:
	// Constructeurs
	ManageButton(byte p_pin);
		
	// Methodes
	void Add(byte p_pin);				// Ajoute un bouton à la gestion des boutons
	byte GetStatus(byte p_btn = 1);		// Demande le statut du bouton p_btn.
	void listen(void);					// Méthode d'écoute de l'état des boutons.
	void Release(byte p_btn = 1);		// Force la remise au status de repos d'un bouton.
	bool OnChange(byte p_btn = 1);		// Est ce que le bouton p_btn est changement d'état ?
	bool OnUp(byte p_btn = 1);			// Est ce que le bouton p_btn est en cours relachement ?
	bool OnPress(byte p_btn = 1);		// Est ce que le bouton p_btn est en cours pression ?
	bool OnRelease(byte p_btn = 1);		// Est ce que le bouton p_btn est relaché ?
	bool OnClick(byte p_btn = 1);		// Est ce que le bouton p_btn a effectué un simple clic ?
	bool OnDblClick(byte p_btn = 1);	// Est ce que le bouton p_btn a effectué un double clic ?
	bool OnMediumClick(byte p_btn = 1);	// Est ce que le bouton p_btn a effectué un clic moyennement long ?
	bool OnLongClick(byte p_btn = 1);	// Est ce que le bouton p_btn a effectué un clic long ?
	bool IsPressed(byte p_btn = 1);		// Est ce que le bouton p_btn est pressé ?
	bool IsUp(byte p_btn = 1);			// Est ce que le bouton p_btn est relaché ?
};

4.Contenu du fichier .cpp

l’implémentation de la librairie préalablement défini dans le fichier .h

librairie button.cpp
/*
Author : Sébastien DELAPORTE 	
Date : 05/2016
Version : 0.9.1
*/

#include "Button.h"

// Constructors
ManageButton::ManageButton(byte p_pin)
{
	this->m_nbButton = 1;
	this->m_btnList = (Button *)malloc(m_nbButton * sizeof(Button));
	this->m_btnList[0].Pin = p_pin;
	this->m_btnList[0].Status = 0;
	this->m_btnList[0].PressTime = 0;
	this->m_btnList[0].ReleaseTime = 0;
	pinMode(this->m_btnList[0].Pin, INPUT);
}

void ManageButton::Add(byte p_pin)
{
	this->m_nbButton++;
	realloc(this->m_btnList, m_nbButton * sizeof(Button));
	this->m_btnList[m_nbButton - 1].Pin = p_pin;
	this->m_btnList[m_nbButton - 1].Status = 0;
	this->m_btnList[m_nbButton - 1].PressTime = 0;
	this->m_btnList[m_nbButton - 1].ReleaseTime = 0;
	pinMode(this->m_btnList[m_nbButton - 1].Pin, INPUT);
}

void ManageButton::SetToStatus(byte &p_status, byte p_mask)
{
	p_status = p_status | p_mask;
}

void ManageButton::UnSetToStatus(byte &p_status, byte p_mask)
{
	p_status = p_status & (255 ^ p_mask);
}

bool ManageButton::HasStatus(byte p_status, byte p_mask)
{
	return (p_status & p_mask) == p_mask;
}

void ManageButton::Release(byte p_btn)
{
	byte v_newStatus = 0;
	SetToStatus(v_newStatus, CHANGEMASK);
	this->m_btnList[p_btn - 1].Status = v_newStatus;
}

void ManageButton::listen(void)
{
	// Sauvegarde temporelle
	this->m_previousTime = this->m_currentTime;		// On sauvegarde l'ancienne capture temporelle												
	this->m_currentTime = millis();									// Capture du nombre de millisecondes
	// Algo de recherche du statut
	if (this->m_currentTime - this->m_previousTime >= LISTENDELAY)	// Il est possible de modier le statut dans ce cas
	{
		for (int i = 0; i < this->m_nbButton; i++)
		{
			// On va vérifier si on est en changement de statut
			byte v_state = digitalRead(this->m_btnList[i].Pin);
			if (this->m_btnList[i].CurrentValue != v_state)
			{	// On change d'état
				this->m_btnList[i].CurrentValue = v_state;
				SetToStatus(this->m_btnList[i].Status, mask::CHANGEMASK);								// Donc changement de statut
				if (this->m_btnList[i].CurrentValue)
				{
					// On appuie
					this->m_btnList[i].PressTime = this->m_currentTime;
					SetToStatus(this->m_btnList[i].Status, mask::PRESSEDMASK);							// On met le pressed
				}
				else
				{
					// On relache
					this->m_btnList[i].ReleaseTime = this->m_currentTime;
					SetToStatus(this->m_btnList[i].Status, mask::UPMASK);										// On est donc en relachement
					unsigned int v_deltaTemp = this->m_currentTime - this->m_btnList[i].PressTime;
					if (v_deltaTemp >= LONGREBOUNDDELAY)
					{
						SetToStatus(this->m_btnList[i].Status, mask::LONGCLICKMASK);				// On met le long click
						this->m_btnList[i].IsPreviousClick = false;
					}
					else if (v_deltaTemp >= MEDIUMREBOUNDDELAY &&  v_deltaTemp < LONGREBOUNDDELAY)
					{
						SetToStatus(this->m_btnList[i].Status, mask::MEDIUMCLICKMASK);			// On met le medium click		
						this->m_btnList[i].IsPreviousClick = false;
					}
					else if (v_deltaTemp <MEDIUMREBOUNDDELAY)
					{
						if (this->m_btnList[i].IsPreviousClick) // Si on avait déjà un simple click avant
						{
							if (v_deltaTemp <= DBLCLICKDELAY)
							{
								SetToStatus(this->m_btnList[i].Status, mask::DBLCLICKMASK);			// On met le double click							
								this->m_btnList[i].IsPreviousClick = false;
							}
						}
						else
						{
							if(!HasStatus(this->m_btnList[i].Status, mask::DBLCLICKMASK))
							{
								SetToStatus(this->m_btnList[i].Status, mask::CLICKMASK);						// On met le simple click
								this->m_btnList[i].IsPreviousClick = true;
							}
						}
					}
				}
			}
			else // On ne change pas d'état
			{
				this->m_btnList[i].CurrentValue = v_state;
				UnSetToStatus(this->m_btnList[i].Status, mask::CHANGEMASK);						// Donc changement de statut
				if (this->m_currentTime - this->m_btnList[i].PressTime >= REBOUNDDELAY)
				{
					UnSetToStatus(this->m_btnList[i].Status, mask::PRESSEDMASK);					// On retire le relachement
				}
				if (this->m_currentTime - this->m_btnList[i].PressTime >= DBLCLICKDELAY)
					this->m_btnList[i].IsPreviousClick = false;
				if (this->m_currentTime - this->m_btnList[i].ReleaseTime >= REBOUNDDELAY)
				{
					UnSetToStatus(this->m_btnList[i].Status, mask::UPMASK);								// On retire le relachement
					UnSetToStatus(this->m_btnList[i].Status, mask::LONGCLICKMASK);				// On retire le long click.
					UnSetToStatus(this->m_btnList[i].Status, mask::MEDIUMCLICKMASK);		// On retire le medium click.
					UnSetToStatus(this->m_btnList[i].Status, mask::DBLCLICKMASK);					// On retire le double click.
					UnSetToStatus(this->m_btnList[i].Status, mask::CLICKMASK);							// On retire le simple click.	
				}
			}
		}
	}
	else
	{
		// On a pas assez de délai, le délai courant reprend la valeur du précédent.
		this->m_currentTime = this->m_previousTime;
	}
}

byte ManageButton::GetStatus(byte p_btn)
{
	return this->m_btnList[p_btn - 1].Status;
}

bool ManageButton::OnChange(byte p_btn)
{
	return HasStatus(this->m_btnList[p_btn - 1].Status, CHANGEMASK);
}

bool ManageButton::OnPress(byte p_btn)
{
	return HasStatus(this->m_btnList[p_btn - 1].Status, mask::PRESSEDMASK);
}

bool ManageButton::OnUp(byte p_btn)
{
	return HasStatus(this->m_btnList[p_btn - 1].Status, mask::UPMASK);
}

bool ManageButton::OnRelease(byte p_btn)
{
	return this->m_btnList[p_btn - 1].Status ==0;
}

bool ManageButton::OnClick(byte p_btn)
{
	return HasStatus(this->m_btnList[p_btn - 1].Status, mask::CLICKMASK);
}

bool ManageButton::OnDblClick(byte p_btn)
{
	return HasStatus(this->m_btnList[p_btn - 1].Status, mask::DBLCLICKMASK);
}

bool ManageButton::OnMediumClick(byte p_btn)
{
	return HasStatus(this->m_btnList[p_btn - 1].Status, mask::MEDIUMCLICKMASK);
}

bool ManageButton::OnLongClick(byte p_btn)
{
	return HasStatus(this->m_btnList[p_btn - 1].Status, mask::LONGCLICKMASK);
}

bool ManageButton::IsPressed(byte p_btn)
{
	return this->m_btnList[p_btn - 1].CurrentValue==1;
}

bool ManageButton::IsUp(byte p_btn)
{
	return this->m_btnList[p_btn - 1].CurrentValue == 0;
}

 

6. L’installation de la librairie

Maintenant que nous avons réalisé notre librairie .h et son .cpp nous aimerions l’installer sur notre machine afin de pouvoir l’utiliser facilement dans des programmes.

Sur votre IDE arduino, dans fichier>préférence, vous pouvez l’emplacement ou sont stockées les librairie (encadré rouge).

Ce répertoire de votre machine contient un répertoire libraries qui l’ensemble des librairies installées. Il vous suffit alors créer un répertoire button et d’y déposer les deux fichiers de notre librairie (Vous faite cela lorsque c’est vous qui créez et installez la librairie).

Vous pouvez ensuite créer un fichier readme.txt dans ce même dossier pour mettre vos crédits, … Ensuite vous pouvez zipper le répertoire.

En effet sous l’IDE arduino, il est possible d’installer des librairies à partir de fichier .zip. Et voilà, le tour est joué. Vous savez comment créer et installer une librairie ainsi que la diffuser.

Vous pouvez télécharger la librairie button sur ce lien.

7. Le programme de test de la librairie.

Le montage est rudimentaire, le code tout aussi simple. Le but est d’afficher les différents statuts sur le moniteur. Ca défile un peu vite mais on peut voir les différents changements.

Sur une ligne j’affiche les différents états et sur la seconde les valeurs. O pour inactif et X pour statut actif.

testButtonLib.ino
#include<Button.h>					// Inclusion de la librairie de gestion des boutons.

#define BTN1 2						// Pin du bouton

ManageButton m_btns(BTN1);			// Init de la lib de boutons

// Affichage du status X activé, O inactif.
void AffStatus(bool p_val)
{
	if(p_val)
		Serial.print("X |");
	else
		Serial.print("O |");
}


// Affichage des différents status.
void PrintButtonStatus()
{
	Serial.println("Ch|UP|Pr|Re|Cl|DC|MC|LC|IP|IU|");
	AffStatus(m_btns.OnChange());
	AffStatus(m_btns.OnUp());
	AffStatus(m_btns.OnPress());
	AffStatus(m_btns.OnRelease());
	AffStatus(m_btns.OnClick());
	AffStatus(m_btns.OnDblClick());
	AffStatus(m_btns.OnMediumClick());
	AffStatus(m_btns.OnLongClick());
	AffStatus(m_btns.IsPressed());
	AffStatus(m_btns.IsUp());
	Serial.println("");
}

void setup()
{		
	Serial.begin(9600);				// On init le port série pour avoir une console.
	pinMode(BTN1, INPUT);			// Inputs : Bouton
}

void loop()
{
	m_btns.listen();				// Permet de gérer les status des boutons.
	PrintButtonStatus();			// Affichage des différents status.
	delay(100);
}

Voyez plutôt en image.

One thought on “Création d’une librairie de gestion des boutons

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.