Si4735 Library with RDS/RBDS control functions
Diff: Si4735.cpp
- Revision:
- 0:ab340864b251
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Si4735.cpp Thu Oct 11 20:10:09 2012 +0000 @@ -0,0 +1,639 @@ +/* mbed Si4735 Library + * Brett Wilson and Brett Berry + * Georgia Tech ECE 4180 + * Note: this code has only been tested for FM. + * Ported from ... + + * Arduino Si4735 Library + * Written by Ryan Owens for SparkFun Electronics + * 5/17/11 + * + * This library is for use with the SparkFun Si4735 Shield + * Released under the 'Buy Me a Beer' license + * (If we ever meet, you buy me a beer) + * + * See the header file for better function documentation. + * + * See the example sketches to learn how to use the library in your code. +*/ + +#include "Si4735.h" +#include "mbed.h" + +//This is just a constructor. +Si4735::Si4735(PinName sda, PinName scl, PinName RST_, Serial *pc) : + _RST_(RST_) +{ + i2c_ = new I2C(sda, scl); + i2c_->frequency(100000); + this->pc = pc; + + strcpy(pty_prev, " "); + _locale = NA; + _ab = 0; + _year = 0; + _month = 0; + _day = 0; + _hour = 0; + _minute = 0; + _newRadioText = 0; + clearRDS(); + +} + + +void Si4735::begin(char mode) +{ + _mode = mode; + //Set the initial volume level. + _currentVolume=63; + + // Reset the Si4735 + _RST_ = 0; + wait(.2); + _RST_ = 1; + wait(.2); + + + //Send the POWER_UP command + switch(_mode) { + case FM: + sprintf(command, "%c%c%c", 0x01, 0x10, 0x05); + break; + case AM: + case SW: + case LW: + sprintf(command, "%c%c%c", 0x01, 0x11, 0x05); + break; + default: + return; + } + sendCommand(command, 3); + wait(.2); + + // Set the RCLK to 32768Hz + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x02, 0x01, 0x80, 0x00); + sendCommand(command, 6); + wait(.2); + + // Set default frequency to 99.7 MHz FM + sprintf(command, "%c%c%c%c", 0x20, 0x00, 0x26, 0xF2); + sendCommand(command, 4); + wait(.2); + + //Set the volume to the current value. + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x40, 0x00, 0x00, _currentVolume); + sendCommand(command, 6); + wait(.2); + + //Disable Mute + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x40, 0x01, 0x00, 0x00); + sendCommand(command, 6); + wait(.2); + + setProperty(0x1502, 0xAA01); + + //Set the seek band for the desired mode (AM and FM can use default values) + switch(_mode) { + case SW: + //Set the lower band limit for Short Wave Radio to 2300 kHz + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x34, 0x00, 0x08, 0xFC); + sendCommand(command, 6); + wait(.001); + //Set the upper band limit for Short Wave Radio to 23000kHz + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x34, 0x01, 0x59, 0xD8); + sendCommand(command, 6); + wait(.001); + break; + case LW: + //Set the lower band limit for Long Wave Radio to 152 kHz + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x34, 0x00, 0x00, 0x99); + sendCommand(command, 6); + wait(.001); + //Set the upper band limit for Long Wave Radio to 279 kHz + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x34, 0x01, 0x01, 0x17); + sendCommand(command, 6); + wait(.001); + break; + default: + break; + } + +} + +/* +* Description: Tunes the Si4735 to a frequency +* +* Params: int frequency - The desired frequency in kHz +* +* Returns: True if tune was successful +* False if tune was unsuccessful +*/ +bool Si4735::tuneFrequency(int frequency) +{ + //Split the desired frequency into two character for use in the + //set frequency command. + char highByte = frequency >> 8; + char lowByte = frequency & 0x00FF; + + //Depending on the current mode, set the new frequency. + switch(_mode) { + case FM: + sprintf(command, "%c%c%c%c", 0x20, 0x00, highByte, lowByte); + break; + case AM: + case SW: + case LW: + sprintf(command, "%c%c%c%c", 0x40, 0x00, highByte, lowByte); + break; + default: + break; + } + sendCommand(command, 4); + wait(.1); + clearRDS(); + return true; +} + +//This function does not work unless you bypass the buffer chip! +int Si4735::getFrequency(void) +{ + char data[8]; + int freq = 0; + // FM only + i2c_->start(); + i2c_->write(address_write); + i2c_->write(0x22); + i2c_->write(0x02); + i2c_->stop(); + i2c_->start(); + i2c_->write(address_read); + for (int i=0; i<8; i++) + data[i] = i2c_->read(1); + //i2c_->read(address_read, data, 8, false); + i2c_->stop(); + freq = data[2]; + freq = (freq << 8) | data[3]; + return freq; +} + +bool Si4735::seekUp(void) +{ + //Use the current mode selection to seek up. + switch(_mode) { + case FM: + sprintf(command, "%c%c", 0x21, 0x0C); + sendCommand(command, 2); + break; + case AM: + case SW: + case LW: + sprintf(command, "%c%c%c%c%c%c", 0x41, 0x0C, 0x00, 0x00, 0x00, 0x00); + sendCommand(command, 6); + break; + default: + break; + } + wait(.001); + clearRDS(); + return true; +} + +bool Si4735::seekDown(void) +{ + //Use the current mode selection to seek down. + switch(_mode) { + case FM: + sprintf(command, "%c%c", 0x21, 0x04); + sendCommand(command, 2); + break; + case AM: + case SW: + case LW: + sprintf(command, "%c%c%c%c%c%c", 0x41, 0x04, 0x00, 0x00, 0x00, 0x00); + sendCommand(command, 6); + break; + default: + break; + } + wait(.001); + clearRDS(); + return true; +} + +void Si4735::volumeUp(void) +{ + //If we're not at the maximum volume yet, increase the volume + if(_currentVolume < 63) { + _currentVolume+=1; + //Set the volume to the current value. + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x40, 0x00, 0x00, _currentVolume); + sendCommand(command, 6); + wait(.01); + } +} + +void Si4735::volumeDown(void) +{ + //If we're not at the maximum volume yet, increase the volume + if(_currentVolume > 0) { + _currentVolume-=1; + //Set the volume to the current value. + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x40, 0x00, 0x00, _currentVolume); + sendCommand(command, 6); + wait(.01); + } +} + +void Si4735::mute(void) +{ + //Disable Mute + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x40, 0x01, 0x00, 0x03); + sendCommand(command, 6); + wait(.001); +} + +void Si4735::unmute(void) +{ + //Disable Mute + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, 0x40, 0x01, 0x00, 0x00); + sendCommand(command, 6); + wait(.001); +} + +void Si4735::end(void) +{ + sprintf(command, "%c", 0x11); + sendCommand(command, 1); + wait(.001); +} + + +void Si4735::setProperty(int address, int value) +{ + sprintf(command, "%c%c%c%c%c%c", 0x12, 0x00, (address>>8)&255, address&255, (value>>8)&255, value&255); + sendCommand(command, 6); + wait(0.01); +} + +bool Si4735::readRDS() +{ + char R[16]; + sprintf(command, "%c%c", 0x24, 0x00); + sendCommand(command, 2); + i2c_->start(); + i2c_->write(address_read); + for (int i=0; i<16; i++) + R[i] = i2c_->read(1); + i2c_->stop(); + + bool ps_rdy = false; + char pty = ((R[6]&3) << 3) | ((R[7] >> 5)&7); + ptystr(pty); + char type = (R[6]>>4) & 15; + bool version = bitRead(R[6], 4); + bool tp = bitRead(R[5], 4); + int pi; + if (version == 0) { + pi = R[4]; + pi = (pi << 8) | R[5]; + } else { + pi = R[8]; + pi = (pi << 8) | R[9]; + } + if(pi>=21672) { + _csign[0]='W'; + pi-=21672; + } else if(pi<21672 && pi>=4096) { + _csign[0]='K'; + pi-=4096; + } else { + pi=-1; + } + if(pi>=0) { + _csign[1]=char(pi/676+65);//char(pi/676); + _csign[2]=char((pi - 676*int(pi/676))/26+65);//char((pi-676*(_csign[1]))/26+65); + _csign[3]=char(((pi - 676*int(pi/676))%26)+65);//char((pi-676*(_csign[1]))%26+65); +//_csign[1]+=65; + _csign[4]='\0'; + } else { + _csign[0]='U'; + _csign[1]='N'; + _csign[2]='K'; + _csign[3]='N'; + _csign[4]='\0'; + } + if (type == 0) { + bool ta = bitRead(R[7], 4); + bool ms = bitRead(R[7], 3); + char addr = R[7] & 3; + bool diInfo = bitRead(R[7], 2); + +// Groups 0A & 0B: to extract PS segment we need blocks 1 and 3 + if (addr >= 0 && addr<= 3) { + if (R[10] != '\0') + _ps[addr*2] = R[10]; + if (R[11] != '\0') + _ps[addr*2+1] = R[11]; + ps_rdy=(addr==3); + } + printable_str(_ps, 8); + } + + else if (type == 2) { + int addressRT = R[7] & 0xf; // Get rightmost 4 bits + bool ab = bitRead(R[7], 4); + bool cr = 0; //indicates that a carriage return was received + char len = 64; + if (version == 0) { + if (addressRT >= 0 && addressRT <= 15) { + if (R[8] != 0x0D ) + _disp[addressRT*4] = R[8]; + else { + len=addressRT*4; + cr=1; + } + if (R[9] != 0x0D) + _disp[addressRT*4+1] = R[9]; + else { + len=addressRT*4+1; + cr=1; + } + if (R[10] != 0x0D) + _disp[addressRT*4+2] = R[10]; + else { + len=addressRT*4+2; + cr=1; + } + if (R[11] != 0x0D) + _disp[addressRT*4+3] = R[11]; + else { + len=addressRT*4+3; + cr=1; + } + } + } else { + if (addressRT >= 0 && addressRT <= 7) { + if (R[10] != '\0') + _disp[addressRT*2] = R[10]; + if (R[11] != '\0') + _disp[addressRT*2+1] = R[11]; + } + } + if(cr) { + for (char i=len; i<64; i++) _disp[i] = ' '; + } + if (ab != _ab) { + for (char i=0; i<64; i++) _disp[i] = ' '; + _disp[64] = '\0'; + _newRadioText=1; + } else { + _newRadioText=0; + } + _ab = ab; + printable_str(_disp, 64); + } + + else if (type == 4 && version == 0) { + unsigned long MJD = (R[7]&3<<15 | (unsigned int)(R[8])<<7 | (R[9]>>1)&127)&0x01FFFF; + unsigned int Y=(MJD-15078.2)/365.25; + unsigned int M=(MJD-14956.1-(unsigned int)(Y*365.25))/30.6001; + char K; + if(M==14||M==15) + K=1; + else + K=0; + _year=(Y+K)%100; + _month=M-1-K*12; + _day=MJD-14956-(unsigned int)(Y*365.25)-(unsigned int)(M*30.6001); + + char sign=(((R[11]>>5)&1)/(-1)); + if(sign==0)sign=1; //Make sign a bipolar variable + char offset=sign*(R[11]&31); + + _hour=((R[9]&1)<<4) | ((R[10]>>4)&15); + _minute=((R[10]&15)<<2 | (R[11]>>6)&3); + _hour= (_hour + (offset/2) + 24)%24; + _minute=(_minute + (offset)%2*30 + 60)%60; + } + wait(0.04); + return ps_rdy; +} + +void Si4735::getRDS() +{ + strcpy(tuned.programService, _ps); + strcpy(tuned.radioText, _disp); + strcpy(tuned.programType, _pty); + strcpy(tuned.callSign, _csign); + tuned.newRadioText=_newRadioText; +} + +void Si4735::getTime() +{ + date.year=_year; + date.month=_month; + date.day=_day; + date.hour=_hour; + date.minute=_minute; +} + +bool Si4735::getIntStatus(void) +{ + char response; + i2c_->start(); + i2c_->write(address_write); + i2c_->write(0x14); + i2c_->stop(); + + i2c_->start(); + i2c_->write(address_read); + response = i2c_->read(1); + i2c_->stop(); + (*pc).printf("Int Status Response: %c\n", response); + + if(response == NULL) { + return false; + } + return true; +} + +void Si4735::clearRDS(void) +{ + char i; + for(i=0; i<64; i++) _disp[i] ='\0'; + for(i=0; i<8; i++) _ps[i] ='\0'; + for(i=0; i<16; i++) _pty[i] ='\0'; + for(i=0; i<4; i++) _csign[i]='\0'; +} + +void Si4735::setLocale(char locale) +{ + _locale=locale; +//Set the deemphasis to match the locale + switch(_locale) { + case NA: + setProperty(0x1100, 0x0002); + break; + case EU: + setProperty(0x1100, 0x0001); + break; + default: + break; + } +} + +char Si4735::getLocale(void) +{ + return _locale; +} + +void Si4735::ptystr(char pty) +{ +// Translate the Program Type bits to the RBDS 16-character fields + if(pty>=0 && pty<32) { + char* pty_LUT[51] = { + " None ", + " News ", + " Information ", + " Sports ", + " Talk ", + " Rock ", + " Classic Rock ", + " Adult Hits ", + " Soft Rock ", + " Top 40 ", + " Country ", + " Oldies ", + " Soft ", + " Nostalgia ", + " Jazz ", + " Classical ", + "Rhythm and Blues", + " Soft R & B ", + "Foreign Language", + "Religious Music ", + " Religious Talk ", + " Personality ", + " Public ", + " College ", + " Reserved -24- ", + " Reserved -25- ", + " Reserved -26- ", + " Reserved -27- ", + " Reserved -28- ", + " Weather ", + " Emergency Test ", + " !!!ALERT!!! ", + "Current Affairs ", + " Education ", + " Drama ", + " Cultures ", + " Science ", + " Varied Speech ", + " Easy Listening ", + " Light Classics ", + "Serious Classics", + " Other Music ", + " Finance ", + "Children's Progs", + " Social Affairs ", + " Phone In ", + "Travel & Touring", + "Leisure & Hobby ", + " National Music ", + " Folk Music ", + " Documentary " + }; + if(_locale==NA) { + strcpy(_pty, pty_LUT[pty]); + } else if(_locale==EU) { + char LUT[32] = {0, 1, 32, 2, + 3, 33, 34, 35, + 36, 37, 9, 5, + 38, 39, 40, 41, + 29, 42, 43, 44, + 20, 45, 46, 47, + 14, 10, 48, 11, + 49, 50, 30, 31 + }; + strcpy(_pty, pty_LUT[LUT[pty]]); + } else { + strcpy(_pty, " LOCALE UNKN0WN "); + } + } else { + strcpy(_pty, " PTY ERROR "); + } +} + +void Si4735::refreshDisplay(int next_stn) +{ + bool ps_rdy = readRDS(); + if(!ps_rdy) + return; + getRDS(); + //getTime(); + bool refresh = false; + if(strcmp(tuned.programType,pty_prev)) { + //refresh = true; + strcpy(pty_prev,tuned.programType); + } + if (strlen(tuned.programService) == 8) { + if(ps_rdy) { + if(strcmp(tuned.programService,ps_prev)) { + refresh = true; + strcpy(ps_prev,tuned.programService); + } + } + } else if(strcmp(tuned.programType," None ")) { + if(!strcmp("01234567",ps_prev)) { + strcpy(tuned.programService, "mBed-Rdo"); + strcpy(ps_prev,"01234567"); + //refresh = true; + } + } + if(!tuned.newRadioText) { + strcpy(RadioText, tuned.radioText); + } + + if(refresh) { + (*pc).printf("\x1B[2J"); + (*pc).printf("\x1B[H"); + (*pc).printf("Current Station: %3.2f MHz FM\n", float(getFrequency())/100.0); + (*pc).printf("Program Type = %s\n", tuned.programType); + (*pc).printf("Program Service = %s\n", tuned.programService); + (*pc).printf("Radio Text = %s\n", RadioText); + (*pc).printf("Call Sign = %s\n", tuned.callSign); + //(*pc).printf("Time = %2d:%2d\n", date.hour, date.minute); + } +} + + +/******************************************* +* +* Private Functions +* +*******************************************/ + +void Si4735::sendCommand(char * command, int length) +{ + //ack = i2c_->write(address_write, command, length, false); + i2c_->start(); + bool ack = i2c_->write(address_write); + for(int i=0; i<length; i++) + ack = i2c_->write(command[i]); + char CTS = i2c_->read(1); + i2c_->stop(); +} + + +bool Si4735::bitRead(char value, int index) +{ + return ((value >> index) & 1) == 1; +} +void Si4735::printable_str(char * str, int length) +{ + for(int i=0; i<length; i++) { + if( (str[i]!=0 && str[i]<32) || str[i]>126 ) str[i]=' '; + } +} \ No newline at end of file