Si4703 FM Tuner Question

If we look at this code:

#include <SparkFunSi4703.h>
#include <Wire.h>

int resetPin = 2;
int SDIO = A4;
int SCLK = A5;
int STC = 3;

Si4703_Breakout radio(resetPin, SDIO, SCLK, STC);
int channel;
int volume;
char rdsBuffer[10];

void setup()
{
  Serial.begin(9600);
  Serial.println("\n\nSi4703_Breakout Test Sketch");
  Serial.println("===========================");  
  Serial.println("a b     Favourite stations");
  Serial.println("+ -     Volume (max 15)");
  Serial.println("u d     Seek up / down");
  Serial.println("r       Listen for RDS Data (15 sec timeout)");
  Serial.println("Send me a command letter.");
  

  radio.powerOn();
  radio.setVolume(0);
}

void loop()
{
  if (Serial.available())
  {
    char ch = Serial.read();
    if (ch == 'u') 
    {
      channel = radio.seekUp();
      displayInfo();
    } 
    else if (ch == 'd') 
    {
      channel = radio.seekDown();
      displayInfo();
    } 
    else if (ch == '+') 
    {
      volume ++;
      if (volume == 16) volume = 15;
      radio.setVolume(volume);
      displayInfo();
    } 
    else if (ch == '-') 
    {
      volume --;
      if (volume < 0) volume = 0;
      radio.setVolume(volume);
      displayInfo();
    } 
    else if (ch == 'a')
    {
      channel = 930; // Rock FM
      radio.setChannel(channel);
      displayInfo();
    }
    else if (ch == 'b')
    {
      channel = 974; // BBC R4
      radio.setChannel(channel);
      displayInfo();
    }
    else if (ch == 'r')
    {
      Serial.println("RDS listening");
      radio.readRDS(rdsBuffer, 15000);
      Serial.print("RDS heard:");
      Serial.println(rdsBuffer);      
    }
  }
}

void displayInfo()
{
   Serial.print("Channel:"); Serial.print(channel); 
   Serial.print(" Volume:"); Serial.println(volume); 
}

We see the line radio.readRDS(rdsBuffer, 15000);, however, this is blocking. The 15000 is how long (milliseconds) it will listen before timing out. Does anyone one know if there is a way to make it non blocking, so it can listen in the background while I do other things?

(Hope that's the right category? @moderators )

It's close enough.

1 Like

Shorter timeout....

It takes about 5000ms to read the RDS on a lot of stations, but I need 100ms or less. NO STATION displays data when the timeout is 500ms or less. Some stations take up to 12000ms.

Here is another library for controlling them....Appears to be non-blocking.

Well, it's Sparkfun's library - so they should know?

Sadly that library doesn't seem to document available commands, and I also can't change the frequency. It seems permanently set to 87.50

The available commands are seen by viewing the source....I agree it isn't as nice as sparkfuns lib.

1 Like

Frequency is hard coded in the examples sketch....However I don't know if you modified the code.

Yeah, I put in a bunch of different frequencies, but the Serial monitor always returned 87.50. Must admit, it's no where near as good as SparkFuns library.

Here is another library specifically designed for the Sparkfun boards.

It says documentation should be available at https://github.com/mathertel/Radio/blob/master/html/index.html - but that just gives a 404.
:frowning_face:

It also says, "A more detailed article is available at www.mathertel.de/Arduino/RadioLibrary.aspx." - but that really doesn't give any detail at all!
:frowning_face:

1 Like

Rather intriguing though, this chip can't understand anything other than FM, so why do we need to define that? Could be omitted or at the least put in the library.

The library is for multiple different boards....

2 Likes

@outbackhut asked at 4:45 am today....They haven't responded yet.

1 Like

I've had a look at the library:

void Si4703_Breakout::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;
		// Serial.print(si4703_registers[RDSD]); Serial.print(" ");
		// Serial.print(index);Serial.print(" ");
		// Serial.write(Dh);
		// Serial.write(Dl);
		// Serial.println();
      }
      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';
}

This is the relevant RDS info.
I've never edited or created a library, and not really sure how it all works, but is there a reason a better implementation of millis() couldn't be used?

Maybe something like this:

void Si4703_Breakout::readRDS(char* buffer, long timeout)
{
  static unsigned long lastCheckTime = 0;
  static boolean completed[] = {false, false, false, false};
  static int completedCount = 0;
  unsigned long currentTime = millis();

  if (currentTime - lastCheckTime >= timeout)
  {
    buffer[0] = '\0'; // Timeout reached, return an empty buffer
    return;
  }

  if (currentTime - lastCheckTime >= 40) // Check for RDS data every 40 milliseconds
  {
    lastCheckTime = currentTime;
    readRegisters();

    if (si4703_registers[STATUSRSSI] & (1 << RDSR))
    {
      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;
      }
    }
  }

  if (completedCount >= 4)
  {
    buffer[8] = '\0'; // Null-terminate the buffer
  }
}

I much prefer the Arduino forum, best out there in my opinion.

No, but you are allowed to test and change the code it's open source....Just remove/add the edited in/out functions in the header(.h file).

Here's the function from the library you're using, I found it on the SparkFun github:

void Si4703_Breakout::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;
		// Serial.print(si4703_registers[RDSD]); Serial.print(" ");
		// Serial.print(index);Serial.print(" ");
		// Serial.write(Dh);
		// Serial.write(Dl);
		// Serial.println();
      }
      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';
}

It looks like this is the loop that keeps you in the function for the full duration

while(completedCount < 4 && millis() < endTime) {

You could try breaking this into a second function, what is inside this while loop. Put the logic for the while loop in your main program and then call the part inside the while loop. There is still a delay(40) inside that function, so you may be able to get around that using the millis() command. I'm not sure if this would work, I haven't taken a detailed look at this or tested it, but perhaps you could get it to work!

1 Like

Is this sort of what you mean?

void Si4703_Breakout::readRDS(char* buffer, long timeout)
{ 
	long Time = millis();
  boolean completed[] = {false, false, false, false};
  int completedCount = 0;
  if ((Time + 100) == millis()){
    if(completedCount < 4 && millis() < 10000) {
	  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;
		  // Serial.print(si4703_registers[RDSD]); Serial.print(" ");
		  // Serial.print(index);Serial.print(" ");
		  // Serial.write(Dh);
		  // Serial.write(Dl);
		  // Serial.println();
        }
	    int time1 = millis();
        //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() >= 10000) {
		buffer[0] ='\0';
		return;
	}

  buffer[8] = '\0';
}

Now it never reads RDS, but is that the concept you are talking about?

Hello,
about 4 years ago I wrote a small sketch for an FM radio with the Si4703. I didn't use a library, but programmed everything in the sketch itself. As far as I remember, you don't have to wait for a full RDS message to be received. That is all done by the Si4703 itself, and it is sufficient to check at regular intervals whether a new message has been completely received. A blocking wait for this is nonsense in my opinion.

2 Likes