Arduino初级项目-简单的FM收音机
全景图
简介
这个项目是建立一个通过Arduino控制的简单的FM收音机,基于rda5807m模块和Arduino Pro mini 3.3v。
经过网络搜索找到这个很好的FM收音机模块rda5807,这个模块还支持RDS信号接受。我觉得这是非常好的一个主意,制作自己的FM收音机并可以自定义特性,还可以使用可充电的锂电池和USB充电器。
nokia 5110 显示屏是一个好的选择,它具有良好可读性,易用性,自定义和低功耗,相应也延长电池的寿命。
硬件
- rda5807 fm radio module
- SparkFun Arduino Pro Mini 328 - 3.3V/8MHz
- nokia 5110 display
- 18650 li-ion battery
- 18650 battery case
- Adafruit USB Li Ion Battery Charger
- audio amplifier
- Adafruit industries ada161 image 75px
- Photo resistor
- General Purpose Transistor NPN
- Resistor 100 ohm
- Resistor 10k ohm
- resistor 300k
- Resistor 1M ohm
- Rotary Encoder with Push-Button
- loadspeakers
软件
- Arduino IDE
制作工具
- 螺丝批及相关
项目特性
- 锂离子电池18650与USB充电器提供电力
- nokia 5110显示屏能创建友好界面
- 最后4个站点列表,简化站之间的导航
- RDS信息(站名)是在屏幕的底部显示
- 屏幕背光自动调整
rda5807无线模块电压为自适应的3.3V,这可让项目更简单。arduino pro mini 3.3v可以用于控制rda5807模块,内部电压调节器保证在无线模块和屏幕正确的电压。利用一个音频放大器来播放声音,锂电池直接连接音频放大器不需要调整电压。
通过u8glib图形库可在nokia 5110显示屏上显示信息。这个库快速,可靠,能很好显示黑白字体,我增加了cyrillic字体在库,你就可以方便使用了。
如何控制
无线模块通过旋转编码器控制。调谐器有两种运行模式,自动或手动模式,默认是自动模块,在这模式下,通过旋转编码器的向前或向后来控制向上或向下搜索电台信号。可以使用手动模式来设定频率,只要按一下编码器就可以自动切换不同模式。
在每次改变电台后,最后4个电台列表保存在Arduino的EEPROM里。电台改变后一分钟,保存的程序会调用。
按2秒显示最后4个电台列表,你可以旋转编码器来选定某一个,按一下编码器切换到选择的电台。
结语
无线模块的RDA5807是伟大的调频收音机,有很方便的库实现了有趣的功能。你需要从广播站在长距离的情况下长到足够多的天线。声音质量是可以接受的。你可以使用更好的音频放大器,但更强大的放大器可以缩短电池寿命。
原理图
实现代码
#include <Wire.h>
#include <radio.h>
#include <RDA5807M.h>
#include "RDSParser.h"
#include "U8glib.h"
#include <EEPROM.h>
#define DIGIT_FONT u8g_font_helvR18n
#define RUS_FONT u8g_font_cronx1h
#define FIX_BAND RADIO_BAND_FM
// Nokia 5110 display connection
const byte lcd_CE = 10;
const byte lcd_RST = 8;
const byte lcd_DC = 9;
const byte lcd_DIN_MOSI = 11;
const byte lcd_CLK = 13;
const byte BACKLIGHT_PIN = 5; // PWM control for the screen backlight
const byte BATTERY_PIN = A0; // Used to measure the battery voltage
const byte LIGHT_SENSOR = A1; // Photo sensor to measure ambient light
// Rotary encoder
const byte R_MAIN_PIN = 2; // Rotary Encoder main pin (right)
const byte R_SECD_PIN = 6; // Rotary Encoder second pin (left)
const byte R_BUTN_PIN = 3; // Rotary Encoder push button pin
const uint8_t battery_bitmap[] PROGMEM = {
0b01111111, 0b11111000,
0b10000000, 0b00000100,
0b10001000, 0b01000111,
0b10001000, 0b01000101,
0b10001000, 0b01000111,
0b10000000, 0b00000100,
0b01111111, 0b11111000,
};
const uint8_t Lhighlight_bitmap[] PROGMEM = {
0b00011000,
0b00011100,
0b00011110,
0b00011111,
0b00011110,
0b00011100,
0b00011000
};
const uint8_t Rhighlight_bitmap[] PROGMEM = {
0b00011000,
0b00111000,
0b01111000,
0b11111000,
0b01111000,
0b00111000,
0b00011000
};
//------------------------------------------ backlight of the LCD display (lite version) ----------------------
#define H_LENGTH 8 // Sensor history length
class BL {
public:
BL(byte sensorPIN, byte lightPIN) {
sensor_pin = sensorPIN;
led_pin = lightPIN;
}
void init(void); // Initialize the data
void adjust(void); // Automatically adjust the brightness
private:
byte sensor_pin; // Light sensor pin
byte led_pin; // Led PWM pin
uint32_t checkMS; // Time in ms when the sensor was checked
byte brightness; // The backlight brightness
byte new_brightness; // The baclight brightness to set up
int queue[H_LENGTH]; // Statistics of the sensor values
byte h_indx; // Current history index
void put(int item); // Save the sensor value to the history
int average(void); // Calculate average value for internal history
const byte default_brightness = 128; // Default brightness of backlight
const byte dayly_brightness = 150; // Dayly brightness of backlight
const uint16_t b_night = 400; // light sensor value of the night
const uint16_t b_day = 700; // light sensor value of the day light
const uint16_t period = 200; // The period in ms to check the photeregister
const byte nightly_brightness = 50; // The display brightness when it is dark
const byte max_dispersion = 15; // The maximum dispersion of the sensor to change the brightness
};
void BL::init(void) {
pinMode(led_pin, OUTPUT);
pinMode(sensor_pin, INPUT);
int light = analogRead(sensor_pin);
for (byte i = 0; i < H_LENGTH; ++i) queue[i] = light;
brightness = new_brightness = default_brightness;
checkMS = 0;
adjust();
h_indx = 0;
}
void BL::put(int item) {
queue[h_indx++] = item;
if (h_indx >= H_LENGTH) h_indx = 0;
}
int BL::average(void) {
long sum = 0;
for (byte i = 0; i < H_LENGTH; ++i) sum += queue[i];
sum += H_LENGTH >> 1; // round the average
sum /= H_LENGTH;
return (int)sum;
}
void BL::adjust(void) {
if (new_brightness != brightness) {
if (new_brightness > brightness) ++brightness; else --brightness;
analogWrite(led_pin, brightness);
delay(5);
}
if (millis() < checkMS) return;
checkMS = millis() + period;
int light = analogRead(sensor_pin);
put(light);
light = average();
if (light < b_night) {
new_brightness = nightly_brightness;
return;
}
if (light > b_day) {
new_brightness = 0;
return;
}
new_brightness = map(light, b_night, b_day, nightly_brightness, dayly_brightness);
}
//------------------------------------------ class BUTTON ------------------------------------------------------
class BUTTON {
public:
volatile byte mode; // The button mode: 0 - not pressed, 1 - pressed, 2 - long pressed
BUTTON(byte ButtonPIN, unsigned int timeout_ms = 2000) { pt = 0; buttonPIN = ButtonPIN; overPress = timeout_ms; }
bool buttonPressLong(void) {
unsigned long now_t = millis();
return ((pt > 0) && (now_t - pt > shortPress) && (now_t - pt < overPress));
}
void init(void) { pinMode(buttonPIN, INPUT_PULLUP); }
void cnangeINTR(void);
bool buttonTick(void);
private:
const unsigned int shortPress = 900;
unsigned int overPress; // Maxumum time in ms the button can be pressed
volatile unsigned long pt; // Time in ms when the button was pressed (press time)
byte buttonPIN; // The pin number connected to the button
};
void BUTTON::cnangeINTR(void) { // Interrupt function, called when the button status changed
bool keyUp = digitalRead(buttonPIN);
unsigned long now_t = millis();
if (!keyUp) { // The button has been pressed
if ((pt == 0) || (now_t - pt > overPress)) pt = now_t;
} else {
if (pt > 0) {
if ((now_t - pt) < shortPress) mode = 1; // short press
else mode = 2; // long press
pt = 0;
}
}
}
bool BUTTON::buttonTick(void) { // Check the button state, called each time in the main loop
bool keyUp = digitalRead(buttonPIN); // Read the current state of the button
unsigned long now_t = millis();
if (!keyUp) { // The button has been pressed
if ((pt == 0) || (now_t - pt > overPress)) pt = now_t;
} else {
if (pt > 0) {
pt = 0;
return true; // Button pressed once
}
}
return false;
}
//------------------------------------------ class ENCODER ------------------------------------------------------
class ENCODER {
public:
ENCODER(byte aPIN, byte bPIN, int16_t initPos = 0) {
pt = 0; mPIN = aPIN; sPIN = bPIN; pos = initPos;
min_pos = -32767; max_pos = 32766; channelB = false; increment = 1;
changed = 0;
is_looped = false;
}
void init(void) {
pinMode(mPIN, INPUT_PULLUP);
pinMode(sPIN, INPUT_PULLUP);
}
void reset(int16_t initPos, int16_t low, int16_t upp, byte inc = 1, byte fast_inc = 0, bool looped = false) {
min_pos = low; max_pos = upp;
if (!write(initPos)) initPos = min_pos;
increment = fast_increment = inc;
if (fast_inc > increment) fast_increment = fast_inc;
is_looped = looped;
}
void set_increment(byte inc) { increment = inc; }
byte get_increment(void) { return increment; }
bool write(int16_t initPos) {
if ((initPos >= min_pos) && (initPos <= max_pos)) {
pos = initPos;
return true;
}
return false;
}
int16_t read(void) { return pos; }
void cnangeINTR(void);
private:
const uint16_t overPress = 1000;
int32_t min_pos, max_pos;
volatile uint32_t pt; // Time in ms when the encoder was rotaded
volatile uint32_t changed; // Time in ms when the value was changed
volatile bool channelB;
volatile int32_t pos; // Encoder current position
byte mPIN, sPIN; // The pin numbers connected to the main channel and to the socondary channel
bool is_looped; // Weither the encoder is looped
byte increment; // The value to add or substract for each encoder tick
byte fast_increment; // The value to change encoder when in runs quickly
const uint16_t fast_timeout = 300; // Time in ms to change encodeq quickly
};
void ENCODER::cnangeINTR(void) { // Interrupt function, called when the channel A of encoder changed
bool rUp = digitalRead(mPIN);
unsigned long now_t = millis();
if (!rUp) { // The channel A has been "pressed"
if ((pt == 0) || (now_t - pt > overPress)) {
pt = now_t;
channelB = digitalRead(sPIN);
}
} else {
if (pt > 0) {
byte inc = increment;
if ((now_t - pt) < overPress) {
if ((now_t - changed) < fast_timeout) inc = fast_increment;
changed = now_t;
if (channelB) pos -= inc; else pos += inc;
if (pos > max_pos) {
if (is_looped)
pos = min_pos;
else
pos = max_pos;
}
if (pos < min_pos) {
if (is_looped)
pos = max_pos;
else
pos = min_pos;
}
}
pt = 0;
}
}
}
//------------------------------------------ class RDS_RADIO, combines the radio and RDS parcer --------------
class RDS_RADIO : public RDA5807M, public RDSParser {
public:
RDS_RADIO() {}
void seekUp(bool Auto) { RDA5807M::seekUp(Auto); resetRDS(); }
void seekDown(bool Auto) { RDA5807M::seekDown(Auto); resetRDS(); }
char* rdsData(void) { return rds_name; }
void rdsServiceCB(char *name);
void setFrequency(uint16_t freq);
void radioInit(void);
private:
void resetRDS(void); // Reset RDS data
char *rds_name; // The pointer to the rds data
void utf8rus(char* str); // Convert Russian utf8 code to the win encoding
};
void RDS_RADIO::utf8rus(char* str) {
byte i, k, n;
byte d = 0;
k = strlen(str); i = 0;
while (i < k) {
n = str[i]; i++;
if (n >= 0xC0) {
switch (n) {
case 0xD0: {
n = str[i]; i++;
if (n == 0x81) { n = 0xA8; break; }
if (n >= 0x90 && n <= 0xBF) n = n + 0x30;
break;
}
case 0xD1: {
n = str[i]; i++;
if (n == 0x91) { n = 0xB8; break; }
if (n >= 0x80 && n <= 0x8F) n = n + 0x70;
break;
}
}
}
str[d++] = n;
}
str[d] = '\0';
}
void RDS_RADIO::rdsServiceCB(char *name) {
utf8rus(name);
rds_name = name;
}
void RDS_RADIO::resetRDS(void) {
rds_name = 0;
RDA5807M::clearRDS();
RDSParser::init();
}
void RDS_RADIO::setFrequency(uint16_t freq) {
RDA5807M::setFrequency(freq);
resetRDS();
}
void RDS_RADIO::radioInit(void) {
RDA5807M::init();
RDSParser::init();
}
//------------------------------------------ Configuration data ------------------------------------------------
/* Config record in the EEPROM is 16 bytes long and it has the following format:
uint32_t ID each time increment by 1
uint16_t stations[5] list of the last 5 stations
byte CRC the checksum
*/
class CONFIG {
public:
CONFIG() {
can_write = is_valid = false;
rAddr = wAddr = 0;
eLength = 0;
nextRecID = 0;
}
void init();
bool load(void);
bool isValid(void) { return is_valid; }
uint16_t getStation(byte index); // Return saved station frequence by the index
bool saveStation(uint16_t st); // write updated config into the EEPROM
private:
uint16_t stations[4]; // The list of last 4 stations. The list must fit to the screen
bool readRecord(uint16_t addr, uint32_t &recID);
bool save(void);
bool can_write; // The flag indicates that data can be saved
bool is_valid; // Weither tha data was loaded
uint16_t rAddr; // Address of thecorrect record in EEPROM to be read
uint16_t wAddr; // Address in the EEPROM to start write new record
uint16_t eLength; // Length of the EEPROM, depends on arduino model
uint32_t nextRecID; // next record ID
const byte record_size = 16; // The size of one record in bytes
};
// Read the records until the last one, point wAddr (write address) after the last record
void CONFIG::init(void) {
eLength = EEPROM.length();
uint32_t recID;
uint32_t minRecID = 0xffffffff;
uint16_t minRecAddr = 0;
uint32_t maxRecID = 0;
uint16_t maxRecAddr = 0;
byte records = 0;
nextRecID = 0;
// read all the records in the EEPROM find min and max record ID
for (uint16_t addr = 0; addr < eLength; addr += record_size) {
if (readRecord(addr, recID)) {
++records;
if (minRecID > recID) {
minRecID = recID;
minRecAddr = addr;
}
if (maxRecID < recID) {
maxRecID = recID;
maxRecAddr = addr;
}
} else {
break;
}
}
if (records == 0) {
wAddr = rAddr = 0;
can_write = true;
return;
}
rAddr = maxRecAddr;
if (records < (eLength / record_size)) { // The EEPROM is not full
wAddr = rAddr + record_size;
if (wAddr > eLength) wAddr = 0;
} else {
wAddr = minRecAddr;
}
can_write = true;
}
uint16_t CONFIG::getStation(byte index) {
uint16_t res;
if (index < 4) res = stations[index];
return res;
}
bool CONFIG::saveStation(uint16_t st) {
byte i = 0;
for ( ; i < 4; ++i) {
if (stations[i] == st) break; // This station is already in the list
}
if (i >= 4) i = 3; // This station is new one
for (char j = i-1; j >= 0; --j) // shift station list one item down
stations[j+1] = stations[j];
stations[0] = st; // Put new entry to the top
return save(); // Save new data into the EEPROM
}
bool CONFIG::save(void) {
if (!can_write) return can_write;
if (nextRecID == 0) nextRecID = 1;
uint16_t startWrite = wAddr;
uint32_t nxt = nextRecID;
byte summ = 0;
for (byte i = 0; i < 4; ++i) {
EEPROM.write(startWrite++, nxt & 0xff);
summ <<=2; summ += nxt;
nxt >>= 8;
}
byte* p = (byte *)stations;
for (byte i = 0; i < 4*sizeof(uint16_t); ++i) {
summ <<= 2; summ += p[i];
EEPROM.write(startWrite++, p[i]);
}
summ ++; // To avoid empty records
EEPROM.write(wAddr+record_size-1, summ);
rAddr = wAddr;
wAddr += record_size;
if (wAddr > EEPROM.length()) wAddr = 0;
return true;
}
bool CONFIG::load(void) {
is_valid = readRecord(rAddr, nextRecID);
nextRecID ++;
return is_valid;
}
bool CONFIG::readRecord(uint16_t addr, uint32_t &recID) {
byte Buff[record_size];
for (byte i = 0; i < record_size; ++i)
Buff[i] = EEPROM.read(addr+i);
byte summ = 0;
for (byte i = 0; i < 4*sizeof(uint16_t) + 4; ++i) {
summ <<= 2; summ += Buff[i];
}
summ ++; // To avoid empty fields
if (summ == Buff[record_size-1]) { // Checksumm is correct
uint32_t ts = 0;
for (char i = 3; i >= 0; --i) {
ts <<= 8;
ts |= Buff[i];
}
recID = ts;
byte i = 4;
memcpy(stations, &Buff[4], 4*sizeof(uint16_t));
return true;
}
return false;
}
//------------------------------------------ class MAIN SCREEN -----------------------------------------------
class MSCR {
public:
MSCR(U8GLIB_PCD8544* pU8g, RDS_RADIO* Radio, ENCODER* Encoder, byte batteryPIN, CONFIG* Cfg) {
pD = pU8g;
pRadio = Radio;
pEnc = Encoder;
batt_pin = batteryPIN;
pCfg = Cfg;
}
void init(void); // Initialize the internal data
void main(void); // The main screen loop
void push(void); // Rotary button has been pressed shortly
void menu(void); // Rotary button has been pressed for long time
private:
void setFrequency(void); // set new frequency to the radio
void selectHistory(void); // Select the station from the history list
U8GLIB_PCD8544* pD; // The pointer to the screen instance
RDS_RADIO* pRadio; // Pointer to the Radio instance
ENCODER* pEnc; // Pointer to the Encoder instance
CONFIG* pCfg; // Pointer to the configuration instance
uint16_t freq; // Current frequency or the one to be set
uint32_t rds_update_time; // The time in ms to update RDS data
uint32_t radio_save_time; // The time in ms to save current station into EEPROM
uint32_t update_time; // The time in ms to update the screen
bool fm_seek; // Weither to seek stations automatically or use fine tune
bool select_history; // select from the history mode
byte batt_pin; // The battery sensor PIN
RADIO_INFO info; // Radio information structure
const uint32_t period = 2000; // The period in ms to redraw the screen
const uint16_t low_station = 7600; // Radio stations diappasone [76 MHz - 108 MHz]
const uint16_t upp_station = 10800;
const byte radio_step = 10; // Increment the frequence by 0.1 MHz
const byte fast_step = 1;
const uint16_t batt_low = 830; // Sensor readings for low battery
const uint16_t batt_high = 920; // Sensor readings for high battery
};
void MSCR::init(void) {
pinMode(batt_pin, INPUT);
update_time = 0;
rds_update_time = radio_save_time = 0; // radio_update_time set to zero means not save the station config in future
fm_seek = true; // By default seek to the next station
select_history = false;
freq = pCfg->getStation(0);
setFrequency();
pEnc->reset(freq, low_station, upp_station, radio_step, fast_step, true);
}
void MSCR::setFrequency(void) {
if (!freq) freq = low_station;
uint16_t t = freq - 10;
if (t < low_station) t = freq + 10;
pRadio->setFrequency(t);
delay(300);
pRadio->setFrequency(freq);
pRadio->getRadioInfo(&info);
}
void MSCR::main(void) {
if (select_history) { // Pick up the station from the history
selectHistory();
return;
}
// Synchronize frequence from the Radio
int16_t pos = pRadio->getFrequency();
if (pos != freq) {
freq = pos;
pEnc->write(freq);
update_time = 0;
}
// If the rotary encoder has been changed, select new radio station
uint32_t nowMS = 0;
pos = pEnc->read();
if ((uint16_t)pos != freq) {
if (fm_seek) {
if (pos > freq)
pRadio->seekUp(true);
else
pRadio->seekDown(true);
} else {
freq = (uint16_t)pos;
pRadio->setFrequency(freq);
}
update_time = 0;
pRadio->getRadioInfo(&info);
nowMS = millis();
rds_update_time = nowMS + 100;
radio_save_time = nowMS + 60000; // Set time to save current station to the history
}
// Update RDS information
nowMS = millis();
if (info.rssi && (nowMS >= rds_update_time)) { // if RDS is active
pRadio->checkRDS();
rds_update_time = nowMS + 20;
}
if (radio_save_time && (nowMS >= radio_save_time)) {
pCfg->saveStation(freq);
radio_save_time = 0; // The station information is already saved in EEPROM
}
if (nowMS > update_time) { // Update the screen
char radio_freq[6];
pRadio->getRadioInfo(&info);
sprintf(radio_freq, "%3d.%1d", freq / 100, (freq % 100) / 10);
int level = info.rssi + 4;
if (level > 32) level = 32;
level = map(level, 0, 32, 0, 16);
int b = analogRead(batt_pin);
if (b > batt_high) b = batt_high;
if (b < batt_low) b = batt_low;
byte battery = map(b, batt_low, batt_high, 0, 13);
pD->firstPage();
do {
pD->setFont(DIGIT_FONT);
byte width = pD->getStrPixelWidth(radio_freq);
byte lpos = 42 - width/2;
pD->drawStr(lpos, 34, radio_freq);
pD->drawTriangle(0, 7, level, 7, level, 7 - (level / 2));
pD->drawBitmapP(84-16, 0, 2, 7, battery_bitmap);
pD->drawBox(84-16+1, 1, battery, 5);
// Display RDS info
pD->setFont(RUS_FONT);
char *rds = pRadio->rdsData();
if (rds) {
int len = pD->getStrPixelWidth(rds);
pD->drawStr(42-len/2, 48, pRadio->rdsData());
}
if (fm_seek) pD->drawStr(30, 8, F("auto"));
} while(pD->nextPage());
update_time = millis() + period;
}
}
void MSCR::push(void) { // short button press
if (select_history) { // the station has been selected from the history
freq = pCfg->getStation(freq); // In the function call freq holds the station index in the list
setFrequency();
pEnc->reset(freq, low_station, upp_station, radio_step, fast_step, true);
select_history = false;
} else {
fm_seek = !fm_seek;
}
update_time = 0;
}
void MSCR::menu(void) { // long button press
if (!select_history) { // switch to the main mode
freq = 0;
pEnc->reset(freq, 0, 3, 1, 1, true);
select_history = true;
radio_save_time = millis() + 60000; // Save new stations configuration in 1 minute
update_time = 0; // Force to refresh the screen
}
}
void MSCR::selectHistory(void) { // Show the history list and the selection marks
uint16_t pos = pEnc->read();
if ((uint16_t)pos != freq) { // In the history mode freq holds station index in the list
freq = pos;
update_time = 0; // Force to refresh the screen
}
if (millis() < update_time) return;
char radio_freq[6];
pD->firstPage();
do {
pD->setFont(RUS_FONT);
for (byte i = 0; i < 4; ++i) { // Only four lines of text can fit the display
uint16_t s = pCfg->getStation(i);
sprintf(radio_freq, "%3d.%1d", s / 100, (s % 100) / 10);
byte width = pD->getStrPixelWidth(radio_freq);
pD->drawStr(42 - width/2, i*12+9, radio_freq);
}
// Draw the marks
pD->drawBitmapP(16, freq*12, 1, 7, Lhighlight_bitmap);
pD->drawBitmapP(84-24, freq*12, 1, 7, Rhighlight_bitmap);
} while(pD->nextPage());
update_time = millis() + period;
}
// ===================================== End of the classes definition ==============================================
U8GLIB_PCD8544 u8g(lcd_CLK, lcd_DIN_MOSI, lcd_CE, lcd_DC, lcd_RST);
RDS_RADIO radio;
ENCODER rotEncoder(R_MAIN_PIN, R_SECD_PIN);
BUTTON rotButton(R_BUTN_PIN);
BL bckLight(LIGHT_SENSOR, BACKLIGHT_PIN);
CONFIG cfg;
MSCR mainScreen(&u8g, &radio, &rotEncoder, BATTERY_PIN, &cfg);
void rdsHandler(uint16_t b1, uint16_t b2, uint16_t b3, uint16_t b4) {
radio.processData(b1, b2, b3, b4);
}
void rdsServiceNameCB(char *name) {
radio.rdsServiceCB(name);
}
void setup() {
u8g.setColorIndex(1); // pixel on
bckLight.init();
radio.radioInit(); // Initialize the Radio
radio.setBand(RADIO_BAND_FMWORLD);
radio.setVolume(5);
radio.setMono(false);
radio.setBassBoost(false);
radio.setMute(false);
radio.setSoftMute(true);
radio.attachReceiveRDS(rdsHandler);
radio.attachServicenNameCallback(rdsServiceNameCB);
rotEncoder.init();
rotButton.init();
cfg.init();
cfg.load();
mainScreen.init();
attachInterrupt(digitalPinToInterrupt(R_MAIN_PIN), rotEncChange, CHANGE);
attachInterrupt(digitalPinToInterrupt(R_BUTN_PIN), rotPushChange, CHANGE);
}
void rotEncChange(void) {
rotEncoder.cnangeINTR();
}
void rotPushChange(void) {
rotButton.cnangeINTR();
}
// ======================== The Main Loop ====================================
void loop() {
if (rotButton.mode) {
if (rotButton.mode == 1) { // Short Button press
mainScreen.push();
} else { // Long Button press
mainScreen.menu();
}
rotButton.mode = 0;
}
mainScreen.main();
bckLight.adjust(); // Automatically adjust backlight of the screen
}
代码下载:下载
原文:https://www.hackster.io/sfrwmaker/simple-fm-radio-5bb328
获取最新文章: 扫一扫右上角的二维码加入“创客智造”公众号