Luxmètre avec ESP32 Yellow Display
Le projet
L' ESP32 Yellow Display est un ESP32 avec un écran.
Il possède les extensions principales suivantes :
Ecran tactile 320 x 240 pixels de 2,8 ''
LED RVB et LDR embarquées
Lecteur de carte micro SD
Connecteur de Port sériel
Connecteur pour haut-parleur
Connecteur GPIO : P3 -> Gnd, GPIO 35,22,21
Connecteur GPIO : CN1 -> Gnd, GPIO 22, 27, 3,3V
Un connecteur micro USB : programmation
et alimentation 5V
La programmation se fait avec l'IDE d'Arduino et utilise les bibliothèques graphiques puissantes :
TFT_espi et LVGL.
J'ai déjà réalisé une station météo avec cette carte et j'avais envie de tester un autre projet. Pour 10 € j'ai commandé une carte sur AliExpress.
Comme j'avais dans mes tiroirs un capteur de lumière un BH1750, j'ai décidé de réaliser un luxmètre.
Les fichiers à télécharger
Le programme Arduino |
Les fichiers de fabrication |
|---|---|
La partie mécanique


Pour protéger le BH1750 j'ai réalisé un boîtier en PVC rigide. J'ai usiné un boîtier sur ma CNC.
Le brut a été coupé dans une barre de 60 x 20 mm d'une longueur suffisante pour permettre
de brider la pièce.
Une plaque de plastique transparente de 1 mm d'épaisseur forme un couvercle.
Ce boîtier peut très bien se réaliser avec une imprimante 3D.
Dans le fichier lux_pro.zip vous trouverez le fichier .stl pour l'imprimante 3D et le fichier .nc
pour les codes G.
On trouve aussi des BH1750 dans un boîtier plastique
semi-sphérique et translucide, mais je n'ai pas testé.
La réalisation

Le BH1750 est branché sur le connecteur CN1 comme sur le dessin de câblage.
Bizarre! La carte possède un connecteur supplémentaire P6 avec les GPIO
de la LED RVB et sur CN1 IO27 est marquée IO22.
Un petit programme de test pour voir si l'affichage fonctionne et rien ne se passe !
Après quelques recherches il s'agit bien d'un ESP32 Yellow Display à écran
non tactile. Le Backline défini dans le fichier User_Setup.h est sur la broche 27
et non la broche 22.
Avec une carte classique la ligne du fichier User_Setup.h est #define TFT_BL 22,
celle de ma carte
est #define TFT_BL 27.
Le fichier User_Setup.h se trouve dans le dossier librairies. Dans l'EDI d'Arduino
c'est le chemin indiqué par : Files -> Préférences -> SketchLocation.
Sur mon ordinateur :
C:\Users\JMDefais\Documents\Arduino\libraries\TFT_eSPI.
Avec une carte clasique la ligne du programme :
# define I2C_SDA 22 devient
# define I2C_SDA 27.
Le programme exploite la librairie LVGL qui permet de créer des boutons
et une animation sur un arc.
Tout le graphisme est défini dans la fonction void setup ().
La fonction void loop() mesure la lumière et rafraichit l'écran à intervalle de 0,5 s.


/*
Luxmeter graphics
Jmd february 2026
*/
#include < lvgl.h >
#include < TFT_eSPI.h >
#include < BH1750.h >
#include < Wire.h >
#define SDA 21
#define SCL 22
#define LUX_ARC_MIN 0
#define LUX_ARC_MAX 100
int luxmes;
BH1750 lightMeter;
lv_obj_t * arc;
lv_obj_t * label;
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];
// Create the global label
void create_label(lv_obj_t *parent)
{
label = lv_label_create(parent);
}
// Set the lux value in the arc and text label
static void set_lux(void * text_label_lux_value, int32_t v)
{
int lux = luxmes / 2;
if(luxmes <= 200)
{
lv_obj_set_style_text_color((lv_obj_t*) text_label_lux_value, lv_palette_main(LV_PALETTE_BLUE), 0);
}
else if (luxmes > 200 && luxmes <= 2000)
{
lux = luxmes / 20;
lv_obj_set_style_text_color((lv_obj_t*) text_label_lux_value, lv_palette_main(LV_PALETTE_ORANGE), 0);
}
else if (luxmes > 2000)
{
lux = luxmes / 200;
lv_obj_set_style_text_color((lv_obj_t*) text_label_lux_value, lv_palette_main(LV_PALETTE_RED), 0);
}
const char symbol[] = "Lux";
lv_arc_set_value(arc, map(int(lux), LUX_ARC_MIN, LUX_ARC_MAX, 0, 100));
String lux_text = String(luxmes) + " " + symbol;
lv_label_set_text((lv_obj_t*) text_label_lux_value, lux_text.c_str());
Serial.print("lumiere: ");
Serial.println(lux_text);
}
void lv_create_main_gui(void)
{
// Draw the widgets
lv_obj_t *screen = lv_scr_act();
create_label(screen);
// Create r1button
lv_obj_t * r1button = lv_button_create(lv_screen_active());
lv_obj_set_size(r1button, 110, 30);
lv_obj_align(r1button, LV_ALIGN_CENTER, 105, -110);
static lv_style_t style_btn1;
lv_style_init(&style_btn1);
lv_style_set_bg_color(&style_btn1, lv_color_make(0x00, 0x00, 0xff));
lv_obj_add_style(r1button, &style_btn1, 0);
lv_style_set_border_color(&style_btn1, lv_palette_main(LV_PALETTE_BLUE_GREY));
lv_style_set_border_width(&style_btn1, 5);
lv_style_set_border_opa(&style_btn1, LV_OPA_50);
lv_style_set_border_side(&style_btn1, (lv_border_side_t)(LV_BORDER_SIDE_BOTTOM | LV_BORDER_SIDE_RIGHT));
lv_obj_t * btn1_label =lv_label_create(r1button);
lv_style_set_text_font(&style_btn1, &lv_font_montserrat_14);
lv_label_set_text(btn1_label, "0-20000 Lux");
// Create label Luxmetre
lv_obj_t * r2button = lv_button_create(lv_screen_active());
lv_obj_set_size(r2button, 125, 30);
lv_obj_align(r2button, LV_ALIGN_CENTER, - 100, -110);
static lv_style_t style_btn2;
lv_style_init(&style_btn2);
lv_style_set_bg_color(&style_btn2, lv_color_make(0x00, 0x00, 0xff));
lv_obj_add_style(r2button, &style_btn2, 0);
lv_style_set_border_color(&style_btn2, lv_palette_main(LV_PALETTE_BLUE_GREY));
lv_style_set_border_width(&style_btn2, 5);
lv_style_set_border_opa(&style_btn2, LV_OPA_50);
lv_style_set_border_side(&style_btn2, (lv_border_side_t)(LV_BORDER_SIDE_BOTTOM | LV_BORDER_SIDE_RIGHT));
lv_obj_t * btn2_label =lv_label_create(r2button);
lv_style_set_text_font(&style_btn2, &lv_font_montserrat_22);
lv_label_set_text(btn2_label, "Luxmetre");
lv_obj_center(btn2_label);
// Create label range
lv_obj_t * labelr1 = lv_label_create(lv_screen_active());
lv_label_set_text(labelr1, "x10");
lv_obj_align(labelr1, LV_ALIGN_CENTER, - 130, 70);
static lv_style_t style_labelr1;
lv_style_init(&style_labelr1);
lv_style_set_text_font(&style_labelr1, &lv_font_montserrat_16);
lv_obj_add_style(labelr1, &style_labelr1, 0);
lv_obj_set_style_text_color(labelr1, lv_color_hex(0x0000ff), 0);
lv_obj_t * labelr2 = lv_label_create(lv_screen_active());
lv_label_set_text(labelr2, "x100");
lv_obj_align(labelr2, LV_ALIGN_CENTER, - 130, 90);
static lv_style_t style_labelr2;
lv_style_init(&style_labelr2);
lv_style_set_text_font(&style_labelr2, &lv_font_montserrat_16);
lv_obj_add_style(labelr2, &style_labelr2, 0);
lv_obj_set_style_text_color(labelr2, lv_color_hex(0xe66c37), 0);
lv_obj_t * labelr3 = lv_label_create(lv_screen_active());
lv_label_set_text(labelr3, "x1000");
lv_obj_align(labelr3, LV_ALIGN_CENTER, - 130, 110);
static lv_style_t style_labelr3;
lv_style_init(&style_labelr3);
lv_style_set_text_font(&style_labelr3, &lv_font_montserrat_16);
lv_obj_add_style(labelr3, &style_labelr3, 0);
lv_obj_set_style_text_color(labelr3, lv_color_hex(0xff0000), 0);
// Create label scale 0 to 20
lv_obj_t * label1 = lv_label_create(lv_screen_active());
lv_label_set_text(label1, "0");
lv_obj_align(label1, LV_ALIGN_CENTER, -60, 90);
static lv_style_t style_label1;
lv_style_init(&style_label1);
lv_style_set_text_font(&style_label1, &lv_font_montserrat_20);
lv_obj_add_style(label1, &style_label1, 0);
lv_obj_set_style_text_color(label1, lv_color_hex(0xff0000), 0);
lv_obj_t * label2 = lv_label_create(lv_screen_active());
lv_label_set_text(label2, "10");
lv_obj_align(label2, LV_ALIGN_CENTER, 0, -90);
static lv_style_t style_label2;
lv_style_init(&style_label2);
lv_style_set_text_font(&style_label2, &lv_font_montserrat_20);
lv_obj_add_style(label2, &style_label2, 0);
lv_obj_set_style_text_color(label2, lv_color_hex(0xff0000), 0);
lv_obj_t * label3 = lv_label_create(lv_screen_active());
lv_label_set_text(label3, "20");
lv_obj_align(label3, LV_ALIGN_CENTER, 60, 90);
static lv_style_t style_label3;
lv_style_init(&style_label3);
lv_style_set_text_font(&style_label3, &lv_font_montserrat_20);
lv_obj_add_style(label3, &style_label3, 0);
lv_obj_set_style_text_color(label3, lv_color_hex(0xff0000), 0);
// Create an Arc
arc = lv_arc_create(lv_screen_active());
lv_obj_set_size(arc, 180, 180);
lv_arc_set_rotation(arc, 135);
lv_arc_set_bg_angles(arc, 0, 270);
lv_obj_set_style_arc_color(arc, lv_color_hex(0x87CEFA), LV_PART_INDICATOR);
lv_obj_set_style_bg_color(arc, lv_color_hex(0x333333), LV_PART_KNOB);
lv_obj_align(arc, LV_ALIGN_CENTER, 0, 10);
// Create a text label in font size 24 to display the latest light reading
lv_obj_t * text_label_lux_value = lv_label_create(lv_screen_active());
lv_label_set_text(text_label_lux_value, "--.--");
lv_obj_align(text_label_lux_value, LV_ALIGN_CENTER, 0, 10);
static lv_style_t style_lux;
lv_style_init(&style_lux);
lv_style_set_text_font(&style_lux, &lv_font_montserrat_24);
lv_obj_add_style(text_label_lux_value, &style_lux, 0);
// Create an animation to update with the latest light value every 10 seconds
lv_anim_t a_lux;
lv_anim_init(&a_lux);
lv_anim_set_exec_cb(&a_lux, set_lux);
lv_anim_set_duration(&a_lux, 1000);
lv_anim_set_playback_duration(&a_lux, 1000);
lv_anim_set_var(&a_lux, text_label_lux_value);
lv_anim_set_values(&a_lux, 0, 100);
lv_anim_set_repeat_count(&a_lux, LV_ANIM_REPEAT_INFINITE);
lv_anim_start(&a_lux);
}
void setup() {
String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin(115200);
Wire.begin(SDA,SCL);
lightMeter.begin();
Serial.println(LVGL_Arduino);
// Start LVGL
lv_init();
// Create a display object
lv_display_t * disp;
// Initialize the TFT display using the TFT_eSPI library
disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);
// Function to draw the GUI
lv_create_main_gui();
}
void loop() {
luxmes = int(lightMeter.readLightLevel());
lv_task_handler(); // let the GUI do its work
lv_tick_inc(500); // tell LVGL how much time has passed
delay(500); // let this time pass
}