library is used
25 | to process signals from a momentary input.
26 |
27 | The OneButton library is available using the Arduino Library.
28 |
29 | It works with processor boards for Arduino UNO, ESP8266 like NodeMCU 1.0 and ESP32.
30 |
31 | This sketch works with all of the implemented chips by using the common radio functions of the library.
32 |
33 | You have to modify the source code line where the radio variable is defined to the chip you use.
34 | There are out-commented lines for every chip in the sketch.
35 | Be sure to activate the lines for the RST signal for SI4703 radio chips.
36 |
37 | The Serial interface, the rotary encoder and the momentary button can be used
38 | for controlling the chip.
39 | Open the Serial Monitor window of the Arduino programming environment
40 | you can see verbose output from the library and can control the settings
41 | by entering commands in the input text line.
42 |
43 | The command ? (and enter) will display a short summary of the available commands.
44 |
45 | If you like to explore the chip specific settings you may use the "x" command
46 | that outputs the chip registers or other chip specific information.
47 |
48 | Have a look into the chip specific implementation for more details.
49 |
50 | Please read the README.md files in the TEST____ examples for more chip specific hints.
51 |
--------------------------------------------------------------------------------
/examples/WebRadio/web/about.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Impressum
6 |
7 |
8 |
12 |
13 |
14 | Impressum
15 |
16 | On this web site you can find the published articles and public available files, source code and projects
17 | of Matthias Hertel.
18 | Auf dieser Site finden Sie Publikationen und die öffentlich zugänglichen Dateien und Projekte von Matthias
19 | Hertel.
20 | Contact / Kontakt
21 |
22 |
23 |
24 |
25 |
Matthias Hertel
26 |
27 |
Dillinger Str. 49a
28 |
29 | Friedrichsdorf ,
30 | 61381
31 | Germany
32 |
33 |
34 |
39 |
40 |
41 | This work is licensed under a BSD style license. See
42 | http://www.mathertel.de/License.aspx
43 |
44 |
45 |
--------------------------------------------------------------------------------
/examples/TestTEA5767/TestTEA5767.ino:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file TestTEA5767.ino
3 | /// \brief An Arduino sketch to operate a TEA5767 chip based radio using the Radio library.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// \details
11 | /// This sketch implements a "as simple as possible" radio without any possibility to modify the settings after initializing the chip.\n
12 | /// The radio chip is initialized and setup to a fixed band and frequency. These settings can be changed by modifying the
13 | /// FIX_BAND and FIX_STATION definitions.
14 | ///
15 | /// Open the Serial console with 57600 baud to see the current radio information.
16 | ///
17 | /// Wiring
18 | /// ------
19 | /// Arduino port | TEA5767 signal
20 | /// ------------ | ---------------
21 | /// 3.3V | VCC
22 | /// GND | GND
23 | /// A5 | SCLK
24 | /// A4 | SDIO
25 | /// D2 | RST
26 | ///
27 | /// More documentation is available at http://www.mathertel.de/Arduino
28 | /// Source Code is available on https://github.com/mathertel/Radio
29 | ///
30 | /// History:
31 | /// --------
32 | /// * 15.09.2014 created.
33 | /// * 15.11.2015 wiring corrected.
34 |
35 | #include
36 | #include
37 | #include
38 | #include
39 |
40 | /// The band that will be tuned by this sketch is FM.
41 | #define FIX_BAND RADIO_BAND_FM
42 |
43 | /// The station that will be tuned by this sketch is 89.30 MHz.
44 | #define FIX_STATION 8930
45 |
46 | TEA5767 radio; // Create an instance of Class for Si4703 Chip
47 |
48 | uint8_t test1;
49 | byte test2;
50 |
51 | /// Setup a FM only radio configuration
52 | /// with some debugging on the Serial port
53 | void setup() {
54 | // open the Serial port
55 | Serial.begin(57600);
56 | Serial.println("Radio...");
57 | delay(200);
58 |
59 | // Initialize the Radio
60 | radio.init();
61 |
62 | // Enable information to the Serial port
63 | radio.debugEnable();
64 |
65 | // HERE: adjust the frequency to a local sender
66 | radio.setBandFrequency(FIX_BAND, FIX_STATION); // hr3 nearby Frankfurt in Germany
67 | radio.setVolume(2);
68 | radio.setMono(false);
69 | } // setup
70 |
71 |
72 | /// show the current chip data every 3 seconds.
73 | void loop() {
74 | char s[12];
75 | radio.formatFrequency(s, sizeof(s));
76 | Serial.print("Station:");
77 | Serial.println(s);
78 |
79 | Serial.print("Radio:");
80 | radio.debugRadioInfo();
81 |
82 | Serial.print("Audio:");
83 | radio.debugAudioInfo();
84 |
85 | delay(3000);
86 | } // loop
87 |
88 | // End.
89 |
90 |
--------------------------------------------------------------------------------
/src/RDSParser.h:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file RDSParser.h
3 | /// \brief RDS Parser class definition.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// \details
11 | ///
12 | /// More documentation and source code is available at http://www.mathertel.de/Arduino
13 | ///
14 | /// History:
15 | /// --------
16 | /// * 01.09.2014 created and RDS sender name working.
17 | /// * 01.11.2014 RDS time added.
18 | /// * 27.03.2015 Reset RDS data by sending a 0 in blockA in the case the frequency changes.
19 | ///
20 |
21 |
22 | #ifndef __RDSPARSER_H__
23 | #define __RDSPARSER_H__
24 |
25 | #include
26 |
27 | /// callback function for passing a ServiceName, text and Time when RDS is available.
28 | extern "C" {
29 | typedef void (*receiveServiceNameFunction)(const char *name);
30 | typedef void (*receiveTextFunction)(const char *name);
31 | typedef void (*receiveTimeFunction)(uint8_t hour, uint8_t minute);
32 | }
33 |
34 |
35 | /// Library for parsing RDS data values and extracting information.
36 | class RDSParser {
37 | public:
38 | RDSParser(); ///< create a new object from this class.
39 |
40 | /// Initialize internal variables before starting or after a change to another channel.
41 | void init();
42 |
43 | /// Pass all available RDS data through this function.
44 | void processData(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4);
45 |
46 | void attachServiceNameCallback(receiveServiceNameFunction newFunction); ///< Register function for displaying a new Service Name.
47 | void attachTextCallback(receiveTextFunction newFunction); ///< Register the function for displaying a rds text.
48 | void attachTimeCallback(receiveTimeFunction newFunction); ///< Register function for displaying a new time
49 |
50 | private:
51 | // ----- actual RDS values
52 | uint8_t rdsGroupType, rdsTP, rdsPTY;
53 | uint8_t _textAB, _last_textAB, _lastTextIDX;
54 |
55 | // Program Service Name data for 2 of 3 verifications
56 | // assuming that error is less than 1/3 data failures.
57 | char _PSName1[10]; // including trailing '\00' character.
58 | char _PSName2[10]; // including trailing '\00' character.
59 | char _PSName3[10]; // including trailing '\00' character.
60 |
61 | char programServiceName[10]; // found station name or empty. Is max. 8 character long.
62 | char lastServiceName[10]; // found station name or empty. Is max. 8 character long.
63 |
64 | receiveServiceNameFunction _sendServiceName; ///< Registered ServiceName function.
65 | receiveTimeFunction _sendTime; ///< Registered Time function.
66 | receiveTextFunction _sendText;
67 |
68 | uint16_t _lastRDSMinutes; ///< last RDS time send to callback.
69 |
70 | char _RDSText[64 + 2];
71 |
72 | }; // RDSParser
73 |
74 | #endif //__RDSPARSER_H__
75 |
--------------------------------------------------------------------------------
/examples/TestSI4705/TestSI4705.ino:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file TestSI4705.ino
3 | /// \brief An Arduino sketch to operate a SI4705 chip based radio using the Radio library.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
8 | ///
9 | /// \details
10 | /// This sketch implements a "as simple as possible" radio without any possibility to modify the settings after initializing the chip.\n
11 | /// The radio chip is initialized and setup to a fixed band and frequency. These settings can be changed by modifying the
12 | /// FIX_BAND and FIX_STATION definitions.
13 | ///
14 | /// Open the Serial console with 57600 baud to see the current radio information.
15 | ///
16 | /// Wiring
17 | /// ------
18 | /// The SI4705 board/chip has to be connected by using the following connections:
19 | /// | Arduino UNO pin | Radio chip signal |
20 | /// | --------------- | -------------------|
21 | /// | 3.3V | VCC |
22 | /// | GND | GND |
23 | /// | A5 or SCL | SCLK |
24 | /// | A4 or SDA | SDIO |
25 | /// | | RST (not used yet) |
26 | /// The locations of the pins on the UNO board are written on the PCB.
27 | /// The locations of the signals on the SI4705 side depend on the board you use.
28 | ///
29 | /// More documentation and source code is available at http://www.mathertel.de/Arduino
30 | ///
31 | /// ChangeLog:
32 | /// ----------
33 | /// * 05.12.2014 created.
34 | /// * 19.05.2015 extended.
35 |
36 | #include
37 | #include
38 | #include
39 | #include
40 |
41 | // ----- Fixed settings here. -----
42 |
43 | #define FIX_BAND RADIO_BAND_FM ///< The band that will be tuned by this sketch is FM.
44 | #define FIX_STATION 8930 ///< The station that will be tuned by this sketch is 89.30 MHz.
45 | #define FIX_VOLUME 8 ///< The volume that will be set by this sketch is level 4.
46 |
47 | SI4705 radio; // Create an instance of Class for SI4705 Chip
48 |
49 | /// Setup a FM only radio configuration
50 | /// with some debugging on the Serial port
51 | void setup() {
52 | // open the Serial port
53 | Serial.begin(115200);
54 | Serial.println("Radio...");
55 | delay(200);
56 |
57 | // Initialize the Radio
58 | radio.init();
59 |
60 | // Enable information to the Serial port
61 | radio.debugEnable();
62 |
63 | // Set all radio setting to the fixed values.
64 | radio.setBandFrequency(FIX_BAND, FIX_STATION);
65 | radio.setVolume(FIX_VOLUME);
66 | radio.setMono(false);
67 | radio.setMute(false);
68 | } // setup
69 |
70 |
71 | /// show the current chip data every 3 seconds.
72 | void loop() {
73 | char s[12];
74 | radio.formatFrequency(s, sizeof(s));
75 | Serial.print("Station:");
76 | Serial.println(s);
77 |
78 | Serial.print("Radio:");
79 | radio.debugRadioInfo();
80 |
81 | Serial.print("Audio:");
82 | radio.debugAudioInfo();
83 |
84 | delay(3000);
85 | } // loop
86 |
87 | // End.
88 |
--------------------------------------------------------------------------------
/src/TEA5767.h:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file TEA5767.h
3 | /// \brief Library header file for the radio library to control the TEA5767 radio chip.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014-2015 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// This library enables the use of the Radio Chip SI4703.
11 | ///
12 | /// More documentation and source code is available at http://www.mathertel.de/Arduino
13 | ///
14 | /// ChangeLog see TEA5767.h:
15 | /// --------
16 | /// * 05.08.2014 created.
17 | /// * 27.05.2015 working-
18 |
19 |
20 | #ifndef TEA5767_h
21 | #define TEA5767_h
22 |
23 | #include
24 | #include
25 |
26 | #include
27 |
28 | // ----- library definition -----
29 |
30 |
31 | /// Library to control the TEA5767 radio chip.
32 | class TEA5767 : public RADIO {
33 | public:
34 | TEA5767();
35 |
36 | bool init(); // initialize library and the chip.
37 | void term(); // terminate all radio functions.
38 |
39 | // Control of the audio features
40 |
41 | /// setVolume is a non-existing function in TEA5767. It will always me MAXVOLUME.
42 | void setVolume(int8_t newVolume) override;
43 |
44 | // Control the bass boost function of the radio chip
45 | void setBassBoost(bool switchOn);
46 |
47 | // Control mono/stereo mode of the radio chip
48 | void setMono(bool switchOn); // Switch to mono mode.
49 |
50 | // Control the mute function of the radio chip
51 | void setMute(bool switchOn); // Switch to mute mode.
52 |
53 | // Control of the core receiver
54 |
55 | // Control the frequency
56 | void setBand(RADIO_BAND newBand);
57 |
58 | void setFrequency(RADIO_FREQ newF);
59 | RADIO_FREQ getFrequency(void);
60 |
61 | void seekUp(bool toNextSender = true); // start seek mode upwards
62 | void seekDown(bool toNextSender = true); // start seek mode downwards
63 |
64 | void checkRDS(); // read RDS data from the current station and process when data available.
65 |
66 | void getRadioInfo(RADIO_INFO *info);
67 | void getAudioInfo(AUDIO_INFO *info);
68 |
69 | // ----- debug Helpers send information to Serial port
70 |
71 | void debugScan(); // Scan all frequencies and report a status
72 | void debugStatus(); // Report Info about actual Station
73 |
74 | // ----- read/write registers of the chip
75 |
76 | void _readRegisters(); // read all status & data registers
77 | void _saveRegisters(); // Save writable registers back to the chip
78 |
79 | private:
80 | // ----- local variables
81 |
82 | // store the current values of the 5 chip internal 8-bit registers
83 | uint8_t registers[5]; ///< registers for controlling the radio chip.
84 | uint8_t status[5]; ///< registers with the current status of the radio chip.
85 |
86 |
87 | // ----- low level communication to the chip using I2C bus
88 |
89 | void _write16(uint16_t val); // Write 16 Bit Value on I2C-Bus
90 | uint16_t _read16(void);
91 |
92 | void _seek(bool seekUp = true);
93 | void _waitEnd();
94 | };
95 |
96 | #endif
97 |
--------------------------------------------------------------------------------
/src/RDA5807M.h:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file RDA5807M.h
3 | /// \brief Library header file for the radio library to control the RDA5807M radio chip.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014-2015 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// \details
11 | /// This library enables the use of the Radio Chip RDA5807M from http://www.rdamicro.com/ that supports FM radio bands and RDS data.
12 | ///
13 | /// More documentation and source code is available at http://www.mathertel.de/Arduino
14 | ///
15 | /// History:
16 | /// --------
17 | /// * 12.05.2014 creation of the RDA5807M library.
18 | /// * 28.06.2014 running simple radio
19 | /// * 08.07.2014 RDS data receive function can be registered.
20 |
21 | // multi-Band enabled
22 |
23 | // - - - - -
24 | // help from: http://arduino.vom-kuhberg.de/index.php
25 | // http://projects.qi-hardware.com/index.php/p/qi-kernel/source/tree/144e9c2530f863e32a3538b06c63484401bbe314/drivers/media/radio/radio-rda5807.c
26 |
27 |
28 | #ifndef RDA5807M_h
29 | #define RDA5807M_h
30 |
31 | #include
32 | #include
33 | #include
34 |
35 | // ----- library definition -----
36 |
37 | /// Library to control the RDA5807M radio chip.
38 | class RDA5807M : public RADIO {
39 | public:
40 | RDA5807M();
41 |
42 | bool init();
43 | void term();
44 |
45 | // ----- Audio features -----
46 |
47 | void setVolume(int8_t newVolume) override;
48 | void setBassBoost(bool switchOn) override;
49 | void setMono(bool switchOn) override;
50 | void setMute(bool switchOn) override;
51 | void setSoftMute(bool switchOn) override; ///< Set the soft mute mode (mute on low signals) on or off.
52 |
53 | // ----- Receiver features -----
54 | void setBand(RADIO_BAND newBand);
55 | void setFrequency(RADIO_FREQ newF);
56 | RADIO_FREQ getFrequency(void);
57 |
58 | void seekUp(bool toNextSender = true); // start seek mode upwards
59 | void seekDown(bool toNextSender = true); // start seek mode downwards
60 |
61 | // ----- Supporting RDS for RADIO_BAND_FM and RADIO_BAND_FMWORLD
62 |
63 | void checkRDS();
64 |
65 | // ----- combined status functions -----
66 |
67 | virtual void getRadioInfo(RADIO_INFO *info); ///< Retrieve some information about the current radio function of the chip.
68 |
69 | // ----- Supporting RDS for RADIO_BAND_FM and RADIO_BAND_FMWORLD
70 |
71 | // ----- debug Helpers send information to Serial port
72 |
73 | void debugScan(); // Scan all frequencies and report a status
74 | void debugStatus(); // DebugInfo about actual chip data available
75 |
76 | protected:
77 | // ----- local variables
78 | uint16_t registers[16]; // memory representation of the registers
79 |
80 | // ----- low level communication to the chip using I2C bus
81 |
82 | void _readRegisters(); // read all status & data registers
83 | void _saveRegisters(); // Save writable registers back to the chip
84 | void _saveRegister(byte regNr); // Save one register back to the chip
85 |
86 | // void _write16(uint16_t val); // Write 16 Bit Value on I2C-Bus
87 | // uint16_t _read16(void);
88 | };
89 |
90 | #endif
91 |
--------------------------------------------------------------------------------
/examples/TestRDA5807M/TestRDA5807M.ino:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file TestRDA5807M.ino
3 | /// \brief An Arduino sketch to operate a SI4705 chip based radio using the Radio library.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
8 | ///
9 | /// \details
10 | /// This sketch implements a "as simple as possible" radio without any possibility to modify the settings after initializing the chip.\n
11 | /// The radio chip is initialized and setup to a fixed band and frequency. These settings can be changed by modifying the
12 | /// FIX_BAND and FIX_STATION definitions.
13 | ///
14 | /// Open the Serial console with 57600 baud to see the current radio information.
15 | ///
16 | /// Wiring
17 | /// ------
18 | /// The RDA5807M board/chip has to be connected by using the following connections:
19 | ///
20 | /// | Signal | Arduino UNO | ESP8266 | ESP32 | Radio chip signal |
21 | /// | ------------ | ------------| ------- | ------ | ----------------- |
22 | /// | VCC (red) | 3.3V | 3v3 | 3v3 | VCC |
23 | /// | GND (black) | GND | GND | GND | GND |
24 | /// | SCL (yellow) | A5 / SCL | D1 | 22 | SCLK |
25 | /// | SDA (blue) | A4 / SDA | D2 | 21 | SDIO |
26 | ///
27 | /// The locations of the signals on the RDA5807M side depend on the board you use.
28 | /// More documentation is available at http://www.mathertel.de/Arduino
29 | /// Source Code is available on https://github.com/mathertel/Radio
30 | ///
31 | /// ChangeLog:
32 | /// ----------
33 | /// * 05.12.2014 created.
34 | /// * 19.05.2015 extended.
35 |
36 | #include
37 | #include
38 |
39 | #include
40 | #include
41 |
42 | // ----- Fixed settings here. -----
43 |
44 | #define FIX_BAND RADIO_BAND_FM ///< The band that will be tuned by this sketch is FM.
45 | #define FIX_STATION 8930 ///< The station that will be tuned by this sketch is 89.30 MHz.
46 | #define FIX_VOLUME 10 ///< The volume that will be set by this sketch is level 4.
47 |
48 | RDA5807M radio; // Create an instance of Class for RDA5807M Chip
49 |
50 | /// Setup a FM only radio configuration
51 | /// with some debugging on the Serial port
52 | void setup() {
53 | delay(3000);
54 | // open the Serial port
55 | Serial.begin(115200);
56 | Serial.println("RDA5807M Radio...");
57 | delay(200);
58 |
59 | // Enable information to the Serial port
60 | radio.debugEnable(true);
61 | radio._wireDebug(false);
62 |
63 | // Set FM Options for Europe
64 | radio.setup(RADIO_FMSPACING, RADIO_FMSPACING_100); // for EUROPE
65 | radio.setup(RADIO_DEEMPHASIS, RADIO_DEEMPHASIS_50); // for EUROPE
66 |
67 | // Initialize the Radio
68 | if (!radio.initWire(Wire)) {
69 | Serial.println("no radio chip found.");
70 | delay(4000);
71 | ESP.restart();
72 | };
73 |
74 | // Set all radio setting to the fixed values.
75 | radio.setBandFrequency(FIX_BAND, FIX_STATION);
76 | radio.setVolume(FIX_VOLUME);
77 | radio.setMono(false);
78 | radio.setMute(false);
79 | } // setup
80 |
81 |
82 | /// show the current chip data every 3 seconds.
83 | void loop() {
84 | char s[12];
85 | radio.formatFrequency(s, sizeof(s));
86 | Serial.print("Station:");
87 | Serial.println(s);
88 |
89 | Serial.print("Radio:");
90 | radio.debugRadioInfo();
91 |
92 | Serial.print("Audio:");
93 | radio.debugAudioInfo();
94 |
95 | delay(3000);
96 | } // loop
97 |
98 | // End.
99 |
--------------------------------------------------------------------------------
/examples/TestRDA5807FP/TestRDA5807FP.ino:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file TestRDA5807FP.ino
3 | /// \brief An Arduino sketch to operate a RDA5807FP chip based radio using the Radio library.
4 | ///
5 | /// \author Marcus Degenkolbe, http://www.degenkolbe.eu
6 | /// \copyright Copyright (c) 2023 by Marcus Degenkolbe.\n
7 | /// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
8 | ///
9 | /// \details
10 | /// This sketch implements a simple radio without any possibility to modify the settings after initializing the chip.\n
11 | /// The radio chip is initialized and setup to a fixed band and frequency. These settings can be changed by modifying the
12 | /// FIX_BAND and FIX_STATION definitions. The chip sends the output data via I2S to a compatible I2S DAC.
13 | ///
14 | /// Open the Serial console with 57600 baud to see the current radio information.
15 | ///
16 | /// Wiring
17 | /// ------
18 | /// The RDA5807FP board/chip has to be connected by using the following connections:
19 | /// | Arduino UNO pin | Radio chip signal | UDA1334A I2S DAC Board |
20 | /// | -------------------| -------------------| -----------------------|
21 | /// | 5V/VIN (red) | | VIN |
22 | /// | 3.3V (red) | VCC | |
23 | /// | GND (black) | GND | GND |
24 | /// | A5 or SCL (yellow) | SCLK | |
25 | /// | A4 or SDA (blue) | SDIO | |
26 | /// | | GPIO1 (WS) | WS |
27 | /// | | GPIO2 (SD/DOUT) | DIN |
28 | /// | | GPIO3 (SCK/BCLK) | BCLK |
29 | /// The locations of the pins on the UNO board are written on the PCB.
30 | /// The locations of the signals on the RDA5807FP side depend on the board you use.
31 | ///
32 | /// More documentation and source code is available at http://www.mathertel.de/Arduino
33 | ///
34 | /// ChangeLog:
35 | /// ----------
36 | /// * 12.01.2023 created.
37 |
38 | #include
39 | #include
40 | #include
41 | #include
42 |
43 | // ----- Fixed settings here. -----
44 |
45 | #define FIX_BAND RADIO_BAND_FM ///< The band that will be tuned by this sketch is FM.
46 | #define FIX_STATION 8930 ///< The station that will be tuned by this sketch is 89.30 MHz.
47 | #define FIX_VOLUME 4 ///< The volume that will be set by this sketch is level 4.
48 |
49 | RDA5807FP radio; // Create an instance of Class for RDA5807FP Chip
50 |
51 | /// Setup a FM only radio configuration
52 | /// with some debugging on the Serial port
53 | void setup() {
54 | // open the Serial port
55 | Serial.begin(57600);
56 | Serial.println("Radio...");
57 | delay(200);
58 |
59 | // Initialize the Radio
60 | radio.init();
61 |
62 | // configure I2S
63 | RDA5807FP_I2SConfig config;
64 | config.enabled = true;
65 | config.slave = false;
66 | config.data_signed = true;
67 | config.rate = RDA5807FP_I2S_WS_CNT::WS_STEP_48;
68 |
69 | // Enable information to the Serial port
70 | radio.debugEnable();
71 |
72 | // Set all radio setting to the fixed values.
73 | radio.setBandFrequency(FIX_BAND, FIX_STATION);
74 | radio.setVolume(FIX_VOLUME);
75 | radio.setMono(false);
76 | radio.setMute(false);
77 | } // setup
78 |
79 |
80 | /// show the current chip data every 3 seconds.
81 | void loop() {
82 | char s[12];
83 | radio.formatFrequency(s, sizeof(s));
84 | Serial.print("Station:");
85 | Serial.println(s);
86 |
87 | Serial.print("Radio:");
88 | radio.debugRadioInfo();
89 |
90 | Serial.print("Audio:");
91 | radio.debugAudioInfo();
92 |
93 | delay(3000);
94 | } // loop
95 |
96 | // End.
97 |
98 |
--------------------------------------------------------------------------------
/examples/WebRadio/StringBuffer.h:
--------------------------------------------------------------------------------
1 |
2 | #define NUL '\0'
3 | #define CR '\r'
4 | #define LF '\n'
5 | #define SPACE ' '
6 | #define QUOTE '\"'
7 |
8 | // StringBuffer is a helper class for building long texts by using an fixed allocated memory region.
9 |
10 | class StringBuffer {
11 | public:
12 | /// setup a StringBuffer by passing a local char[] variable and it's size.
13 | StringBuffer(char *buffer, unsigned int bufferSize)
14 | {
15 | _buf = buffer;
16 | _size = bufferSize;
17 | clear();
18 | };
19 |
20 | /// clear the buffer.
21 | void clear() {
22 | *_buf = '\0';
23 | _len = 1; // The ending NUL character has to be part of the buffer;
24 | };
25 |
26 | char *getBuffer() {
27 | return (_buf);
28 | };
29 |
30 | uint16_t getLength() {
31 | return(_len);
32 | };
33 |
34 | void append(char c)
35 | {
36 | char *t = _buf + _len - 1;
37 | if (_len < _size) {
38 | *t++ = c;
39 | _len++;
40 | } // if
41 | *t = NUL;
42 | }; // append()
43 |
44 | void append(const char *txt)
45 | {
46 | char *t = _buf + _len - 1;
47 | char *s = (char *)txt;
48 | while (_len < _size) {
49 | if (! *s) break;
50 | *t++ = *s++;
51 | _len++;
52 | }
53 | *t = NUL;
54 | }; // append()
55 |
56 |
57 | void append(const __FlashStringHelper *txt)
58 | {
59 | char *t = _buf + _len - 1;
60 | PGM_P s = reinterpret_cast(txt);
61 | while (_len < _size) {
62 | unsigned char c = pgm_read_byte(s++);
63 | if (! c) break;
64 | *t++ = c;
65 | _len++;
66 | }
67 | *t = NUL;
68 | }; // append()
69 |
70 |
71 | /// Append a number.
72 | void append(int num)
73 | {
74 | char buf[1 + 3 * sizeof(int)];
75 | itoa(num, buf, 10);
76 | append(buf);
77 | } // append()
78 |
79 |
80 | void append_without_itoa(int num)
81 | {
82 | char buf[1 + 3 * sizeof(int)];
83 | int n = 3 * sizeof(int);
84 |
85 | buf[n--] = NUL; // terminating the result string
86 |
87 | // allow negative numbers
88 | if (num < 0) {
89 | append('-');
90 | num = - num;
91 | } // if
92 |
93 | do {
94 | buf[n--] = '0' + (num % 10);
95 | num = num / 10;
96 | } while (num);
97 | append(&buf[n+1]);
98 | } // append()
99 |
100 |
101 |
102 | /// Append a number.
103 | void append(uint32_t num)
104 | {
105 | char buf[1 + 3 * sizeof(uint32_t)];
106 | ultoa(num, buf, 10);
107 | append(buf);
108 | } // append()
109 |
110 |
111 | /// Append a string with enclosing quotes at the given buffer.
112 | void appendQuoted(const char *txt)
113 | {
114 | append(QUOTE);
115 | append(txt);
116 | append(QUOTE);
117 | } // appendQuoted()
118 |
119 |
120 | /// Append an object with value in the JSON notation to the buffer.
121 | void appendJSON(const char *name, char *value) {
122 | appendQuoted(name);
123 | append(':');
124 | appendQuoted(value);
125 | } // appendJSON()
126 |
127 |
128 | /// Append an object with value in the JSON notation to the buffer.
129 | void appendJSON(const char *name, int value) {
130 | appendQuoted(name);
131 | append(':');
132 | append(value);
133 | } // appendJSON()
134 |
135 | private:
136 | char* _buf; ///< The allocated buffer
137 | unsigned int _size; ///< The size of the buffer.
138 | unsigned int _len; ///< The actual used len of the buffer.
139 |
140 | };
141 |
142 |
--------------------------------------------------------------------------------
/src/SI4703.h:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file SI4703.h
3 | /// \brief Library header file for the radio library to control the SI4703 radio chip.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014-2015 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// This library enables the use of the Radio Chip SI4703.
11 | ///
12 | /// More documentation is available at http://www.mathertel.de/Arduino
13 | /// Source Code is available on https://github.com/mathertel/Radio
14 | ///
15 | /// History:
16 | /// --------
17 | /// * 05.08.2014 created.
18 | /// * 05.02.2023 clearing RDS data after frequency changes and scan.
19 |
20 | #ifndef SI4703_h
21 | #define SI4703_h
22 |
23 | #include
24 | #include
25 |
26 | #include
27 |
28 | // ----- library definition -----
29 |
30 | // writeGPIO parameters
31 | // GPIO1-3 Pins
32 | static const uint8_t GPIO1 = 0; // GPIO1
33 | static const uint8_t GPIO2 = 2; // GPIO2
34 | static const uint8_t GPIO3 = 4; // GPIO3
35 |
36 | // GPIO1-3 Possible Values
37 | static const uint8_t GPIO_Z = 0b00; // High impedance (default)
38 | static const uint8_t GPIO_I = 0b01; // GPIO1-Reserved, GPIO2-STC/RDS int, or GPIO3-Mono/Sterio Indicator
39 | static const uint8_t GPIO_Low = 0b10; // Low output (GND level)
40 | static const uint8_t GPIO_High = 0b11; // High output (VIO level)
41 |
42 | /// Library to control the SI4703 radio chip.
43 | class SI4703 : public RADIO {
44 | public:
45 | SI4703();
46 |
47 | void setup(int feature, int value) override;
48 | bool init() override; // initialize library and the chip.
49 | void term() override; // terminate all radio functions.
50 |
51 | // Control of the audio features
52 |
53 | // Control the volume output of the radio chip
54 | void setVolume(int8_t newVolume) override; ///< Control the volume output of the radio chip in the range 0..15.
55 |
56 | // Control mono/stereo mode of the radio chip
57 | void setMono(bool switchOn) override; // Switch to mono mode.
58 |
59 | // Control the mute function of the radio chip
60 | void setMute(bool switchOn) override; // Switch to mute mode.
61 |
62 | // Control the softMute function of the radio chip
63 | void setSoftMute(bool switchOn) override; // Switch to soft mute mode.
64 |
65 | void writeGPIO(int GPIO, int val);
66 |
67 | // Control of the core receiver
68 |
69 | // Control the frequency
70 | void setBand(RADIO_BAND newBand) override;
71 |
72 | void setFrequency(RADIO_FREQ newF);
73 | RADIO_FREQ getFrequency(void);
74 |
75 | void seekUp(bool toNextSender = true); // start seek mode upwards
76 | void seekDown(bool toNextSender = true); // start seek mode downwards
77 |
78 | void checkRDS(); // read RDS data from the current station and process when data available.
79 |
80 | // ----- combined status functions -----
81 |
82 | virtual void getRadioInfo(RADIO_INFO *info); ///< Retrieve some information about the current radio function of the chip.
83 |
84 | virtual void getAudioInfo(AUDIO_INFO *info); ///< Retrieve some information about the current audio function of the chip.
85 |
86 | // ----- debug Helpers send information to Serial port
87 |
88 | void debugScan(); // Scan all frequencies and report a status
89 | void debugStatus(); // Report Info about actual Station
90 |
91 | // ----- read/write registers of the chip
92 |
93 | void _readRegisters(); // read all status & data registers
94 | void _readRegister0A(); // read just status 0x0A register
95 | void _saveRegisters(); // Save writable registers back to the chip
96 |
97 | private:
98 | // ----- local variables
99 | int _sdaPin = -1;
100 |
101 | // store the current values of the 16 chip internal 16-bit registers
102 | uint16_t registers[16];
103 |
104 | // last RDS Poll to prevent polling < 40
105 | unsigned long _lastRDSPoll = 0;
106 |
107 | // ----- low level communication to the chip using I2C bus
108 |
109 | void _seek(bool seekUp = true);
110 | void _waitEnd();
111 | };
112 |
113 | #endif
114 |
--------------------------------------------------------------------------------
/examples/TransmitSI4721/TransmitSI4721.ino:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file TransmitSI4721.ino
3 | /// \brief An Arduino sketch to operate a SI4721 chip in transmit mode using the Radio library.
4 | ///
5 | /// \author N Poole, nickpoole.me
6 | /// \author Matthias Hertel, http://www.mathertel.de
7 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
8 | /// This work is licensed under a BSD 3-Clause License. See http://www.mathertel.de/License.aspx
9 | ///
10 | /// \details
11 | /// This sketch implements FM transmit mode with RDS broadcasting. \n
12 | /// The radio chip is initialized and setup to a fixed band and frequency. These settings can be changed by modifying the
13 | /// FIX_BAND and FIX_STATION definitions.
14 | ///
15 | /// Open the Serial console with 115200 baud to see the current radio information.
16 | ///
17 | /// Wiring
18 | /// ------
19 | /// The SI4703 board/chip has to be connected by using the following connections:
20 | /// | Arduino UNO pin | Radio chip |
21 | /// | --------------- | ---------------|
22 | /// | 3.3V | VCC |
23 | /// | GND | GND |
24 | /// | A5 or SCL | SCLK |
25 | /// | A4 or SDA | SDIO |
26 | /// | | RST (not used) |
27 | /// The locations of the pins on the UNO board are written on the PCB.
28 | /// The locations of the signals on the SI4721 side depend on the board you use.
29 | ///
30 | /// More documentation and source code is available at http://www.mathertel.de/Arduino
31 | ///
32 | /// ChangeLog:
33 | /// ----------
34 | /// * 05.12.2019 created.
35 | /// * 20.09.2020 Integrated into radio library version 2.0.0
36 |
37 | #include
38 | #include
39 | #include
40 | #include
41 |
42 | // ----- Fixed settings here. -----
43 |
44 | #define FIX_BAND RADIO_BAND_FMTX ///< The band that will be tuned by this sketch is FM.
45 | #define FIX_STATION 10410 ///< The station that will be tuned by this sketch is 104.10 MHz.
46 | #define FIX_POWER 90 ///< The transmit output power that will be set by this sketch.
47 |
48 | #define RDS_SERVICENAME "Testing" ///< The rds service name that will be set by this sketch is "Testing"
49 | #define RDS_TEXTBUFFER "Hello World!" ///< The rds text buffer that will be set by this sketch is "Hello World!"
50 |
51 | SI4721 transmitter; // Create an instance of Class for SI4721 Chip
52 |
53 | /// Setup a FM only radio configuration
54 | /// with some debugging on the Serial port
55 | void setup()
56 | {
57 | delay(3000);
58 |
59 | // open the Serial port
60 | Serial.begin(115200);
61 | Serial.println("Radio Initialize...");
62 | delay(200);
63 |
64 | #ifdef ESP8266
65 | // For ESP8266 boards (like NodeMCU) the I2C GPIO pins in use
66 | // need to be specified.
67 | Wire.begin(D2, D1); // a common GPIO pin setting for I2C
68 | #endif
69 |
70 | // see if a chip can be found
71 | if (transmitter._wireExists(&Wire, SI4721_ADR)) {
72 | Serial.print("Device found at address ");
73 | Serial.println(SI4721_ADR);
74 | } else {
75 | Serial.print("Device NOT found at address ");
76 | Serial.println(SI4721_ADR);
77 | }
78 |
79 | // Enable information to the Serial port
80 | transmitter.debugEnable(false);
81 | transmitter._wireDebug(false);
82 |
83 | // Initialize the Radio
84 | transmitter.init();
85 | // transmitter.setDeemphasis(75); // Un-comment this line in the USA to set correct deemphasis/preemphasis timing
86 |
87 | transmitter.setBandFrequency(FIX_BAND, FIX_STATION);
88 | transmitter.setTXPower(FIX_POWER);
89 |
90 | transmitter.beginRDS();
91 | transmitter.setRDSstation(RDS_SERVICENAME);
92 | transmitter.setRDSbuffer(RDS_TEXTBUFFER);
93 | } // setup
94 |
95 |
96 | /// show the current chip data every 3 seconds.
97 | void loop()
98 | {
99 | Serial.print("Transmitting on ");
100 | Serial.print(transmitter.getTuneStatus().frequency);
101 | Serial.println("kHz...");
102 |
103 | Serial.print("ASQ: ");
104 | Serial.println(transmitter.getASQ().asq);
105 |
106 | Serial.print("Audio In Level: ");
107 | Serial.println(transmitter.getASQ().audioInLevel);
108 |
109 | delay(3000);
110 | } // loop
111 |
112 | // End.
--------------------------------------------------------------------------------
/examples/TestSI4703/TestSI4703.ino:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file TestSI4703.ino
3 | /// \brief An Arduino sketch to operate a SI4703 chip based radio using the Radio library.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// \details
11 | /// This sketch implements a "as simple as possible" radio without any possibility to modify the settings after initializing the chip.\n
12 | /// The radio chip is initialized and setup to a fixed band and frequency. These settings can be changed by modifying the
13 | /// FIX_BAND and FIX_STATION definitions.
14 | ///
15 | /// Open the Serial console with 115200 baud to see the current radio information.
16 | ///
17 | /// Wiring
18 | /// ------
19 | ///
20 | /// The SI4703 board has to be connected by using the following connections:
21 | ///
22 | /// | Signal | Arduino UNO | ESP8266 | ESP32 | Radio chip signal |
23 | /// | ------------ | ------------| ------- | ------ | ----------------- |
24 | /// | VCC (red) | 3.3V | 3v3 | 3v3 | VCC |
25 | /// | GND (black) | GND | GND | GND | GND |
26 | /// | SCL (yellow) | A5 / SCL | D1 | 22 | SCLK |
27 | /// | SDA (blue) | A4 / SDA | D2 | 21 | SDIO |
28 | /// | Reset | D2 | D5 | | RST |
29 | ///
30 | /// More documentation is available at http://www.mathertel.de/Arduino
31 | /// Source Code is available on https://github.com/mathertel/Radio
32 | ///
33 | /// CHangeLog:
34 | /// ----------
35 | /// * 05.08.2014 created.
36 | /// * 03.05.2015 corrections and documentation.
37 | /// * 17.01.2023 use radio.setup to configure chip specific features.
38 |
39 | #include
40 | #include
41 | #include
42 | #include
43 |
44 | // ===== Processor / Board specific pin wiring =====
45 |
46 | #if defined(ARDUINO_ARCH_AVR)
47 | #define RESET_PIN 2
48 | #define MODE_PIN A4 // same as SDA
49 |
50 | #elif defined(ESP8266)
51 | #define RESET_PIN D5
52 | #define MODE_PIN D2 // same as SDA
53 |
54 | #elif defined(ESP32)
55 | #define RESET_PIN 4
56 | #define MODE_PIN 21 // same as SDA
57 |
58 | #endif
59 |
60 | // ----- Fixed settings here. -----
61 |
62 | #define FIX_BAND RADIO_BAND_FM ///< The band that will be tuned by this sketch is FM.
63 | #define FIX_STATION 8930 ///< The station that will be tuned by this sketch is 89.30 MHz.
64 | #define FIX_VOLUME 8 ///< The volume that will be set by this sketch is level 4.
65 |
66 | SI4703 radio; // Create an instance of Class for Si4703 Chip
67 |
68 | /// Setup a FM only radio configuration
69 | /// with some debugging on the Serial port
70 | void setup() {
71 | delay(3000);
72 |
73 | // open the Serial port
74 | Serial.begin(115200);
75 | Serial.println("Radio::TestSI4703...");
76 | delay(200);
77 |
78 | radio.setup(RADIO_RESETPIN, RESET_PIN);
79 | radio.setup(RADIO_MODEPIN, MODE_PIN);
80 |
81 | // Enable information to the Serial port
82 | radio.debugEnable(true);
83 | radio._wireDebug(true);
84 |
85 | // Set FM Options for Europe
86 | radio.setup(RADIO_FMSPACING, RADIO_FMSPACING_100); // for EUROPE
87 | radio.setup(RADIO_DEEMPHASIS, RADIO_DEEMPHASIS_50); // for EUROPE
88 |
89 | // Initialize the Radio
90 | radio.initWire(Wire);
91 |
92 | radio.debugEnable(true);
93 | radio._wireDebug(true);
94 |
95 |
96 | // Set all radio setting to the fixed values.
97 | radio.setBandFrequency(FIX_BAND, FIX_STATION);
98 | radio.setVolume(FIX_VOLUME);
99 | radio.setMono(false);
100 | radio.setMute(false);
101 |
102 | } // setup
103 |
104 |
105 | /// show the current chip data every 3 seconds.
106 | void loop() {
107 | char s[12];
108 | radio.formatFrequency(s, sizeof(s));
109 | Serial.print("Station:");
110 | Serial.println(s);
111 |
112 | Serial.print("Radio:");
113 | radio.debugRadioInfo();
114 |
115 | Serial.print("Audio:");
116 | radio.debugAudioInfo();
117 |
118 | delay(3000);
119 | } // loop
120 |
121 | // End.
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Radio Library Overview
2 |
3 | [](https://github.com/mathertel/Radio/actions/workflows/buildESP8266.yml)
4 |
5 | This library is about controlling an FM radio chips by using an Arduino, ESP8266 or ESP32 board
6 | and some optional components like a LCD display, a rotary encoder, a LCD+Keyboard shield
7 | or an Ethernet Shield to build a standalone radio.
8 |
9 | In the [github.com/mathertel/Radio](https://github.com/mathertel/Radio) repository on github.com you find an Arduino library for implementing an FM receiver using one of the supported radio chips for receiving FM broadcast audio signals.
10 |
11 | There are various examples included that show using the library using different hardware setups.
12 |
13 | The library is working for many boards like Arduino, Arduino Mega, ESP8266, ESP32 and maybe more.
14 |
15 | See also the [Changelog](CHANGELOG.md).
16 |
17 | ## Documentation
18 |
19 | The API documentation for the libraries in DOXYGEN style can be found at [http://mathertel.github.io/Radio/html](html/index.html).
20 |
21 | A more detailed article is available at [www.mathertel.de/Arduino/RadioLibrary.aspx](http://www.mathertel.de/Arduino/RadioLibrary.aspx).
22 |
23 | Currently the following radio receiver chips are supported:
24 |
25 | * The **RDA5807M** and **RDA5807FP** with I2S support from RDA Microelectronics
26 | * The **SI4703** from Silicon Labs, now Skyworks
27 | * The **SI4705** from Silicon Labs, now Skyworks
28 | * The **SI4721** and **SI4730** chips from Silicon Labs, now Skyworks
29 | * The **TEA5767** from NXP
30 |
31 | They all are capable for receiving FM radio stations in stereo with European and US settings and can be controlled by using the I2C bus. However there are differences in the sensitivity and quality and well on receiving RDS information from the stations.
32 |
33 | For each of these chips a specific library is implemented that knows how to communicate with the chip using the I2C bus and the wire library. These libraries all share a common base, the radio library so that all the common code is only implemented once in there:
34 |
35 | All the libraries share the same interface (defined by the radio library) so it is possible to exchange them when not using one of the chip specific functions.
36 |
37 | Currently the following radio transmitter chips are supported:
38 |
39 | * The **SI4721** from Silicon Labs, now Skyworks
40 |
41 |
42 | ## Contributions
43 |
44 | Contributions to the library like features, fixes and support of other chips and boards are welcome using Pull Requests.
45 |
46 | Please don't ask general programming questions in this project.
47 | Radio chip specific questions may be answered by the community (or not) and are closed after some months of inactivity.
48 |
49 | ## Examples
50 |
51 | Within the Arduino library you can find examples that implement different scenarios to control various radio chips.
52 |
53 | The basic examples only startup the chips and set a static station and volume:LCDKeypadRadio
54 |
55 | * **TestRDA5807M** to test the RDA5807M chip.
56 | * **TestSI4703** to test the SI4703 chip.
57 | * **TestSI47xx** to test the SI4705, SI4721 and SI4730 chips.
58 | * **TestTEA5767** to test the TEA5767 chip.
59 | * **TransmitSI4721** to test transmission mode of SI4721 chip.
60 |
61 | The examples can be used with several chips and boards:
62 |
63 | * The **SerialRadio** example doesn't need extra inputs as it
64 | uses the Serial in- and output to change the settings and report information.
65 | This example can be used with Arduino, ESP8266 and ESP32.
66 |
67 | * The **LCDRadio** example is similar to SerialRadio but also populates some information to an attached LCD.
68 | This example can be used with Arduino, ESP8266 and ESP32.
69 |
70 | * The **ScanRadio** is similar to the SerialRadio example
71 | and includes some experimental scanning approaches.
72 | This example can be used with Arduino, ESP8266 and ESP32.
73 |
74 | * The **LCDKeypadRadio** example uses the popular LCDKeypad shield for **Arduino UNO** only.
75 |
76 | * The **WebRadio** example is the most advanced radio that runs on an **Arduino Mega** with an Ethernet Shield and an rotator encoder.
77 | You can also control the radio by using a web site that is available on the Arduino.
78 |
79 | The only sending example for the SI4721 chip can be found in **TransmitSI4721**.
80 |
81 |
--------------------------------------------------------------------------------
/examples/TestSI47xx/TestSI47xx.ino:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file TestSI4721.ino
3 | /// \brief An Arduino sketch to operate a SI4720 and SI4721 chip based radio using the Radio library.
4 | ///
5 | /// \author N Poole, nickpoole.me
6 | /// \author Matthias Hertel, http://www.mathertel.de
7 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
8 | /// This work is licensed under a BSD 3-Clause License. See http://www.mathertel.de/License.aspx
9 | ///
10 | /// \details
11 | /// This sketch implements a "as simple as possible" radio without any possibility to modify the settings after initializing the chip.\n
12 | /// The radio chip is initialized and setup to a fixed band and frequency. These settings can be changed by modifying the
13 | /// FIX_BAND and FIX_STATION definitions.
14 | ///
15 | /// Open the Serial console with 115200 baud to see the current radio information.
16 | ///
17 | /// Wiring
18 | /// ------
19 | /// The SI4720 / SI4721 board/chip has to be connected by using the following connections:
20 |
21 | /// | Arduino UNO pin | ESP8266 | ESP32 | Radio chip signal |
22 | /// | --------------- | ------- | ----- | -------------------|
23 | /// | 3.3V / 5V | Vin | V5 | VCC |
24 | /// | GND | GND | GND | GND |
25 | /// | A5 or SCL | D1 | IO22 | SCLK |
26 | /// | A4 or SDA | D2 | IO21 | SDIO |
27 | ///
28 | /// More documentation is available at http://www.mathertel.de/Arduino
29 | /// Source Code is available on https://github.com/mathertel/Radio
30 | ///
31 | /// ChangeLog:
32 | /// ----------
33 | /// * 05.12.2019 created.
34 | /// * 18.05.2022 property oriented interface adapted.
35 | /// * 15.01.2023 cleanup compiler warnings.
36 |
37 | #include
38 | #include
39 |
40 | #include
41 | #include
42 |
43 | // ----- Fixed settings here. -----
44 |
45 | #define FIX_BAND RADIO_BAND_FM ///< The band that will be tuned by this sketch is FM.
46 | #define FIX_STATION 8930 ///< The station that will be tuned by this sketch is 89.30 MHz.
47 | #define FIX_VOLUME 10 ///< The volume that will be set by this sketch is level 4.
48 |
49 | SI47xx radio; // Create an instance of Class for SI47xx Chip
50 |
51 | /// Setup a FM only radio configuration
52 | /// with some debugging on the Serial port
53 | void setup() {
54 | delay(3000);
55 |
56 | // open the Serial port
57 | Serial.begin(115200);
58 | Serial.println("SI47xxRadio...");
59 | delay(200);
60 |
61 | #if defined(ARDUINO_ARCH_AVR)
62 |
63 | #elif defined(ESP8266)
64 | // For ESP8266 boards (like NodeMCU) the I2C GPIO pins in use
65 | // need to be specified.
66 | Wire.begin(D2, D1); // a common GPIO pin setting for I2C
67 |
68 | #elif defined(ESP32)
69 | Wire.begin(); // a common GPIO pin setting for I2C = SDA:21, SCL:22
70 |
71 | #endif
72 |
73 | // see if a chip can be found
74 | if (radio._wireExists(&Wire, 0x11)) {
75 | Serial.println("Device found at address 0x11");
76 | } else if (radio._wireExists(&Wire, 0x61)) {
77 | Serial.println("Device found at address 0x61");
78 | } else if (radio._wireExists(&Wire, 0x63)) {
79 | Serial.println("Device found at address 0x63");
80 | } else {
81 | Serial.println("Device NOT found at any address ");
82 | }
83 |
84 | Wire.begin(); // a common GPIO pin setting for I2C = SDA:21, SCL:22
85 |
86 | // Enable debug information to the Serial port
87 | radio.debugEnable(true);
88 | radio._wireDebug(false);
89 |
90 | // Set FM Options for Europe
91 | radio.setup(RADIO_FMSPACING, RADIO_FMSPACING_100); // for EUROPE
92 | radio.setup(RADIO_DEEMPHASIS, RADIO_DEEMPHASIS_50); // for EUROPE
93 |
94 | // Initialize the Radio
95 | radio.initWire(Wire);
96 |
97 | // radio.setDeemphasis(75); // Un-comment this line for USA
98 |
99 | // Set all radio setting to the fixed values.
100 | radio.setBandFrequency(FIX_BAND, FIX_STATION);
101 | radio.setVolume(FIX_VOLUME);
102 | radio.setMono(true);
103 | radio.setMute(false);
104 |
105 | radio.setup(RADIO_ANTENNA, RADIO_ANTENNA_OPT1);
106 | } // setup
107 |
108 |
109 | /// show the current chip data every 3 seconds.
110 | void loop() {
111 | char s[12];
112 | radio.formatFrequency(s, sizeof(s));
113 | Serial.print("Station:");
114 | Serial.println(s);
115 |
116 | Serial.print("Radio:");
117 | radio.debugRadioInfo();
118 |
119 | Serial.print("Audio:");
120 | radio.debugAudioInfo();
121 |
122 | delay(3000);
123 | } // loop
124 |
125 | // End.
126 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## [unpublished]
6 |
7 | * Runtime Configuration of de-emphasis and channel spacing.
8 |
9 | ``` cpp
10 | // Set FM Options for Europe
11 | radio.setup(RADIO_FMSPACING, RADIO_FMSPACING_100); // for EUROPE
12 | radio.setup(RADIO_DEEMPHASIS, RADIO_DEEMPHASIS_50); // for EUROPE
13 | ```
14 |
15 | This code was added to the examples but not all radio chip implementation do use it yet.
16 |
17 | * The initialization sequence on ESP32 is sensitive in case of SI4703
18 | as the SDA signal from I2C bus is also used to define the bus mode
19 | of the SI4703.
20 | It is important to NOT initialize the I2C bus before the reset of the radio chip.
21 |
22 |
23 |
24 | ## [3.0.0] - 2023-01-15
25 |
26 | This is a major update as some behaviors in the library have changed.
27 |
28 | ### Major Changes
29 |
30 | * RDSParser::attachServiceNameCallback has been renamed to avoid typo.
31 | * The radio library includes a new function `getMaxVolume` to retrieve the max. volume level supported by the chip.
32 | The setVolume function now accepts values in the range of 0..getMaxVolume().
33 | * Some functions have been corrected to use (const char *) instead of (char*) parameters to enforce that the passed strings are not changed
34 | (e.g. receiveServiceNameFunction or receiveTextFunction). Be sure to change the parameter type avoid casting errors/warnings.
35 | * The interfaces to the Radio chip functionality has been changed to be more a property oriented interface.
36 | Use the `radio.setup()` function to specify chip specific features and extra pins.
37 |
38 | ### Other changes
39 |
40 | * fixing warnings from ESP32 compiler
41 | * A lot inline and example documentation
42 | * The Wire object for I2C bus communication is now used through _i2cPort that can be initializes with initWire().
43 | This allows specifying the right i2c port in case the CPU supports multiple instances.
44 | * The ESP32 processor can be used to compile some of the examples:
45 | * SerialRadio.ino -- just uncomment the chip you have.
46 | * TestSI4721.ino -- this example works with the SI4720 and SI4721 radio chips.
47 | * RDS Error detection for SI4703
48 | * The `.clang-format` and `.markdownlint.json` files are provided to define formatting rules.
49 |
50 |
51 | ## [2.0.0] - 2020-09-17
52 |
53 | > **Important changes**
54 | >
55 | > This release focuses adding the si4721 radio chip to the library. The adoption was done using the board from sparkfun you can find here:
56 | > .
57 | >
58 | > This chip also supports sending FM signals, there is a special example 'TransmitSI4721.ino' to show this functionality.
59 |
60 | Thanks to [@NPoole](https://github.com/NPoole) for adding the adoption of the si4721 chip.
61 |
62 |
63 | ### Added Examples
64 |
65 | * **[TestSI4721.ino](/examples/TestSI4721/TestSI4721.md)** - This is the simple fixed settings example to proof correct functionality and wiring.
66 |
67 | * **[TransmitSI4721.ino](/examples/TransmitSI4721/TransmitSI4721.md)** - This example shows how to implement FM transmission using the SI4721 chip. Please respect your local radio transmission policies and rules by your government.
68 |
69 |
70 | ### Enhancements
71 |
72 | * The base radio class implementations now has some I2c utility routings that will be further adopted in the chip libraries.
73 | * Some code to initialize the I2C bus has been added to the examples to support ESP8266 boards.
74 |
75 | ### Fixes
76 |
77 | With some chips that support multiple modes the power up should be handles when specifying the mode in setBand and power situation will be handled according the following schema:
78 |
79 | * The init() function establishes communication with the radio chip. As a result the radio chip will not yet work and is in power down state.
80 | * The setBand() function will configure the radio chip and start operation. The radio chip will be in power up state.
81 | * The term() function will stop any operation. The radio chip will be in power down state. Communication with the chip will still work.
82 | * To implement a "standby" functionality setBand() for "on" and term() for "standby" should be used.
83 |
84 | This is verified for SI4721 for now.
85 |
86 | ### Known Issues
87 |
88 | The TransmitSI4721.ino example and the library doesn't offer all options for transmitting but some basic functionality.
89 | Improvements are welcome.
90 |
91 | ---
92 |
93 | ## Notes
94 |
95 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
96 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as used for the Arduino libraries.
97 |
--------------------------------------------------------------------------------
/src/SI4705.h:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file SI4705.h
3 | /// \brief Library header file for the radio library to control the SI4705 radio chip.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// This library enables the use of the Radio Chip SI4705.
11 | ///
12 | /// More documentation and source code is available at http://www.mathertel.de/Arduino
13 | ///
14 | /// ChangeLog:
15 | /// ----------
16 | /// * 05.12.2014 created.
17 | /// * 30.01.2015 working first version.
18 | /// * 07.02.2015 cleanup
19 | /// * 15.02.2015 RDS is working.
20 | /// * 27.03.2015 scanning is working. No changes to default settings needed.
21 | /// * 03.05.2015 softmute is working.
22 |
23 |
24 | #ifndef SI4705_h
25 | #define SI4705_h
26 |
27 | #include
28 |
29 | // The wire library is used for the communication with the radio chip.
30 | #include
31 |
32 | // Include the radio library that is extended by the SI4705 library.
33 | #include
34 |
35 | // ----- library definition -----
36 |
37 | /// Library to control the SI4705 radio chip.
38 | class SI4705 : public RADIO {
39 | public:
40 | SI4705();
41 |
42 | bool init(); ///< Initialize the library and the chip.
43 | void term(); ///< Terminate all radio functions in the chip.
44 |
45 | // ----- Audio functions -----
46 |
47 | void setVolume(int8_t newVolume) override; ///< Control the volume output of the radio chip in the range 0..15.
48 |
49 | void setMute(bool switchOn) override; ///< Control the mute mode of the radio chip.
50 | void setSoftMute(bool switchOn) override; ///< Control the softmute mode (mute on low signals) of the radio chip.
51 |
52 | // Overwrite audio functions that are not supported.
53 | void setBassBoost(bool switchOn) override; ///< regardless of the given parameter, the Bass Boost will never switch on.
54 |
55 | // ----- Radio receiver functions -----
56 |
57 | void setMono(bool switchOn) override; ///< Control the mono/stereo mode of the radio chip.
58 |
59 | void setBand(RADIO_BAND newBand) override; ///< Control the band of the radio chip.
60 |
61 | void setFrequency(RADIO_FREQ newF); ///< Control the frequency.
62 | RADIO_FREQ getFrequency(void);
63 |
64 | void seekUp(bool toNextSender = true); // start seek mode upwards
65 | void seekDown(bool toNextSender = true); // start seek mode downwards
66 |
67 | void checkRDS(); // read RDS data from the current station and process when data available.
68 |
69 | void getRadioInfo(RADIO_INFO *info);
70 | void getAudioInfo(AUDIO_INFO *info);
71 |
72 | // ----- debug Helpers send information to Serial port
73 |
74 | void debugScan(); // Scan all frequencies and report a status
75 | void debugStatus(); // Report Info about actual Station
76 |
77 | private:
78 | // ----- local variables
79 |
80 | uint8_t _realVolume; ///< The real volume set to the chip.
81 |
82 | // store the current status values
83 | uint8_t _status; ///< the status after sending a command
84 |
85 | uint8_t tuneStatus[8];
86 | uint8_t rsqStatus[1 + 7];
87 | uint8_t rdsStatusx[1 + 12];
88 | uint8_t agcStatus[1 + 2];
89 |
90 | /// structure used to read status information from the SI4705 radio chip.
91 | union {
92 | // use structured access
93 | struct {
94 | uint8_t status;
95 | uint8_t resp1;
96 | uint8_t resp2;
97 | uint8_t rdsFifoUsed;
98 | uint8_t blockAH; uint8_t blockAL;
99 | uint8_t blockBH; uint8_t blockBL;
100 | uint8_t blockCH; uint8_t blockCL;
101 | uint8_t blockDH; uint8_t blockDL;
102 | uint8_t blockErrors;
103 | };
104 | // use the the byte while receiving and sending.
105 | uint8_t buffer[1 + 7];
106 | } tuneStatus2; // union RDSSTATUS
107 |
108 |
109 | /// structure used to read RDS information from the SI4705 radio chip.
110 | union {
111 | // use structured access
112 | struct {
113 | uint8_t status;
114 | uint8_t resp1;
115 | uint8_t resp2;
116 | uint8_t rdsFifoUsed;
117 | uint8_t blockAH; uint8_t blockAL;
118 | uint8_t blockBH; uint8_t blockBL;
119 | uint8_t blockCH; uint8_t blockCL;
120 | uint8_t blockDH; uint8_t blockDL;
121 | uint8_t blockErrors;
122 | };
123 | // use the the byte while receiving and sending.
124 | uint8_t buffer[1 + 12];
125 | } rdsStatus; // union RDSSTATUS
126 |
127 |
128 | // ----- low level communication to the chip using I2C bus
129 |
130 | /// send a command
131 | void _sendCommand(int cnt, int cmd, ...);
132 |
133 | /// set a property
134 | void _setProperty(uint16_t prop, uint16_t value);
135 |
136 | /// read the interrupt status.
137 | uint8_t _readStatus();
138 |
139 | /// read status information into a buffer
140 | void _readStatusData(uint8_t cmd, uint8_t param, uint8_t *values, uint8_t len);
141 |
142 | void _seek(bool seekUp = true);
143 | void _waitEnd();
144 | };
145 |
146 | #endif
147 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | # Taken from https://github.com/arduino/arduino-language-server/blob/e453c5fbd059bae673bb21d028f5ca8e690744be/handler/handler.go#L1769-L1952
2 | # Which will be used in the IDE 2.0+ when 'format sketch' option is selected
3 |
4 | Language: Cpp
5 | # LLVM is the default style setting, used when a configuration option is not set here
6 | BasedOnStyle: LLVM
7 | AccessModifierOffset: -2
8 | AlignAfterOpenBracket: Align
9 | AlignConsecutiveAssignments: false
10 | AlignConsecutiveBitFields: false
11 | AlignConsecutiveDeclarations: false
12 | AlignConsecutiveMacros: false
13 | AlignEscapedNewlines: DontAlign
14 | AlignOperands: Align
15 | AlignTrailingComments: true
16 | AllowAllArgumentsOnNextLine: true
17 | AllowAllConstructorInitializersOnNextLine: true
18 | AllowAllParametersOfDeclarationOnNextLine: true
19 | AllowShortBlocksOnASingleLine: Always
20 | AllowShortCaseLabelsOnASingleLine: true
21 | AllowShortEnumsOnASingleLine: true
22 | AllowShortFunctionsOnASingleLine: Empty
23 | AllowShortIfStatementsOnASingleLine: Always
24 | AllowShortLambdasOnASingleLine: Empty
25 | AllowShortLoopsOnASingleLine: true
26 | AlwaysBreakAfterDefinitionReturnType: None
27 | AlwaysBreakAfterReturnType: None
28 | AlwaysBreakBeforeMultilineStrings: false
29 | AlwaysBreakTemplateDeclarations: No
30 | BinPackArguments: true
31 | BinPackParameters: true
32 | # Only used when "BreakBeforeBraces" set to "Custom"
33 | BraceWrapping:
34 | AfterCaseLabel: false
35 | AfterClass: false
36 | AfterControlStatement: Never
37 | AfterEnum: false
38 | AfterFunction: false
39 | AfterNamespace: false
40 | #AfterObjCDeclaration:
41 | AfterStruct: false
42 | AfterUnion: false
43 | AfterExternBlock: false
44 | BeforeCatch: false
45 | BeforeElse: false
46 | BeforeLambdaBody: false
47 | BeforeWhile: false
48 | IndentBraces: false
49 | SplitEmptyFunction: false
50 | SplitEmptyRecord: false
51 | SplitEmptyNamespace: false
52 | # Java-specific
53 | #BreakAfterJavaFieldAnnotations:
54 | BreakBeforeBinaryOperators: NonAssignment
55 | BreakBeforeBraces: Attach
56 | BreakBeforeTernaryOperators: true
57 | BreakConstructorInitializers: BeforeColon
58 | BreakInheritanceList: BeforeColon
59 | BreakStringLiterals: false
60 | # v12 has various problems with this set to 0 (no limit)
61 | # possible workaround is to set this to 4294967295 aka some large number
62 | # see https://reviews.llvm.org/D96896
63 | ColumnLimit: 0
64 | # "" matches none
65 | CommentPragmas: ""
66 | CompactNamespaces: false
67 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
68 | ConstructorInitializerIndentWidth: 2
69 | ContinuationIndentWidth: 2
70 | Cpp11BracedListStyle: false
71 | DeriveLineEnding: true
72 | DerivePointerAlignment: true
73 | DisableFormat: false
74 | # Docs say "Do not use this in config files". The default (LLVM 11.0.1) is "false".
75 | #ExperimentalAutoDetectBinPacking:
76 | FixNamespaceComments: false
77 | ForEachMacros: []
78 | IncludeBlocks: Preserve
79 | IncludeCategories: []
80 | # "" matches none
81 | IncludeIsMainRegex: ""
82 | IncludeIsMainSourceRegex: ""
83 | IndentCaseBlocks: true
84 | IndentCaseLabels: true
85 | IndentExternBlock: Indent
86 | IndentGotoLabels: false
87 | IndentPPDirectives: None
88 | IndentWidth: 2
89 | IndentWrappedFunctionNames: false
90 | InsertTrailingCommas: None
91 | # Java-specific
92 | #JavaImportGroups:
93 | # JavaScript-specific
94 | #JavaScriptQuotes:
95 | #JavaScriptWrapImports
96 | KeepEmptyLinesAtTheStartOfBlocks: true
97 | MacroBlockBegin: ""
98 | MacroBlockEnd: ""
99 | # Set to a large number to effectively disable
100 | MaxEmptyLinesToKeep: 100000
101 | NamespaceIndentation: None
102 | NamespaceMacros: []
103 | # Objective C-specific
104 | #ObjCBinPackProtocolList:
105 | #ObjCBlockIndentWidth:
106 | #ObjCBreakBeforeNestedBlockParam:
107 | #ObjCSpaceAfterProperty:
108 | #ObjCSpaceBeforeProtocolList
109 | PenaltyBreakAssignment: 1
110 | PenaltyBreakBeforeFirstCallParameter: 1
111 | PenaltyBreakComment: 1
112 | PenaltyBreakFirstLessLess: 1
113 | PenaltyBreakString: 1
114 | PenaltyBreakTemplateDeclaration: 1
115 | PenaltyExcessCharacter: 1
116 | PenaltyReturnTypeOnItsOwnLine: 1
117 | # Used as a fallback if alignment style can't be detected from code (DerivePointerAlignment: true)
118 | PointerAlignment: Right
119 | RawStringFormats: []
120 | ReflowComments: true
121 | SortIncludes: false
122 | SortUsingDeclarations: false
123 | SpaceAfterCStyleCast: false
124 | SpaceAfterLogicalNot: false
125 | SpaceAfterTemplateKeyword: false
126 | SpaceBeforeAssignmentOperators: true
127 | SpaceBeforeCpp11BracedList: false
128 | SpaceBeforeCtorInitializerColon: true
129 | SpaceBeforeInheritanceColon: true
130 | SpaceBeforeParens: ControlStatements
131 | SpaceBeforeRangeBasedForLoopColon: true
132 | SpaceBeforeSquareBrackets: false
133 | SpaceInEmptyBlock: false
134 | SpaceInEmptyParentheses: false
135 | SpacesBeforeTrailingComments: 2
136 | SpacesInAngles: false
137 | SpacesInCStyleCastParentheses: false
138 | SpacesInConditionalStatement: false
139 | SpacesInContainerLiterals: false
140 | SpacesInParentheses: false
141 | SpacesInSquareBrackets: false
142 | Standard: Auto
143 | StatementMacros: []
144 | TabWidth: 2
145 | TypenameMacros: []
146 | # Default to LF if line endings can't be detected from the content (DeriveLineEnding).
147 | UseCRLF: false
148 | UseTab: Never
149 | WhitespaceSensitiveMacros: []
--------------------------------------------------------------------------------
/src/RDSParser.cpp:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file RDSParser.cpp
3 | /// \brief RDS Parser class implementation.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// \details
11 | ///
12 | /// More documentation and source code is available at http://www.mathertel.de/Arduino
13 | ///
14 | /// ChangeLog see RDSParser.h.
15 |
16 | #include "RDSParser.h"
17 |
18 | #define DEBUG_FUNC0(fn) \
19 | { \
20 | Serial.print(fn); \
21 | Serial.println("()"); \
22 | }
23 |
24 | /// Setup the RDS object and initialize private variables to 0.
25 | RDSParser::RDSParser() {
26 | memset(this, 0, sizeof(RDSParser));
27 | } // RDSParser()
28 |
29 |
30 | void RDSParser::init() {
31 | strcpy(_PSName1, "11111111");
32 | strcpy(_PSName2, "22222222");
33 | strcpy(_PSName3, "33333333");
34 | strcpy(programServiceName, " ");
35 | strcpy(lastServiceName, " ");
36 | memset(_RDSText, 0, sizeof(_RDSText));
37 | _lastTextIDX = 0;
38 | } // init()
39 |
40 |
41 | void RDSParser::attachServiceNameCallback(receiveServiceNameFunction newFunction) {
42 | _sendServiceName = newFunction;
43 | } // attachServiceNameCallback
44 |
45 | void RDSParser::attachTextCallback(receiveTextFunction newFunction) {
46 | _sendText = newFunction;
47 | } // attachTextCallback
48 |
49 |
50 | void RDSParser::attachTimeCallback(receiveTimeFunction newFunction) {
51 | _sendTime = newFunction;
52 | } // attachTimeCallback
53 |
54 |
55 | void RDSParser::processData(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) {
56 | // DEBUG_FUNC0("process");
57 | uint8_t idx; // index of rdsText
58 | char c1, c2;
59 |
60 | uint16_t mins; ///< RDS time in minutes
61 | uint8_t off; ///< RDS time offset and sign
62 |
63 | // Serial.print('('); Serial.print(block1, HEX); Serial.print(' '); Serial.print(block2, HEX); Serial.print(' '); Serial.print(block3, HEX); Serial.print(' '); Serial.println(block4, HEX);
64 |
65 | if (block1 == 0) {
66 | // reset all the RDS info.
67 | init();
68 | // Send out empty data
69 | if (_sendServiceName) _sendServiceName(programServiceName);
70 | if (_sendText) _sendText("");
71 | return;
72 | } // if
73 |
74 | // analyzing Block 2
75 | rdsGroupType = 0x0A | ((block2 & 0xF000) >> 8) | ((block2 & 0x0800) >> 11);
76 | rdsTP = (block2 & 0x0400);
77 | rdsPTY = (block2 & 0x0400);
78 |
79 | switch (rdsGroupType) {
80 | case 0x0A:
81 | case 0x0B:
82 | // The data received is part of the Service Station Name
83 | idx = 2 * (block2 & 0x0003); // idx = 0, 2, 4, 6
84 |
85 | // new data is 2 chars from block 4
86 | c1 = block4 >> 8;
87 | c2 = block4 & 0x00FF;
88 |
89 | // Serial.printf(">%d %c%c %02x %02x\n", idx, c1, c2, c1, c2);
90 |
91 | // shift new data into _PSNameN
92 | _PSName3[idx] = _PSName2[idx];
93 | _PSName2[idx] = _PSName1[idx];
94 | _PSName1[idx] = c1;
95 |
96 | _PSName3[idx+1] = _PSName2[idx+1];
97 | _PSName2[idx+1] = _PSName1[idx+1];
98 | _PSName1[idx+1] = c2;
99 |
100 | // check that the data was received successfully twice
101 | // before publishing the station name
102 | if (idx == 6) {
103 | bool isGood = true;
104 | // create programServiceName with 2 of 3
105 | for (int n= 0; n < 8; n++) {
106 | if ((_PSName1[n] == _PSName2[n]) || (_PSName1[n] == _PSName3[n])) {
107 | programServiceName[n] = _PSName1[n];
108 |
109 | } else if (_PSName2[n] == _PSName3[n]) {
110 | programServiceName[n] = _PSName2[n];
111 |
112 | } else {
113 | isGood = false;
114 | }
115 | }
116 | if ((isGood) && (strcmp(lastServiceName, programServiceName) != 0)) {
117 | strcpy(lastServiceName, programServiceName);
118 | if (_sendServiceName)
119 | _sendServiceName(programServiceName);
120 | }
121 | } // if
122 | break;
123 |
124 | case 0x2A:
125 | // The data received is part of the RDS Text.
126 | _textAB = (block2 & 0x0010);
127 | idx = 4 * (block2 & 0x000F);
128 |
129 | if (idx < _lastTextIDX) {
130 | // the existing text might be complete because the index is starting at the beginning again.
131 | // now send it to the possible listener.
132 | if (_sendText)
133 | _sendText(_RDSText);
134 | }
135 | _lastTextIDX = idx;
136 |
137 | if (_textAB != _last_textAB) {
138 | // when this bit is toggled the whole buffer should be cleared.
139 | _last_textAB = _textAB;
140 | memset(_RDSText, 0, sizeof(_RDSText));
141 | // Serial.println("T>CLEAR");
142 | } // if
143 |
144 |
145 | // new data is 2 chars from block 3
146 | _RDSText[idx] = (block3 >> 8);
147 | idx++;
148 | _RDSText[idx] = (block3 & 0x00FF);
149 | idx++;
150 |
151 | // new data is 2 chars from block 4
152 | _RDSText[idx] = (block4 >> 8);
153 | idx++;
154 | _RDSText[idx] = (block4 & 0x00FF);
155 | idx++;
156 |
157 | // Serial.print(' '); Serial.println(_RDSText);
158 | // Serial.print("T>"); Serial.println(_RDSText);
159 | break;
160 |
161 | case 0x4A:
162 | // Clock time and date
163 | off = (block4)&0x3F; // 6 bits
164 | mins = (block4 >> 6) & 0x3F; // 6 bits
165 | mins += 60 * (((block3 & 0x0001) << 4) | ((block4 >> 12) & 0x0F));
166 |
167 | // adjust offset
168 | if (off & 0x20) {
169 | mins -= 30 * (off & 0x1F);
170 | } else {
171 | mins += 30 * (off & 0x1F);
172 | }
173 |
174 | if ((_sendTime) && (mins != _lastRDSMinutes)) {
175 | _lastRDSMinutes = mins;
176 | _sendTime(mins / 60, mins % 60);
177 | } // if
178 | break;
179 |
180 | case 0x6A:
181 | // IH
182 | break;
183 |
184 | case 0x8A:
185 | // TMC
186 | break;
187 |
188 | case 0xAA:
189 | // TMC
190 | break;
191 |
192 | case 0xCA:
193 | // TMC
194 | break;
195 |
196 | case 0xEA:
197 | // IH
198 | break;
199 |
200 | default:
201 | // Serial.print("RDS_GRP:"); Serial.println(rdsGroupType, HEX);
202 | break;
203 | }
204 | } // processData()
205 |
206 | // End.
--------------------------------------------------------------------------------
/src/SI47xx.h:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file SI47xx.h
3 | /// \brief Library header file for the radio library to control the SI47xx radio chips.
4 | ///
5 | /// \author N Poole, nickpoole.me
6 | /// \author Matthias Hertel, http://www.mathertel.de
7 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
8 | /// This work is licensed under a BSD style license.\n
9 | /// See http://www.mathertel.de/License.aspx
10 | ///
11 | /// This library enables the use of the Radio Chip SI47xx for receiving and transmitting.
12 | /// Settings are compatible top the following board from sparkfun: https://www.sparkfun.com/products/15853
13 | ///
14 | /// More documentation and source code is available at http://www.mathertel.de/Arduino
15 | ///
16 | /// ChangeLog:
17 | /// ----------
18 | /// * 01.12.2019 created.
19 | /// * 17.09.2020 si4721 specific initialization moved into setBand()
20 | /// * 04.12.2020 more si47xx chips support.
21 |
22 | #ifndef SI47xx_h
23 | #define SI47xx_h
24 |
25 | #include
26 |
27 | // The wire library is used for the communication with the radio chip.
28 | #include
29 |
30 | // Include the radio library that is extended by the SI47xx library.
31 | #include
32 |
33 | // A structure for storing ASQ Status and Audio Input Metrics
34 | struct ASQ_STATUS {
35 | uint8_t asq;
36 | uint8_t audioInLevel;
37 | };
38 |
39 | // A structure for storing TX Tuning Status
40 | struct TX_STATUS {
41 | uint16_t frequency;
42 | uint8_t dBuV;
43 | uint8_t antennaCap;
44 | uint8_t noiseLevel;
45 | };
46 |
47 | // ----- library definition -----
48 |
49 | /// Library to control the SI47xx radio chip.
50 | class SI47xx : public RADIO {
51 | public:
52 | SI47xx();
53 |
54 | void setup(int feature, int value) override;
55 |
56 | /** Initialize the library and the chip. */
57 | bool init();
58 |
59 | /** Terminate all radio functions in the chip. */
60 | void term();
61 |
62 | // ----- Audio functions -----
63 |
64 | void setVolume(int8_t newVolume) override; ///< Control the volume output of the radio chip in the range 0..15.
65 |
66 | void setMute(bool switchOn) override; ///< Control the mute mode of the radio chip.
67 | void setSoftMute(bool switchOn) override; ///< Control the softmute mode (mute on low signals) of the radio chip.
68 |
69 | // Overwrite audio functions that are not supported.
70 | void setBassBoost(bool switchOn) override; ///< regardless of the given parameter, the Bass Boost will never switch on.
71 |
72 | // ----- Radio receiver functions -----
73 |
74 | void setMono(bool switchOn) override; ///< Control the mono/stereo mode of the radio chip.
75 |
76 | /**
77 | * Start using the new band for receiving or transmitting.
78 | * This function resets the mode so it should not be called without good reason to avoid breaks.
79 | * @param newBand The new band to be enabled.
80 | * @return void
81 | */
82 | void setBand(RADIO_BAND newBand);
83 |
84 | void setFrequency(RADIO_FREQ newF); ///< Control the frequency.
85 | RADIO_FREQ getFrequency(void);
86 |
87 | void seekUp(bool toNextSender = true); // start seek mode upwards
88 | void seekDown(bool toNextSender = true); // start seek mode downwards
89 |
90 | void attachReceiveRDS(receiveRDSFunction newFunction) override; ///< Register a RDS processor function.
91 | void checkRDS(); // read RDS data from the current station and process when data available.
92 |
93 | void getRadioInfo(RADIO_INFO *info);
94 | void getAudioInfo(AUDIO_INFO *info);
95 |
96 | // ----- debug Helpers send information to Serial port
97 |
98 | void debugScan(); // Scan all frequencies and report a status
99 | void debugStatus(); // Report Info about actual Station
100 |
101 | // ----- transmit functions
102 |
103 | void beginRDS(uint16_t programID = 0xBEEF);
104 | void setRDSstation(char *s);
105 | void setRDSbuffer(char *s);
106 |
107 | uint8_t getTXPower();
108 | void setTXPower(uint8_t pwr);
109 |
110 | ASQ_STATUS getASQ();
111 | TX_STATUS getTuneStatus();
112 |
113 | // ----- regional compatibility
114 |
115 | void setDeemphasis(uint8_t uS); // set the deemphasis (50 for Europe, 75 for USA)
116 |
117 | private:
118 | // ----- local variables
119 | bool _hasRDS = false;
120 | bool _hasAM = false;
121 | bool _hasTX = false;
122 |
123 | uint8_t _fmDeemphasis = 50; ///< RX Deemphasis and TX Preemphasis in uS
124 |
125 | // store the current status values
126 | uint8_t _status; ///< the status after sending a command
127 |
128 | uint8_t tuneStatus[8];
129 | uint8_t rsqStatus[1 + 7];
130 | uint8_t rdsStatusx[1 + 12];
131 | uint8_t agcStatus[1 + 2];
132 |
133 | uint8_t _txPower;
134 |
135 | /// structure used to read status information from the SI47xx radio chip.
136 | union {
137 | // use structured access
138 | struct {
139 | uint8_t status;
140 | uint8_t resp1;
141 | uint8_t resp2;
142 | uint8_t rdsFifoUsed;
143 | uint8_t blockAH;
144 | uint8_t blockAL;
145 | uint8_t blockBH;
146 | uint8_t blockBL;
147 | uint8_t blockCH;
148 | uint8_t blockCL;
149 | uint8_t blockDH;
150 | uint8_t blockDL;
151 | uint8_t blockErrors;
152 | };
153 | // use the the byte while receiving and sending.
154 | uint8_t buffer[1 + 7];
155 | } tuneStatus2; // union RDSSTATUS
156 |
157 |
158 | /// structure used to read RDS information from the SI47xx radio chip.
159 | union {
160 | // use structured access
161 | struct {
162 | uint8_t status;
163 | uint8_t resp1;
164 | uint8_t resp2;
165 | uint8_t rdsFifoUsed;
166 | uint8_t blockAH;
167 | uint8_t blockAL;
168 | uint8_t blockBH;
169 | uint8_t blockBL;
170 | uint8_t blockCH;
171 | uint8_t blockCL;
172 | uint8_t blockDH;
173 | uint8_t blockDL;
174 | uint8_t blockErrors;
175 | };
176 | // use the the byte while receiving and sending.
177 | uint8_t buffer[1 + 12];
178 | } rdsStatus; // union RDSSTATUS
179 |
180 |
181 | // ----- low level communication to the chip using I2C bus
182 |
183 | /// send a command
184 | void _sendCommand(int cnt, int cmd, ...);
185 |
186 | /// set a property
187 | void _setProperty(uint16_t prop, uint16_t value);
188 |
189 | /// read the interrupt status.
190 | uint8_t _readStatus();
191 |
192 | /// read status information into a buffer
193 | void _readStatusData(uint8_t cmd, uint8_t param, uint8_t *values, uint8_t len);
194 |
195 | void _seek(bool seekUp = true);
196 | void _waitEnd();
197 | };
198 |
199 | #endif
200 |
--------------------------------------------------------------------------------
/src/TEA5767.cpp:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file TEA5767.cpp
3 | /// \brief Implementation for the radio library to control the TEA5767 radio chip.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014-2015 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// This library enables the use of the Radio Chip TEA5767.
11 | ///
12 | /// More documentation is available at http://www.mathertel.de/Arduino
13 | /// Source Code is available on https://github.com/mathertel/Radio
14 | ///
15 | /// good links for hints how to implement this chip:
16 | /// http://www.sparkfun.com/datasheets/Wireless/General/TEA5767.pdf
17 | /// http://www.rockbox.org/wiki/pub/Main/DataSheets/application_note_tea5767-8.pdf
18 | /// https://raw.githubusercontent.com/mroger/TEA5767/master/TEA5767N.cpp
19 | /// http://www.electronicsblog.net
20 | /// https://github.com/andykarpov/TEA5767/blob/master/TEA5767.cpp
21 | ///
22 | /// ChangeLog see TEA5767.h:
23 |
24 | #include
25 | #include // The chip is controlled via the standard Arduiino Wire library and the IIC/I2C bus.
26 |
27 | #include // Include the common radio library interface
28 | #include
29 |
30 | // ----- Definitions for the Wire communication
31 |
32 | #define TEA5767_ADR 0x60 // I2C address of TEA5767
33 |
34 | // ----- Radio chip specific definitions including the registers
35 |
36 | #define QUARTZ 32768
37 | #define FILTER 225000
38 |
39 | // Define the registers
40 |
41 | #define REG_1 0x00
42 | #define REG_1_MUTE 0x80
43 | #define REG_1_SM 0x40
44 | #define REG_1_PLL 0x3F
45 |
46 | #define REG_2 0x01
47 | #define REG_2_PLL 0xFF
48 |
49 | #define REG_3 0x02
50 | #define REG_3_MS 0x08
51 | #define REG_3_SSL 0x60
52 | #define REG_3_SUD 0x80
53 |
54 | #define REG_4 0x03
55 | #define REG_4_SMUTE 0x08
56 | #define REG_4_XTAL 0x10
57 | #define REG_4_BL 0x20
58 | #define REG_4_STBY 0x40
59 |
60 | #define REG_5 0x04
61 | #define REG_5_PLLREF 0x80
62 | #define REG_5_DTC 0x40
63 |
64 |
65 | #define STAT_3 0x02
66 | #define STAT_3_STEREO 0x80
67 |
68 | #define STAT_4 0x03
69 | #define STAT_4_ADC 0xF0
70 |
71 |
72 | // // Use this define to setup European FM specific settings in the chip.
73 | #define IN_EUROPE
74 |
75 | // ----- implement
76 |
77 | // initialize the extra variables in SI4703
78 | TEA5767::TEA5767() {
79 | _maxVolume = 1;
80 | }
81 |
82 | // initialize all internals.
83 | bool TEA5767::init() {
84 | bool result = false; // no chip found yet.
85 | DEBUG_FUNC0("init");
86 |
87 | RADIO::init(); // will create reset impulse
88 |
89 | registers[0] = 0x00;
90 | registers[1] = 0x00;
91 | registers[2] = 0xB0;
92 | registers[REG_4] = REG_4_XTAL | REG_4_SMUTE;
93 |
94 | #ifdef IN_EUROPE
95 | registers[REG_5] = 0; // 50 ms Europe setup
96 | #else
97 | registers[REG_5] = REG_5_DTC; // 75 ms Europe setup
98 | #endif
99 | Wire.begin();
100 |
101 | return(result);
102 | } // init()
103 |
104 |
105 | // switch the power off
106 | void TEA5767::term()
107 | {
108 | DEBUG_FUNC0("term");
109 | } // term
110 |
111 |
112 | // ----- Volume control -----
113 |
114 | /// setVolume is a non-existing function in TEA5767. It will always be 1.
115 | void TEA5767::setVolume(int8_t newVolume)
116 | {
117 | (void)newVolume;
118 | RADIO::setVolume(1);
119 | } // setVolume()
120 |
121 |
122 | /// setBassBoost is a non-existing function in TEA5767. It will never be activated.
123 | void TEA5767::setBassBoost(bool switchOn)
124 | {
125 | (void)switchOn;
126 | RADIO::setBassBoost(false);
127 | } // setBassBoost()
128 |
129 |
130 | /// force mono receiving mode.
131 | void TEA5767::setMono(bool switchOn)
132 | {
133 | DEBUG_FUNC0("setMono");
134 | RADIO::setMono(switchOn);
135 |
136 | if (switchOn) {
137 | registers[REG_3] |= REG_3_MS;
138 | } else {
139 | registers[REG_3] &= ~REG_3_MS;
140 | } // if
141 | _saveRegisters();
142 | } // setMono
143 |
144 |
145 | /// Force mute mode.
146 | void TEA5767::setMute(bool switchOn)
147 | {
148 | DEBUG_FUNC0("setMute");
149 | RADIO::setMute(switchOn);
150 |
151 | if (switchOn) {
152 | registers[REG_1] |= REG_1_MUTE;
153 | } else {
154 | registers[REG_1] &= ~REG_1_MUTE;
155 | } // if
156 | _saveRegisters();
157 | } // setMute()
158 |
159 |
160 | // ----- Band and frequency control methods -----
161 |
162 | /// Tune to new a band.
163 | void TEA5767::setBand(RADIO_BAND newBand) {
164 | if (newBand == RADIO_BAND_FM) {
165 |
166 | RADIO::setBand(newBand);
167 |
168 | #ifdef IN_EUROPE
169 | //Freq(MHz) = 0.100(in Europe) * Channel + 87.5MHz
170 | registers[REG_4] &= ~REG_4_BL;
171 |
172 | #else
173 | //Freq(MHz) = 0.200(in USA) * Channel + 87.5MHz
174 | registers[REG_4] |= REG_4_BL;
175 | #endif
176 |
177 | } // if
178 |
179 |
180 | // FM mixer for conversion to IF of the US/Europe (87.5 MHz to 108 MHz) and Japanese
181 | // (76 MHz to 91 MHz) FM ba
182 |
183 | } // setBand()
184 |
185 |
186 |
187 |
188 | /**
189 | * @brief Retrieve the real frequency from the chip after automatic tuning.
190 | * @return RADIO_FREQ the current frequency.
191 | */
192 | RADIO_FREQ TEA5767::getFrequency() {
193 | _readRegisters();
194 |
195 | unsigned long frequencyW = ((status[REG_1] & REG_1_PLL) << 8) | status[REG_2];
196 | frequencyW = ((frequencyW * QUARTZ / 4) - FILTER) / 10000;
197 |
198 | return ((RADIO_FREQ)frequencyW);
199 | } // getFrequency
200 |
201 |
202 | /**
203 | * @brief Change the frequency in the chip.
204 | * @param newF
205 | * @return void
206 | */
207 | void TEA5767::setFrequency(RADIO_FREQ newF) {
208 | DEBUG_FUNC1("setFrequency", newF);
209 | _freq = newF;
210 |
211 | unsigned int frequencyB = 4 * (newF * 10000L + FILTER) / QUARTZ;
212 | Serial.print('*'); Serial.println(frequencyB);
213 |
214 | registers[0] = frequencyB >> 8;
215 | registers[1] = frequencyB & 0XFF;
216 | _saveRegisters();
217 | delay(100);
218 | } // setFrequency()
219 |
220 |
221 | /// Start seek mode upwards.
222 | void TEA5767::seekUp(bool toNextSender) {
223 | DEBUG_FUNC0("seekUp");
224 | (void)toNextSender;
225 | _seek(true);
226 | } // seekUp()
227 |
228 |
229 | /// Start seek mode downwards.
230 | void TEA5767::seekDown(bool toNextSender) {
231 | (void)toNextSender;
232 | _seek(false);
233 | } // seekDown()
234 |
235 |
236 |
237 | /// Load all status registers from to the chip
238 | void TEA5767::_readRegisters()
239 | {
240 | Wire.requestFrom(TEA5767_ADR, 5); // We want to read all the 5 registers.
241 |
242 | if (Wire.available()) {
243 | for (uint8_t n = 0; n < 5; n++) {
244 | status[n] = Wire.read();
245 | } // for
246 | } // if
247 | } // _readRegisters
248 |
249 |
250 | // Save writable registers back to the chip
251 | // using the sequential write access mode.
252 | void TEA5767::_saveRegisters()
253 | {
254 | Wire.beginTransmission(TEA5767_ADR);
255 | for (uint8_t n = 0; n < sizeof(registers); n++) {
256 | Wire.write(registers[n]);
257 | } // for
258 |
259 | byte ack = Wire.endTransmission();
260 | if (ack != 0) { //We have a problem!
261 | Serial.print("Write Fail:"); //No ACK!
262 | Serial.println(ack, DEC); //I2C error: 0 = success, 1 = data too long, 2 = rx NACK on address, 3 = rx NACK on data, 4 = other error
263 | } // if
264 |
265 | } // _saveRegisters
266 |
267 |
268 | void TEA5767::getRadioInfo(RADIO_INFO *info) {
269 | RADIO::getRadioInfo(info);
270 |
271 | _readRegisters();
272 | if (status[STAT_3] & STAT_3_STEREO) info->stereo = true;
273 | info->rssi = (status[STAT_4] & STAT_4_ADC) >> 4;
274 |
275 | } // getRadioInfo()
276 |
277 |
278 | void TEA5767::getAudioInfo(AUDIO_INFO *info) {
279 | RADIO::getAudioInfo(info);
280 | } // getAudioInfo()
281 |
282 |
283 | void TEA5767::checkRDS()
284 | {
285 | // DEBUG_FUNC0("checkRDS");
286 | } // checkRDS
287 |
288 | // ----- Debug functions -----
289 |
290 | /// Send the current values of all registers to the Serial port.
291 | void TEA5767::debugStatus()
292 | {
293 | RADIO::debugStatus();
294 | } // debugStatus
295 |
296 |
297 | /// Seeks out the next available station
298 | void TEA5767::_seek(bool seekUp) {
299 | DEBUG_FUNC0("_seek");
300 | (void)seekUp;
301 | } // _seek
302 |
303 |
304 | /// wait until the current seek and tune operation is over.
305 | void TEA5767::_waitEnd() {
306 | DEBUG_FUNC0("_waitEnd");
307 |
308 | } // _waitEnd()
309 |
310 |
311 |
312 | // ----- internal functions -----
313 |
314 | // The End.
315 |
316 |
317 |
--------------------------------------------------------------------------------
/examples/LCDKeypadRadio/LCDKeypadRadio.ino:
--------------------------------------------------------------------------------
1 | /// \file LCDKeypadRadio.ino
2 | /// \brief Radio implementation using the Serial communication.
3 | ///
4 | /// \author Matthias Hertel, http://www.mathertel.de
5 | /// \copyright Copyright (c) 2014 by Matthias Hertel.\n
6 | /// This work is licensed under a BSD style license.\n
7 | /// See http://www.mathertel.de/License.aspx
8 | ///
9 | /// \details
10 | /// This is a full function radio implementation that uses a LCD display to show the current station information.\n
11 | /// It can be used with various chips after adjusting the radio object definition.\n
12 | /// Open the Serial console with 115200 baud to see current radio information and change various settings.
13 | ///
14 | /// Wiring
15 | /// ------
16 | /// The necessary wiring of the various chips are described in the Testxxx example sketches.
17 | /// The boards have to be connected e. g. by using the following connections:
18 | ///
19 | /// Arduino port | SI4703 signal | RDA5807M signal
20 | /// :----------: | :-----------: | :-------------:
21 | /// GND | GND | GND
22 | /// 3.3V | VCC | VCC
23 | /// A5 | SCLK | SCLK
24 | /// A4 | SDIO | SDIO
25 | /// D2 | RST | -
26 | ///
27 | /// More documentation is available at http://www.mathertel.de/Arduino
28 | /// Source Code is available on https://github.com/mathertel/Radio
29 | ///
30 | /// History:
31 | /// --------
32 | /// * 05.08.2014 created.
33 | /// * 04.10.2014 working.
34 | /// * 22.03.2015 Copying to LCDKeypadRadio.
35 | /// * 23.01.2023 more robust, nicer LCD messages and cleanup.
36 | /// * 30.01.2023 avoid using analogRead() too often.
37 |
38 | #include
39 |
40 | #include
41 |
42 | #include
43 | #include
44 | #include
45 | #include
46 | #include
47 | #include
48 |
49 | #include
50 |
51 | // Define some stations available at your locations here:
52 | // 89.30 MHz as 8930
53 |
54 | RADIO_FREQ preset[] = {
55 | 8930, // Sender:< hr3 >
56 | 9060, // Sender:< hr1 >
57 | 9310, //
58 | 9340, // Sender:
59 | 9440, // Sender:< hr1 >
60 | 9530, // Sender:< YOU FM >
61 | 9670, // Sender:< hr2 >
62 | 9870, // Sender:< Dlf >
63 | 10020, // Sender:< planet >
64 | 10140, // Sender:
65 | 10160, // Sender:< hr4 >
66 | 10300 // Sender:
67 | };
68 |
69 | uint16_t presetIndex = 0; ///< Start at Station with index = 1
70 |
71 |
72 | // ===== SI4703 specific pin wiring =====
73 | // #define ENABLE_SI4703
74 |
75 | #ifdef ENABLE_SI4703
76 | #if defined(ARDUINO_ARCH_AVR)
77 | #define RESET_PIN 2
78 | #define MODE_PIN A4 // same as SDA
79 |
80 | #elif defined(ESP8266)
81 | #define RESET_PIN D5
82 | #define MODE_PIN D2 // same as SDA
83 |
84 | #elif defined(ESP32)
85 | #define RESET_PIN 4
86 | #define MODE_PIN 21 // same as SDA
87 |
88 | #endif
89 | #endif
90 |
91 |
92 | // The keys available on the keypad
93 | enum KEYSTATE {
94 | KEYSTATE_NONE,
95 | KEYSTATE_SELECT,
96 | KEYSTATE_LEFT,
97 | KEYSTATE_UP,
98 | KEYSTATE_DOWN,
99 | KEYSTATE_RIGHT
100 | } __attribute__((packed));
101 |
102 |
103 | // ----- forwards -----
104 | // You need this because the function is not returning a simple value.
105 | KEYSTATE getLCDKeypadKey();
106 |
107 | /// The radio object has to be defined by using the class corresponding to the used chip.
108 | /// by uncommenting the right radio object definition.
109 |
110 | // RADIO radio; ///< Create an instance of a non functional radio.
111 | RDA5807M radio; ///< Create an instance of a RDA5807 chip radio
112 | // SI4703 radio; ///< Create an instance of a SI4703 chip radio.
113 | // SI4705 radio; ///< Create an instance of a SI4705 chip radio.
114 | // SI47xx radio; ///< Create an instance of a SI4721 chip radio.
115 | // TEA5767 radio; ///< Create an instance of a TEA5767 chip radio.
116 |
117 |
118 | /// get a RDS parser
119 | RDSParser rds;
120 |
121 | unsigned long nextFreqTime = 0;
122 | unsigned long nextKeyTime = 0;
123 | unsigned long nextClearTime = 0;
124 |
125 | bool doReportKey = false;
126 | KEYSTATE lastKey = KEYSTATE_NONE;
127 |
128 |
129 | // - - - - - - - - - - - - - - - - - - - - - - - - - -
130 |
131 |
132 | // initialize the library with the numbers of the interface pins
133 | LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
134 |
135 |
136 | /// This function will be when a new frequency is received.
137 | /// Update the Frequency on the LCD display.
138 | void DisplayFrequency() {
139 | char s[12];
140 | radio.formatFrequency(s, sizeof(s));
141 | lcd.setCursor(0, 0);
142 | lcd.print(s);
143 | } // DisplayFrequency()
144 |
145 |
146 | /// This function will be called by the RDS module when a new ServiceName is available.
147 | /// Update the LCD to display the ServiceName in row 1 chars 0 to 7.
148 | void DisplayServiceName(const char *name) {
149 | size_t len = strlen(name);
150 | lcd.setCursor(0, 1);
151 | lcd.print(name);
152 | while (len < 8) {
153 | lcd.print(' ');
154 | len++;
155 | } // while
156 | } // DisplayServiceName()
157 |
158 |
159 | /// This function will be called by the RDS module when a rds time message was received.
160 | /// Update the LCD to display the time in right upper corner.
161 | void DisplayTime(uint8_t hour, uint8_t minute) {
162 | lcd.setCursor(11, 0);
163 | if (hour < 10) lcd.print('0');
164 | lcd.print(hour);
165 | lcd.print(':');
166 | if (minute < 10) lcd.print('0');
167 | lcd.println(minute);
168 | } // DisplayTime()
169 |
170 |
171 | // - - - - - - - - - - - - - - - - - - - - - - - - - -
172 |
173 |
174 | void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) {
175 | rds.processData(block1, block2, block3, block4);
176 | }
177 |
178 |
179 | /// This function determines the current pressed key but
180 | /// * doesn't return short key down situations that are key signal bouncing.
181 | /// * returns a specific key down only once.
182 | ///
183 | /// [Next] [seek--] [Vol+] [seek++] [RST]
184 | /// [Vol-]
185 | ///
186 | KEYSTATE getLCDKeypadKey() {
187 | KEYSTATE newKey = KEYSTATE_NONE;
188 |
189 | // Get current key state
190 | int v = analogRead(A0); // the buttons are read from the analog0 pin
191 |
192 | if (v < 100) {
193 | newKey = KEYSTATE_RIGHT;
194 | } else if (v < 200) {
195 | newKey = KEYSTATE_UP;
196 | } else if (v < 400) {
197 | newKey = KEYSTATE_DOWN;
198 | } else if (v < 600) {
199 | newKey = KEYSTATE_LEFT;
200 | } else if (v < 800) {
201 | newKey = KEYSTATE_SELECT;
202 | } else {
203 | newKey = KEYSTATE_NONE;
204 | }
205 |
206 | if (newKey != lastKey) {
207 | // a new situation - just remember but do not return anything pressed.
208 | doReportKey = true;
209 | lastKey = newKey;
210 | return (KEYSTATE_NONE);
211 |
212 | } else if ((newKey == lastKey) && (doReportKey)) {
213 | doReportKey = false; // don't report twice
214 | return (newKey);
215 | }
216 | return (KEYSTATE_NONE);
217 | } // getLCDKeypadKey()
218 |
219 |
220 | void displayKeyValue(const char *key, int value) {
221 | lcd.setCursor(0, 1);
222 | lcd.print(key);
223 | lcd.print(": ");
224 | lcd.print(value);
225 | lcd.print(" ");
226 | } // displayKeyValue
227 |
228 |
229 | /// Setup a FM only radio configuration with I/O for commands and debugging on the Serial port.
230 | void setup() {
231 | delay(1000);
232 | // open the Serial port
233 | Serial.begin(115200);
234 | Serial.println("LCDKeypadRadio...");
235 |
236 | // set up the LCD's number of columns and rows:
237 | lcd.begin(16, 2);
238 | lcd.clear();
239 | lcd.setCursor(0, 0);
240 | lcd.print("LCDKeypadRadio");
241 | delay(2000);
242 | lcd.clear();
243 |
244 | #if defined(RESET_PIN)
245 | // This is required for SI4703 chips:
246 | radio.setup(RADIO_RESETPIN, RESET_PIN);
247 | radio.setup(RADIO_MODEPIN, MODE_PIN);
248 | #endif
249 |
250 | // Initialize the Radio
251 | bool found = radio.initWire(Wire);
252 |
253 | if (!found) {
254 | Serial.println("No Radio Chip found.");
255 | Serial.println("Press reset...");
256 |
257 | lcd.setCursor(0, 0);
258 | lcd.print("No Radio Chip");
259 | lcd.setCursor(0, 1);
260 | lcd.print("Press reset...");
261 |
262 | while (1) {}; // forever
263 | }
264 |
265 | // Enable information to the Serial port
266 | radio.debugEnable();
267 |
268 | radio.setBandFrequency(RADIO_BAND_FM, preset[presetIndex]); // first preset.
269 |
270 | radio.setMono(false);
271 | radio.setMute(false);
272 | radio.setVolume(radio.getMaxVolume());
273 |
274 | // setup the information chain for RDS data.
275 | radio.attachReceiveRDS(RDS_process);
276 | rds.attachServiceNameCallback(DisplayServiceName);
277 | rds.attachTimeCallback(DisplayTime);
278 | } // Setup
279 |
280 |
281 | /// Constantly check for keypad and trigger command execution.
282 | void loop() {
283 | unsigned long now = millis();
284 |
285 | // check for RDS data
286 | radio.checkRDS();
287 |
288 | // some internal static values for parsing the input
289 | static RADIO_FREQ lastFrequency = 0;
290 | RADIO_FREQ f = 0;
291 |
292 | // detect any key press on LCD Keypad Module
293 | if (now > nextKeyTime) {
294 | nextKeyTime = now + 100;
295 |
296 | KEYSTATE k = getLCDKeypadKey();
297 |
298 | if (k == KEYSTATE_RIGHT) {
299 | radio.seekUp(true);
300 |
301 | } else if (k == KEYSTATE_UP) {
302 | // increase volume
303 | int v = radio.getVolume();
304 | if (v < radio.getMaxVolume()) radio.setVolume(++v);
305 | displayKeyValue("vol", v);
306 | nextClearTime = now + 2000;
307 |
308 | } else if (k == KEYSTATE_DOWN) {
309 | // decrease volume
310 | int v = radio.getVolume();
311 | if (v > 0) radio.setVolume(--v);
312 | displayKeyValue("vol", v);
313 | nextClearTime = now + 2000;
314 |
315 | } else if (k == KEYSTATE_LEFT) {
316 | radio.seekDown(true);
317 |
318 | } else if (k == KEYSTATE_SELECT) {
319 | if (presetIndex < (sizeof(preset) / sizeof(RADIO_FREQ)) - 1) {
320 | presetIndex++;
321 | } else {
322 | presetIndex = 0;
323 | }
324 |
325 | radio.setFrequency(preset[presetIndex]);
326 | nextClearTime = now;
327 | }
328 | } // if
329 |
330 | // update the display from time to time
331 | if (now > nextFreqTime) {
332 | f = radio.getFrequency();
333 | if (f != lastFrequency) {
334 | // print current tuned frequency
335 | DisplayFrequency();
336 | lastFrequency = f;
337 | } // if
338 | nextFreqTime = now + 400;
339 | } // if
340 |
341 | // clear 2. line of display
342 | if ((nextClearTime) && (now > nextClearTime)) {
343 | lcd.setCursor(0, 1);
344 | lcd.print(" ");
345 | nextClearTime = 0;
346 | } // if
347 | } // loop
348 |
349 | // End.
350 |
--------------------------------------------------------------------------------
/examples/SerialRadio/SerialRadio.ino:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file SerialRadio.ino
3 | /// \brief Radio implementation using the Serial communication.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) by Matthias Hertel.\n
7 | /// This work is licensed under a BSD 3-Clause license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// \details
11 | /// This is a Arduino sketch radio implementation that can be controlled using commands on the Serial input.
12 | /// It can be used with various chips after adjusting the radio object definition.\n
13 | /// Open the Serial console with 115200 baud to see current radio information and change various settings.
14 | ///
15 | /// Wiring
16 | /// ------
17 | /// The necessary wiring of the various chips are described in the Testxxx example sketches.
18 | /// No additional components are required because all is done through the serial interface.
19 | ///
20 | /// More documentation is available at http://www.mathertel.de/Arduino
21 | /// Source Code is available on https://github.com/mathertel/Radio
22 | ///
23 | /// History:
24 | /// --------
25 | /// * 05.08.2014 created.
26 | /// * 04.10.2014 working.
27 | /// * 15.01.2023 ESP32, cleanup compiler warnings.
28 |
29 | #include
30 | #include
31 |
32 | #include
33 |
34 | // all possible radio chips included.
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include
40 | #include
41 |
42 | #include
43 |
44 |
45 | // Define some stations available at your locations here:
46 | // 89.30 MHz as 8930
47 |
48 | RADIO_FREQ preset[] = {
49 | 8930, // Sender:< hr3 >
50 | 9060, // Sender:< hr1 >
51 | 9310, //
52 | 9340, // Sender:
53 | 9440, // Sender:< hr1 >
54 | 9530, // Sender:< YOU FM >
55 | 9670, // Sender:< hr2 >
56 | 9870, // Sender:< Dlf >
57 | 10020, // Sender:< planet >
58 | 10140, // Sender:
59 | 10160, // Sender:< hr4 >
60 | 10300 // Sender:
61 | };
62 |
63 | uint16_t presetIndex = 0; ///< Start at Station with index = 1
64 |
65 |
66 | // ===== SI4703 specific pin wiring =====
67 | #define ENABLE_SI4703
68 |
69 | #ifdef ENABLE_SI4703
70 | #if defined(ARDUINO_ARCH_AVR)
71 | #define RESET_PIN 2
72 | #define MODE_PIN A4 // same as SDA
73 |
74 | #elif defined(ESP8266)
75 | #define RESET_PIN D5
76 | #define MODE_PIN D2 // same as SDA
77 |
78 | #elif defined(ESP32)
79 | #define RESET_PIN 4
80 | #define MODE_PIN 21 // same as SDA
81 |
82 | #endif
83 | #endif
84 |
85 |
86 | // Standard I2C/Wire pins for Arduino UNO: = SDA:A4, SCL:A5
87 | // Standard I2C/Wire pins for ESP8266: SDA:D2, SCL:D1
88 | // Standard I2C/Wire pins for ESP32: SDA:21, SCL:22
89 |
90 | /// The radio object has to be defined by using the class corresponding to the used chip.
91 | /// by uncommenting the right radio object definition.
92 |
93 | // RADIO radio; ///< Create an instance of a non functional radio.
94 | // RDA5807FP radio; ///< Create an instance of a RDA5807FP chip radio
95 | // RDA5807M radio; ///< Create an instance of a RDA5807M chip radio
96 | SI4703 radio; ///< Create an instance of a SI4703 chip radio.
97 | // SI4705 radio; ///< Create an instance of a SI4705 chip radio.
98 | // SI47xx radio; ///< Create an instance of a SI4720,21,30 (and maybe more) chip radio.
99 | // TEA5767 radio; ///< Create an instance of a TEA5767 chip radio.
100 |
101 | /// get a RDS parser
102 | RDSParser rds;
103 |
104 |
105 | /// State of Keyboard input for this radio implementation.
106 | enum RADIO_STATE {
107 | STATE_PARSECOMMAND, ///< waiting for a new command character.
108 | STATE_PARSEINT, ///< waiting for digits for the parameter.
109 | STATE_EXEC ///< executing the command.
110 | };
111 |
112 | RADIO_STATE kbState; ///< The state of parsing input characters.
113 | char kbCommand;
114 | int16_t kbValue;
115 |
116 | bool lowLevelDebug = false;
117 |
118 |
119 | /// Update the Frequency on the LCD display.
120 | void DisplayFrequency() {
121 | char s[12];
122 | radio.formatFrequency(s, sizeof(s));
123 | Serial.print("FREQ:");
124 | Serial.println(s);
125 | } // DisplayFrequency()
126 |
127 |
128 | /// Update the ServiceName text on the LCD display.
129 | void DisplayServiceName(const char *name) {
130 | Serial.print("RDS:");
131 | Serial.println(name);
132 | } // DisplayServiceName()
133 |
134 |
135 | // - - - - - - - - - - - - - - - - - - - - - - - - - -
136 |
137 |
138 | void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) {
139 | rds.processData(block1, block2, block3, block4);
140 | }
141 |
142 |
143 | /// Execute a command identified by a character and an optional number.
144 | /// See the "?" command for available commands.
145 | /// \param cmd The command character.
146 | /// \param value An optional parameter for the command.
147 | void runSerialCommand(char cmd, int16_t value) {
148 | if (cmd == '?') {
149 | Serial.println();
150 | Serial.println("? Help");
151 | Serial.println("+ increase volume");
152 | Serial.println("- decrease volume");
153 | Serial.println("> next preset");
154 | Serial.println("< previous preset");
155 | Serial.println(". scan up : scan up to next sender");
156 | Serial.println(", scan down ; scan down to next sender");
157 | Serial.println("fnnnnn: direct frequency input");
158 | Serial.println("i station status");
159 | Serial.println("s mono/stereo mode");
160 | Serial.println("b bass boost");
161 | Serial.println("u mute/unmute");
162 | }
163 |
164 | // ----- control the volume and audio output -----
165 |
166 | else if (cmd == '+') {
167 | // increase volume
168 | int v = radio.getVolume();
169 | radio.setVolume(++v);
170 | } else if (cmd == '-') {
171 | // decrease volume
172 | int v = radio.getVolume();
173 | if (v > 0)
174 | radio.setVolume(--v);
175 | }
176 |
177 | else if (cmd == 'u') {
178 | // toggle mute mode
179 | radio.setMute(!radio.getMute());
180 | }
181 |
182 | // toggle stereo mode
183 | else if (cmd == 's') {
184 | radio.setMono(!radio.getMono());
185 | }
186 |
187 | // toggle bass boost
188 | else if (cmd == 'b') {
189 | radio.setBassBoost(!radio.getBassBoost());
190 | }
191 |
192 | // ----- control the frequency -----
193 |
194 | else if (cmd == '>') {
195 | // next preset
196 | if (presetIndex < (sizeof(preset) / sizeof(RADIO_FREQ)) - 1) {
197 | presetIndex++;
198 | radio.setFrequency(preset[presetIndex]);
199 | } // if
200 | } else if (cmd == '<') {
201 | // previous preset
202 | if (presetIndex > 0) {
203 | presetIndex--;
204 | radio.setFrequency(preset[presetIndex]);
205 | } // if
206 |
207 | } else if (cmd == 'f') {
208 | radio.setFrequency(value);
209 | }
210 |
211 | else if (cmd == '.') {
212 | radio.seekUp(false);
213 | } else if (cmd == ':') {
214 | radio.seekUp(true);
215 | } else if (cmd == ',') {
216 | radio.seekDown(false);
217 | } else if (cmd == ';') {
218 | radio.seekDown(true);
219 | }
220 |
221 |
222 | else if (cmd == '!') {
223 | // not in help
224 | RADIO_FREQ f = radio.getFrequency();
225 | if (value == 0) {
226 | radio.term();
227 | } else if (value == 1) {
228 | radio.initWire(Wire);
229 | radio.setBandFrequency(RADIO_BAND_FM, f);
230 | radio.setVolume(10);
231 | }
232 |
233 | } else if (cmd == 'i') {
234 | // info
235 | char s[12];
236 | radio.formatFrequency(s, sizeof(s));
237 | Serial.print("Station:");
238 | Serial.println(s);
239 | Serial.print("Radio:");
240 | radio.debugRadioInfo();
241 | Serial.print("Audio:");
242 | radio.debugAudioInfo();
243 |
244 | } else if (cmd == 'x') {
245 | radio.debugStatus(); // print chip specific data.
246 |
247 | } else if (cmd == '*') {
248 | lowLevelDebug = !lowLevelDebug;
249 | radio._wireDebug(lowLevelDebug);
250 | }
251 | } // runSerialCommand()
252 |
253 |
254 | /// Setup a FM only radio configuration with I/O for commands and debugging on the Serial port.
255 | void setup() {
256 | delay(3000);
257 |
258 | // open the Serial port
259 | Serial.begin(115200);
260 | Serial.println("SerialRadio...");
261 | delay(200);
262 |
263 | #if defined(RESET_PIN)
264 | // This is required for SI4703 chips:
265 | radio.setup(RADIO_RESETPIN, RESET_PIN);
266 | radio.setup(RADIO_MODEPIN, MODE_PIN);
267 | #endif
268 |
269 | // Enable information to the Serial port
270 | radio.debugEnable(true);
271 | radio._wireDebug(lowLevelDebug);
272 |
273 | // Set FM Options for Europe
274 | radio.setup(RADIO_FMSPACING, RADIO_FMSPACING_100); // for EUROPE
275 | radio.setup(RADIO_DEEMPHASIS, RADIO_DEEMPHASIS_50); // for EUROPE
276 |
277 | // Initialize the Radio
278 | if (!radio.initWire(Wire)) {
279 | Serial.println("no radio chip found.");
280 | delay(4000);
281 | while (1) {};
282 | };
283 |
284 | radio.setBandFrequency(RADIO_BAND_FM, preset[presetIndex]);
285 |
286 | radio.setMono(false);
287 | radio.setMute(false);
288 | radio.setVolume(radio.getMaxVolume() / 2);
289 |
290 | // setup the information chain for RDS data.
291 | radio.attachReceiveRDS(RDS_process);
292 | rds.attachServiceNameCallback(DisplayServiceName);
293 |
294 | runSerialCommand('?', 0);
295 | kbState = STATE_PARSECOMMAND;
296 | } // Setup
297 |
298 |
299 | /// Constantly check for serial input commands and trigger command execution.
300 | void loop() {
301 | unsigned long now = millis();
302 | static unsigned long nextFreqTime = 0;
303 |
304 | // some internal static values for parsing the input
305 | static RADIO_FREQ lastFrequency = 0;
306 | RADIO_FREQ f = 0;
307 |
308 | if (Serial.available() > 0) {
309 | // read the next char from input.
310 | char c = Serial.peek();
311 |
312 | if ((kbState == STATE_PARSECOMMAND) && (c < 0x20)) {
313 | // ignore unprintable chars
314 | Serial.read();
315 |
316 | } else if (kbState == STATE_PARSECOMMAND) {
317 | // read a command.
318 | kbCommand = Serial.read();
319 | kbState = STATE_PARSEINT;
320 |
321 | } else if (kbState == STATE_PARSEINT) {
322 | if ((c >= '0') && (c <= '9')) {
323 | // build up the value.
324 | c = Serial.read();
325 | kbValue = (kbValue * 10) + (c - '0');
326 | } else {
327 | // not a value -> execute
328 | runSerialCommand(kbCommand, kbValue);
329 | kbCommand = ' ';
330 | kbState = STATE_PARSECOMMAND;
331 | kbValue = 0;
332 | } // if
333 | } // if
334 | } // if
335 |
336 | // check for RDS data
337 | radio.checkRDS();
338 |
339 | // update the display from time to time
340 | if (now > nextFreqTime) {
341 | f = radio.getFrequency();
342 | if (f != lastFrequency) {
343 | // print current tuned frequency
344 | DisplayFrequency();
345 | lastFrequency = f;
346 | } // if
347 | nextFreqTime = now + 400;
348 | } // if
349 |
350 | } // loop
351 |
352 | // End.
353 |
--------------------------------------------------------------------------------
/examples/ScanRadio/ScanRadio.ino:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file ScanRadio.ino
3 | /// \brief This sketch implements a FM scanner that lists all availabe radio stations including some information.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) by Matthias Hertel.\n
7 | /// This work is licensed under a BSD 3-Clause license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// \details
11 | /// This is a Arduino sketch radio implementation that can be controlled using commands on the Serial input.
12 | /// There are some experimental scan algorythms implemented that uses a state machine to scan through all radio stations
13 | /// the radio chip can detect and outputs them on the Serial interface.
14 | /// Open the Serial console with 115200 baud to see current radio information and change various settings.
15 | ///
16 | /// Wiring
17 | /// ------
18 | /// The necessary wiring of the various chips are described in the Testxxx example sketches.
19 | /// No additional components are required because all is done through the serial interface.
20 | ///
21 | /// More documentation is available at http://www.mathertel.de/Arduino
22 | /// Source Code is available on https://github.com/mathertel/Radio
23 | ///
24 | /// History:
25 | /// --------
26 | /// * 17.05.2015 created.
27 | /// * 27.05.2015 first version is working (beta with SI4705).
28 | /// * 04.07.2015 2 scan algorithms working with good results with SI4705.
29 | /// * 18.09.2020 more RDS output, better command handling.
30 |
31 | #include
32 | #include
33 | #include
34 |
35 | // all possible radio chips included.
36 | #include
37 | #include
38 | #include
39 | #include
40 | #include
41 |
42 | #include
43 |
44 |
45 | // ===== SI4703 specific pin wiring =====
46 | #define ENABLE_SI4703
47 |
48 | #ifdef ENABLE_SI4703
49 | #if defined(ARDUINO_ARCH_AVR)
50 | #define RESET_PIN 2
51 | #define MODE_PIN A4 // same as SDA
52 |
53 | #elif defined(ESP8266)
54 | #define RESET_PIN D5
55 | #define MODE_PIN D2 // same as SDA
56 |
57 | #elif defined(ESP32)
58 | #define RESET_PIN 4
59 | #define MODE_PIN 21 // same as SDA
60 |
61 | #endif
62 | #endif
63 |
64 |
65 | /// The radio object has to be defined by using the class corresponding to the used chip.
66 | /// by uncommenting the right radio object definition.
67 |
68 | /// Create the radio instance that fits the current chip:
69 | // RDA5807M radio; ///< Create an instance of a RDA5807 chip radio
70 | SI4703 radio; ///< Create an instance of a SI4703 chip radio.
71 | // SI4705 radio; ///< Create an instance of a SI4705 chip radio.
72 | // SI47xx radio; ///< Create an instance of a SI4705 chip radio.
73 | // TEA5767 radio; ///< Create an instance of a TEA5767 chip radio.
74 |
75 | // Standard I2C/Wire pins for Arduino UNO: = SDA:A4, SCL:A5
76 | // Standard I2C/Wire pins for ESP8266: SDA:D2, SCL:D1
77 | // Standard I2C/Wire pins for ESP32: SDA:21, SCL:22
78 |
79 | /// get a RDS parser
80 | RDSParser rds;
81 |
82 |
83 | /// State of Keyboard input for this radio implementation.
84 | enum RADIO_STATE {
85 | STATE_PARSECOMMAND, ///< waiting for a new command character.
86 | STATE_PARSEINT, ///< waiting for digits for the parameter.
87 | STATE_EXEC ///< executing the command.
88 | };
89 |
90 | RADIO_STATE kbState; ///< The state of parsing input characters.
91 | char kbCommand;
92 | int16_t kbValue;
93 |
94 | uint16_t g_block1;
95 | bool radioDebug = false;
96 | bool lowLevelDebug = false;
97 | String lastServiceName;
98 |
99 | // - - - - - - - - - - - - - - - - - - - - - - - - - -
100 |
101 | // use a function in between the radio chip and the RDS parser
102 | // to catch the block1 value (used for sender identification)
103 | void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) {
104 | // Serial.printf("RDS: 0x%04x 0x%04x 0x%04x 0x%04x\n", block1, block2, block3, block4);
105 | g_block1 = block1;
106 | rds.processData(block1, block2, block3, block4);
107 | }
108 |
109 | /// Update the Time
110 | void DisplayTime(uint8_t hour, uint8_t minute) {
111 | Serial.print("Time: ");
112 | if (hour < 10)
113 | Serial.print('0');
114 | Serial.print(hour);
115 | Serial.print(':');
116 | if (minute < 10)
117 | Serial.print('0');
118 | Serial.println(minute);
119 | } // DisplayTime()
120 |
121 |
122 | /// Update the ServiceName text on the LCD display.
123 | void DisplayServiceName(const char *name) {
124 | bool found = false;
125 |
126 | for (uint8_t n = 0; n < 8; n++)
127 | if (name[n] != ' ')
128 | found = true;
129 |
130 | if (found) {
131 | lastServiceName = name;
132 | Serial.print("Sender:<");
133 | Serial.print(name);
134 | Serial.println('>');
135 | }
136 | } // DisplayServiceName()
137 |
138 |
139 | /// Update the ServiceName text on the LCD display.
140 | void DisplayText(const char *txt) {
141 | Serial.print("Text: <");
142 | Serial.print(txt);
143 | Serial.println('>');
144 | } // DisplayText()
145 |
146 |
147 | void PrintScanInfo(RADIO_INFO *ri) {
148 | char sFreq[12];
149 | radio.formatFrequency(sFreq, sizeof(sFreq));
150 | Serial.print(sFreq);
151 | Serial.print(' ');
152 |
153 | Serial.print(ri->rssi);
154 | Serial.print(' ');
155 | // Serial.print(ri->snr);
156 | // Serial.print(' ');
157 | Serial.print(ri->tuned ? 'T' : '-');
158 | Serial.print(ri->stereo ? 'S' : '-');
159 | Serial.print(ri->rds ? 'R' : '-');
160 | Serial.println();
161 | }
162 |
163 | /// Execute a command identified by a character and an optional number.
164 | /// See the "?" command for available commands.
165 | /// \param cmd The command character.
166 | /// \param value An optional parameter for the command.
167 | void runSerialCommand(char cmd, int16_t value) {
168 | unsigned long startSeek; // after 300 msec must be tuned. after 500 msec must have RDS.
169 | RADIO_FREQ fSave, fLast = 0;
170 | RADIO_FREQ f = radio.getMinFrequency();
171 | RADIO_FREQ fMax = radio.getMaxFrequency();
172 | RADIO_INFO ri;
173 |
174 | if ((cmd == '\n') || (cmd == '\r')) {
175 | return;
176 | }
177 |
178 | Serial.print("do:");
179 | Serial.println(cmd);
180 |
181 | if (cmd == '?') {
182 | Serial.println();
183 | Serial.println("? Help");
184 | Serial.println("+ increase volume");
185 | Serial.println("- decrease volume");
186 | Serial.println("1 scan Frequency + Data");
187 | Serial.println("2 scan version 2");
188 | Serial.println("3 scan RDS stations");
189 | Serial.println(". scan up : scan up to next sender");
190 | Serial.println(", scan down ; scan down to next sender");
191 | Serial.println("i station status");
192 | Serial.println("s mono/stereo mode");
193 | Serial.println("b bass boost");
194 | Serial.println("m mute/unmute");
195 | Serial.println("u soft mute/unmute");
196 | Serial.println("x debug...");
197 | Serial.println("y toggle Debug Messages...");
198 | Serial.println("* toggle i2c debug output");
199 |
200 | // ----- control the volume and audio output -----
201 |
202 | } else if (cmd == '+') {
203 | // increase volume
204 | int v = radio.getVolume();
205 | if (v < 15)
206 | radio.setVolume(++v);
207 | } else if (cmd == '-') {
208 | // decrease volume
209 | int v = radio.getVolume();
210 | if (v > 0)
211 | radio.setVolume(--v);
212 |
213 | } else if (cmd == 'm') {
214 | // toggle mute mode
215 | radio.setMute(!radio.getMute());
216 |
217 | } else if (cmd == 'u') {
218 | // toggle soft mute mode
219 | radio.setSoftMute(!radio.getSoftMute());
220 |
221 | } else if (cmd == 's') {
222 | // toggle stereo mode
223 | radio.setMono(!radio.getMono());
224 |
225 | } else if (cmd == 'b') {
226 | // toggle bass boost
227 | radio.setBassBoost(!radio.getBassBoost());
228 |
229 |
230 | } else if (cmd == '1') {
231 | // ----- control the frequency -----
232 | Serial.println("Scanning all available frequencies... (1)");
233 | fSave = radio.getFrequency();
234 |
235 | // start Simple Scan: all channels
236 | while (f <= fMax) {
237 | radio.setFrequency(f);
238 | delay(100);
239 |
240 | radio.getRadioInfo(&ri);
241 |
242 | // you may adjust the following condition to adjust to the chip you use.
243 | // some do not report snr or tuned and good rssi differs.
244 |
245 | if (true) { // print all frequencies
246 | // if (ri.stereo) { // print all stereo frequencies
247 | // if (ri.rssi >= 32) { // si4703 usable threshold value
248 |
249 | // PrintScanInfo(&ri);
250 |
251 | RADIO_FREQ f = radio.getFrequency();
252 | Serial.print(f);
253 | Serial.print(',');
254 | Serial.print(ri.stereo);
255 | Serial.print(',');
256 | Serial.print(ri.rds);
257 | Serial.print(',');
258 | Serial.println(ri.rssi);
259 | }
260 |
261 | // tune up by 1 step
262 | f += radio.getFrequencyStep();
263 | } // while
264 | Serial.println("done.");
265 | radio.setFrequency(fSave);
266 |
267 | } else if (cmd == '2') {
268 | Serial.println("Seeking all frequencies... (2)");
269 | fSave = radio.getFrequency();
270 |
271 | // start Scan
272 | radio.setFrequency(f);
273 |
274 | while (f <= fMax) {
275 | radio.seekUp(true);
276 | delay(100); //
277 | startSeek = millis();
278 |
279 | // wait for seek complete
280 | do {
281 | radio.getRadioInfo(&ri);
282 | } while ((!ri.tuned) && (startSeek + 600 > millis()));
283 |
284 | // check frequency
285 | f = radio.getFrequency();
286 | if (f < fLast) {
287 | break;
288 | }
289 | fLast = f;
290 |
291 | // you may adjust the following condition to adjust to the chip you use.
292 | // some do not report snr or tuned and good rssi differs.
293 |
294 | if (true) { // every frequency detected by builtin scan
295 | // if ((ri.tuned) && (ri.rssi > 34) && (ri.snr > 12)) {
296 | // if (ri.rssi >= 32) { // si4703 threshold value
297 |
298 | radio.getRadioInfo(&ri);
299 | PrintScanInfo(&ri);
300 | } // if
301 | } // while
302 | Serial.println("done.");
303 | radio.setFrequency(fSave);
304 |
305 |
306 | } else if (cmd == '3') {
307 | Serial.println("Seeking all RDS senders...");
308 | fSave = radio.getFrequency();
309 |
310 | // start Scan
311 | radio.setFrequency(f);
312 |
313 | // start Simple Scan: all channels
314 | while (f <= fMax) {
315 | radio.setFrequency(f);
316 | lastServiceName.clear();
317 | delay(100);
318 |
319 | radio.getRadioInfo(&ri);
320 |
321 | // you may adjust the following condition to adjust to the chip you use.
322 | // some do not report snr or tuned and good rssi differs.
323 |
324 | // if (true) { // print all frequencies
325 | // if (ri.stereo) { // print all stereo frequencies
326 |
327 | if (ri.rssi >= 32) { // si4703 usable threshold value
328 | if (ri.rds) {
329 | radio.checkRDS();
330 | PrintScanInfo(&ri);
331 | startSeek = millis();
332 |
333 | // wait 3 secs for sender name
334 | do {
335 | radio.checkRDS();
336 | delay(30);
337 | } while (lastServiceName.isEmpty() && (startSeek + 6000 > millis()));
338 | } // if
339 | }
340 |
341 | // tune up by 1 step
342 | f += radio.getFrequencyStep();
343 | } // while
344 | Serial.println("done.");
345 | radio.setFrequency(fSave);
346 | } else if (cmd == 'f') {
347 | radio.setFrequency(value);
348 | }
349 |
350 | else if (cmd == '.') {
351 | radio.seekUp(false);
352 | } else if (cmd == ':') {
353 | radio.seekUp(true);
354 | } else if (cmd == ',') {
355 | radio.seekDown(false);
356 | } else if (cmd == ';') {
357 | radio.seekDown(true);
358 |
359 | } else if (cmd == '!') {
360 | // not in help
361 | RADIO_FREQ f = radio.getFrequency();
362 | if (value == 0) {
363 | radio.term();
364 | } else if (value == 1) {
365 | radio.init();
366 | radio.setBandFrequency(RADIO_BAND_FM, f);
367 | }
368 | } else if (cmd == 'i') {
369 | // info
370 | char s[12];
371 | radio.formatFrequency(s, sizeof(s));
372 | Serial.print("Station:");
373 | Serial.println(s);
374 | Serial.print("Radio:");
375 | radio.debugRadioInfo();
376 | Serial.print("Audio:");
377 | radio.debugAudioInfo();
378 | } else if (cmd == 'x') {
379 | radio.debugStatus(); // print chip specific data.
380 | } else if (cmd == 'y') {
381 | radioDebug = !radioDebug;
382 | radio.debugEnable(radioDebug);
383 | } else if (cmd == '*') {
384 | lowLevelDebug = !lowLevelDebug;
385 | radio._wireDebug(lowLevelDebug);
386 | }
387 | } // runSerialCommand()
388 |
389 |
390 | /// Setup a FM only radio configuration with I/O for commands and debugging on the Serial port.
391 | void setup() {
392 | delay(3000);
393 |
394 | // open the Serial port
395 | Serial.begin(115200);
396 | Serial.println("ScanRadio...");
397 | delay(200);
398 |
399 | #if defined(RESET_PIN)
400 | // This is required for SI4703 chips:
401 | radio.setup(RADIO_RESETPIN, RESET_PIN);
402 | radio.setup(RADIO_MODEPIN, MODE_PIN);
403 | #endif
404 |
405 | // Enable information to the Serial port
406 | radio.debugEnable(radioDebug);
407 | radio._wireDebug(lowLevelDebug);
408 |
409 | // Set FM Options for Europe
410 | radio.setup(RADIO_FMSPACING, RADIO_FMSPACING_100); // for EUROPE
411 | radio.setup(RADIO_DEEMPHASIS, RADIO_DEEMPHASIS_50); // for EUROPE
412 |
413 | // Initialize the Radio
414 | if (!radio.initWire(Wire)) {
415 | Serial.println("no radio chip found.");
416 | delay(4000);
417 | while (1) {};
418 | };
419 |
420 | radio.setBandFrequency(RADIO_BAND_FM, 8930);
421 |
422 | radio.setMono(false);
423 | radio.setMute(false);
424 | radio.setVolume(5);
425 |
426 | // setup the information chain for RDS data.
427 | radio.attachReceiveRDS(RDS_process);
428 | rds.attachServiceNameCallback(DisplayServiceName);
429 | // rds.attachTextCallback(DisplayText);
430 | // rds.attachTimeCallback(DisplayTime);
431 |
432 | runSerialCommand('?', 0);
433 | kbState = STATE_PARSECOMMAND;
434 | } // Setup
435 |
436 |
437 | /// Constantly check for serial input commands and trigger command execution.
438 | void loop() {
439 | if (Serial.available() > 0) {
440 | // read the next char from input.
441 | char c = Serial.peek();
442 |
443 | if ((kbState == STATE_PARSECOMMAND) && (c < 0x20)) {
444 | // ignore unprintable chars
445 | Serial.read();
446 |
447 | } else if (kbState == STATE_PARSECOMMAND) {
448 | // read a command.
449 | kbCommand = Serial.read();
450 | kbState = STATE_PARSEINT;
451 |
452 | } else if (kbState == STATE_PARSEINT) {
453 | if ((c >= '0') && (c <= '9')) {
454 | // build up the value.
455 | c = Serial.read();
456 | kbValue = (kbValue * 10) + (c - '0');
457 | } else {
458 | // not a value -> execute
459 | runSerialCommand(kbCommand, kbValue);
460 | kbCommand = ' ';
461 | kbState = STATE_PARSECOMMAND;
462 | kbValue = 0;
463 | } // if
464 | } // if
465 | } // if
466 |
467 | // check for RDS data
468 | radio.checkRDS();
469 |
470 |
471 | } // loop
472 |
473 | // End.
474 |
--------------------------------------------------------------------------------
/src/radio.h:
--------------------------------------------------------------------------------
1 | /* \file Radio.h
2 | * \brief Library header file for the radio libraries to control radio chips.
3 | *
4 | * \author Matthias Hertel, http://www.mathertel.de
5 | * \copyright Copyright (c) 2014 by Matthias Hertel.\n
6 | * This work is licensed under a BSD style license.\n
7 | * See http://www.mathertel.de/License.aspx
8 | *
9 | * The **RDA5807M** and **RDA5807FP** with I2S support from RDA Microelectronics
10 | * The **SI4703** from Silicon Labs, now Skyworks
11 | * The **SI4705** from Silicon Labs, now Skyworks
12 | * The **SI4721** and **SI4730** chips from Silicon Labs, now Skyworks
13 | * The **TEA5767** from NXP
14 | *
15 | * The following chip is planned to be supported too:
16 | * ...
17 | *
18 | * More documentation and source code is available at http://www.mathertel.de/Arduino
19 | *
20 | * ChangeLog:
21 | * ----------
22 | * * 08.07.2014 creation of the common radio class.
23 | * * 15.07.2014 examples working with RDA5807M.
24 | * * 26.08.2014 examples working with SI4703.
25 | * * 31.08.2014 Doxygen style comments added.
26 | * * 05.02.2015 mainpage content added.
27 | * * 29.04.2015 clear RDS function, need to clear RDS info after tuning.
28 | * * 17.09.2020 Wire Util functions added.
29 | * * 06.12.2020 I2C Wire and Reset initialization centralized.
30 | *
31 | * TODO:
32 | */
33 |
34 | /// TODO:
35 | /// --------
36 | /// * multi-Band enabled
37 |
38 | /// \mainpage
39 | /// An Arduino library to control radio for receiving FM broadcast signals.
40 | ///
41 | /// Currently the following chips are supported:
42 | /// * The SI4703 from Silicon Labs
43 | /// * The SI4705 from Silicon Labs
44 | /// * The SI4721 from Silicon Labs
45 | /// * The TEA5767 from NXP
46 | /// * The RDA5807M from RDA Microelectronics
47 | ///
48 | /// They all are capable for receiving FM radio stations in stereo with European and US settings and can be controlled by using the I2C bus.However there are differences in the sensitivity and quality and well on receiving RDS information from the stations.
49 | ///
50 | /// For each of these chips a specific library is implemented that knows how to communicate with the chip using the I2C bus and the wire library.These libraries all share a common base, the radio library so that all the common code is only implemented once in there :
51 | ///
52 | /// All the libraries share the same interface (defined by the radio library) so it is possible to exchange them when not using one of the chip specific functions.
53 | ///
54 |
55 |
56 | #ifndef __RADIO_h__
57 | #define __RADIO_h__
58 |
59 | #include
60 | #include
61 |
62 | #define UNUSED __attribute__((unused))
63 |
64 | // The DEBUG_xxx Macros enable Information to the Serial port.
65 | // They can be enabled by setting the _debugEnabled variable to true disabled by using the debugEnable function.
66 | // When the code has to be minimized they can be redefined without implementation like:
67 | // #define DEBUG_STR(txt) {}
68 |
69 | /// Used for Debugging text information.
70 | #define DEBUG_STR(txt) \
71 | if (_debugEnabled) { \
72 | Serial.print('>'); \
73 | Serial.println(txt); \
74 | }
75 |
76 | /// Used for Debugging function entries without parameters.
77 | #define DEBUG_VAL(label, val) \
78 | if (_debugEnabled) { \
79 | Serial.print('>'); \
80 | Serial.print(label); \
81 | Serial.print(':'); \
82 | Serial.println(val); \
83 | }
84 | #define DEBUG_VALX(label, val) \
85 | if (_debugEnabled) { \
86 | Serial.print('>'); \
87 | Serial.print(label); \
88 | Serial.print(':'); \
89 | Serial.println(val, HEX); \
90 | }
91 |
92 | /// Used for Debugging function entries without parameters.
93 | #define DEBUG_FUNC0(fn) \
94 | if (_debugEnabled) { \
95 | Serial.print('>'); \
96 | Serial.print(fn); \
97 | Serial.println("()"); \
98 | }
99 |
100 | /// Used for Debugging function entries with 1 parameter.
101 | #define DEBUG_FUNC1(fn, p1) \
102 | if (_debugEnabled) { \
103 | Serial.print('>'); \
104 | Serial.print(fn); \
105 | Serial.print('('); \
106 | Serial.print(p1); \
107 | Serial.println(')'); \
108 | }
109 |
110 | /// Used for Debugging function entries with 1 parameters as hex Value.
111 | #define DEBUG_FUNC1X(fn, p1) \
112 | if (_debugEnabled) { \
113 | Serial.print('>'); \
114 | Serial.print(fn); \
115 | Serial.print("(0x"); \
116 | Serial.print(p1, HEX); \
117 | Serial.println(')'); \
118 | }
119 |
120 | /// Used for Debugging function entries with 2 parameters.
121 | #define DEBUG_FUNC2(fn, p1, p2) \
122 | if (_debugEnabled) { \
123 | Serial.print('>'); \
124 | Serial.print(fn); \
125 | Serial.print('('); \
126 | Serial.print(p1); \
127 | Serial.print(", "); \
128 | Serial.print(p2); \
129 | Serial.println(')'); \
130 | }
131 |
132 | /// Used for Debugging function entries with 2 parameters and Hex Value.
133 | #define DEBUG_FUNC2X(fn, p1, p2) \
134 | if (_debugEnabled) { \
135 | Serial.print('>'); \
136 | Serial.print(fn); \
137 | Serial.print("(0x"); \
138 | Serial.print(p1, HEX); \
139 | Serial.print(", 0x"); \
140 | Serial.print(p2, HEX); \
141 | Serial.println(')'); \
142 | }
143 |
144 |
145 | // ----- Callback function types -----
146 |
147 | /// callback function for passing RDS data.
148 | extern "C" {
149 | typedef void (*receiveRDSFunction)(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4);
150 | }
151 |
152 |
153 | // ----- type definitions -----
154 |
155 | /// Band datatype.
156 | /// The BANDs a receiver probably can implement.
157 | enum RADIO_BAND {
158 | RADIO_BAND_NONE = 0, ///< No band selected.
159 |
160 | RADIO_BAND_FM = 0x01, ///< FM band 87.5 - 108 MHz (USA, Europe) selected.
161 | RADIO_BAND_FMWORLD = 0x02, ///< FM band 76 - 108 MHz (Japan, Worldwide) selected.
162 | RADIO_BAND_AM = 0x03, ///< AM band selected.
163 | RADIO_BAND_KW = 0x04, ///< KW band selected.
164 |
165 | RADIO_BAND_FMTX = 0x11, ///< Transmit for FM.
166 | };
167 |
168 |
169 | /// Frequency data type.
170 | /// Only 16 bits are used for any frequency value (not the real one)
171 | typedef uint16_t RADIO_FREQ;
172 |
173 |
174 | /// A structure that contains information about the radio features from the chip.
175 | struct RADIO_INFO {
176 | bool active; ///< receiving is active.
177 | uint8_t rssi; ///< Radio Station Strength Information.
178 | uint8_t snr; ///< Signal Noise Ratio.
179 | bool rds; ///< RDS information is available.
180 | bool tuned; ///< A stable frequency is tuned.
181 | bool mono; ///< Mono mode is on.
182 | bool stereo; ///< Stereo audio is available
183 | };
184 |
185 |
186 | /// a structure that contains information about the audio features
187 | struct AUDIO_INFO {
188 | uint8_t volume;
189 | bool mute;
190 | bool softmute;
191 | bool bassBoost;
192 | };
193 |
194 | // ----- common RADIO class definition -----
195 |
196 | // setup() features and defined values
197 |
198 | #define RADIO_RESETPIN 0x01
199 | #define RADIO_MODEPIN 0x02
200 | #define RADIO_I2CADDRESS 0x03
201 |
202 | #define RADIO_ANTENNA 0x04
203 | #define RADIO_ANTENNA_DEFAULT 0
204 | #define RADIO_ANTENNA_OPT1 1
205 | #define RADIO_ANTENNA_OPT2 2
206 |
207 | // FM channel spacing configuration is supported by some radio chips.
208 | #define RADIO_FMSPACING 0x05
209 | #define RADIO_FMSPACING_25 25
210 | #define RADIO_FMSPACING_50 50
211 | #define RADIO_FMSPACING_100 100 // 100 kHz typically used in Europe / Japan = default
212 | #define RADIO_FMSPACING_200 200 // 200 kHz typically used in US / Australia
213 |
214 | // FM High Frequency de-emphasis
215 | #define RADIO_DEEMPHASIS 0x06
216 | #define RADIO_DEEMPHASIS_50 50 // 50µs typically used in Europe, Australia, Japan
217 | #define RADIO_DEEMPHASIS_75 75 // 75µs typically used in USA
218 |
219 | /// Library to control radio chips in general. This library acts as a base library for the chip specific implementations.
220 | class RADIO {
221 |
222 | public:
223 | const uint8_t MAXVOLUME = 15; ///< max volume level for all radio library consumers.
224 |
225 | RADIO(); ///< create a new object from this class.
226 |
227 | virtual void setup(int feature, int value); ///< configure board/hardware specific features before init().
228 | virtual bool init(); ///< initialize library and the chip.
229 | virtual bool initWire(TwoWire &port); // init with I2C bus
230 | virtual void term(); ///< terminate all radio functions.
231 |
232 | // ----- Audio features -----
233 |
234 | virtual void setVolume(int8_t newVolume); ///< Set the volume output of the radio chip.
235 | virtual int8_t getVolume(); ///< Retrieve the current output volume.
236 | virtual int8_t getMaxVolume(); ///< Retrieve the maximum possible output volume.
237 |
238 | virtual void setMute(bool switchOn); ///< Control the mute mode of the radio chip.
239 | virtual bool getMute(); ///< Retrieve the current mute mode setting.
240 |
241 | virtual void setSoftMute(bool switchOn); ///< Control the softmute mode (mute on low signals) of the radio chip.
242 | virtual bool getSoftMute(); ///< Retrieve the current soft mute mode setting.
243 |
244 | virtual void setBassBoost(bool switchOn); ///< Control the bass boost mode of the radio chip.
245 | virtual bool getBassBoost(); ///< Retrieve the current bass boost mode setting.
246 |
247 | // ----- Receiver features -----
248 |
249 | virtual RADIO_FREQ getMinFrequency(); ///< Get the minimum frequency of the current selected band.
250 | virtual RADIO_FREQ getMaxFrequency(); ///< Get the maximum frequency of the current selected band.
251 | virtual RADIO_FREQ getFrequencyStep(); ///< Get resolution of the current selected band.
252 |
253 | virtual void setBand(RADIO_BAND newBand); ///< Set the current band.
254 | virtual RADIO_BAND getBand(); ///< Retrieve the current band setting.
255 |
256 | virtual void setFrequency(RADIO_FREQ newF); ///< Start using the new frequency for receiving.
257 | virtual RADIO_FREQ getFrequency(void); ///< Retrieve the current tuned frequency.
258 |
259 | virtual void setBandFrequency(RADIO_BAND newBand, RADIO_FREQ newFreq); ///< Set Band and Frequency in one call.
260 |
261 | virtual void seekUp(bool toNextSender = true); ///< Start a seek upwards from the current frequency.
262 | virtual void seekDown(bool toNextSender = true); ///< Start a seek downwards from the current frequency.
263 |
264 | virtual void setMono(bool switchOn); ///< Control the mono mode of the radio chip.
265 | virtual bool getMono(); ///< Retrieve the current mono mode setting.
266 |
267 | // ----- combined status functions -----
268 |
269 | virtual void getRadioInfo(RADIO_INFO *info); ///< Retrieve some information about the current radio function of the chip.
270 |
271 | virtual void getAudioInfo(AUDIO_INFO *info); ///< Retrieve some information about the current audio function of the chip.
272 |
273 | // ----- Supporting RDS for FM bands -----
274 |
275 | virtual void attachReceiveRDS(receiveRDSFunction newFunction); ///< Register a RDS processor function.
276 | virtual void checkRDS(); ///< Check if RDS Data is available and good.
277 | virtual void clearRDS(); ///< Clear RDS data in the attached RDS Receiver by sending 0,0,0,0.
278 |
279 | // ----- Utilities -----
280 |
281 | /// Format the current frequency for display and printing.
282 | virtual void formatFrequency(char *s, uint8_t length);
283 |
284 | // ----- debug Helpers send information to Serial port
285 |
286 | /**
287 | * Enable debugging information on Serial port.
288 | * This is for logging on a higher level than i2c data transport.
289 | * @param enable true to switch logging on.
290 | */
291 | virtual void debugEnable(bool enable = true);
292 |
293 | virtual void debugRadioInfo(); ///< Print out all radio information.
294 | virtual void debugAudioInfo(); ///< Print out all audio information.
295 | virtual void debugStatus(); ///< Send debug information about actual available chip functionality and other internal things.
296 |
297 | // ===== Wire Utilities (static) =====
298 |
299 | static bool _wireDebugFlag;
300 | static void _wireWriteTo(TwoWire *port, int address, uint8_t *cmdData, int cmdLen);
301 | static uint8_t _wireReadFrom(TwoWire *port, int address, uint8_t *data, int len);
302 |
303 | // write a 16 bit value in High-Low order to the Wire.
304 | static void _write16HL(TwoWire *port, uint16_t val);
305 | static uint16_t _read16HL(TwoWire *port);
306 |
307 | /**
308 | * Enable low level i2c debugging information on Serial port.
309 | * @param enable true to switch logging on.
310 | */
311 | virtual void _wireDebug(bool enable = true);
312 |
313 | /** check for a device on address.
314 | * @return true when i2c device answered.
315 | */
316 | bool _wireExists(TwoWire *port, int address);
317 |
318 | /**
319 | * Write and optionally read data on the i2c bus.
320 | * A debug output can be enabled using _wireDebug().
321 | * @param port i2c port to be used.
322 | * @param address i2c address to be used.
323 | * @param reg the register to be read (1 byte send).
324 | * @param data buffer array with received data. If this parameter is nullptr no data will be requested.
325 | * @param len length of data buffer.
326 | * @return number of register values received.
327 | */
328 | int _wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int len);
329 |
330 | /**
331 | * Write and optionally read data on the i2c bus.
332 | * A debug output can be enabled using _wireDebug().
333 | * @param port i2c port to be used.
334 | * @param address i2c address to be used.
335 | * @param cmdData array with data to be send.
336 | * @param cmdLen length of cmdData.
337 | * @param data buffer array with received data. If this parameter is nullptr no data will be requested.
338 | * @param len length of data buffer.
339 | * @return number of register values received.
340 | */
341 | int _wireRead(TwoWire *port, int address, uint8_t *cmdData, int cmdLen, uint8_t *data, int len);
342 |
343 | protected:
344 | bool _debugEnabled = false; ///< Set by debugEnable() and controls debugging functionality.
345 | bool _wireDebugEnabled = false; ///< Set by _wireDebug() and controls i2c data level debugging.
346 |
347 | uint8_t _volume = 0; ///< Last set volume level.
348 | uint8_t _maxVolume = 15; ///< maximum of volume supported by the chip.
349 |
350 | bool _bassBoost = false; ///< Last set bass Boost effect.
351 | bool _mono = false; ///< Last set mono effect.
352 | bool _mute = false; ///< Last set mute effect.
353 | bool _softMute = false; ///< Last set softMute effect.
354 |
355 | RADIO_BAND _band; ///< Last set band.
356 | RADIO_FREQ _freq; ///< Last set frequency.
357 |
358 | RADIO_FREQ _freqLow; ///< Lowest frequency of the current selected band.
359 | RADIO_FREQ _freqHigh; ///< Highest frequency of the current selected band.
360 | RADIO_FREQ _freqSteps; ///< Resolution of the tuner.
361 |
362 | receiveRDSFunction _sendRDS; ///< Registered RDS Function that is called on new available data.
363 |
364 | void _printHex2(uint8_t val); ///< Prints a byte as 2 character hexadecimal code with leading zeros.
365 | void _printHex4(uint16_t val); ///< Prints a register as 4 character hexadecimal code with leading zeros.
366 |
367 | // i2c bus communication
368 | TwoWire *_i2cPort;
369 | int _i2caddr;
370 |
371 | // extra pins
372 | int _resetPin = -1;
373 |
374 | // Antenna Features
375 | int _antennaOption = 0;
376 |
377 | // FM Channel Spacing Features
378 | int _fmSpacing = RADIO_FMSPACING_100;
379 |
380 | // FM de-emphasis
381 | int _deEmphasis = RADIO_DEEMPHASIS_50;
382 |
383 | private:
384 | void int16_to_s(char *s, uint16_t val); ///< Converts a int16 number to a string, similar to itoa, but using the format "00000".
385 |
386 | }; // class RADIO
387 |
388 |
389 | #endif
390 |
391 | // End.
--------------------------------------------------------------------------------
/src/radio.cpp:
--------------------------------------------------------------------------------
1 | /** \file Radio.cpp
2 | * \brief Library implementation for the radio libraries to control radio chips.
3 | *
4 | * \author Matthias Hertel, http://www.mathertel.de
5 | * \copyright Copyright (c) 2014 by Matthias Hertel.\n
6 | * This work is licensed under a BSD style license.\n
7 | * See http://www.mathertel.de/License.aspx
8 | * More documentation and source code is available at http://www.mathertel.de/Arduino
9 | *
10 | * ChangeLog see: radio.h
11 | */
12 |
13 | #include
14 | #include
15 | #include
16 |
17 |
18 | // ----- Register Definitions -----
19 |
20 | // no chip-registers without a chip.
21 |
22 | // ----- implement
23 |
24 | /// Setup the radio object and initialize private variables to 0.
25 | /// Don't change the radio chip (yet).
26 | RADIO::RADIO() {
27 | } // RADIO()
28 |
29 |
30 | void RADIO::setup(int feature, int value) {
31 | if (feature == RADIO_RESETPIN) {
32 | _resetPin = value;
33 | } else if ((feature == RADIO_I2CADDRESS) && (value > 0)) {
34 | _i2caddr = value;
35 | } else if ((feature == RADIO_ANTENNA) && (value > 0)) {
36 | _antennaOption = value;
37 | } else if ((feature == RADIO_FMSPACING) && (value > 0)) {
38 | _fmSpacing = value;
39 | } else if ((feature == RADIO_DEEMPHASIS) && (value > 0)) {
40 | _deEmphasis = value;
41 | }
42 |
43 | } // setup()
44 |
45 |
46 | /// The RADIO class doesn't implement a concrete chip so nothing has to be initialized.
47 | bool RADIO::initWire(TwoWire &port) {
48 | DEBUG_FUNC0("RADIO::initWire");
49 |
50 | _i2cPort = &port;
51 | return (this->init());
52 | } // initWire()
53 |
54 |
55 | /// The RADIO class doesn't implement a concrete chip so nothing has to be initialized.
56 | bool RADIO::init() {
57 | if (_resetPin >= 0) {
58 | // create a reset impulse
59 | pinMode(_resetPin, OUTPUT);
60 | digitalWrite(_resetPin, LOW); // Put chip into reset
61 | delay(5);
62 | digitalWrite(_resetPin, HIGH); // Bring chip out of reset
63 | delay(5);
64 | }
65 | return (false);
66 | } // init()
67 |
68 |
69 | /// The RADIO class doesn't implement a concrete chip so nothing has to be initialized.
70 | void RADIO::term() {
71 | } // term()
72 |
73 |
74 | // ----- Volume control -----
75 |
76 | void RADIO::setVolume(int8_t newVolume) {
77 | _volume = constrain(newVolume, 0, _maxVolume);
78 | } // setVolume()
79 |
80 |
81 | int8_t RADIO::getVolume() {
82 | return (_volume);
83 | } // getVolume()
84 |
85 |
86 | int8_t RADIO::getMaxVolume() {
87 | return (_maxVolume);
88 | } // getMaxVolume()
89 |
90 |
91 | // ----- bass boost control -----
92 |
93 | /// Control the bass boost mode of the radio chip.
94 | /// The base implementation ony stores the value to the internal variable.
95 | /// @param switchOn true to switch bassBoost mode on, false to switch bassBoost mode off.
96 | void RADIO::setBassBoost(bool switchOn) {
97 | DEBUG_FUNC1("setBassBoost", switchOn);
98 | _bassBoost = switchOn;
99 | } // setBassBoost()
100 |
101 |
102 | /// Retrieve the current bass boost mode setting.
103 | /// The base implementation returns only the value in the internal variable.
104 | bool RADIO::getBassBoost() {
105 | return (_bassBoost);
106 | } // getBassBoost()
107 |
108 |
109 | // ----- mono control -----
110 |
111 | /// The base implementation ony stores the value to the internal variable.
112 | void RADIO::setMono(bool switchOn) {
113 | DEBUG_FUNC1("setMono", switchOn);
114 | _mono = switchOn;
115 | } // setMono()
116 |
117 |
118 | /// The base implementation returns only the value in the internal variable.
119 | bool RADIO::getMono() {
120 | return (_mono);
121 | } // getMono()
122 |
123 |
124 | // ----- mute control -----
125 |
126 | /// The base implementation ony stores the value to the internal variable.
127 | void RADIO::setMute(bool switchOn) {
128 | _mute = switchOn;
129 | } // setMute()
130 |
131 |
132 | /// The base implementation returns only the value in the internal variable.
133 | bool RADIO::getMute() {
134 | return (_mute);
135 | } // getMute()
136 |
137 |
138 | // ----- softmute control -----
139 |
140 | /// The base implementation ony stores the value to the internal variable.
141 | void RADIO::setSoftMute(bool switchOn) {
142 | DEBUG_FUNC1("setSoftMute", switchOn);
143 | _softMute = switchOn;
144 | } // setSoftMute()
145 |
146 |
147 | /// The base implementation returns only the value in the internal variable.
148 | bool RADIO::getSoftMute() {
149 | return (_softMute);
150 | } // getSoftMute()
151 |
152 |
153 | // ----- receiver control -----
154 |
155 | // some implementations to return internal variables if used by concrete chip implementations
156 |
157 | /// Start using the new band for receiving.
158 | void RADIO::setBand(RADIO_BAND newBand) {
159 | DEBUG_FUNC1("setBand", newBand);
160 | _band = newBand;
161 | if (newBand == RADIO_BAND_FM) {
162 | _freqLow = 8700;
163 | _freqHigh = 10800;
164 | _freqSteps = 10; // 20 in USA ???
165 |
166 | } else if (newBand == RADIO_BAND_FMWORLD) {
167 | _freqLow = 7600;
168 | _freqHigh = 10800;
169 | _freqSteps = 10;
170 | } // if
171 | } // setBand()
172 |
173 |
174 | /// Start using the new frequency for receiving.
175 | /// The new frequency is stored for later retrieval.
176 | void RADIO::setFrequency(RADIO_FREQ newFreq) {
177 | DEBUG_FUNC1("setFrequency", newFreq);
178 | _freq = newFreq;
179 | } // setFrequency()
180 |
181 |
182 | void RADIO::setBandFrequency(RADIO_BAND newBand, RADIO_FREQ newFreq) {
183 | setBand(newBand);
184 | setFrequency(newFreq);
185 | } // setBandFrequency()
186 |
187 |
188 | void RADIO::seekUp(bool) {}
189 | void RADIO::seekDown(bool) {}
190 |
191 | RADIO_BAND RADIO::getBand() {
192 | return (_band);
193 | }
194 | RADIO_FREQ RADIO::getFrequency() {
195 | return (_freq);
196 | }
197 | RADIO_FREQ RADIO::getMinFrequency() {
198 | return (_freqLow);
199 | }
200 | RADIO_FREQ RADIO::getMaxFrequency() {
201 | return (_freqHigh);
202 | }
203 | RADIO_FREQ RADIO::getFrequencyStep() {
204 | return (_freqSteps);
205 | }
206 |
207 |
208 | /// Return all the Radio settings.
209 | /// This implementation only knows some values from the last settings.
210 | void RADIO::getRadioInfo(RADIO_INFO *info) {
211 | // set everything to false and 0.
212 | memset(info, 0, sizeof(RADIO_INFO));
213 | // info->tuned = false;
214 | // info->rds = false;
215 | // info->stereo = false;
216 |
217 | // use current settings
218 | info->mono = _mono;
219 |
220 | } // getRadioInfo()
221 |
222 |
223 | /// Return current settings as far as no chip is required.
224 | /// When using the radio::setXXX methods, no chip specific implementation is needed.
225 | void RADIO::getAudioInfo(AUDIO_INFO *info) {
226 | // set everything to false and 0.
227 | memset(info, 0, sizeof(AUDIO_INFO));
228 |
229 | // use current settings
230 | info->volume = _volume;
231 | info->mute = _mute;
232 | info->softmute = _softMute;
233 | info->bassBoost = _bassBoost;
234 | } // getAudioInfo()
235 |
236 |
237 | /// In the general radio implementation there is no chip for RDS.
238 | /// This function needs to be implemented for radio chips with RDS receiving functionality.
239 | void RADIO::checkRDS() { /* no chip : nothing to check */
240 | }
241 |
242 |
243 | /// Send a 0.0.0.0 to the RDS receiver if there is any attached.
244 | /// This is to point out that there is a new situation and all existing data should be invalid from now on.
245 | void RADIO::clearRDS() {
246 | if (_sendRDS)
247 | _sendRDS(0, 0, 0, 0);
248 | } // clearRDS()
249 |
250 |
251 | // send valid and good data to the RDS processor via newFunction
252 | // remember the RDS function
253 | void RADIO::attachReceiveRDS(receiveRDSFunction newFunction) {
254 | _sendRDS = newFunction;
255 | } // attachReceiveRDS()
256 |
257 |
258 | // format the current frequency for display and printing
259 | void RADIO::formatFrequency(char *s, uint8_t length) {
260 | RADIO_BAND b = getBand();
261 | RADIO_FREQ f = getFrequency();
262 |
263 | if ((s != NULL) && (length > 10)) {
264 | *s = '\0';
265 |
266 | if ((b == RADIO_BAND_FM) || (b == RADIO_BAND_FMWORLD)) {
267 | // " ff.ff MHz" or "fff.ff MHz"
268 | int16_to_s(s, (uint16_t)f);
269 |
270 | // insert decimal point
271 | s[5] = s[4];
272 | s[4] = s[3];
273 | s[3] = '.';
274 |
275 | // append units
276 | strcpy(s + 6, " MHz");
277 | } // if
278 |
279 | // f = _freqLow + (channel * _bandSteps);
280 | // if (f < 10000) Serial.write(' ');
281 | // Serial.print(f / 100); Serial.print('.'); Serial.print(f % 100); Serial.print(" MHz ");
282 | } // if
283 |
284 | } // formatFrequency()
285 |
286 |
287 | /**
288 | * Enable debugging information on Serial port.
289 | * This is for logging on a higher level than i2c data transport.
290 | * @param enable true to switch logging on.
291 | */
292 | void RADIO::debugEnable(bool enable) {
293 | _debugEnabled = enable;
294 | } // debugEnable()
295 |
296 |
297 | // print out all radio information
298 | void RADIO::debugRadioInfo() {
299 | RADIO_INFO info;
300 | this->getRadioInfo(&info);
301 |
302 | Serial.print(info.rds ? " RDS" : " ---");
303 | Serial.print(info.tuned ? " TUNED" : " -----");
304 | Serial.print(info.stereo ? " STEREO" : " MONO ");
305 | Serial.print(" RSSI: ");
306 | Serial.print(info.rssi);
307 | Serial.print(" SNR: ");
308 | Serial.print(info.snr);
309 | Serial.println();
310 | } // debugRadioInfo()
311 |
312 |
313 | // print out all audio information
314 | void RADIO::debugAudioInfo() {
315 | AUDIO_INFO info;
316 | this->getAudioInfo(&info);
317 |
318 | Serial.print(info.mute ? " MUTE" : " ----");
319 | Serial.print(info.softmute ? " SOFTMUTE" : " --------");
320 | Serial.print(info.bassBoost ? " BASS" : " ----");
321 | Serial.println();
322 | } // debugAudioInfo()
323 |
324 |
325 | /// The RADIO class doesn't have interesting status information so nothing is sent.
326 | void RADIO::debugStatus() {
327 | // no output.
328 | } // debugStatus
329 |
330 |
331 | /// This is a special format routine used to format frequencies as strings with leading blanks.
332 | /// up to 5 digits only (" 0".."99999")
333 | /// *s MUST be able to hold the characters
334 | void RADIO::int16_to_s(char *s, uint16_t val) {
335 | uint8_t n = 5;
336 |
337 | while (n > 0) {
338 | n--;
339 | if ((n == 4) || (val > 0)) {
340 | s[n] = '0' + (val % 10);
341 | val = val / 10;
342 | } else {
343 | s[n] = ' ';
344 | }
345 | } // while
346 | } // int16_to_s()
347 |
348 |
349 | // ===== Wire Utilities =====
350 |
351 | bool RADIO::_wireDebugFlag = false;
352 |
353 | /**
354 | * Enable low level i2c debugging information on Serial port.
355 | * @param enable true to switch logging on.
356 | */
357 | void RADIO::_wireDebug(bool enable) {
358 | _wireDebugEnabled = enable;
359 | _wireDebugFlag = enable;
360 | } // _wireDebug()
361 |
362 |
363 | bool RADIO::_wireExists(TwoWire *port, int address) {
364 | port->beginTransmission(address);
365 | uint8_t err = port->endTransmission();
366 | if (_wireDebugEnabled) {
367 | Serial.print("_wireExists(");
368 | Serial.print(address);
369 | Serial.print("): err=");
370 | Serial.println(err);
371 | }
372 | return (err == 0);
373 | }
374 |
375 | // a i2c transmission in one call
376 | void RADIO::_wireWriteTo(TwoWire *port, int address, uint8_t *cmdData, int cmdLen) {
377 | if (cmdData && cmdLen > 0) {
378 | // send out command sequence
379 | port->beginTransmission(address);
380 | if (_wireDebugFlag) {
381 | Serial.print("--write(0x");
382 | Serial.print(address, 16);
383 | Serial.print("): ");
384 | }
385 |
386 | for (int i = 0; i < cmdLen; i++) {
387 | uint8_t d = cmdData[i];
388 | port->write(d);
389 | if (_wireDebugFlag) {
390 | // write a hex value to Serial
391 | if (d < 16) Serial.print('0');
392 | Serial.print(d, 16);
393 | Serial.print(' ');
394 | } // if
395 | } // for
396 |
397 | port->endTransmission();
398 | } // if
399 | } // _wireWriteTo
400 |
401 |
402 | // a i2c request in one call
403 | uint8_t RADIO::_wireReadFrom(TwoWire *port, int address, uint8_t *data, int len) {
404 | uint8_t received = 0;
405 | if (data && len > 0) {
406 | while (!received) {
407 | received = port->requestFrom(address, len);
408 | if (_wireDebugFlag) {
409 | Serial.print('[');
410 | Serial.print(received);
411 | Serial.print(']');
412 | }
413 | }
414 |
415 | uint8_t *d = data;
416 | for (int n = 0; n < received; n++) {
417 | *d = port->read();
418 | if (_wireDebugFlag) {
419 | // write a hex value to Serial
420 | if (*d < 16) Serial.print('0');
421 | Serial.print(*d, 16);
422 | Serial.print(' ');
423 | }
424 | d++;
425 | }
426 | }
427 | return (received);
428 | } // _wireReadFrom
429 |
430 |
431 | // write a 16 bit value in High-Low order to the Wire.
432 | void RADIO::_write16HL(TwoWire *port, uint16_t val) {
433 | port->write(val >> 8);
434 | port->write(val & 0xFF);
435 | } // _write16HL
436 |
437 |
438 | // read a 16 bit value in High-Low order from the Wire.
439 | uint16_t RADIO::_read16HL(TwoWire *port) {
440 | uint8_t hiByte = port->read();
441 | uint8_t loByte = port->read();
442 | return ((hiByte << 8) + loByte);
443 | } // _read16HL
444 |
445 |
446 | /**
447 | * Write and optionally read data on the i2c bus.
448 | * A debug output can be enabled using _wireDebug().
449 | * @param port i2c port to be used.
450 | * @param address i2c address to be used.
451 | * @param reg the register to be read (1 byte send).
452 | * @param data buffer array with received data. If this parameter is nullptr no data will be requested.
453 | * @param len length of data buffer.
454 | * @return number of register values received.
455 | */
456 | int RADIO::_wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int len) {
457 | return (_wireRead(port, address, ®, 1, data, len));
458 | } // _wireRead()
459 |
460 |
461 | /**
462 | * Write and optionally read data on the i2c bus.
463 | * A debug output can be enabled using _wireDebug().
464 | * @param port i2c port to be used.
465 | * @param address i2c address to be used.
466 | * @param cmdData array with data to be send.
467 | * @param cmdLen length of cmdData.
468 | * @param data buffer array with received data. If this parameter is nullptr no data will be requested.
469 | * @param len length of data buffer.
470 | * @return number of register values received.
471 | */
472 | int RADIO::_wireRead(TwoWire *port, int address, uint8_t *cmdData, int cmdLen, uint8_t *data, int len) {
473 | int received = 0;
474 |
475 | RADIO::_wireWriteTo(port, address, cmdData, cmdLen);
476 |
477 | // read requested data (when buffer is available)
478 | if (data) {
479 | while (received == 0) {
480 | if (RADIO::_wireDebugFlag) {
481 | Serial.print(" -> ");
482 | }
483 | received = RADIO::_wireReadFrom(port, address, data, len);
484 | if (!(*data & 0x80))
485 | received = 0;
486 | }
487 |
488 | if (RADIO::_wireDebugFlag) {
489 | Serial.println('.');
490 | }
491 | } // if (data)
492 |
493 | return (received);
494 | } // _wireRead()
495 |
496 |
497 | /// Prints a byte as 2 character hexadecimal code with leading zeros.
498 | void RADIO::_printHex2(uint8_t val) {
499 | Serial.print(' ');
500 | if (val <= 0x000F)
501 | Serial.print('0'); // if less 2 Digit
502 | Serial.print(val, HEX);
503 | } // _printHex2
504 |
505 |
506 | /// Prints a word as 4 character hexadecimal code with leading zeros.
507 | void RADIO::_printHex4(uint16_t val) {
508 | Serial.print(' ');
509 | if (val <= 0x000F)
510 | Serial.print('0'); // if less 2 Digit
511 | if (val <= 0x00FF)
512 | Serial.print('0'); // if less 3 Digit
513 | if (val <= 0x0FFF)
514 | Serial.print('0'); // if less 4 Digit
515 | Serial.print(val, HEX);
516 | } // _printHex4
517 |
518 | // The End.
519 |
--------------------------------------------------------------------------------
/src/RDA5807M.cpp:
--------------------------------------------------------------------------------
1 | /// \file RDA5807M.cpp
2 | /// \brief Implementation for the radio library to control the RDA5807M radio chip.
3 | ///
4 | /// \author Matthias Hertel, http://www.mathertel.de
5 | /// \copyright Copyright (c) 2014-2015 by Matthias Hertel.\n
6 | /// This work is licensed under a BSD style license.\n
7 | /// See http://www.mathertel.de/License.aspx
8 | ///
9 | /// This library enables the use of the radio chip RDA5807M from http://www.rdamicro.com/.
10 | ///
11 | /// More documentation is available at http://www.mathertel.de/Arduino
12 | /// Source Code is available on https://github.com/mathertel/Radio
13 | ///
14 | /// History:
15 | /// --------
16 | /// * 05.08.2014 created.
17 |
18 |
19 | #include
20 | #include
21 | #include
22 |
23 | #include
24 |
25 | // ----- Register Definitions -----
26 |
27 | // this chip only supports FM mode
28 | #define FREQ_STEPS 10
29 |
30 | #define RADIO_REG_CHIPID 0x00
31 |
32 | #define RADIO_REG_CTRL 0x02
33 | #define RADIO_REG_CTRL_OUTPUT 0x8000
34 | #define RADIO_REG_CTRL_UNMUTE 0x4000
35 | #define RADIO_REG_CTRL_MONO 0x2000
36 | #define RADIO_REG_CTRL_BASS 0x1000
37 | #define RADIO_REG_CTRL_SEEKUP 0x0200
38 | #define RADIO_REG_CTRL_SEEK 0x0100
39 | #define RADIO_REG_CTRL_RDS 0x0008
40 | #define RADIO_REG_CTRL_NEW 0x0004
41 | #define RADIO_REG_CTRL_RESET 0x0002
42 | #define RADIO_REG_CTRL_ENABLE 0x0001
43 |
44 | #define RADIO_REG_CHAN 0x03
45 | #define RADIO_REG_CHAN_SPACE 0x0003
46 | #define RADIO_REG_CHAN_SPACE_100 0x0000
47 | #define RADIO_REG_CHAN_BAND 0x000C
48 | #define RADIO_REG_CHAN_BAND_FM 0x0000
49 | #define RADIO_REG_CHAN_BAND_FMWORLD 0x0008
50 | #define RADIO_REG_CHAN_TUNE 0x0010
51 | // RADIO_REG_CHAN_TEST 0x0020
52 | #define RADIO_REG_CHAN_NR 0x7FC0
53 |
54 | #define RADIO_REG_R4 0x04
55 | #define RADIO_REG_R4_EM50 0x0800
56 | // RADIO_REG_R4_RES 0x0400
57 | #define RADIO_REG_R4_SOFTMUTE 0x0200
58 | #define RADIO_REG_R4_AFC 0x0100
59 |
60 |
61 | #define RADIO_REG_VOL 0x05
62 | #define RADIO_REG_VOL_VOL 0x000F
63 |
64 |
65 | #define RADIO_REG_RA 0x0A
66 | #define RADIO_REG_RA_RDS 0x8000
67 | #define RADIO_REG_RA_RDSBLOCK 0x0800
68 | #define RADIO_REG_RA_STEREO 0x0400
69 | #define RADIO_REG_RA_NR 0x03FF
70 |
71 | #define RADIO_REG_RB 0x0B
72 | #define RADIO_REG_RB_FMTRUE 0x0100
73 | #define RADIO_REG_RB_FMREADY 0x0080
74 |
75 |
76 | #define RADIO_REG_RDSA 0x0C
77 | #define RADIO_REG_RDSB 0x0D
78 | #define RADIO_REG_RDSC 0x0E
79 | #define RADIO_REG_RDSD 0x0F
80 |
81 | // I2C-Address RDA Chip for sequential Access
82 | #define I2C_SEQ 0x10
83 |
84 | // I2C-Address RDA Chip for Index Access
85 | #define I2C_INDX 0x11
86 |
87 |
88 | // ----- implement
89 |
90 | // initialize the extra variables in RDA5807M
91 | RDA5807M::RDA5807M() {
92 | // maximum volume level of the chip.
93 | _maxVolume = 15;
94 | }
95 |
96 | // initialize all internals.
97 | bool RDA5807M::init() {
98 | bool result = false; // no chip found yet.
99 | DEBUG_FUNC0("init");
100 |
101 | RADIO::init(); // will create reset impulse
102 |
103 | _i2cPort->begin();
104 | _i2cPort->beginTransmission(I2C_INDX);
105 | result = _i2cPort->endTransmission();
106 | if (result != 0) {
107 | DEBUG_STR("NO radio found.");
108 |
109 | } else {
110 | DEBUG_STR("radio found.");
111 | result = true;
112 |
113 | // initialize all registers
114 | registers[RADIO_REG_CHIPID] = 0x5804; // 00 id
115 | registers[1] = 0x0000; // 01 not used
116 | registers[RADIO_REG_CTRL] = (RADIO_REG_CTRL_RESET | RADIO_REG_CTRL_ENABLE);
117 | setBand(RADIO_BAND_FM);
118 | registers[RADIO_REG_R4] = RADIO_REG_R4_EM50; // 0x1800; // 04 DE ? SOFTMUTE
119 | registers[RADIO_REG_VOL] = 0x9081; // 0x81D1; // 0x82D1 / INT_MODE, SEEKTH=0110,????, Volume=1
120 | registers[6] = 0x0000;
121 | registers[7] = 0x0000;
122 | registers[8] = 0x0000;
123 | registers[9] = 0x0000;
124 |
125 | // reset the chip
126 | _saveRegisters();
127 |
128 | registers[RADIO_REG_CTRL] = RADIO_REG_CTRL_ENABLE;
129 | _saveRegister(RADIO_REG_CTRL);
130 | } // if
131 | return (result);
132 | } // init()
133 |
134 |
135 | // switch the power off
136 | void RDA5807M::term() {
137 | DEBUG_FUNC0("term");
138 | setVolume(0);
139 | registers[RADIO_REG_CTRL] = 0x0000; // all bits off
140 | _saveRegisters();
141 | } // term
142 |
143 |
144 | // ----- Volume control -----
145 |
146 | void RDA5807M::setVolume(int8_t newVolume) {
147 | DEBUG_FUNC1("setVolume", newVolume);
148 | RADIO::setVolume(newVolume); // will constrain the _volume in the range 0.._maxVolume
149 | uint8_t v = _volume & RADIO_REG_VOL_VOL;
150 |
151 | registers[RADIO_REG_VOL] &= (~RADIO_REG_VOL_VOL);
152 | registers[RADIO_REG_VOL] |= v;
153 | _saveRegister(RADIO_REG_VOL);
154 | } // setVolume()
155 |
156 |
157 | void RDA5807M::setBassBoost(bool switchOn) {
158 | RADIO::setBassBoost(switchOn);
159 | uint16_t regCtrl = registers[RADIO_REG_CTRL];
160 | if (switchOn)
161 | regCtrl |= RADIO_REG_CTRL_BASS;
162 | else
163 | regCtrl &= (~RADIO_REG_CTRL_BASS);
164 | registers[RADIO_REG_CTRL] = regCtrl;
165 | _saveRegister(RADIO_REG_CTRL);
166 | } // setBassBoost()
167 |
168 |
169 | // Mono / Stereo
170 | void RDA5807M::setMono(bool switchOn) {
171 | RADIO::setMono(switchOn);
172 |
173 | registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_SEEK);
174 | if (switchOn) {
175 | registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_MONO;
176 | } else {
177 | registers[RADIO_REG_CTRL] &= ~RADIO_REG_CTRL_MONO;
178 | }
179 | _saveRegister(RADIO_REG_CTRL);
180 | } // setMono
181 |
182 |
183 | // Switch mute mode.
184 | void RDA5807M::setMute(bool switchOn) {
185 | RADIO::setMute(switchOn);
186 |
187 | if (switchOn) {
188 | // now don't unmute
189 | registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_UNMUTE);
190 | } else {
191 | // now unmute
192 | registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_UNMUTE;
193 | } // if
194 | _saveRegister(RADIO_REG_CTRL);
195 | } // setMute()
196 |
197 |
198 | // Switch softmute mode.
199 | void RDA5807M::setSoftMute(bool switchOn) {
200 | RADIO::setSoftMute(switchOn);
201 |
202 | if (switchOn) {
203 | registers[RADIO_REG_R4] |= (RADIO_REG_R4_SOFTMUTE);
204 | } else {
205 | registers[RADIO_REG_R4] &= (~RADIO_REG_R4_SOFTMUTE);
206 | } // if
207 | _saveRegister(RADIO_REG_R4);
208 | } // setSoftMute()
209 |
210 |
211 | // ----- Band and frequency control methods -----
212 |
213 | // tune to new band.
214 | void RDA5807M::setBand(RADIO_BAND newBand) {
215 | uint16_t r;
216 | RADIO::setBand(newBand);
217 |
218 | if (newBand == RADIO_BAND_FM) {
219 | r = RADIO_REG_CHAN_BAND_FM;
220 | } else if (newBand == RADIO_BAND_FMWORLD) {
221 | r = RADIO_REG_CHAN_BAND_FMWORLD;
222 | } else {
223 | return;
224 | }
225 | registers[RADIO_REG_CHAN] = (r | RADIO_REG_CHAN_SPACE_100);
226 | _saveRegister(RADIO_REG_CHAN);
227 | } // setBand()
228 |
229 |
230 | // retrieve the real frequency from the chip after automatic tuning.
231 | RADIO_FREQ RDA5807M::getFrequency() {
232 | // check register A
233 | _i2cPort->requestFrom(I2C_SEQ, 2);
234 | registers[RADIO_REG_RA] = _read16HL(_i2cPort);
235 |
236 | uint16_t ch = registers[RADIO_REG_RA] & RADIO_REG_RA_NR;
237 |
238 | _freq = _freqLow + (ch * 10); // assume 100 kHz spacing
239 | return (_freq);
240 | } // getFrequency
241 |
242 |
243 | void RDA5807M::setFrequency(RADIO_FREQ newF) {
244 | DEBUG_FUNC1("setFrequency", newF);
245 | uint16_t newChannel;
246 | uint16_t regChannel = registers[RADIO_REG_CHAN] & (RADIO_REG_CHAN_SPACE | RADIO_REG_CHAN_BAND);
247 |
248 | if (newF < _freqLow) newF = _freqLow;
249 | if (newF > _freqHigh) newF = _freqHigh;
250 | newChannel = (newF - _freqLow) / 10;
251 |
252 | regChannel += RADIO_REG_CHAN_TUNE; // enable tuning
253 | regChannel |= newChannel << 6;
254 |
255 | // enable output and unmute
256 | registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_OUTPUT | RADIO_REG_CTRL_UNMUTE | RADIO_REG_CTRL_RDS | RADIO_REG_CTRL_ENABLE; // | RADIO_REG_CTRL_NEW
257 | _saveRegister(RADIO_REG_CTRL);
258 |
259 | registers[RADIO_REG_CHAN] = regChannel;
260 | _saveRegister(RADIO_REG_CHAN);
261 |
262 | // adjust Volume
263 | _saveRegister(RADIO_REG_VOL);
264 | } // setFrequency()
265 |
266 |
267 | // start seek mode upwards
268 | void RDA5807M::seekUp(bool toNextSender) {
269 | // start seek mode
270 | registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_SEEKUP;
271 | registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_SEEK;
272 | _saveRegister(RADIO_REG_CTRL);
273 |
274 | registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_SEEK); // clear seekmode
275 | if (!toNextSender) {
276 | // stop scanning right now
277 | // registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_SEEK);
278 | _saveRegister(RADIO_REG_CTRL);
279 | } // if
280 | } // seekUp()
281 |
282 |
283 | // start seek mode downwards
284 | void RDA5807M::seekDown(bool toNextSender) {
285 | registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_SEEKUP);
286 | registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_SEEK;
287 | _saveRegister(RADIO_REG_CTRL);
288 |
289 | registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_SEEK); // clear seekmode
290 | if (!toNextSender) {
291 | // stop scanning right now
292 | _saveRegister(RADIO_REG_CTRL);
293 | } // if
294 | } // seekDown()
295 |
296 |
297 | // Load all status registers from to the chip
298 | // registers 0A through 0F
299 | // using the sequential read access mode.
300 | void RDA5807M::_readRegisters() {
301 | _i2cPort->requestFrom(I2C_SEQ, (6 * 2));
302 | for (int i = 0; i < 6; i++) {
303 | registers[0xA + i] = _read16HL(_i2cPort);
304 | }
305 | } // _readRegisters()
306 |
307 |
308 | // Save writable registers back to the chip
309 | // The registers 02 through 06, containing the configuration
310 | // using the sequential write access mode.
311 | void RDA5807M::_saveRegisters() {
312 | DEBUG_FUNC0("saveRegisters");
313 | _i2cPort->beginTransmission(I2C_SEQ);
314 | for (int i = 2; i <= 6; i++)
315 | _write16HL(_i2cPort, registers[i]);
316 | _i2cPort->endTransmission();
317 | } // _saveRegisters
318 |
319 |
320 | // Save one register back to the chip
321 | void RDA5807M::_saveRegister(byte regNr) {
322 | DEBUG_FUNC2X("saveRegister", regNr, registers[regNr]);
323 |
324 | _i2cPort->beginTransmission(I2C_INDX);
325 | _i2cPort->write(regNr);
326 | _write16HL(_i2cPort, registers[regNr]);
327 | _i2cPort->endTransmission();
328 | } // _saveRegister
329 |
330 |
331 |
332 | // // write a register value using 2 bytes into the Wire.
333 | // void RDA5807M::_write16(uint16_t val) {
334 | // _i2cPort->write(val >> 8);
335 | // _i2cPort->write(val & 0xFF);
336 | // } // _write16
337 |
338 |
339 | // read a register value using 2 bytes in a row
340 | // uint16_t RDA5807M::_read16(void) {
341 | // uint8_t hiByte = _i2cPort->read();
342 | // uint8_t loByte = _i2cPort->read();
343 | // return (256 * hiByte + loByte);
344 | // } // _read16
345 |
346 |
347 | // return current Radio Station Strength Information
348 | // uint8_t RDA5807M::getRSSI() {
349 | // _readRegisters();
350 | // uint8_t rssi = registers[RADIO_REG_RB] >> 10;
351 | // return(rssi);
352 | // } // getRSSI
353 |
354 |
355 | void RDA5807M::checkRDS() {
356 | // DEBUG_FUNC0("checkRDS");
357 |
358 | // check RDS data if there is a listener !
359 | if (_sendRDS) {
360 |
361 | // check register A
362 | _i2cPort->requestFrom(I2C_SEQ, 2);
363 | registers[RADIO_REG_RA] = _read16HL(_i2cPort);
364 |
365 | // if (registers[RADIO_REG_RA] & RADIO_REG_RA_RDSBLOCK) {
366 | // DEBUG_STR("BLOCK_E found.");
367 | // } // if
368 |
369 | if (registers[RADIO_REG_RA] & RADIO_REG_RA_RDS) {
370 | // check for new RDS data available
371 | uint16_t newData;
372 | bool result = false;
373 |
374 | _i2cPort->beginTransmission(I2C_INDX); // Device 0x11 for random access
375 | _i2cPort->write(RADIO_REG_RDSA); // Start at Register 0x0C
376 | _i2cPort->endTransmission(0); // restart condition
377 |
378 | _i2cPort->requestFrom(I2C_INDX, 8, 1); // Retransmit device address with READ, followed by 8 bytes
379 | newData = _read16HL(_i2cPort);
380 | if (newData != registers[RADIO_REG_RDSA]) {
381 | registers[RADIO_REG_RDSA] = newData;
382 | result = true;
383 | }
384 |
385 | newData = _read16HL(_i2cPort);
386 | if (newData != registers[RADIO_REG_RDSB]) {
387 | registers[RADIO_REG_RDSB] = newData;
388 | result = true;
389 | }
390 |
391 | newData = _read16HL(_i2cPort);
392 | if (newData != registers[RADIO_REG_RDSC]) {
393 | registers[RADIO_REG_RDSC] = newData;
394 | result = true;
395 | }
396 |
397 | newData = _read16HL(_i2cPort);
398 | if (newData != registers[RADIO_REG_RDSD]) {
399 | registers[RADIO_REG_RDSD] = newData;
400 | result = true;
401 | }
402 |
403 | // _printHex(registers[RADIO_REG_RDSA]); _printHex(registers[RADIO_REG_RDSB]);
404 | // _printHex(registers[RADIO_REG_RDSC]); _printHex(registers[RADIO_REG_RDSD]);
405 | // Serial.println();
406 |
407 | if (result) {
408 | // new data in the registers
409 | // send to RDS decoder
410 | _sendRDS(registers[RADIO_REG_RDSA], registers[RADIO_REG_RDSB], registers[RADIO_REG_RDSC], registers[RADIO_REG_RDSD]);
411 | } // if
412 | } // if
413 | }
414 | }
415 |
416 |
417 | /// Retrieve all the information related to the current radio receiving situation.
418 | void RDA5807M::getRadioInfo(RADIO_INFO *info) {
419 |
420 | RADIO::getRadioInfo(info);
421 |
422 | // read data from registers A .. F of the chip into class memory
423 | _readRegisters();
424 | info->active = true; // ???
425 | if (registers[RADIO_REG_RA] & RADIO_REG_RA_STEREO) info->stereo = true;
426 | if (registers[RADIO_REG_RA] & RADIO_REG_RA_RDS) info->rds = true;
427 | info->rssi = registers[RADIO_REG_RB] >> 10;
428 | if (registers[RADIO_REG_RB] & RADIO_REG_RB_FMTRUE) info->tuned = true;
429 | if (registers[RADIO_REG_CTRL] & RADIO_REG_CTRL_MONO) info->mono = true;
430 | } // getRadioInfo()
431 |
432 |
433 | // ----- Debug functions -----
434 |
435 | void RDA5807M::debugScan() {
436 | DEBUG_FUNC0("debugScan");
437 | uint16_t regChannel = registers[RADIO_REG_CHAN] & (RADIO_REG_CHAN_SPACE | RADIO_REG_CHAN_BAND);
438 | RADIO_FREQ f = _freqLow;
439 | int channel = 0;
440 |
441 | while (f < _freqHigh) {
442 | registers[RADIO_REG_CHAN] = regChannel | RADIO_REG_CHAN_TUNE | (channel << 6);
443 | _saveRegister(RADIO_REG_CHAN);
444 |
445 | delay(500);
446 | debugStatus();
447 |
448 | f += _freqSteps;
449 | channel += 1;
450 | } // while
451 | } // debugScan
452 |
453 |
454 | // send a status report to the serial port
455 | // dump all registers to Serial output
456 | void RDA5807M::debugStatus() {
457 | char s[12];
458 |
459 | // read data from registers A .. F of the chip into class memory
460 | _readRegisters();
461 |
462 | formatFrequency(s, sizeof(s));
463 | Serial.print("Frequency=");
464 | Serial.print(s);
465 |
466 | uint16_t pi = registers[RADIO_REG_RDSA];
467 | Serial.print(" PI=");
468 | _printHex4(pi);
469 |
470 | Serial.print((registers[RADIO_REG_RA] & RADIO_REG_RA_STEREO) ? " Stereo" : " Mono ");
471 | Serial.print((registers[RADIO_REG_RA] & RADIO_REG_RA_RDS) ? " ---" : " RDS");
472 |
473 | int rssi = registers[RADIO_REG_RB] >> 10;
474 |
475 | Serial.print(" Sig=");
476 | if (rssi < 10) Serial.write(' ');
477 | Serial.print(rssi);
478 | Serial.print(' ');
479 | for (int i = 0; i < rssi - 15; i++) { Serial.write('*'); } // Empfangspegel ab 15. Zeichen
480 | Serial.println();
481 |
482 | // ruler
483 | Serial.println("0 1 2 3 4 5 6 7 8 9 A B C D E F");
484 | // variables
485 | for (int n = 0; n < 16; n++) { _printHex4(registers[n]); }
486 | Serial.println();
487 |
488 | // registers
489 | _i2cPort->beginTransmission(I2C_INDX); // Device 0x11 for random access
490 | _i2cPort->write(0x00); // Start at Register 0x0C
491 | _i2cPort->endTransmission(0); // restart condition
492 | _i2cPort->requestFrom(I2C_INDX, 32, 1); // Retransmit device address with READ, followed by 8 bytes
493 | for (int n = 0; n < 16; n++) {
494 | _printHex4(_read16HL(_i2cPort));
495 | }
496 | Serial.println();
497 |
498 | // clear text information in Registers
499 | if (getBassBoost()) Serial.print("BassBoost ");
500 | if (getMono()) Serial.print("Mono ");
501 | int v = getVolume();
502 | Serial.print("Volume=");
503 | Serial.print(v);
504 | Serial.print(' ');
505 | Serial.println();
506 |
507 | } // debugStatus
508 |
509 |
510 | // ----- internal functions -----
511 |
512 |
513 | // The End.
514 |
--------------------------------------------------------------------------------
/src/SI4703.cpp:
--------------------------------------------------------------------------------
1 | ///
2 | /// \file SI4703.cpp
3 | /// \brief Implementation for the radio library to control the SI4703 radio chip.
4 | ///
5 | /// \author Matthias Hertel, http://www.mathertel.de
6 | /// \copyright Copyright (c) 2014-2015 by Matthias Hertel.\n
7 | /// This work is licensed under a BSD style license.\n
8 | /// See http://www.mathertel.de/License.aspx
9 | ///
10 | /// This library enables the use of the Radio Chip SI4703.
11 | ///
12 | /// More documentation is available at http://www.mathertel.de/Arduino
13 | /// Source Code is available on https://github.com/mathertel/Radio
14 | ///
15 | /// History, see
16 |
17 | #include
18 | #include // The chip is controlled via the standard Arduiino Wire library and the IIC/I2C bus.
19 |
20 | #include // Include the common radio library interface
21 | #include
22 |
23 | // ----- Definitions for the Wire communication
24 |
25 | #define SI4703_ADR 0x10 // 0b._001.0000 = I2C address of Si4703 - note that the Wire function assumes non-left-shifted I2C address, not 0b.0010.000W
26 | #define I2C_FAIL_MAX 10 // This is the number of attempts we will try to contact the device before erroring out
27 |
28 |
29 | // ----- Radio chip specific definitions including the registers
30 |
31 | // Register Definitions -----
32 |
33 | // Define the register names
34 | #define SI4703_DEVICEID 0x00
35 | #define SI4703_CHIPID 0x01
36 | #define POWERCFG 0x02
37 | #define CHANNEL 0x03
38 | #define SYSCONFIG1 0x04
39 | #define SYSCONFIG2 0x05
40 | #define SYSCONFIG3 0x06
41 | #define STATUSRSSI 0x0A
42 | #define READCHAN 0x0B
43 | #define RDSA 0x0C
44 | #define RDSB 0x0D
45 | #define RDSC 0x0E
46 | #define RDSD 0x0F
47 |
48 | // Register 0x02 - POWERCFG
49 | #define DSMUTE 15
50 | #define DMUTE 14
51 | #define SETMONO 13
52 | #define RDSMODE 11
53 | #define SKMODE 10
54 | #define SEEKUP 9
55 | #define SEEK 8
56 |
57 | // Register 0x03 - CHANNEL
58 | #define TUNE 15
59 |
60 | // Register 0x04 - SYSCONFIG1
61 | #define DEEMPHASIS50 0x0800
62 | #define RDS 12
63 | #define DE 11
64 |
65 |
66 | // Register 0x05 - SYSCONFIG2
67 | #define SEEKTH_MASK 0xFF00
68 | #define SEEKTH_MIN 0x0000
69 | #define SEEKTH_MID 0x1000
70 | #define SEEKTH_MAX 0x7F00
71 |
72 | #define FMSPACE_MASK 0x0030
73 | #define FMSPACE_50 0x0020
74 | #define FMSPACE_100 0x0010
75 | #define FMSPACE_200 0x0000
76 |
77 | #define VOLUME_MASK 0x000F
78 |
79 | // Register 0x06 - SYSCONFIG3
80 | #define SKSNR_MASK 0x00F0
81 | #define SKSNR_OFF 0x0000
82 | #define SKSNR_MIN 0x0010
83 | #define SKSNR_MID 0x0030
84 | #define SKSNR_MAX 0x0070
85 |
86 | #define SKCNT_MASK 0x000F
87 | #define SKCNT_OFF 0x0000
88 | #define SKCNT_MIN 0x000F
89 | #define SKCNT_MID 0x0003
90 | #define SKCNT_MAX 0x0001
91 |
92 |
93 | // Register 0x0A - STATUSRSSI
94 | #define RDSR 0x8000 ///< RDS ready
95 | #define STC 0x4000 ///< Seek Tune Complete
96 | #define SFBL 0x2000 ///< Seek Fail Band Limit
97 | #define AFCRL 0x1000
98 | #define RDSS 0x0800 ///< RDS syncronized
99 | #define SI 0x0100 ///< Stereo Indicator
100 | #define RSSI 0x00FF
101 |
102 | // ----- implement
103 |
104 | // initialize the extra variables in SI4703
105 | SI4703::SI4703() {
106 | _i2caddr = 0x10; // standard address of chip
107 | }
108 |
109 | void SI4703::setup(int feature, int value) {
110 | RADIO::setup(feature, value);
111 | if (feature == RADIO_MODEPIN) {
112 | _sdaPin = value;
113 | } // if
114 | } // setup()
115 |
116 |
117 | // initialize all internals.
118 | bool SI4703::init() {
119 | bool found = false; // no chip found yet.
120 | DEBUG_FUNC0("SI4703::init");
121 |
122 | // To get the Si4703 into 2-wire mode, SEN needs to be high and SDIO needs to be low after a reset
123 | // The breakout board has SEN pulled high, but also has SDIO pulled high. Therefore, after a normal power up
124 | // The Si4703 will be in an unknown state. RST must be controlled
125 |
126 | if (_sdaPin >= 0) {
127 | pinMode(_sdaPin, OUTPUT); // SDIO is connected to SDA for I2C
128 | digitalWrite(_sdaPin, LOW); // A low SDA during reset indicates a 2-wire interface
129 | delay(5);
130 | }
131 |
132 | RADIO::init(); // will create reset impulse
133 |
134 | _i2cPort->begin(); // Now that the unit is reset and I2C inteface mode, we need to begin I2C
135 | found = RADIO::_wireExists(_i2cPort, _i2caddr);
136 |
137 | _readRegisters(); // Read the current register set
138 | // registers[0x07] = 0xBC04; //Enable the oscillator, from AN230 page 9, rev 0.5 (DOES NOT WORK, wtf Silicon Labs datasheet?)
139 | registers[0x07] = 0x8100; // Enable the oscillator, from AN230 page 9, rev 0.61 (works)
140 | _saveRegisters(); // Update
141 |
142 | delay(500); // Wait for clock to settle - from AN230 page 9
143 |
144 | return (found);
145 | } // init()
146 |
147 |
148 | // switch the power off
149 | void SI4703::term() {
150 | DEBUG_FUNC0("SI4703::term");
151 | RADIO::term();
152 | } // term
153 |
154 |
155 | // ----- Volume control -----
156 |
157 | void SI4703::setVolume(int8_t newVolume) {
158 | DEBUG_FUNC1("setVolume", newVolume);
159 | if (newVolume > 15)
160 | newVolume = 15;
161 | _readRegisters(); // Read the current register set
162 | registers[SYSCONFIG2] &= ~(VOLUME_MASK); // Clear volume bits
163 | registers[SYSCONFIG2] |= newVolume; // Set new volume
164 | _saveRegisters(); // Update
165 | RADIO::setVolume(newVolume);
166 | } // setVolume()
167 |
168 |
169 | // Mono / Stereo
170 | void SI4703::setMono(bool switchOn) {
171 | DEBUG_FUNC1("setMono", switchOn);
172 | RADIO::setMono(switchOn);
173 | _readRegisters(); // Read the current register set
174 | if (switchOn) {
175 | registers[POWERCFG] |= (1 << SETMONO); // set force mono bit
176 | } else {
177 | registers[POWERCFG] &= ~(1 << SETMONO); // clear force mono bit
178 | } // if
179 | _saveRegisters();
180 | } // setMono
181 |
182 |
183 | /// Switch mute mode.
184 | void SI4703::setMute(bool switchOn) {
185 | DEBUG_FUNC1("setMute", switchOn);
186 | RADIO::setMute(switchOn);
187 |
188 | if (switchOn) {
189 | registers[POWERCFG] &= ~(1 << DMUTE); // clear mute bit
190 | } else {
191 | registers[POWERCFG] |= (1 << DMUTE); // set mute bit
192 | } // if
193 | _saveRegisters();
194 |
195 | } // setMute()
196 |
197 |
198 | /// Switch soft mute mode.
199 | void SI4703::setSoftMute(bool switchOn) {
200 | DEBUG_FUNC1("setSoftMute", switchOn);
201 | RADIO::setSoftMute(switchOn);
202 |
203 | if (switchOn) {
204 | registers[POWERCFG] &= ~(1 << DSMUTE); // clear mute bit
205 | } else {
206 | registers[POWERCFG] |= (1 << DSMUTE); // set mute bit
207 | } // if
208 | _saveRegisters();
209 |
210 | } // setSoftMute()
211 |
212 |
213 | // ----- Band and frequency control methods -----
214 |
215 | // tune to new band.
216 | void SI4703::setBand(RADIO_BAND newBand) {
217 |
218 | if (newBand == RADIO_BAND_FM) {
219 | // init chip here for FM
220 | RADIO::setBand(newBand);
221 | _freqLow = 8750;
222 |
223 | _readRegisters(); // Read the current register set
224 |
225 | // PowerConfig
226 | registers[POWERCFG] = 0x4001; // Enable the IC
227 | if (!_mute)
228 | registers[POWERCFG] |= (1 << DMUTE); // disable Mute
229 | if (!_softMute)
230 | registers[POWERCFG] |= (1 << DSMUTE); // disable softmute
231 |
232 | registers[SYSCONFIG1] |= (1 << RDS); // Enable RDS
233 |
234 |
235 | if (_deEmphasis == RADIO_DEEMPHASIS_50) {
236 | registers[SYSCONFIG1] |= DEEMPHASIS50; // 50µs
237 | } else {
238 | registers[SYSCONFIG1] &= ~DEEMPHASIS50; // 75µs
239 | }
240 |
241 | if (_fmSpacing == RADIO_FMSPACING_50) {
242 | _freqSteps = 5;
243 | registers[SYSCONFIG2] &= ~(FMSPACE_MASK); // Clear all
244 | registers[SYSCONFIG2] |= FMSPACE_50;
245 |
246 | } else if (_fmSpacing == RADIO_FMSPACING_100) {
247 | _freqSteps = 10;
248 | registers[SYSCONFIG2] &= ~(FMSPACE_MASK); // Clear all
249 | registers[SYSCONFIG2] |= FMSPACE_100; // set 100
250 |
251 | } else if (_fmSpacing == RADIO_FMSPACING_200) {
252 | _freqSteps = 20;
253 | registers[SYSCONFIG2] &= ~(FMSPACE_MASK); // Clear all
254 | registers[SYSCONFIG2] |= FMSPACE_200;
255 | }
256 |
257 | _volume = 1;
258 | registers[SYSCONFIG2] &= ~(VOLUME_MASK);
259 | registers[SYSCONFIG2] |= (_volume & VOLUME_MASK); // Set volume
260 |
261 | // set seek parameters
262 | registers[SYSCONFIG2] |= SEEKTH_MID; // Set volume
263 | registers[SYSCONFIG3] &= ~(SKSNR_MASK); // Clear seek mask bits
264 | registers[SYSCONFIG3] |= SKSNR_MID; // Set volume
265 |
266 | registers[POWERCFG] |= (1 << RDSMODE); // set force verbose bit
267 |
268 | _saveRegisters(); // Update
269 | delay(110); // Max powerup time, from datasheet page 13
270 | } // if
271 | } // setBand()
272 |
273 |
274 | /**
275 | * @brief Retrieve the real frequency from the chip after automatic tuning.
276 | * @return RADIO_FREQ the current frequency.
277 | */
278 | RADIO_FREQ SI4703::getFrequency() {
279 | _readRegisters();
280 | int channel = registers[READCHAN] & 0x03FF; // Mask out everything but the lower 10 bits
281 | _freq = (channel * _freqSteps) + _freqLow;
282 | return (_freq);
283 | } // getFrequency
284 |
285 |
286 | /**
287 | * @brief Change the frequency in the chip.
288 | * @param newF
289 | * @return void
290 | */
291 | void SI4703::setFrequency(RADIO_FREQ newF) {
292 | DEBUG_FUNC1("setFrequency", newF);
293 | if (newF < _freqLow)
294 | newF = _freqLow;
295 | if (newF > _freqHigh)
296 | newF = _freqHigh;
297 |
298 | _readRegisters();
299 | int channel = (newF - _freqLow) / _freqSteps;
300 |
301 | // These steps come from AN230 page 20 rev 0.5
302 | registers[CHANNEL] &= 0xFE00; // Clear out the channel bits
303 | registers[CHANNEL] |= channel; // Mask in the new channel
304 | registers[CHANNEL] |= (1 << TUNE); // Set the TUNE bit to start
305 | _saveRegisters();
306 | if (_sendRDS) {
307 | _sendRDS(0, 0, 0, 0);
308 | }
309 | _waitEnd();
310 | } // setFrequency()
311 |
312 |
313 | // start seek mode upwards
314 | void SI4703::seekUp(bool toNextSender) {
315 | DEBUG_FUNC1("seekUp", toNextSender);
316 | _seek(true);
317 | } // seekUp()
318 |
319 |
320 | // start seek mode downwards
321 | void SI4703::seekDown(bool toNextSender) {
322 | DEBUG_FUNC1("seekDown", toNextSender);
323 | _seek(false);
324 | } // seekDown()
325 |
326 |
327 | // Load all status registers from to the chip
328 | void SI4703::_readRegisters() {
329 | // Si4703 begins reading from register upper register of 0x0A and reads to 0x0F, then loops to 0x00.
330 | _i2cPort->requestFrom(_i2caddr, 32); // We want to read the entire register set from 0x0A to 0x09 = 32 bytes.
331 |
332 | // Remember, register 0x0A comes in first so we have to shuffle the array around a bit
333 | for (int x = 0x0A;; x++) { // Read in these 32 bytes
334 | if (x == 0x10)
335 | x = 0; // Loop back to zero
336 | registers[x] = _read16HL(_i2cPort);
337 | if (x == 0x09)
338 | break; // We're done!
339 | } // for
340 | } // _readRegisters()
341 |
342 |
343 | // Load all status registers from to the chip
344 | void SI4703::_readRegister0A() {
345 | _i2cPort->requestFrom(_i2caddr, 2); // We want to read the entire register set from 0x0A to 0x09 = 32 bytes.
346 | registers[0x0A] = _read16HL(_i2cPort);
347 | } // _readRegister0A()
348 |
349 |
350 | // Save writable registers back to the chip
351 | // The registers 02 through 06, containing the configuration
352 | // using the sequential write access mode.
353 | void SI4703::_saveRegisters() {
354 | // Write the current 9 control registers (0x02 to 0x07) to the Si4703
355 | // It's a little weird, you don't write an I2C addres
356 | // The Si4703 assumes you are writing to 0x02 first, then increments
357 |
358 | _i2cPort->beginTransmission(_i2caddr);
359 | // A write command automatically begins with register 0x02 so no need to send a write-to address
360 | // First we send the 0x02 to 0x07 control registers
361 | // In general, we should not write to registers 0x08 and 0x09
362 | for (int regSpot = 0x02; regSpot < 0x08; regSpot++) {
363 | _write16HL(_i2cPort, registers[regSpot]);
364 | }
365 |
366 | // End this transmission
367 | byte ack = _i2cPort->endTransmission();
368 | if (ack != 0) { // We have a problem!
369 | Serial.print("Write Fail:"); // No ACK!
370 | Serial.println(ack, DEC); // I2C error: 0 = success, 1 = data too long, 2 = rx NACK on address, 3 = rx NACK on data, 4 = other error
371 | }
372 | } // _saveRegisters
373 |
374 |
375 | /// Retrieve all the information related to the current radio receiving situation.
376 | void SI4703::getRadioInfo(RADIO_INFO *info) {
377 | RADIO::getRadioInfo(info); // all settings to last current settings
378 |
379 | _readRegisters();
380 | info->active = true; // ???
381 | if (registers[STATUSRSSI] & SI)
382 | info->stereo = true;
383 | info->rssi = registers[STATUSRSSI] & RSSI;
384 | if (registers[STATUSRSSI] & (RDSS))
385 | info->rds = true;
386 | if (registers[STATUSRSSI] & STC)
387 | info->tuned = true;
388 | if (registers[POWERCFG] & (1 << SETMONO))
389 | info->mono = true;
390 | } // getRadioInfo()
391 |
392 |
393 | /// Return current audio settings.
394 | void SI4703::getAudioInfo(AUDIO_INFO *info) {
395 | RADIO::getAudioInfo(info);
396 |
397 | _readRegisters();
398 | if (!(registers[POWERCFG] & (1 << DMUTE)))
399 | info->mute = true;
400 | if (!(registers[POWERCFG] & (1 << DSMUTE)))
401 | info->softmute = true;
402 | info->bassBoost = false; // no bassBoost
403 | info->volume = registers[SYSCONFIG2] & VOLUME_MASK;
404 | } // getAudioInfo()
405 |
406 |
407 | void SI4703::checkRDS() {
408 | // DEBUG_FUNC0("checkRDS");
409 | unsigned long now = millis();
410 |
411 | // check if there is a listener !
412 | if ((_sendRDS) && (now > _lastRDSPoll + 40)) {
413 | _readRegister0A();
414 | _lastRDSPoll = now;
415 |
416 | // int r1 = registers[STATUSRSSI];
417 | // _readRegisters();
418 | // int r2 = registers[STATUSRSSI];
419 | // Serial.printf("== 0x%04x 0x%04x\n", r1, r2);
420 |
421 |
422 | // check for a RDS data set ready
423 | if (registers[STATUSRSSI] & RDSR) {
424 | _readRegisters();
425 | _lastRDSPoll = now;
426 | uint8_t errA = (registers[STATUSRSSI] >> 9) & 3;
427 | uint8_t errB = (registers[READCHAN] >> 14) & 3;
428 | uint8_t errC = (registers[READCHAN] >> 12) & 3;
429 | uint8_t errD = (registers[READCHAN] >> 10) & 3;
430 | if ((errA != 3) && (errB != 3) && (errC != 3) && (errD != 3))
431 | _sendRDS(registers[RDSA], registers[RDSB], registers[RDSC], registers[RDSD]);
432 | /*
433 | Serial.print(" = 0x"); _printHex4( (registers[STATUSRSSI] >> 9)&3 ); Serial.print(' ');
434 | Serial.print(" = 0x"); _printHex4( (registers[READCHAN] >> 14)&3 ); Serial.print(' ');
435 | Serial.print(" = 0x"); _printHex4( (registers[READCHAN] >> 12)&3 ); Serial.print(' ');
436 | Serial.print(" = 0x"); _printHex4( (registers[READCHAN] >> 10)&3 ); Serial.println();
437 | */
438 | } // if
439 | } // if
440 | } // checkRDS
441 |
442 |
443 | void SI4703::writeGPIO(int GPIO, int val) {
444 | _readRegisters(); // Read the current register set
445 |
446 | switch (GPIO) {
447 | case GPIO1:
448 | registers[SYSCONFIG1] &= ~(0b11 << GPIO1);
449 | registers[SYSCONFIG1] |= (val << GPIO1);
450 | break;
451 | case GPIO2:
452 | registers[SYSCONFIG1] &= ~(0b11 << GPIO2);
453 | registers[SYSCONFIG1] |= (val << GPIO2);
454 | break;
455 | case GPIO3:
456 | registers[SYSCONFIG1] &= ~(0b11 << GPIO3);
457 | registers[SYSCONFIG1] |= (val << GPIO3);
458 | break;
459 | default:
460 | break;
461 | }
462 | _saveRegisters(); // Write to registers
463 | }
464 |
465 | // ----- Debug functions -----
466 |
467 | /// Send the current values of all registers to the Serial port.
468 | void SI4703::debugStatus() {
469 | RADIO::debugStatus();
470 |
471 | _readRegisters();
472 | // Print all registers for debugging
473 | for (int x = 0; x < 16; x++) {
474 | Serial.print("Reg: 0x0");
475 | Serial.print(x, HEX);
476 | Serial.print(" = 0x");
477 | _printHex4(registers[x]);
478 | Serial.println();
479 | } // for
480 | } // debugStatus
481 |
482 |
483 | /// Seeks out the next available station
484 | void SI4703::_seek(bool seekUp) {
485 | uint16_t reg;
486 |
487 | _readRegisters();
488 | // not wrapping around.
489 | reg = registers[POWERCFG] & ~((1 << SKMODE) | (1 << SEEKUP));
490 |
491 | if (seekUp)
492 | reg |= (1 << SEEKUP); // Set the Seek-up bit
493 |
494 | reg |= (1 << SEEK); // Start seek now
495 |
496 | // save the registers and start seeking...
497 | registers[POWERCFG] = reg;
498 | _saveRegisters();
499 | if (_sendRDS) {
500 | _sendRDS(0, 0, 0, 0);
501 | }
502 | _waitEnd();
503 | } // _seek
504 |
505 |
506 | /// wait until the current seek and tune operation is over.
507 | void SI4703::_waitEnd() {
508 | DEBUG_FUNC0("_waitEnd");
509 |
510 | // wait until STC gets high
511 | do {
512 | _readRegister0A();
513 | delay(10);
514 | } while ((registers[STATUSRSSI] & STC) == 0);
515 |
516 | // DEBUG_VAL("Freq:", getFrequency());
517 |
518 | _readRegisters();
519 | // get the SFBL bit.
520 | if (registers[STATUSRSSI] & SFBL)
521 | DEBUG_STR("Seek limit hit");
522 |
523 | // end the seek mode
524 | registers[POWERCFG] &= ~(1 << SEEK);
525 | registers[CHANNEL] &= ~(1 << TUNE); // Clear the tune after a tune has completed
526 | _saveRegisters();
527 |
528 | // wait until STC gets down again
529 | do {
530 | _readRegisters();
531 | } while ((registers[STATUSRSSI] & STC) != 0);
532 | } // _waitEnd()
533 |
534 |
535 | // ----- internal functions -----
536 |
537 | // The End.
538 |
--------------------------------------------------------------------------------