Si4735 Library with RDS/RBDS control functions

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