Si4735 Library with RDS/RBDS control functions

Si4735.cpp

Committer:
kpatel70
Date:
2012-10-11
Revision:
0:ab340864b251

File content as of revision 0:ab340864b251:

/* 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]=' ';
    }
}