Radio FM étanche pour douche : Arduino Nano, RDA5807, écran Nokia 5110 et PAM8403
Composants et fournitures
- Arduino Nano R3 × 1
- Module radio FM RDA5807 × 1
- Écran LCD Nokia 5110 (version 3,3–5 V) × 1
- Module RTC Tiny (DS1307 + EEPROM 24C32) × 1
- Amplificateur classe D PAM8403 (2×3 W) × 1
- Convertisseur abaisseur LM2596 (DC-DC) × 1
- Batterie rechargeable LIR2032 3,6 V × 1
- Résistances : 100 Ω, 1 kΩ, 10 kΩ ×6, 100 kΩ × 1
- MOSFET de puissance IRLZ44 × 1
- Haut-parleurs PEIYING PY1010C 60 W 4 Ω × 1
- Alimentation 12 V 3 A (récupérée) × 1
- Condensateurs 2200 µF 25 V × 2
- Condensateur 100 µF × 1
- LED 3 W (remplace halogène G4 10 W) × 1
À propos de ce projet
Genèse
Mon ancienne cabine de douche intégrait un système de commande TR028 avec radio. Un jour, il a rendu l'âme, irréparable. J'ai acheté une radio de douche bon marché : elle a duré un an. Ma famille étant habituée à la radio sous la douche, j'ai étudié le TR028 : il contenait un module TEA5767. Mes recherches m'ont mené au RDA5807, très proche mais avec RDS, contrôle de volume et bass boost. C'était mon choix.
Ma vision
- Interface tactile pour étanchéité totale (réutilisation du panneau tactile du TR028 mort).
- Radio FM avec RDS sur deux lignes.
- 6 présélections de stations favorites.
- Recherche automatique et manuelle de stations.
- Mémorisation d'une station trouvée sur l'une des 6 présélections.
- Contrôle du volume et du bass boost.
- Horloge et calendrier.
- Indicateur de force du signal (RSSI) et mode stéréo.
- Commande de l'éclairage de la cabine.
- Fonction marche/arrêt (veille).
Ce que j'ai obtenu
- Radio FM avec RDS sur 2 lignes.
- 6 présélections.
- Accord auto/manuel.
- Mémorisation sur présélection.
- Volume et bass boost.
- Commande éclairage cabine.
- Horloge/calendrier.
- RSSI, indicateur stéréo, marche/arrêt.
Fonctionnement
- Mise sous tension : la radio ne démarre pas automatiquement (évite les démarrages intempestifs). Appuyer sur la touche Power après quelques secondes. Elle reprend la dernière station à volume 03, mode VOLUME. Appuyer à nouveau sur Power pour éteindre (écran, rétroéclairage, ampli, lampe coupés).
- Recherche de station : touche « Mod » pour basculer Auto/Manuel. Touches « < » / « > » pour chercher vers le bas/haut. Touche « Mem » + sélection d'une présélection (1–6) dans les 4 s pour mémoriser.
- Date : touche « I » (Info) affiche la date 4 s.
- Réglage heure : maintenir « D » ≥ 2 s au signal horaire (top horaire). Relâcher → règle sur hh:00:00. Si l'horloge avait 15–1 min de retard : minutes/secondes à 00, heures +1. Si 1–15 min d'avance : minutes/secondes à 00.
Améliorations envisagées
- Résonateur du RTC pour meilleure précision (la correction horaire compense).
- Écran plus grand et lumineux (1,8" ou 2,0" couleur TFT) : le Nokia 5110 est petit et peu lisible.
- Ampli plus puissant : PAM8610 (2×15 W) ou TDA7297.
Conclusion
Projet fonctionnel depuis 1 mois, seul le dérèglement de l'horloge est notable. Le code perfectible (premier projet Arduino partagé). Questions bienvenues en commentaires.
Maj 1 : fusible 2 A sur ligne 12 V (sécurité). Ligne 586 ajoutée : menu = 1; pour revenir en mode VOLUME après Power.
Maj 2 : l'écran LCD montre des signes de fin de vie. Recherche d'alternative (1,8" TFT envisagé, mais plus gourmand en ressources).
Code
Sketch principal (Arduino)
// Radio FM douche Arduino Nano - RDA5807M, RTC, EEPROM, LCD5110, Thermistance
#include <LCD5110_Graph.h>
#include <AT24CX.h>
#include <Wire.h>
#include <RDSParser.h>
#include <radio.h>
#include <RTClib.h>
#include <RDA5807M.h>
#include <Keypad.h>
#define MAXmenu 4
#define ledPin 13
#define blPin 7
char keys[7][2] = {
{'L','P'}, {'I','D'}, {'1','2'}, {'3','4'}, {'5','6'}, {'M','m'}, {'<','>'}
};
byte rowPins[7] = {11,12,10,17,9,16,8};
byte colPins[2] = {15,14};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, 7, 2);
boolean bass = 0, dspl = 0, memdisplay = 0, mempress = 0, adj = 0;
boolean ledPin_state, power_state;
int menu, volume, volumeOld = 5, frequency, frequencyOld;
int txtl = 0, temparray = 0;
int samples[5];
unsigned int status[6];
unsigned long timeprevious = 0, timeprev = 0;
AT24CX mem;
RTC_DS1307 rtc;
LCD5110 lcd(6,5,4,2,3);
RDA5807M radio;
RDSParser rds;
extern unsigned char SmallFont[];
extern uint8_t signal5[]; extern uint8_t signal4[]; extern uint8_t signal3[];
extern uint8_t signal2[]; extern uint8_t signal1[];
void setup() {
analogReference(EXTERNAL);
Serial.begin(9600);
Wire.begin();
radio.init(); radio.debugEnable();
lcd.InitLCD(); lcd.clrScr(); lcd.setFont(SmallFont); lcd.enableSleep();
power_state = 0;
keypad.addStatedEventListener(keypadEvent);
keypad.setHoldTime(1500);
pinMode(ledPin, OUTPUT); pinMode(blPin, OUTPUT);
digitalWrite(ledPin, LOW); digitalWrite(blPin, LOW);
ledPin_state = digitalRead(ledPin);
frequency = mem.readInt(201);
volume = 2; menu = 1;
if (volume < 0) volume = 0; if (volume > 15) volume = 15;
if (frequency < 0) frequency = 0; if (frequency > 210) frequency = 210;
WriteReg(0x02, 0xC00d);
radio.attachReceiveRDS(RDS_process);
rds.attachServicenNameCallback(DisplayServiceName);
rds.attachTextCallback(DisplayText);
}
void loop() {
if (frequency != frequencyOld) { frequencyOld = frequency; mem.writeInt(201, frequency); canal(frequency); }
if (volume != volumeOld) { volumeOld = volume; WriteReg(5, 0x84D0 | volume); }
char key = keypad.getKey();
radio.checkRDS();
// Température (moyenne sur 5 lectures toutes les 0,6 s)
unsigned long timenow = millis();
if ((unsigned long)(timenow - timeprevious) > 600) {
timeprevious = timenow;
samples[temparray] = analogRead(A7);
temparray++;
}
if (temparray == 5) {
float average = 0;
for (int i=0;i<5;i++) average += samples[i];
printTemp(average);
temparray = 0;
}
// Timeout 4 s pour affichage MEM
unsigned long dabar = millis();
if (mempress == 1) { timeprev = dabar; memdisplay = 1; mempress = 0; }
if (memdisplay == 1) {
if ((unsigned long)(dabar - timeprev) < 4000) memdisplay = 1; else memdisplay = 0;
}
// Ajustement RTC via Serial (hXX, mXX, sXX)
if (Serial.available() > 0) {
char t = Serial.read();
DateTime now = rtc.now();
switch (t) {
case 'h': { unsigned int h = Serial.parseInt(); rtc.adjust(DateTime(now.year(),now.month(),now.day(),h,now.minute(),now.second())); Serial.println("Hours"); Serial.println(h); break; }
case 'm': { unsigned int m = Serial.parseInt(); rtc.adjust(DateTime(now.year(),now.month(),now.day(),now.hour(),m,now.second())); Serial.println("Minutes"); Serial.println(m); break; }
case 's': { unsigned int s = Serial.parseInt(); rtc.adjust(DateTime(now.year(),now.month(),now.day(),now.hour(),now.minute(),s)); Serial.println("Seconds"); Serial.println(s); break; }
}
}
printSignalStrength(); printLines(); printTime(); printFreq(); printStereo(); printMode(); printMenu(); printDate();
lcd.update();
}
// --- Fonctions d'affichage ---
void printSignalStrength() {
unsigned int sig; Readstatus(); sig = status[1] / 1000;
if (sig <= 12) lcd.drawBitmap(1,1,signal1,17,6);
else if (sig <= 24) lcd.drawBitmap(1,1,signal2,17,6);
else if (sig <= 36) lcd.drawBitmap(1,1,signal3,17,6);
else if (sig <= 48) lcd.drawBitmap(1,1,signal4,17,6);
else lcd.drawBitmap(1,1,signal5,17,6);
}
void printLines() { lcd.drawLine(0,9,84,9); lcd.drawLine(0,39,84,39); }
void printTemp(float avg) {
avg /= 5; avg = 1023/avg - 1; avg = 51700/avg;
float steinhart = avg/50000; steinhart = log(steinhart); steinhart /= 3950;
steinhart += 1.0/(25+273.15); steinhart = 1.0/steinhart; steinhart -= 273.15;
int tmp = round(steinhart);
lcd.printNumI(tmp,60,1,2); lcd.print("~C",72,1);
}
void DisplayServiceName(char *name) { lcd.print(name,18,22); }
void DisplayText(char *text) { lcd.print(text,txtl,30); txtl -= 66; if (txtl == -396) txtl = 0; }
void printTime() { DateTime now = rtc.now(); lcd.printNumI(now.hour(),24,1,2,'0'); lcd.print(":",36,1); lcd.printNumI(now.minute(),42,1,2,'0'); }
void printDate() {
if (dspl == 1) { ClearRDS(); DateTime now = rtc.now();
lcd.printNumI(now.year(),12,22,4); lcd.print(".",36,22); lcd.printNumI(now.month(),42,22,2,'0'); lcd.print(".",54,22); lcd.printNumI(now.day(),60,22,2,'0');
int dw = now.dayOfTheWeek();
const char* days[] = {"Sekmadienis","Pirmadienis","Antradienis","Treciadienis","Ketvirtadienis","Penktadienis","Sestadienis"};
lcd.print(days[dw], CENTER, 30); lcd.update(); delay(4000); ClearRDS(); dspl = 0;
}
}
void printMode() { lcd.print("MODE ",0,41); }
void printMenu() {
if (menu==1) { lcd.print("VOLUME ",30,41); if (volume<0) lcd.print("XX",72,41); else lcd.printNumI(volume+1,72,41,2,'0'); }
if (menu==2) lcd.print("AUTO-TUNE",30,41);
if (menu==3) lcd.print("MAN.-TUNE",30,41);
if (menu==4) { lcd.print(" BASS ",30,41); if (bass==0) lcd.print("OFF",66,41); else lcd.print(" ON",66,41); }
}
void printFreq() {
int fr = 870 + frequency; int frH = fr/10; int frD = fr%10;
lcd.printNumI(frH,30,12,3); lcd.print(".",48,12); lcd.printNumI(frD,54,12,1); lcd.print("MHz",66,12);
}
void printStereo() {
if (memdisplay) lcd.print("MEM>",0,12);
else if ((status[0] & 0x0400) == 0) lcd.print("( )",0,12);
else lcd.print("(ST)",0,12);
}
// --- Radio / RDS ---
void search(byte direc) {
byte i; if (!direc) WriteReg(0x02,0xC30d); else WriteReg(0x02,0xC10d);
for (i=0;i<10;i++) { delay(200); Readstatus(); if (status[0] & 0x4000) { frequency = status[0] & 0x03ff; break; } }
}
void canal(int canal) {
byte nH = canal >> 2; byte nL = ((canal & 3) << 6) | 0x10;
Wire.beginTransmission(0x11); Wire.write(0x03); Wire.write(nH); Wire.write(nL); Wire.endTransmission();
}
int Readstatus() { Wire.requestFrom(0x10,12); for(int i=0;i<6;i++) status[i]=256*Wire.read()+Wire.read(); Wire.endTransmission(); }
void WriteReg(byte reg, unsigned int valor) { Wire.beginTransmission(0x11); Wire.write(reg); Wire.write(valor>>8); Wire.write(valor&0xFF); Wire.endTransmission(); }
void RDS_process(uint16_t b1,uint16_t b2,uint16_t b3,uint16_t b4) { rds.processData(b1,b2,b3,b4); }
void ClearRDS() { lcd.print(" ",0,22); lcd.print(" ",0,30); }
// --- Gestion clavier ---
void keypadEvent(KeypadEvent key, KeyState kpadState) {
switch (kpadState) {
case HOLD: if (key=='D' && power_state) adj=1; break;
case RELEASED:
if (key=='D' && adj) {
DateTime now = rtc.now();
if (now.minute()>=45 && now.minute()<=59) rtc.adjust(DateTime(now.year(),now.month(),now.day(),now.hour()+1,0,0));
if (now.minute()>=1 && now.minute()<=15) rtc.adjust(DateTime(now.year(),now.month(),now.day(),now.hour(),0,0));
adj=0;
} break;
case PRESSED:
if (key=='M' && power_state) { memdisplay=0; menu++; if (menu>MAXmenu) menu=1; }
if (key=='>' && power_state) { memdisplay=0;
switch(menu) {
case 1: if (volume<0) { if(bass==0) WriteReg(0x02,0xD00D); else WriteReg(0x02,0xC00D); volume=0; } else volume++; if (volume>15) volume=15; break;
case 2: search(0); ClearRDS(); break;
case 3: frequency++; if (frequency>210) frequency=210; ClearRDS(); break;
case 4: if (bass==0) { bass=1; WriteReg(0x02,0xD00D); } break;
}
}
if (key=='<' && power_state) { memdisplay=0;
switch(menu) {
case 1: volume--; if (volume<0) WriteReg(0x02,0x800D); break;
case 2: search(1); ClearRDS(); break;
case 3: frequency--; if (frequency<0) frequency=0; ClearRDS(); break;
case 4: if (bass==1) { bass=0; WriteReg(0x02,0xC00D); } break;
}
}
if (key=='L' && power_state) { digitalWrite(ledPin, !digitalRead(ledPin)); ledPin_state = digitalRead(ledPin); }
if (key=='P') { digitalWrite(blPin, !digitalRead(blPin)); power_state = digitalRead(blPin); if (power_state==0) { lcd.enableSleep(); digitalWrite(ledPin,LOW); } else lcd.disableSleep(); volume=2; menu=1; }
for (int p=1;p<=6;p++) {
if (key==char('0'+p) && power_state) {
int addr = 100 + p*10;
if (!memdisplay) { frequency = mem.readInt(addr); ClearRDS(); }
else { mem.writeInt(addr, frequency); memdisplay=0; }
}
}
if (key=='m' && power_state) mempress=1; else mempress=0;
if (key=='I' && power_state) dspl=1;
break;
}
}
Symboles de force du signal (C/C++)
#if defined(__AVR__) #include #define imagedatatype const uint8_t
#elif defined(__PIC32MX__) #define PROGMEM #define imagedatatype const unsigned char
#elif defined(__arm__) #define PROGMEM #define imagedatatype const unsigned char
#endif
imagedatatype signal5[] PROGMEM={0xC1,0xC2,0xC4,0xFF,0xC4,0xC2,0xC1,0xC0,0xE0,0xC0,0xF0,0xC0,0xF8,0xC0,0xFC,0xC0,0xFE};
imagedatatype signal4[] PROGMEM={0xC1,0xC2,0xC4,0xFF,0xC4,0xC2,0xC1,0xC0,0xE0,0xC0,0xF0,0xC0,0xF8,0xC0,0xFC,0xC0,0xC0};
imagedatatype signal3[] PROGMEM={0xC1,0xC2,0xC4,0xFF,0xC4,0xC2,0xC1,0xC0,0xE0,0xC0,0xF0,0xC0,0xF8,0xC0,0xC0,0xC0,0xC0};
imagedatatype signal2[] PROGMEM={0xC1,0xC2,0xC4,0xFF,0xC4,0xC2,0xC1,0xC0,0xE0,0xC0,0xF0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
imagedatatype signal1[] PROGMEM={0xC1,0xC2,0xC4,0xFF,0xC4,0xC2,0xC1,0xC0,0xE0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
Schéma
Schéma de câblage complet (image fournie).
Processus de fabrication
- L'évolution et l'ingénierie de la technologie radio
- Circuits intégrés (CI) : puces compactes et puissantes alimentant l'électronique moderne
- JX Wave Generator – Générateur DDS compatible Arduino avec affichage OLED
- MotionSense : Détection d'intrusion intelligente avec Arduino et ESP8266
- Construire une boîte à énigme IoT sécurisée avec Arduino MKR Bundle – Guide étape par étape
- Régulateur de douche intelligent – Électrovanne fiable pour contrôle précis de la température
- Scanner IR de température sans fil – Arduino Nano, Bluetooth & MLX90614
- Char autonome « WalaBeer » : Walabot, Arduino, Raspberry Pi & Alexa
- Kit MOSMusic : MOSFET, Arduino et plus pour éclairage musical