加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
  • 推荐器件
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

帮助你拒绝无聊的淋浴房调频收音机

2022/11/25 作者:有灵魂的发烧友
3065
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

这个FM收音机内置Arduino, RDA5807M, Tiny RTC, PAM8403 D类放大器模块,TR028触摸面板。

背景

几年前,我买了一个淋浴房,里面安装了TR028无线电控制系统。不幸的是,有一天我发现这个系统完全失效了。在这之后我咨询了一段时间,发现找不到能修好他的人,所以之后我又买了一个更便宜的淋浴收音机。但大约一年后它又坏了。

这两次遭遇让我想起来我家以前在淋浴间里有一台收音机,所以我准备研究这些收音机是怎么制造出来的。在TR028系统中,我发现了一个奇怪的TEA5767模块。在经过一番搜索后,我了解到这是一个小的廉价调频无线电模块。同时在查找资料的过程中,我发现了另一个有趣的调频无线电模块RDA5807。它非常类似于TEA5767,但有RDS,音量控制和低音增强功能。所以我决定在我的新项目中使用RDA5807。

准备着手

我在网上搜索了一下,找到了几个使用RDA5807模块的项目,在参考完这些项目后,激发了我的一些想象力。

我的愿景:

  • 触摸屏装置,确保防水。(我使用的是带有TR0289触控面板的外壳)。
  • 广播功能
  • 预设几个最喜欢的电台
  • 音量控制
  • 自动寻台功能
  • 可能记住寻找的电台
  • 查看当前时间的时钟。
  • 开/关功能
  • 灯光控制
  • 一些次要的信息显示,如舱内温度,RDS。

在买到了RDA5807,带有32kb EEPROM的微型RTC, PAM8403, NOKIA 5110 LCD, LM2596模块后,我开始了实验。

最后得到了:

  • 2排调频收音机
  • 6个预设为最喜欢的电台
  • 自动或手动调优
  • 可以存储最喜欢的电台到6个预设之一
  • 音量和低音升压控制
  • 淋浴房灯控制
  • 时钟蚂蚁日历
  • 无线电信号强度指示器
  • 立体声模式指示灯
  • 开/关功能

项目图片

对于诺基亚5110显示器,我找到了一个不错的库

了解TR028触摸屏的工作原理。实际上是2列X 7行键盘。为了操作它,我使用了这个库。

组装好的板放在盒子里。你可以注意到,我已经拆除了USB插座,并直接焊接了电缆。这是连接PC和未来软件改进的可能性。

如何工作

  • 接通电源后打开收音机。第一次要打开收音机你必须连接电源,几秒钟后按下电源键。该电台将播放最后播放的电台音量在03级。操作模式为音量控制。要关闭收音机只需按电源键。该装置将关闭LCD, LCD背光,放大器和LED /卤素灯。
  • 要寻找一个电台,您可以选择自动或手动调谐模式,按下“Mod”按钮。通过按“<”或“>”按钮,收音机将搜索一个电台减少或增加频率。要存储已找到的电台,按下“Mem”按钮,你将有4秒钟的时间从六个预设中选择一个,将你喜欢的存储起来。
  • 按I (info)键查看当前日期。日期将在4秒后显示。这部分代码可以进行优化,因为它使用了delay()函数。
  • 当你听到一个小时结束的信号(时间信号),或在一些精确的时钟上看到一个小时的最后几秒时,按下并保持D键至少2秒来调整时钟。按D键设置hh.00.00。如果您的时钟从15分钟迟到到1分钟,分秒将设置为00,小时将增加1;如果您的时钟从1分钟迟到到15分钟,经过调整程序,只有分秒将设置为00。

未来可能的改进

  • RTC模块上的谐振器具有较好的精度,但时钟调节功能可以解决这一问题。
  • 把5110的液晶显示器换成更大更亮的。可能是一些1、8”或2.0”彩色LCD,因为有时很难阅读项目中使用的诺基亚5110 LCD的信息。
  • PAM8403放大器到PAM8610,输出功率为2x15W或具有相同特性的TDA7297。

结论
我对我的新项目很满意。工作1个月后没有发现任何问题,除了时钟精度。

Project sketch:

/////////////////////////////////////////////////////////////////
//          Arduino based shower FM Radio Project              //
//  Arduino NANO, RDA5807M, RTC, EEPROM, LCD5110, Thermistor   //
/////////////////////////////////////////////////////////////////

#include <LCD5110_Graph.h> //http://www.rinkydinkelectronics.com/library.php?id=48
#include <AT24CX.h> //https://github.com/cyberp/AT24Cx
#include <Wire.h> //Arduino IDE included
#include <RDSParser.h> //http://www.mathertel.de/Arduino/RadioLibrary.aspx
#include <radio.h> //http://www.mathertel.de/Arduino/RadioLibrary.aspx
#include <RTClib.h> //https://github.com/adafruit/RTClib
#include <RDA5807M.h> //http://www.mathertel.de/Arduino/RadioLibrary.aspx
#include <Keypad.h> //http://playground.arduino.cc/Code/Keypad
#define MAXmenu  4
#define ledPin 13
#define blPin 7

//define the cymbols on the buttons of the keypads
char keys[7][2] = {
  {'L', 'P'}, //LED, POWER
  {'I', 'D'}, //INFO, DISPLAY
  {'1', '2'}, //presets
  {'3', '4'}, //from 1
  {'5', '6'}, //to 6
  {'M', 'm'}, //MODE, MEM
  {'<', '>'}  //down, up
};
byte rowPins[7] = {11, 12, 10, 17, 9, 16, 8}; //connect to the row pinouts of the keypad
byte colPins[2] = {15, 14}; //connect to the column pinouts of the keypad

//Keypad kpd = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
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;
int volume, volumeOld = 5;
int frequency, frequencyOld;
int txtl = 0, temparray = 0;
int samples[5];
unsigned int status[6];
unsigned long timeprevious = 0, timeprev = 0;

// EEPROM object
AT24CX mem;

RTC_DS1307 rtc;

//(clk, din, dc, ce, rst)
LCD5110 lcd(6, 5, 4, 2, 3);

// Create an instance of a RDA5807 chip radio
RDA5807M radio;

/// get a RDS parser
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[];

//--------------------------SETUP----------------------------------//
void setup()
{
  analogReference(EXTERNAL);
  Serial.begin(9600);
  Wire.begin();

  // Initialize the Radio
  radio.init();
  radio.debugEnable();

  //initialize the Screen
  lcd.InitLCD();
  lcd.clrScr();
  //lcd.setContrast(45); //adjust if default isn't good
  lcd.setFont(SmallFont);
  lcd.enableSleep(); //stand-by mode

  power_state = 0; //don't "power-on" of the unit (stand by mode) when power supply is connected

  //inicialize the keyboard
  keypad.addStatedEventListener(keypadEvent); // Add an event listener for this keypad
  keypad.setHoldTime(1500);

  pinMode(ledPin, OUTPUT);              // Sets the digital pin as output.
  pinMode(blPin, OUTPUT);              // Sets the digital pin as output.
  digitalWrite(ledPin, LOW);           // Turn the LED off.
  digitalWrite(blPin, LOW);           // Turn the BL off (stand-by mode)
  ledPin_state = digitalRead(ledPin);   // Store initial LED state. HIGH when LED is on.

  //uncomment if rtc need to be adjusted
  /*if (! rtc.isrunning()) {
      Serial.println("RTC is NOT running!");
      // following line sets the RTC to the date & time this sketch was compiled
      rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
      // This line sets the RTC with an explicit date & time, for example to set
      // January 21, 2014 at 3am you would call:
      //rtc.adjust(DateTime(2018, 3, 13, 22, 33, 0));
     }*/

  // read value of last frequency
  frequency = mem.readInt(201);

  volume = 2; //volume level at start
  menu = 1; //shows VOLUMME mode at start

  if (volume < 0) volume = 0;
  if (volume > 15) volume = 15;
  if (frequency < 0) frequency = 0;
  if (frequency > 210) frequency = 210;

  WriteReg(0x02, 0xC00d); // write 0xC00d into Reg.2 ( soft reset, enable,RDS, ) //bbz
  canal(frequency);

  // setup the information chain for RDS data.
  radio.attachReceiveRDS(RDS_process);
  rds.attachServicenNameCallback(DisplayServiceName);
  //rds.attachTimeCallback(DisplayTime); //for future use. very inaccurate when RDS signal is weak.
  rds.attachTextCallback(DisplayText);
}

//-----------------------end of Setup------------------------------------//

//--------------------------LOOP----------------------------------------//

void loop()
{
  if ( frequency != frequencyOld)
  {
    frequencyOld = frequency;
    mem.writeInt(201, frequency);
    canal(frequency);
  }

  if (volume != volumeOld)
  {
    volumeOld = volume;
    WriteReg(5, 0x84D0 | volume);
  }

  //read the keyboard
  char key = keypad.getKey();

  //check for RDS data
  radio.checkRDS();

  // reads temperature probe every 0,6 sec 5 times and calculates average
  float average;
  unsigned long timenow = millis();
  if ((unsigned long)(timenow - timeprevious) > 600) {
    timeprevious = timenow;
    samples[temparray] = analogRead(A7);
    temparray++;
  }
  if (temparray == 5) {
    // calculating average of readings
    average = 0;
    for (int i = 0; i < 5; i++) {
      average += samples[i];
    }
    printTemp(average);
    temparray = 0;
  }

  // 4 sec timeout for MEM display and enter
  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;
    }
  }

  /*Time adjustment instructions:
     1. Run serial monitor
     2. Set 9600 boud
     3. Hit enter to activate serial reading
     4. Write hXX, where XX is current hour reading on some time server and hit enter to adjust RTC hours. Serial monitor should write "Hours XX"
     5. Write mXX, where XX is current minutes reading on some time server and hit enter to adjust RTC minutes. Serial monitor should write "Minutes XX"
     6. Write sXX, where XX is current seconds reading on some time server and hit enter to adjust RTC seconds. Serial monitor should write "Seconds XX"
     7. You can adjust only hours. I.e. when day light saving time was changed.
     8. You can adjust only seconds if you need to correct time only.
     9. If RTC has to be adjusted from zero (year, month, day, etc), uncomment RTC adjustment statement lines and upload the sketch.
  */
  DateTime now = rtc.now();
  if (Serial.available() > 0) {
    char t = Serial.read();
    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;
        }
    }
  }

  //display various information on LCD
  printSignalStrength();
  printLines();

  printTime();
  printFreq();
  printStereo();
  printMode();
  printMenu();
  printDate();
  lcd.update();
}
//------------------------End of Loop------------------------------------//

void printSignalStrength() //from 0000 to 1111 (0-63)
{
  unsigned int sig;
  Readstatus();
  sig = status[1] / 1000;
  if ((sig >= 0) && (sig <= 12)) {
    lcd.drawBitmap(1, 1, signal1, 17 , 6);
  }
  if ((sig >= 13) && (sig <= 24)) {
    lcd.drawBitmap(1, 1, signal2, 17 , 6);
  }
  if ((sig >= 25) && (sig <= 36)) {
    lcd.drawBitmap(1, 1, signal3, 17 , 6);
  }
  if ((sig >= 37) && (sig <= 48)) {
    lcd.drawBitmap(1, 1, signal4, 17 , 6);
  }
  if (sig >= 49) {
    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) //could be optimised :)
{
  average /= 5;
  average = 1023 / average - 1;
  average = 51700 / average;

  float steinhart;
  steinhart = average / 50000; // (R/Ro)
  steinhart = log(steinhart); // ln(R/Ro)
  steinhart /= 3950; // 1/B * ln(R/Ro)
  steinhart += 1.0 / (25 + 273.15); // + (1/To)
  steinhart = 1.0 / steinhart; // invert
  steinhart -= 273.15; // celsius conversion

  int tmp = round(steinhart);
  lcd.printNumI(tmp, 60, 1, 2);
  lcd.print(F("~C"), 72, 1);
}

/// Update the ServiceName text on the LCD display when in RDS mode.
void DisplayServiceName(char *name)
{
  lcd.print(name, 18, 22);
}

void DisplayText(char *text)
{
  //scroll second RDS line
  lcd.print(text, txtl, 30);
  txtl = 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) { //checks if display key was pressed
    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();
    switch (dw) {
      case 0:
        lcd.print(F("Sekmadienis"), CENTER, 30); //sunday F() macro to save sdram
        break;
      case 1:
        lcd.print(F("Pirmadienis"), CENTER, 30); //monday etc...
        break;
      case 2:
        lcd.print(F("Antradienis"), CENTER, 30);
        break;
      case 3:
        lcd.print(F("Treciadienis"), CENTER, 30);
        break;
      case 4:
        lcd.print(F("Ketvirtadienis"), CENTER, 30);
        break;
      case 5:
        lcd.print(F("Penktadienis"), CENTER, 30);
        break;
      case 6:
        lcd.print(F("Sestadienis"), CENTER, 30);
        break;
    }
    lcd.update();
    delay(4000); //not optimal
    ClearRDS();
    dspl = 0;
  }
}

void printMode()
{
  lcd.print(F("MODE "), 0, 41);
}

void printMenu()
{
  if (menu == 1) {
    lcd.print(F("VOLUME "), 30, 41);
    if (volume < 0) {
      lcd.print(F("XX"), 72, 41);
    }
    else
      lcd.printNumI(volume + 1, 72, 41, 2, '0');
  }
  if (menu == 2) {
    lcd.print(F("AUTO-TUNE"), 30, 41);
  }
  if (menu == 3) {
    lcd.print(F("MAN.-TUNE"), 30, 41);
  }
  if (menu == 4) {
    lcd.print(F(" BASS "), 30, 41);
    if (bass == 0) {
      lcd.print(F("OFF"), 66, 41);
    }
    else
      lcd.print(F(" ON"), 66, 41);

  }
}

void printFreq() //displays current frequency
{
  int frHundr, frDec;
  unsigned int fr;
  fr = 870 + frequency;
  frHundr = fr / 10;
  frDec = fr % 10;
  lcd.printNumI(frHundr, 30, 12, 3);
  lcd.print(F("."), 48, 12);
  lcd.printNumI(frDec, 54, 12, 1);
  lcd.print(F("MHz"), 66, 12);
}

void printStereo()
{
  if (memdisplay == 1) { //if MEM key was pressed
    lcd.print(F("MEM>"), 0, 12);
  }
  //Stereo detection
  else if ((status[0] & 0x0400) == 0)
    lcd.print(F("(  )"), 0, 12); //means MONO
  else
    lcd.print (F("(ST)"), 0, 12); //means STEREO
}

void search(byte direc) //automatic seek
{
  byte i; //seek up or down
  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) //direct frequency
{
  byte numberH, numberL;
  numberH =  canal >> 2;
  numberL = ((canal & 3) << 6 | 0x10);
  Wire.beginTransmission(0x11);
  Wire.write(0x03);
  Wire.write(numberH);      // write frequency into bits 15:6, set tune bit
  Wire.write(numberL);
  Wire.endTransmission();
}

//RDA5807_adrs=0x10;
// I2C-Address RDA Chip for sequential  Access
int Readstatus()
{
  Wire.requestFrom(0x10, 12);
  for (int i = 0; i < 6; i++) {
    status[i] = 256 * Wire.read () + Wire.read();
  }
  Wire.endTransmission();

}
//RDA5807_adrr=0x11;
// I2C-Address RDA Chip for random Access
void WriteReg(byte reg, unsigned int valor)
{
  Wire.beginTransmission(0x11);
  Wire.write(reg); Wire.write(valor >> 8); Wire.write(valor & 0xFF);
  Wire.endTransmission();
  //delay(50);
}

void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) {
  rds.processData(block1, block2, block3, block4);
}
void ClearRDS()
{
  lcd.print("              ", 0, 22);
  lcd.print("              ", 0, 30);
}

// Taking care of some special events.
void keypadEvent(KeypadEvent key, KeyState kpadState ) {
  switch (kpadState) {
    /*
      Another way to adjust time:
      1. Press and hold key D for at least 2 sec, when you hear hour ending time signals, or see last hour seconds
      on some accurate clock.
      2. Release key D to adjust hh.00.00.
      3. Ahter adjustment, if your clock was late from 15 to 1 minutes, minutes and seconds will be 00
      and hours will be increased by 1.
      4. If your clock was hurry from 1 to 15 minutes, only minutes and seconds will be 00.
    */
    case HOLD:
      if (key == 'D' && power_state == 1) {
        adj = 1;
      }
      break;
    case RELEASED:
      if (key == 'D' && adj == 1) {
        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 == 1) {
        memdisplay = 0;
        menu++;
        if (menu > MAXmenu)menu = 1;
      }

      if (key == '>' && power_state == 1) {
        memdisplay = 0;
        switch (menu)
        {
          case 1:
            if (volume < 0) {
              if (bass == 0) {
                WriteReg(0x02, 0xD00D);
                volume = 0;
              }
              if (bass == 1) {
                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; // upper frequency limit
            ClearRDS();
            break;
          case 4:
            if (bass == 0) {
              bass = 1;
              WriteReg(0x02, 0xD00D);
            }
            break;
        }
      }

      if (key == '<' && power_state == 1) {
        memdisplay = 0;
        switch (menu)
        {
          case 1:
            volume--;
            if (volume < 0) {
              WriteReg(0x02, 0x800D);
              //volume = 0;
            }
            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;
        }
      }
      // LED lights on/off
      if (key == 'L' && power_state == 1) {
        digitalWrite(ledPin, !digitalRead(ledPin));
        ledPin_state = digitalRead(ledPin);  // Remember LED state, lit or unlit.
      }
      // turns "power" on or off (stand-by mode)
      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;
      }

      if (key == '1' && power_state == 1) {
        switch (memdisplay) {
          case 0:
            frequency = mem.readInt(110);
            ClearRDS();
            break;
          case 1:
            mem.writeInt(110, frequency);
            memdisplay = 0;
            break;

        }
      }
      if (key == '2' && power_state == 1) {
        switch (memdisplay) {
          case 0:
            frequency = mem.readInt(120);
            ClearRDS();
            break;
          case 1:
            mem.writeInt(120, frequency);
            memdisplay = 0;
            break;

        }
      }
      if (key == '3' && power_state == 1) {
        switch (memdisplay) {
          case 0:
            frequency = mem.readInt(130);
            ClearRDS();
            break;
          case 1:
            mem.writeInt(130, frequency);
            memdisplay = 0;
            break;

        }
      }
      if (key == '4' && power_state == 1) {
        switch (memdisplay) {
          case 0:
            frequency = mem.readInt(140);
            ClearRDS();
            break;
          case 1:
            mem.writeInt(140, frequency);
            memdisplay = 0;
            break;

        }
      }
      if (key == '5' && power_state == 1) {
        switch (memdisplay) {
          case 0:
            frequency = mem.readInt(150);
            ClearRDS();
            break;
          case 1:
            mem.writeInt(150, frequency);
            memdisplay = 0;
            break;
        }
      }

      if (key == '6' && power_state == 1) {
        switch (memdisplay) {
          case 0:
            frequency = mem.readInt(160);
            ClearRDS();
            break;
          case 1:
            mem.writeInt(160, frequency);
            memdisplay = 0;
            break;
        }
      }

      if (key == 'm' && power_state == 1) {
        mempress = 1;
      }
      else {
        mempress = 0;
      }

      if (key == 'I' && power_state == 1) {
        dspl = 1;
      }
      break;
  }
}

如果您对此项目有任何想法、意见或问题,请在下方留言。

以上内容翻译自网络,原作者:Saulius Bandzevičius,如涉及侵权,可联系删除。

推荐器件

器件型号 数量 器件厂商 器件描述 数据手册 ECAD模型 风险等级 参考价格 更多信息
SA604AHRZ 1 NXP Semiconductors SA604A - High performance low power FM IF system QFN 16-Pin
暂无数据 查看
ADV7280BCPZ 1 Analog Devices Inc 10-Bit, 4x Oversampled SDTV Video Decoder with Deinterlacer

ECAD模型

下载ECAD模型
$11.87 查看
STUSB4500QTR 1 STMicroelectronics Standalone USB PD controller for power sinking devices

ECAD模型

下载ECAD模型
$2.45 查看

相关推荐

电子产业图谱