├── README ├── examples └── SimpleRadioFM │ └── SimpleRadioFM.ino ├── TEA5767.h └── TEA5767.cpp /README: -------------------------------------------------------------------------------- 1 | Library for interfacing the Arduino microcontroller with TEA5767 single chip FM radio 2 | 3 | -------------------------------------------------------------------------------- /examples/SimpleRadioFM/SimpleRadioFM.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | TEA5767 Radio; 7 | LiquidCrystal lcd(12,11,10,9,8,7); 8 | double old_frequency; 9 | double frequency; 10 | int search_mode = 0; 11 | int search_direction; 12 | unsigned long last_pressed; 13 | 14 | Button btn_forward(3, PULLUP); 15 | Button btn_backward(2, PULLUP); 16 | 17 | void setup() { 18 | Wire.begin(); 19 | Radio.init(); 20 | Radio.set_frequency(105.4); 21 | Serial.begin(9600); 22 | lcd.begin(16,2); 23 | lcd.clear(); 24 | } 25 | 26 | void loop() { 27 | 28 | unsigned char buf[5]; 29 | int stereo; 30 | int signal_level; 31 | double current_freq; 32 | unsigned long current_millis = millis(); 33 | 34 | if (Radio.read_status(buf) == 1) { 35 | current_freq = floor (Radio.frequency_available (buf) / 100000 + .5) / 10; 36 | stereo = Radio.stereo(buf); 37 | signal_level = Radio.signal_level(buf); 38 | lcd.setCursor(0,0); 39 | lcd.print("FM: "); lcd.print(current_freq); 40 | lcd.setCursor(0,1); 41 | if (stereo) lcd.print("STEREO "); else lcd.print("MONO "); 42 | //lcd.print(signal_level); 43 | } 44 | 45 | if (search_mode == 1) { 46 | if (Radio.process_search (buf, search_direction) == 1) { 47 | search_mode = 0; 48 | } 49 | } 50 | 51 | if (btn_forward.isPressed()) { 52 | last_pressed = current_millis; 53 | search_mode = 1; 54 | search_direction = TEA5767_SEARCH_DIR_UP; 55 | Radio.search_up(buf); 56 | delay(300); 57 | } 58 | 59 | if (btn_backward.isPressed()) { 60 | last_pressed = current_millis; 61 | search_mode = 1; 62 | search_direction = TEA5767_SEARCH_DIR_DOWN; 63 | Radio.search_down(buf); 64 | delay(300); 65 | } 66 | //delay(20); 67 | delay(50); 68 | } 69 | -------------------------------------------------------------------------------- /TEA5767.h: -------------------------------------------------------------------------------- 1 | /* 2 | * TEA5767.h 3 | * defintions for TEA5767 FM radio module. 4 | * Created by Andrey Karpov 5 | * Based on this project: http://kalum.posterous.com/arduino-with-tea5767-single-chip-radio-and-no 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2, or (at your option) any 10 | * later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20 | */ 21 | 22 | #ifndef _TEA5767_H_ 23 | #define _TEA5767_H_ 24 | 25 | /****************************** 26 | * Write mode register values * 27 | ******************************/ 28 | 29 | /* First register */ 30 | #define TEA5767_MUTE 0x80 /* Mutes output */ 31 | #define TEA5767_SEARCH 0x40 /* Activates station search */ 32 | /* Bits 0-5 for divider MSB */ 33 | 34 | /* Second register */ 35 | /* Bits 0-7 for divider LSB */ 36 | 37 | /* Third register */ 38 | 39 | /* Station search from botton to up */ 40 | #define TEA5767_SEARCH_UP 0x80 41 | 42 | /* Searches with ADC output = 10 */ 43 | #define TEA5767_SRCH_HIGH_LVL 0x60 44 | 45 | /* Searches with ADC output = 10 */ 46 | #define TEA5767_SRCH_MID_LVL 0x40 47 | 48 | /* Searches with ADC output = 5 */ 49 | #define TEA5767_SRCH_LOW_LVL 0x20 50 | 51 | /* if on, div=4*(Frf+Fif)/Fref otherwise, div=4*(Frf-Fif)/Freq) */ 52 | #define TEA5767_HIGH_LO_INJECT 0x10 53 | 54 | /* Disable stereo */ 55 | #define TEA5767_MONO 0x08 56 | 57 | /* Disable right channel and turns to mono */ 58 | #define TEA5767_MUTE_RIGHT 0x04 59 | 60 | /* Disable left channel and turns to mono */ 61 | #define TEA5767_MUTE_LEFT 0x02 62 | 63 | #define TEA5767_PORT1_HIGH 0x01 64 | 65 | /* Fourth register */ 66 | #define TEA5767_PORT2_HIGH 0x80 67 | /* Chips stops working. Only I2C bus remains on */ 68 | #define TEA5767_STDBY 0x40 69 | 70 | /* Japan freq (76-108 MHz. If disabled, 87.5-108 MHz */ 71 | #define TEA5767_JAPAN_BAND 0x20 72 | 73 | /* Unselected means 32.768 KHz freq as reference. Otherwise Xtal at 13 MHz */ 74 | #define TEA5767_XTAL_32768 0x10 75 | 76 | /* Cuts weak signals */ 77 | #define TEA5767_SOFT_MUTE 0x08 78 | 79 | /* Activates high cut control */ 80 | #define TEA5767_HIGH_CUT_CTRL 0x04 81 | 82 | /* Activates stereo noise control */ 83 | #define TEA5767_ST_NOISE_CTL 0x02 84 | 85 | /* If activate PORT 1 indicates SEARCH or else it is used as PORT1 */ 86 | #define TEA5767_SRCH_IND 0x01 87 | 88 | /* Fifth register */ 89 | 90 | /* By activating, it will use Xtal at 13 MHz as reference for divider */ 91 | #define TEA5767_PLLREF_ENABLE 0x80 92 | 93 | /* By activating, deemphasis=50, or else, deemphasis of 50us */ 94 | #define TEA5767_DEEMPH_75 0X40 95 | 96 | /***************************** 97 | * Read mode register values * 98 | *****************************/ 99 | 100 | /* First register */ 101 | #define TEA5767_READY_FLAG_MASK 0x80 102 | #define TEA5767_BAND_LIMIT_MASK 0X40 103 | /* Bits 0-5 for divider MSB after search or preset */ 104 | 105 | /* Second register */ 106 | /* Bits 0-7 for divider LSB after search or preset */ 107 | 108 | /* Third register */ 109 | #define TEA5767_STEREO_MASK 0x80 110 | #define TEA5767_IF_CNTR_MASK 0x7f 111 | 112 | /* Fourth register */ 113 | #define TEA5767_ADC_LEVEL_MASK 0xf0 114 | 115 | /* should be 0 */ 116 | #define TEA5767_CHIP_ID_MASK 0x0f 117 | 118 | /* Fifth register */ 119 | /* Reserved for future extensions */ 120 | #define TEA5767_RESERVED_MASK 0xff 121 | 122 | /* internal constants */ 123 | #define TEA5767_SEARCH_DIR_UP 1 124 | #define TEA5767_SEARCH_DIR_DOWN 2 125 | 126 | #include 127 | 128 | struct tea5767_ctrl 129 | { 130 | unsigned int port1:1; 131 | unsigned int port2:1; 132 | unsigned int high_cut:1; 133 | unsigned int st_noise:1; 134 | unsigned int soft_mute:1; 135 | unsigned int japan_band:1; 136 | unsigned int deemph_75:1; 137 | unsigned int pllref:1; 138 | unsigned int xtal_freq; 139 | }; 140 | 141 | class TEA5767 { 142 | private: 143 | tea5767_ctrl ctrl_data; 144 | int HILO; 145 | protected: 146 | int hilo_optimal (unsigned long freq); 147 | void set_frequency (int hilo, double freq); 148 | int ready (unsigned char *buf); 149 | int bl_reached (unsigned char *buf); 150 | public: 151 | TEA5767(); 152 | TEA5767(double initial_freq); 153 | void set_frequency (double freq); 154 | int read_status (unsigned char *buf); 155 | int signal_level (unsigned char *buf); 156 | int stereo (unsigned char *buf); 157 | double frequency_available (unsigned char *buf); 158 | void search_up (unsigned char *buf); 159 | void search_down (unsigned char *buf); 160 | int process_search (unsigned char *buf, int search_dir); 161 | void init(); 162 | }; 163 | 164 | #endif // _TEA5767_H_ 165 | -------------------------------------------------------------------------------- /TEA5767.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "TEA5767.h" 6 | 7 | TEA5767::TEA5767() { 8 | Wire.begin(); 9 | HILO = 1; 10 | } 11 | 12 | TEA5767::TEA5767(double initial_freq) { 13 | Wire.begin(); 14 | HILO = 1; 15 | set_frequency(initial_freq); 16 | } 17 | 18 | //calculate the optimial hi or lo injection mode for the freq is in hz 19 | //return 1 if high is the best, or 0 for low injection 20 | int TEA5767::hilo_optimal (unsigned long freq) { 21 | 22 | int signal_high = 0; 23 | int signal_low = 0; 24 | unsigned char buf[5]; 25 | 26 | set_frequency (1, (double) (freq + 450000) / 1000000); 27 | delay (30); 28 | 29 | // Read the signal level 30 | if (read_status (buf) == 1) { 31 | signal_high = signal_level (buf); 32 | } 33 | 34 | set_frequency (0, (double) (freq - 450000) / 1000000); 35 | delay (30); 36 | 37 | if (read_status (buf) == 1) { 38 | signal_low = signal_level (buf); 39 | } 40 | 41 | return (signal_high < signal_low) ? 1 : 0; 42 | } 43 | 44 | void TEA5767::set_frequency (int hilo, double freq) { 45 | unsigned char buffer[5]; 46 | unsigned div; 47 | int rc; 48 | 49 | memset (buffer, 0, 5); 50 | 51 | buffer[2] = 0; 52 | buffer[2] |= TEA5767_PORT1_HIGH; 53 | 54 | if (hilo == 1) 55 | buffer[2] |= TEA5767_HIGH_LO_INJECT; 56 | 57 | buffer[3] = 0; 58 | 59 | if (ctrl_data.port2) 60 | buffer[3] |= TEA5767_PORT2_HIGH; 61 | 62 | if (ctrl_data.high_cut) 63 | buffer[3] |= TEA5767_HIGH_CUT_CTRL; 64 | 65 | if (ctrl_data.st_noise) 66 | buffer[3] |= TEA5767_ST_NOISE_CTL; 67 | 68 | if (ctrl_data.soft_mute) 69 | buffer[3] |= TEA5767_SOFT_MUTE; 70 | 71 | if (ctrl_data.japan_band) 72 | buffer[3] |= TEA5767_JAPAN_BAND; 73 | 74 | buffer[3] |= TEA5767_XTAL_32768; 75 | buffer[4] = 0; 76 | 77 | if (ctrl_data.deemph_75) 78 | buffer[4] |= TEA5767_DEEMPH_75; 79 | 80 | if (ctrl_data.pllref) 81 | buffer[4] |= TEA5767_PLLREF_ENABLE; 82 | 83 | if (hilo == 1) 84 | div = (4 * (freq * 1000 + 225)) / 32.768; 85 | else 86 | div = (4 * (freq * 1000 - 225)) / 32.768; 87 | 88 | buffer[0] = (div >> 8) & 0x3f; 89 | buffer[1] = div & 0xff; 90 | 91 | Wire.beginTransmission (0x60); 92 | 93 | for (int i = 0; i < 5; i++) 94 | Wire.write (buffer[i]); 95 | 96 | Wire.endTransmission (); 97 | } 98 | 99 | /* Freq should be specifyed at X M hz */ 100 | void TEA5767::set_frequency (double freq) { 101 | HILO = hilo_optimal ((unsigned long) (freq * 1000000)); 102 | set_frequency (HILO, freq); 103 | } 104 | 105 | //read functions 106 | 107 | int TEA5767::read_status (unsigned char *buf) { 108 | memset (buf, 0, 5); 109 | Wire.requestFrom (0x60, 5); //reading TEA5767 110 | 111 | if (Wire.available ()) { 112 | for (int i = 0; i < 5; i++) { 113 | buf[i] = Wire.read (); 114 | } 115 | return 1; 116 | } else { 117 | return 0; 118 | } 119 | } 120 | 121 | int TEA5767::signal_level (unsigned char *buf) { 122 | int signal = ((buf[3] & TEA5767_ADC_LEVEL_MASK) >> 4); 123 | return signal; 124 | } 125 | 126 | int TEA5767::stereo (unsigned char *buf) { 127 | int stereo = (buf[2] & TEA5767_STEREO_MASK); 128 | return stereo ? 1 : 0; 129 | } 130 | 131 | //returns 1 if tuning completed or BL reached 132 | int TEA5767::ready (unsigned char *buf) { 133 | return (buf[0] & 0x80) ? 1 : 0; 134 | } 135 | 136 | //returns 1 if band limit is reached during searching 137 | int TEA5767::bl_reached (unsigned char *buf) { 138 | return (buf[0] & 0x40) ? 1 : 0; 139 | } 140 | 141 | //returns freq available in Hz 142 | double TEA5767::frequency_available (unsigned char *buf) { 143 | double freq_available; 144 | if (HILO == 1) 145 | freq_available = (((buf[0] & 0x3F) << 8) + buf[1]) * 32768 / 4 - 225000; 146 | else 147 | freq_available = (((buf[0] & 0x3F) << 8) + buf[1]) * 32768 / 4 + 225000; 148 | return freq_available; 149 | } 150 | 151 | void TEA5767::search_up (unsigned char *buf) { 152 | unsigned div; 153 | double freq_av; 154 | 155 | freq_av = frequency_available (buf); 156 | 157 | div = (4 * (((freq_av + 98304) / 1000000) * 1000000 + 225000)) / 32768; 158 | 159 | buf[0] = (div >> 8) & 0x3f; 160 | buf[1] = div & 0xff; 161 | 162 | buf[0] |= TEA5767_SEARCH; 163 | 164 | buf[2] = 0; 165 | 166 | buf[2] |= TEA5767_SEARCH_UP; 167 | buf[2] |= TEA5767_SRCH_MID_LVL; 168 | buf[2] |= TEA5767_HIGH_LO_INJECT; 169 | 170 | //buf[3] = 0x18; 171 | buf[3] = 0; 172 | 173 | if (ctrl_data.port2) 174 | buf[3] |= TEA5767_PORT2_HIGH; 175 | 176 | if (ctrl_data.high_cut) 177 | buf[3] |= TEA5767_HIGH_CUT_CTRL; 178 | 179 | if (ctrl_data.st_noise) 180 | buf[3] |= TEA5767_ST_NOISE_CTL; 181 | 182 | if (ctrl_data.soft_mute) 183 | buf[3] |= TEA5767_SOFT_MUTE; 184 | 185 | if (ctrl_data.japan_band) 186 | buf[3] |= TEA5767_JAPAN_BAND; 187 | 188 | buf[3] |= TEA5767_XTAL_32768; 189 | 190 | buf[4] = 0; 191 | 192 | if (ctrl_data.deemph_75) 193 | buf[4] |= TEA5767_DEEMPH_75; 194 | 195 | if (ctrl_data.pllref) 196 | buf[4] |= TEA5767_PLLREF_ENABLE; 197 | 198 | Wire.beginTransmission (0x60); 199 | 200 | for (int i = 0; i < 5; i++) 201 | Wire.write (buf[i]); 202 | 203 | Wire.endTransmission (); 204 | HILO = 1; 205 | } 206 | 207 | void TEA5767::search_down (unsigned char *buf) 208 | { 209 | unsigned div; 210 | double freq_av; 211 | 212 | freq_av = frequency_available (buf); 213 | 214 | div = (4 * (((freq_av - 98304) / 1000000) * 1000000 + 225000)) / 32768; 215 | 216 | buf[0] = (div >> 8) & 0x3f; 217 | buf[1] = div & 0xff; 218 | 219 | buf[0] |= TEA5767_SEARCH; 220 | 221 | buf[2] = 0; 222 | 223 | buf[2] |= TEA5767_SRCH_MID_LVL; 224 | buf[2] |= TEA5767_HIGH_LO_INJECT; 225 | 226 | buf[3] = 0; 227 | 228 | if (ctrl_data.port2) 229 | buf[3] |= TEA5767_PORT2_HIGH; 230 | 231 | if (ctrl_data.high_cut) 232 | buf[3] |= TEA5767_HIGH_CUT_CTRL; 233 | 234 | if (ctrl_data.st_noise) 235 | buf[3] |= TEA5767_ST_NOISE_CTL; 236 | 237 | if (ctrl_data.soft_mute) 238 | buf[3] |= TEA5767_SOFT_MUTE; 239 | 240 | if (ctrl_data.japan_band) 241 | buf[3] |= TEA5767_JAPAN_BAND; 242 | 243 | buf[3] |= TEA5767_XTAL_32768; 244 | 245 | buf[4] = 0; 246 | 247 | if (ctrl_data.deemph_75) 248 | buf[4] |= TEA5767_DEEMPH_75; 249 | 250 | if (ctrl_data.pllref) 251 | buf[4] |= TEA5767_PLLREF_ENABLE; 252 | 253 | Wire.beginTransmission (0x60); 254 | 255 | for (int i = 0; i < 5; i++) 256 | Wire.write (buf[i]); 257 | 258 | Wire.endTransmission (); 259 | 260 | HILO = 1; 261 | } 262 | 263 | //Returns 1 if search is finished, 0 if wrapped and new search initiated 264 | //TODO - To prevent endless looping add a static variable to abort if it has searched for more than 2 loops 265 | int TEA5767::process_search (unsigned char *buf, int search_dir) 266 | { 267 | if (ready (buf) == 1) { 268 | if (bl_reached (buf) == 1) { 269 | if (search_dir == TEA5767_SEARCH_DIR_UP) { 270 | //wrap down 271 | set_frequency (87.5); 272 | read_status (buf); 273 | search_up (buf); 274 | return 0; 275 | } else if (search_dir == TEA5767_SEARCH_DIR_DOWN) { 276 | //wrap up 277 | set_frequency (108); 278 | read_status (buf); 279 | search_down (buf); 280 | return 0; 281 | } 282 | } else { 283 | // search finished - round up the pll word and feed it back as recommended 284 | double rounded_freq; 285 | double freq_available = frequency_available (buf); 286 | rounded_freq = floor (freq_available / 100000 + .5) / 10; 287 | set_frequency (rounded_freq); 288 | return 1; 289 | } 290 | } 291 | } 292 | 293 | void TEA5767::init() { 294 | ctrl_data.port1 = 1; 295 | ctrl_data.port2 = 1; 296 | ctrl_data.high_cut = 1; 297 | ctrl_data.st_noise = 1; 298 | ctrl_data.soft_mute = 1; 299 | ctrl_data.deemph_75 = 0; 300 | ctrl_data.japan_band = 0; 301 | ctrl_data.pllref = 0; 302 | 303 | // unsigned long freq = 87500000; 304 | // set_frequency((float) freq / 1000000); 305 | 306 | } 307 | 308 | --------------------------------------------------------------------------------