├── Radio_Logo.png ├── examples ├── WebRadio │ ├── web │ │ ├── radio.ico │ │ ├── radio48.png │ │ ├── radio128.png │ │ ├── radio192.png │ │ ├── redirect.htm │ │ ├── radiomanifest.json │ │ ├── iotstyle.css │ │ └── about.htm │ └── StringBuffer.h ├── TestSI4703 │ ├── README.md │ └── TestSI4703.ino ├── LCDKeypadRadio │ ├── README.md │ └── LCDKeypadRadio.ino ├── SerialRadio │ ├── README.md │ └── SerialRadio.ino ├── LCDRadio │ └── README.md ├── TestTEA5767 │ └── TestTEA5767.ino ├── TestSI4705 │ └── TestSI4705.ino ├── TestRDA5807M │ └── TestRDA5807M.ino ├── TestRDA5807FP │ └── TestRDA5807FP.ino ├── TransmitSI4721 │ └── TransmitSI4721.ino ├── TestSI47xx │ └── TestSI47xx.ino └── ScanRadio │ └── ScanRadio.ino ├── .gitignore ├── .markdownlint.json ├── library.properties ├── LICENSE ├── .github └── workflows │ ├── buildESP32.yml │ ├── buildAVR.yml │ └── buildESP8266.yml ├── src ├── RDA5807FP.h ├── RDA5807FP.cpp ├── RDSParser.h ├── TEA5767.h ├── RDA5807M.h ├── SI4703.h ├── SI4705.h ├── RDSParser.cpp ├── SI47xx.h ├── TEA5767.cpp ├── radio.h ├── radio.cpp ├── RDA5807M.cpp └── SI4703.cpp ├── keywords.txt ├── README.md ├── CHANGELOG.md └── .clang-format /Radio_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathertel/Radio/HEAD/Radio_Logo.png -------------------------------------------------------------------------------- /examples/WebRadio/web/radio.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathertel/Radio/HEAD/examples/WebRadio/web/radio.ico -------------------------------------------------------------------------------- /examples/WebRadio/web/radio48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathertel/Radio/HEAD/examples/WebRadio/web/radio48.png -------------------------------------------------------------------------------- /examples/WebRadio/web/radio128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathertel/Radio/HEAD/examples/WebRadio/web/radio128.png -------------------------------------------------------------------------------- /examples/WebRadio/web/radio192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mathertel/Radio/HEAD/examples/WebRadio/web/radio192.png -------------------------------------------------------------------------------- /examples/WebRadio/web/redirect.htm: -------------------------------------------------------------------------------- 1 | 2 |

Please follow this link.

-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Compiled Dynamic libraries 8 | *.so 9 | *.dylib 10 | *.dll 11 | 12 | # Compiled Static libraries 13 | *.lai 14 | *.la 15 | *.a 16 | *.lib 17 | 18 | # Executables 19 | *.exe 20 | *.out 21 | *.app 22 | 23 | _* 24 | /.vscode 25 | /.development 26 | /build 27 | .gitignore 28 | *.code-workspace 29 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "no-hard-tabs": true, 4 | "no-trailing-spaces": true, 5 | "blank_lines": true, 6 | "no-multiple-blanks": { 7 | "maximum": 2 8 | }, 9 | "ul-indent": { 10 | "indent": 2 11 | }, 12 | "ul-style": { 13 | "style": "asterisk" 14 | }, 15 | "MD013": false, 16 | "no-inline-html": false, 17 | "MD046": { 18 | "style": "fenced" 19 | } 20 | } -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Radio 2 | version=3.0.1 3 | author=Matthias Hertel 4 | maintainer=Matthias Hertel, 5 | sentence=Library for controlling FM radio receiver chips. 6 | 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. 7 | category=Communication 8 | url=http://www.mathertel.de/Arduino/RadioLibrary.aspx 9 | architectures=* -------------------------------------------------------------------------------- /examples/WebRadio/web/radiomanifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Radio", 3 | "name": "Remote Radio Control.", 4 | "icons": [ 5 | { 6 | "src": "radio-48.png", 7 | "sizes": "48x48", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "radio-128.png", 12 | "sizes": "128x128", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "radio-192.png", 17 | "sizes": "192x192", 18 | "type": "image/png" 19 | } 20 | ], 21 | "start_url": "./radio.htm", 22 | "display": "standalone", 23 | "orientation": "portrait", 24 | "theme_color": "#304878", 25 | "background_color": "#c0c0c0" 26 | 27 | } -------------------------------------------------------------------------------- /examples/TestSI4703/README.md: -------------------------------------------------------------------------------- 1 | # Si4703 2 | 3 | These are some hints when using a SI4703 radio chip. 4 | 5 | * The Antenna is not available as a separate connector. 6 | The GND signal on the earphones is used instead as antenna. 7 | * The RST pin must be connected as this is required at startup to initialize I2C control mode. 8 | * The SI4703 has 3 GPIO pins that also can be used for input and output. 9 | The `writeGPIO` function can be used for controlling these pins. 10 | * The GPIO3 is used for wiring the XTAL so it cannot be used on these boards 11 | and also cannot be used as a stereo indicator. 12 | 13 | A schema for a typical board is available on 14 | -------------------------------------------------------------------------------- /examples/LCDKeypadRadio/README.md: -------------------------------------------------------------------------------- 1 | # LCDKeypadRadio Example 2 | 3 | This example allows controlling the a supported radio chip by using the LCDKeypad Shield 4 | stacked on a Arduino UNO board. 5 | 6 | It is designed for Arduino UNO only. 7 | 8 | You have to modify the source code line where the radio variable is defined to the chip you use. 9 | There are out-commented lines for every chip in the sketch. 10 | 11 | Be sure to activate the lines for the RST signal for SI4703 radio chips. 12 | 13 | Please read the README.md files in the TEST____ examples for more chip specific hints. 14 | 15 | There is a picture of this setup with a RDA5807 chip available at 16 | 17 | -------------------------------------------------------------------------------- /examples/SerialRadio/README.md: -------------------------------------------------------------------------------- 1 | # SerialRadio Example 2 | 3 | This example allows controlling the a supported radio chip by using the Serial interface 4 | and without specific other hardware. 5 | 6 | It works with processor boards for Arduino UNO, ESP8266 like NodeMCU 1.0 and ESP32. 7 | 8 | This sketch works with all of the implemented chips by using the common radio functions of the library. 9 | 10 | You have to modify the source code line where the radio variable is defined to the chip you use. 11 | There are out-commented lines for every chip in the sketch. 12 | Be sure to activate the lines for the RST signal for SI4703 radio chips. 13 | 14 | The only IO this sketch does is through the Serial interface and when you open the Serial Monitor window 15 | of the Arduino programming environment you can see verbose output from the library 16 | and can control the settings by entering commands in the input text line. 17 | 18 | The command ? (and enter) will display a short summary of the available commands. 19 | 20 | If you like to explore the chip specific settings you may use the "x" command 21 | that outputs the chip registers or other chip specific information. 22 | 23 | Have a look into the chip specific implementation for more details. 24 | 25 | Please read the README.md files in the TEST____ examples for more chip specific hints. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2005-2020, Matthias Hertel, http://www.mathertel.de/ 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | 3. Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /.github/workflows/buildESP32.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action Workflow 2 | 3 | name: Build Examples for ESP32 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the develop branch 8 | push: 9 | branches: [master] 10 | pull_request: 11 | branches: [master] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # Build Examples for ESP32 19 | build-esp32: 20 | name: build examples on ESP32 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v3 27 | 28 | # Run Arduino Compiler 29 | # see https://github.com/arduino/compile-sketches 30 | - name: Compile examples 31 | uses: arduino/compile-sketches@v1 32 | with: 33 | verbose: true 34 | platforms: | 35 | # Install ESP32 platform via Boards Manager 36 | - name: "esp32:esp32" 37 | source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json 38 | # version: 2.0.4 doesn't work with CLI caused by issue https://github.com/espressif/arduino-esp32/pull/7060 39 | version: 2.0.2 40 | fqbn: esp32:esp32:esp32 41 | libraries: | 42 | - source-path: ./ 43 | sketch-paths: | 44 | - 'examples/SerialRadio' 45 | - 'examples/ScanRadio' 46 | -------------------------------------------------------------------------------- /.github/workflows/buildAVR.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action Workflow 2 | 3 | name: Build Examples for AVR UNO 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [master] 10 | pull_request: 11 | branches: [master] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | 19 | # This defines a job for checking the Arduino library format specifications 20 | # see 21 | compile-uno: 22 | name: check library format 23 | runs-on: ubuntu-latest 24 | continue-on-error: true 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | 29 | # Arduino - lint 30 | - name: Arduino-lint 31 | uses: arduino/arduino-lint-action@v1 32 | with: 33 | library-manager: update 34 | verbose: false 35 | 36 | # Run Arduino Compiler 37 | # see https://github.com/arduino/compile-sketches 38 | - name: Compile examples 39 | uses: arduino/compile-sketches@v1 40 | with: 41 | verbose: true 42 | fqbn: arduino:avr:uno 43 | 44 | libraries: | 45 | - source-path: ./ 46 | - name: LiquidCrystal 47 | sketch-paths: | 48 | - 'examples/LCDKeypadRadio' 49 | - 'examples/ScanRadio' 50 | 51 | # size-report-sketch: 'ConnectionHandlerDemo' 52 | # enable-size-deltas-report: 'true' 53 | # sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} 54 | -------------------------------------------------------------------------------- /src/RDA5807FP.h: -------------------------------------------------------------------------------- 1 | /// 2 | /// \file RDA5807FP.h 3 | /// \brief Library header file for the radio library to control the RDA5807FP radio chip. 4 | /// 5 | /// \author Marcus Degenkolbe, http://www.degenkolbe.eu 6 | /// \copyright Copyright (c) 2023 by Marcus Degenkolbe.\n 7 | /// This work is licensed under a BSD style license.\n 8 | /// See http://www.mathertel.de/License.aspx 9 | /// 10 | /// \details 11 | /// This library enables the use of the Radio Chip RDA5807FP from http://www.rdamicro.com/ that supports FM radio bands, RDS data and I2S output. 12 | /// 13 | /// More documentation and source code is available at http://www.mathertel.de/Arduino 14 | /// 15 | /// History: 16 | /// -------- 17 | /// * 10.01.2023 created. 18 | 19 | // inherits all features from RDA5807M and adds basic I2S support 20 | 21 | #ifndef RDA5807FP_h 22 | #define RDA5807FP_h 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | enum RDA5807FP_I2S_WS_CNT 30 | { 31 | WS_STEP_48 = 0b1000, 32 | WS_STEP_44_1 = 0b0111, 33 | WS_STEP_32 = 0b0110, 34 | WS_STEP_24 = 0b0101, 35 | WS_STEP_22_05 = 0b0100, 36 | WS_STEP_16 = 0b0011, 37 | WS_STEP_12 = 0b0010, 38 | WS_STEP_11_025 = 0b0001, 39 | WS_STEP_8 = 0, 40 | }; 41 | 42 | struct RDA5807FP_I2SConfig 43 | { 44 | bool enabled = false; 45 | bool slave = false; 46 | RDA5807FP_I2S_WS_CNT rate = WS_STEP_48; 47 | bool data_signed = false; 48 | }; 49 | 50 | // ----- library definition ----- 51 | 52 | /// Library to control the RDA5807FP radio chip. 53 | class RDA5807FP : public RDA5807M 54 | { 55 | public: 56 | RDA5807FP(); 57 | 58 | void SetupI2S(RDA5807FP_I2SConfig config); 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For Arduino Radio Library 3 | # see 4 | ####################################### 5 | 6 | ####################################### 7 | # Datatypes (KEYWORD1) 8 | ####################################### 9 | 10 | RDA5807M KEYWORD1 11 | RADIO KEYWORD1 12 | SI4703 KEYWORD1 13 | SI4705 KEYWORD1 14 | SI4721 KEYWORD1 15 | TEA5767 KEYWORD1 16 | 17 | RADIO_FREQ KEYWORD1 18 | RADIO_BAND KEYWORD1 19 | RADIO_INFO KEYWORD1 20 | AUDIO_INFO KEYWORD1 21 | 22 | ####################################### 23 | # Methods and Functions (KEYWORD2) 24 | ####################################### 25 | 26 | init KEYWORD2 27 | term KEYWORD2 28 | 29 | setVolume KEYWORD2 30 | getVolume KEYWORD2 31 | setBassBoost KEYWORD2 32 | getBassBoost KEYWORD2 33 | setMono KEYWORD2 34 | getMono KEYWORD2 35 | setMute KEYWORD2 36 | getMute KEYWORD2 37 | setSoftMute KEYWORD2 38 | getSoftMute KEYWORD2 39 | 40 | setBand KEYWORD2 41 | getBand KEYWORD2 42 | 43 | setFrequency KEYWORD2 44 | getFrequency KEYWORD2 45 | 46 | setBandFrequency KEYWORD2 47 | 48 | seekUp KEYWORD2 49 | seekDown KEYWORD2 50 | 51 | getMinFrequency KEYWORD2 52 | getMaxFrequency KEYWORD2 53 | getFrequencyStep KEYWORD2 54 | 55 | getRadioInfo KEYWORD2 56 | getAudioInfo KEYWORD2 57 | 58 | checkRDS KEYWORD2 59 | attachReceiveRDS KEYWORD2 60 | 61 | formatFrequency KEYWORD2 62 | 63 | beginRDS KEYWORD2 64 | setRDSstation KEYWORD2 65 | setRDSbuffer KEYWORD2 66 | 67 | getASQ KEYWORD2 68 | getTuneStatus KEYWORD2 69 | 70 | ####################################### 71 | # Instances (KEYWORD2) 72 | ####################################### 73 | 74 | 75 | ####################################### 76 | # Constants (LITERAL1) 77 | ####################################### 78 | -------------------------------------------------------------------------------- /.github/workflows/buildESP8266.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action Workflow 2 | 3 | name: Build Examples for ESP8266 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [master] 10 | pull_request: 11 | branches: [master] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # Build Examples for ESP8266 19 | build-esp8266: 20 | name: build examples on ESP8266 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v3 27 | 28 | # Run Arduino Compiler 29 | # see https://github.com/arduino/compile-sketches 30 | - name: Compile examples 31 | uses: arduino/compile-sketches@v1 32 | with: 33 | verbose: true 34 | platforms: | 35 | # Install ESP8266 platform via Boards Manager 36 | - name: "esp8266:esp8266" 37 | source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json 38 | version: 3.1.1 39 | fqbn: esp8266:esp8266:nodemcuv2 40 | libraries: | 41 | - source-path: ./ 42 | - name: OneButton 43 | - name: RotaryEncoder 44 | - name: LiquidCrystal_PCF8574 45 | sketch-paths: | 46 | - 'examples/SerialRadio' 47 | - 'examples/ScanRadio' 48 | - 'examples/TestSI4703' 49 | - 'examples/TestSI47xx' 50 | 51 | # size-report-sketch: 'ConnectionHandlerDemo' 52 | # enable-size-deltas-report: 'true' 53 | # sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} 54 | -------------------------------------------------------------------------------- /examples/WebRadio/web/iotstyle.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-family: Arial, Helvetica, sans-serif; 4 | font-size: 100%; 5 | } 6 | 7 | body, 8 | div, 9 | td, 10 | th, 11 | button { 12 | color: black; 13 | box-sizing: border-box; 14 | padding: 0; 15 | margin: 0; 16 | } 17 | 18 | svg.icon { 19 | width: 4em; 20 | height: 4em; 21 | } 22 | 23 | svg.icon .object { 24 | fill: #304878; 25 | fill-opacity: 1; 26 | } 27 | 28 | svg.icon .connect { 29 | fill: green; 30 | fill-opacity: 1; 31 | } 32 | 33 | p, 34 | h1, 35 | h2, 36 | h3 { 37 | margin: 0 0 0.5em 0; 38 | } 39 | 40 | h1, 41 | h2 { 42 | font-family: Comic Sans MS, Verdana, Helvetica, Arial; 43 | color: black; 44 | } 45 | 46 | h1 { 47 | font-size: 2.2em; 48 | } 49 | 50 | h2 { 51 | font-size: 1.6em; 52 | } 53 | 54 | h3 { 55 | font-size: 1em; 56 | font-weight: bold; 57 | } 58 | 59 | 60 | /* Grid Design */ 61 | 62 | .container { 63 | margin-right: auto; 64 | margin-left: auto; 65 | } 66 | 67 | 68 | /* row with 0.5rem gutter */ 69 | 70 | .row { 71 | display: flex; 72 | flex-direction: row; 73 | flex-wrap: wrap; 74 | margin: -0.5rem 0 0.5rem -0.5rem; 75 | } 76 | 77 | .col { 78 | flex: 0 0 2rem; 79 | margin: 0.5rem 0 0 0.5rem; 80 | } 81 | 82 | .col.stretch { 83 | flex-grow: 1; 84 | } 85 | 86 | button { 87 | font-size: 1em; 88 | border: 0.2em solid gray; 89 | border-radius: 0.5em; 90 | padding: 0.6rem 0.8rem; 91 | cursor: pointer; 92 | text-align: center; 93 | white-space: nowrap; 94 | } 95 | 96 | button.activ { 97 | background-color: #acc1e4 !important; 98 | border-color: #203050 !important; 99 | } 100 | 101 | label, 102 | input, 103 | button, 104 | textarea, 105 | img.INPUTFUNC { 106 | display: inline-block; 107 | height: 2.4rem; 108 | font-size: 1rem; 109 | line-height: 1rem; 110 | border-radius: 0.2rem; 111 | vertical-align: baseline; 112 | border: 0.1rem solid gray; 113 | } -------------------------------------------------------------------------------- /src/RDA5807FP.cpp: -------------------------------------------------------------------------------- 1 | /// \file RDA5807FP.cpp 2 | /// \brief Implementation for the radio library to control the RDA5807FP radio chip. 3 | /// 4 | /// \author Marcus Degenkolbe, http://www.degenkolbe.eu 5 | /// \copyright Copyright (c) 2023 by Marcus Degenkolbe.\n 6 | /// This work is licensed under a BSD style license.\n 7 | /// See http://www.mathertel.de/License.aspx 8 | /// 9 | /// This library enables the use of the radio chip RDA5807FP from http://www.rdamicro.com/. 10 | /// 11 | /// More documentation and source code is available at http://www.mathertel.de/Arduino 12 | /// 13 | /// History: 14 | /// -------- 15 | /// * 10.01.2023 created. 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | // ----- Register Definitions ----- 24 | 25 | // this chip only supports FM mode 26 | 27 | #define RADIO_REG_R4 0x04 28 | #define RADIO_REG_R4_I2S 0xF0 29 | 30 | // regisrer R6 contains I2S options 31 | #define RADIO_REG_R6 0x06 32 | #define RADIO_REG_R6_I2S_WS_CNT 0xF0 33 | #define RADIO_REG_R6_I2S_DATA_SIGNED 0x200 34 | #define RADIO_REG_R6_I2S_MODE 0x1000 35 | 36 | // ----- implement 37 | 38 | // initialize the extra variables in RDA5807FP 39 | RDA5807FP::RDA5807FP() 40 | { 41 | 42 | } 43 | 44 | // Configures I2S output via GPIO1 (WS), GPIO2 (SD/DOUT), GPIO3 (SCK/BCLK) 45 | void RDA5807FP::SetupI2S(RDA5807FP_I2SConfig config) 46 | { 47 | // enable I2S in register 0x4 48 | registers[RADIO_REG_R4] = (registers[RADIO_REG_R4] & ~RADIO_REG_R4_I2S) | ((config.enabled << 6) & RADIO_REG_R4_I2S); 49 | _saveRegister(RADIO_REG_R4); 50 | 51 | // set I2S options in register 0x6 52 | registers[RADIO_REG_R6] = (registers[RADIO_REG_R6] & ~RADIO_REG_R6_I2S_DATA_SIGNED) | ((config.data_signed << 9) & RADIO_REG_R6_I2S_DATA_SIGNED); 53 | registers[RADIO_REG_R6] = (registers[RADIO_REG_R6] & ~RADIO_REG_R6_I2S_MODE) | ( (config.slave << 12) & RADIO_REG_R6_I2S_MODE); 54 | registers[RADIO_REG_R6] = (registers[RADIO_REG_R6] & ~RADIO_REG_R6_I2S_WS_CNT) | ((config.rate << 4) & RADIO_REG_R6_I2S_WS_CNT); 55 | _saveRegister(RADIO_REG_R6); 56 | } 57 | 58 | // ----- internal functions ----- 59 | 60 | // The End. 61 | -------------------------------------------------------------------------------- /examples/LCDRadio/README.md: -------------------------------------------------------------------------------- 1 | # LCDRadio Example 2 | 3 | This example allows controlling the a supported radio chip by using 4 | 5 | * a rotary encoder 6 | * a push button 7 | * a LCD display attached to the i2c bus using a PCF8574 based adapter. 8 | 9 | The following additional libaries are required for this example: 10 | 11 | **LiquidCrystal_PCF8574** -- 12 | The library is used 13 | to control the LCD via I2C using a PCF8574 based i2c adapter. 14 | 15 | The LiquidCrystal_PCF8574 library is available using the Arduino Library. 16 | 17 | **RotaryEncoder** -- 18 | The library is used 19 | to process signals from the RotaryEncoder. 20 | 21 | The RotaryEncoder library is available using the Arduino Library. 22 | 23 | **OneButton** -- 24 | The library is used 25 | to process signals from a momentary input. 26 | 27 | The OneButton library is available using the Arduino Library. 28 | 29 | It works with processor boards for Arduino UNO, ESP8266 like NodeMCU 1.0 and ESP32. 30 | 31 | This sketch works with all of the implemented chips by using the common radio functions of the library. 32 | 33 | You have to modify the source code line where the radio variable is defined to the chip you use. 34 | There are out-commented lines for every chip in the sketch. 35 | Be sure to activate the lines for the RST signal for SI4703 radio chips. 36 | 37 | The Serial interface, the rotary encoder and the momentary button can be used 38 | for controlling the chip. 39 | Open the Serial Monitor window of the Arduino programming environment 40 | you can see verbose output from the library and can control the settings 41 | by entering commands in the input text line. 42 | 43 | The command ? (and enter) will display a short summary of the available commands. 44 | 45 | If you like to explore the chip specific settings you may use the "x" command 46 | that outputs the chip registers or other chip specific information. 47 | 48 | Have a look into the chip specific implementation for more details. 49 | 50 | Please read the README.md files in the TEST____ examples for more chip specific hints. 51 | -------------------------------------------------------------------------------- /examples/WebRadio/web/about.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Impressum 6 | 7 | 8 | 12 | 13 | 14 |

Impressum

15 | 16 |

On this web site you can find the published articles and public available files, source code and projects 17 | of Matthias Hertel.

18 |

Auf dieser Site finden Sie Publikationen und die öffentlich zugänglichen Dateien und Projekte von Matthias 19 | Hertel.

20 |

Contact / Kontakt

21 |

22 | adress

23 |
24 |
25 | Matthias Hertel 26 |
27 |
Dillinger Str. 49a
28 |
29 | Friedrichsdorf, 30 | 61381 31 | Germany 32 |
33 |
34 | 39 |
40 |
41 |

This work is licensed under a BSD style license. See 42 | http://www.mathertel.de/License.aspx

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