Micro contrôleurs AVR/Travail pratique/Utilisation du shield LCD keypad

Dans ce TP, nous allons utiliser le shield LCD keypad permettant d'afficher sur un LCD de deux lignes de 16 caractères. L'objectif du TP est de réaliser un réveil. Toute la programmation se fera en C et ne nécessitera aucune librairie extérieure.

Gestion du LCD

modifier

La gestion du LCD se fera à l'aide d'une librairie de très bas niveau écrite par Michel Doussot à laquelle nous avons ajouté quelques fonctions et surtout que nous avons adapté pour l'Arduino UNO.

Voici la librairie :

//      lcd.c
//      
//      Copyright 2014 Michel Doussot <michel@mustafar>
//      
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 2 of the License, or
//      (at your option) any later version.
//      
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//      
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.


#include <avr/io.h>
#define RS 		0x01
#define E		0x02
#define DATAS  	0xF0

// unite approximative 2us
void delai(unsigned long int delai) {
	volatile long int i=0;
	for(i=0;i<delai;i+=1);
} 

// Arduino UNO
//PORTD : b7 b6 b5 b4
//        D3 D2 D1 D0
// PORTB :b1 b0
//      E RS      
void rs_haut(void) {
   PORTB = PORTB | RS;
}

void rs_bas(void) {
   PORTB = PORTB & ~RS; 
}

void e_haut(void) {
   PORTB = PORTB | E;
   delai(8);
}

void e_bas(void) {
    PORTB = PORTB & ~E; 
    delai(8);
}

void e_puls(void) {
   e_haut();
   e_bas();
}

void ecris_4(unsigned char valeur) {
	unsigned char v;
	v = (valeur << 4) & DATAS;
	PORTD = PORTD & ~DATAS ;
	PORTD = PORTD | v ;
    e_puls();
}

void ecris_8(unsigned char valeur) {
	unsigned char v;
	v = valeur & DATAS;
	PORTD = PORTD & ~DATAS ;
	PORTD = PORTD | v ;
    e_puls();
	v = (valeur << 4) & DATAS;
	PORTD = PORTD & ~DATAS ;
	PORTD = PORTD | v ;
    e_puls();    
}


void setup() {

   PORTB = 0;
   delai(6000);
   ecris_4(0x03);
   delai(1600);
   ecris_4(0x03);
   delai(800);
   ecris_4(0x03);
   delai(800);
   ecris_4(0x02);
   delai(40);
   ecris_4(0x02);
   ecris_4(0x08);
   delai(40);
   ecris_4(0x00);
   ecris_4(0x06);
   delai(40);
   ecris_4(0x00);
   ecris_4(0x0C);
   delai(40);
   ecris_4(0x00);
   ecris_4(0x01);
   delai(800);   
}

void writecar(char car) {
	rs_haut();
	ecris_8((unsigned char)car);
}

void writestr(char *chaine) {
	rs_haut();
	while (*chaine) {
		ecris_8((unsigned char)*chaine++);
	}
}

void command (uint8_t value) {
  rs_bas();
  ecris_8(value);
}

#define LCD_CLEARDISPLAY 0x01

void clearScreen() {
  command(LCD_CLEARDISPLAY);  // clear display, set cursor position to zero
  delai(1000);  // this command takes a long time!
}

#define LCD_SETDDRAMADDR 0x80

void setCursor(uint8_t col, uint8_t row)
{
  col = col & 0x0F; // %16
  row = row & 0x01; // %2
  command(LCD_SETDDRAMADDR | (col + 0x40*row));
}

Exercice 1

modifier

Écrire un programme d'exemple d'utilisation de cette librairie.

Exercice 2

modifier

Écrire un sous-programme capable d'afficher un nombre de 8 bits en décimal sur l'afficheur LCD.

Exercice 3

modifier

Écrire un sous-programme capable d'afficher un nombre de 8 bits, déjà en format BCD, en décimal sur l'afficheur LCD.

Exercice 4

modifier

On vous donne un sous-programme capable de gérer l'incrémentation d'une variable 16 bits en format représentant des heures et minutes.

void incrementHHMM(uint16_t *hh_mm) {
  (*hh_mm)++;
      if ((*hh_mm & 0x000F) > 0x0009)
      *hh_mm += 0x0006;
      if ((*hh_mm & 0x00F0) > 0x0050)
      *hh_mm += 0x00A0;
      if ((*hh_mm & 0x0F00) > 0x0900)
      *hh_mm += 0x0600;
      if ((*hh_mm & 0xFF00) > 0x2300)
      *hh_mm = 0x0000;
}

Essayer ce sous programme et réaliser la décrémentation des heures et minutes sur le même principe. Préparer alors un programme d'affichage des heures et minutes pour un essai.

Ressources

modifier

Pour ceux qui désireraient une variation par rapport aux exercices proposés nous proposons quelques sous-programmes à essayer.

Il s'agit d'une incrémentation et décrémentation BCD sur 4 digits (donc 16 bits) :

void incrementBCD(uint16_t *cnt) { 
  (*cnt)++;    
  if ((*cnt & 0x000F) > 0x0009) *cnt += 6; 
  if ((*cnt & 0x00F0) > 0x0090) *cnt += 0x0060;
  if ((*cnt & 0x00F0) > 0x0900) *cnt += 0x0600; 
  if ((*cnt & 0x00F0) > 0x9000) *cnt += 0x6000; 
} 

void decrementBCD(uint16_t *cnt) { 
  (*cnt)--;    
  if ((*cnt & 0x000F) == 0x000F) *cnt -= 6; 
  if ((*cnt & 0x00F0) == 0x00F0) *cnt -= 0x0060; 
  if ((*cnt & 0x0F00) == 0x0F00) *cnt -= 0x0600; 
  if ((*cnt & 0xF000) == 0xF000) *cnt -= 0x6000; 
}

Gestion de la conversion analogique numérique

modifier

La particularité de ce shield est de proposer une gestion des boutons avec une seule broche de l'ATMega328. Pour réaliser cela, il faut naturellement utiliser une conversion analogique numérique comme nous l'avons déjà présenté dans le chapitre correspondant.

Exercice 5

modifier

On vous demande de préparer un sous-programme capable d'afficher le résultat de la conversion analogique numérique (CAN dans la suite).

Indication : on rappelle que la CAN est sur 10 bits et peut donc retourner un nombre entre 0 et 1023.

Exercice 6

modifier

Réaliser la conversion analogique numérique en cherchant comment sont montés les boutons poussoirs. Utiliser le résultat de l'exercice 5 pour faire vos tests. Ce travail doit être séparé en deux sous-programmes :

  • un sous programme de configuration du CAN
  • un sous-programme de lecture du CAN proprement dit

Exercice 7

modifier

Utiliser l'expérimentation de l'exercice 6 pour écrire un programme qui affiche en clair sur le LCD le bouton parmi {SELECT,LEFT,RIGHT,UP,DOWN} qui a été appuyé.

Indications :

  • Évidemment ce travail se fait à partir des résultats obtenus dans l'exercice 6. En effet il est possible de calculer des seuils de tension entre les boutons.
  • On vous propose d'abord de réaliser une fonction qui retourne un uint8_t avec les valeurs suivantes :
    • pas de touche appuyée : valeur 0,
    • touche LEFT appuyée, mettre b0 à 1
    • touche SELECT appuyée mettre b1 à 1
    • touche UP appuyée mettre b2 à 1
    • touche DOWN appuyée mettre b3 à 1
    • touche RIGHT appuyée mettre b4 à 1

Son prototype est :

uint8_t ComputeTouch();

et elle retourne l'information d'appui comme ci-dessous, dans une variable 8 bits :

b7
b6
b5
b4
b3
b2
b1
b0
0
0
0
RIGHT
DOWN
UP
SELECT
LEFT
  • Réaliser ensuite un sous-programme responsable d'afficher le nom de la touche appuyée.

Gestion d'un réveil

modifier

Nous allons maintenant utiliser les sous-programmes que l'on a réalisé précédemment pour réaliser un réveil. Nous allons commencer par le réglage de l'heure de réveil avec les boutons.

Exercice 8

modifier

Réaliser un réglage de l'heure de réveil avec les deux boutons "UP" et "DOWN".

Indications :

  • une vitesse lente peut être réalisée en détectant un appui sur un bouton "UP" ou "DOWN"
  • une vitesse plus lente de défilement put être réalisée en restant appuyé sur "UP" ou "DOWN"
  • option : envisager un incrément qui augmente en fonction du temps d'appui

Voici une solution partielle :

Exercice 9

modifier
 

Réaliser un mécanisme d'armement du réveil (avec la touche "SELECT") lui permettant de sonner quand l'heure courante est égale à l'heure réveil.

Indications : comme le montre le dessin, la gestion d'un mécanisme de gestion de sonnerie peut se faire avec 3 états. La terminologie du dessin empruntée d'un wikibook sur VHDL est assez différente de celle de la présente section :

  • KEY sera à remplacer par SELECT
  • TRIP sera l'égalité entre l'heure courante et l'heure réveil (qui ne dure qu'une minute)
  • L'action RING <= '1' déclenche la sonnerie
  • On pourra utiliser un type enum pour gérer l'automatisme du réveil :
enum etats {Off,Armed,Ringing}; // declaration du type énuméré
//......
enum etats etat=Off; // declaration et initialisation