diff --git a/.gitignore b/.gitignore index ee8e2a2..6476934 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,9 @@ # Executables *.exe *.out -*.app \ No newline at end of file +*.app + +/temp +/.vscode +/.development +_* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1381cfe --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,53 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [2.0.0] - 2020-09-17 + +> **Important changes** +> +> This release focuses adding the si4721 radio chip to the library. The adoption was done using the board from sparkfun you can find here: +> . +> +> This chip also supports sending FM signals, there is a special example 'TransmitSI4721.ino' to show this functionality. + +Thanks to [@NPoole](https://github.com/NPoole) for adding the adoption of the si4721 chip. + + +### Added Examples + +* **[TestSI4721.ino](/examples/TestSI4721/TestSI4721.md)** - This is the simple fixed settings example to proof correct functionality and wiring. + +* **[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 govermant. + + + +### Enhancements + +* The base radio class implementations now has some I2c utility routings that will be further adopted in the chip libraries. +* Some code to initialize the I2C bus has beed added to the examples to support ESP8266 boards. + +### Fixes + +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: + +* 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. +* The setBand() function will configure the radio chip and start operation. The radio chip will be in power up state. +* The term() function will stop any operation. The radio chip will be in power down state. Communication with the chip will still work. +* To implement a "standby" functionality setBand() for "on" and term() for "standby" should be used. + +This is verified for SI4721 for now. + + +### Known Issues + +The TransmitSI4721.ino example and the library doesn't offer all options for transmitting but some basic functionality. +Improvements are welcome. + +--- + +## Notes + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as used for the Arduino libraries. + diff --git a/LICENSE b/LICENSE index 761b9f4..6c4db90 100644 --- a/LICENSE +++ b/LICENSE @@ -1,15 +1,30 @@ -See http://www.mathertel.de/License.aspx +BSD 3-Clause License -Software License Agreement (BSD License) - -Copyright (c) 2005-2014 by Matthias Hertel, http://www.mathertel.de/ +Copyright (c) 2005-2020, Matthias Hertel, http://www.mathertel.de/ All rights reserved. -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. -•Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -•Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -•Neither the name of the copyright owners nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index b4e5220..aec0213 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,27 @@ This library is about controlling an FM radio chips by using an Arduino board and some optional components like a LCD display, a rotary encoder, a LCD+Keyboard shield or an Ethernet Shield to build a standalone radio. -In the [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. +In the [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. There are various examples included that show using the library using different hardware setups. +The library is working for many boards like Arduino, Arduino Mega, ESP8266 and maybe more. + +See also the [Changelog](CHANGELOG.md). + ## Documentation -The documentation for the libraries in DOXYGEN style can be found at [http://mathertel.github.io/Radio/html](html/index.html). +The API documentation for the libraries in DOXYGEN style can be found at [http://mathertel.github.io/Radio/html](html/index.html). -A more detailed article is availabe at [www.mathertel.de/Arduino/RadioLibrary.aspx](http://www.mathertel.de/Arduino/RadioLibrary.aspx). +A more detailed article is available at [www.mathertel.de/Arduino/RadioLibrary.aspx](http://www.mathertel.de/Arduino/RadioLibrary.aspx). -Currently the following chips are supported: +Currently the following radio receiver chips are supported: -* The RDA5807M from RDA Microelectronics -* The SI4703 from Silicon Labs -* The SI4705 from Silicon Labs -* The TEA5767 from NXP +* The **RDA5807M** from RDA Microelectronics +* The **SI4703** from Silicon Labs +* The **SI4705** from Silicon Labs +* The **SI4721** from Silicon Labs +* The **TEA5767** from NXP 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. @@ -25,14 +30,35 @@ For each of these chips a specific library is implemented that knows how to comm 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. +Currently the following radio transmitter chips are supported: + +* The **SI4721** from Silicon Labs + + +## Contributions + +Contributions to the library like features, fixes and support of other chips and boards are welcome using Pull Requests. + +Please don't ask general programming questions in this project. Radio chip specific questions may be answered by the community (or not) and are closed after some months of inactivity. + ## Examples -Within the Arduino library you can find examples that implement different scenarios to control the radio chips: +Within the Arduino library you can find examples that implement different scenarios to control various radio chips. + +The basic examples only startup the chips and set a static station and volume:LCDKeypadRadio +* **TestRDA5807M** to test the RDA5807M chip. +* **TestSI4703** to test the SI4703 chip. +* **TestSI4705** to test the SI4705 chip. +* **TestSI4721** to test the SI4721 chip. +* **TestTEA5767** to test the TEA5767 chip. + +The examples can be used with several chips: -* The basic examples only startup the chips and set a static station and volume. -* The SerialRadio example needs only an arduino and uses the Serial in- and output to change the settings and report information. -* The LCDRadio example is similar to SerialRadio but also populates some information to an attached LCD. -* The LCDKeypadRadio example uses the popular LCDKeypad shield. -* The WebRadio example is the most advanced radio that runs on an Arduino Mega with an Ethernet Shield and an rotator encoder. You can also control the radio by using a web site that is available on the Arduino. +* The **SerialRadio** example needs only an arduino and uses the Serial in- and output to change the settings and report information. +* The **ScanRadio** is similar to the SerialRadio example but includes some experimental scanning approaches. +* The **LCDRadio** example is similar to SerialRadio but also populates some information to an attached LCD. +* The **LCDKeypadRadio** example uses the popular LCDKeypad shield. +* The **WebRadio** example is the most advanced radio that runs on an Arduino Mega with an Ethernet Shield and an rotator encoder. You can also control the radio by using a web site that is available on the Arduino. +The only sending example for the SI4721 chip can be found in **TransmitSI4721**. diff --git a/examples/LCDKeypadRadio/LCDKeypadRadio.ino b/examples/LCDKeypadRadio/LCDKeypadRadio.ino index 2ab77f5..9572bde 100644 --- a/examples/LCDKeypadRadio/LCDKeypadRadio.ino +++ b/examples/LCDKeypadRadio/LCDKeypadRadio.ino @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -90,7 +91,8 @@ int i_sidx=5; ///< Start at Station with index=5 // RADIO radio; ///< Create an instance of a non functional radio. // RDA5807M radio; ///< Create an instance of a RDA5807 chip radio // SI4703 radio; ///< Create an instance of a SI4703 chip radio. -SI4705 radio; ///< Create an instance of a SI4705 chip radio. +// SI4705 radio; ///< Create an instance of a SI4705 chip radio. +SI4721 radio; ///< Create an instance of a SI4721 chip radio. // TEA5767 radio; ///< Create an instance of a TEA5767 chip radio. @@ -234,7 +236,7 @@ void setup() { radio.setMono(false); radio.setMute(false); - // radio.debugRegisters(); + // radio._wireDebug(); radio.setVolume(8); Serial.write('>'); @@ -307,4 +309,3 @@ void loop() { } // loop // End. - diff --git a/examples/LCDRadio/LCDRadio.ino b/examples/LCDRadio/LCDRadio.ino index f98d415..aa18d01 100644 --- a/examples/LCDRadio/LCDRadio.ino +++ b/examples/LCDRadio/LCDRadio.ino @@ -1,544 +1,545 @@ -/// -/// \file LCDRadio.ino -/// \brief Radio implementation including LCD output. -/// -/// \author Matthias Hertel, http://www.mathertel.de -/// \copyright Copyright (c) 2014 by Matthias Hertel.\n -/// This work is licensed under a BSD style license.\n -/// See http://www.mathertel.de/License.aspx -/// -/// \details -/// This is a full function radio implementation that uses a LCD display to show the current station information.\n -/// It can be used with various chips after adjusting the radio object definition.\n -/// Open the Serial console with 57600 baud to see current radio information and change various settings. -/// -/// Wiring -/// ------ -/// The necessary wiring of the various chips are described in the Testxxx example sketches. -/// The boards have to be connected by using the following connections: -/// -/// Arduino port | SI4703 signal | RDA5807M signal -/// :----------: | :-----------: | :-------------: -/// GND (black) | GND | GND -/// 3.3V (red) | VCC | VCC -/// 5V (red) | - | - -/// A5 (yellow) | SCLK | SCLK -/// A4 (blue) | SDIO | SDIO -/// D2 | RST | - -/// -/// More documentation and source code is available at http://www.mathertel.de/Arduino -/// -/// History: -/// -------- -/// * 05.08.2014 created. -/// * 06.10.2014 working. - - - -#include - -#include -#include -#include -#include - -#include - -#include - -#include -#include - -// Define some stations available at your locations here: -// 89.40 MHz as 8940 - -// RADIO_FREQ preset[] = {8850, 8930, 9320,9350, 9450,9570, 9680, 9880, 10030, 10260, 10400, 10500, 10600,10650,10800}; - -RADIO_FREQ preset[] = { - 8770, - 8810, // hr1 - 8820, - 8850, // Bayern2 - 8890, // ??? - 8930, // * hr3 - 8980, - 9180, - 9220, 9350, - 9440, // * hr1 - 9510, // - Antenne Frankfurt - 9530, - 9560, // Bayern 1 - 9680, 9880, - 10020, // planet - 10090, // ffh - 10110, // SWR3 - 10030, 10260, 10380, 10400, - 10500 // * FFH -}; -// , 10650,10650,10800 - -int i_sidx=5; // Start at Station with index=5 -int i_smax=14; // Max Index of Stations - -/// Setup a RoraryEncoder for pins A2 and A3: -RotaryEncoder encoder(A2, A3); -// RotaryEncoder encoder(A9, A8); - -int encoderLastPos; -unsigned long encoderLastTime; - - -// variables for rotator encoder -//unsigned long lastRotatorChange = 0; // last time a change of the rotator was detected. - -/// The radio object has to be defined by using the class corresponding to the used chip. -/// by uncommenting the right radio object definition. - -// RADIO radio; // Create an instance of a non functional radio. -// RDA5807M radio; // Create an instance of a RDA5807 chip radio -SI4703 radio; // Create an instance of a SI4703 chip radio. -// TEA5767 radio; // Create an instance of a TEA5767 chip radio. - -/// The lcd object has to be defined by using a LCD library that supports the standard functions -/// When using a I2C->LCD library ??? the I2C bus can be used to control then radio chip and the lcd. - -/// get a LCD instance -LiquidCrystal_PCF8574 lcd(0x27); // set the LCD address to 0x27 for a 16 chars and 2 line display - -OneButton menuButton(A10, true); -OneButton seekButton(A11, true); - - -/// get a RDS parser -RDSParser rds; - - -/// State definition for this radio implementation. -enum RADIO_STATE { - STATE_NONE = 0, - STATE_PARSECOMMAND, ///< waiting for a new command character. - STATE_PARSEINT, ///< waiting for digits for the parameter. - STATE_EXEC, ///< executing the command. - - STATE_FREQ, - STATE_VOL, - STATE_MONO, - STATE_SMUTE - -}; - -RADIO_STATE state; ///< The state variable is used for parsing input characters. -RADIO_STATE rot_state; - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -/// Update the Frequency on the LCD display. -void DisplayFrequency(RADIO_FREQ f) -{ - char s[12]; - radio.formatFrequency(s, sizeof(s)); - Serial.print("FREQ:"); Serial.println(s); - lcd.setCursor(0, 0); - lcd.print(s); -} // DisplayFrequency() - - -/// Update the ServiceName text on the LCD display when in RDS mode. -void DisplayServiceName(char *name) -{ - Serial.print("RDS:"); Serial.println(name); - if (rot_state == STATE_FREQ) { - lcd.setCursor(0, 1); - lcd.print(name); - } -} // DisplayServiceName() - - -void DisplayTime(uint8_t hour, uint8_t minute) { - Serial.print("RDS-Time:"); - if (hour < 10) Serial.print('0'); - Serial.print(hour); - Serial.print(':'); - if (minute < 10) Serial.print('0'); - Serial.print(minute); -} // DisplayTime() - - -/// Display the current volume. -void DisplayVolume(uint8_t v) -{ - Serial.print("VOL: "); Serial.println(v); - - lcd.setCursor(0, 1); - lcd.print("VOL: "); lcd.print(v); -} // DisplayVolume() - - -/// Display the current mono switch. -void DisplayMono(uint8_t v) -{ - Serial.print("MONO: "); Serial.println(v); - lcd.setCursor(0, 1); - lcd.print("MONO: "); lcd.print(v); -} // DisplayMono() - - -/// Display the current soft mute switch. -void DisplaySoftMute(uint8_t v) -{ - Serial.print("SMUTE: "); Serial.println(v); - lcd.setCursor(0, 1); - lcd.print("SMUTE: "); lcd.print(v); -} // DisplaySoftMute() - - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) { - rds.processData(block1, block2, block3, block4); -} - - -// this function will be called when the menuButton was clicked -void doMenuClick() { - unsigned long now = millis(); - - if (rot_state == STATE_FREQ) { - // jump into volume mode - rot_state = STATE_VOL; - encoderLastPos = radio.getVolume(); - encoder.setPosition(encoderLastPos); - DisplayVolume(encoderLastPos); - - } - else if (rot_state == STATE_VOL) { - // jump into mono/stereo switch - rot_state = STATE_MONO; - encoderLastPos = radio.getMono(); - encoder.setPosition(encoderLastPos); - DisplayMono(encoderLastPos); - - } - else if (rot_state == STATE_MONO) { - // jump into soft mute switch - rot_state = STATE_SMUTE; - encoderLastPos = radio.getSoftMute(); - encoder.setPosition(encoderLastPos); - DisplaySoftMute(encoderLastPos); - - } - else if (rot_state == STATE_SMUTE) { - rot_state = STATE_FREQ; - encoderLastPos = (radio.getFrequency() - radio.getMinFrequency()) / radio.getFrequencyStep(); - encoder.setPosition(encoderLastPos); - DisplayServiceName("..."); - - } // if - encoderLastTime = now; - -} // doMenuClick() - - -// this function will be called when the seekButton was clicked -void doSeekClick() { - Serial.println("SEEK..."); - radio.seekUp(true); -} // doSeekClick() - - -// The Interrupt Service Routine for Pin Change Interrupts -// On Arduino UNO you can use the PCINT1 interrupt vector that covers digital value changes on A2 and A3. -// On Arduino Mega 2560 you can use the PCINT2 interrupt vector that covers digital value changes on A8 and A9. -// Read http://www.atmel.com/Images/doc8468.pdf for more details on external interrupts. - -ISR(PCINT2_vect) { - encoder.tick(); // just call tick() to check the state. -} // ISR for PCINT2 - - -/// Setup a FM only radio configuration with I/O for commands and debugging on the Serial port. -void setup() { - // open the Serial port - Serial.begin(57600); - - // Initialize the lcd - lcd.begin(16, 2); - lcd.setBacklight(1); - lcd.print("Radio..."); - - delay(800); - lcd.clear(); - - // Initialize the Radio - radio.init(); - - // Enable information to the Serial port - radio.debugEnable(); - - // radio.setBandFrequency(RADIO_BAND_FM, 8930); // hr3 - radio.setBandFrequency(RADIO_BAND_FM, preset[i_sidx]); // 5. preset. - // radio.setFrequency(10140); // Radio BOB // preset[i_sidx] - - // Setup rotary encoder - - // You may have to modify the next 2 lines if using other pins than A2 and A3 - // On Arduino-Uno: rotary encoder in A2(PCINT10) and A3(PCINT11) - // PCICR |= (1 << PCIE1); // This enables Pin Change Interrupt 1 that covers the Analog input pins or Port C. - // PCMSK1 |= (1 << PCINT10) | (1 << PCINT11); // This enables the interrupt for pin 2 and 3 of Port C. - - // On Arduino-MEGA2560: A8(PCINT16) and A9(PCINT17) for interrupt vector PCINT2 - PCICR |= (1 << PCIE2); - PCMSK2 |= (1 << PCINT16) | (1 << PCINT17); - - encoderLastPos = (radio.getFrequency() - radio.getMinFrequency()) / radio.getFrequencyStep(); - Serial.println(encoderLastPos); - encoder.setPosition(encoderLastPos); - - // Setup the buttons - // link the doMenuClick function to be called on a click event. - menuButton.attachClick(doMenuClick); - - // link the doSeekClick function to be called on a click event. - seekButton.attachClick(doSeekClick); - - delay(100); - - radio.setMono(false); - radio.setMute(false); - // radio.debugRegisters(); - - Serial.write('>'); - - state = STATE_PARSECOMMAND; - rot_state = STATE_NONE; - - // setup the information chain for RDS data. - radio.attachReceiveRDS(RDS_process); - - rds.attachServicenNameCallback(DisplayServiceName); - - rds.attachTimeCallback(DisplayTime); - -} // Setup - - -/// Execute a command identified by a character and an optional number. -/// See the "?" command for available commands. -/// \param cmd The command character. -/// \param value An optional parameter for the command. -void runCommand(char cmd, int16_t value) -{ - if (cmd == '?') { - Serial.println(); - Serial.println("? Help"); - Serial.println("+ increase volume"); - Serial.println("- decrease volume"); - Serial.println("> next preset"); - Serial.println("< previous preset"); - Serial.println(". scan up : scan up to next sender"); - Serial.println(", scan down ; scan down to next sender"); - Serial.println("fnnnnn: direct frequency input"); - Serial.println("i station status"); - Serial.println("s mono/stereo mode"); - Serial.println("b bass boost"); - Serial.println("m mute/unmute"); - Serial.println("u soft mute/unmute"); - } - - // ----- control the volume and audio output ----- - - else if (cmd == '+') { - // increase volume - int v = radio.getVolume(); - if (v < 15) radio.setVolume(++v); - } - else if (cmd == '-') { - // decrease volume - int v = radio.getVolume(); - if (v > 0) radio.setVolume(--v); - } - - else if (cmd == 'm') { - // toggle mute mode - radio.setMute(! radio.getMute()); - } - - else if (cmd == 'u') { - // toggle soft mute mode - radio.setSoftMute(! radio.getSoftMute()); - } - - // toggle stereo mode - else if (cmd == 's') { radio.setMono(! radio.getMono()); } - - // toggle bass boost - else if (cmd == 'b') { radio.setBassBoost(! radio.getBassBoost()); } - - // ----- control the frequency ----- - - else if (cmd == '>') { - // next preset - if (i_sidx < (sizeof(preset) / sizeof(RADIO_FREQ))-1) { - i_sidx++; radio.setFrequency(preset[i_sidx]); - } // if - } - else if (cmd == '<') { - // previous preset - if (i_sidx > 0) { - i_sidx--; - radio.setFrequency(preset[i_sidx]); - } // if - - } - else if (cmd == 'f') { radio.setFrequency(value); } - - else if (cmd == '.') { radio.seekUp(false); } - else if (cmd == ':') { radio.seekUp(true); } - else if (cmd == ',') { radio.seekDown(false); } - else if (cmd == ';') { radio.seekDown(true); } - - - // not in help: - else if (cmd == '!') { - if (value == 0) radio.term(); - if (value == 1) radio.init(); - - } - else if (cmd == 'i') { - char s[12]; - radio.formatFrequency(s, sizeof(s)); - Serial.print("Station:"); Serial.println(s); - Serial.print("Radio:"); radio.debugRadioInfo(); - Serial.print("Audio:"); radio.debugAudioInfo(); - -// Serial.print(" RSSI: "); -// Serial.print(info.rssi); -// -// for (uint8_t i = 0; i < info.rssi - 15; i+=2) { Serial.write('*'); } // Empfangspegel ab 15. Zeichen -// Serial.println(); - } // info -// else if (cmd == 'n') { radio.debugScan(); } - else if (cmd == 'x') { radio.debugStatus(); } - - -} // runCommand() - - -/// Constantly check for serial input commands and trigger command execution. -void loop() { - int newPos; - unsigned long now = millis(); - static unsigned long nextFreqTime = 0; - static unsigned long nextRadioInfoTime = 0; - - // some internal static values for parsing the input - static char command; - static int16_t value; - static RADIO_FREQ lastf = 0; - RADIO_FREQ f = 0; - char c; - - // check for the menuButton - menuButton.tick(); - - if (Serial.available() > 0) { - // read the next char from input. - c = Serial.peek(); - - if ((state == STATE_PARSECOMMAND) && (c < 0x20)) { - // ignore unprintable chars - Serial.read(); - - } - else if (state == STATE_PARSECOMMAND) { - // read a command. - command = Serial.read(); - state = STATE_PARSEINT; - - } - else if (state == STATE_PARSEINT) { - if ((c >= '0') && (c <= '9')) { - // build up the value. - c = Serial.read(); - value = (value * 10) + (c - '0'); - } - else { - // not a value -> execute - runCommand(command, value); - command = ' '; - state = STATE_PARSECOMMAND; - value = 0; - } // if - } // if - } // if - - - // check for the rotary encoder - newPos = encoder.getPosition(); - if (newPos != encoderLastPos) { - - if (rot_state == STATE_FREQ) { - RADIO_FREQ f = radio.getMinFrequency() + (newPos * radio.getFrequencyStep()); - radio.setFrequency(f); - encoderLastPos = newPos; - nextFreqTime = now + 10; - - } - else if (rot_state == STATE_VOL) { - radio.setVolume(newPos); - encoderLastPos = newPos; - DisplayVolume(newPos); - - } - else if (rot_state == STATE_MONO) { - radio.setMono(newPos & 0x01); - encoderLastPos = newPos; - DisplayMono(newPos & 0x01); - - } - else if (rot_state == STATE_SMUTE) { - radio.setSoftMute(newPos & 0x01); - encoderLastPos = newPos; - DisplaySoftMute(newPos & 0x01); - - } // if - encoderLastTime = now; - - } - else if (now > encoderLastTime + 2000) { - // fall into FREQ + RDS mode - rot_state = STATE_FREQ; - encoderLastPos = (radio.getFrequency() - radio.getMinFrequency()) / radio.getFrequencyStep(); - encoder.setPosition(encoderLastPos); - - } // if - - // check for RDS data - radio.checkRDS(); - - // update the display from time to time - if (now > nextFreqTime) { - f = radio.getFrequency(); - if (f != lastf) { - // don't display a Service Name while frequency is no stable. - DisplayServiceName(" "); - DisplayFrequency(f); - lastf = f; - } // if - nextFreqTime = now + 400; - } // if - - if (now > nextRadioInfoTime) { - RADIO_INFO info; - radio.getRadioInfo(&info); - lcd.setCursor(14, 0); - lcd.print(info.rssi); - nextRadioInfoTime = now + 1000; - } // update - -} // loop - -// End. +/// +/// \file LCDRadio.ino +/// \brief Radio implementation including LCD output. +/// +/// \author Matthias Hertel, http://www.mathertel.de +/// \copyright Copyright (c) 2014 by Matthias Hertel.\n +/// This work is licensed under a BSD style license.\n +/// See http://www.mathertel.de/License.aspx +/// +/// \details +/// This is a full function radio implementation that uses a LCD display to show the current station information.\n +/// It can be used with various chips after adjusting the radio object definition.\n +/// Open the Serial console with 57600 baud to see current radio information and change various settings. +/// +/// Wiring +/// ------ +/// The necessary wiring of the various chips are described in the Testxxx example sketches. +/// The boards have to be connected by using the following connections: +/// +/// Arduino port | SI4703 signal | RDA5807M signal +/// :----------: | :-----------: | :-------------: +/// GND (black) | GND | GND +/// 3.3V (red) | VCC | VCC +/// 5V (red) | - | - +/// A5 (yellow) | SCLK | SCLK +/// A4 (blue) | SDIO | SDIO +/// D2 | RST | - +/// +/// More documentation and source code is available at http://www.mathertel.de/Arduino +/// +/// History: +/// -------- +/// * 05.08.2014 created. +/// * 06.10.2014 working. + + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +// Define some stations available at your locations here: +// 89.40 MHz as 8940 + +// RADIO_FREQ preset[] = {8850, 8930, 9320,9350, 9450,9570, 9680, 9880, 10030, 10260, 10400, 10500, 10600,10650,10800}; + +RADIO_FREQ preset[] = { + 8770, + 8810, // hr1 + 8820, + 8850, // Bayern2 + 8890, // ??? + 8930, // * hr3 + 8980, + 9180, + 9220, 9350, + 9440, // * hr1 + 9510, // - Antenne Frankfurt + 9530, + 9560, // Bayern 1 + 9680, 9880, + 10020, // planet + 10090, // ffh + 10110, // SWR3 + 10030, 10260, 10380, 10400, + 10500 // * FFH +}; +// , 10650,10650,10800 + +int i_sidx=5; // Start at Station with index=5 +int i_smax=14; // Max Index of Stations + +/// Setup a RoraryEncoder for pins A2 and A3: +RotaryEncoder encoder(A2, A3); +// RotaryEncoder encoder(A9, A8); + +int encoderLastPos; +unsigned long encoderLastTime; + + +// variables for rotator encoder +//unsigned long lastRotatorChange = 0; // last time a change of the rotator was detected. + +/// The radio object has to be defined by using the class corresponding to the used chip. +/// by uncommenting the right radio object definition. + +// RADIO radio; // Create an instance of a non functional radio. +// RDA5807M radio; // Create an instance of a RDA5807 chip radio +// SI4703 radio; // Create an instance of a SI4703 chip radio. +SI4721 radio; // Create an instance of a SI4721 chip radio. +// TEA5767 radio; // Create an instance of a TEA5767 chip radio. + +/// The lcd object has to be defined by using a LCD library that supports the standard functions +/// When using a I2C->LCD library ??? the I2C bus can be used to control then radio chip and the lcd. + +/// get a LCD instance +LiquidCrystal_PCF8574 lcd(0x27); // set the LCD address to 0x27 for a 16 chars and 2 line display + +OneButton menuButton(A10, true); +OneButton seekButton(A11, true); + + +/// get a RDS parser +RDSParser rds; + + +/// State definition for this radio implementation. +enum RADIO_STATE { + STATE_NONE = 0, + STATE_PARSECOMMAND, ///< waiting for a new command character. + STATE_PARSEINT, ///< waiting for digits for the parameter. + STATE_EXEC, ///< executing the command. + + STATE_FREQ, + STATE_VOL, + STATE_MONO, + STATE_SMUTE + +}; + +RADIO_STATE state; ///< The state variable is used for parsing input characters. +RADIO_STATE rot_state; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - + + +/// Update the Frequency on the LCD display. +void DisplayFrequency(RADIO_FREQ f) +{ + char s[12]; + radio.formatFrequency(s, sizeof(s)); + Serial.print("FREQ:"); Serial.println(s); + lcd.setCursor(0, 0); + lcd.print(s); +} // DisplayFrequency() + + +/// Update the ServiceName text on the LCD display when in RDS mode. +void DisplayServiceName(char *name) +{ + Serial.print("RDS:"); Serial.println(name); + if (rot_state == STATE_FREQ) { + lcd.setCursor(0, 1); + lcd.print(name); + } +} // DisplayServiceName() + + +void DisplayTime(uint8_t hour, uint8_t minute) { + Serial.print("RDS-Time:"); + if (hour < 10) Serial.print('0'); + Serial.print(hour); + Serial.print(':'); + if (minute < 10) Serial.print('0'); + Serial.print(minute); +} // DisplayTime() + + +/// Display the current volume. +void DisplayVolume(uint8_t v) +{ + Serial.print("VOL: "); Serial.println(v); + + lcd.setCursor(0, 1); + lcd.print("VOL: "); lcd.print(v); +} // DisplayVolume() + + +/// Display the current mono switch. +void DisplayMono(uint8_t v) +{ + Serial.print("MONO: "); Serial.println(v); + lcd.setCursor(0, 1); + lcd.print("MONO: "); lcd.print(v); +} // DisplayMono() + + +/// Display the current soft mute switch. +void DisplaySoftMute(uint8_t v) +{ + Serial.print("SMUTE: "); Serial.println(v); + lcd.setCursor(0, 1); + lcd.print("SMUTE: "); lcd.print(v); +} // DisplaySoftMute() + + +// - - - - - - - - - - - - - - - - - - - - - - - - - - + + +void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) { + rds.processData(block1, block2, block3, block4); +} + + +// this function will be called when the menuButton was clicked +void doMenuClick() { + unsigned long now = millis(); + + if (rot_state == STATE_FREQ) { + // jump into volume mode + rot_state = STATE_VOL; + encoderLastPos = radio.getVolume(); + encoder.setPosition(encoderLastPos); + DisplayVolume(encoderLastPos); + + } + else if (rot_state == STATE_VOL) { + // jump into mono/stereo switch + rot_state = STATE_MONO; + encoderLastPos = radio.getMono(); + encoder.setPosition(encoderLastPos); + DisplayMono(encoderLastPos); + + } + else if (rot_state == STATE_MONO) { + // jump into soft mute switch + rot_state = STATE_SMUTE; + encoderLastPos = radio.getSoftMute(); + encoder.setPosition(encoderLastPos); + DisplaySoftMute(encoderLastPos); + + } + else if (rot_state == STATE_SMUTE) { + rot_state = STATE_FREQ; + encoderLastPos = (radio.getFrequency() - radio.getMinFrequency()) / radio.getFrequencyStep(); + encoder.setPosition(encoderLastPos); + DisplayServiceName("..."); + + } // if + encoderLastTime = now; + +} // doMenuClick() + + +// this function will be called when the seekButton was clicked +void doSeekClick() { + Serial.println("SEEK..."); + radio.seekUp(true); +} // doSeekClick() + + +// The Interrupt Service Routine for Pin Change Interrupts +// On Arduino UNO you can use the PCINT1 interrupt vector that covers digital value changes on A2 and A3. +// On Arduino Mega 2560 you can use the PCINT2 interrupt vector that covers digital value changes on A8 and A9. +// Read http://www.atmel.com/Images/doc8468.pdf for more details on external interrupts. + +ISR(PCINT2_vect) { + encoder.tick(); // just call tick() to check the state. +} // ISR for PCINT2 + + +/// Setup a FM only radio configuration with I/O for commands and debugging on the Serial port. +void setup() { + // open the Serial port + Serial.begin(57600); + + // Initialize the lcd + lcd.begin(16, 2); + lcd.setBacklight(1); + lcd.print("Radio..."); + + delay(800); + lcd.clear(); + + // Initialize the Radio + radio.init(); + + // Enable information to the Serial port + radio.debugEnable(); + + // radio.setBandFrequency(RADIO_BAND_FM, 8930); // hr3 + radio.setBandFrequency(RADIO_BAND_FM, preset[i_sidx]); // 5. preset. + // radio.setFrequency(10140); // Radio BOB // preset[i_sidx] + + // Setup rotary encoder + + // You may have to modify the next 2 lines if using other pins than A2 and A3 + // On Arduino-Uno: rotary encoder in A2(PCINT10) and A3(PCINT11) + // PCICR |= (1 << PCIE1); // This enables Pin Change Interrupt 1 that covers the Analog input pins or Port C. + // PCMSK1 |= (1 << PCINT10) | (1 << PCINT11); // This enables the interrupt for pin 2 and 3 of Port C. + + // On Arduino-MEGA2560: A8(PCINT16) and A9(PCINT17) for interrupt vector PCINT2 + PCICR |= (1 << PCIE2); + PCMSK2 |= (1 << PCINT16) | (1 << PCINT17); + + encoderLastPos = (radio.getFrequency() - radio.getMinFrequency()) / radio.getFrequencyStep(); + Serial.println(encoderLastPos); + encoder.setPosition(encoderLastPos); + + // Setup the buttons + // link the doMenuClick function to be called on a click event. + menuButton.attachClick(doMenuClick); + + // link the doSeekClick function to be called on a click event. + seekButton.attachClick(doSeekClick); + + delay(100); + + radio.setMono(false); + radio.setMute(false); + // radio._wireDebug(); + + Serial.write('>'); + + state = STATE_PARSECOMMAND; + rot_state = STATE_NONE; + + // setup the information chain for RDS data. + radio.attachReceiveRDS(RDS_process); + + rds.attachServicenNameCallback(DisplayServiceName); + + rds.attachTimeCallback(DisplayTime); + +} // Setup + + +/// Execute a command identified by a character and an optional number. +/// See the "?" command for available commands. +/// \param cmd The command character. +/// \param value An optional parameter for the command. +void runCommand(char cmd, int16_t value) +{ + if (cmd == '?') { + Serial.println(); + Serial.println("? Help"); + Serial.println("+ increase volume"); + Serial.println("- decrease volume"); + Serial.println("> next preset"); + Serial.println("< previous preset"); + Serial.println(". scan up : scan up to next sender"); + Serial.println(", scan down ; scan down to next sender"); + Serial.println("fnnnnn: direct frequency input"); + Serial.println("i station status"); + Serial.println("s mono/stereo mode"); + Serial.println("b bass boost"); + Serial.println("m mute/unmute"); + Serial.println("u soft mute/unmute"); + } + + // ----- control the volume and audio output ----- + + else if (cmd == '+') { + // increase volume + int v = radio.getVolume(); + if (v < 15) radio.setVolume(++v); + } + else if (cmd == '-') { + // decrease volume + int v = radio.getVolume(); + if (v > 0) radio.setVolume(--v); + } + + else if (cmd == 'm') { + // toggle mute mode + radio.setMute(! radio.getMute()); + } + + else if (cmd == 'u') { + // toggle soft mute mode + radio.setSoftMute(! radio.getSoftMute()); + } + + // toggle stereo mode + else if (cmd == 's') { radio.setMono(! radio.getMono()); } + + // toggle bass boost + else if (cmd == 'b') { radio.setBassBoost(! radio.getBassBoost()); } + + // ----- control the frequency ----- + + else if (cmd == '>') { + // next preset + if (i_sidx < (sizeof(preset) / sizeof(RADIO_FREQ))-1) { + i_sidx++; radio.setFrequency(preset[i_sidx]); + } // if + } + else if (cmd == '<') { + // previous preset + if (i_sidx > 0) { + i_sidx--; + radio.setFrequency(preset[i_sidx]); + } // if + + } + else if (cmd == 'f') { radio.setFrequency(value); } + + else if (cmd == '.') { radio.seekUp(false); } + else if (cmd == ':') { radio.seekUp(true); } + else if (cmd == ',') { radio.seekDown(false); } + else if (cmd == ';') { radio.seekDown(true); } + + + // not in help: + else if (cmd == '!') { + if (value == 0) radio.term(); + if (value == 1) radio.init(); + + } + else if (cmd == 'i') { + char s[12]; + radio.formatFrequency(s, sizeof(s)); + Serial.print("Station:"); Serial.println(s); + Serial.print("Radio:"); radio.debugRadioInfo(); + Serial.print("Audio:"); radio.debugAudioInfo(); + +// Serial.print(" RSSI: "); +// Serial.print(info.rssi); +// +// for (uint8_t i = 0; i < info.rssi - 15; i+=2) { Serial.write('*'); } // Empfangspegel ab 15. Zeichen +// Serial.println(); + } // info +// else if (cmd == 'n') { radio.debugScan(); } + else if (cmd == 'x') { radio.debugStatus(); } + + +} // runCommand() + + +/// Constantly check for serial input commands and trigger command execution. +void loop() { + int newPos; + unsigned long now = millis(); + static unsigned long nextFreqTime = 0; + static unsigned long nextRadioInfoTime = 0; + + // some internal static values for parsing the input + static char command; + static int16_t value; + static RADIO_FREQ lastf = 0; + RADIO_FREQ f = 0; + char c; + + // check for the menuButton + menuButton.tick(); + + if (Serial.available() > 0) { + // read the next char from input. + c = Serial.peek(); + + if ((state == STATE_PARSECOMMAND) && (c < 0x20)) { + // ignore unprintable chars + Serial.read(); + + } + else if (state == STATE_PARSECOMMAND) { + // read a command. + command = Serial.read(); + state = STATE_PARSEINT; + + } + else if (state == STATE_PARSEINT) { + if ((c >= '0') && (c <= '9')) { + // build up the value. + c = Serial.read(); + value = (value * 10) + (c - '0'); + } + else { + // not a value -> execute + runCommand(command, value); + command = ' '; + state = STATE_PARSECOMMAND; + value = 0; + } // if + } // if + } // if + + + // check for the rotary encoder + newPos = encoder.getPosition(); + if (newPos != encoderLastPos) { + + if (rot_state == STATE_FREQ) { + RADIO_FREQ f = radio.getMinFrequency() + (newPos * radio.getFrequencyStep()); + radio.setFrequency(f); + encoderLastPos = newPos; + nextFreqTime = now + 10; + + } + else if (rot_state == STATE_VOL) { + radio.setVolume(newPos); + encoderLastPos = newPos; + DisplayVolume(newPos); + + } + else if (rot_state == STATE_MONO) { + radio.setMono(newPos & 0x01); + encoderLastPos = newPos; + DisplayMono(newPos & 0x01); + + } + else if (rot_state == STATE_SMUTE) { + radio.setSoftMute(newPos & 0x01); + encoderLastPos = newPos; + DisplaySoftMute(newPos & 0x01); + + } // if + encoderLastTime = now; + + } + else if (now > encoderLastTime + 2000) { + // fall into FREQ + RDS mode + rot_state = STATE_FREQ; + encoderLastPos = (radio.getFrequency() - radio.getMinFrequency()) / radio.getFrequencyStep(); + encoder.setPosition(encoderLastPos); + + } // if + + // check for RDS data + radio.checkRDS(); + + // update the display from time to time + if (now > nextFreqTime) { + f = radio.getFrequency(); + if (f != lastf) { + // don't display a Service Name while frequency is no stable. + DisplayServiceName(" "); + DisplayFrequency(f); + lastf = f; + } // if + nextFreqTime = now + 400; + } // if + + if (now > nextRadioInfoTime) { + RADIO_INFO info; + radio.getRadioInfo(&info); + lcd.setCursor(14, 0); + lcd.print(info.rssi); + nextRadioInfoTime = now + 1000; + } // update + +} // loop + +// End. diff --git a/examples/ScanRadio/ScanRadio.ino b/examples/ScanRadio/ScanRadio.ino index 7d98905..440be0f 100644 --- a/examples/ScanRadio/ScanRadio.ino +++ b/examples/ScanRadio/ScanRadio.ino @@ -1,16 +1,16 @@ /// -/// \file ScanRadio.ino +/// \file ScanRadio.ino /// \brief This sketch implements a scanner that lists all availabe radio stations including some information. -/// +/// /// \author Matthias Hertel, http://www.mathertel.de -/// \copyright Copyright (c) 2015 by Matthias Hertel.\n -/// This work is licensed under a BSD style license.\n +/// \copyright Copyright (c) by Matthias Hertel.\n +/// This work is licensed under a BSD 3-Clause license.\n /// See http://www.mathertel.de/License.aspx /// /// \details /// This is a Arduino sketch that uses a state machine to scan through all radio stations the radio chip can detect /// and outputs them on the Serial interface.\n -/// Open the Serial console with 57600 baud to see current radio information and change various settings. +/// Open the Serial console with 115200 baud to see current radio information and change various settings. /// /// Wiring /// ------ @@ -24,13 +24,16 @@ /// * 17.05.2015 created. /// * 27.05.2015 first version is working (beta with SI4705). /// * 04.07.2015 2 scan algorithms working with good results with SI4705. +/// * 18.09.2020 more RDS output, better command handling. +#include #include - #include + #include #include #include +#include #include #include @@ -41,52 +44,84 @@ // RADIO radio; ///< Create an instance of a non functional radio. // RDA5807M radio; ///< Create an instance of a RDA5807 chip radio // SI4703 radio; ///< Create an instance of a SI4703 chip radio. -SI4705 radio; ///< Create an instance of a SI4705 chip radio. +// SI4705 radio; ///< Create an instance of a SI4705 chip radio. +SI4721 radio; ///< Create an instance of a SI4705 chip radio. // TEA5767 radio; ///< Create an instance of a TEA5767 chip radio. /// get a RDS parser RDSParser rds; -/// State definition for this radio implementation. -enum SCAN_STATE { - STATE_START, ///< waiting for a new command character. - STATE_NEWFREQ, - STATE_WAITFIXED, - STATE_WAITRDS, - STATE_PRINT, - STATE_END ///< executing the command. +// Keyboard input + +enum RADIO_STATE { + STATE_PARSECOMMAND, ///< waiting for a new command character. + + STATE_PARSEINT, ///< waiting for digits for the parameter. + STATE_EXEC ///< executing the command. }; -SCAN_STATE state; ///< The state variable is used for parsing input characters. +RADIO_STATE kbState; ///< The state of parsing input characters. +char kbCommand; +int16_t kbValue; + uint16_t g_block1; +bool lowLevelDebug = false; +RADIO_FREQ frequency; + // - - - - - - - - - - - - - - - - - - - - - - - - - - -// use a function inbetween the radio chip and the RDS parser +// use a function in between the radio chip and the RDS parser // to catch the block1 value (used for sender identification) -void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) { +void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) +{ + // Serial.printf("RDS: 0x%04x 0x%04x 0x%04x 0x%04x\n", block1, block2, block3, block4); g_block1 = block1; rds.processData(block1, block2, block3, block4); } +/// Update the Time +void DisplayTime(uint8_t hour, uint8_t minute) +{ + Serial.print("Time: "); + if (hour < 10) + Serial.print('0'); + Serial.print(hour); + Serial.print(':'); + if (minute < 10) + Serial.print('0'); + Serial.println(minute); +} // DisplayTime() + + /// Update the ServiceName text on the LCD display. void DisplayServiceName(char *name) { bool found = false; for (uint8_t n = 0; n < 8; n++) - if (name[n] != ' ') found = true; + if (name[n] != ' ') + found = true; if (found) { - Serial.print("RDS:"); + Serial.print("Sender:<"); Serial.print(name); - Serial.println('.'); + Serial.println('>'); } } // DisplayServiceName() +/// Update the ServiceName text on the LCD display. +void DisplayText(char *txt) +{ + Serial.print("Text: <"); + Serial.print(txt); + Serial.println('>'); +} // DisplayText() + + /// Execute a command identified by a character and an optional number. /// See the "?" command for available commands. /// \param cmd The command character. @@ -100,6 +135,13 @@ void runSerialCommand(char cmd, int16_t value) char sFreq[12]; RADIO_INFO ri; + if ((cmd == '\n') || (cmd == '\r')) { + return; + } + + Serial.print("do:"); + Serial.println(cmd); + if (cmd == '?') { Serial.println(); Serial.println("? Help"); @@ -112,42 +154,50 @@ void runSerialCommand(char cmd, int16_t value) Serial.println("i station status"); Serial.println("s mono/stereo mode"); Serial.println("b bass boost"); - Serial.println("u mute/unmute"); - } + Serial.println("m mute/unmute"); + Serial.println("u soft mute/unmute"); + Serial.println("x debug..."); + Serial.println("* toggle i2c debug output"); - // ----- control the volume and audio output ----- + // ----- control the volume and audio output ----- - else if (cmd == '+') { + } else if (cmd == '+') { // increase volume int v = radio.getVolume(); - if (v < 15) radio.setVolume(++v); + if (v < 15) + radio.setVolume(++v); } else if (cmd == '-') { // decrease volume int v = radio.getVolume(); - if (v > 0) radio.setVolume(--v); - } + if (v > 0) + radio.setVolume(--v); - else if (cmd == 'u') { + } else if (cmd == 'm') { // toggle mute mode radio.setMute(!radio.getMute()); - } - // toggle stereo mode - else if (cmd == 's') { radio.setMono(!radio.getMono()); } + } else if (cmd == 'u') { + // toggle soft mute mode + radio.setSoftMute(!radio.getSoftMute()); + + } else if (cmd == 's') { + // toggle stereo mode + radio.setMono(!radio.getMono()); - // toggle bass boost - else if (cmd == 'b') { radio.setBassBoost(!radio.getBassBoost()); } + } else if (cmd == 'b') { + // toggle bass boost + radio.setBassBoost(!radio.getBassBoost()); - // ----- control the frequency ----- - else if (cmd == '1') { + } else if (cmd == '1') { + // ----- control the frequency ----- Serial.println("Scanning all available frequencies... (1)"); fSave = radio.getFrequency(); // start Simple Scan: all channels while (f <= fMax) { radio.setFrequency(f); - delay(50); + delay(80); radio.getRadioInfo(&ri); if (ri.tuned) { @@ -155,8 +205,10 @@ void runSerialCommand(char cmd, int16_t value) Serial.print(sFreq); Serial.print(' '); - Serial.print(ri.rssi); Serial.print(' '); - Serial.print(ri.snr); Serial.print(' '); + Serial.print(ri.rssi); + Serial.print(' '); + Serial.print(ri.snr); + Serial.print(' '); Serial.print(ri.stereo ? 'S' : '-'); Serial.print(ri.rds ? 'R' : '-'); Serial.println(); @@ -177,13 +229,13 @@ void runSerialCommand(char cmd, int16_t value) while (f <= fMax) { radio.seekUp(true); - delay(100); // + delay(100); // startSeek = millis(); // wait for seek complete do { radio.getRadioInfo(&ri); - } while ((!ri.tuned) && (startSeek + 300 > millis())); + } while ((!ri.tuned) && (startSeek + 600 > millis())); // check frequency f = radio.getFrequency(); @@ -207,14 +259,18 @@ void runSerialCommand(char cmd, int16_t value) // fetch final status for printing radio.getRadioInfo(&ri); - Serial.print(ri.rssi); Serial.print(' '); - Serial.print(ri.snr); Serial.print(' '); + Serial.print(ri.rssi); + Serial.print(' '); + Serial.print(ri.snr); + Serial.print(' '); Serial.print(ri.stereo ? 'S' : '-'); Serial.print(ri.rds ? 'R' : '-'); if (g_block1) { Serial.print(' '); - Serial.print('['); Serial.print(g_block1, HEX); Serial.print(']'); + Serial.print('['); + Serial.print(g_block1, HEX); + Serial.print(']'); } // if Serial.println(); } // if @@ -223,70 +279,124 @@ void runSerialCommand(char cmd, int16_t value) Serial.println(); - } else if (cmd == 'f') { radio.setFrequency(value); } + } else if (cmd == 'f') { + frequency = value; + radio.setFrequency(value); + } - else if (cmd == '.') { radio.seekUp(false); } else if (cmd == ':') { radio.seekUp(true); } else if (cmd == ',') { radio.seekDown(false); } else if (cmd == ';') { radio.seekDown(true); } + else if (cmd == '.') { + radio.seekUp(false); + } else if (cmd == ':') { + radio.seekUp(true); + } else if (cmd == ',') { + radio.seekDown(false); + } else if (cmd == ';') { + radio.seekDown(true); + } // not in help: else if (cmd == '!') { - if (value == 0) radio.term(); - if (value == 1) radio.init(); + if (value == 0) { + radio.term(); + } else if (value == 1) { + radio.init(); + radio.setBandFrequency(RADIO_BAND_FM, frequency); + } } else if (cmd == 'i') { + // info char s[12]; radio.formatFrequency(s, sizeof(s)); - Serial.print("Station:"); Serial.println(s); - Serial.print("Radio:"); radio.debugRadioInfo(); - Serial.print("Audio:"); radio.debugAudioInfo(); - - } // info - - else if (cmd == 'x') { + Serial.print("Station:"); + Serial.println(s); + Serial.print("Radio:"); + radio.debugRadioInfo(); + Serial.print("Audio:"); + radio.debugAudioInfo(); + + } else if (cmd == 'x') { radio.debugStatus(); // print chip specific data. + + } else if (cmd == '*') { + lowLevelDebug = !lowLevelDebug; + radio._wireDebug(lowLevelDebug); } } // runSerialCommand() /// Setup a FM only radio configuration with I/O for commands and debugging on the Serial port. -void setup() { +void setup() +{ // open the Serial port - Serial.begin(57600); + Serial.begin(115200); Serial.print("Radio..."); delay(500); - // Initialize the Radio - radio.init(); +#ifdef ESP8266 + // For ESP8266 boards (like NodeMCU) the I2C GPIO pins in use + // need to be specified. + Wire.begin(D2, D1); // a common GPIO pin setting for I2C +#endif // Enable information to the Serial port - radio.debugEnable(); + radio.debugEnable(true); + radio._wireDebug(lowLevelDebug); - radio.setBandFrequency(RADIO_BAND_FM, 8930); + // Initialize the Radio + radio.init(); + + frequency = 8930; + radio.setBandFrequency(RADIO_BAND_FM, frequency); // delay(100); radio.setMono(false); radio.setMute(false); - // radio.debugRegisters(); - radio.setVolume(5); + radio.setVolume(10); Serial.write('>'); // setup the information chain for RDS data. radio.attachReceiveRDS(RDS_process); rds.attachServicenNameCallback(DisplayServiceName); + rds.attachTextCallback(DisplayText); + rds.attachTimeCallback(DisplayTime); runSerialCommand('?', 0); + kbState = STATE_PARSECOMMAND; } // Setup /// Constantly check for serial input commands and trigger command execution. -void loop() { - char c; +void loop() +{ if (Serial.available() > 0) { // read the next char from input. - c = Serial.read(); - runSerialCommand(c, 8930); + char c = Serial.peek(); + + if ((kbState == STATE_PARSECOMMAND) && (c < 0x20)) { + // ignore unprintable chars + Serial.read(); + + } else if (kbState == STATE_PARSECOMMAND) { + // read a kbCommand. + kbCommand = Serial.read(); + kbState = STATE_PARSEINT; + + } else if (kbState == STATE_PARSEINT) { + if ((c >= '0') && (c <= '9')) { + // build up the value. + c = Serial.read(); + kbValue = (kbValue * 10) + (c - '0'); + } else { + // not a value -> execute + runSerialCommand(kbCommand, kbValue); + kbCommand = ' '; + kbState = STATE_PARSECOMMAND; + kbValue = 0; + } // if + } // if } // if // check for RDS data diff --git a/examples/SerialRadio/SerialRadio.ino b/examples/SerialRadio/SerialRadio.ino index bce4f19..7515cbd 100644 --- a/examples/SerialRadio/SerialRadio.ino +++ b/examples/SerialRadio/SerialRadio.ino @@ -228,7 +228,7 @@ void setup() { radio.setMono(false); radio.setMute(false); - // radio.debugRegisters(); + // radio._wireDebug(); radio.setVolume(8); Serial.write('>'); diff --git a/examples/TestSI4721/TestSI4721.ino b/examples/TestSI4721/TestSI4721.ino new file mode 100644 index 0000000..8343df8 --- /dev/null +++ b/examples/TestSI4721/TestSI4721.ino @@ -0,0 +1,108 @@ +/// +/// \file TestSI4721.ino +/// \brief An Arduino sketch to operate a SI4705 chip based radio using the Radio library. +/// +/// \author N Poole, nickpoole.me +/// \author Matthias Hertel, http://www.mathertel.de +/// \copyright Copyright (c) 2014 by Matthias Hertel.\n +/// This work is licensed under a BSD 3-Clause License. See http://www.mathertel.de/License.aspx +/// +/// \details +/// This sketch implements a "as simple as possible" radio without any possibility to modify the settings after initializing the chip.\n +/// The radio chip is initialized and setup to a fixed band and frequency. These settings can be changed by modifying the +/// FIX_BAND and FIX_STATION definitions. +/// +/// Open the Serial console with 115200 baud to see the current radio information. +/// +/// Wiring +/// ------ +/// The SI4721 board/chip has to be connected by using the following connections: + +/// | Arduino UNO pin | Radio chip signal | +/// | --------------- | -------------------| +/// | 3.3V / 5V | VCC | +/// | GND | GND | +/// | A5 or SCL | SCLK | +/// | A4 or SDA | SDIO | +/// +/// More documentation and source code is available at http://www.mathertel.de/Arduino +/// +/// ChangeLog: +/// ---------- +/// * 05.12.2019 created. + +#include +#include +#include + +#include + +// ----- Fixed settings here. ----- + +#define FIX_BAND RADIO_BAND_FM ///< The band that will be tuned by this sketch is FM. +#define FIX_STATION 8930 ///< The station that will be tuned by this sketch is 89.30 MHz. +#define FIX_VOLUME 10 ///< The volume that will be set by this sketch is level 4. + +SI4721 radio; // Create an instance of Class for SI4705 Chip + +/// Setup a FM only radio configuration +/// with some debugging on the Serial port +void setup() +{ + delay(3000); + + // open the Serial port + Serial.begin(115200); + Serial.println("Radio..."); + delay(200); + +#ifdef ESP8266 + // For ESP8266 boards (like NodeMCU) the I2C GPIO pins in use + // need to be specified. + Wire.begin(D2, D1); // a common GPIO pin setting for I2C +#endif + + // see if a chip can be found + if (radio._wireExists(&Wire, SI4721_ADR)) { + Serial.print("Device found at address "); + Serial.println(SI4721_ADR); + } else { + Serial.print("Device NOT found at address "); + Serial.println(SI4721_ADR); + } + + // Enable debug information to the Serial port + radio.debugEnable(false); + radio._wireDebug(false); + + // Initialize the Radio + radio.init(Wire, SI4721_ADR); + + // radio.setDeemphasis(75); // Un-comment this line in the USA + + // Set all radio setting to the fixed values. + radio.setBandFrequency(FIX_BAND, FIX_STATION); + radio.setVolume(FIX_VOLUME); + radio.setMono(true); + radio.setMute(false); +} // setup + + +/// show the current chip data every 3 seconds. +void loop() +{ + char s[12]; + radio.formatFrequency(s, sizeof(s)); + Serial.print("Station:"); + Serial.println(s); + + Serial.print("Radio:"); + radio.debugRadioInfo(); + + Serial.print("Audio:"); + radio.debugAudioInfo(); + + delay(3000); +} // loop + +// End. diff --git a/examples/TransmitSI4721/TransmitSI4721.ino b/examples/TransmitSI4721/TransmitSI4721.ino new file mode 100644 index 0000000..90b5b42 --- /dev/null +++ b/examples/TransmitSI4721/TransmitSI4721.ino @@ -0,0 +1,112 @@ +/// +/// \file TransmitSI4721.ino +/// \brief An Arduino sketch to operate a SI4721 chip in transmit mode using the Radio library. +/// +/// \author N Poole, nickpoole.me +/// \author Matthias Hertel, http://www.mathertel.de +/// \copyright Copyright (c) 2014 by Matthias Hertel.\n +/// This work is licensed under a BSD 3-Clause License. See http://www.mathertel.de/License.aspx +/// +/// \details +/// This sketch implements FM transmit mode with RDS broadcasting. \n +/// The radio chip is initialized and setup to a fixed band and frequency. These settings can be changed by modifying the +/// FIX_BAND and FIX_STATION definitions. +/// +/// Open the Serial console with 115200 baud to see the current radio information. +/// +/// Wiring +/// ------ +/// The SI4703 board/chip has to be connected by using the following connections: +/// | Arduino UNO pin | Radio chip | +/// | --------------- | ---------------| +/// | 3.3V | VCC | +/// | GND | GND | +/// | A5 or SCL | SCLK | +/// | A4 or SDA | SDIO | +/// | | RST (not used) | +/// The locations of the pins on the UNO board are written on the PCB. +/// The locations of the signals on the SI4721 side depend on the board you use. +/// +/// More documentation and source code is available at http://www.mathertel.de/Arduino +/// +/// ChangeLog: +/// ---------- +/// * 05.12.2019 created. +/// * 20.09.2020 Integrated into radio library version 2.0.0 + +#include +#include +#include +#include + +// ----- Fixed settings here. ----- + +#define FIX_BAND RADIO_BAND_FMTX ///< The band that will be tuned by this sketch is FM. +#define FIX_STATION 10410 ///< The station that will be tuned by this sketch is 104.10 MHz. +#define FIX_POWER 90 ///< The transmit output power that will be set by this sketch. + +#define RDS_SERVICENAME "Testing" ///< The rds service name that will be set by this sketch is "Testing" +#define RDS_TEXTBUFFER "Hello World!" ///< The rds text buffer that will be set by this sketch is "Hello World!" + +SI4721 transmitter; // Create an instance of Class for SI4721 Chip + +/// Setup a FM only radio configuration +/// with some debugging on the Serial port +void setup() +{ + delay(3000); + + // open the Serial port + Serial.begin(115200); + Serial.println("Radio Initialize..."); + delay(200); + +#ifdef ESP8266 + // For ESP8266 boards (like NodeMCU) the I2C GPIO pins in use + // need to be specified. + Wire.begin(D2, D1); // a common GPIO pin setting for I2C +#endif + + // see if a chip can be found + if (transmitter._wireExists(&Wire, SI4721_ADR)) { + Serial.print("Device found at address "); + Serial.println(SI4721_ADR); + } else { + Serial.print("Device NOT found at address "); + Serial.println(SI4721_ADR); + } + + // Enable information to the Serial port + transmitter.debugEnable(false); + transmitter._wireDebug(false); + + // Initialize the Radio + transmitter.init(); + // transmitter.setDeemphasis(75); // Un-comment this line in the USA to set correct deemphasis/preemphasis timing + + transmitter.setBandFrequency(FIX_BAND, FIX_STATION); + transmitter.setTXPower(FIX_POWER); + + transmitter.beginRDS(); + transmitter.setRDSstation(RDS_SERVICENAME); + transmitter.setRDSbuffer(RDS_TEXTBUFFER); +} // setup + + +/// show the current chip data every 3 seconds. +void loop() +{ + Serial.print("Transmitting on "); + Serial.print(transmitter.getTuneStatus().frequency); + Serial.println("kHz..."); + + Serial.print("ASQ: "); + Serial.println(transmitter.getASQ().asq); + + Serial.print("Audio In Level: "); + Serial.println(transmitter.getASQ().audioInLevel); + + delay(3000); +} // loop + +// End. \ No newline at end of file diff --git a/examples/WebRadio/WebRadio.ino b/examples/WebRadio/WebRadio.ino index 0b4e9aa..29ab06b 100644 --- a/examples/WebRadio/WebRadio.ino +++ b/examples/WebRadio/WebRadio.ino @@ -979,7 +979,7 @@ void setupRadio() { radio.setMono(false); radio.setMute(false); - // radio.debugRegisters(); + // radio._wireDebug(); radio.setVolume(8); // Setup rotary encoder @@ -1336,4 +1336,4 @@ void loop() // End. - + diff --git a/keywords.txt b/keywords.txt index 5e29d46..455ac6a 100644 --- a/keywords.txt +++ b/keywords.txt @@ -11,6 +11,7 @@ RDA5807M KEYWORD1 RADIO KEYWORD1 SI4703 KEYWORD1 SI4705 KEYWORD1 +SI4721 KEYWORD1 TEA5767 KEYWORD1 RADIO_FREQ KEYWORD1 @@ -59,6 +60,13 @@ attachReceiveRDS KEYWORD2 formatFrequency KEYWORD2 +beginRDS KEYWORD2 +setRDSstation KEYWORD2 +setRDSbuffer KEYWORD2 + +getASQ KEYWORD2 +getTuneStatus KEYWORD2 + ####################################### # Instances (KEYWORD2) ####################################### diff --git a/library.properties b/library.properties index c0e6e87..47e49e9 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=Radio -version=1.2.0 +version=2.0.0 author=Matthias Hertel maintainer=Matthias Hertel, sentence=Library for controlling FM radio receiver chips. -paragraph=This library implements the functions to control the FM radio receiver chips TEA5767, RDA5807M, SI4703, SI4705 to build a FM radio receiver. The library unifies the functions for all the chips so they may be swapped on demand. +paragraph=This library implements the functions to control the FM radio receiver chips TEA5767, RDA5807M, SI4703, SI4705, SI4721 to build a FM radio receiver. The library unifies the functions for all the chips so they may be swapped on demand. category=Communication url=http://www.mathertel.de/Arduino/RadioLibrary.aspx architectures=* \ No newline at end of file diff --git a/src/RDA5807M.cpp b/src/RDA5807M.cpp index 4b2abed..345293a 100644 --- a/src/RDA5807M.cpp +++ b/src/RDA5807M.cpp @@ -17,8 +17,8 @@ #include #include - #include + #include // ----- Register Definitions ----- diff --git a/src/RDA5807M.h b/src/RDA5807M.h index 531836d..6e60726 100644 --- a/src/RDA5807M.h +++ b/src/RDA5807M.h @@ -30,7 +30,6 @@ #include #include - #include // ----- library definition ----- diff --git a/src/RDSParser.h b/src/RDSParser.h index 4180e41..b13710a 100644 --- a/src/RDSParser.h +++ b/src/RDSParser.h @@ -16,7 +16,7 @@ /// * 01.09.2014 created and RDS sender name working. /// * 01.11.2014 RDS time added. /// * 27.03.2015 Reset RDS data by sending a 0 in blockA in the case the frequency changes. -/// +/// #ifndef __RDSPARSER_H__ diff --git a/src/SI4705.cpp b/src/SI4705.cpp index f33d017..06b5f12 100644 --- a/src/SI4705.cpp +++ b/src/SI4705.cpp @@ -15,10 +15,9 @@ /// /// ChangeLog see SI4705.h. -#include -#include // The chip is controlled via the standard Arduiino Wire library and the IIC/I2C bus. +// Include the common radio library interface +#include -#include // Include the common radio library interface #include // ----- Definitions for the Wire communication @@ -45,7 +44,7 @@ #define CMD_POWER_DOWN 0x11 // Power down device. #define CMD_SET_PROPERTY 0x12 // Sets the value of a property. -#define CMD_GET_PROPERTY 0x13 // Retrieves a property’s value. +#define CMD_GET_PROPERTY 0x13 // Retrieves a property�s value. #define CMD_GET_INT_STATUS 0x14 // Reads interrupt status bits. #define CMD_GET_INT_STATUS_CTS 0x80 // CTS flag in status diff --git a/src/SI4721.cpp b/src/SI4721.cpp new file mode 100644 index 0000000..e9f2c9f --- /dev/null +++ b/src/SI4721.cpp @@ -0,0 +1,862 @@ +/// +/// \file SI4721.cpp +/// \brief Implementation for the radio library to control the SI4721 radio chip. +/// +/// \author N Poole, nickpoole.me +/// \author Matthias Hertel, http://www.mathertel.de +/// \copyright Copyright (c) 2014 by Matthias Hertel.\n +/// This work is licensed under a BSD style license.\n +/// See http://www.mathertel.de/License.aspx +/// +/// This library enables the use of the Radio Chip SI4721. +/// +/// More documentation and source code is available at http://www.mathertel.de/Arduino +/// +/// Many hints can be found in AN332: http://www.silabs.com/Support%20Documents/TechnicalDocs/AN332.pdf +/// +/// ChangeLog see SI4721.h. + +#include +#include // The chip is controlled via the standard Arduiino Wire library and the IIC/I2C bus. + +#include // Include the common radio library interface + +// Include the chip specific radio library interface +#include + +// ----- Radio chip specific definitions including the registers + +// Commands and Parameter definitions + +#define CMD_POWER_UP 0x01 // Power up device and mode selection. +#define CMD_POWER_UP_1_FUNC_FM 0x00 +#define CMD_POWER_UP_1_FUNC_FMTX 0x02 +#define CMD_POWER_UP_1_XOSCEN 0x10 +#define CMD_POWER_UP_1_PATCH 0x20 +#define CMD_POWER_UP_1_GPO2OEN 0x40 +#define CMD_POWER_UP_1_CTSIEN 0x80 +#define CMD_POWER_UP_2_ANALOGOUT 0x05 +#define CMD_POWER_UP_2_ANALOGIN 0x50 + +#define CMD_GET_REV 0x10 // Returns revision information on the device. +#define CMD_POWER_DOWN 0x11 // Power down device. + +#define CMD_SET_PROPERTY 0x12 // Sets the value of a property. +#define CMD_GET_PROPERTY 0x13 // Retrieves a property’s value. +#define CMD_GET_INT_STATUS 0x14 // Reads interrupt status bits. +#define CMD_GET_INT_STATUS_CTS 0x80 // CTS flag in status + + +#define CMD_PATCH_ARGS *0x15 // Reserved command used for patch file downloads. +#define CMD_PATCH_DATA *0x16 // Reserved command used for patch file downloads. +#define CMD_FM_TUNE_FREQ 0x20 // Selects the FM tuning frequency. +#define CMD_FM_SEEK_START 0x21 // Begins searching for a valid frequency. +#define CMD_FM_TUNE_STATUS 0x22 // Queries the status of previous FM_TUNE_FREQ or FM_SEEK_START command. +#define CMD_FM_RSQ_STATUS 0x23 // Queries the status of the Received Signal Quality (RSQ) of the current channel +#define CMD_FM_RDS_STATUS 0x24 // Returns RDS information for current channel and reads an entry from RDS FIFO. +#define CMD_FM_AGC_STATUS 0x27 // Queries the current AGC settings All +#define CMD_FM_AGC_OVERRIDE 0x28 // Override AGC setting by disabling and forcing it to a fixed value + +// FM Transmit Commands + +#define CMD_TX_TUNE_FREQ 0x30 +#define CMD_TX_TUNE_POWER 0x31 +#define CMD_TX_TUNE_MEASURE 0x32 +#define CMD_TX_TUNE_STATUS 0x33 +#define CMD_TX_TUNE_STATUS_IN_INTACK 0x01 + +#define CMD_TX_ASQ_STATUS 0x34 +#define CMD_TX_ASQ_STATUS_IN_INTACK 0x01 +#define CMD_TX_ASQ_STATUS_OUT_IALL 0x01 +#define CMD_TX_ASQ_STATUS_OUT_IALH 0x02 +#define CMD_TX_ASQ_STATUS_OUT_OVERMOD 0x04 + +#define CMD_TX_RDS_BUFF 0x35 +#define CMD_TX_RDS_BUFF_IN_INTACK 0x01 +#define CMD_TX_RDS_BUFF_IN_MTBUFF 0x02 +#define CMD_TX_RDS_BUFF_IN_LDBUFF 0x04 +#define CMD_TX_RDS_BUFF_IN_FIFO 0x80 + +#define CMD_TX_RDS_PS 0x36 + +// FM Transmit Parameters + +#define PROP_DIGITAL_INPUT_FORMAT 0x0101 +#define PROP_DIGITAL_INPUT_SAMPLE_RATE 0x0103 +#define PROP_REFCLK_FREQ 0x0201 +#define PROP_REFCLK_PRESCALE 0x0202 + +#define PROP_TX_COMPONENT_ENABLE 0x2100 +#define PROP_TX_COMPONENT_ENABLE_PILOT 0x0001 +#define PROP_TX_COMPONENT_ENABLE_LMR 0x0002 +#define PROP_TX_COMPONENT_ENABLE_RDS 0x0004 + + +#define PROP_TX_AUDIO_DEVIATION 0x2101 +#define PROP_TX_PILOT_DEVIATION 0x2102 +#define PROP_TX_RDS_DEVIATION 0x2103 +#define PROP_TX_LINE_INPUT_LEVEL 0x2104 + +#define PROP_TX_LINE_INPUT_LEVEL_396 0x0000 +#define PROP_TX_LINE_INPUT_LEVEL_100 0x1000 +#define PROP_TX_LINE_INPUT_LEVEL_74 0x2000 +#define PROP_TX_LINE_INPUT_LEVEL_60 0x3000 + +#define PROP_TX_LINE_INPUT_MUTE 0x2105 +#define PROP_TX_PILOT_FREQUENCY 0x2107 +#define PROP_TX_ACOMP_ENABLE 0x2200 +#define PROP_TX_ACOMP_THRESHOLD 0x2201 +#define PROP_TX_ACOMP_ATTACK_TIME 0x2202 +#define PROP_TX_ACOMP_RELEASE_TIME 0x2203 +#define PROP_TX_ACOMP_GAIN 0x2204 +#define PROP_TX_LIMITER_RELEASE_TIME 0x2205 +#define PROP_TX_ASQ_INTERRUPT_SOURCE 0x2300 +#define PROP_TX_ASQ_LEVEL_LOW 0x2301 +#define PROP_TX_ASQ_DURATION_LOW 0x2302 +#define PROP_TX_ASQ_LEVEL_HIGH 0x2303 +#define PROP_TX_ASQ_DURATION_HIGH 0x2304 +#define PROP_TX_RDS_INTERRUPT_SOURCE 0x2C00 +#define PROP_TX_RDS_PI 0x2C01 +#define PROP_TX_RDS_PS_MIX 0x2C02 +#define PROP_TX_RDS_PS_MISC 0x2C03 +#define PROP_TX_RDS_PS_REPEAT_COUNT 0x2C04 +#define PROP_TX_RDS_MESSAGE_COUNT 0x2C05 +#define PROP_TX_RDS_PS_AF 0x2C06 +#define PROP_TX_RDS_FIFO_SIZE 0x2C07 + + +// GPIO Control Commands + +#define CMD_GPIO_CTL 0x80 // Configures GPO1, 2, and 3 as output or Hi-Z. +#define CMD_GPIO_CTL_GPO1OEN 0x02 +#define CMD_GPIO_CTL_GPO2OEN 0x04 +#define CMD_GPIO_CTL_GPO3OEN 0x08 + +#define CMD_GPIO_SET 0x81 // Sets GPO1, 2, and 3 output level (low or high). +#define CMD_GPIO_SET_GPO1LEVEL 0x02 +#define CMD_GPIO_SET_GPO2LEVEL 0x04 +#define CMD_GPIO_SET_GPO3LEVEL 0x08 + +#define CMD_GPO_CTL 0x80 +#define CMD_GPO_SET 0x81 + +// Property and Parameter definitions + +#define PROP_GPO_IEN 0x0001 +#define PROP_GPO_IEN_STCIEN 0x0001 +#define PROP_GPO_IEN_RDSIEN 0x0004 +#define PROP_GPO_IEN_ERRIEN 0x0040 +#define PROP_GPO_IEN_CTSIEN 0x0080 + +// Deemphasis time constant. +#define PROP_FM_DEEMPHASIS 0x1100 +#define PROP_FM_DEEMPHASIS_50 0x01 +#define PROP_FM_DEEMPHASIS_75 0x02 + +// stereo blend +#define PROP_FM_BLEND_STEREO_THRESHOLD 0x1105 + +// setup the antenna input pin +#define PROP_FM_ANTENNA_INPUT 0x1107 +#define PROP_FM_ANTENNA_INPUT_FMI 0x00 +#define PROP_FM_ANTENNA_INPUT_SHORT 0x01 + +// FM_MAX_TUNE_ERROR +// #define FM_MAX_TUNE_ERROR 0x1108 + +// #define FM_SOFT_MUTE_RATE 0x1300 // not in use any more +#define FM_SOFT_MUTE_SLOPE 0x1301 +#define FM_SOFT_MUTE_MAX_ATTENUATION 0x1302 +#define FM_SOFT_MUTE_SNR_THRESHOLD 0x1303 +#define FM_SOFT_MUTE_RELEASE_RATE 0x1304 +#define FM_SOFT_MUTE_ATTACK_RATE 0x1305 + +#define PROP_FM_SEEK_FREQ_SPACING 0x1402 +#define FM_SEEK_TUNE_SNR_THRESHOLD 0x1403 +#define FM_SEEK_TUNE_RSSI_TRESHOLD 0x1404 + +#define PROP_RDS_INTERRUPT_SOURCE 0x1500 +#define PROP_RDS_INTERRUPT_SOURCE_RDSRECV 0x01 + +#define PROP_RDS_INT_FIFO_COUNT 0x1501 + +#define PROP_RDS_CONFIG 0x1502 + +#define PROP_RX_VOLUME 0x4000 + +#define PROP_FM_BLEND_RSSI_STEREO_THRESHOLD 0x1800 +#define PROP_FM_BLEND_RSSI_MONO_THRESHOLD 0x1801 + +#define PROP_RX_HARD_MUTE 0x4001 +#define PROP_RX_HARD_MUTE_RIGHT 0x01 +#define PROP_RX_HARD_MUTE_LEFT 0x02 +#define PROP_RX_HARD_MUTE_BOTH 0x03 + + +// Preemphasis time constant. +#define PROP_TX_PREEMPHASIS 0x2106 +#define PROP_TX_PREEMPHASIS_50 0x01 +#define PROP_TX_PREEMPHASIS_75 0x00 + +/// Initialize the extra variables in SI4721 +SI4721::SI4721() +{ + _realVolume = 0; + _txPower = 90; +} + +/// Initialize the library and the chip. +/// Set all internal variables to the standard values. +/// @return bool The return value is true when a SI4721 chip was found. +bool SI4721::init(TwoWire &wirePort, uint8_t deviceAddress) +{ + bool result; // chip found ?. + DEBUG_FUNC0("init"); + + RADIO::init(); + + // Now that the unit is reset and I2C interface mode, we need to begin I2C + _i2cPort = &wirePort; + _i2cPort->begin(); + _i2caddr = deviceAddress; + + // see if a chip can be found + result = RADIO::_wireExists(_i2cPort, deviceAddress); + + // Power down the device + _sendCommand(1, CMD_POWER_DOWN); + + // powering up is done by specifying the band etc. so it's implemented in setBand + return (result); +} // init() + + +/// Switch all functions of the chip off by powering down. +/// @return void +void SI4721::term() +{ + _sendCommand(1, CMD_POWER_DOWN); +} // term + + +// ----- Audio output control ----- + +/// This function maps the newVolume value in the range 0..15 to the range 0..63 that is available in this chip. +/// @param newVolume The new volume level of audio output. +void SI4721::setVolume(uint8_t newVolume) +{ + DEBUG_FUNC1("setVolume", newVolume); + setVolumeX(newVolume * 4); +} // setVolume() + + +/// This function sets the volume in the range 0..63. +/// @param newVolume The new volume level of audio output. +void SI4721::setVolumeX(uint8_t newVolume) +{ + if (newVolume > 63) + newVolume = 63; + _setProperty(PROP_RX_VOLUME, newVolume); + _realVolume = newVolume; + RADIO::setVolume(newVolume / 4); +} // setVolumeX() + + +/// Retrieve the current output volume in the range 0..63. +/// @return uint8_t actual volume. +uint8_t SI4721::getVolumeX() +{ + return (_realVolume); +} // getVolumeX() + + +/// Control the mute mode of the radio chip +/// In mute mode no output will be produced by the radio chip. +/// @param switchOn The new state of the mute mode. True to switch on, false to switch off. +/// @return void +void SI4721::setMute(bool switchOn) +{ + RADIO::setMute(switchOn); + + if (switchOn) { + // Set mute bits in the fm receiver + _setProperty(PROP_RX_HARD_MUTE, PROP_RX_HARD_MUTE_BOTH); + + } else { + // clear mute bits in the fm receiver + _setProperty(PROP_RX_HARD_MUTE, 0x00); + } // if +} // setMute() + + +/// Control the FM deemphasis of the radio chip +/// This number is also used as the pre-emphasis in transmit mode +/// @param uS The new deemphasis value in µS +/// @return void +void SI4721::setDeemphasis(uint8_t uS) +{ + _fmDeemphasis = uS; +} + +/// Control the softmute mode of the radio chip +/// If switched on the radio output is muted when no sender was found. +/// @param switchOn The new state of the softmute mode. True to switch on, false to switch off. +/// @return void +void SI4721::setSoftMute(bool switchOn) +{ + RADIO::setSoftMute(switchOn); + + if (switchOn) { + // to enable the softmute mode the attenuation is set to 0x10. + _setProperty(FM_SOFT_MUTE_MAX_ATTENUATION, 0x14); + } else { + // to disable the softmute mode the attenuation is set to 0. + _setProperty(FM_SOFT_MUTE_MAX_ATTENUATION, 0x00); + } +} // setSoftMute() + + +/// BassBoost is not supported by the SI4721 chip. +/// @param switchOn this functions ignores the switchOn parameter and always sets bassBoost to false. +/// @return void +void SI4721::setBassBoost(bool switchOn) +{ + DEBUG_STR("not supported."); + RADIO::setBassBoost(false); +} // setBassBoost() + + +/// Control the mono mode of the radio chip +/// In mono mode the stereo decoding will be switched off completely and the noise is typically reduced. +/// @param switchOn The new state of the mono mode. True to switch on, false to switch off. +/// @return void +void SI4721::setMono(bool switchOn) +{ + RADIO::setMono(switchOn); + if (_band == RADIO_BAND_FMTX) { + // switch Off ??? + // _setProperty(PROP_TX_COMPONENT_ENABLE, 0x0007); // stereo, pilot+rds + + } else if (_band == RADIO_BAND_FM) { + if (switchOn) { + // disable automatic stereo feature + _setProperty(PROP_FM_BLEND_STEREO_THRESHOLD, 127); + + } else { + // Automatic stereo feature on. + _setProperty(PROP_FM_BLEND_STEREO_THRESHOLD, 49); // default = 49 dBμV + } // if + } // if +} // setMono + + +// ----- Band and frequency control methods ----- + +/** + * Start using the new band for receiving or transmitting. + * This function resets the mode so it should not be called without good reason to avoid breaks. + * @param newBand The new band to be enabled. + * @return void + */ +void SI4721::setBand(RADIO_BAND newBand) +{ + // Power down the device + _sendCommand(1, CMD_POWER_DOWN); + // Give the device some time to power down before restart + delay(500); + + if (newBand == RADIO_BAND_FM) { + // set band boundaries and steps + RADIO::setBand(newBand); + + // Power up in receive mode without patch + _sendCommand(3, CMD_POWER_UP, (CMD_POWER_UP_1_XOSCEN | CMD_POWER_UP_1_FUNC_FM), CMD_POWER_UP_2_ANALOGOUT); + + // delay 500 msec when using the crystal oscillator as mentioned in the note from the POWER_UP command. + delay(500); + + _setProperty(PROP_REFCLK_FREQ, 32768); // crystal is 32.768 + _setProperty(PROP_FM_DEEMPHASIS, _fmDeemphasis == 75 ? PROP_FM_DEEMPHASIS_75 : PROP_FM_DEEMPHASIS_50); // Europe 50μS / USA 75μS deemphasis + _setProperty(PROP_FM_SEEK_FREQ_SPACING, _freqSteps); // in 100kHz spacing + _setProperty(PROP_FM_ANTENNA_INPUT, PROP_FM_ANTENNA_INPUT_SHORT); // sets antenna input + setFrequency(_freqLow); + + setMono(true); + setSoftMute(true); + setVolume(0); + setMute(false); + + // adjust sensibility for scanning + _setProperty(FM_SEEK_TUNE_SNR_THRESHOLD, 12); + _setProperty(FM_SEEK_TUNE_RSSI_TRESHOLD, 42); + + // _setProperty(PROP_GPO_IEN, 0); // no interrupts + _setProperty(PROP_GPO_IEN, PROP_GPO_IEN_STCIEN | PROP_GPO_IEN_RDSIEN); // | PROP_GPO_IEN_RDSIEN ???? + + } else if (newBand == RADIO_BAND_FMTX) { + RADIO::setBand(newBand); + + // Power up in transmit mode + _sendCommand(3, CMD_POWER_UP, (CMD_POWER_UP_1_XOSCEN | CMD_POWER_UP_1_FUNC_FMTX), CMD_POWER_UP_2_ANALOGIN); + // delay 500 msec when using the crystal oscillator as mentioned in the note from the POWER_UP command. + delay(500); + + _setProperty(PROP_REFCLK_FREQ, 32768); // crystal is 32.768 + _setProperty(PROP_TX_PREEMPHASIS, _fmDeemphasis == 75 ? PROP_TX_PREEMPHASIS_75 : PROP_TX_PREEMPHASIS_50); // uses the RX deemphasis as the TX preemphasis + _setProperty(PROP_TX_ACOMP_GAIN, 10); // sets max gain + _setProperty(PROP_TX_ACOMP_ENABLE, 0x0); // turns off limiter and AGC + + setTXPower(_txPower); // set Power after frequency + + // ---------------------- + // not all features of the FM transmitting functionality of the chip are featured by this library. + // There are more options you may adapt. See `AN332 Programming Guide.pdf`. + + // other possible setting: + // _setProperty(PROP_GPO_IEN, PROP_GPO_IEN_STCIEN | PROP_GPO_IEN_ERRIEN | PROP_GPO_IEN_CTSIEN); + // _setProperty(PROP_TX_COMPONENT_ENABLE, PROP_TX_COMPONENT_ENABLE_RDS | PROP_TX_COMPONENT_ENABLE_LMR | PROP_TX_COMPONENT_ENABLE_PILOT); + + // _setProperty(PROP_TX_AUDIO_DEVIATION, 6825); // default value + // _setProperty(PROP_TX_PILOT_DEVIATION, 675); // default value + // _setProperty(PROP_TX_RDS_DEVIATION, 200); // default value + + // _setProperty(PROP_TX_ACOMP_GAIN, 0x0F); // sets max gain + // _setProperty(PROP_TX_ACOMP_ENABLE, 0x03); // turns on limiter and AGC + + // _setProperty(PROP_TX_ACOMP_THRESHOLD, 0xFFD8); + // _setProperty(PROP_TX_ACOMP_ATTACK_TIME, 0x0002); + // _setProperty(PROP_TX_ACOMP_RELEASE_TIME, 0x0004); + // _setProperty(PROP_TX_LIMITER_RELEASE_TIME, 0x000D); + + // _setProperty(PROP_TX_LINE_INPUT_MUTE, 0x0000); + // _setProperty(PROP_TX_LINE_INPUT_LEVEL, PROP_TX_LINE_INPUT_LEVEL_60 | 0x27C); // not too sensitive + } // if +} // setBand() + + +/// Retrieve the real frequency from the chip after manual or automatic tuning. +/// @return RADIO_FREQ the current frequency. +RADIO_FREQ SI4721::getFrequency() +{ + + if (_band == RADIO_BAND_FMTX) { + _readStatusData(CMD_TX_TUNE_STATUS, 0x01, tuneStatus, sizeof(tuneStatus)); + } else { + _readStatusData(CMD_FM_TUNE_STATUS, 0x03, tuneStatus, sizeof(tuneStatus)); + } + + _freq = (tuneStatus[2] << 8) + tuneStatus[3]; + return (_freq); +} // getFrequency + + +/// If the device is currently in receive mode then \n +/// start using the new frequency for receiving.\n +/// If the device is in transmit mode then set the transmit frequency. \n +/// The new frequency is stored for later retrieval by the base class.\n +/// Because the chip may change the frequency automatically (when seeking) +/// the stored value might not be the current frequency. +/// @param newF The new frequency to be received/transmitted. +/// @return void +void SI4721::setFrequency(RADIO_FREQ newF) +{ + uint8_t status; + + RADIO::setFrequency(newF); + + if (_band == RADIO_BAND_FMTX) { + _sendCommand(4, CMD_TX_TUNE_FREQ, 0, (newF >> 8) & 0xff, (newF)&0xff); + setTXPower(_txPower); // ??? + + } else { + _sendCommand(5, CMD_FM_TUNE_FREQ, 0, (newF >> 8) & 0xff, (newF)&0xff, 0); + // reset the RDSParser + clearRDS(); + } + + // loop until status ok. + do { + status = _readStatus(); + } while (!(status & CMD_GET_INT_STATUS_CTS)); + Serial.println(status); +} // setFrequency() + + +/// Start seek mode upwards. +void SI4721::seekUp(bool toNextSender) +{ + uint8_t status; + + if (!toNextSender) { + RADIO_FREQ newF = getFrequency() + _freqSteps; + setFrequency(newF); + + } else { + _setProperty(FM_SEEK_TUNE_SNR_THRESHOLD, 12); + _setProperty(FM_SEEK_TUNE_RSSI_TRESHOLD, 42); + // start tuning + _sendCommand(2, CMD_FM_SEEK_START, 0x0C); + + // reset the RDSParser + clearRDS(); + + // loop until status ok. + do { + status = _readStatus(); + } while (!(status & CMD_GET_INT_STATUS_CTS)); + } // if +} // seekUp() + + +/// Start seek mode downwards. +void SI4721::seekDown(bool toNextSender) +{ + uint8_t status; + if (!toNextSender) { + RADIO_FREQ newF = getFrequency() - _freqSteps; + setFrequency(newF); + + } else { + // start tuning + _sendCommand(2, CMD_FM_SEEK_START, 0x04); + + // reset the RDSParser + clearRDS(); + + // loop until status ok. + do { + status = _readStatus(); + } while (!(status & CMD_GET_INT_STATUS_CTS)); + } // if +} // seekDown() + + +/// Load the status information from to the chip. +uint8_t SI4721::_readStatus() +{ + uint8_t data[1]; + _wireRead(_i2cPort, _i2caddr, CMD_GET_INT_STATUS, data, 1); + return (data[0]); +} // _readStatus() + + +/// Load status information from to the chip. +void SI4721::_readStatusData(uint8_t cmd, uint8_t param, uint8_t *values, uint8_t len) +{ + uint8_t buffer[2] = {cmd, param}; + _wireRead(_i2cPort, _i2caddr, buffer, 2, values, len); +} // _readStatusData() + + +/// Return a filled RADIO_INFO with the status of the radio features of the chip. +void SI4721::getRadioInfo(RADIO_INFO *info) +{ + RADIO::getRadioInfo(info); + + _readStatusData(CMD_FM_TUNE_STATUS, 0x01, tuneStatus, sizeof(tuneStatus)); + + info->active = true; + if (tuneStatus[1] & 0x01) + info->tuned = true; + + _readStatusData(CMD_FM_RSQ_STATUS, 0x01, rsqStatus, sizeof(rsqStatus)); + if (rsqStatus[3] & 0x80) + info->stereo = true; + info->rssi = rsqStatus[4]; + info->snr = rsqStatus[5]; + + _readStatusData(CMD_FM_RDS_STATUS, 0x05, rdsStatus.buffer, sizeof(rdsStatus)); + if (rdsStatus.resp2 & 0x01) + info->rds = true; +} // getRadioInfo() + + +/// Return a filled AUIO_INFO with the actual audio settings. +void SI4721::getAudioInfo(AUDIO_INFO *info) +{ + RADIO::getAudioInfo(info); +} // getAudioInfo() + + +// initialize RDS mode +void SI4721::attachReceiveRDS(receiveRDSFunction newFunction) +{ + DEBUG_FUNC0("attachReceiveRDS"); + + // enable RDS + _setProperty(PROP_RDS_INTERRUPT_SOURCE, PROP_RDS_INTERRUPT_SOURCE_RDSRECV); // Set the CTS status bit after receiving RDS data. + _setProperty(PROP_RDS_INT_FIFO_COUNT, 4); + _setProperty(PROP_RDS_CONFIG, 0xFF01); // accept all correctable data and enable rds + + RADIO::attachReceiveRDS(newFunction); +} + +/// Retrieve the next RDS data if available. +void SI4721::checkRDS() +{ + if (_sendRDS) { + // fetch the current RDS data + _readStatusData(CMD_FM_RDS_STATUS, 0x01, rdsStatus.buffer, sizeof(rdsStatus)); + + if ((rdsStatus.resp2 = 0x01) && (rdsStatus.rdsFifoUsed) && (rdsStatus.blockErrors == 0)) { + // RDS is in sync, it's a complete entry and no errors + +#define RDSBLOCKWORD(h, l) (h << 8 | l) + + _sendRDS(RDSBLOCKWORD(rdsStatus.blockAH, rdsStatus.blockAL), + RDSBLOCKWORD(rdsStatus.blockBH, rdsStatus.blockBL), + RDSBLOCKWORD(rdsStatus.blockCH, rdsStatus.blockCL), + RDSBLOCKWORD(rdsStatus.blockDH, rdsStatus.blockDL)); + } // if + } // if _sendRDS +} // checkRDS() + +// ----- Transmitter functions ----- + +/// Get the current output power. +uint8_t SI4721::getTXPower() +{ + return (_txPower); +} + + +/// Set the output power of the device. +/// @param pwr Output power of the device in dBµV (valid range is 88 to 115) +/// @return void +void SI4721::setTXPower(uint8_t pwr) +{ + _txPower = pwr; + _sendCommand(5, CMD_TX_TUNE_POWER, 0, 0, pwr, 0); +} + +/// Begin Broadcasting RDS and optionally set Program ID. \n +/// The Program ID should be a unique 4 character hexadecimal \n +/// code that identifies your station. By default, the Radio library \n +/// will use 0xBEEF as your Program ID. +/// @param programID Optional 4 character hexadecimal ID +/// @return void +void SI4721::beginRDS(uint16_t programID) +{ + + _setProperty(PROP_TX_AUDIO_DEVIATION, 6625); // 66.25KHz (default is 68.25) + _setProperty(PROP_TX_RDS_DEVIATION, 200); // 2KHz (default) + _setProperty(PROP_TX_RDS_INTERRUPT_SOURCE, 0x0001); // RDS IRQ + _setProperty(PROP_TX_RDS_PI, programID); // program identifier + _setProperty(PROP_TX_RDS_PS_MIX, 0x03); // 50% mix (default) + _setProperty(PROP_TX_RDS_PS_MISC, 0x1808); // RDSD0 & RDSMS (default) + _setProperty(PROP_TX_RDS_PS_REPEAT_COUNT, 3); // 3 repeats (default) + _setProperty(PROP_TX_RDS_MESSAGE_COUNT, 1); + _setProperty(PROP_TX_RDS_PS_AF, 0xE0E0); // no AF + _setProperty(PROP_TX_RDS_FIFO_SIZE, 0); + _setProperty(PROP_TX_COMPONENT_ENABLE, 0x0007); +} + +/// Set the RDS station name string. \n +/// Your Station Name (Programme Service Name) is a static, \n +/// 8 character display that represents your call letters or \n +/// station identity name. +/// @param *s string containing your 8 character name +/// @return void +void SI4721::setRDSstation(char *s) +{ + uint8_t i, len = strlen(s); + uint8_t slots = (len + 3) / 4; + + for (uint8_t i = 0; i < slots; i++) { + uint8_t psChar[4] = {' ', ' ', ' ', ' '}; + memcpy(psChar, s, min(4, (int)strlen(s))); // copy from index of string s to the minimum of 4 or the length of s + s += 4; // advance index of s by 4 + _sendCommand(6, CMD_TX_RDS_PS, i, psChar[0], psChar[1], psChar[2], psChar[3], 0); + } +} + +/// Load new data into RDS Radio Text Buffer. +/// @param *s string containing arbitrary text to be transmitted as RDS Radio Text +/// @return void +void SI4721::setRDSbuffer(char *s) +{ + uint8_t i, len = strlen(s); + uint8_t slots = (len + 3) / 4; + char slot[5]; + + for (uint8_t i = 0; i < slots; i++) { + uint8_t rdsBuff[4] = {' ', ' ', ' ', ' '}; + memcpy(rdsBuff, s, min(4, (int)strlen(s))); + s += 4; + _sendCommand(8, CMD_TX_RDS_BUFF, i == 0 ? 0x06 : 0x04, 0x20, i, rdsBuff[0], rdsBuff[1], rdsBuff[2], rdsBuff[3], 0); + } + + _setProperty(PROP_TX_COMPONENT_ENABLE, 0x0007); // stereo, pilot+rds +} + +/// Get TX Status and Audio Input Metrics +/// @param void +/// @return ASQ_STATUS struct containing asq and audioInLevel values +ASQ_STATUS SI4721::getASQ() +{ + _sendCommand(2, CMD_TX_ASQ_STATUS, 0x1); + + _i2cPort->requestFrom((uint8_t)_i2caddr, (uint8_t)5); + + ASQ_STATUS result; + + uint8_t response[5]; + for (uint8_t i = 0; i < 5; i++) { + response[i] = _i2cPort->read(); + } + + result.asq = response[1]; + result.audioInLevel = response[4]; + + return result; +} + +/// Get TX Tuning Status +/// @param void +/// @return TX_STATUS struct containing frequency, dBuV, antennaCap, and noiseLevel values +TX_STATUS SI4721::getTuneStatus() +{ + _sendCommand(2, CMD_TX_TUNE_STATUS, 0x1); + + _i2cPort->requestFrom((uint8_t)_i2caddr, (uint8_t)8); + + TX_STATUS result; + + uint8_t response[8]; + for (uint8_t i = 0; i < 8; i++) { + response[i] = _i2cPort->read(); + } + + result.frequency = response[2]; + result.frequency <<= 8; + result.frequency |= response[3]; + result.dBuV = response[5]; + result.antennaCap = response[6]; + result.noiseLevel = response[7]; + + return result; +} + +// ----- Debug functions ----- + +/// Send the current values of all registers to the Serial port. +void SI4721::debugStatus() +{ + RADIO::debugStatus(); + _readStatusData(CMD_FM_TUNE_STATUS, 0x03, tuneStatus, sizeof(tuneStatus)); + + Serial.print("Tune-Status: "); + Serial.print(tuneStatus[0], HEX); + Serial.print(' '); + Serial.print(tuneStatus[1], HEX); + Serial.print(' '); + + Serial.print("TUNE:"); + Serial.print((tuneStatus[2] << 8) + tuneStatus[3]); + Serial.print(' '); + // RSSI and SNR when tune is complete (not the actual one ?) + Serial.print("RSSI:"); + Serial.print(tuneStatus[4]); + Serial.print(' '); + Serial.print("SNR:"); + Serial.print(tuneStatus[5]); + Serial.print(' '); + Serial.print("MULT:"); + Serial.print(tuneStatus[6]); + Serial.print(' '); + Serial.print(tuneStatus[7]); + Serial.print(' '); + Serial.println(); + + Serial.print("RSQ-Status: "); + _readStatusData(CMD_FM_RSQ_STATUS, 0x01, rsqStatus, sizeof(rsqStatus)); + Serial.print(rsqStatus[0], HEX); + Serial.print(' '); + Serial.print(rsqStatus[1], HEX); + Serial.print(' '); + Serial.print(rsqStatus[2], HEX); + Serial.print(' '); + if (rsqStatus[2] & 0x08) + Serial.print("SMUTE "); + Serial.print(rsqStatus[3], HEX); + Serial.print(' '); + if (rsqStatus[3] & 0x80) + Serial.print("STEREO "); + // The current RSSI and SNR. + Serial.print("RSSI:"); + Serial.print(rsqStatus[4]); + Serial.print(' '); + Serial.print("SNR:"); + Serial.print(rsqStatus[5]); + Serial.print(' '); + Serial.print(rsqStatus[7], HEX); + Serial.print(' '); + Serial.println(); + + Serial.print("RDS-Status: "); + _readStatusData(CMD_FM_RDS_STATUS, 0x01, rdsStatus.buffer, sizeof(rdsStatus)); + for (uint8_t n = 0; n < 12; n++) { + Serial.print(rsqStatus[n], HEX); + Serial.print(' '); + } // for + Serial.println(); + + // AGC settings and status + Serial.print("AGC-Status: "); + _readStatusData(CMD_FM_AGC_STATUS, 0x01, agcStatus, sizeof(agcStatus)); + Serial.print(agcStatus[0], HEX); + Serial.print(' '); + Serial.print(agcStatus[1], HEX); + Serial.print(' '); + Serial.print(agcStatus[2], HEX); + Serial.print(' '); + Serial.println(); + +} // debugStatus + + +/// wait until the current seek and tune operation is over. +void SI4721::_waitEnd() +{ + DEBUG_FUNC0("_waitEnd"); +} // _waitEnd() + + +/// Send an array of bytes to the radio chip +void SI4721::_sendCommand(int cnt, int cmd, ...) +{ + uint8_t cmdData[12]; + + va_list params; + va_start(params, cmd); + cmdData[0] = cmd; + for (uint8_t i = 1; i < cnt; i++) { + cmdData[i] = va_arg(params, int); + } + + _wireRead(_i2cPort, _i2caddr, cmdData, cnt, &_status, 1); + + // wait for command is executed finally. + while (!(_status & CMD_GET_INT_STATUS_CTS)) { + delay(10); + _i2cPort->requestFrom((uint8_t)_i2caddr, (uint8_t)1); + _status = _i2cPort->read(); + if (_wireDebugEnabled) { + Serial.print(" =0x"); + Serial.println(_status, HEX); + } + } // while +} // _sendCommand() + + +/// Set a property in the radio chip +void SI4721::_setProperty(uint16_t prop, uint16_t value) +{ + uint8_t cmdData[6] = {CMD_SET_PROPERTY, 0, prop >> 8, prop & 0x00FF, value >> 8, value & 0x00FF}; + _wireRead(_i2cPort, _i2caddr, cmdData, 6, &_status, 1); +} // _setProperty() + + +// ----- internal functions ----- + +// The End. diff --git a/src/SI4721.h b/src/SI4721.h new file mode 100644 index 0000000..bb2dc53 --- /dev/null +++ b/src/SI4721.h @@ -0,0 +1,209 @@ +/// +/// \file SI4721.h +/// \brief Library header file for the radio library to control the SI4721 radio chip. +/// +/// \author N Poole, nickpoole.me +/// \author Matthias Hertel, http://www.mathertel.de +/// \copyright Copyright (c) 2014 by Matthias Hertel.\n +/// This work is licensed under a BSD style license.\n +/// See http://www.mathertel.de/License.aspx +/// +/// This library enables the use of the Radio Chip SI4721 for receiving and transmitting. +/// Settings are compatible top the following board from sparkfun: https://www.sparkfun.com/products/15853 +/// +/// More documentation and source code is available at http://www.mathertel.de/Arduino +/// +/// ChangeLog: +/// ---------- +/// * 01.12.2019 created. +/// * 17.09.2020 si4721 specific initialization moved into setBand() + +#ifndef SI4721_h +#define SI4721_h + +#include + +// The wire library is used for the communication with the radio chip. +#include + +// Include the radio library that is extended by the SI4721 library. +#include + +// SI4721 specifics + +#define SI4721_ADR 0x11 ///< The I2C address of SI4721 is 0x11 or 0x63 + +// A structure for storing ASQ Status and Audio Input Metrics +typedef struct ASQ_STATUS { + uint8_t asq; + uint8_t audioInLevel; +}; + +// A structure for storing TX Tuning Status +typedef struct TX_STATUS { + uint16_t frequency; + uint8_t dBuV; + uint8_t antennaCap; + uint8_t noiseLevel; +}; + +// ----- library definition ----- + +/// Library to control the SI4721 radio chip. +class SI4721 : public RADIO +{ +public: + const uint8_t MAXVOLUME = 15; ///< max volume level for radio implementations. + const uint8_t MAXVOLUMEX = 63; ///< max volume level for the SI4721 specific implementation. + + SI4721(); + + /** Initialize the library and the chip. */ + bool init(TwoWire &wirePort = Wire, uint8_t deviceAddress = SI4721_ADR); + + /** Terminate all radio functions in the chip. */ + void term(); + + // ----- Audio functions ----- + + void setVolume(uint8_t newVolume); ///< Control the volume output of the radio chip in the range 0..15. + + void setVolumeX(uint8_t newVolume); ///< Control the volume output of the radio chip in the range 0..63. + uint8_t getVolumeX(); ///< Retrieve the current output volume in the range 0..63. + + void setMute(bool switchOn) override; ///< Control the mute mode of the radio chip. + void setSoftMute(bool switchOn) override; ///< Control the softmute mode (mute on low signals) of the radio chip. + + // Overwrite audio functions that are not supported. + void setBassBoost(bool switchOn) override; ///< regardless of the given parameter, the Bass Boost will never switch on. + + // ----- Radio receiver functions ----- + + void setMono(bool switchOn); ///< Control the mono/stereo mode of the radio chip. + + /** + * Start using the new band for receiving or transmitting. + * This function resets the mode so it should not be called without good reason to avoid breaks. + * @param newBand The new band to be enabled. + * @return void + */ + void setBand(RADIO_BAND newBand); + + void setFrequency(RADIO_FREQ newF); ///< Control the frequency. + RADIO_FREQ getFrequency(void); + + void seekUp(bool toNextSender = true); // start seek mode upwards + void seekDown(bool toNextSender = true); // start seek mode downwards + + void attachReceiveRDS(receiveRDSFunction newFunction) override; ///< Register a RDS processor function. + void checkRDS(); // read RDS data from the current station and process when data available. + + void getRadioInfo(RADIO_INFO *info); + void getAudioInfo(AUDIO_INFO *info); + + // ----- debug Helpers send information to Serial port + + void debugScan(); // Scan all frequencies and report a status + void debugStatus(); // Report Info about actual Station + + // ----- transmit functions + + void beginRDS(uint16_t programID = 0xBEEF); + void setRDSstation(char *s); + void setRDSbuffer(char *s); + + uint8_t getTXPower(); + void setTXPower(uint8_t pwr); + + ASQ_STATUS getASQ(); + TX_STATUS getTuneStatus(); + + // ----- regional compatibility + + void setDeemphasis(uint8_t uS); // set the deemphasis (50 for Europe, 75 for USA) + +private: + // ----- local variables + + uint8_t _realVolume; ///< The real volume set to the chip. + + uint8_t _fmDeemphasis = 50; ///< RX Deemphasis and TX Preemphasis in uS + + // store the current status values + uint8_t _status; ///< the status after sending a command + + uint8_t tuneStatus[8]; + uint8_t rsqStatus[1 + 7]; + uint8_t rdsStatusx[1 + 12]; + uint8_t agcStatus[1 + 2]; + + uint8_t _txPower; + + /// structure used to read status information from the SI4721 radio chip. + union { + // use structured access + struct { + uint8_t status; + uint8_t resp1; + uint8_t resp2; + uint8_t rdsFifoUsed; + uint8_t blockAH; + uint8_t blockAL; + uint8_t blockBH; + uint8_t blockBL; + uint8_t blockCH; + uint8_t blockCL; + uint8_t blockDH; + uint8_t blockDL; + uint8_t blockErrors; + }; + // use the the byte while receiving and sending. + uint8_t buffer[1 + 7]; + } tuneStatus2; // union RDSSTATUS + + + /// structure used to read RDS information from the SI4721 radio chip. + union { + // use structured access + struct { + uint8_t status; + uint8_t resp1; + uint8_t resp2; + uint8_t rdsFifoUsed; + uint8_t blockAH; + uint8_t blockAL; + uint8_t blockBH; + uint8_t blockBL; + uint8_t blockCH; + uint8_t blockCL; + uint8_t blockDH; + uint8_t blockDL; + uint8_t blockErrors; + }; + // use the the byte while receiving and sending. + uint8_t buffer[1 + 12]; + } rdsStatus; // union RDSSTATUS + + + // ----- low level communication to the chip using I2C bus + + /// send a command + void _sendCommand(int cnt, int cmd, ...); + + /// set a property + void _setProperty(uint16_t prop, uint16_t value); + + /// read the interrupt status. + uint8_t _readStatus(); + + /// read status information into a buffer + void _readStatusData(uint8_t cmd, uint8_t param, uint8_t *values, uint8_t len); + + void _seek(bool seekUp = true); + void _waitEnd(); + + TwoWire *_i2cPort; + int _i2caddr; +}; + +#endif diff --git a/src/radio.cpp b/src/radio.cpp index e95076c..af2df00 100644 --- a/src/radio.cpp +++ b/src/radio.cpp @@ -17,47 +17,52 @@ /// /// More documentation and source code is available at http://www.mathertel.de/Arduino /// -/// ChangeLog see: radio.h +/// ChangeLog see: radio.h -#include "Arduino.h" +#include +#include +#include -#include "radio.h" // ----- Register Definitions ----- // no chip-registers without a chip. - // ----- implement /// Setup the radio object and initialize private variables to 0. /// Don't change the radio chip (yet). -RADIO::RADIO() { +RADIO::RADIO() +{ memset(this, 0, sizeof(RADIO)); } // RADIO() /// The RADIO class doesn't implement a concrete chip so nothing has to be initialized. -bool RADIO::init() { - return(false); +bool RADIO::init() +{ + return (false); } // init() /// switch the power off /// The RADIO class doesn't implement a concrete chip so nothing has to be terminated. -void RADIO::term() { +void RADIO::term() +{ } // term() // ----- Volume control ----- -void RADIO::setVolume(uint8_t newVolume) { +void RADIO::setVolume(uint8_t newVolume) +{ _volume = newVolume; } // setVolume() -uint8_t RADIO::getVolume() { - return(_volume); +uint8_t RADIO::getVolume() +{ + return (_volume); } // getVolume() @@ -66,57 +71,68 @@ uint8_t RADIO::getVolume() { /// Control the bass boost mode of the radio chip. /// The base implementation ony stores the value to the internal variable. /// @param switchOn true to switch bassBoost mode on, false to switch bassBoost mode off. -void RADIO::setBassBoost(bool switchOn) { +void RADIO::setBassBoost(bool switchOn) +{ + DEBUG_FUNC1("setBassBoost", switchOn); _bassBoost = switchOn; } // setBassBoost() /// Retrieve the current bass boost mode setting. /// The base implementation returns only the value in the internal variable. -bool RADIO::getBassBoost() { - return(_bassBoost); +bool RADIO::getBassBoost() +{ + return (_bassBoost); } // getBassBoost() // ----- mono control ----- /// The base implementation ony stores the value to the internal variable. -void RADIO::setMono(bool switchOn) { +void RADIO::setMono(bool switchOn) +{ + DEBUG_FUNC1("setMono", switchOn); _mono = switchOn; } // setMono() /// The base implementation returns only the value in the internal variable. -bool RADIO::getMono() { - return(_mono); +bool RADIO::getMono() +{ + return (_mono); } // getMono() // ----- mute control ----- /// The base implementation ony stores the value to the internal variable. -void RADIO::setMute(bool switchOn) { +void RADIO::setMute(bool switchOn) +{ _mute = switchOn; } // setMute() /// The base implementation returns only the value in the internal variable. -bool RADIO::getMute() { - return(_mute); +bool RADIO::getMute() +{ + return (_mute); } // getMute() // ----- softmute control ----- /// The base implementation ony stores the value to the internal variable. -void RADIO::setSoftMute(bool switchOn) { +void RADIO::setSoftMute(bool switchOn) +{ + DEBUG_FUNC1("setSoftMute", switchOn); _softMute = switchOn; } // setSoftMute() /// The base implementation returns only the value in the internal variable. -bool RADIO::getSoftMute() { - return(_softMute); +bool RADIO::getSoftMute() +{ + return (_softMute); } // getSoftMute() @@ -125,15 +141,16 @@ bool RADIO::getSoftMute() { // some implementations to return internal variables if used by concrete chip implementations /// Start using the new band for receiving. -void RADIO::setBand(RADIO_BAND newBand) { +void RADIO::setBand(RADIO_BAND newBand) +{ + DEBUG_FUNC1("setBand", newBand); _band = newBand; if (newBand == RADIO_BAND_FM) { _freqLow = 8700; _freqHigh = 10800; _freqSteps = 10; - } - else if (newBand == RADIO_BAND_FMWORLD) { + } else if (newBand == RADIO_BAND_FMWORLD) { _freqLow = 7600; _freqHigh = 10800; _freqSteps = 10; @@ -143,30 +160,49 @@ void RADIO::setBand(RADIO_BAND newBand) { /// Start using the new frequency for receiving. /// The new frequency is stored for later retrieval. -void RADIO::setFrequency(RADIO_FREQ newFreq) { +void RADIO::setFrequency(RADIO_FREQ newFreq) +{ + DEBUG_FUNC1("setFrequency", newFreq); _freq = newFreq; } // setFrequency() -void RADIO::setBandFrequency(RADIO_BAND newBand, RADIO_FREQ newFreq) { +void RADIO::setBandFrequency(RADIO_BAND newBand, RADIO_FREQ newFreq) +{ setBand(newBand); setFrequency(newFreq); } // setBandFrequency() -void RADIO::seekUp(bool) {} +void RADIO::seekUp(bool) {} void RADIO::seekDown(bool) {} -RADIO_BAND RADIO::getBand() { return(_band); } -RADIO_FREQ RADIO::getFrequency() { return(_freq); } -RADIO_FREQ RADIO::getMinFrequency() { return(_freqLow); } -RADIO_FREQ RADIO::getMaxFrequency() { return(_freqHigh); } -RADIO_FREQ RADIO::getFrequencyStep(){ return(_freqSteps); } +RADIO_BAND RADIO::getBand() +{ + return (_band); +} +RADIO_FREQ RADIO::getFrequency() +{ + return (_freq); +} +RADIO_FREQ RADIO::getMinFrequency() +{ + return (_freqLow); +} +RADIO_FREQ RADIO::getMaxFrequency() +{ + return (_freqHigh); +} +RADIO_FREQ RADIO::getFrequencyStep() +{ + return (_freqSteps); +} /// Return all the Radio settings. /// This implementation only knows some values from the last settings. -void RADIO::getRadioInfo(RADIO_INFO *info) { +void RADIO::getRadioInfo(RADIO_INFO *info) +{ // set everything to false and 0. memset(info, 0, sizeof(RADIO_INFO)); // info->tuned = false; @@ -181,7 +217,8 @@ void RADIO::getRadioInfo(RADIO_INFO *info) { /// Return current settings as far as no chip is required. /// When using the radio::setXXX methods, no chip specific implementation is needed. -void RADIO::getAudioInfo(AUDIO_INFO *info) { +void RADIO::getAudioInfo(AUDIO_INFO *info) +{ // set everything to false and 0. memset(info, 0, sizeof(AUDIO_INFO)); @@ -195,12 +232,15 @@ void RADIO::getAudioInfo(AUDIO_INFO *info) { /// In the general radio implementation there is no chip for RDS. /// This function needs to be implemented for radio chips with RDS receiving functionality. -void RADIO::checkRDS() { /* no chip : nothing to check */ } +void RADIO::checkRDS() +{ /* no chip : nothing to check */ +} /// Send a 0.0.0.0 to the RDS receiver if there is any attached. /// This is to point out that there is a new situation and all existing data should be invalid from now on. -void RADIO::clearRDS() { +void RADIO::clearRDS() +{ if (_sendRDS) _sendRDS(0, 0, 0, 0); } // clearRDS() @@ -215,7 +255,8 @@ void RADIO::attachReceiveRDS(receiveRDSFunction newFunction) // format the current frequency for display and printing -void RADIO::formatFrequency(char *s, uint8_t length) { +void RADIO::formatFrequency(char *s, uint8_t length) +{ RADIO_BAND b = getBand(); RADIO_FREQ f = getFrequency(); @@ -227,10 +268,12 @@ void RADIO::formatFrequency(char *s, uint8_t length) { int16_to_s(s, (uint16_t)f); // insert decimal point - s[5] = s[4]; s[4] = s[3]; s[3] = '.'; + s[5] = s[4]; + s[4] = s[3]; + s[3] = '.'; // append units - strcpy(s+6, " MHz"); + strcpy(s + 6, " MHz"); } // if // f = _freqLow + (channel * _bandSteps); @@ -241,40 +284,50 @@ void RADIO::formatFrequency(char *s, uint8_t length) { } // formatFrequency() -// enable debugging information on Serial port. -void RADIO::debugEnable(bool enable) { +/** + * Enable debugging information on Serial port. + * This is for logging on a higher level than i2c data transport. + * @param enable true to switch logging on. + */ +void RADIO::debugEnable(bool enable) +{ _debugEnabled = enable; } // debugEnable() // print out all radio information -void RADIO::debugRadioInfo() { +void RADIO::debugRadioInfo() +{ RADIO_INFO info; this->getRadioInfo(&info); - Serial.print(info.rds ? " RDS" : " ---"); - Serial.print(info.tuned ? " TUNED" : " -----"); + Serial.print(info.rds ? " RDS" : " ---"); + Serial.print(info.tuned ? " TUNED" : " -----"); Serial.print(info.stereo ? " STEREO" : " MONO "); - Serial.print(" RSSI: "); Serial.print(info.rssi); - Serial.print(" SNR: "); Serial.print(info.snr); + Serial.print(" RSSI: "); + Serial.print(info.rssi); + Serial.print(" SNR: "); + Serial.print(info.snr); Serial.println(); } // debugRadioInfo() // print out all audio information -void RADIO::debugAudioInfo() { +void RADIO::debugAudioInfo() +{ AUDIO_INFO info; this->getAudioInfo(&info); - Serial.print(info.bassBoost ? " BASS" : " ----"); - Serial.print(info.mute ? " MUTE" : " ----"); - Serial.print(info.softmute ? " SOFTMUTE" : " --------"); + Serial.print(info.mute ? " MUTE" : " ----"); + Serial.print(info.softmute ? " SOFTMUTE" : " --------"); + Serial.print(info.bassBoost ? " BASS" : " ----"); Serial.println(); } // debugAudioInfo() /// The RADIO class doesn't have interesting status information so nothing is sent. -void RADIO::debugStatus() { +void RADIO::debugStatus() +{ // no output. } // debugStatus @@ -282,7 +335,8 @@ void RADIO::debugStatus() { /// This is a special format routine used to format frequencies as strings with leading blanks. /// up to 5 digits only (" 0".."99999") /// *s MUST be able to hold the characters -void RADIO::int16_to_s(char *s, uint16_t val) { +void RADIO::int16_to_s(char *s, uint16_t val) +{ uint8_t n = 5; while (n > 0) { @@ -290,25 +344,136 @@ void RADIO::int16_to_s(char *s, uint16_t val) { if ((n == 4) || (val > 0)) { s[n] = '0' + (val % 10); val = val / 10; - } - else { + } else { s[n] = ' '; } } // while } // int16_to_s() -/// Prints a register as 4 character hexadecimal code with leading zeros. -void RADIO::_printHex4(uint16_t val) +// ===== Wire Utilities ===== + +/** + * Enable low level i2c debugging information on Serial port. + * @param enable true to switch logging on. + */ +void RADIO::_wireDebug(bool enable) +{ + _wireDebugEnabled = enable; +} // _wireDebug() + + +bool RADIO::_wireExists(TwoWire *port, int address) +{ + port->beginTransmission(address); + uint8_t err = port->endTransmission(); + if (_wireDebugEnabled) { + Serial.print("_wireExists("); + Serial.print(address); + Serial.print("): err="); + Serial.println(err); + } + return (err == 0); +} + + +/** + * Write and optionally read data on the i2c bus. + * A debug output can be enabled using _wireDebug(). + * @param port i2c port to be used. + * @param address i2c address to be used. + * @param reg the register to be read (1 byte send). + * @param data buffer array with received data. If this parameter is nullptr no data will be requested. + * @param len length of data buffer. + * @return number of register values received. + */ +int RADIO::_wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int len) +{ + return (_wireRead(port, address, ®, 1, data, len)); +} // _wireRead() + + +/** + * Write and optionally read data on the i2c bus. + * A debug output can be enabled using _wireDebug(). + * @param port i2c port to be used. + * @param address i2c address to be used. + * @param cmdData array with data to be send. + * @param cmdLen length of cmdData. + * @param data buffer array with received data. If this parameter is nullptr no data will be requested. + * @param len length of data buffer. + * @return number of register values received. + */ +int RADIO::_wireRead(TwoWire *port, int address, uint8_t *cmdData, int cmdLen, uint8_t *data, int len) +{ + int recieved = 0; + + // send out command sequence + port->beginTransmission(address); + if (_wireDebugEnabled) { + Serial.print("_wire("); + Serial.print(address); + Serial.print("): "); + } + + for (int i = 0; i < cmdLen; i++) { + uint8_t d = cmdData[i]; + port->write(d); + if (_wireDebugEnabled) { + _printHex2(d); + } // if + } // for + + port->endTransmission(); + + // read requested data (when buffer is available) + if (data) { + if (_wireDebugEnabled) { + Serial.print(" -> "); + } + uint8_t *d = data; + + port->requestFrom(address, len); + + while (port->available() && (recieved < len)) { + *d = port->read(); + recieved++; + if (_wireDebugEnabled) { + _printHex2(*d); + } + d++; + } + + if (_wireDebugEnabled) { + Serial.println('.'); + } + } // if (data) + + return (recieved); +} // _wireRead() + + +/// Prints a byte as 2 character hexadecimal code with leading zeros. +void RADIO::_printHex2(uint8_t val) { - if (val <= 0x000F) Serial.print('0'); // if less 2 Digit - if (val <= 0x00FF) Serial.print('0'); // if less 3 Digit - if (val <= 0x0FFF) Serial.print('0'); // if less 4 Digit + Serial.print(' '); + if (val <= 0x000F) + Serial.print('0'); // if less 2 Digit Serial.print(val, HEX); +} // _printHex2 + + +/// Prints a word as 4 character hexadecimal code with leading zeros. +void RADIO::_printHex4(uint16_t val) +{ Serial.print(' '); + if (val <= 0x000F) + Serial.print('0'); // if less 2 Digit + if (val <= 0x00FF) + Serial.print('0'); // if less 3 Digit + if (val <= 0x0FFF) + Serial.print('0'); // if less 4 Digit + Serial.print(val, HEX); } // _printHex4 - // The End. - - diff --git a/src/radio.h b/src/radio.h index 455e549..f8c09d1 100644 --- a/src/radio.h +++ b/src/radio.h @@ -27,17 +27,19 @@ /// * 31.08.2014 Doxygen style comments added. /// * 05.02.2015 mainpage content added. /// * 29.04.2015 clear RDS function, need to clear RDS info after tuning. -/// +/// * 17.09.2020 Wire Util functions added. + /// TODO: /// -------- /// * multi-Band enabled /// \mainpage /// An Arduino library to control radio for receiving FM broadcast signals. -/// -/// Currently the following chios are supported: +/// +/// Currently the following chips are supported: /// * The SI4703 from Silicon Labs /// * The SI4705 from Silicon Labs +/// * The SI4721 from Silicon Labs /// * The TEA5767 from NXP /// * The RDA5807M from RDA Microelectronics /// @@ -98,12 +100,12 @@ extern "C" { enum RADIO_BAND { RADIO_BAND_NONE = 0, ///< No band selected. - RADIO_BAND_FM = 1, ///< FM band 87.5 – 108 MHz (USA, Europe) selected. - RADIO_BAND_FMWORLD = 2, ///< FM band 76 – 108 MHz (Japan, Worldwide) selected. - RADIO_BAND_AM = 3, ///< AM band selected. - RADIO_BAND_KW = 4, ///< KW band selected. + RADIO_BAND_FM = 0x01, ///< FM band 87.5 � 108 MHz (USA, Europe) selected. + RADIO_BAND_FMWORLD = 0x02, ///< FM band 76 � 108 MHz (Japan, Worldwide) selected. + RADIO_BAND_AM = 0x03, ///< AM band selected. + RADIO_BAND_KW = 0x04, ///< KW band selected. - RADIO_BAND_MAX = 4 ///< Maximal band enumeration value. + RADIO_BAND_FMTX = 0x11, ///< Transmit for FM. }; @@ -187,9 +189,9 @@ class RADIO { // ----- Supporting RDS for FM bands ----- + virtual void attachReceiveRDS(receiveRDSFunction newFunction); ///< Register a RDS processor function. virtual void checkRDS(); ///< Check if RDS Data is available and good. virtual void clearRDS(); ///< Clear RDS data in the attached RDS Receiver by sending 0,0,0,0. - virtual void attachReceiveRDS(receiveRDSFunction newFunction); ///< Register a RDS processor function. // ----- Utilitys ----- @@ -198,13 +200,56 @@ class RADIO { // ----- debug Helpers send information to Serial port - virtual void debugEnable(bool enable = true); ///< Enable sending debug information to the Serial port. + /** + * Enable debugging information on Serial port. + * This is for logging on a higher level than i2c data transport. + * @param enable true to switch logging on. + */ + virtual void debugEnable(bool enable = true); + virtual void debugRadioInfo(); ///< Print out all radio information. virtual void debugAudioInfo(); ///< Print out all audio information. - virtual void debugStatus(); ///< Send debug information about actual available chip functionality and other internal things. + virtual void debugStatus(); ///< Send debug information about actual available chip functionality and other internal things. + + // ===== Wire Utilities ===== + + /** + * Enable low level i2c debugging information on Serial port. + * @param enable true to switch logging on. + */ + virtual void _wireDebug(bool enable = true); + + /** check for a device on address */ + bool _wireExists(TwoWire *port, int address); + + /** + * Write and optionally read data on the i2c bus. + * A debug output can be enabled using _wireDebug(). + * @param port i2c port to be used. + * @param address i2c address to be used. + * @param reg the register to be read (1 byte send). + * @param data buffer array with received data. If this parameter is nullptr no data will be requested. + * @param len length of data buffer. + * @return number of register values received. + */ + int _wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int len); + + /** + * Write and optionally read data on the i2c bus. + * A debug output can be enabled using _wireDebug(). + * @param port i2c port to be used. + * @param address i2c address to be used. + * @param cmdData array with data to be send. + * @param cmdLen length of cmdData. + * @param data buffer array with received data. If this parameter is nullptr no data will be requested. + * @param len length of data buffer. + * @return number of register values received. + */ + int _wireRead(TwoWire *port, int address, uint8_t *cmdData, int cmdLen, uint8_t *data, int len); protected: bool _debugEnabled; ///< Set by debugEnable() and controls debugging functionality. + bool _wireDebugEnabled; ///< Set by _wireDebug() and controls i2c data level debugging. uint8_t _volume; ///< Last set volume level. bool _bassBoost; ///< Last set bass Boost effect. @@ -217,11 +262,12 @@ class RADIO { RADIO_FREQ _freqLow; ///< Lowest frequency of the current selected band. RADIO_FREQ _freqHigh; ///< Highest frequency of the current selected band. - RADIO_FREQ _freqSteps; ///< Resulution of the tuner. + RADIO_FREQ _freqSteps; ///< Resolution of the tuner. receiveRDSFunction _sendRDS; ///< Registered RDS Function that is called on new available data. - void _printHex4(uint16_t val); ///> Prints a register as 4 character hexadecimal code with leading zeros. + void _printHex2(uint8_t val); ///< Prints a byte as 2 character hexadecimal code with leading zeros. + void _printHex4(uint16_t val); ///< Prints a register as 4 character hexadecimal code with leading zeros. private: void int16_to_s(char *s, uint16_t val); ///< Converts a int16 number to a string, similar to itoa, but using the format "00000".