Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
/*
Si4703 LIBRARY FOR ATTINY CHIPS
Jeff Thompson | 2017 | jeffreythompson.org
See .h file for credit where credit is due.
*/
#include "Arduino.h"
#include "Si4703_ATtiny.h"
#include "TinyWireM.h"
Si4703_ATtiny::Si4703_ATtiny(int resetPin, int sdioPin, int sclkPin) {
_resetPin = resetPin;
_sdioPin = sdioPin;
_sclkPin = sclkPin;
}
void Si4703_ATtiny::powerOn() {
si4703_init();
}
void Si4703_ATtiny::setChannel(int channel) {
// freq(MHz) = 0.200(in USA) * Channel + 87.5MHz
int newChannel = channel * 10; // 973 * 10 = 9730
newChannel -= 8750; // 9730 - 8750 = 980
newChannel /= 10; // 980 / 10 = 98
// these steps come from AN230 page 20 rev 0.5
readRegisters();
si4703_registers[CHANNEL] &= 0xFE00; // clear out the channel bits
si4703_registers[CHANNEL] |= newChannel; // mask in the new channel
si4703_registers[CHANNEL] |= (1 << TUNE); // set the TUNE bit to start
updateRegisters();
// poll to see if STC is set
while (1) {
readRegisters();
if ( (si4703_registers[STATUSRSSI] & (1 << STC)) != 0) break; // tuning complete!
}
readRegisters();
si4703_registers[CHANNEL] &= ~(1 << TUNE); // clear the tune after a tune has completed
updateRegisters();
// wait for the si4703 to clear the STC as well
while (1) {
readRegisters();
if ( (si4703_registers[STATUSRSSI] & (1 << STC)) == 0) break; // tuning complete!
}
}
int Si4703_ATtiny::seekUp() {
return seek(SEEK_UP);
}
int Si4703_ATtiny::seekDown() {
return seek(SEEK_DOWN);
}
void Si4703_ATtiny::setVolume(int volume) {
readRegisters(); // read the current register set
if (volume < 0) volume = 0;
if (volume > 15) volume = 15;
si4703_registers[SYSCONFIG2] &= 0xFFF0; // clear volume bits
si4703_registers[SYSCONFIG2] |= volume; // set new volume
updateRegisters(); // update
}
void Si4703_ATtiny::readRDS(char* buffer, long timeout) {
long endTime = millis() + timeout;
boolean completed[] = {false, false, false, false};
int completedCount = 0;
while (completedCount < 4 && millis() < endTime) {
readRegisters();
if (si4703_registers[STATUSRSSI] & (1 << RDSR)) {
// ls 2 bits of B determine the 4 letter pairs
// once we have a full set return
// if you get nothing after 20 readings return with empty string
uint16_t b = si4703_registers[RDSB];
int index = b & 0x03;
if (! completed[index] && b < 500) {
completed[index] = true;
completedCount ++;
char Dh = (si4703_registers[RDSD] & 0xFF00) >> 8;
char Dl = (si4703_registers[RDSD] & 0x00FF);
buffer[index * 2] = Dh;
buffer[index * 2 + 1] = Dl;
}
delay(40); // wait for the RDS bit to clear
}
else {
delay(30); // from AN230, using the polling method 40ms should
// be sufficient amount of time between checks
}
}
if (millis() >= endTime) {
buffer[0] = '\0';
return;
}
buffer[8] = '\0';
}
// to get the Si4703 inito 2-wire mode, SEN needs to be high and SDIO needs to be low after a reset
// the breakout board has SEN pulled high, but also has SDIO pulled high
// therefore, after a normal power up the Si4703 will be in an unknown state
// RST must be controlled
void Si4703_ATtiny::si4703_init() {
pinMode(_resetPin, OUTPUT);
pinMode(_sdioPin, OUTPUT); // SDIO is connected to A4 for I2C
digitalWrite(_sdioPin, LOW); // a low SDIO indicates a 2-wire interface
digitalWrite(_resetPin, LOW); // put Si4703 into reset
delay(1); // some delays while we allow pins to settle
digitalWrite(_resetPin, HIGH); // bring Si4703 out of reset with SDIO set to low and
// SEN pulled high with on-board resistor
delay(1); // allow Si4703 to come out of reset
TinyWireM.begin(); // now that the unit is reset and I2C inteface mode, we need to begin I2C
readRegisters(); // read the current register set
si4703_registers[0x07] = 0x8100; // enable the oscillator, from AN230 page 9, rev 0.61 (works)
updateRegisters(); // update
delay(500); // wait for clock to settle - from AN230 page 9
readRegisters(); // read the current register set
si4703_registers[POWERCFG] = 0x4001; // enable the IC
si4703_registers[SYSCONFIG1] |= (1 << RDS); // enable RDS
si4703_registers[SYSCONFIG1] |= (1 << DE); // 50kHz Europe setup
si4703_registers[SYSCONFIG2] |= (1 << SPACE0); // 100kHz channel spacing for Europe
si4703_registers[SYSCONFIG2] &= 0xFFF0; // clear volume bits
si4703_registers[SYSCONFIG2] |= 0x0001; // set volume to lowest
updateRegisters(); // update
delay(110); // max powerup time, from datasheet page 13
}
// read the entire register control set from 0x00 to 0x0F
void Si4703_ATtiny::readRegisters() {
// Si4703 begins reading from register upper register of 0x0A and reads to 0x0F, then loops to 0x00
// we want to read the entire register set from 0x0A to 0x09 = 32 bytes
TinyWireM.requestFrom(SI4703, 32);
// wait for 16 words/32 bytes to come back from slave I2C device
while (TinyWireM.available() < 32) ;
// we may want some time-out error here...
// remember, register 0x0A comes in first so we have to shuffle the array around a bit
for (int x = 0x0A ; ; x++) { // read in these 32 bytes
if (x == 0x10) x = 0; // loop back to zero
si4703_registers[x] = TinyWireM.read() << 8;
si4703_registers[x] |= TinyWireM.read();
if (x == 0x09) break; // we're done!
}
}
// write the current 9 control registers (0x02 to 0x07) to the Si4703
// it's a little weird, you don't write an I2C address - the Si4703 assumes
// you are writing to 0x02 first, then increments
byte Si4703_ATtiny::updateRegisters() {
TinyWireM.beginTransmission(SI4703);
// a write command automatically begins with register 0x02 so no need to send a write-to address
// first we send the 0x02 to 0x07 control registers
// in general, we should not write to registers 0x08 and 0x09
for (int regSpot = 0x02 ; regSpot < 0x08 ; regSpot++) {
byte high_byte = si4703_registers[regSpot] >> 8;
byte low_byte = si4703_registers[regSpot] & 0x00FF;
TinyWireM.write(high_byte); // upper 8 bits
TinyWireM.write(low_byte); // lower 8 bits
}
// end this transmission
byte ack = TinyWireM.endTransmission();
if (ack != 0) { // we have a problem!
return (FAIL);
}
return (SUCCESS);
}
// seeks out the next available station
// returns the freq if it made it, returns zero if failed
int Si4703_ATtiny::seek(byte seekDirection) {
readRegisters();
// set seek mode wrap bit
si4703_registers[POWERCFG] |= (1 << SKMODE); // allow wrap
if (seekDirection == SEEK_DOWN) si4703_registers[POWERCFG] &= ~(1 << SEEKUP); // seek down is the default upon reset
else si4703_registers[POWERCFG] |= 1 << SEEKUP; // set the bit to seek up
si4703_registers[POWERCFG] |= (1 << SEEK); // start seek
updateRegisters(); // seeking will now start
// poll to see if STC is set
while (1) {
readRegisters();
if ((si4703_registers[STATUSRSSI] & (1 << STC)) != 0) break; // tuning complete!
}
readRegisters();
int valueSFBL = si4703_registers[STATUSRSSI] & (1 << SFBL); // store the value of SFBL
si4703_registers[POWERCFG] &= ~(1 << SEEK); // clear the seek bit after seek has completed
updateRegisters();
// wait for the si4703 to clear the STC as well
while (1) {
readRegisters();
if ( (si4703_registers[STATUSRSSI] & (1 << STC)) == 0) break; // tuning complete!
}
if (valueSFBL) { // the bit was set indicating we hit a band limit or failed to find a station
return (0);
}
return getChannel();
}
// reads the current channel from READCHAN
// returns a number like 973 for 97.3MHz
int Si4703_ATtiny::getChannel() {
readRegisters();
int channel = si4703_registers[READCHAN] & 0x03FF; // mask out everything but the lower 10 bits
// freq(MHz) = 0.100 (in Europe) * channel + 87.5MHz
// X = 0.1 * channel + 87.5
// 98 + 875 = 973
channel += 875;
return (channel);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.