Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Водонепроницаемая FM-радио для душа на Arduino Nano, RDA5807, Nokia LCD и PAM8403

Компоненты и расходники

Arduino Nano R3×1
FM-модуль RDA5807 (RDA Microelectronics)×1
Дисплей Nokia 5110 (3V–5V версия)×1
Модуль реального времени Tiny RTC (DS1307 + 24C32 EEPROM)×1
Усилитель PAM8403 (2×3 В, класс D)×1
Понижающий преобразователь LM2596 (DC-DC)×1
Накопительная батарея LIR2032 3,6 В×1
Резисторы: 100 Ом, 1 кОм, 10 кОм (×6), 100 кОм—
МОП-транзистор IRLZ44×1
Динамики PEIYING PY1010C 60 Вт 4 Ом×1
Блок питания 12 В 3 А (из подручных материалов)×1
Конденсаторы 2200 мкФ 25 В×2
Конденсатор 100 мкФ×1
LED-лампы 3 Вт (замена галогенок G4 10 Вт)×1

О проекте

Несколько лет назад я купил китайскую душевую кабину с системой управления TR028, в которой был встроен rádio. Однажды система вышла из строя, её не смогли починить. Купил дешёвую душевую радио — прослужило год. Семья привыкла к радио в душе, поэтому я решил собрать своё, изучив устройство TR028. Внутри нашёл модуль TEA5767, но затем открыл для себя RDA5807 — он умеет RDS, регулировку громкости и бас. Именно его я выбрал.

Мой список желаний

Что получилось в итоге

Как это работает

Что бы я изменил

Заключение

Работает стабильно уже месяц, кроме точности часов. Код можно оптимизировать — я не профи, учусь год, это первый публичный проект. Вопросы — в комментариях или ЛС.

Обновление 1: добавлен предохранитель 2 А на линию 12 В. В код добавлена строка menu = 1; при включении (возврат в режим громкости).

Обновление 2: Nokia 5110 деградирует. Ищу замену — планирую 1,8″ TFT, но он жрёт ресурсы. Советы приветствуются.

Код (основной скетч)

// Arduino based shower FM Radio Project
// Arduino NANO, RDA5807M, RTC, EEPROM, LCD5110, Thermistor
#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'}, // Light, Power
  {'I', 'D'}, // Info, Date
  {'1', '2'}, // Presets 1-2
  {'3', '4'}, // Presets 3-4
  {'5', '6'}, // Presets 5-6
  {'M', 'm'}, // Mode, Mem
  {'<', '>'}  // Down, Up
};

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); // clk, din, dc, ce, rst
RDA5807M radio;
RDSParser rds;

extern unsigned char SmallFont[];
extern uint8_t signal5[], signal4[], signal3[], signal2[], 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.addEventListener(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); // soft reset, enable, RDS
  canal(frequency);
  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();

  // Температура каждые 0,6 с, усреднение по 5 замеров
  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;
  }

  // Таймаут 4 с для отображения 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;
  }

  // Serial-коррекция RTC (hXX, mXX, sXX)
  if (Serial.available() > 0) {
    char t = Serial.read();
    DateTime now = rtc.now();
    switch (t) {
      case 'h': {
        unsigned int hours = Serial.parseInt();
        rtc.adjust(DateTime(now.year(), now.month(), now.day(), hours, now.minute(), now.second()));
        Serial.println(F("Hours")); Serial.println(hours); break;
      }
      case 'm': {
        unsigned int mins = Serial.parseInt();
        rtc.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), mins, now.second()));
        Serial.println(F("Minutes")); Serial.println(mins); break;
      }
      case 's': {
        unsigned int sec = Serial.parseInt();
        rtc.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), now.minute(), sec));
        Serial.println(F("Seconds")); Serial.println(sec); break;
      }
    }
  }

  printSignalStrength();
  printLines();
  printTime();
  printFreq();
  printStereo();
  printMode();
  printMenu();
  printDate();
  lcd.update();
}

// --- Вспомогательные функции вывода на LCD ---
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 average) {
  average /= 5;
  average = 1023 / average - 1;
  average = 51700 / average;
  float steinhart = average / 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(F("°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 __FlashStringHelper* days[] = {F("Воскресенье"), F("Понедельник"), F("Вторник"), F("Среда"), F("Четверг"), F("Пятница"), F("Суббота")};
    lcd.print(days[dw], CENTER, 30);
    lcd.update(); delay(4000); ClearRDS(); dspl = 0;
  }
}
void printMode() { lcd.print(F("MODE "), 0, 41); }
void printMenu() {
  if (menu == 1) { lcd.print(F("VOLUME "), 30, 41); lcd.printNumI(volume + 1, 72, 41, 2, '0'); }
  else if (menu == 2) lcd.print(F("AUTO-TUNE"), 30, 41);
  else if (menu == 3) lcd.print(F("MAN.-TUNE"), 30, 41);
  else if (menu == 4) { lcd.print(F(" BASS "), 30, 41); lcd.print(bass ? F(" ON") : F("OFF"), 66, 41); }
}
void printFreq() {
  int fr = 870 + frequency;
  lcd.printNumI(fr / 10, 30, 12, 3);
  lcd.print(F("."), 48, 12);
  lcd.printNumI(fr % 10, 54, 12, 1);
  lcd.print(F("MHz"), 66, 12);
}
void printStereo() {
  if (memdisplay) lcd.print(F("MEM>"), 0, 12);
  else if ((status[0] & 0x0400) == 0) lcd.print(F("( )"), 0, 12);
  else lcd.print(F("(ST)"), 0, 12);
}

// --- Работа с RDA5807 ---
void search(byte direc) {
  WriteReg(0x02, direc ? 0xC10D : 0xC30D);
  for (byte i = 0; i < 10; i++) {
    delay(200); Readstatus();
    if (status[0] & 0x4000) { frequency = status[0] & 0x03FF; break; }
  }
}
void canal(int canal) {
  byte numberH = canal >> 2;
  byte numberL = ((canal & 3) << 6) | 0x10;
  Wire.beginTransmission(0x11);
  Wire.write(0x03); Wire.write(numberH); Wire.write(numberL);
  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); }

// --- Обработка клавиатуры ---
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) rtc.adjust(DateTime(now.year(), now.month(), now.day(), now.hour() + 1, 0, 0));
        else 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; if (++menu > MAXmenu) menu = 1; }
      if (key == '>' && power_state) {
        memdisplay = 0;
        switch (menu) {
          case 1: if (volume < 0) { WriteReg(0x02, bass ? 0xD00D : 0xC00D); volume = 0; } else volume++; if (volume > 15) volume = 15; break;
          case 2: search(0); ClearRDS(); break;
          case 3: if (++frequency > 210) frequency = 210; ClearRDS(); break;
          case 4: if (!bass) { bass = 1; WriteReg(0x02, 0xD00D); } break;
        }
      }
      if (key == '<' && power_state) {
        memdisplay = 0;
        switch (menu) {
          case 1: if (--volume < 0) WriteReg(0x02, 0x800D); break;
          case 2: search(1); ClearRDS(); break;
          case 3: if (--frequency < 0) frequency = 0; ClearRDS(); break;
          case 4: if (bass) { 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) { lcd.enableSleep(); digitalWrite(ledPin, LOW); } else lcd.disableSleep(); volume = 2; menu = 1; }
      for (char k = '1'; k <= '6'; k++) if (key == k && power_state) {
        int addr = 100 + (k - '0') * 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;
  }
}

Символы уровня сигнала (для LCD5110)

#if defined(__AVR__)
#include 
#define imagedatatype const uint8_t
#elif defined(__PIC32MX__) || 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 };

Схема подключения

Водонепроницаемая FM-радио для душа на Arduino Nano, RDA5807, Nokia LCD и PAM8403

Производственный процесс

  1. Эволюция и инженерия радиотехнологии: от Маркони до наших дней
  2. Интегральные схемы (ИС): компактные мощные чипы, питающие современную электронику
  3. JX Wave Generator — Генератор сигналов на Arduino с DDS AD9833 и OLED-дисплеем
  4. MotionSense: умная система обнаружения проникновения на Arduino и ESP8266
  5. Сборка защищённой IoT‑головоломки (Puzzle Box) на базе Arduino MKR Bundle
  6. Умный регулятор душа: электромагнитный клапан для точного контроля времени и экономии воды
  7. Беспроводной ИК-термометр на Arduino Nano с Bluetooth и MLX90614
  8. Автономный танк с доставкой пива: Walabot, Arduino, Raspberry Pi и Alexa
  9. Продвинутая лаборатория микроконтроллеров: ESP32, Arduino, PIC и сенсорные модули
  10. Набор MOSMusic: MOSFET, Arduino и управление светом по музыке