#define DEBUG
#include "RDA5807M.h"
#include <EEPROM.h>
#include <Wire.h>
#include <LiquidCrystal_I2C_Menu.h> // https://github.com/VladimirTsibrov/LiquidCrystal_I2C_Menu
LiquidCrystal_I2C_Menu lcd(0x27, 20, 4);
#define EEPROM_START_ADDRESS 0
#define PIN_CLK 2 // Энкодер пин A
#define PIN_DT 3 // Энкодер пин B
#define PIN_SW 4 // Кнопка
void OneBigHandler(); // Прототип обработчика меню
enum {mkBack, mkRoot, mkSeekUp, mkSeekDown, mkSetVolume, mkSetFreq, mkRotatePurpose,
mkSoundOptions, mkBassBoost, mkSoftMute, mkSoftBlend, mkSoftBlendThreshold,
mkForceMono, mkTunerOptions, mkNewDemodulation, mkSeekThreshold, mkSaveSettings};
sMenuItem menu[] = {
{mkBack, mkRoot, "Menu"},
{mkRoot, mkSeekUp, "Seek up"},
{mkRoot, mkSeekDown, "Seek down"},
{mkRoot, mkSetVolume, "Set volume", OneBigHandler},
{mkRoot, mkSetFreq, "Set frequency", OneBigHandler},
{mkRoot, mkRotatePurpose, "Encoder purpose",OneBigHandler},
{mkRoot, mkSoundOptions, "Sound options"},
{mkSoundOptions, mkBassBoost, NULL, OneBigHandler},
{mkSoundOptions, mkForceMono, NULL, OneBigHandler},
{mkSoundOptions, mkSoftMute, NULL, OneBigHandler},
{mkSoundOptions, mkSoftBlend, NULL, OneBigHandler},
{mkSoundOptions, mkSoftBlendThreshold, NULL, OneBigHandler},
{mkSoundOptions, mkBack, "Back"},
{mkRoot, mkTunerOptions, "Tuner options"},
{mkTunerOptions, mkNewDemodulation, NULL, OneBigHandler},
{mkTunerOptions, mkSeekThreshold, NULL, OneBigHandler},
{mkTunerOptions, mkBack, "Back"},
{mkRoot, mkSaveSettings, "Save settings", OneBigHandler},
{mkRoot, mkBack, "Back"}
};
uint8_t menuLen = sizeof(menu) / sizeof(sMenuItem);
bool Searching = false;
bool NeedRepaint = false;
double Freq;
enum {RotateVolume, RotateStation} EncoderPurpose = RotateStation;
unsigned long tm = 0;
char *listOffOn[] = {"OFF", "ON"};
int getItemIndexByKey(uint8_t key) {
for (uint8_t i = 0; i < menuLen; i++)
if (menu[i].key == key)
return i;
return -1;
}
void updateCaption(uint8_t key, const char format[], const char value[]) {
uint8_t index = getItemIndexByKey(key);
char* buf = (char*) malloc(40);
sprintf(buf, format, value);
menu[index].caption = (char*) realloc(menu[index].caption, strlen(buf) + 1);
strcpy(menu[index].caption, buf);
free(buf);
}
void setup() {
Wire.begin();
lcd.begin();
lcd.attachEncoder(PIN_DT, PIN_CLK, PIN_SW);
#if defined(DEBUG)
Serial.begin(57600);
lcd.attachIdleFunc(checkSerial);
#endif
// Читаем настройки из EEPROM
readEEPROM();
// Обновляем заголовки в меню
updateCaption(mkBassBoost, "Bass boost (%s)", listOffOn[(reg02h & RDA5807M_FLG_BASS) > 0]);
updateCaption(mkSoftMute, "Soft mute (%s)", listOffOn[(reg04h & RDA5807M_FLG_SOFTMUTE) > 0]);
updateCaption(mkSoftBlend, "Soft blend (%s)", listOffOn[(reg07h & RDA5807M_FLG_SOFTBLEND) > 0]);
updateCaption(mkSoftBlendThreshold, "Soft blend threshold (%s)", String((reg07h & RDA5807M_SOFTBLENDTH_MASK) >> RDA5807M_SOFTBLENDTH_SHIFT).c_str());
updateCaption(mkForceMono, "Force mono (%s)", listOffOn[(reg02h & RDA5807M_FLG_MONO) > 0]);
updateCaption(mkNewDemodulation, "New demodulation (%s)", listOffOn[(reg02h & RDA5807M_FLG_NEW) > 0]);
updateCaption(mkSeekThreshold, "Seek threshold (%s)", String((reg05h & RDA5807M_SEEKTH_MASK) >> RDA5807M_SEEKTH_SHIFT).c_str());
}
#if defined(DEBUG)
void checkSerial() {
uint8_t b;
if (Serial.available()) {
delay(1);
b = Serial.read();
if (b == 255) { // Прочитать регистр
b = Serial.read(); // Адрес регистра
Wire.beginTransmission(0x11);
Wire.write(b);
Wire.endTransmission(false);
Wire.requestFrom(0x11, 2, true);
Serial.write(b);
Serial.write(Wire.read());
Serial.write(Wire.read());
}
else { // Записать регистр
Wire.beginTransmission(0x11);
Wire.write(b);
Wire.write(Serial.read());
Wire.write(Serial.read());
Wire.endTransmission(true);
}
}
}
#endif
void loop() {
#if defined(DEBUG)
checkSerial();
#endif
// Опрос энкодера
eEncoderState encoderState = lcd.getEncoderState();
if (encoderState == eButton) { // Нажата кнопка. Покажем меню
uint8_t selectedMenuItem = lcd.showMenu(menu, menuLen, 1);
if (selectedMenuItem == mkSeekUp) { // Поиск радиостанции вверх
seekStation(1);
}
else if (selectedMenuItem == mkSeekDown) { // Поиск радиостанции вниз
seekStation(0);
}
// Остальные пункты меню в обработчике
NeedRepaint = true; // После выхода из меню нужна перерисовка дисплея
}
else if (encoderState == eLeft){ // Энкодер повернули влево
if (EncoderPurpose == RotateVolume) { // Убавить громкость
uint16_t volume = (reg05h & RDA5807M_VOLUME_MASK);
if (volume > 0) {
reg05h &= ~RDA5807M_VOLUME_MASK;
reg05h |= --volume;
setRegister(RDA5807M_REG_VOLUME, reg05h);
if (volume < 10)
lcd.printfAt(0, 1, "< %d >", volume);
else
lcd.printfAt(0, 1, "< %d >", volume);
tm = millis();
}
}
else if (EncoderPurpose == RotateStation) { // Искать вниз
seekStation(0);
NeedRepaint = true;
}
}
else if (encoderState == eRight){
if (EncoderPurpose == RotateVolume) { // Прибавить громкость
uint16_t volume = (reg05h & RDA5807M_VOLUME_MASK);
if (volume < 15) {
reg05h &= ~RDA5807M_VOLUME_MASK;
reg05h |= ++volume;
setRegister(RDA5807M_REG_VOLUME, reg05h);
if (volume < 10)
lcd.printfAt(0, 1, "< %d >", volume);
else
lcd.printfAt(0, 1, "< %d >", volume);
tm = millis();
}
}
else if (EncoderPurpose == RotateStation) { // Искать вверх
seekStation(1);
NeedRepaint = true;
}
}
if (Searching) { // Проверим найдена ли радиостанция
reg0Ah = getRegister(RDA5807M_REG_STATUS1);
if (reg0Ah & RDA5807M_FLAG_STC) {
// Найдена
Searching = false;
NeedRepaint = true;
reg03h = getRegister(RDA5807M_REG_TUNING);
Freq = 87 + double((reg03h & RDA5807M_CHAN_MASK) >> RDA5807M_CHAN_SHIFT) / 10;
}
}
if ((NeedRepaint) or (tm and (millis() - tm > 3000)))
LCDRepaint();
}
void LCDRepaint(){
// Перерисовать дисплей
lcd.clear();
if (Searching)
lcd.print("Searching...");
else {
String s(Freq, 1);
lcd.printf("%sFM", s.c_str());
}
if (EncoderPurpose == RotateVolume)
lcd.printAt(0, 1, "< Volume >");
else if (EncoderPurpose == RotateStation)
lcd.printAt(0, 1, "< Station >");
NeedRepaint = false;
tm = 0;
}
void OneBigHandler(){
// Обработчик для пунктов меню. Здесь только те пункты,
// при выборе которых не требуется выход из меню
uint8_t selectedMenuItem = lcd.getSelectedMenuItem();
if (selectedMenuItem == mkSetVolume) { // Установить громкость
uint16_t volume = (reg05h & RDA5807M_VOLUME_MASK);
/* Можно и так, но громкость будет изменяться только после выхода из inputVal
volume = lcd.inputVal<uint16_t>("Volume", 0, 15, volume);
reg05h &= ~RDA5807M_VOLUME_MASK;
reg05h |= volume;
setRegister(RDA5807M_REG_VOLUME, reg05h);
*/
lcd.clear();
lcd.print("Input volume");
lcd.printAt(0, 1, volume);
while (1) {
eEncoderState encoderState = lcd.getEncoderState();
if (encoderState == eNone) {
#if defined(DEBUG)
checkSerial();
#endif
continue;
}
else if (encoderState == eButton) break;
else if (encoderState == eLeft) {
if (volume > 0) volume--; else continue;
}
else if (encoderState == eRight) {
if (volume < 15) volume++; else continue;
}
lcd.printAt(0, 1, volume);
lcd.print(" ");
reg05h &= ~RDA5807M_VOLUME_MASK;
reg05h |= volume;
setRegister(RDA5807M_REG_VOLUME, reg05h);
}
}
else if (selectedMenuItem == mkSetFreq) { // Запросить и установить частоту
double newFreq = Freq;
if (! lcd.inputValBitwise("Input freq", newFreq, 4, 1, 0)) return; // XXX.X
while ((newFreq < 87) or (newFreq > 108)) {
lcd.printMultiline("Invalid frequency. Should be in 87..108");
if (!lcd.inputValBitwise("Input freq", newFreq, 4, 1, 0)) return;
}
Freq = newFreq;
uint16_t chan = uint8_t((newFreq - 87) * 10);
reg03h &= ~RDA5807M_CHAN_MASK;
reg03h |= chan << RDA5807M_CHAN_SHIFT;
setRegister(RDA5807M_REG_TUNING, reg03h | RDA5807M_FLG_TUNE);
}
else if (selectedMenuItem == mkRotatePurpose) { // Поведение при вращении энкодера
String listEncPurpose[] = {"Set volume", "Set station"};
EncoderPurpose = lcd.selectVal("Encoder purose", listEncPurpose, 2, (int)EncoderPurpose);
}
else if (selectedMenuItem == mkBassBoost) { // Предложим включить усиление басов
bool bassBoost = reg02h & RDA5807M_FLG_BASS;
bassBoost = lcd.selectVal("Bass boost", listOffOn, 2, bassBoost);
if (bassBoost) reg02h |= RDA5807M_FLG_BASS;
else reg02h &= ~RDA5807M_FLG_BASS;
setRegister(RDA5807M_REG_CONFIG, reg02h);
updateCaption(mkBassBoost, "Bass boost (%s)", listOffOn[bassBoost]);
}
else if (selectedMenuItem == mkForceMono) { // Предложим переключиться в моно
bool forceMono = reg02h & RDA5807M_FLG_MONO;
forceMono = lcd.selectVal("Force mono", listOffOn, 2, forceMono);
if (forceMono) reg02h |= RDA5807M_FLG_MONO;
else reg02h &= ~RDA5807M_FLG_MONO;
setRegister(RDA5807M_REG_CONFIG, reg02h);
updateCaption(mkForceMono, "Force mono (%s)", listOffOn[forceMono]);
}
else if (selectedMenuItem == mkSoftMute) { // Предложим включить softmute
bool softMute = reg04h & RDA5807M_FLG_SOFTMUTE;
softMute = lcd.selectVal("Soft mute", listOffOn, 2, softMute);
if (softMute) reg04h |= RDA5807M_FLG_SOFTMUTE;
else reg04h &= ~RDA5807M_FLG_SOFTMUTE;
setRegister(RDA5807M_REG_DEEMPH, reg04h);
updateCaption(mkSoftMute, "Soft mute (%s)", listOffOn[softMute]);
}
else if (selectedMenuItem == mkSoftBlend) { // Предложим включить softblend
bool softBlend = reg07h & RDA5807M_FLG_SOFTBLEND;
softBlend = lcd.selectVal("Soft blend", listOffOn, 2, softBlend);
if (softBlend) reg07h |= RDA5807M_FLG_SOFTBLEND;
else reg07h &= ~RDA5807M_FLG_SOFTBLEND;
setRegister(RDA5807M_REG_THRESH, reg07h);
updateCaption(mkSoftBlend, "Soft blend (%s)", listOffOn[softBlend]);
}
else if (selectedMenuItem == mkSoftBlendThreshold) { // Изменить порог softblend
uint16_t threshold = (reg07h & RDA5807M_SOFTBLENDTH_MASK) >> RDA5807M_SOFTBLENDTH_SHIFT;
threshold = lcd.inputVal<uint16_t>("Input blend theshold", 0, 31, threshold);
reg07h &= ~RDA5807M_SOFTBLENDTH_MASK;
reg07h |= (threshold << RDA5807M_SOFTBLENDTH_SHIFT);
setRegister(RDA5807M_REG_THRESH, reg07h);
updateCaption(mkSoftBlendThreshold, "Soft blend threshold (%s)", String(threshold).c_str());
}
else if (selectedMenuItem == mkNewDemodulation) { // Предложим включить новый метод демодуляции
bool newDemodulation = reg02h & RDA5807M_FLG_NEW;
newDemodulation = lcd.selectVal("New demodulation", listOffOn, 2, newDemodulation);
if (newDemodulation) reg02h |= RDA5807M_FLG_NEW;
else reg02h &= ~RDA5807M_FLG_NEW;
setRegister(RDA5807M_REG_CONFIG, reg02h);
updateCaption(mkNewDemodulation, "New demodulation (%s)", listOffOn[newDemodulation]);
}
else if (selectedMenuItem == mkSeekThreshold) { // Изменить порог для поиска
uint16_t threshold = (reg05h & RDA5807M_SEEKTH_MASK) >> RDA5807M_SEEKTH_SHIFT;
threshold = lcd.inputVal<uint16_t>("Input seek theshold", 0, 15, threshold);
reg05h &= ~RDA5807M_SEEKTH_MASK;
reg05h |= (threshold << RDA5807M_SEEKTH_SHIFT);
setRegister(RDA5807M_REG_VOLUME, reg05h);
updateCaption(mkSeekThreshold, "Seek threshold (%s)", String(threshold).c_str());
}
else if (selectedMenuItem == mkSaveSettings) { // Сохранить настройки в EEPROM
EEPROM.put(EEPROM_START_ADDRESS, reg02h);
EEPROM.put(EEPROM_START_ADDRESS + 2, reg03h);
EEPROM.put(EEPROM_START_ADDRESS + 4, reg04h);
EEPROM.put(EEPROM_START_ADDRESS + 6, reg05h);
EEPROM.put(EEPROM_START_ADDRESS + 8, reg07h);
lcd.printMultiline("Settings have been saved");
}
}
void seekStation(bool UP){
if (UP)
reg02h |= RDA5807M_FLG_SEEKUP;
else
reg02h &= ~RDA5807M_FLG_SEEKUP;
setRegister(RDA5807M_REG_CONFIG, reg02h | RDA5807M_FLG_SEEK);
Searching = true;
}
void readEEPROM(){
EEPROM.get(EEPROM_START_ADDRESS, reg02h);
if ((reg02h == 255) or !(reg02h & RDA5807M_FLG_ENABLE) or
!(reg02h & RDA5807M_FLG_DHIZ) or !(reg02h & RDA5807M_FLG_DMUTE) or
(reg02h & RDA5807M_FLG_RESET)) {
// В EEPROM что-то не то. Установим настройки по умолчанию
reg02h = 0xC001;
reg03h = 0x00;
reg04h = 0x0800;
reg05h = 0x88C1;
reg07h = 0x42C6;
}
else {
EEPROM.get(EEPROM_START_ADDRESS + 2, reg03h);
EEPROM.get(EEPROM_START_ADDRESS + 4, reg04h);
EEPROM.get(EEPROM_START_ADDRESS + 6, reg05h);
EEPROM.get(EEPROM_START_ADDRESS + 8, reg07h);
}
setRegister(RDA5807M_REG_CONFIG, reg02h);
setRegister(RDA5807M_REG_TUNING, (reg03h & RDA5807M_CHAN_MASK) ? reg03h | RDA5807M_FLG_TUNE : reg03h);
setRegister(RDA5807M_REG_DEEMPH, reg04h);
setRegister(RDA5807M_REG_VOLUME, reg05h);
setRegister(RDA5807M_REG_THRESH, reg07h);
Freq = 87 + double((reg03h & RDA5807M_CHAN_MASK) >> RDA5807M_CHAN_SHIFT) / 10;
NeedRepaint = true;
}
void setRegister(uint8_t reg, const uint16_t value) {
Wire.beginTransmission(RDA5807M_RANDOM_ACCESS_ADDRESS);
Wire.write(reg);
Wire.write(highByte(value));
Wire.write(lowByte(value));
Wire.endTransmission(true);
}
uint16_t getRegister(uint8_t reg) {
uint16_t result;
Wire.beginTransmission(RDA5807M_RANDOM_ACCESS_ADDRESS);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(RDA5807M_RANDOM_ACCESS_ADDRESS, 2, true);
result = (uint16_t)Wire.read() << 8;
result |= Wire.read();
return result;
}