From 5b15afc19a0d9cedbc291e6ab234e71d57992e34 Mon Sep 17 00:00:00 2001 From: Nick Poole Date: Mon, 2 Dec 2019 17:13:27 -0700 Subject: [PATCH 01/17] Begin Si4721 Implementation --- src/SI4721.cpp | 613 +++++++++++++++++++++++++++++++++++++++++++++++++ src/SI4721.h | 148 ++++++++++++ 2 files changed, 761 insertions(+) create mode 100644 src/SI4721.cpp create mode 100644 src/SI4721.h diff --git a/src/SI4721.cpp b/src/SI4721.cpp new file mode 100644 index 0000000..1be1bd7 --- /dev/null +++ b/src/SI4721.cpp @@ -0,0 +1,613 @@ +/// +/// \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 + +// ----- Definitions for the Wire communication + +#define SI4721_ADR 0x63 ///< The I2C address of SI4721 is 0x61 or 0x63 + +/// Uncomment this definition when not using the ELV radio board. +/// There is a special mute implementation. +#define ELVRADIO + +// ----- 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_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_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 + +#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_ASQ_STATUS 0x34 +#define CMD_TX_RDS_BUFF 0x35 +#define CMD_TX_RDS_PS 0x36 + +#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 0x01 +#define PROP_GPO_IEN_RDSIEN 0x04 + +// Deemphasis time constant. +#define PROP_FM_DEEMPHASIS 0x1100 +#define PROP_FM_DEEMPHASIS_50 0x01 + +// 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 + +// 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_AUDIO_DEVIATION 0x2101 +#define PROP_TX_PILOT_DEVIATION 0x2102 +#define PROP_TX_RDS_DEVIATION 0x2103 +#define PROP_TX_LINE_LEVEL_INPUT_LEVEL 0x2104 +#define PROP_TX_LINE_INPUT_MUTE 0x2105 +#define PROP_TX_PREEMPHASIS 0x2106 +#define PROP_TX_PILOT_FREQUENCY 0x2107 +#define PROP_TX_ACOMP_ENABLE 0x2200 +#define PROP_TX_ACOMP_THRESHOLD 0x2201 +#define PROP_TX_ATTACK_TIME 0x2202 +#define PROP_TX_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_AQS_LEVEL_HIGH 0x2303 +#define PROP_TX_AQS_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 + +/// Initialize the extra variables in SI4721 +SI4721::SI4721() { + _realVolume = 0; +} + +/// 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() { + bool result = false; // no chip found yet. + DEBUG_FUNC0("init"); + + // Now that the unit is reset and I2C inteface mode, we need to begin I2C + Wire.begin(); + + // powering up is done by specifying the band etc. so it's implemented in setBand + setBand(RADIO_BAND_FM); + + // set some common properties + _setProperty(PROP_FM_ANTENNA_INPUT, PROP_FM_ANTENNA_INPUT_SHORT); + +#if defined(ELVRADIO) + // enable GPO1 output for mute function + _sendCommand(2, CMD_GPIO_CTL, CMD_GPIO_CTL_GPO1OEN); +#endif + + // set volume to 0 and mute so no noise gets out here. + _setProperty(PROP_RX_VOLUME, 0); + + // Set mute bits in the fm receiver + setMute(true); + setSoftMute(true); + + // adjust sensibility for scanning + _setProperty(FM_SEEK_TUNE_SNR_THRESHOLD, 12); + _setProperty(FM_SEEK_TUNE_RSSI_TRESHOLD, 42); + + _setProperty(PROP_GPO_IEN, PROP_GPO_IEN_STCIEN); // | PROP_GPO_IEN_RDSIEN ???? + + // 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 + + 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) +{ + 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); + +#if defined(ELVRADIO) + // mute the ELV board by using GPO1 + _sendCommand(2, CMD_GPIO_SET, CMD_GPIO_SET_GPO1LEVEL); +#endif + + } else { + // clear mute bits in the fm receiver + _setProperty(PROP_RX_HARD_MUTE, 0x00); + +#if defined(ELVRADIO) + // unmute the ELV board by using GPO1 + _sendCommand(2, CMD_GPIO_SET, 0); +#endif + } // if +} // setMute() + + +/// 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) +{ + 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 (switchOn) { + // disable automatic stereo feature + _setProperty(PROP_FM_BLEND_RSSI_STEREO_THRESHOLD, 127); + _setProperty(PROP_FM_BLEND_RSSI_MONO_THRESHOLD, 127); + + } else { + // Automatic stereo feature on. + _setProperty(PROP_FM_BLEND_RSSI_STEREO_THRESHOLD, 0x0031); // default = 49 + _setProperty(PROP_FM_BLEND_RSSI_MONO_THRESHOLD, 0x001E); // default = 30 + } // if +} // setMono + + +// ----- Band and frequency control methods ----- + +/// Start using the new band for receiving. +/// @param newBand The new band to be received. +/// @return void +void SI4721::setBand(RADIO_BAND newBand) { + if (newBand == RADIO_BAND_FM) { + // set band boundaries and steps + RADIO::setBand(newBand); + + // powering up in FM mode, analog outputs, crystal oscillator, GPO2 enabled for interrupts. + _sendCommand(3, CMD_POWER_UP, (CMD_POWER_UP_1_XOSCEN | CMD_POWER_UP_1_GPO2OEN | 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_FM_DEEMPHASIS, PROP_FM_DEEMPHASIS_50); // for Europe 50 deemphasis + _setProperty(PROP_FM_SEEK_FREQ_SPACING, _freqSteps); // in 100kHz spacing + + } else { + _sendCommand(1, CMD_POWER_DOWN); + + } // if +} // setBand() + + + +/// Retrieve the real frequency from the chip after manual or automatic tuning. +/// @return RADIO_FREQ the current frequency. +RADIO_FREQ SI4721::getFrequency() { + _readStatusData(CMD_FM_TUNE_STATUS, 0x03, tuneStatus, sizeof(tuneStatus)); + _freq = (tuneStatus[2] << 8) + tuneStatus[3]; + return (_freq); +} // getFrequency + + +/// Start using the new frequency for receiving.\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. +/// @return void +void SI4721::setFrequency(RADIO_FREQ newF) { + uint8_t status; + + RADIO::setFrequency(newF); + _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)); +} // 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 value; + + Wire.beginTransmission(SI4721_ADR); + Wire.write(CMD_GET_INT_STATUS); + Wire.endTransmission(); + + Wire.requestFrom(SI4721_ADR, 1); // We want to read 1 byte only. + value = Wire.read(); + + return(value); +} // _readStatus() + + +/// Load status information from to the chip. +void SI4721::_readStatusData(uint8_t cmd, uint8_t param, uint8_t *values, uint8_t len) +{ + Wire.beginTransmission(SI4721_ADR); + Wire.write(cmd); + Wire.write(param); + + Wire.endTransmission(); + Wire.requestFrom(SI4721_ADR, (int)len); //We want to read some bytes. + + for (uint8_t n = 0; n < len; n++) { + //Read in these bytes + values[n] = Wire.read(); + } // for +} // _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() + + +/// Retrieve the next RDS data if available. +void SI4721::checkRDS() +{ + if (_sendRDS) { + // fetch the interrupt status first + uint8_t status = _readStatus(); + + // 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() + + +// ----- 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, ...) { + if (cnt > 8) { + // see AN332: "Writing more than 8 bytes results in unpredictable device behavior." + Serial.println("error: _sendCommand: too much parameters!"); + + } else { + Wire.beginTransmission(SI4721_ADR); + Wire.write(cmd); + + va_list params; + va_start(params, cmd); + + for (uint8_t i = 1; i < cnt; i++) { + uint8_t c = va_arg(params, int); + Wire.write(c); + } + Wire.endTransmission(); + va_end(params); + + // wait for Command being processed + Wire.requestFrom(SI4721_ADR, 1); // We want to read the status byte. + _status = Wire.read(); + } // if + +} // _sendCommand() + + +/// Set a property in the radio chip +void SI4721::_setProperty(uint16_t prop, uint16_t value) { + Wire.beginTransmission(SI4721_ADR); + Wire.write(CMD_SET_PROPERTY); + Wire.write(0); + + Wire.write(prop >> 8); + Wire.write(prop & 0x00FF); + Wire.write(value >> 8); + Wire.write(value & 0x00FF); + + Wire.endTransmission(); + + Wire.requestFrom(SI4721_ADR, 1); // We want to read the status byte. + _status = Wire.read(); +} // _setProperty() + + +// ----- internal functions ----- + +// The End. + + diff --git a/src/SI4721.h b/src/SI4721.h new file mode 100644 index 0000000..e788748 --- /dev/null +++ b/src/SI4721.h @@ -0,0 +1,148 @@ +/// +/// \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. +/// +/// More documentation and source code is available at http://www.mathertel.de/Arduino +/// +/// ChangeLog: +/// ---------- +/// * 01.12.2019 created. + + +#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 + +// ----- 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(); + + bool init(); ///< Initialize the library and the chip. + void term(); ///< Terminate all radio functions in the chip. + + // ----- 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); ///< Control the mute mode of the radio chip. + void setSoftMute(bool switchOn); ///< Control the softmute mode (mute on low signals) of the radio chip. + + // Overwrite audio functions that are not supported. + void setBassBoost(bool switchOn); ///< 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. + + void setBand(RADIO_BAND newBand); ///< Control the band of the radio chip. + + 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 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 + +private: + // ----- local variables + + uint8_t _realVolume; ///< The real volume set to the chip. + + // 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]; + + /// 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(); +}; + +#endif From f8c3ea3d6a9fe0b020c1cc6495f9210b2baf5bac Mon Sep 17 00:00:00 2001 From: Nick Poole Date: Thu, 5 Dec 2019 16:22:57 -0700 Subject: [PATCH 02/17] SI4721 Implementation Adds Tx funcs to SI4721.cpp Adds setMode() func Modifies setFrequency() Updates Library version Next commit will be documentation, examples and testing. --- examples/TestSI4721/TestSI4721.ino | 86 +++++++++++ examples/TransmitSI4721/TransmitSI4721.ino | 87 +++++++++++ keywords.txt | 11 ++ library.properties | 2 +- src/SI4721.cpp | 165 ++++++++++++++++++++- src/SI4721.h | 32 ++++ 6 files changed, 375 insertions(+), 8 deletions(-) create mode 100644 examples/TestSI4721/TestSI4721.ino create mode 100644 examples/TransmitSI4721/TransmitSI4721.ino diff --git a/examples/TestSI4721/TestSI4721.ino b/examples/TestSI4721/TestSI4721.ino new file mode 100644 index 0000000..ef1390a --- /dev/null +++ b/examples/TestSI4721/TestSI4721.ino @@ -0,0 +1,86 @@ +/// +/// \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 style 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 57600 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 signal | +/// | --------------- | -------------------| +/// | 3.3V | VCC | +/// | GND | GND | +/// | A5 or SCL | SCLK | +/// | A4 or SDA | SDIO | +/// | | RST (not used) | +/// +/// 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 4 ///< 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() { + // open the Serial port + Serial.begin(57600); + Serial.println("Radio..."); + delay(200); + + // Initialize the Radio + // SET_FM_DEEMPHASIS = 75; // Un-comment this line in the USA + radio.init(); + + // Enable information to the Serial port + radio.debugEnable(); + + // Set all radio setting to the fixed values. + radio.setBandFrequency(FIX_BAND, FIX_STATION); + radio.setVolume(FIX_VOLUME); + radio.setMono(false); + 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..968d91a --- /dev/null +++ b/examples/TransmitSI4721/TransmitSI4721.ino @@ -0,0 +1,87 @@ +/// +/// \file TestSI4705.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 style 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 57600 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 signal | +/// | --------------- | -------------------| +/// | 3.3V | VCC | +/// | GND | GND | +/// | A5 or SCL | SCLK | +/// | A4 or SDA | SDIO | +/// | | RST (not used yet) | +/// The locations of the pins on the UNO board are written on the PCB. +/// The locations of the signals on the SI4705 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. + +#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 4 ///< The volume that will be set by this sketch is level 4. + +SI4705 radio; // Create an instance of Class for SI4705 Chip + +/// Setup a FM only radio configuration +/// with some debugging on the Serial port +void setup() { + // open the Serial port + Serial.begin(57600); + Serial.println("Radio..."); + delay(200); + + // Initialize the Radio + radio.init(); + + // Enable information to the Serial port + radio.debugEnable(); + + // Set all radio setting to the fixed values. + radio.setBandFrequency(FIX_BAND, FIX_STATION); + radio.setVolume(FIX_VOLUME); + radio.setMono(false); + 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/keywords.txt b/keywords.txt index 5e29d46..ee60685 100644 --- a/keywords.txt +++ b/keywords.txt @@ -59,6 +59,14 @@ attachReceiveRDS KEYWORD2 formatFrequency KEYWORD2 +setMode KEYWORD2 +beginRDS KEYWORD2 +setRDSstation KEYWORD2 +setRDSbuffer KEYWORD2 + +getASQ +getTuneStatus + ####################################### # Instances (KEYWORD2) ####################################### @@ -67,3 +75,6 @@ formatFrequency KEYWORD2 ####################################### # Constants (LITERAL1) ####################################### + +RX LITERAL1 +TX LITERAL1 \ No newline at end of file diff --git a/library.properties b/library.properties index c0e6e87..9f5272f 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Radio -version=1.2.0 +version=1.3.0 author=Matthias Hertel maintainer=Matthias Hertel, sentence=Library for controlling FM radio receiver chips. diff --git a/src/SI4721.cpp b/src/SI4721.cpp index 1be1bd7..60f3e37 100644 --- a/src/SI4721.cpp +++ b/src/SI4721.cpp @@ -91,6 +91,7 @@ // Deemphasis time constant. #define PROP_FM_DEEMPHASIS 0x1100 #define PROP_FM_DEEMPHASIS_50 0x01 +#define PROP_FM_DEEMPHASIS_75 0x02 // setup the antenna input pin #define PROP_FM_ANTENNA_INPUT 0x1107 @@ -139,7 +140,6 @@ #define PROP_TX_RDS_DEVIATION 0x2103 #define PROP_TX_LINE_LEVEL_INPUT_LEVEL 0x2104 #define PROP_TX_LINE_INPUT_MUTE 0x2105 -#define PROP_TX_PREEMPHASIS 0x2106 #define PROP_TX_PILOT_FREQUENCY 0x2107 #define PROP_TX_ACOMP_ENABLE 0x2200 #define PROP_TX_ACOMP_THRESHOLD 0x2201 @@ -161,6 +161,15 @@ #define PROP_TX_RDS_PS_AF 0x2C06 #define PROP_TX_RDS_FIFO_SIZE 0x2C07 +// Preemphasis time constant. +#define PROP_TX_PREEMPHASIS 0x2106 +#define PROP_TX_PREEMPHASIS_50 0x01 +#define PROP_TX_PREEMPHASIS_75 0x00 + +// Mode Keywords (for convenience) +#define RX false +#define TX true + /// Initialize the extra variables in SI4721 SI4721::SI4721() { _realVolume = 0; @@ -173,7 +182,7 @@ bool SI4721::init() { bool result = false; // no chip found yet. DEBUG_FUNC0("init"); - // Now that the unit is reset and I2C inteface mode, we need to begin I2C + // Now that the unit is reset and I2C interface mode, we need to begin I2C Wire.begin(); // powering up is done by specifying the band etc. so it's implemented in setBand @@ -273,6 +282,17 @@ void SI4721::setMute(bool switchOn) { } // 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 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. @@ -333,7 +353,7 @@ void SI4721::setBand(RADIO_BAND newBand) { _sendCommand(3, CMD_POWER_UP, (CMD_POWER_UP_1_XOSCEN | CMD_POWER_UP_1_GPO2OEN | 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_FM_DEEMPHASIS, PROP_FM_DEEMPHASIS_50); // for Europe 50 deemphasis + _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 } else { @@ -347,23 +367,32 @@ void SI4721::setBand(RADIO_BAND newBand) { /// Retrieve the real frequency from the chip after manual or automatic tuning. /// @return RADIO_FREQ the current frequency. RADIO_FREQ SI4721::getFrequency() { - _readStatusData(CMD_FM_TUNE_STATUS, 0x03, tuneStatus, sizeof(tuneStatus)); + + if(_transmitMode){ + _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 -/// Start using the new frequency for receiving.\n +/// 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. +/// @param newF The new frequency to be received/transmitted. /// @return void void SI4721::setFrequency(RADIO_FREQ newF) { uint8_t status; RADIO::setFrequency(newF); - _sendCommand(5, CMD_FM_TUNE_FREQ, 0, (newF >> 8) & 0xff, (newF)& 0xff, 0); + _sendCommand(5, _transmitMode ? CMD_TX_TUNE_FREQ : CMD_FM_TUNE_FREQ, 0, (newF >> 8) & 0xff, (newF)& 0xff, 0); // reset the RDSParser clearRDS(); @@ -503,6 +532,128 @@ void SI4721::checkRDS() } // if _sendRDS } // checkRDS() +// ----- Transmitter functions ----- + +/// Change device mode between transmit and receive +void SI4721::setMode(bool transmit) { + + // Power down the device + _sendCommand(1, CMD_POWER_DOWN); + + // Power up in selected mode + if(transmit){ + _sendCommand(3, CMD_POWER_UP, 0x12, 0x50); + _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 on limiter and AGC + }else{ + _sendCommand(3, CMD_POWER_UP, 0x10, 0x05); + _setProperty(PROP_REFCLK_FREQ, 32768); // crystal is 32.768 + _setProperty(PROP_FM_DEEMPHASIS, _fmDeemphasis == 75 ? PROP_FM_DEEMPHASIS_75 : PROP_FM_DEEMPHASIS_50); // set the RX deemphasis + _setProperty(PROP_FM_ANTENNA_INPUT, 1); // sets antenna input + } + + _transmitMode = transmit; + +} + +/// Set the output power of the device in dBµV (valid range is 88 to 115) +void SI4721::setTXpower(uint8_t pwr) { + _sendCommand(5, CMD_TX_TUNE_POWER, 0, 0, pwr, 0); +} + +/// Begin Broadcasting RDS and optionally set Program ID +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 +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 Group Buffer +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 +ASQ_STATUS SI4721::getASQ() { + _sendCommand(2, CMD_TX_ASQ_STATUS, 0x1); + + Wire.requestFrom((uint8_t)_i2caddr, (uint8_t)5); + + ASQ_STATUS result; + + uint8_t response[5]; + for(uint8_t i = 0; i < 5; i++){ + response[i] = Wire.read(); + } + + result.asq = response[1]; + result.audioInLevel = response[4]; + + return result; + +} + +/// Get TX Tuning Status +TX_STATUS SI4721::getTuneStatus() { + _sendCommand(2, CMD_TX_TUNE_STATUS, 0x1); + + Wire.requestFrom((uint8_t)_i2caddr, (uint8_t)8); + + TX_STATUS result; + + uint8_t response[8]; + for(uint8_t i = 0; i < 8; i++){ + response[i] = Wire.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 ----- diff --git a/src/SI4721.h b/src/SI4721.h index e788748..b7aed25 100644 --- a/src/SI4721.h +++ b/src/SI4721.h @@ -28,6 +28,20 @@ // Include the radio library that is extended by the SI4721 library. #include +// 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. @@ -75,11 +89,29 @@ class SI4721 : public RADIO { void debugScan(); // Scan all frequencies and report a status void debugStatus(); // Report Info about actual Station + + // ----- transmit functions + + void setMode(bool transmit = false); + void beginRDS(uint16_t programID = 0xBEEF); + void setRDSstation(char *s); + void setRDSbuffer(char *s); + + 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. + + bool _transmitMode = false; ///< Remember which mode we're in + + 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 From 1eae4196531c9a26168193b080ae2c5fb1587e98 Mon Sep 17 00:00:00 2001 From: Nick Poole Date: Tue, 10 Dec 2019 16:24:27 -0700 Subject: [PATCH 03/17] v1.3.0 release --- keywords.txt | 8 +- library.properties | 2 +- src/RDSParser.h | 2 +- src/SI4721.cpp | 180 +++++++++++++++++++++++++++------------------ src/SI4721.h | 12 ++- 5 files changed, 122 insertions(+), 82 deletions(-) diff --git a/keywords.txt b/keywords.txt index ee60685..d120700 100644 --- a/keywords.txt +++ b/keywords.txt @@ -59,8 +59,9 @@ attachReceiveRDS KEYWORD2 formatFrequency KEYWORD2 -setMode KEYWORD2 -beginRDS KEYWORD2 +setModeReceive KEYWORD2 +setModeTransmit KEYWORD2 +beginRDS KEYWORD2 setRDSstation KEYWORD2 setRDSbuffer KEYWORD2 @@ -75,6 +76,3 @@ getTuneStatus ####################################### # Constants (LITERAL1) ####################################### - -RX LITERAL1 -TX LITERAL1 \ No newline at end of file diff --git a/library.properties b/library.properties index 9f5272f..c3eb503 100644 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ version=1.3.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/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/SI4721.cpp b/src/SI4721.cpp index 60f3e37..5097927 100644 --- a/src/SI4721.cpp +++ b/src/SI4721.cpp @@ -22,10 +22,6 @@ #include // Include the common radio library interface #include -// ----- Definitions for the Wire communication - -#define SI4721_ADR 0x63 ///< The I2C address of SI4721 is 0x61 or 0x63 - /// Uncomment this definition when not using the ELV radio board. /// There is a special mute implementation. #define ELVRADIO @@ -166,10 +162,6 @@ #define PROP_TX_PREEMPHASIS_50 0x01 #define PROP_TX_PREEMPHASIS_75 0x00 -// Mode Keywords (for convenience) -#define RX false -#define TX true - /// Initialize the extra variables in SI4721 SI4721::SI4721() { _realVolume = 0; @@ -178,12 +170,14 @@ SI4721::SI4721() { /// 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() { +bool SI4721::init(TwoWire &wirePort, uint8_t deviceAddress) { bool result = false; // no chip found yet. DEBUG_FUNC0("init"); // Now that the unit is reset and I2C interface mode, we need to begin I2C - Wire.begin(); + _i2cPort = &wirePort; + _i2cPort->begin(); + _i2caddr = deviceAddress; // powering up is done by specifying the band etc. so it's implemented in setBand setBand(RADIO_BAND_FM); @@ -196,13 +190,6 @@ bool SI4721::init() { _sendCommand(2, CMD_GPIO_CTL, CMD_GPIO_CTL_GPO1OEN); #endif - // set volume to 0 and mute so no noise gets out here. - _setProperty(PROP_RX_VOLUME, 0); - - // Set mute bits in the fm receiver - setMute(true); - setSoftMute(true); - // adjust sensibility for scanning _setProperty(FM_SEEK_TUNE_SNR_THRESHOLD, 12); _setProperty(FM_SEEK_TUNE_RSSI_TRESHOLD, 42); @@ -287,10 +274,9 @@ void SI4721::setMute(bool switchOn) { /// This number is also used as the pre-emphasis in transmit mode /// @param uS The new deemphasis value in µS /// @return void -void setDeemphasis(uint8_t uS) { - +void SI4721::setDeemphasis(uint8_t uS) +{ _fmDeemphasis = uS; - } /// Control the softmute mode of the radio chip @@ -392,7 +378,12 @@ void SI4721::setFrequency(RADIO_FREQ newF) { uint8_t status; RADIO::setFrequency(newF); - _sendCommand(5, _transmitMode ? CMD_TX_TUNE_FREQ : CMD_FM_TUNE_FREQ, 0, (newF >> 8) & 0xff, (newF)& 0xff, 0); + + if(_transmitMode){ + _sendCommand(4, CMD_TX_TUNE_FREQ, 0, (newF >> 8) & 0xff, (newF) & 0xff); + }else{ + _sendCommand(5, CMD_FM_TUNE_FREQ, 0, (newF >> 8) & 0xff, (newF)& 0xff, 0); + } // reset the RDSParser clearRDS(); @@ -456,12 +447,12 @@ uint8_t SI4721::_readStatus() { uint8_t value; - Wire.beginTransmission(SI4721_ADR); - Wire.write(CMD_GET_INT_STATUS); - Wire.endTransmission(); + _i2cPort->beginTransmission(_i2caddr); + _i2cPort->write(CMD_GET_INT_STATUS); + _i2cPort->endTransmission(); - Wire.requestFrom(SI4721_ADR, 1); // We want to read 1 byte only. - value = Wire.read(); + _i2cPort->requestFrom(_i2caddr, 1); // We want to read 1 byte only. + value = _i2cPort->read(); return(value); } // _readStatus() @@ -470,16 +461,16 @@ uint8_t SI4721::_readStatus() /// Load status information from to the chip. void SI4721::_readStatusData(uint8_t cmd, uint8_t param, uint8_t *values, uint8_t len) { - Wire.beginTransmission(SI4721_ADR); - Wire.write(cmd); - Wire.write(param); + _i2cPort->beginTransmission(_i2caddr); + _i2cPort->write(cmd); + _i2cPort->write(param); - Wire.endTransmission(); - Wire.requestFrom(SI4721_ADR, (int)len); //We want to read some bytes. + _i2cPort->endTransmission(); + _i2cPort->requestFrom(_i2caddr, (int)len); //We want to read some bytes. for (uint8_t n = 0; n < len; n++) { //Read in these bytes - values[n] = Wire.read(); + values[n] = _i2cPort->read(); } // for } // _readStatusData() @@ -534,36 +525,68 @@ void SI4721::checkRDS() // ----- Transmitter functions ----- -/// Change device mode between transmit and receive -void SI4721::setMode(bool transmit) { +/// Change device mode to transmit by powering down and \n +/// then back up into the correct mode. Store the current mode in a local \n +/// variable for setFrequency() to reference. +/// @return void +void SI4721::setModeTransmit() { // Power down the device _sendCommand(1, CMD_POWER_DOWN); - // Power up in selected mode - if(transmit){ - _sendCommand(3, CMD_POWER_UP, 0x12, 0x50); - _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 on limiter and AGC - }else{ - _sendCommand(3, CMD_POWER_UP, 0x10, 0x05); - _setProperty(PROP_REFCLK_FREQ, 32768); // crystal is 32.768 - _setProperty(PROP_FM_DEEMPHASIS, _fmDeemphasis == 75 ? PROP_FM_DEEMPHASIS_75 : PROP_FM_DEEMPHASIS_50); // set the RX deemphasis - _setProperty(PROP_FM_ANTENNA_INPUT, 1); // sets antenna input - } + // Give the device some time to power down before restart + delay(500); + + // Power up in transmit mode + _sendCommand(3, CMD_POWER_UP, 0x12, 0x50); + // 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 on limiter and AGC - _transmitMode = transmit; + _transmitMode = true; } -/// Set the output power of the device in dBµV (valid range is 88 to 115) +/// Change device mode to receive by powering down and \n +/// then back up into the correct mode. Store the current mode in a local \n +/// variable for setFrequency() to reference. +/// @return void +void SI4721::setModeReceive() { + + // Power down the device + _sendCommand(1, CMD_POWER_DOWN); + + // Give the device some time to power down before restart + delay(500); + + // Power up in receive mode + _sendCommand(3, CMD_POWER_UP, 0x10, 0x05); + // 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); // set the RX deemphasis + _setProperty(PROP_FM_ANTENNA_INPUT, 1); // sets antenna input + + _transmitMode = false; + +} + +/// 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) { _sendCommand(5, CMD_TX_TUNE_POWER, 0, 0, pwr, 0); } -/// Begin Broadcasting RDS and optionally set Program ID +/// 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) @@ -580,7 +603,12 @@ void SI4721::beginRDS(uint16_t programID) { } -/// Set the RDS station name string +/// 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; @@ -594,7 +622,9 @@ void SI4721::setRDSstation(char *s) { } -/// Load new data into RDS Group Buffer +/// 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; @@ -607,21 +637,23 @@ void SI4721::setRDSbuffer(char *s) { _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 + _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); - Wire.requestFrom((uint8_t)_i2caddr, (uint8_t)5); + _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] = Wire.read(); + response[i] = _i2cPort->read(); } result.asq = response[1]; @@ -632,16 +664,18 @@ ASQ_STATUS SI4721::getASQ() { } /// 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); - Wire.requestFrom((uint8_t)_i2caddr, (uint8_t)8); + _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] = Wire.read(); + response[i] = _i2cPort->read(); } result.frequency = response[2]; @@ -718,22 +752,22 @@ void SI4721::_sendCommand(int cnt, int cmd, ...) { Serial.println("error: _sendCommand: too much parameters!"); } else { - Wire.beginTransmission(SI4721_ADR); - Wire.write(cmd); + _i2cPort->beginTransmission(_i2caddr); + _i2cPort->write(cmd); va_list params; va_start(params, cmd); for (uint8_t i = 1; i < cnt; i++) { uint8_t c = va_arg(params, int); - Wire.write(c); + _i2cPort->write(c); } - Wire.endTransmission(); + _i2cPort->endTransmission(); va_end(params); // wait for Command being processed - Wire.requestFrom(SI4721_ADR, 1); // We want to read the status byte. - _status = Wire.read(); + _i2cPort->requestFrom(_i2caddr, 1); // We want to read the status byte. + _status = _i2cPort->read(); } // if } // _sendCommand() @@ -741,19 +775,19 @@ void SI4721::_sendCommand(int cnt, int cmd, ...) { /// Set a property in the radio chip void SI4721::_setProperty(uint16_t prop, uint16_t value) { - Wire.beginTransmission(SI4721_ADR); - Wire.write(CMD_SET_PROPERTY); - Wire.write(0); + _i2cPort->beginTransmission(_i2caddr); + _i2cPort->write(CMD_SET_PROPERTY); + _i2cPort->write(0); - Wire.write(prop >> 8); - Wire.write(prop & 0x00FF); - Wire.write(value >> 8); - Wire.write(value & 0x00FF); + _i2cPort->write(prop >> 8); + _i2cPort->write(prop & 0x00FF); + _i2cPort->write(value >> 8); + _i2cPort->write(value & 0x00FF); - Wire.endTransmission(); + _i2cPort->endTransmission(); - Wire.requestFrom(SI4721_ADR, 1); // We want to read the status byte. - _status = Wire.read(); + _i2cPort->requestFrom(_i2caddr, 1); // We want to read the status byte. + _status = _i2cPort->read(); } // _setProperty() diff --git a/src/SI4721.h b/src/SI4721.h index b7aed25..9302d69 100644 --- a/src/SI4721.h +++ b/src/SI4721.h @@ -20,6 +20,8 @@ #ifndef SI4721_h #define SI4721_h +#define SI4721_ADR 0x63 ///< The I2C address of SI4721 is 0x61 or 0x63 + #include // The wire library is used for the communication with the radio chip. @@ -52,7 +54,7 @@ class SI4721 : public RADIO { SI4721(); - bool init(); ///< Initialize the library and the chip. + bool init(TwoWire &wirePort = Wire, uint8_t deviceAddress = SI4721_ADR); ///< Initialize the library and the chip. void term(); ///< Terminate all radio functions in the chip. // ----- Audio functions ----- @@ -92,10 +94,12 @@ class SI4721 : public RADIO { // ----- transmit functions - void setMode(bool transmit = false); + void setModeReceive(); + void setModeTransmit(); void beginRDS(uint16_t programID = 0xBEEF); void setRDSstation(char *s); void setRDSbuffer(char *s); + void setTXpower(uint8_t pwr); ASQ_STATUS getASQ(); TX_STATUS getTuneStatus(); @@ -175,6 +179,10 @@ class SI4721 : public RADIO { void _seek(bool seekUp = true); void _waitEnd(); + + TwoWire *_i2cPort; + uint8_t _i2caddr; + }; #endif From 787a1f91883cc21f2e0102c713347a670ec42c4d Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Thu, 17 Sep 2020 13:18:52 +0200 Subject: [PATCH 04/17] Wire Utilities added --- src/radio.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++++++----- src/radio.h | 23 ++++++++++++------ 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/radio.cpp b/src/radio.cpp index e95076c..dc47b41 100644 --- a/src/radio.cpp +++ b/src/radio.cpp @@ -17,14 +17,17 @@ /// /// 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 ----- +#define DUMP_I2C 1 + // no chip-registers without a chip. @@ -132,8 +135,7 @@ void RADIO::setBand(RADIO_BAND newBand) { _freqHigh = 10800; _freqSteps = 10; - } - else if (newBand == RADIO_BAND_FMWORLD) { + } else if (newBand == RADIO_BAND_FMWORLD) { _freqLow = 7600; _freqHigh = 10800; _freqSteps = 10; @@ -227,7 +229,9 @@ 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"); @@ -298,6 +302,55 @@ void RADIO::int16_to_s(char *s, uint16_t val) { } // int16_to_s() +// ===== Wire Utilities ===== + +bool RADIO::_wireExists(TwoWire *port, int address) +{ + port->beginTransmission(address); + uint8_t err = port->endTransmission(); +#if DUMP_I2C + Serial.printf("_wireExists 0x%02x: %d\n", address, err); +#endif + return (err == 0); +} + +/** read a sequence of register values into a buffer. + * @return number of register values read. + */ +int RADIO::_wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int len) +{ + int done = 0; + +#if DUMP_I2C + Serial.printf("_wireRead 0x%02x:", reg); +#endif + + if (data) { + uint8_t *d = data; + + port->beginTransmission(address); + port->write(reg); + port->endTransmission(); + + port->requestFrom(address, len); + while (port->available() && (done < len)) { + *d = port->read(); + done++; +#if DUMP_I2C + Serial.printf(" %02x", *d); +#endif + d++; + } + +#if DUMP_I2C + Serial.println(); +#endif + } + + return (done); +} // _wireRead() + + /// Prints a register as 4 character hexadecimal code with leading zeros. void RADIO::_printHex4(uint16_t val) { diff --git a/src/radio.h b/src/radio.h index 455e549..0ad0bef 100644 --- a/src/radio.h +++ b/src/radio.h @@ -27,7 +27,8 @@ /// * 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 @@ -35,9 +36,10 @@ /// \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. }; @@ -203,6 +205,13 @@ class RADIO { virtual void debugAudioInfo(); ///< Print out all audio information. virtual void debugStatus(); ///< Send debug information about actual available chip functionality and other internal things. + // ===== Wire Utilities ===== + + /** check for a device on address */ + static bool _wireExists(TwoWire *port, int address); + + static int _wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int len); + protected: bool _debugEnabled; ///< Set by debugEnable() and controls debugging functionality. From f60bc9983d2252f1dda5619cf8ff608ad5bc4592 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Thu, 17 Sep 2020 13:20:58 +0200 Subject: [PATCH 05/17] move si4721 specific initialization into setBand --- src/SI4721.cpp | 1557 +++++++++++++++++++++++------------------------- src/SI4721.h | 378 ++++++------ 2 files changed, 949 insertions(+), 986 deletions(-) diff --git a/src/SI4721.cpp b/src/SI4721.cpp index 5097927..3f828f8 100644 --- a/src/SI4721.cpp +++ b/src/SI4721.cpp @@ -1,798 +1,759 @@ -/// -/// \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 - -/// Uncomment this definition when not using the ELV radio board. -/// There is a special mute implementation. -#define ELVRADIO - -// ----- 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_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_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 - -#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_ASQ_STATUS 0x34 -#define CMD_TX_RDS_BUFF 0x35 -#define CMD_TX_RDS_PS 0x36 - -#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 0x01 -#define PROP_GPO_IEN_RDSIEN 0x04 - -// Deemphasis time constant. -#define PROP_FM_DEEMPHASIS 0x1100 -#define PROP_FM_DEEMPHASIS_50 0x01 -#define PROP_FM_DEEMPHASIS_75 0x02 - -// 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 - -// 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_AUDIO_DEVIATION 0x2101 -#define PROP_TX_PILOT_DEVIATION 0x2102 -#define PROP_TX_RDS_DEVIATION 0x2103 -#define PROP_TX_LINE_LEVEL_INPUT_LEVEL 0x2104 -#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_ATTACK_TIME 0x2202 -#define PROP_TX_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_AQS_LEVEL_HIGH 0x2303 -#define PROP_TX_AQS_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 - -// 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; -} - -/// 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 = false; // no chip found yet. - DEBUG_FUNC0("init"); - - // Now that the unit is reset and I2C interface mode, we need to begin I2C - _i2cPort = &wirePort; - _i2cPort->begin(); - _i2caddr = deviceAddress; - - // powering up is done by specifying the band etc. so it's implemented in setBand - setBand(RADIO_BAND_FM); - - // set some common properties - _setProperty(PROP_FM_ANTENNA_INPUT, PROP_FM_ANTENNA_INPUT_SHORT); - -#if defined(ELVRADIO) - // enable GPO1 output for mute function - _sendCommand(2, CMD_GPIO_CTL, CMD_GPIO_CTL_GPO1OEN); -#endif - - // adjust sensibility for scanning - _setProperty(FM_SEEK_TUNE_SNR_THRESHOLD, 12); - _setProperty(FM_SEEK_TUNE_RSSI_TRESHOLD, 42); - - _setProperty(PROP_GPO_IEN, PROP_GPO_IEN_STCIEN); // | PROP_GPO_IEN_RDSIEN ???? - - // 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 - - 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) -{ - 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); - -#if defined(ELVRADIO) - // mute the ELV board by using GPO1 - _sendCommand(2, CMD_GPIO_SET, CMD_GPIO_SET_GPO1LEVEL); -#endif - - } else { - // clear mute bits in the fm receiver - _setProperty(PROP_RX_HARD_MUTE, 0x00); - -#if defined(ELVRADIO) - // unmute the ELV board by using GPO1 - _sendCommand(2, CMD_GPIO_SET, 0); -#endif - } // 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) -{ - 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 (switchOn) { - // disable automatic stereo feature - _setProperty(PROP_FM_BLEND_RSSI_STEREO_THRESHOLD, 127); - _setProperty(PROP_FM_BLEND_RSSI_MONO_THRESHOLD, 127); - - } else { - // Automatic stereo feature on. - _setProperty(PROP_FM_BLEND_RSSI_STEREO_THRESHOLD, 0x0031); // default = 49 - _setProperty(PROP_FM_BLEND_RSSI_MONO_THRESHOLD, 0x001E); // default = 30 - } // if -} // setMono - - -// ----- Band and frequency control methods ----- - -/// Start using the new band for receiving. -/// @param newBand The new band to be received. -/// @return void -void SI4721::setBand(RADIO_BAND newBand) { - if (newBand == RADIO_BAND_FM) { - // set band boundaries and steps - RADIO::setBand(newBand); - - // powering up in FM mode, analog outputs, crystal oscillator, GPO2 enabled for interrupts. - _sendCommand(3, CMD_POWER_UP, (CMD_POWER_UP_1_XOSCEN | CMD_POWER_UP_1_GPO2OEN | 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_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 - - } else { - _sendCommand(1, CMD_POWER_DOWN); - - } // 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(_transmitMode){ - _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(_transmitMode){ - _sendCommand(4, CMD_TX_TUNE_FREQ, 0, (newF >> 8) & 0xff, (newF) & 0xff); - }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)); -} // 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 value; - - _i2cPort->beginTransmission(_i2caddr); - _i2cPort->write(CMD_GET_INT_STATUS); - _i2cPort->endTransmission(); - - _i2cPort->requestFrom(_i2caddr, 1); // We want to read 1 byte only. - value = _i2cPort->read(); - - return(value); -} // _readStatus() - - -/// Load status information from to the chip. -void SI4721::_readStatusData(uint8_t cmd, uint8_t param, uint8_t *values, uint8_t len) -{ - _i2cPort->beginTransmission(_i2caddr); - _i2cPort->write(cmd); - _i2cPort->write(param); - - _i2cPort->endTransmission(); - _i2cPort->requestFrom(_i2caddr, (int)len); //We want to read some bytes. - - for (uint8_t n = 0; n < len; n++) { - //Read in these bytes - values[n] = _i2cPort->read(); - } // for -} // _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() - - -/// Retrieve the next RDS data if available. -void SI4721::checkRDS() -{ - if (_sendRDS) { - // fetch the interrupt status first - uint8_t status = _readStatus(); - - // 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 ----- - -/// Change device mode to transmit by powering down and \n -/// then back up into the correct mode. Store the current mode in a local \n -/// variable for setFrequency() to reference. -/// @return void -void SI4721::setModeTransmit() { - - // Power down the device - _sendCommand(1, CMD_POWER_DOWN); - - // Give the device some time to power down before restart - delay(500); - - // Power up in transmit mode - _sendCommand(3, CMD_POWER_UP, 0x12, 0x50); - // 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 on limiter and AGC - - _transmitMode = true; - -} - -/// Change device mode to receive by powering down and \n -/// then back up into the correct mode. Store the current mode in a local \n -/// variable for setFrequency() to reference. -/// @return void -void SI4721::setModeReceive() { - - // Power down the device - _sendCommand(1, CMD_POWER_DOWN); - - // Give the device some time to power down before restart - delay(500); - - // Power up in receive mode - _sendCommand(3, CMD_POWER_UP, 0x10, 0x05); - // 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); // set the RX deemphasis - _setProperty(PROP_FM_ANTENNA_INPUT, 1); // sets antenna input - - _transmitMode = false; - -} - -/// 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) { - _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, ...) { - if (cnt > 8) { - // see AN332: "Writing more than 8 bytes results in unpredictable device behavior." - Serial.println("error: _sendCommand: too much parameters!"); - - } else { - _i2cPort->beginTransmission(_i2caddr); - _i2cPort->write(cmd); - - va_list params; - va_start(params, cmd); - - for (uint8_t i = 1; i < cnt; i++) { - uint8_t c = va_arg(params, int); - _i2cPort->write(c); - } - _i2cPort->endTransmission(); - va_end(params); - - // wait for Command being processed - _i2cPort->requestFrom(_i2caddr, 1); // We want to read the status byte. - _status = _i2cPort->read(); - } // if - -} // _sendCommand() - - -/// Set a property in the radio chip -void SI4721::_setProperty(uint16_t prop, uint16_t value) { - _i2cPort->beginTransmission(_i2caddr); - _i2cPort->write(CMD_SET_PROPERTY); - _i2cPort->write(0); - - _i2cPort->write(prop >> 8); - _i2cPort->write(prop & 0x00FF); - _i2cPort->write(value >> 8); - _i2cPort->write(value & 0x00FF); - - _i2cPort->endTransmission(); - - _i2cPort->requestFrom(_i2caddr, 1); // We want to read the status byte. - _status = _i2cPort->read(); -} // _setProperty() - - -// ----- internal functions ----- - -// The End. - - +/// +/// \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_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_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 + +#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_ASQ_STATUS 0x34 +#define CMD_TX_RDS_BUFF 0x35 +#define CMD_TX_RDS_PS 0x36 + +#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 0x01 +#define PROP_GPO_IEN_RDSIEN 0x04 + +// Deemphasis time constant. +#define PROP_FM_DEEMPHASIS 0x1100 +#define PROP_FM_DEEMPHASIS_50 0x01 +#define PROP_FM_DEEMPHASIS_75 0x02 + +// 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 + +// 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_AUDIO_DEVIATION 0x2101 +#define PROP_TX_PILOT_DEVIATION 0x2102 +#define PROP_TX_RDS_DEVIATION 0x2103 +#define PROP_TX_LINE_LEVEL_INPUT_LEVEL 0x2104 +#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_ATTACK_TIME 0x2202 +#define PROP_TX_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_AQS_LEVEL_HIGH 0x2303 +#define PROP_TX_AQS_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 + +// 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; +} + +/// 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) +{ + 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) +{ + 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 (switchOn) { + // disable automatic stereo feature + _setProperty(PROP_FM_BLEND_RSSI_STEREO_THRESHOLD, 127); + _setProperty(PROP_FM_BLEND_RSSI_MONO_THRESHOLD, 127); + + } else { + // Automatic stereo feature on. + _setProperty(PROP_FM_BLEND_RSSI_STEREO_THRESHOLD, 0x0031); // default = 49 + _setProperty(PROP_FM_BLEND_RSSI_MONO_THRESHOLD, 0x001E); // default = 30 + } // if +} // setMono + + +// ----- Band and frequency control methods ----- + +/** + * Start using the new band for receiving or transmitting. + * @param newBand The new band to be enabled. + * @return void + */ +void SI4721::setBand(RADIO_BAND newBand) +{ + DEBUG_FUNC1("setBand", newBand); + + if (newBand == RADIO_BAND_FM) { + // set band boundaries and steps + RADIO::setBand(newBand); + + // Power down the device + _sendCommand(1, CMD_POWER_DOWN); + + // Give the device some time to power down before restart + delay(500); + + // Power up in receive mode + _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); + + // 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 + + // 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 + + + } else if (newBand == RADIO_BAND_FMTX) { + // Power down the device + _sendCommand(1, CMD_POWER_DOWN); + + // Give the device some time to power down before restart + delay(500); + + // Power up in transmit mode + _sendCommand(3, CMD_POWER_UP, 0x12, 0x50); + // 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 on limiter and AGC + + } else { + _sendCommand(1, CMD_POWER_DOWN); + + } // 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); + } 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)); +} // 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) +{ + _i2cPort->beginTransmission(_i2caddr); + _i2cPort->write(cmd); + _i2cPort->write(param); + + _i2cPort->endTransmission(); + _i2cPort->requestFrom(_i2caddr, (int)len); //We want to read some bytes. + + for (uint8_t n = 0; n < len; n++) { + //Read in these bytes + values[n] = _i2cPort->read(); + } // for +} // _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() + + +/// Retrieve the next RDS data if available. +void SI4721::checkRDS() +{ + if (_sendRDS) { + // fetch the interrupt status first + uint8_t status = _readStatus(); + + // 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 ----- + +/// 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) { + _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, ...) { + if (_debugEnabled) Serial.printf("CMD(%2x):"); + if (cnt > 8) { + // see AN332: "Writing more than 8 bytes results in unpredictable device behavior." + Serial.println("error: _sendCommand: too much parameters!"); + + } else { + _i2cPort->beginTransmission(_i2caddr); + _i2cPort->write(cmd); + + va_list params; + va_start(params, cmd); + + for (uint8_t i = 1; i < cnt; i++) { + uint8_t c = va_arg(params, int); + if (_debugEnabled) Serial.printf("%02x ", c); + _i2cPort->write(c); + } + _i2cPort->endTransmission(); + va_end(params); + + // wait for Command being processed + _i2cPort->requestFrom(_i2caddr, 1); // We want to read the status byte. + _status = _i2cPort->read(); + } // if + + if (_debugEnabled) Serial.println(); +} // _sendCommand() + + +/// Set a property in the radio chip +void SI4721::_setProperty(uint16_t prop, uint16_t value) +{ + if (_debugEnabled) Serial.printf("PROP(%04x): %04x\n", prop, value); + _i2cPort->beginTransmission(_i2caddr); + _i2cPort->write(CMD_SET_PROPERTY); + _i2cPort->write(0); + + _i2cPort->write(prop >> 8); + _i2cPort->write(prop & 0x00FF); + _i2cPort->write(value >> 8); + _i2cPort->write(value & 0x00FF); + + _i2cPort->endTransmission(); + + _i2cPort->requestFrom(_i2caddr, 1); // We want to read the status byte. + _status = _i2cPort->read(); +} // _setProperty() + + +// ----- internal functions ----- + +// The End. diff --git a/src/SI4721.h b/src/SI4721.h index 9302d69..0b1bbf6 100644 --- a/src/SI4721.h +++ b/src/SI4721.h @@ -1,188 +1,190 @@ -/// -/// \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. -/// -/// More documentation and source code is available at http://www.mathertel.de/Arduino -/// -/// ChangeLog: -/// ---------- -/// * 01.12.2019 created. - - -#ifndef SI4721_h -#define SI4721_h - -#define SI4721_ADR 0x63 ///< The I2C address of SI4721 is 0x61 or 0x63 - -#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 - -// 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(); - - bool init(TwoWire &wirePort = Wire, uint8_t deviceAddress = SI4721_ADR); ///< Initialize the library and the chip. - void term(); ///< Terminate all radio functions in the chip. - - // ----- 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); ///< Control the mute mode of the radio chip. - void setSoftMute(bool switchOn); ///< Control the softmute mode (mute on low signals) of the radio chip. - - // Overwrite audio functions that are not supported. - void setBassBoost(bool switchOn); ///< 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. - - void setBand(RADIO_BAND newBand); ///< Control the band of the radio chip. - - 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 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 setModeReceive(); - void setModeTransmit(); - void beginRDS(uint16_t programID = 0xBEEF); - void setRDSstation(char *s); - void setRDSbuffer(char *s); - 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. - - bool _transmitMode = false; ///< Remember which mode we're in - - 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]; - - /// 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; - uint8_t _i2caddr; - -}; - -#endif +/// +/// \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. +/// 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 move si4721 specific initialization 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); ///< Control the mute mode of the radio chip. + void setSoftMute(bool switchOn); ///< Control the softmute mode (mute on low signals) of the radio chip. + + // Overwrite audio functions that are not supported. + void setBassBoost(bool switchOn); ///< 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. + + void setBand(RADIO_BAND newBand); ///< Control the band of the radio chip. + + 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 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); + 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]; + + /// 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 From 0729409f2922934996821f3e05cda5bb934e3917 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Thu, 17 Sep 2020 20:17:04 +0200 Subject: [PATCH 06/17] License format update (no change) --- LICENSE | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) 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 From 1987430fd7cf2a413cbedb9a4e8dde7136087d67 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Thu, 17 Sep 2020 20:41:55 +0200 Subject: [PATCH 07/17] SI4721 fixes. --- src/SI4721.cpp | 222 ++++++++++++++++++++++++++++++++++--------------- src/SI4721.h | 105 ++++++++++++----------- 2 files changed, 210 insertions(+), 117 deletions(-) diff --git a/src/SI4721.cpp b/src/SI4721.cpp index 3f828f8..24e068a 100644 --- a/src/SI4721.cpp +++ b/src/SI4721.cpp @@ -17,9 +17,9 @@ /// ChangeLog see SI4721.h. #include -#include // The chip is controlled via the standard Arduiino Wire library and the IIC/I2C bus. +#include // The chip is controlled via the standard Arduiino Wire library and the IIC/I2C bus. -#include // Include the common radio library interface +#include // Include the common radio library interface // Include the chip specific radio library interface #include @@ -87,6 +87,9 @@ #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 @@ -161,14 +164,16 @@ #define PROP_TX_PREEMPHASIS_75 0x00 /// Initialize the extra variables in SI4721 -SI4721::SI4721() { +SI4721::SI4721() +{ _realVolume = 0; } /// 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 SI4721::init(TwoWire &wirePort, uint8_t deviceAddress) +{ bool result; // chip found ?. DEBUG_FUNC0("init"); @@ -188,7 +193,7 @@ bool SI4721::init(TwoWire &wirePort, uint8_t deviceAddress) { // powering up is done by specifying the band etc. so it's implemented in setBand - return(result); + return (result); } // init() @@ -206,6 +211,7 @@ void SI4721::term() /// @param newVolume The new volume level of audio output. void SI4721::setVolume(uint8_t newVolume) { + DEBUG_FUNC1("setVolume", newVolume); setVolumeX(newVolume * 4); } // setVolume() @@ -214,7 +220,8 @@ void SI4721::setVolume(uint8_t newVolume) /// @param newVolume The new volume level of audio output. void SI4721::setVolumeX(uint8_t newVolume) { - if (newVolume > 63) newVolume = 63; + if (newVolume > 63) + newVolume = 63; _setProperty(PROP_RX_VOLUME, newVolume); _realVolume = newVolume; RADIO::setVolume(newVolume / 4); @@ -223,8 +230,9 @@ void SI4721::setVolumeX(uint8_t newVolume) /// Retrieve the current output volume in the range 0..63. /// @return uint8_t actual volume. -uint8_t SI4721::getVolumeX() { - return(_realVolume); +uint8_t SI4721::getVolumeX() +{ + return (_realVolume); } // getVolumeX() @@ -232,7 +240,8 @@ uint8_t SI4721::getVolumeX() { /// 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) { +void SI4721::setMute(bool switchOn) +{ RADIO::setMute(switchOn); if (switchOn) { @@ -246,7 +255,6 @@ void SI4721::setMute(bool switchOn) { } // 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 @@ -260,7 +268,10 @@ void SI4721::setDeemphasis(uint8_t uS) /// 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) { +void SI4721::setSoftMute(bool switchOn) +{ + DEBUG_FUNC1("setSoftMute", switchOn); + RADIO::setSoftMute(switchOn); if (switchOn) { @@ -278,6 +289,8 @@ void SI4721::setSoftMute(bool switchOn) { /// @return void void SI4721::setBassBoost(bool switchOn) { + DEBUG_FUNC1("setBassBoost", switchOn); + DEBUG_STR("not supported."); RADIO::setBassBoost(false); } // setBassBoost() @@ -288,16 +301,18 @@ void SI4721::setBassBoost(bool switchOn) /// @return void void SI4721::setMono(bool switchOn) { + DEBUG_FUNC1("setMono", switchOn); RADIO::setMono(switchOn); if (switchOn) { // disable automatic stereo feature - _setProperty(PROP_FM_BLEND_RSSI_STEREO_THRESHOLD, 127); - _setProperty(PROP_FM_BLEND_RSSI_MONO_THRESHOLD, 127); + _setProperty(PROP_FM_BLEND_STEREO_THRESHOLD, 127); + // not available in SI4721: + // _setProperty(PROP_FM_BLEND_RSSI_STEREO_THRESHOLD, 127); + // _setProperty(PROP_FM_BLEND_RSSI_MONO_THRESHOLD, 127); } else { // Automatic stereo feature on. - _setProperty(PROP_FM_BLEND_RSSI_STEREO_THRESHOLD, 0x0031); // default = 49 - _setProperty(PROP_FM_BLEND_RSSI_MONO_THRESHOLD, 0x001E); // default = 30 + _setProperty(PROP_FM_BLEND_STEREO_THRESHOLD, 49); // default = 49 dBμV } // if } // setMono @@ -323,7 +338,7 @@ void SI4721::setBand(RADIO_BAND newBand) // Give the device some time to power down before restart delay(500); - // Power up in receive mode + // 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. @@ -334,17 +349,17 @@ void SI4721::setBand(RADIO_BAND newBand) _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 - - // 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 - + // _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) { // Power down the device @@ -369,10 +384,10 @@ void SI4721::setBand(RADIO_BAND newBand) } // setBand() - /// Retrieve the real frequency from the chip after manual or automatic tuning. /// @return RADIO_FREQ the current frequency. -RADIO_FREQ SI4721::getFrequency() { +RADIO_FREQ SI4721::getFrequency() +{ if (_band == RADIO_BAND_FMTX) { _readStatusData(CMD_TX_TUNE_STATUS, 0x01, tuneStatus, sizeof(tuneStatus)); @@ -393,7 +408,8 @@ RADIO_FREQ SI4721::getFrequency() { /// 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) { +void SI4721::setFrequency(RADIO_FREQ newF) +{ uint8_t status; RADIO::setFrequency(newF); @@ -415,7 +431,8 @@ void SI4721::setFrequency(RADIO_FREQ newF) { /// Start seek mode upwards. -void SI4721::seekUp(bool toNextSender) { +void SI4721::seekUp(bool toNextSender) +{ uint8_t status; if (!toNextSender) { @@ -440,7 +457,8 @@ void SI4721::seekUp(bool toNextSender) { /// Start seek mode downwards. -void SI4721::seekDown(bool toNextSender) { +void SI4721::seekDown(bool toNextSender) +{ uint8_t status; if (!toNextSender) { RADIO_FREQ newF = getFrequency() - _freqSteps; @@ -488,49 +506,71 @@ void SI4721::_readStatusData(uint8_t cmd, uint8_t param, uint8_t *values, uint8_ /// Return a filled RADIO_INFO with the status of the radio features of the chip. -void SI4721::getRadioInfo(RADIO_INFO *info) { +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; + if (tuneStatus[1] & 0x01) + info->tuned = true; _readStatusData(CMD_FM_RSQ_STATUS, 0x01, rsqStatus, sizeof(rsqStatus)); - if (rsqStatus[3] & 0x80) info->stereo = true; + 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; + if (rdsStatus.resp2 & 0x01) + info->rds = true; } // getRadioInfo() /// Return a filled AUIO_INFO with the actual audio settings. -void SI4721::getAudioInfo(AUDIO_INFO *info) { +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 interrupt status first - uint8_t status = _readStatus(); + // uint8_t status = _readStatus(); // fetch the current RDS data + // _wireRead(_i2cPort, _i2caddr, ) _readStatusData(CMD_FM_RDS_STATUS, 0x01, rdsStatus.buffer, sizeof(rdsStatus)); + // Serial.print("Status:"); + // Serial.println(rdsStatus.status, HEX); + 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)); + RDSBLOCKWORD(rdsStatus.blockBH, rdsStatus.blockBL), + RDSBLOCKWORD(rdsStatus.blockCH, rdsStatus.blockCL), + RDSBLOCKWORD(rdsStatus.blockDH, rdsStatus.blockDL)); } // if } // if _sendRDS } // checkRDS() @@ -540,7 +580,8 @@ void SI4721::checkRDS() /// 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) { +void SI4721::setTXpower(uint8_t pwr) +{ _sendCommand(5, CMD_TX_TUNE_POWER, 0, 0, pwr, 0); } @@ -550,7 +591,8 @@ void SI4721::setTXpower(uint8_t pwr) { /// will use 0xBEEF as your Program ID. /// @param programID Optional 4 character hexadecimal ID /// @return void -void SI4721::beginRDS(uint16_t programID) { +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) @@ -571,7 +613,8 @@ void SI4721::beginRDS(uint16_t programID) { /// station identity name. /// @param *s string containing your 8 character name /// @return void -void SI4721::setRDSstation(char *s) { +void SI4721::setRDSstation(char *s) +{ uint8_t i, len = strlen(s); uint8_t slots = (len + 3) / 4; @@ -586,7 +629,8 @@ void SI4721::setRDSstation(char *s) { /// 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) { +void SI4721::setRDSbuffer(char *s) +{ uint8_t i, len = strlen(s); uint8_t slots = (len + 3) / 4; char slot[5]; @@ -604,7 +648,8 @@ void SI4721::setRDSbuffer(char *s) { /// Get TX Status and Audio Input Metrics /// @param void /// @return ASQ_STATUS struct containing asq and audioInLevel values -ASQ_STATUS SI4721::getASQ() { +ASQ_STATUS SI4721::getASQ() +{ _sendCommand(2, CMD_TX_ASQ_STATUS, 0x1); _i2cPort->requestFrom((uint8_t)_i2caddr, (uint8_t)5); @@ -625,7 +670,8 @@ ASQ_STATUS SI4721::getASQ() { /// Get TX Tuning Status /// @param void /// @return TX_STATUS struct containing frequency, dBuV, antennaCap, and noiseLevel values -TX_STATUS SI4721::getTuneStatus() { +TX_STATUS SI4721::getTuneStatus() +{ _sendCommand(2, CMD_TX_TUNE_STATUS, 0x1); _i2cPort->requestFrom((uint8_t)_i2caddr, (uint8_t)8); @@ -633,7 +679,7 @@ TX_STATUS SI4721::getTuneStatus() { TX_STATUS result; uint8_t response[8]; - for(uint8_t i = 0; i < 8; i++){ + for (uint8_t i = 0; i < 8; i++) { response[i] = _i2cPort->read(); } @@ -656,56 +702,90 @@ void SI4721::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(' '); + 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.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 "); + 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.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(' '); + 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.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() { +void SI4721::_waitEnd() +{ DEBUG_FUNC0("_waitEnd"); } // _waitEnd() /// Send an array of bytes to the radio chip -void SI4721::_sendCommand(int cnt, int cmd, ...) { - if (_debugEnabled) Serial.printf("CMD(%2x):"); +void SI4721::_sendCommand(int cnt, int cmd, ...) +{ + if (_debugRegisters) { + Serial.print("CMD("); + _printHex2(cmd); + Serial.println(")"); + } if (cnt > 8) { // see AN332: "Writing more than 8 bytes results in unpredictable device behavior." Serial.println("error: _sendCommand: too much parameters!"); @@ -719,7 +799,8 @@ void SI4721::_sendCommand(int cnt, int cmd, ...) { for (uint8_t i = 1; i < cnt; i++) { uint8_t c = va_arg(params, int); - if (_debugEnabled) Serial.printf("%02x ", c); + if (_debugRegisters) + _printHex2(c); _i2cPort->write(c); } _i2cPort->endTransmission(); @@ -730,14 +811,17 @@ void SI4721::_sendCommand(int cnt, int cmd, ...) { _status = _i2cPort->read(); } // if - if (_debugEnabled) Serial.println(); + if (_debugRegisters) + Serial.println(); } // _sendCommand() /// Set a property in the radio chip void SI4721::_setProperty(uint16_t prop, uint16_t value) { - if (_debugEnabled) Serial.printf("PROP(%04x): %04x\n", prop, value); + if (_debugRegisters) { + // Serial.printf("PROP(%04x): %04x\n", prop, value); + } _i2cPort->beginTransmission(_i2caddr); _i2cPort->write(CMD_SET_PROPERTY); _i2cPort->write(0); diff --git a/src/SI4721.h b/src/SI4721.h index 0b1bbf6..df62f76 100644 --- a/src/SI4721.h +++ b/src/SI4721.h @@ -31,7 +31,7 @@ // SI4721 specifics -#define SI4721_ADR 0x11 ///< The I2C address of SI4721 is 0x11 or 0x63 +#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 { @@ -50,44 +50,46 @@ typedef struct TX_STATUS { // ----- library definition ----- /// Library to control the SI4721 radio chip. -class SI4721 : public RADIO { +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. + 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); + bool init(TwoWire &wirePort = Wire, uint8_t deviceAddress = SI4721_ADR); /** Terminate all radio functions in the chip. */ - void term(); + void term(); // ----- Audio functions ----- - void setVolume(uint8_t newVolume); ///< Control the volume output of the radio chip in the range 0..15. + 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 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); ///< Control the mute mode of the radio chip. - void setSoftMute(bool switchOn); ///< Control the softmute mode (mute on low signals) of the radio chip. + 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); ///< regardless of the given parameter, the Bass Boost will never switch on. + 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. + void setMono(bool switchOn); ///< Control the mono/stereo mode of the radio chip. - void setBand(RADIO_BAND newBand); ///< Control the band of the radio chip. + void setBand(RADIO_BAND newBand); ///< Control the band of the radio chip. - void setFrequency(RADIO_FREQ newF); ///< Control the frequency. + void setFrequency(RADIO_FREQ newF); ///< Control the frequency. RADIO_FREQ getFrequency(void); - void seekUp(bool toNextSender = true); // start seek mode upwards + 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); @@ -95,32 +97,32 @@ class SI4721 : public RADIO { // ----- debug Helpers send information to Serial port - void debugScan(); // Scan all frequencies and report a status - void debugStatus(); // Report Info about actual Station - + 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); + void setRDSbuffer(char *s); 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 _status; ///< the status after sending a command uint8_t tuneStatus[8]; uint8_t rsqStatus[1 + 7]; @@ -129,17 +131,21 @@ class SI4721 : public RADIO { /// structure used to read status information from the SI4721 radio chip. union { - // use structured access + // 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; + 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]; @@ -148,17 +154,21 @@ class SI4721 : public RADIO { /// structure used to read RDS information from the SI4721 radio chip. union { - // use structured access + // 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; + 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]; @@ -181,10 +191,9 @@ class SI4721 : public RADIO { void _seek(bool seekUp = true); void _waitEnd(); - + TwoWire *_i2cPort; int _i2caddr; - }; #endif From 69c58fabb947ca8c3a20cdf47093620f6f46f930 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Thu, 17 Sep 2020 21:07:13 +0200 Subject: [PATCH 08/17] some refactorings. --- examples/LCDKeypadRadio/LCDKeypadRadio.ino | 5 +- examples/LCDRadio/LCDRadio.ino | 1087 ++++++++++---------- examples/ScanRadio/ScanRadio.ino | 165 ++- examples/TestSI4721/TestSI4721.ino | 194 ++-- src/RDA5807M.cpp | 2 +- src/RDA5807M.h | 1 - src/SI4705.cpp | 7 +- src/radio.cpp | 206 ++-- src/radio.h | 13 +- 9 files changed, 919 insertions(+), 761 deletions(-) diff --git a/examples/LCDKeypadRadio/LCDKeypadRadio.ino b/examples/LCDKeypadRadio/LCDKeypadRadio.ino index 2ab77f5..309f14b 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. @@ -307,4 +309,3 @@ void loop() { } // loop // End. - diff --git a/examples/LCDRadio/LCDRadio.ino b/examples/LCDRadio/LCDRadio.ino index f98d415..d61fe39 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.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. diff --git a/examples/ScanRadio/ScanRadio.ino b/examples/ScanRadio/ScanRadio.ino index 7d98905..aae55df 100644 --- a/examples/ScanRadio/ScanRadio.ino +++ b/examples/ScanRadio/ScanRadio.ino @@ -1,7 +1,7 @@ /// -/// \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 @@ -10,7 +10,7 @@ /// \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 /// ------ @@ -25,12 +25,14 @@ /// * 27.05.2015 first version is working (beta with SI4705). /// * 04.07.2015 2 scan algorithms working with good results with SI4705. +#include #include - #include + #include #include #include +#include #include #include @@ -41,7 +43,8 @@ // 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 @@ -49,12 +52,12 @@ RDSParser rds; /// State definition for this radio implementation. enum SCAN_STATE { - STATE_START, ///< waiting for a new command character. + STATE_START, ///< waiting for a new command character. STATE_NEWFREQ, STATE_WAITFIXED, STATE_WAITRDS, STATE_PRINT, - STATE_END ///< executing the command. + STATE_END ///< executing the command. }; SCAN_STATE state; ///< The state variable is used for parsing input characters. @@ -66,27 +69,48 @@ uint16_t g_block1; // use a function inbetween 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: "); + Serial.print(hour); + Serial.print(':'); + Serial.println(minute); +} + /// 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 +124,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 +143,49 @@ 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..."); - // ----- 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()); - // toggle bass boost - else if (cmd == 'b') { radio.setBassBoost(!radio.getBassBoost()); } + } else if (cmd == 's') { + // toggle stereo mode + radio.setMono(!radio.getMono()); - // ----- control the frequency ----- + } else if (cmd == 'b') { + // toggle bass boost + radio.setBassBoost(!radio.getBassBoost()); - 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 +193,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 +217,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 +247,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,22 +267,37 @@ void runSerialCommand(char cmd, int16_t value) Serial.println(); - } else if (cmd == 'f') { radio.setFrequency(value); } + } 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); } + 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(); + 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("Station:"); + Serial.println(s); + Serial.print("Radio:"); + radio.debugRadioInfo(); + Serial.print("Audio:"); + radio.debugAudioInfo(); } // info @@ -249,17 +308,25 @@ void runSerialCommand(char cmd, int16_t value) /// 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.debugRegisters(false); + + // Initialize the Radio + radio.init(); radio.setBandFrequency(RADIO_BAND_FM, 8930); @@ -267,21 +334,23 @@ void setup() { 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(DisplayServiceName); + rds.attachTimeCallback(DisplayTime); runSerialCommand('?', 0); } // Setup /// Constantly check for serial input commands and trigger command execution. -void loop() { +void loop() +{ char c; if (Serial.available() > 0) { // read the next char from input. diff --git a/examples/TestSI4721/TestSI4721.ino b/examples/TestSI4721/TestSI4721.ino index ef1390a..71a3461 100644 --- a/examples/TestSI4721/TestSI4721.ino +++ b/examples/TestSI4721/TestSI4721.ino @@ -1,86 +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 style 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 57600 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 signal | -/// | --------------- | -------------------| -/// | 3.3V | VCC | -/// | GND | GND | -/// | A5 or SCL | SCLK | -/// | A4 or SDA | SDIO | -/// | | RST (not used) | -/// -/// 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 4 ///< 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() { - // open the Serial port - Serial.begin(57600); - Serial.println("Radio..."); - delay(200); - - // Initialize the Radio - // SET_FM_DEEMPHASIS = 75; // Un-comment this line in the USA - radio.init(); - - // Enable information to the Serial port - radio.debugEnable(); - - // Set all radio setting to the fixed values. - radio.setBandFrequency(FIX_BAND, FIX_STATION); - radio.setVolume(FIX_VOLUME); - radio.setMono(false); - 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. +/// +/// \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 style 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(); + + // 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/src/RDA5807M.cpp b/src/RDA5807M.cpp index affa583..6070e5c 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/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/radio.cpp b/src/radio.cpp index dc47b41..af83821 100644 --- a/src/radio.cpp +++ b/src/radio.cpp @@ -26,41 +26,43 @@ // ----- Register Definitions ----- -#define DUMP_I2C 1 - // 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() @@ -69,57 +71,65 @@ 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) +{ _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) +{ _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) +{ _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() @@ -128,7 +138,8 @@ 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) +{ _band = newBand; if (newBand == RADIO_BAND_FM) { _freqLow = 8700; @@ -145,30 +156,48 @@ 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) +{ _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; @@ -183,7 +212,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)); @@ -197,12 +227,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() @@ -217,7 +250,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(); @@ -234,7 +268,7 @@ void RADIO::formatFrequency(char *s, uint8_t length) { s[3] = '.'; // append units - strcpy(s+6, " MHz"); + strcpy(s + 6, " MHz"); } // if // f = _freqLow + (channel * _bandSteps); @@ -246,39 +280,52 @@ void RADIO::formatFrequency(char *s, uint8_t length) { // enable debugging information on Serial port. -void RADIO::debugEnable(bool enable) { +void RADIO::debugEnable(bool enable) +{ _debugEnabled = enable; } // debugEnable() +// enable low level debugging information on Serial port. +void RADIO::debugRegisters(bool enable) +{ + _debugRegisters = enable; +} // debugRegisters() + + // 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.bassBoost ? " BASS" : " ----"); + Serial.print(info.mute ? " MUTE" : " ----"); + Serial.print(info.softmute ? " SOFTMUTE" : " --------"); 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 @@ -286,7 +333,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) { @@ -294,8 +342,7 @@ 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 @@ -308,9 +355,12 @@ bool RADIO::_wireExists(TwoWire *port, int address) { port->beginTransmission(address); uint8_t err = port->endTransmission(); -#if DUMP_I2C - Serial.printf("_wireExists 0x%02x: %d\n", address, err); -#endif + if (_debugRegisters) { + Serial.print("_wireExists("); + Serial.print(address); + Serial.print("): err="); + Serial.println(err); + } return (err == 0); } @@ -321,9 +371,13 @@ int RADIO::_wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int { int done = 0; -#if DUMP_I2C - Serial.printf("_wireRead 0x%02x:", reg); -#endif + if (_debugRegisters) { + Serial.print("_wireRead("); + Serial.print(address); + Serial.print(", "); + Serial.print(reg); + Serial.println(")"); + } if (data) { uint8_t *d = data; @@ -336,32 +390,42 @@ int RADIO::_wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int while (port->available() && (done < len)) { *d = port->read(); done++; -#if DUMP_I2C - Serial.printf(" %02x", *d); -#endif + if (_debugRegisters) { + _printHex2(*d); + } d++; } -#if DUMP_I2C - Serial.println(); -#endif + if (_debugRegisters) { + Serial.println(); + } } return (done); } // _wireRead() -/// Prints a register as 4 character hexadecimal code with leading zeros. -void RADIO::_printHex4(uint16_t val) +/// 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 0ad0bef..e5e188f 100644 --- a/src/radio.h +++ b/src/radio.h @@ -189,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 ----- @@ -201,6 +201,7 @@ class RADIO { // ----- debug Helpers send information to Serial port virtual void debugEnable(bool enable = true); ///< Enable sending debug information to the Serial port. + virtual void debugRegisters(bool enable = true); ///< Enable sending debug information to the Serial port. 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. @@ -208,12 +209,13 @@ class RADIO { // ===== Wire Utilities ===== /** check for a device on address */ - static bool _wireExists(TwoWire *port, int address); + bool _wireExists(TwoWire *port, int address); - static int _wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int len); + int _wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int len); protected: bool _debugEnabled; ///< Set by debugEnable() and controls debugging functionality. + bool _debugRegisters; ///< Set by debugEnable() and controls debugging functionality. uint8_t _volume; ///< Last set volume level. bool _bassBoost; ///< Last set bass Boost effect. @@ -226,11 +228,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". From d4324159fa65f4cca16f48f568e9d836d7435f6f Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Fri, 18 Sep 2020 17:06:44 +0200 Subject: [PATCH 09/17] cleanup, refactoring, refinement in debug output --- src/radio.cpp | 107 +++++++++++++++++++++++++++++++++++++------------- src/radio.h | 44 ++++++++++++++++++--- 2 files changed, 118 insertions(+), 33 deletions(-) diff --git a/src/radio.cpp b/src/radio.cpp index af83821..8e4bf71 100644 --- a/src/radio.cpp +++ b/src/radio.cpp @@ -73,6 +73,7 @@ uint8_t RADIO::getVolume() /// @param switchOn true to switch bassBoost mode on, false to switch bassBoost mode off. void RADIO::setBassBoost(bool switchOn) { + DEBUG_FUNC1("setBassBoost", switchOn); _bassBoost = switchOn; } // setBassBoost() @@ -90,6 +91,7 @@ bool RADIO::getBassBoost() /// The base implementation ony stores the value to the internal variable. void RADIO::setMono(bool switchOn) { + DEBUG_FUNC1("setMono", switchOn); _mono = switchOn; } // setMono() @@ -122,6 +124,7 @@ bool RADIO::getMute() /// The base implementation ony stores the value to the internal variable. void RADIO::setSoftMute(bool switchOn) { + DEBUG_FUNC1("setSoftMute", switchOn); _softMute = switchOn; } // setSoftMute() @@ -140,6 +143,7 @@ bool RADIO::getSoftMute() /// Start using the new band for receiving. void RADIO::setBand(RADIO_BAND newBand) { + DEBUG_FUNC1("setBand", newBand); _band = newBand; if (newBand == RADIO_BAND_FM) { _freqLow = 8700; @@ -158,6 +162,7 @@ void RADIO::setBand(RADIO_BAND newBand) /// The new frequency is stored for later retrieval. void RADIO::setFrequency(RADIO_FREQ newFreq) { + DEBUG_FUNC1("setFrequency", newFreq); _freq = newFreq; } // setFrequency() @@ -279,20 +284,17 @@ void RADIO::formatFrequency(char *s, uint8_t length) } // formatFrequency() -// enable debugging information on 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. + */ void RADIO::debugEnable(bool enable) { _debugEnabled = enable; } // debugEnable() -// enable low level debugging information on Serial port. -void RADIO::debugRegisters(bool enable) -{ - _debugRegisters = enable; -} // debugRegisters() - - // print out all radio information void RADIO::debugRadioInfo() { @@ -316,9 +318,9 @@ 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.bassBoost ? " BASS" : " ----"); Serial.println(); } // debugAudioInfo() @@ -351,11 +353,21 @@ void RADIO::int16_to_s(char *s, 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 (_debugRegisters) { + if (_wireDebugEnabled) { Serial.print("_wireExists("); Serial.print(address); Serial.print("): err="); @@ -364,44 +376,83 @@ bool RADIO::_wireExists(TwoWire *port, int address) return (err == 0); } -/** read a sequence of register values into a buffer. - * @return number of register values read. + +/** + * 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) { - int done = 0; + return(_wireRead(port, address, ®, 1, data, len)); +} // _wireRead() + - if (_debugRegisters) { +/** + * 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("_wireRead("); Serial.print(address); - Serial.print(", "); - Serial.print(reg); - Serial.println(")"); + 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(); + if (_wireDebugEnabled) { + Serial.print(')'); } + // read requested data (when buffer is available) if (data) { uint8_t *d = data; - port->beginTransmission(address); - port->write(reg); - port->endTransmission(); - port->requestFrom(address, len); - while (port->available() && (done < len)) { + if (_wireDebugEnabled) { + Serial.print(':'); + } + + while (port->available() && (recieved < len)) { *d = port->read(); - done++; - if (_debugRegisters) { + recieved++; + if (_wireDebugEnabled) { _printHex2(*d); } d++; } - if (_debugRegisters) { - Serial.println(); + if (_wireDebugEnabled) { + Serial.println('.'); } - } + } // if (data) - return (done); + return (recieved); } // _wireRead() diff --git a/src/radio.h b/src/radio.h index e5e188f..f8c09d1 100644 --- a/src/radio.h +++ b/src/radio.h @@ -35,7 +35,7 @@ /// \mainpage /// An Arduino library to control radio for receiving FM broadcast signals. -/// +/// /// Currently the following chips are supported: /// * The SI4703 from Silicon Labs /// * The SI4705 from Silicon Labs @@ -200,22 +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. - virtual void debugRegisters(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 _debugRegisters; ///< 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. From adaf0bd29707e689b8f1dbc76d40f9555d6f5d60 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Fri, 18 Sep 2020 17:20:53 +0200 Subject: [PATCH 10/17] refactorings, use i2c utilities --- examples/LCDKeypadRadio/LCDKeypadRadio.ino | 2 +- examples/LCDRadio/LCDRadio.ino | 2 +- examples/ScanRadio/ScanRadio.ino | 74 ++++++++++++++++------ examples/SerialRadio/SerialRadio.ino | 2 +- examples/WebRadio/WebRadio.ino | 4 +- src/SI4721.cpp | 34 +++------- 6 files changed, 67 insertions(+), 51 deletions(-) diff --git a/examples/LCDKeypadRadio/LCDKeypadRadio.ino b/examples/LCDKeypadRadio/LCDKeypadRadio.ino index 309f14b..9572bde 100644 --- a/examples/LCDKeypadRadio/LCDKeypadRadio.ino +++ b/examples/LCDKeypadRadio/LCDKeypadRadio.ino @@ -236,7 +236,7 @@ void setup() { radio.setMono(false); radio.setMute(false); - // radio.debugRegisters(); + // radio._wireDebug(); radio.setVolume(8); Serial.write('>'); diff --git a/examples/LCDRadio/LCDRadio.ino b/examples/LCDRadio/LCDRadio.ino index d61fe39..aa18d01 100644 --- a/examples/LCDRadio/LCDRadio.ino +++ b/examples/LCDRadio/LCDRadio.ino @@ -309,7 +309,7 @@ void setup() { radio.setMono(false); radio.setMute(false); - // radio.debugRegisters(); + // radio._wireDebug(); Serial.write('>'); diff --git a/examples/ScanRadio/ScanRadio.ino b/examples/ScanRadio/ScanRadio.ino index aae55df..71c4333 100644 --- a/examples/ScanRadio/ScanRadio.ino +++ b/examples/ScanRadio/ScanRadio.ino @@ -50,24 +50,27 @@ SI4721 radio; ///< Create an instance of a SI4705 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 = true; // - - - - - - - - - - - - - - - - - - - - - - - - - - -// 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) { @@ -80,10 +83,15 @@ void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t blo 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) @@ -146,6 +154,7 @@ void runSerialCommand(char cmd, int16_t value) 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 ----- @@ -290,6 +299,7 @@ void runSerialCommand(char cmd, int16_t value) radio.init(); } else if (cmd == 'i') { + // info char s[12]; radio.formatFrequency(s, sizeof(s)); Serial.print("Station:"); @@ -299,10 +309,12 @@ void runSerialCommand(char cmd, int16_t value) Serial.print("Audio:"); radio.debugAudioInfo(); - } // info - - else if (cmd == 'x') { + } else if (cmd == 'x') { radio.debugStatus(); // print chip specific data. + + } else if (cmd == '*') { + lowLevelDebug = !lowLevelDebug; + radio._wireDebug(lowLevelDebug); } } // runSerialCommand() @@ -322,8 +334,8 @@ void setup() #endif // Enable information to the Serial port - radio.debugEnable(); - radio.debugRegisters(false); + radio.debugEnable(true); + radio._wireDebug(lowLevelDebug); // Initialize the Radio radio.init(); @@ -341,21 +353,43 @@ void setup() // setup the information chain for RDS data. radio.attachReceiveRDS(RDS_process); rds.attachServicenNameCallback(DisplayServiceName); - rds.attachTextCallback(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; 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/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/src/SI4721.cpp b/src/SI4721.cpp index 24e068a..28e0d4d 100644 --- a/src/SI4721.cpp +++ b/src/SI4721.cpp @@ -270,8 +270,6 @@ void SI4721::setDeemphasis(uint8_t uS) /// @return void void SI4721::setSoftMute(bool switchOn) { - DEBUG_FUNC1("setSoftMute", switchOn); - RADIO::setSoftMute(switchOn); if (switchOn) { @@ -289,7 +287,6 @@ void SI4721::setSoftMute(bool switchOn) /// @return void void SI4721::setBassBoost(bool switchOn) { - DEBUG_FUNC1("setBassBoost", switchOn); DEBUG_STR("not supported."); RADIO::setBassBoost(false); } // setBassBoost() @@ -301,7 +298,6 @@ void SI4721::setBassBoost(bool switchOn) /// @return void void SI4721::setMono(bool switchOn) { - DEBUG_FUNC1("setMono", switchOn); RADIO::setMono(switchOn); if (switchOn) { // disable automatic stereo feature @@ -362,6 +358,7 @@ void SI4721::setBand(RADIO_BAND newBand) _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 down the device _sendCommand(1, CMD_POWER_DOWN); @@ -427,6 +424,7 @@ void SI4721::setFrequency(RADIO_FREQ newF) do { status = _readStatus(); } while (!(status & CMD_GET_INT_STATUS_CTS)); + Serial.println(status); } // setFrequency() @@ -491,17 +489,8 @@ uint8_t SI4721::_readStatus() /// Load status information from to the chip. void SI4721::_readStatusData(uint8_t cmd, uint8_t param, uint8_t *values, uint8_t len) { - _i2cPort->beginTransmission(_i2caddr); - _i2cPort->write(cmd); - _i2cPort->write(param); - - _i2cPort->endTransmission(); - _i2cPort->requestFrom(_i2caddr, (int)len); //We want to read some bytes. - - for (uint8_t n = 0; n < len; n++) { - //Read in these bytes - values[n] = _i2cPort->read(); - } // for + uint8_t buffer[2] = {cmd, param}; + _wireRead(_i2cPort, _i2caddr, buffer, 2, values, len); } // _readStatusData() @@ -552,16 +541,9 @@ void SI4721::attachReceiveRDS(receiveRDSFunction newFunction) void SI4721::checkRDS() { if (_sendRDS) { - // fetch the interrupt status first - // uint8_t status = _readStatus(); - // fetch the current RDS data - // _wireRead(_i2cPort, _i2caddr, ) _readStatusData(CMD_FM_RDS_STATUS, 0x01, rdsStatus.buffer, sizeof(rdsStatus)); - // Serial.print("Status:"); - // Serial.println(rdsStatus.status, HEX); - if ((rdsStatus.resp2 = 0x01) && (rdsStatus.rdsFifoUsed) && (rdsStatus.blockErrors == 0)) { // RDS is in sync, it's a complete entry and no errors @@ -781,7 +763,7 @@ void SI4721::_waitEnd() /// Send an array of bytes to the radio chip void SI4721::_sendCommand(int cnt, int cmd, ...) { - if (_debugRegisters) { + if (_wireDebugEnabled) { Serial.print("CMD("); _printHex2(cmd); Serial.println(")"); @@ -799,7 +781,7 @@ void SI4721::_sendCommand(int cnt, int cmd, ...) for (uint8_t i = 1; i < cnt; i++) { uint8_t c = va_arg(params, int); - if (_debugRegisters) + if (_wireDebugEnabled) _printHex2(c); _i2cPort->write(c); } @@ -811,7 +793,7 @@ void SI4721::_sendCommand(int cnt, int cmd, ...) _status = _i2cPort->read(); } // if - if (_debugRegisters) + if (_wireDebugEnabled) Serial.println(); } // _sendCommand() @@ -819,7 +801,7 @@ void SI4721::_sendCommand(int cnt, int cmd, ...) /// Set a property in the radio chip void SI4721::_setProperty(uint16_t prop, uint16_t value) { - if (_debugRegisters) { + if (_wireDebugEnabled) { // Serial.printf("PROP(%04x): %04x\n", prop, value); } _i2cPort->beginTransmission(_i2caddr); From 600e74fb6ed172832a0e2e8f5f5b1b641b19394b Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Fri, 18 Sep 2020 17:34:24 +0200 Subject: [PATCH 11/17] ScanRadio fixed --- examples/ScanRadio/ScanRadio.ino | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/examples/ScanRadio/ScanRadio.ino b/examples/ScanRadio/ScanRadio.ino index 71c4333..440be0f 100644 --- a/examples/ScanRadio/ScanRadio.ino +++ b/examples/ScanRadio/ScanRadio.ino @@ -3,8 +3,8 @@ /// \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 @@ -24,6 +24,7 @@ /// * 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 @@ -55,8 +56,8 @@ RDSParser rds; enum RADIO_STATE { STATE_PARSECOMMAND, ///< waiting for a new command character. - STATE_PARSEINT, ///< waiting for digits for the parameter. - STATE_EXEC ///< executing the command. + STATE_PARSEINT, ///< waiting for digits for the parameter. + STATE_EXEC ///< executing the command. }; RADIO_STATE kbState; ///< The state of parsing input characters. @@ -65,7 +66,9 @@ int16_t kbValue; uint16_t g_block1; -bool lowLevelDebug = true; +bool lowLevelDebug = false; +RADIO_FREQ frequency; + // - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -277,6 +280,7 @@ void runSerialCommand(char cmd, int16_t value) } else if (cmd == 'f') { + frequency = value; radio.setFrequency(value); } @@ -293,10 +297,12 @@ void runSerialCommand(char cmd, int16_t value) // not in help: else if (cmd == '!') { - if (value == 0) + if (value == 0) { radio.term(); - if (value == 1) + } else if (value == 1) { radio.init(); + radio.setBandFrequency(RADIO_BAND_FM, frequency); + } } else if (cmd == 'i') { // info @@ -340,7 +346,8 @@ void setup() // Initialize the Radio radio.init(); - radio.setBandFrequency(RADIO_BAND_FM, 8930); + frequency = 8930; + radio.setBandFrequency(RADIO_BAND_FM, frequency); // delay(100); From 4b1846c200e7c44a117090c0df966667e7af78af Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 20 Sep 2020 11:58:09 +0200 Subject: [PATCH 12/17] SI4721 definitions and TxPower getter --- src/SI4721.cpp | 115 +++++++++++++++++++++++++++++++++---------------- src/SI4721.h | 6 ++- 2 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/SI4721.cpp b/src/SI4721.cpp index 28e0d4d..1f7a0af 100644 --- a/src/SI4721.cpp +++ b/src/SI4721.cpp @@ -30,11 +30,13 @@ #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. @@ -55,14 +57,76 @@ #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 @@ -79,8 +143,10 @@ // Property and Parameter definitions #define PROP_GPO_IEN 0x0001 -#define PROP_GPO_IEN_STCIEN 0x01 -#define PROP_GPO_IEN_RDSIEN 0x04 +#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 @@ -126,37 +192,6 @@ #define PROP_RX_HARD_MUTE_LEFT 0x02 #define PROP_RX_HARD_MUTE_BOTH 0x03 -// 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_AUDIO_DEVIATION 0x2101 -#define PROP_TX_PILOT_DEVIATION 0x2102 -#define PROP_TX_RDS_DEVIATION 0x2103 -#define PROP_TX_LINE_LEVEL_INPUT_LEVEL 0x2104 -#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_ATTACK_TIME 0x2202 -#define PROP_TX_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_AQS_LEVEL_HIGH 0x2303 -#define PROP_TX_AQS_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 // Preemphasis time constant. #define PROP_TX_PREEMPHASIS 0x2106 @@ -187,12 +222,10 @@ bool SI4721::init(TwoWire &wirePort, uint8_t 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() @@ -322,8 +355,6 @@ void SI4721::setMono(bool switchOn) */ void SI4721::setBand(RADIO_BAND newBand) { - DEBUG_FUNC1("setBand", newBand); - if (newBand == RADIO_BAND_FM) { // set band boundaries and steps RADIO::setBand(newBand); @@ -559,11 +590,19 @@ void SI4721::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) +void SI4721::setTXPower(uint8_t pwr) { + _txPower = pwr; _sendCommand(5, CMD_TX_TUNE_POWER, 0, 0, pwr, 0); } diff --git a/src/SI4721.h b/src/SI4721.h index df62f76..d92865d 100644 --- a/src/SI4721.h +++ b/src/SI4721.h @@ -105,7 +105,9 @@ class SI4721 : public RADIO void beginRDS(uint16_t programID = 0xBEEF); void setRDSstation(char *s); void setRDSbuffer(char *s); - void setTXpower(uint8_t pwr); + + uint8_t getTXPower(); + void setTXPower(uint8_t pwr); ASQ_STATUS getASQ(); TX_STATUS getTuneStatus(); @@ -129,6 +131,8 @@ class SI4721 : public RADIO 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 From 6c012036d288343f2c7a7aabf791d0a3b328a896 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 20 Sep 2020 11:59:15 +0200 Subject: [PATCH 13/17] low level wire function fixed --- src/radio.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/radio.cpp b/src/radio.cpp index 8e4bf71..314ae81 100644 --- a/src/radio.cpp +++ b/src/radio.cpp @@ -411,12 +411,12 @@ int RADIO::_wireRead(TwoWire *port, int address, uint8_t *cmdData, int cmdLen, u // send out command sequence port->beginTransmission(address); if (_wireDebugEnabled) { - Serial.print("_wireRead("); + Serial.print("_wire("); Serial.print(address); - Serial.print(','); + Serial.print("): "); } - for (int i = 0; i <= cmdLen; i++) { + for (int i = 0; i < cmdLen; i++) { uint8_t d = cmdData[i]; port->write(d); if (_wireDebugEnabled) { @@ -425,18 +425,13 @@ int RADIO::_wireRead(TwoWire *port, int address, uint8_t *cmdData, int cmdLen, u } // for port->endTransmission(); - if (_wireDebugEnabled) { - Serial.print(')'); - } // read requested data (when buffer is available) if (data) { + Serial.print(" -> "); uint8_t *d = data; port->requestFrom(address, len); - if (_wireDebugEnabled) { - Serial.print(':'); - } while (port->available() && (recieved < len)) { *d = port->read(); From 5cd3579a9d7ec8cf3360b4760f3a767a8bbe8478 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 20 Sep 2020 18:28:41 +0200 Subject: [PATCH 14/17] Finalizing TransmitSI4721 example --- examples/TestSI4721/TestSI4721.ino | 6 +- examples/TransmitSI4721/TransmitSI4721.ino | 199 ++++++++++++--------- src/SI4721.cpp | 140 +++++++-------- src/SI4721.h | 12 +- src/radio.cpp | 6 +- 5 files changed, 197 insertions(+), 166 deletions(-) diff --git a/examples/TestSI4721/TestSI4721.ino b/examples/TestSI4721/TestSI4721.ino index 71a3461..8343df8 100644 --- a/examples/TestSI4721/TestSI4721.ino +++ b/examples/TestSI4721/TestSI4721.ino @@ -5,7 +5,7 @@ /// \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. See http://www.mathertel.de/License.aspx +/// 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 @@ -69,11 +69,11 @@ void setup() } else { Serial.print("Device NOT found at address "); Serial.println(SI4721_ADR); - } // Enable debug information to the Serial port - radio.debugEnable(); + radio.debugEnable(false); + radio._wireDebug(false); // Initialize the Radio radio.init(Wire, SI4721_ADR); diff --git a/examples/TransmitSI4721/TransmitSI4721.ino b/examples/TransmitSI4721/TransmitSI4721.ino index 968d91a..90b5b42 100644 --- a/examples/TransmitSI4721/TransmitSI4721.ino +++ b/examples/TransmitSI4721/TransmitSI4721.ino @@ -1,87 +1,112 @@ -/// -/// \file TestSI4705.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 style 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 57600 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 signal | -/// | --------------- | -------------------| -/// | 3.3V | VCC | -/// | GND | GND | -/// | A5 or SCL | SCLK | -/// | A4 or SDA | SDIO | -/// | | RST (not used yet) | -/// The locations of the pins on the UNO board are written on the PCB. -/// The locations of the signals on the SI4705 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. - -#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 4 ///< The volume that will be set by this sketch is level 4. - -SI4705 radio; // Create an instance of Class for SI4705 Chip - -/// Setup a FM only radio configuration -/// with some debugging on the Serial port -void setup() { - // open the Serial port - Serial.begin(57600); - Serial.println("Radio..."); - delay(200); - - // Initialize the Radio - radio.init(); - - // Enable information to the Serial port - radio.debugEnable(); - - // Set all radio setting to the fixed values. - radio.setBandFrequency(FIX_BAND, FIX_STATION); - radio.setVolume(FIX_VOLUME); - radio.setMono(false); - 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. +/// +/// \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/src/SI4721.cpp b/src/SI4721.cpp index 1f7a0af..e9f2c9f 100644 --- a/src/SI4721.cpp +++ b/src/SI4721.cpp @@ -202,6 +202,7 @@ SI4721::SI4721() { _realVolume = 0; + _txPower = 90; } /// Initialize the library and the chip. @@ -332,16 +333,19 @@ void SI4721::setBassBoost(bool switchOn) void SI4721::setMono(bool switchOn) { RADIO::setMono(switchOn); - if (switchOn) { - // disable automatic stereo feature - _setProperty(PROP_FM_BLEND_STEREO_THRESHOLD, 127); - // not available in SI4721: - // _setProperty(PROP_FM_BLEND_RSSI_STEREO_THRESHOLD, 127); - // _setProperty(PROP_FM_BLEND_RSSI_MONO_THRESHOLD, 127); + if (_band == RADIO_BAND_FMTX) { + // switch Off ??? + // _setProperty(PROP_TX_COMPONENT_ENABLE, 0x0007); // stereo, pilot+rds - } else { - // Automatic stereo feature on. - _setProperty(PROP_FM_BLEND_STEREO_THRESHOLD, 49); // default = 49 dBμV + } 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 @@ -350,26 +354,27 @@ void SI4721::setMono(bool switchOn) /** * 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 down the device - _sendCommand(1, CMD_POWER_DOWN); - - // Give the device some time to power down before restart - delay(500); - // 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 @@ -390,24 +395,41 @@ void SI4721::setBand(RADIO_BAND newBand) } else if (newBand == RADIO_BAND_FMTX) { RADIO::setBand(newBand); - // Power down the device - _sendCommand(1, CMD_POWER_DOWN); - - // Give the device some time to power down before restart - delay(500); // Power up in transmit mode - _sendCommand(3, CMD_POWER_UP, 0x12, 0x50); + _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 on limiter and AGC + _setProperty(PROP_TX_ACOMP_ENABLE, 0x0); // turns off limiter and AGC - } else { - _sendCommand(1, CMD_POWER_DOWN); + 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() @@ -444,13 +466,14 @@ void SI4721::setFrequency(RADIO_FREQ 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(); } - // reset the RDSParser - clearRDS(); - // loop until status ok. do { status = _readStatus(); @@ -802,60 +825,35 @@ void SI4721::_waitEnd() /// Send an array of bytes to the radio chip void SI4721::_sendCommand(int cnt, int cmd, ...) { - if (_wireDebugEnabled) { - Serial.print("CMD("); - _printHex2(cmd); - Serial.println(")"); - } - if (cnt > 8) { - // see AN332: "Writing more than 8 bytes results in unpredictable device behavior." - Serial.println("error: _sendCommand: too much parameters!"); - - } else { - _i2cPort->beginTransmission(_i2caddr); - _i2cPort->write(cmd); + uint8_t cmdData[12]; - va_list params; - va_start(params, cmd); + va_list params; + va_start(params, cmd); + cmdData[0] = cmd; + for (uint8_t i = 1; i < cnt; i++) { + cmdData[i] = va_arg(params, int); + } - for (uint8_t i = 1; i < cnt; i++) { - uint8_t c = va_arg(params, int); - if (_wireDebugEnabled) - _printHex2(c); - _i2cPort->write(c); - } - _i2cPort->endTransmission(); - va_end(params); + _wireRead(_i2cPort, _i2caddr, cmdData, cnt, &_status, 1); - // wait for Command being processed - _i2cPort->requestFrom(_i2caddr, 1); // We want to read the status byte. + // 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 - - if (_wireDebugEnabled) - Serial.println(); + 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) { - if (_wireDebugEnabled) { - // Serial.printf("PROP(%04x): %04x\n", prop, value); - } - _i2cPort->beginTransmission(_i2caddr); - _i2cPort->write(CMD_SET_PROPERTY); - _i2cPort->write(0); - - _i2cPort->write(prop >> 8); - _i2cPort->write(prop & 0x00FF); - _i2cPort->write(value >> 8); - _i2cPort->write(value & 0x00FF); - - _i2cPort->endTransmission(); - - _i2cPort->requestFrom(_i2caddr, 1); // We want to read the status byte. - _status = _i2cPort->read(); + uint8_t cmdData[6] = {CMD_SET_PROPERTY, 0, prop >> 8, prop & 0x00FF, value >> 8, value & 0x00FF}; + _wireRead(_i2cPort, _i2caddr, cmdData, 6, &_status, 1); } // _setProperty() diff --git a/src/SI4721.h b/src/SI4721.h index d92865d..bb2dc53 100644 --- a/src/SI4721.h +++ b/src/SI4721.h @@ -8,7 +8,7 @@ /// 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. +/// 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 @@ -16,7 +16,7 @@ /// ChangeLog: /// ---------- /// * 01.12.2019 created. -/// * 17.09.2020 move si4721 specific initialization into setBand() +/// * 17.09.2020 si4721 specific initialization moved into setBand() #ifndef SI4721_h #define SI4721_h @@ -81,7 +81,13 @@ class SI4721 : public RADIO void setMono(bool switchOn); ///< Control the mono/stereo mode of the radio chip. - void setBand(RADIO_BAND newBand); ///< Control the band 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); diff --git a/src/radio.cpp b/src/radio.cpp index 314ae81..af2df00 100644 --- a/src/radio.cpp +++ b/src/radio.cpp @@ -389,7 +389,7 @@ bool RADIO::_wireExists(TwoWire *port, int address) */ int RADIO::_wireRead(TwoWire *port, int address, uint8_t reg, uint8_t *data, int len) { - return(_wireRead(port, address, ®, 1, data, len)); + return (_wireRead(port, address, ®, 1, data, len)); } // _wireRead() @@ -428,7 +428,9 @@ int RADIO::_wireRead(TwoWire *port, int address, uint8_t *cmdData, int cmdLen, u // read requested data (when buffer is available) if (data) { - Serial.print(" -> "); + if (_wireDebugEnabled) { + Serial.print(" -> "); + } uint8_t *d = data; port->requestFrom(address, len); From 427839e4556c9bea286a5a954da38a092453004e Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 20 Sep 2020 18:42:21 +0200 Subject: [PATCH 15/17] Prepare version 2.0.0 --- .gitignore | 7 +++++- CHANGELOG.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++ README.md | 53 ++++++++++++++++++++++++++++++++++------------ library.properties | 2 +- 4 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 CHANGELOG.md 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/README.md b/README.md index b4e5220..f7671a9 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,34 @@ 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 **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/library.properties b/library.properties index c3eb503..47e49e9 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Radio -version=1.3.0 +version=2.0.0 author=Matthias Hertel maintainer=Matthias Hertel, sentence=Library for controlling FM radio receiver chips. From e7c5ee7669fc94f64e75dc1fd70ba5ec1fd15c4f Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 20 Sep 2020 18:46:02 +0200 Subject: [PATCH 16/17] doku --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f7671a9..aec0213 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ The basic examples only startup the chips and set a static station and volume:LC The examples can be used with several chips: * 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. From 33acfdee6f9a035fabd44b82dca25d12ec1970d3 Mon Sep 17 00:00:00 2001 From: Matthias Hertel Date: Sun, 20 Sep 2020 19:35:39 +0200 Subject: [PATCH 17/17] doku --- keywords.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/keywords.txt b/keywords.txt index d120700..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,14 +60,12 @@ attachReceiveRDS KEYWORD2 formatFrequency KEYWORD2 -setModeReceive KEYWORD2 -setModeTransmit KEYWORD2 -beginRDS KEYWORD2 +beginRDS KEYWORD2 setRDSstation KEYWORD2 setRDSbuffer KEYWORD2 -getASQ -getTuneStatus +getASQ KEYWORD2 +getTuneStatus KEYWORD2 ####################################### # Instances (KEYWORD2)