fbpixel
Utilisation d’un écran Nextion avec Arduino

Utilisation d’un écran Nextion avec Arduino

L’écran Nextion est une des meilleures solutions pour créer une interface graphique afin de piloter votre projet Arduino. L’interface la plus connue pour interagir avec un Arduino est l’écran LCD avec quelques boutons et potentiomètres au prix des entrées-sorties et d’une surcharge du code Arduino. Nous allons voir dans ce tutoriel comment configurer un écran Nextion et comment l’utiliser avec Arduino

Matériel

  • Arduino (ou autre carte avec port UART)
  • Écran Nextion
  • Adaptateur USB-TTL 5V
  • 4x Dupont wire avec connecteur JST

Présentation de la carte Nextion

L’interface de base pour piloter un Arduino, et présent dans tous les kits, est l’écran LCD avec quelques boutons et potentiomètres qui existe sous forme de Shield LCD. Il existe aussi des Shield muni d’écran tactile mais, ces derniers, utilisent toutes les entrées-sorties de l’Arduino et surcharge le code Arduino. Une solution à cela est d’utiliser un écran Nextion qui contient son propre programme et communique avec n’importe quelle microcontrôleur avec le port série.

N.B.: Il est possible de créer une interface graphique sur PC ou créer une interface web pour piloter le projet en ajoutant un module de communication sans fil.

Installation et présentation de l’éditeur Nextion

Téléchargez et installez l’éditeur Nextion

Lorsque vous ouvrez un nouveau projet ou fichier, le logiciel vous demande le modèle de l’écran (dans notre cas NX4832K035_011). Vous pouvez modifier la selection dans le menu Device>Settings

Puis choisissez l’orientation de l’écran et l’encodage.

Création d’une interface graphique

Nous allons utiliser l’éditeur graphique Nextion pour créer notre interface graphique. Dans cet exemple, nous allons ajouter:

  • Une image
  • Quelques textes pour le titre ou pour afficher des données
  • Un bouton qui modifie l’interface
  • Un bouton qui envoie une commande sur le port série
  • Un timer qui rafraichit l’interface
  • Une variable qui stocke une donnée reçue du port série

Pour ajouter un objet cliquez sur l’objet désiré dans la fenêtre Toolbox et l’objet sera inséré automatiquement dans la fenêtre Display. Vous pouvez ensuite configurer l’objet dans la fenêtre Attributes

Vous pouvez télécharger le fichier IHM à importer dans Nextion Editor. Avec cet exemple, vous serez en mesure de créer des interfaces bien plus complexes.

Ajouter une image

Pour ajouter une image, il faut d’abord importer une image dans le logiciel à l’aide de la touche (+) de la fenêtre “Picture”

Vous pouvez ensuite insérer un objet Picture dans la fenêtre Display puis sélectionnez l’image en appuyant sur l’attribut pic > browse…

N.B.: Ajouter une image aux dimensions désirées

Ajouter du texte

Avant de rajouter un objet text, il vous faut générer une police de caractère. Ceci peut se faire dans Tools> Generate Font

Une fois la police générée, vous pouvez la sélectionnez dans l’attribut font de l’objet Text. Vous pouvez ensuite modifier le texte dans l’attribut txt (Attention au nombre de caractère maximum txt_maxl).

Nous ajoutons quatre objets texte:

  • Le label titre
  • l’état de la LED
  • le label “Analog Val”
  • la valeur analogique reçue du micrcontrôleur

Ajouter un bouton

Une fois l’objet bouton ajouté à l’interface, vous pouvez régler dans les attributs:

  • le texte affiché
  • la couleur lorsque le bouton est pressé
  • la couleur lorsque la couleur est relâché

Dans la fenêtre Events, il est possible de programmer ce que l’écran va faire lorsque le bouton est pressé ou relâché. Il y a un certain nombre d’instructions que vous pouvez utiliser dans ces fenêtres. Notamment:

  • Modifier des éléments de l’interface (Ex: t0.txt=”Pressed”)
  • Envoyer l’identifiant du bouton via le port série
  • Envoyer une autre commande avec prints
  • Bouton b0
  • Bouton b1

Dans l’onglet Touch Release Event , nous écrivons la commande suivante qui est une commande prédéfinie de la librairie Nextion et correspondant à trigger1()

printh 23 02 54 01

  • Bouton b2

Pour le bouton b2, nous utilisons la même commande mais pour trigger2()

printh 23 02 54 02

Ajouter un timer

L’objet Timer permet d’exécuter un code régulièrement. Particulièrement utile pour récupérer des données provenant du port série et mettre à jour l’interface.

Dans l’onglet Timer event, nous utilisons la fonctions covx pour convertir la valeur de la variable en string et l’écrire dans le texte t4

covx analog0.val,t4.txt,0,0

Charger le programme sur l’écran Nextion

Brancher la carte Nextion au convertisseur USB-TTL

Pour charger l’interface, appuyez sur Compile puis Upload

Connexion de l’écran Nextion au microcontrôleur Arduino

Pour téléverser le code Arduino sur la carte, les pin RX et TX doivent être déconnectées de l’écran.

Utilisation de la librairie Nextion.h

Il existe différentes librairies que vous pouvez utiliser pour gérer un écran Nextion

Nous utilisons EasyNextion

Dans Nextion Editor, nous définissons un event release “printh 23 02 54 01” pour le bouton ON et un event release “printh 23 02 54 02” pour le bouton Off.

A l’aide de la fonction NextionListen(), ces commandes vont activer les fonctions trigger1 et trigger2.

#include "EasyNextionLibrary.h"

#define baudrate 9600

EasyNex myNex(Serial);

//Variables
uint16_t analog;
bool ledstate;

const int REFRESH_TIME = 100;
unsigned long refresh_timer = millis();

void setup() {
  myNex.begin(baudrate);

  pinMode(A0, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  myNex.NextionListen();

  if ((millis() - refresh_timer) > REFRESH_TIME) {
    analog = analogRead(A0);
    myNex.writeNum("analog0.val", analog);

    refresh_timer = millis();
  }
}

//`printh 23 02 54 XX` , where `XX` the id for the triggerXX() in HEX.
void trigger1() {
  digitalWrite(LED_BUILTIN, HIGH);
  myNex.writeStr("t2.txt", "LED ON");
}

void trigger2() {
  digitalWrite(LED_BUILTIN, LOW);
  myNex.writeStr("t2.txt", "LED OFF");
}

Résultats

Bonus: Nextion Editor Simulator

Nextion propose un simulateur permettant de tester les interactions entre l’écran et microcontrôleur et l’interface graphique. Ce simulateur est accessible en appuyant sur “Debug”

Sources

Utilisation du module SD du shield TFT avec Arduino Mega

Utilisation du module SD du shield TFT avec Arduino Mega

Le shield TFT se compose d’un écran tactile et d’un module de carte micro SD qui n’est pas compatible avec la carte Arduino Mega. Nous avons vu dans les précédents tutoriels comment utiliser l’écran du shield et son module de carte SD. Nous allons voir comment modifier la librairie SD pour rendre compatible le module de carte SD et la carte Arduino Mega

Matériel

  • Ordinateur
  • Arduino MEGA
  • Câble USB A Mâle/B Mâle
  • TFT LCD Shield muni d’un module SD
  • Carte microSD

Explication

Le shield TFT utilise le port SPI pour communiquer avec le module SD. Or, comme la plupart des shields, il est développé pour l’Arduino UNO et n’est pas compatible avec l’Arduino Mega. En effet, les broches du module SD sont connectées aux broches 11,12 et 13 de l’Arduino qui correspond au port SPI d’une UNO mais pas d’une Mega (51,52,53).

Arduino BoardMOSIMISOSCKSS
UNO11121310
Mega51505253

Solution 1: souder les broches

Pour ne pas modifier le code, il est possible de souder les broches selon la liste suivante:

  • MOSI 11 –> 51
  • MISO 12 –> 50
  • SCK 13 –> 52

L’avantage est que vous n’avez pas à plongez dans le code des librairies Arduino et vous conservez les performances du port SPI. L’inconvénient est que vous perdez des entrées-sorties.

Solution 2: Modifier la librairie SD.h

Si vous ne souhaitez pas souder des fils sur l’Arduino, vous pouvez modifier le code de la libraire SD.h.

Pour rendre compatible le module SD du shield TFT, il faut modifier 3 fichiers Sd2Card.h, Sd2Card.cpp et SD.cpp

Fichier Sd2Card.h

Ce fichier se trouve dans le répertoire, C:\Program Files (x86)\Arduino\libraries\SD\src\utility

Ligne 47, mettez la variable MEGA_SOFT_SPI à 1, commentez la ligne 38 et placez #define USE_SPI_LIB après la ligne #ifndef SOFTWARE_SPI, ligne 56

  • Avant
/**
   USE_SPI_LIB: if set, use the SPI library bundled with Arduino IDE, otherwise
   run with a standalone driver for AVR.
*/
#define USE_SPI_LIB
/**
   Define MEGA_SOFT_SPI non-zero to use software SPI on Mega Arduinos.
   Pins used are SS 10, MOSI 11, MISO 12, and SCK 13.

   MEGA_SOFT_SPI allows an unmodified Adafruit GPS Shield to be used
   on Mega Arduinos.  Software SPI works well with GPS Shield V1.1
   but many SD cards will fail with GPS Shield V1.0.
*/
#define MEGA_SOFT_SPI 0 //XW
//------------------------------------------------------------------------------
#if MEGA_SOFT_SPI && (defined(__AVR_ATmega1280__)||defined(__AVR_ATmega2560__))
  #define SOFTWARE_SPI
#endif  // MEGA_SOFT_SPI
//------------------------------------------------------------------------------
// SPI pin definitions
//
#ifndef SOFTWARE_SPI
  // hardware pin defs

  • Après
/**
   USE_SPI_LIB: if set, use the SPI library bundled with Arduino IDE, otherwise
   run with a standalone driver for AVR.
*/
//#define USE_SPI_LIB
/**
   Define MEGA_SOFT_SPI non-zero to use software SPI on Mega Arduinos.
   Pins used are SS 10, MOSI 11, MISO 12, and SCK 13.

   MEGA_SOFT_SPI allows an unmodified Adafruit GPS Shield to be used
   on Mega Arduinos.  Software SPI works well with GPS Shield V1.1
   but many SD cards will fail with GPS Shield V1.0.
*/
#define MEGA_SOFT_SPI 1
//------------------------------------------------------------------------------
#if MEGA_SOFT_SPI && (defined(__AVR_ATmega1280__)||defined(__AVR_ATmega2560__))
  #define SOFTWARE_SPI
#endif  // MEGA_SOFT_SPI
//------------------------------------------------------------------------------
// SPI pin definitions
//
#ifndef SOFTWARE_SPI
  #define USE_SPI_LIB

Fichier Sd2Card.cpp

Ce fichier se trouve dans le répertoire, C:\Program Files (x86)\Arduino\libraries\SD\src\utility

Ligne 20, commentez la commande #define USE_SPI_LIB. Puis placez cette ligne dans #if #endif après l’appel du fichier Sd2Card.h

  • Avant
#define USE_SPI_LIB
#include <Arduino.h>
#include "Sd2Card.h"

  • Après
//#define USE_SPI_LIB
#include <Arduino.h>
#include "Sd2Card.h"
//modif mega
#ifndef SOFTWARE_SPI  //Added to enable use of MEGA
 #define USE_SPI_LIB   
#endif  //SOFTWARE_SPI
//modif mega

Fichier SD.cpp

Ligne 356, modifiez la définition de la fonction SDClass::begin() pour retirer la ligne sdSpiClock.

  • Avant
  boolean SDClass::begin(uint32_t clock, uint8_t csPin) {
    if (root.isOpen()) {
      root.close();
    }

    return card.init(SPI_HALF_SPEED, csPin) &&
           card.setSpiClock(clock) &&
           volume.init(card) &&
           root.openRoot(volume);
  }

  • Après
#ifndef SOFTWARE_SPI   
  boolean SDClass::begin(uint32_t clock, uint8_t csPin) {
    return card.init(SPI_HALF_SPEED, csPin) &&
           card.setSpiClock(clock) &&
           volume.init(card) &&
           root.openRoot(volume);
  }
#else
  boolean SDClass::begin(uint32_t clock, uint8_t csPin) {
    return card.init(SPI_HALF_SPEED, csPin) &&
           volume.init(card) &&
           root.openRoot(volume);
  }
#endif  //SOFTWARE_SPI

Vous pouvez désormais faire fonctionner le module SD du shield TFT avec la carte Arduino Mega et utiliser les codes des tutoriels précédents.

N.B: N’oubliez pas de mettre MEGA_SOFT_SPI à 0 lorsque vous utilisez une autre carte Arduino.

Sources

Utilisation d’une Matrice de LED 8×8 avec Arduino

Utilisation d’une Matrice de LED 8×8 avec Arduino

La matrice de LED 8×8 comporte 8 lignes et 8 colonnes de LED contrôlables individuellement. Les matrices de LED peuvent être utiles pour des panneaux publicitaires, pour l’affichage de températures/l’heure ou tout autre information.

Matériel

  • Ordinateur
  • Arduino UNO
  • Câble USB A Mâle/B Mâle
  • Matrice de LED

Principe de fonctionnement

La module matrice de LED contient une platine de pilotage avec un registre MAX7219 qui permet de s’interfacer simplement avec un microcontrôleur tout en gérant l’état de 64 LEDs. Il existe plusieurs dimensions de matrice en fonction du nombre de registres à décalage utilisés.

Schéma

Le module matrice utilise 3 broches du microcontrôleur. Il peut être alimenté grâce à la sortie 5V du microcontrôleur.

Code

Pour gérer la matrice de LED, il n’y a pas de bibliothèque particulière; Il faut venir sélectionner l’état de la LED pour chaque pixel. Pour cela il est possible d’envoyer une valeur hexadécimale pour chaque ligne. Pour obtenir les valeur hexadécimales en fonction de ce que vous souhaitez dessiner, vous pouvez utiliser cet outil.

int DIN_pin = 11;
int CS_pin = 10;
int CLK_pin = 12;

int D[8] = {0xC0, 0xA0, 0x90, 0x88, 0x84, 0x98, 0x90, 0xE0}; //afficher la lettre D
int A[8] = {0x18, 0x24, 0x42, 0x42, 0x7E, 0x42, 0x42, 0x42}; //afficher la lettre A
int M[8] = {0xC3, 0xA5, 0x99, 0x99, 0x81, 0x81, 0x81, 0x81}; //afficher la lettre M
int I[8] = {0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C}; //afficher la lettre I
int E[8] = {0x00, 0x7E, 0x40, 0x40, 0x7E, 0x40, 0x40, 0x7E}; //afficher la lettre E
int N[8] = {0xC1, 0xA1, 0x91, 0x89, 0x85, 0x82, 0x80, 0x80}; //afficher la lettre N

void write_pix(int data)
{
  digitalWrite(CS_pin, LOW);
  for (int i = 0; i < 8; i++)
  {
    digitalWrite(CLK_pin, LOW);
    digitalWrite(DIN_pin, data & 0x80); // masquage de donnée
    data = data << 1; // on décale les bits vers la gauche
    digitalWrite(CLK_pin, HIGH);
  }
}

void write_line(int adress, int data)
{
  digitalWrite(CS_pin, LOW);
  write_pix(adress);
  write_pix(data);
  digitalWrite(CS_pin, HIGH);
}

void write_matrix(int *tab)
{
  for (int i = 0; i < 8; i++) write_line(i + 1, tab[i]);
}
void init_MAX7219(void)
{
  write_line(0x09, 0x00); //decoding BCD
  write_line(0X0A, 0x01); //brightness
  write_line(0X0B, 0x07); //scanlimit 8leds
  write_line(0X0C, 0x01); //power-down mode 0, normalmode1;
  write_line(0X0F, 0x00);
}

void clear_matrix(void)
{
  const int clean[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  write_matrix(clean);
}

int intToHex(int x)
{
  switch (x)
  {
    case 0: return 0x01; break; //LED sur la première case
    case 1: return 0x02; break; //LED sur 2 case
    case 2: return 0x04; break; //LED sur 3 case
    case 3: return 0x08; break; //LED sur 4 case
    case 4: return 0x10; break; //LED sur 5 case
    case 5: return 0x20; break; //LED sur 6 case
    case 6: return 0x40; break; //LED sur 7 case
    case 7: return 0x80; break; //LED sur 8 case
  }
}

void setup()
{
  pinMode(CS_pin, OUTPUT);
  pinMode(DIN_pin, OUTPUT);
  pinMode(CLK_pin, OUTPUT);
  delay(50);

  init_MAX7219();
  clear_matrix();

}

void loop()
{
  write_matrix(D);
  delay(500);
  write_matrix(A);
  delay(500);
  write_matrix(M);
  delay(500);
  write_matrix(I);
  delay(500);
  write_matrix(E);
  delay(500);
  write_matrix(N);
  delay(500);
}

Dans cet exemple, nous avons décrit le prénom “Damien” avec les valeurs hexadécimales pour chaque LED. A vous de définir les valeurs pour pouvoir afficher les caractères alphabétiques, numériques ou tout autre dessin que vous souhaitez.

Applications

  • Créer une interface graphique avec plusieurs matrices
  • Créer un jeu vidéo avec un écran simple

Sources

Utilisation du module SD du shield TFT avec Arduino Mega

Utilisation du Shield TFT et son module de carte SD

Le Shield TFT est généralement fourni avec un module de carte SD pour enregistrer des données ou des images. Les écran LCD tactiles permettant d’afficher des images et de créer des interfaces graphiques utilisateurs. Dans ce tutoriel, nous utilisons le shield Kuman TFT 2.8″ (très proche du shield 3.5″) et nous allons voir comment s’interfacer avec la carte microSD.

N.B.: Même si le shield est compatible avec la carte Arduino Mega, le module SD n’est pas utilisable directement.

Matériel

  • Ordinateur
  • Arduino UNO
  • Câble USB A Mâle/B Mâle
  • TFT LCD Shield muni d’un module SD
  • Carte microSD contenant une image bitmap au dimensions de l’écran

Schéma

Le shield se place directement sur une carte Arduino UNO ou Mega. Le shield utilise quasiment toutes les broches de l’Arduino UNO. Assurez-vous de ne pas utiliser les mêmes pour d’autres modules. Le module de carte SD du shield TFT utilise le bus SPI et la broche de sélection 10.

Code

Pour utiliser l’objet le shield TFT et notamment la communication avec la carte SD, nous utilisons les librairies SD.h, Dans cet exemple, nous allons récupérer la liste des fichiers contenus sur la carte SD et l’afficher sur l’écran LCD

//Libraries
#include <SD.h>//https://www.arduino.cc/en/reference/SD
#include <Adafruit_GFX.h>//https://github.com/adafruit/Adafruit-GFX-Library
#include <MCUFRIEND_kbv.h>//https://github.com/prenticedavid/MCUFRIEND_kbv
#include <TouchScreen.h> //https://github.com/adafruit/Adafruit_TouchScreen

//Constants
#define SD_CS 10
#define BLACK 0
#define GREY 21845
#define BLUE 31
#define RED 63488
#define GREEN 2016
#define DARKGREEN 1472
#define CYAN 2047
#define MAGENTA 63519
#define YELLOW 65504
#define GOLD 56768
#define WHITE 65535

//Touch screen configuration
#define MINPRESSURE 200
#define MAXPRESSURE 1000
// ALL Touch panels and wiring is DIFFERENT
// copy-paste results from TouchScreen_Calibr_native.ino
//3.5 Parameters
//const int XP = 8, XM = A2, YP = A3, YM = 9; //320x480 ID=0x9486
//const int TS_LEFT = 144, TS_RT = 887, TS_TOP = 936, TS_BOT = 87;
//2.8 Parameters
const int XP = 8, XM = A2, YP = A3, YM = 9; //240x320 ID=0x9341
const int TS_LEFT = 907, TS_RT = 120, TS_TOP = 74, TS_BOT = 913;

TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
TSPoint p;
bool down;
int pixel_x, pixel_y;     //Touch_getXY() updates global vars

//Variables
int currentPage  = 0, oldPage = -1;

int nbFiles = 0;
String fileList[10];
//Objects
MCUFRIEND_kbv tft;

// Button calibration
Adafruit_GFX_Button page1_btn, page2_btn;
int margin = 5;
int btnWidth = 100;
int btnHeight = 40;
int btnY = 200;

void setup() {
  //Init Serial USB
  Serial.begin(9600);
  Serial.println(F("Initialize System"));
  //Init tft screen
  uint16_t ID = tft.readID();
  if (ID == 0xD3D3) ID = 0x9486;  //for 3.5" TFT LCD Shield , 0x9341 for 2.8" TFT LCD Shield
  tft.begin(ID);
  tft.setRotation(1);//0-PORTRAIT 1-PAYSAGE 2-REVERSE PORTRAIT 3-REVERSE PAYSAGE
  //Uncomment if you are using SD
  if (!SD.begin(SD_CS)) {
    Serial.println(F("initialization failed!"));
    return;
  }
  currentPage = 0; // Indicates that we are at Home Screen
}

void loop() {
  down = Touch_getXY();
  switch (currentPage) {
    case 0:
      if (currentPage != oldPage) {
        Serial.println(F("Draw list"));
        drawList();
      }
      if (down) {
        currentPage = 1;
      }
      break;

    case 1:
      if (currentPage != oldPage) {
        tft.fillScreen(BLACK);
        delay(200);
        oldPage = currentPage;
      }
      currentPage = 0;
      break;
  }
}

void drawList() { /* function drawHomePage */
  getFilenames();
  tft.setRotation(1);
  tft.fillScreen(BLACK);
  //Title
  tft.setCursor(0, 10);
  tft.setTextSize(3);
  tft.setTextColor(WHITE, BLACK);
  tft.print("File list"); // Prints the string on the screen
  tft.drawLine(0, 32, 319, 32, DARKGREEN); // Draws the red line


  //text
  tft.setTextSize(2);
  tft.setTextColor(RED, BLACK);

  for (int i = 0; i < nbFiles; i++) {
    tft.setCursor(10, 40 + 20 * i);
    tft.print(fileList[i]);
  }
  oldPage = currentPage;
}

/************************************************************************************
    UTILITY FUNCTION
*************************************************************************************/
bool Touch_getXY(void)
{
  p = ts.getPoint();
  pinMode(YP, OUTPUT);      //restore shared pins
  pinMode(XM, OUTPUT);
  digitalWrite(YP, HIGH);
  digitalWrite(XM, HIGH);
  bool pressed = (p.z > MINPRESSURE && p.z < MAXPRESSURE);
  if (pressed) {
    if (tft.width() <= tft.height()) { //Portrait
      pixel_x = map(p.x, TS_LEFT, TS_RT, 0, tft.width()); //.kbv makes sense to me
      pixel_y = map(p.y, TS_TOP, TS_BOT, 0, tft.height());
    } else {
      pixel_x = map(p.y, TS_TOP, TS_BOT, 0, tft.width());
      pixel_y = map(p.x, TS_RT, TS_LEFT, 0, tft.height());
    }
  }
  return pressed;
}


File root;
void getFilenames(void ) { /* function printFilenames */
  ////find files in SD card
  root = SD.open("/");
  int i = 0;
  while (true) {
    File entry =  root.openNextFile();
    if (! entry) {
      break;// no more files
    }
    fileList[i] = entry.name();
    i++;
    Serial.println(entry.name());
    entry.close();
  }
  nbFiles = i;
}

Bonus: Afficher des images BMP à partir de la carte SD

L’intérêt prinicipale du module MicroSD sur le shield TFT est de pouvoir stocker des images afin de les afficher sur l’écran. Si vous n’avez pas d’image bitmap sous la main, vous pouvez télécharger celle-ci que nous utilisons dans cet exemple.

Pour afficher une image bitmap, il nous faut utiliser une focntion particulière que vous pouvez réutiliser dans votre code tel quel.

#define BUFFPIXEL 20           //Drawing speed, 20 is meant to be the best but you can use 60 altough it takes a lot of uno's RAM         
void bmpDraw(char *filename, int x, int y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel in buffer (R+G+B per pixel)
  uint16_t lcdbuffer[BUFFPIXEL];  // pixel out buffer (16-bit per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();
  uint8_t  lcdidx = 0;
  boolean  first = true;

  if((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  progmemPrint(PSTR("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');
  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    progmemPrintln(PSTR("File not found"));
    return;
  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    progmemPrint(PSTR("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    progmemPrint(PSTR("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    progmemPrint(PSTR("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      progmemPrint(PSTR("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        progmemPrint(PSTR("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // For each scanline...
          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col=0; col<w; col++) { // For each column...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              // Push LCD buffer to the display first
              if(lcdidx > 0) {
                tft.pushColors(lcdbuffer, lcdidx, first);
                lcdidx = 0;
                first  = false;
              }
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            lcdbuffer[lcdidx++] = tft.color565(r,g,b);
          } // end pixel
        } // end scanline
        // Write any remaining data to LCD
        if(lcdidx > 0) {
          tft.pushColors(lcdbuffer, lcdidx, first);
        } 
        progmemPrint(PSTR("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) progmemPrintln(PSTR("BMP format not recognized."));
}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

// Copy string from flash to serial port
// Source string MUST be inside a PSTR() declaration!
void progmemPrint(const char *str) {
  char c;
  while(c = pgm_read_byte(str++)) Serial.print(c);
}

// Same as above, with trailing newline
void progmemPrintln(const char *str) {
  progmemPrint(str);
  Serial.println();
}

Nous allons créer un bouton pour chaque fichier et a l’aide de la fonction précédente, nous allons afficher les images contenues sur la carte SD lorsque nous appuyons sur le bouton correspondant.

//Libraries
#include <SD.h>//https://www.arduino.cc/en/reference/SD
#include <Adafruit_GFX.h>//https://github.com/adafruit/Adafruit-GFX-Library
#include <MCUFRIEND_kbv.h>//https://github.com/prenticedavid/MCUFRIEND_kbv
#include <TouchScreen.h> //https://github.com/adafruit/Adafruit_TouchScreen

//Constants
#define SD_CS 10
#define BLACK 0
#define GREY 21845
#define BLUE 31
#define RED 63488
#define GREEN 2016
#define DARKGREEN 1472
#define CYAN 2047
#define MAGENTA 63519
#define YELLOW 65504
#define GOLD 56768
#define WHITE 65535

//Touch screen configuration
#define MINPRESSURE 200
#define MAXPRESSURE 1000
// ALL Touch panels and wiring is DIFFERENT
// copy-paste results from TouchScreen_Calibr_native.ino
//3.5 Parameters
//const int XP = 8, XM = A2, YP = A3, YM = 9; //320x480 ID=0x9486
//const int TS_LEFT = 144, TS_RT = 887, TS_TOP = 936, TS_BOT = 87;
//2.8 Parameters
const int XP = 8, XM = A2, YP = A3, YM = 9; //240x320 ID=0x9341
const int TS_LEFT = 907, TS_RT = 120, TS_TOP = 74, TS_BOT = 913;

TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
TSPoint p;
bool down;
int pixel_x, pixel_y;     //Touch_getXY() updates global vars

//Variables
int currentPage  = 0, oldPage = -1;

int nbFiles = 0;
String fileList[10];
const int charBuff=50;
char charCopy[50];

//Objects
MCUFRIEND_kbv tft;

// Button calibration
Adafruit_GFX_Button fileBtn[10];

int margin = 5;
int btnWidth = 100;
int btnHeight = 40;
int btnY = 200;

void setup() {
  //Init Serial USB
  Serial.begin(9600);
  Serial.println(F("Initialize System"));
  //Init tft screen
  uint16_t ID = tft.readID();
  if (ID == 0xD3D3) ID = 0x9486;  //for 3.5" TFT LCD Shield , 0x9341 for 2.8" TFT LCD Shield
  tft.begin(ID);
  tft.setRotation(1);//0-PORTRAIT 1-PAYSAGE 2-REVERSE PORTRAIT 3-REVERSE PAYSAGE
  //Uncomment if you are using SD
  if (!SD.begin(SD_CS)) {
    Serial.println(F("initialization failed!"));
    return;
  }
  currentPage = 0; // Indicates that we are at Home Screen
}

void loop() {
  down = Touch_getXY();
  switch (currentPage) {
    case 0:
      if (currentPage != oldPage) {
        Serial.println(F("Draw list"));
        drawList();
      }
      oldPage = currentPage;
      for(int i=1;i<nbFiles;i++){
        fileBtn[i].press(down && fileBtn[i].contains(pixel_x, pixel_y));
        if (fileBtn[i].justReleased()) fileBtn[i].drawButton();

        if (fileBtn[i].justPressed()) {
          fileBtn[i].drawButton(true);
          fileList[i].toCharArray(charCopy,charBuff);
          bmpDraw(charCopy, 0, 0);
          currentPage = 1;
        }
      }
      
      
      break;

    case 1:
      oldPage=currentPage;
      if (down) {
        currentPage = 0;
      }
      break;
  }
}

void drawList() { /* function drawHomePage */
  getFilenames();
  tft.setRotation(1);
  tft.fillScreen(BLACK);
  //Title
  tft.setCursor(0, 10);
  tft.setTextSize(3);
  tft.setTextColor(WHITE, BLACK);
  tft.print("File list"); // Prints the string on the screen
  tft.drawLine(0, 32, 319, 32, DARKGREEN); // Draws the red line


  //text
  tft.setTextSize(2);
  tft.setTextColor(RED, BLACK);

  for (int i = 1; i < nbFiles; i++) {
    //tft.setCursor(10, 40 + 20 * i);
    //tft.print(fileList[i]);

    // Button
    fileList[i].toCharArray(charCopy,charBuff);
    fileBtn[i].initButton(&tft,  tft.width() / 2. , 45+ i*(20+5), 2 * 100, 20, WHITE, GREEN, BLACK, charCopy, 2);
    fileBtn[i].drawButton(false);
  }
}

/************************************************************************************
    UTILITY FUNCTION
*************************************************************************************/
bool Touch_getXY(void)
{
  p = ts.getPoint();
  pinMode(YP, OUTPUT);      //restore shared pins
  pinMode(XM, OUTPUT);
  digitalWrite(YP, HIGH);
  digitalWrite(XM, HIGH);
  bool pressed = (p.z > MINPRESSURE && p.z < MAXPRESSURE);
  if (pressed) {
    if (tft.width() <= tft.height()) { //Portrait
      pixel_x = map(p.x, TS_LEFT, TS_RT, 0, tft.width()); //.kbv makes sense to me
      pixel_y = map(p.y, TS_TOP, TS_BOT, 0, tft.height());
    } else {
      pixel_x = map(p.y, TS_TOP, TS_BOT, 0, tft.width());
      pixel_y = map(p.x, TS_RT, TS_LEFT, 0, tft.height());
    }
  }
  return pressed;
}


File root;
void getFilenames(void ) { /* function printFilenames */
  ////find files in SD card
  root = SD.open("/");
  int i = 0;
  while (true) {
    File entry =  root.openNextFile();
    if (! entry) {
      break;// no more files
    }
    fileList[i] = entry.name();
    i++;
    Serial.println(entry.name());
    entry.close();
  }
  nbFiles = i;
  root.close();
}

#define BUFFPIXEL 20           //Drawing speed, 20 is meant to be the best but you can use 60 altough it takes a lot of uno's RAM         
void bmpDraw(char *filename, int x, int y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel in buffer (R+G+B per pixel)
  uint16_t lcdbuffer[BUFFPIXEL];  // pixel out buffer (16-bit per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();
  uint8_t  lcdidx = 0;
  boolean  first = true;

  if((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  progmemPrint(PSTR("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');
  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    progmemPrintln(PSTR("File not found"));
    return;
  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    progmemPrint(PSTR("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    progmemPrint(PSTR("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    progmemPrint(PSTR("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      progmemPrint(PSTR("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        progmemPrint(PSTR("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // For each scanline...
          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col=0; col<w; col++) { // For each column...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              // Push LCD buffer to the display first
              if(lcdidx > 0) {
                tft.pushColors(lcdbuffer, lcdidx, first);
                lcdidx = 0;
                first  = false;
              }
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            lcdbuffer[lcdidx++] = tft.color565(r,g,b);
          } // end pixel
        } // end scanline
        // Write any remaining data to LCD
        if(lcdidx > 0) {
          tft.pushColors(lcdbuffer, lcdidx, first);
        } 
        progmemPrint(PSTR("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) progmemPrintln(PSTR("BMP format not recognized."));
}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

// Copy string from flash to serial port
// Source string MUST be inside a PSTR() declaration!
void progmemPrint(const char *str) {
  char c;
  while(c = pgm_read_byte(str++)) Serial.print(c);
}

// Same as above, with trailing newline
void progmemPrintln(const char *str) {
  progmemPrint(str);
  Serial.println();
}

Une fois le code téléversé, un menu va s’afficher avec un bouton pour chaque fichier contenus sur la carte SD. Si vous appuyé sur un bouton, l’image bitmap correspondante s’affichera sur l’écran. Si vous appuyé de nouveau sur l’écran vous revenez au menu principal.

Applications

  • Créer une interface graphique avec des images

Sources

Retrouvez nos tutoriels et d’autres exemples dans notre générateur automatique de code
La Programmerie

Utilisation d’un afficheur OLED 0.91in avec Arduino

Utilisation d’un afficheur OLED 0.91in avec Arduino

L’afficheur OLED 0.91in est un afficheur graphique compact avec une résolution de 128×32 pixels qui permet de dessiner et d’afficher du texte afin de créer une interface graphique. Parmi tous les écrans disponible pour l’Arduino, l’écran OLED prend de plus en plus de place sur le marché. Étant donné qu’ils sont plus fins car ne nécessitent pas de rétro-éclairages, ils sont facile à insérer dans des projets miniaturisés.

Matériel

  • Ordinateur
  • Arduino UNO
  • Câble USB A Mâle/B Mâle
  • OLED 0.91in

Principe de fonctionnement

L’afficheur OLED 0.91in est un écran monochrome piloté par le circuit intégré SSD1306 et s’interface à l’aide de la communication I2C.

Schéma

L’écran OLED présente 4 broches pour permettre la gestion de l’affichage. Il est alimenté par le microcontrôleur et se connecte au bus I2C.

  • GND Relier à la masse du microcontrôleur
  • VCC Broche d’alimentation. Typiquement connectée à la broche 3V ou 5V du microcontrôleur.
  • SCL Horloge de la communication I2C
  • SDA Données de la connexion I2C.

Code

Une fois votre afficheur OLED correctement branché, vous pouvez modifier le code suivant pour obtenir la fonctionnalité désirée. Dans l’exemple suivant, nous allons simplement réaliser l’affichage d’un compteur.
Pour gérer l’écran OLED, les librairies utilisées sont Adafruit_GFX.h et Adafruit_SSD1306.h dont les fonctions à connaître sont les suivantes:

  • Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &Wire); pour définir l’écran en fonction du type
  • display.begin(SSD1306_SWITCHCAPVCC, 0x3C); pour initialiser l’écran
  • display.setTextSize() pour définir la taille du texte
  • display.clearDisplay(); pour effacer ce qui est affiché sur l’écran
  • display.display(); pour rafraichir l’écran
  • display.setCursor() pour placer le curseur
  • display.print() et display.println() pour afficher du texte

D’autres fonctions plus spécifiques existent pour dessiner des rectangles, des lignes ou encore pour afficher des images. Lorsque vous maitriserez ces fonctions, il vous sera facile d’afficher ce que vous voulez.

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &Wire);

byte compteur;

void setup() {
  Serial.begin(9600);

  Serial.println("OLED intialized");
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Address 0x3C for 128x32

  display.display();
  delay(1000);

  // Clear the buffer.
  display.clearDisplay();
  display.display();

  // text display tests
  display.setTextSize(1);
  display.setTextColor(WHITE);
}

void loop() {


  for (compteur = 0 ; compteur < 25 ; compteur++) {
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println("test compteur");
    display.print("compteur: ");
    display.println(compteur);
    display.display();
    delay(1000);
  }
}

Résultat

Une fois le code chargé, observez bien où sont placées les chaînes de caractères affichées et jouez avec les paramètres d’entrées des fonctions pour comparer leurs effets. Cela vous permettra d’avoir une meilleur compréhension de la librairie et des fonctionnalités du module Oled.

Applications

  • Fabriquer une horloge à l’aide d’un module RTC
  • Créer un menu interactif avec un encodeur rotatif

Sources

Retrouvez nos tutoriels et d’autres exemples dans notre générateur automatique de code
La Programmerie