pu2clr / SI470X

It is an Arduino Library for the SI4702 and SI4703, BROADCAST RECEIVER.
MIT License
22 stars 13 forks source link

Suggestions for modifications #2

Open drp0 opened 3 years ago

drp0 commented 3 years ago

Your library is very useful, thanks.

The rds transfer functions are particularly useful. There is probably work to do on checking whether the rds is a) Error free b) Relevant to the current channel after the channel has changed

I could not eradicate these issues. I was able to improve reliability with a few added modifications:

In SI470x.cpp 1) add interrupt reg04->refined.RDSIEN = 1; // was 0 = no interrupt drp

2) add link interrupt: reg04->refined.GPIO1 = reg04->refined.GPIO2 = reg04->refined.GPIO3 = 0; reg04->refined.GPIO2 = 1; // + drp

3) check for number of errors before returning true in getDdsReady(): bool SI470X::getRdsReady() { getStatus(); if (reg0a->refined.RDSR) { // drp re-write from: return reg0a->refined.RDSR; if ((reg0a->refined.BLERA) < 6) return true; } return false; };

4) Add get the number of rds errors: int SI470X::getRdsErrors() { // drp added getStatus(); if (reg0a->refined.RDSR) return reg0a->refined.BLERA; else return -1; };

In SI470x.h : add to public int getRdsErrors(); // drp

This is a working example for the reasonably priced 128 x 128 oled screen at https://shop.pimoroni.com/products/1-12-oled-breakout?variant=29421050757203 It uses the interrupt from GPIO2, attached to D3, to initiate rds transfer. There are options for Serial output and polled rds transfer.

//  si470x_RDS_oled3.ino
//  D.R.Patterson (drp)
//  15/11/2020
//  Adapted from example in U8x8lib library
//  https://github.com/pu2clr/SI470X
//  Requires drp modified version of the SI470X library

//  Will work with headphones / lineout
//  With laptop:
//    radio volume 2
//    use TECNET usb input- volume < 5
//    set audio input to listen - output spakers

//  Arduino Pro Mini and SI4703 wire up

//  | Device  Si470X |  Arduino Pin  |
//  | ---------------| ------------  |
//  | RESET          |     D2        |
//  | SDIO           |     A4        |
//  | SCLK           |     A5        |
//  | GPIO2          |     D3        |

//  ATTENTION:
//    Best to avoid using the computer connected to the mains during testing.
//    Run the computer on it's battery.
//  By Ricardo Lima Caratti, 2020.

#include <SI470X.h>
#include <U8x8lib.h>
#include <Wire.h>

bool haveOled   = true;
bool useSerial  = false;
bool useIrq     = true;
bool useRds     = true;

U8X8_SH1107_PIMORONI_128X128_HW_I2C u8x8(U8X8_PIN_NONE);

const byte resetPin = 2;          // radio reset
const byte SDIO     = A4;         // sda
const byte SCLK     = A5;         // scl
const byte pot      = A3;         // volume pot
const byte potVs    = A2;         // 3v3 supply for volume pot
const byte led      = A1;         // stereo led indicator
const byte bat      = A0;         // read battery voltage across potential divider
const float m       = 1/0.3222;   // battery voltage multiplier 1 / potential divider ratio
const byte favP     = 4;          // favourite select port
const byte up       = 5;          // frequency up select port
const byte down     = 6;          // frequency down select port
const byte rdsP     = 7;          // tggle RDS port
const byte GPIO2    = 3;          // radio irq detect pin 

unsigned int fav1 = 885;    // Radio 2
unsigned int fav2 = 892;    // Pride Radio
unsigned int fav3 = 907;    // classical R3
unsigned int fav4 = 929;    // radio 4
unsigned int fav5 = 936;    // Radio Tyneside
unsigned int fav6 = 954;    // BBC Radio Newcastle
unsigned int fav7 = 971;    // metro
unsigned int fav8 = 975;    // smooth radio ne
unsigned int fav9 = 981;    // Radio 1 (loud)
unsigned int fav10 = 1003;  // classical (loud)
unsigned int fav11 = 1018;  // Heart 
unsigned int fav12 = 1053;  // Capital NE
//unsigned int fav12 = 1075;  // Smooth Radio NE (weak)

String station[] = {
"BBC 2", "Pride", "BBC 3", 
"BBC 4", "Tyneside", "Newcastle", 
"Metro", "Smooth", "BBC 1",
"Classical", "Heart", "Capital NE",
};

String fav        = "12342567890*/";
byte volume       = 8;
bool useVolPot    = true;
byte favNow       = 1;
unsigned int Channel;

#define RESET_PIN 2

#define MAX_DELAY_RDS 80   // 40ms - polling method
#define MAX_DELAY_STATUS   2000 

unsigned long rds_elapsed;
unsigned long status_elapsed;
unsigned long vstart;
volatile bool haveRds = false;

SI470X rx;

void irqDetect(){
haveRds = true;
}

void setup(){
  if (useSerial){
  Serial.begin(115200);
    while (!Serial) delay(100);
  }
  if (useSerial) Serial.println(F("\nPU2CLR SI470X Arduino Library."));
pinMode(potVs, OUTPUT);
  if (useVolPot) digitalWrite(potVs, HIGH);
pinMode(favP, INPUT_PULLUP);
pinMode(up, INPUT_PULLUP);
pinMode(down, INPUT_PULLUP);
pinMode(rdsP, INPUT_PULLUP);
pinMode(led, OUTPUT);
pinMode(GPIO2, INPUT_PULLUP);

Wire.begin();
Wire.beginTransmission(0x3C);
  if (Wire.endTransmission() == 0) {
  u8x8.begin();
  u8x8.setFont(u8x8_font_8x13B_1x2_f);
  u8x8.clear();
  u8x8.print(F("FM Radio"));
  //u8x8.setCursor(9, 14); u8x8.print(F("dBuV"));
  haveOled = true;  
  }else{
    if (useSerial) Serial.println(F("No oled"));
  Wire.end();
  haveOled = false;
  }

rx.setup(RESET_PIN, A4);  // A4 SDA pin  for Arduino ATmega328
rx.setBand(0);              // Europe- do not setSpace(1) as reception stops

rx.setRDS(true);          // Turns RDS on

  if (useVolPot) {
  potVolume();
  }else{
  rx.setVolume(volume);
  }

delay(500);

// Select a station with RDS service in your place
  if (useSerial) {Serial.print(F("\nTuning ")); Serial.println(fav1);}
setFav();
// Enable SDR
rx.setRds(true);
rx.setRdsMode(1);   // verbose
rx.setMono(false);

  if (useSerial) showHelp();
rds_elapsed     = millis();
status_elapsed  = rds_elapsed;
vstart          = rds_elapsed;
attachInterrupt(digitalPinToInterrupt(GPIO2), irqDetect, FALLING);
}

void loop() {
unsigned long tnow;
  if (useRds){
    if (useIrq) {
    // check flag from interrupt line
      if (haveRds) {
      checkRDS(false);
      haveRds= false;
      }   
    }else{
    // polling rds
      if ((millis() - rds_elapsed) > MAX_DELAY_RDS ) {  
        if ( rx.getRdsReady() ) checkRDS(true);
      rds_elapsed = millis();
      }
    }
  }

  if (useVolPot)              potVolume();
  if (digitalRead(favP) == 0) setFav();
  if (digitalRead(up) == 0)   setUp();
  if (digitalRead(down) == 0) setDown();
  if (digitalRead(rdsP) == 0) setRds();

tnow = millis();
  if ((tnow - status_elapsed) > MAX_DELAY_STATUS ) {
  status_elapsed = tnow;
  showStatus();
  }

  if(useSerial) checkSerial();

tnow = millis(); 
  if ((tnow - vstart) > 999) {
  vstart = tnow;
  batVolt();
  }
}

/*********************************************************
   RDS
 *********************************************************/
char *rdsMsg;
char *stationName;
char *rdsTime;

void showCount(int errcount){
u8x8.setCursor(13,6);
  if(errcount == 0){
  u8x8.print(F("   ")); 
  }else{
  u8x8.print("E"); u8x8.print(errcount);
    if((errcount < 10) && (errcount > -1)) u8x8.print(" ");
  }
}

void checkRDS(bool polling){
int P;
String tmp;
  if (polling){
    if (!rx.getRdsReady()) return;
  }
int errCount = rx.getRdsErrors();             // get the number of errors
  if ( (rdsMsg = rx.getRdsText2A()) != NULL) {    
    if (useSerial) Serial.println(rdsMsg);
    if (haveOled) {
    wipe(14); wipe(12); wipe(10); wipe(8);
    tmp = rdsMsg;
    P = tmp.indexOf('\0');
      if(P > -1) tmp = tmp.substring(0, P);
    tmp.trim();
      if (tmp.length() > 0) u8x8.print(tmp.substring(0, 16));
      if (tmp.length() > 15) {u8x8.setCursor(0,10); u8x8.print(tmp.substring(16, 32));}
      if (tmp.length() > 31) {u8x8.setCursor(0,12); u8x8.print(tmp.substring(32, 48));}
      if (tmp.length() > 47) {u8x8.setCursor(0,14); u8x8.print(tmp.substring(48, 64));}
    showCount(errCount);    
    }
  }
  else if ( (stationName = rx.getRdsText0A()) != NULL){
    if (useSerial) Serial.println(stationName);
    if (haveOled) {
    tmp = stationName;
    P = tmp.indexOf('\0');
      if(P> -1) tmp = tmp.substring(0, P);
    tmp.trim();
    P = tmp.length();
    u8x8.setCursor(1, 6); u8x8.print(tmp);
      for (byte I = P; I < 12; I++) u8x8.print(" ");
    showCount(errCount);
    }
  }
  else if ( (rdsTime = rx.getRdsTime()) != NULL ){
    if (useSerial) Serial.println(rdsTime);
  }
}

void setFav(){
  if (favNow == 1) Channel  = fav1;
  if (favNow == 2) Channel  = fav2;
  if (favNow == 3) Channel  = fav3;
  if (favNow == 4) Channel  = fav4;
  if (favNow == 5) Channel  = fav5;
  if (favNow == 6) Channel  = fav6;
  if (favNow == 7) Channel  = fav7;
  if (favNow == 8) Channel  = fav8;
  if (favNow == 9) Channel  = fav9;
  if (favNow == 10) Channel = fav10;
  if (favNow == 11) Channel = fav11;
  if (favNow == 12) Channel = fav12;
  if (haveOled) u8x8.clear();
rx.setFrequency(Channel * 10);
  if (useSerial){
  Serial.print(F("\nFavourite ")); Serial.println(favNow);
  }
showStatus();
  if (haveOled){
  u8x8.setCursor(0, 4);
  u8x8.print(F("Favourite ")); u8x8.print(favNow);
  showFav(favNow);
  }

favNow++;
  if (favNow == 13) favNow = 1;
  while(digitalRead(favP) == 0){
  delay(100);
  }
}

void wipe(byte thisLine){
u8x8.setCursor(0, thisLine);
u8x8.print(F("                "));
u8x8.setCursor(0, thisLine);
}

void showFav(byte fav){
wipe(14);
String tmp = station[fav - 1];
byte L = (16 - tmp.length()) / 2;
  for(byte I = 0; I < L; I++){
  tmp = " " + tmp; 
  }
u8x8.print(tmp);
}

void batVolt(){
float V = analogRead(bat);
V = m * V* 3.29 / 1024.0;
u8x8.setCursor(12, 0);
u8x8.print(V,1); u8x8.print("V");
}

void potVolume(){
static byte oldVolume = 255;
static int oldVal = -1000;
int newVal = analogRead(pot);
  if(abs(newVal - oldVal) < 5) return;  // allow for pot jitter
oldVal = newVal;
volume = map(newVal, 0, 960, 0, 15);    // map(value, fromLow, fromHigh, toLow, toHigh)
  if (volume != oldVolume){
  oldVolume = volume;
  rx.setVolume(volume);
  u8x8.setCursor(10, 2); u8x8.print(volume);
    if(volume < 9) u8x8.print(" ");
  }
}

void setRds(){
useRds = !useRds;
  if(useSerial){
  Serial.print(F("Use Rds: "));
  Serial.println(useRds);
  }
  if (!haveOled) return;

  if(!useRds){
  wipe(6); wipe(8); wipe(10); wipe(12); wipe(14);
  u8x8.setCursor(4, 12); u8x8.print(F("Rds off"));
  }else {
  u8x8.setCursor(4, 12); u8x8.print(F("Rds on "));
  haveRds = false;
  }
  while(digitalRead(rdsP) == LOW){
  delay(100);
  }
}

void setUp(){
  if (useSerial) Serial.println(F("Search Up"));
  if (haveOled) {
  u8x8.clear(); u8x8.setCursor(0,0);
  u8x8.print(F("Search Up"));
  }
rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_UP);
wipe(0);
showStatus();
}

void setDown(){
  if (useSerial) Serial.println(F("Search Down"));
  if (haveOled) {
  u8x8.clear(); u8x8.setCursor(0,0);
  u8x8.print(F("Search down"));
  }
rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_DOWN);
wipe(0);
showStatus();
}

void showHelp(){
Serial.println(F("Type U to increase and D to decrease the frequency"));
Serial.println(F("Type S or s to seek station Up or Down"));
Serial.println(F("Type + or - to volume Up or Down"));
Serial.println(F("Type 0 to show current status"));
Serial.println(F("Type ? to this help."));
Serial.println(F("=================================================="));
Serial.flush();
}

void showStatus(){
uint16_t F = rx.getFrequency();
int RSSI = rx.getRssi();
int V = rx.getVolume();
bool S = rx.isStereo();
  if (haveOled) {
  float freq = F;
  u8x8.setCursor(0, 0); u8x8.print(freq / 100, 1);  u8x8.print(F(" MHz "));

  wipe(2); u8x8.print(RSSI); u8x8.print(F(" dBuV"));
  u8x8.setCursor(10, 2); u8x8.print(V); u8x8.setCursor(15, 2);
    if (S) {
    u8x8.print("S");
    digitalWrite(led, HIGH);
    }else{
    u8x8.print("M");
    digitalWrite(led, LOW);
    }
  }

  if (useSerial) {
  char aux[80];
  sprintf(aux, "\nYou are tuned on %u MHz | RSSI: %3.3u dbUv | Vol: %2.2u | Stereo: %s\n", F, RSSI, V, (S) ? "Yes" : "No" );
  Serial.print(aux); Serial.flush();
  }
status_elapsed = millis();
}

void checkSerial(){
  if (Serial.available() > 0) {
  char key = Serial.read();
    switch (key) {
      case '+':
        rx.setVolumeUp();
        break;
      case '-':
        rx.setVolumeDown();
        break;
      case 'U':
      case 'u':
        rx.setFrequencyUp();
        break;
      case 'D':
      case 'd':
        rx.setFrequencyDown();
        break;
      case 'S':
        rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_UP);
        break;
      case 's':
        rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_DOWN);
        break;
      case '?':
        showHelp();
        break;
      default:
        break;
    }
  showStatus();
  }
}

David

pu2clr commented 3 years ago

Hello David. Thanks for your email and advices. I will follow the steps you have described below as soon as possible. However, I’m far from my stufs during this week. I’ll try it next weekend. I’m sure it will improve the RDS feature of the library.

I’ll keep you updated about that. Thanks again. Ricardo.

On Mon, 16 Nov 2020 at 06:18 David Patterson notifications@github.com wrote:

Your library is very useful, thanks.

The rds transfer functions are very useful. There is probably work to do on checking whether the rds is a) Error free b) Relevant to the current channel after the channel has changed

I could not eradicate these issues. I was able to improve reliability with a few added modifications:

In SI470x.cpp

1.

add interrupt reg04->refined.RDSIEN = 1; // was 0 = no interrupt drp 2.

add link interrupt: reg04->refined.GPIO1 = reg04->refined.GPIO2 = reg04->refined.GPIO3 = 0; reg04->refined.GPIO2 = 1; // + drp 3.

check for number of errors before returning true in getDdsReady(): bool SI470X::getRdsReady() { getStatus(); if (reg0a->refined.RDSR) { // drp re-write from: return reg0a->refined.RDSR; if ((reg0a->refined.BLERA) < 6) return true; } return false; }; 4.

Add get the number of rds errors: int SI470X::getRdsErrors() { // drp added getStatus(); if (reg0a->refined.RDSR) return reg0a->refined.BLERA; else return -1; };

In SI470x.h : add to public int getRdsErrors(); // drp

This is a working example for the reasonably priced 128 x 128 oled screen at

https://shop.pimoroni.com/products/1-12-oled-breakout?variant=29421050757203 It uses the interrupt from GPIO2, attached to D3, to initiate rds transfer. There are options for Serial output and polled rds transfer.

// si470x_RDS_oled3.ino // D.R.Patterson (drp) // 15/11/2020 // Adapted from example in U8x8lib library // https://github.com/pu2clr/SI470X // Requires drp modified version of the SI470X library

// Will work with headphones / lineout // With laptop: // radio volume 2 // use TECNET usb input- volume < 5 // set audio input to listen - output spakers

// Arduino Pro Mini and SI4703 wire up

// | Device Si470X | Arduino Pin | // | ---------------| ------------ | // | RESET | D2 | // | SDIO | A4 | // | SCLK | A5 | // | GPIO2 | D3 |

// ATTENTION: // Best to avoid using the computer connected to the mains during testing. // Run the computer on it's battery. // By Ricardo Lima Caratti, 2020.

include

include

include

bool haveOled = true; bool useSerial = false; bool useIrq = true; bool useRds = true;

U8X8_SH1107_PIMORONI_128X128_HW_I2C u8x8(U8X8_PIN_NONE);

const byte resetPin = 2; // radio reset const byte SDIO = A4; // sda const byte SCLK = A5; // scl const byte pot = A3; // volume pot const byte potVs = A2; // 3v3 supply for volume pot const byte led = A1; // stereo led indicator const byte bat = A0; // read battery voltage across potential divider const float m = 1/0.3222; // battery voltage multiplier 1 / potential divider ratio const byte favP = 4; // favourite select port const byte up = 5; // frequency up select port const byte down = 6; // frequency down select port const byte rdsP = 7; // tggle RDS port const byte GPIO2 = 3; // radio irq detect pin

unsigned int fav1 = 885; // Radio 2 unsigned int fav2 = 892; // Pride Radio unsigned int fav3 = 907; // classical R3 unsigned int fav4 = 929; // radio 4 unsigned int fav5 = 936; // Radio Tyneside unsigned int fav6 = 954; // BBC Radio Newcastle unsigned int fav7 = 971; // metro unsigned int fav8 = 975; // smooth radio ne unsigned int fav9 = 981; // Radio 1 (loud) unsigned int fav10 = 1003; // classical (loud) unsigned int fav11 = 1018; // Heart unsigned int fav12 = 1053; // Capital NE //unsigned int fav12 = 1075; // Smooth Radio NE (weak)

String station[] = { "BBC 2", "Pride", "BBC 3", "BBC 4", "Tyneside", "Newcastle", "Metro", "Smooth", "BBC 1", "Classical", "Heart", "Capital NE", };

String fav = "12342567890*/"; byte volume = 8; bool useVolPot = true; byte favNow = 1; unsigned int Channel;

define RESET_PIN 2

define MAX_DELAY_RDS 80 // 40ms - polling method

define MAX_DELAY_STATUS 2000

unsigned long rds_elapsed; unsigned long status_elapsed; unsigned long vstart; volatile bool haveRds = false;

SI470X rx;

void irqDetect(){ haveRds = true; }

void setup(){ if (useSerial){ Serial.begin(115200); while (!Serial) delay(100); } if (useSerial) Serial.println(F("\nPU2CLR SI470X Arduino Library.")); pinMode(potVs, OUTPUT); if (useVolPot) digitalWrite(potVs, HIGH); pinMode(favP, INPUT_PULLUP); pinMode(up, INPUT_PULLUP); pinMode(down, INPUT_PULLUP); pinMode(rdsP, INPUT_PULLUP); pinMode(led, OUTPUT); pinMode(GPIO2, INPUT_PULLUP);

Wire.begin(); Wire.beginTransmission(0x3C); if (Wire.endTransmission() == 0) { u8x8.begin(); u8x8.setFont(u8x8_font_8x13B_1x2_f); u8x8.clear(); u8x8.print(F("FM Radio")); //u8x8.setCursor(9, 14); u8x8.print(F("dBuV")); haveOled = true; }else{ if (useSerial) Serial.println(F("No oled")); Wire.end(); haveOled = false; }

rx.setup(RESET_PIN, A4); // A4 SDA pin for Arduino ATmega328 rx.setBand(0); // Europe- do not setSpace(1) as reception stops

rx.setRDS(true); // Turns RDS on

if (useVolPot) { potVolume(); }else{ rx.setVolume(volume); }

delay(500);

// Select a station with RDS service in your place if (useSerial) {Serial.print(F("\nTuning ")); Serial.println(fav1);} setFav(); // Enable SDR rx.setRds(true); rx.setRdsMode(1); // verbose rx.setMono(false);

if (useSerial) showHelp(); rds_elapsed = millis(); status_elapsed = rds_elapsed; vstart = rds_elapsed; attachInterrupt(digitalPinToInterrupt(GPIO2), irqDetect, FALLING); }

void loop() { unsigned long tnow; if (useRds){ if (useIrq) { // check flag from interrupt line if (haveRds) { checkRDS(false); haveRds= false; } }else{ // polling rds if ((millis() - rds_elapsed) > MAX_DELAY_RDS ) { if ( rx.getRdsReady() ) checkRDS(true); rds_elapsed = millis(); } } }

if (useVolPot) potVolume(); if (digitalRead(favP) == 0) setFav(); if (digitalRead(up) == 0) setUp(); if (digitalRead(down) == 0) setDown(); if (digitalRead(rdsP) == 0) setRds();

tnow = millis(); if ((tnow - status_elapsed) > MAX_DELAY_STATUS ) { status_elapsed = tnow; showStatus(); }

if(useSerial) checkSerial();

tnow = millis(); if ((tnow - vstart) > 999) { vstart = tnow; batVolt(); } }

/ RDS / char rdsMsg; char stationName; char *rdsTime;

void showCount(int errcount){ u8x8.setCursor(13,6); if(errcount == 0){ u8x8.print(F(" ")); }else{ u8x8.print("E"); u8x8.print(errcount); if((errcount < 10) && (errcount > -1)) u8x8.print(" "); } }

void checkRDS(bool polling){ int P; String tmp; if (polling){ if (!rx.getRdsReady()) return; } int errCount = rx.getRdsErrors(); // get the number of errors if ( (rdsMsg = rx.getRdsText2A()) != NULL) { if (useSerial) Serial.println(rdsMsg); if (haveOled) { wipe(14); wipe(12); wipe(10); wipe(8); tmp = rdsMsg; P = tmp.indexOf('\0'); if(P > -1) tmp = tmp.substring(0, P); tmp.trim(); if (tmp.length() > 0) u8x8.print(tmp.substring(0, 16)); if (tmp.length() > 15) {u8x8.setCursor(0,10); u8x8.print(tmp.substring(16, 32));} if (tmp.length() > 31) {u8x8.setCursor(0,12); u8x8.print(tmp.substring(32, 48));} if (tmp.length() > 47) {u8x8.setCursor(0,14); u8x8.print(tmp.substring(48, 64));} showCount(errCount); } } else if ( (stationName = rx.getRdsText0A()) != NULL){ if (useSerial) Serial.println(stationName); if (haveOled) { tmp = stationName; P = tmp.indexOf('\0'); if(P> -1) tmp = tmp.substring(0, P); tmp.trim(); P = tmp.length(); u8x8.setCursor(1, 6); u8x8.print(tmp); for (byte I = P; I < 12; I++) u8x8.print(" "); showCount(errCount); } } else if ( (rdsTime = rx.getRdsTime()) != NULL ){ if (useSerial) Serial.println(rdsTime); } }

void setFav(){ if (favNow == 1) Channel = fav1; if (favNow == 2) Channel = fav2; if (favNow == 3) Channel = fav3; if (favNow == 4) Channel = fav4; if (favNow == 5) Channel = fav5; if (favNow == 6) Channel = fav6; if (favNow == 7) Channel = fav7; if (favNow == 8) Channel = fav8; if (favNow == 9) Channel = fav9; if (favNow == 10) Channel = fav10; if (favNow == 11) Channel = fav11; if (favNow == 12) Channel = fav12; if (haveOled) u8x8.clear(); rx.setFrequency(Channel * 10); if (useSerial){ Serial.print(F("\nFavourite ")); Serial.println(favNow); } showStatus(); if (haveOled){ u8x8.setCursor(0, 4); u8x8.print(F("Favourite ")); u8x8.print(favNow); showFav(favNow); }

favNow++; if (favNow == 13) favNow = 1; while(digitalRead(favP) == 0){ delay(100); } }

void wipe(byte thisLine){ u8x8.setCursor(0, thisLine); u8x8.print(F(" ")); u8x8.setCursor(0, thisLine); }

void showFav(byte fav){ wipe(14); String tmp = station[fav - 1]; byte L = (16 - tmp.length()) / 2; for(byte I = 0; I < L; I++){ tmp = " " + tmp; } u8x8.print(tmp); }

void batVolt(){ float V = analogRead(bat); V = m V 3.29 / 1024.0; u8x8.setCursor(12, 0); u8x8.print(V,1); u8x8.print("V"); }

void potVolume(){ static byte oldVolume = 255; static int oldVal = -1000; int newVal = analogRead(pot); if(abs(newVal - oldVal) < 5) return; // allow for pot jitter oldVal = newVal; volume = map(newVal, 0, 960, 0, 15); // map(value, fromLow, fromHigh, toLow, toHigh) if (volume != oldVolume){ oldVolume = volume; rx.setVolume(volume); u8x8.setCursor(10, 2); u8x8.print(volume); if(volume < 9) u8x8.print(" "); } }

void setRds(){ useRds = !useRds; if(useSerial){ Serial.print(F("Use Rds: ")); Serial.println(useRds); } if (!haveOled) return;

if(!useRds){ wipe(6); wipe(8); wipe(10); wipe(12); wipe(14); u8x8.setCursor(4, 12); u8x8.print(F("Rds off")); }else { u8x8.setCursor(4, 12); u8x8.print(F("Rds on ")); haveRds = false; } while(digitalRead(rdsP) == LOW){ delay(100); } }

void setUp(){ if (useSerial) Serial.println(F("Search Up")); if (haveOled) { u8x8.clear(); u8x8.setCursor(0,0); u8x8.print(F("Search Up")); } rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_UP); wipe(0); showStatus(); }

void setDown(){ if (useSerial) Serial.println(F("Search Down")); if (haveOled) { u8x8.clear(); u8x8.setCursor(0,0); u8x8.print(F("Search down")); } rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_DOWN); wipe(0); showStatus(); }

void showHelp(){ Serial.println(F("Type U to increase and D to decrease the frequency")); Serial.println(F("Type S or s to seek station Up or Down")); Serial.println(F("Type + or - to volume Up or Down")); Serial.println(F("Type 0 to show current status")); Serial.println(F("Type ? to this help.")); Serial.println(F("==================================================")); Serial.flush(); }

void showStatus(){ uint16_t F = rx.getFrequency(); int RSSI = rx.getRssi(); int V = rx.getVolume(); bool S = rx.isStereo(); if (haveOled) { float freq = F; u8x8.setCursor(0, 0); u8x8.print(freq / 100, 1); u8x8.print(F(" MHz "));

wipe(2); u8x8.print(RSSI); u8x8.print(F(" dBuV")); u8x8.setCursor(10, 2); u8x8.print(V); u8x8.setCursor(15, 2); if (S) { u8x8.print("S"); digitalWrite(led, HIGH); }else{ u8x8.print("M"); digitalWrite(led, LOW); } }

if (useSerial) { char aux[80]; sprintf(aux, "\nYou are tuned on %u MHz | RSSI: %3.3u dbUv | Vol: %2.2u | Stereo: %s\n", F, RSSI, V, (S) ? "Yes" : "No" ); Serial.print(aux); Serial.flush(); } status_elapsed = millis(); }

void checkSerial(){ if (Serial.available() > 0) { char key = Serial.read(); switch (key) { case '+': rx.setVolumeUp(); break; case '-': rx.setVolumeDown(); break; case 'U': case 'u': rx.setFrequencyUp(); break; case 'D': case 'd': rx.setFrequencyDown(); break; case 'S': rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_UP); break; case 's': rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_DOWN); break; case '?': showHelp(); break; default: break; } showStatus(); } }

David

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/pu2clr/SI470X/issues/2, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAVSJNBDPE5PHLUUQELO5FTSQDU6FANCNFSM4TW47D7A .

--

QRZ: http://www.qrz.com/db/PU2CLR Skype: ricardo_caratti Mobile: +55 61 981442296

drp0 commented 3 years ago

I am trying to understand how the si4703 returns rds errors - the manufacture's documentation is terrible! "If in verbose mode, the BLERA bits indicate how many errors were corrected in block A. If BLERA indicates 6 or more errors, the data in RDSA should be discarded."

Is BLERA reporting on the errors in just the text returned by rx.getRdsText2A()?

rx.getRdsText0A() returns the station name. Is this in "block A" or another block? If it is in another block, I see that there are other registers with error numbers.. BLERD, BLERC, and BLERB (for block B?) If the station name belonged to block B, for instance, it would be sensible to add a further routine to return the error for this:

int SI470X::getRdsErrorsB()
{                   // drp added- errors in block B
    getAllRegisters();
    if (reg0a->refined.RDSR) return reg0b->refined.BLERB; else return -1;
};

I am not even sure if the "if (reg0a->refined.RDSR) " section is applicable to a "block B" return

With the correct error number reporting, some returned values of station name and rdsMsg could be rejected.

David

pu2clr commented 3 years ago

Hello David.

You are right. The documentation about RDS on SI devices is terrible.

Also, the FM/RDS stations in my location do not follow the RDS protocols. It also is terrible. I need to study more about the RDS protocol and also an efficient way to debug my code. I bought recently a small FM/RDS transmitter to help me during my development activities.

However, I have dedicated my free time to HF (AM and SSB modes).

To further complicate matters, I started a master's program at the Federal University of Ceará.

Now my free time will be restricted even more.

Anyway, I will try to do some improvements as soon as possible.

I can see you are a good C++ programmer. I think we can fix it together.

Regards.

Ricardo.

On Tue, Nov 17, 2020 at 10:42 AM David Patterson notifications@github.com wrote:

I am trying to understand how the si4703 returns rds errors - the manufacture's documentation is terrible! "If in verbose mode, the BLERA bits indicate how many errors were corrected in block A. If BLERA indicates 6 or more errors, the data in RDSA should be discarded."

Is BLERA reporting on the errors in just the text returned by rx.getRdsText2A()?

rx.getRdsText0A() returns the station name. Is this in "block A" or another block? If it is in another block, I see that there are other registers with error numbers.. BLERD, BLERC, and BLERB (for block B?) If the station name belonged to block B, for instance, it would be sensible to add a further routine to return the error for this:

int SI470X::getRdsErrorsB() { // drp added- errors in block B getAllRegisters(); if (reg0a->refined.RDSR) return reg0b->refined.BLERB; else return -1; };

I am not even sure if the "if (reg0a->refined.RDSR) " section is applicable to a "block B" return

With the correct error number reporting, some returned values of station name and rdsMsg could be rejected.

David

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/pu2clr/SI470X/issues/2#issuecomment-728934874, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAVSJNCCPR63TLYQOB5ED53SQJ4VJANCNFSM4TW47D7A .

--

QRZ: http://www.qrz.com/db/PU2CLR Skype: ricardo_caratti Mobile: +55 61 981442296

drp0 commented 3 years ago

After a bit more delving into the manuals I made a couple of significant changes today

1) AN243.pdf, page 9 Group 0A returns the PS and TP fields. The same PS message typically repeats three to four times and in some cases it may be advantageous to compare multiple PS messages and update the display only when multiple messages are equivalent.

I have re-coded my sketch to compare the value of rx.getRdsText2A() (station text) with the last value and update when they are the same. The same for rx.getRdsText0A() (station name). This concept could be directly coded in the library.

2) AN230.pdf, page 25 Valid time if BLERB, BLERC, BLERD == 0

 BLERA < 3 all ok, else if BLERB < 2 ok

 So more error functions are needed..

3) I now have 3 error returning functions in the library:

int SI470X::getRdsErrors()
{                   // drp added- errors in block A
    getStatus();
      if (reg0a->refined.RDSR) return reg0a->refined.BLERA; else return -1;
};

int SI470X::getRdsErrorsB()
{                   // drp added- errors in block B
    getAllRegisters();
      if (reg0a->refined.RDSR) return reg0b->refined.BLERB; else return -1;
};
int SI470X::getRdsErrorsBCD()
{                   // drp added- errors in block B+C+D- valid time if 0
    getAllRegisters();
      if (reg0a->refined.RDSR) {
      return (reg0b->refined.BLERB) + (reg0b->refined.BLERC) + (reg0b->refined.BLERD); 
      }else return -1;
};

4) I therefore reject time values if getRdsErrorsBCD() > 0

5) By including the library TimeLib.h I can set the time library clock using the returned time from the radio. The clock can then be polled for updated time

Todays sketch:

//  si470x_RDS_oled4.ino
//  D.R.Patterson (drp)
//  15/11/2020
//  Adapted from example in U8x8lib library
//  https://github.com/pu2clr/SI470X
//  Requires drp modified version of the SI470X library
//  Time signal support- sets Timelib time in oled mode

//  Will work with headphones / lineout
//  With laptop:
//    radio volume 2
//    use TECNET usb input- volume < 5
//    set audio input to listen - output spakers

//  Arduino Pro Mini and SI4703 wire up

//  | Device  Si470X |  Arduino Pin  |
//  | ---------------| ------------  |
//  | RESET          |     D2        |
//  | SDIO           |     A4        |
//  | SCLK           |     A5        |
//  | GPIO2          |     D3        |

//  ATTENTION:
//    Best to avoid using the computer connected to the mains during testing.
//    Run the computer on it's battery.
//  By Ricardo Lima Caratti, 2020.

#include <TimeLib.h>
#include <SI470X.h>
#include <U8x8lib.h>
#include <Wire.h>

bool haveOled   = true;
bool useSerial  = false;
bool useIrq     = true;
bool useRds     = true;

bool lineInUse  = false;
bool clockSet   = false;

U8X8_SH1107_PIMORONI_128X128_HW_I2C u8x8(U8X8_PIN_NONE);

const byte resetPin = 2;          // radio reset
const byte SDIO     = A4;         // sda
const byte SCLK     = A5;         // scl
const byte pot      = A3;         // volume pot
const byte potVs    = A2;         // 3v3 supply for volume pot
const byte led      = A1;         // stereo led indicator
const byte bat      = A0;         // read battery voltage across potential divider
const float m       = 1/0.3222;   // battery voltage multiplier 1 / potential divider ratio
const byte favP     = 4;          // favourite select port
const byte up       = 5;          // frequency up select port
const byte down     = 6;          // frequency down select port
const byte rdsP     = 7;          // tggle RDS port
const byte GPIO2    = 3;          // radio irq detect pin 

unsigned int fav1 = 885;    // Radio 2
unsigned int fav2 = 892;    // Pride Radio
unsigned int fav3 = 907;    // classical R3
unsigned int fav4 = 929;    // radio 4
unsigned int fav5 = 936;    // Radio Tyneside
unsigned int fav6 = 954;    // BBC Radio Newcastle
unsigned int fav7 = 971;    // metro
unsigned int fav8 = 975;    // smooth radio ne
unsigned int fav9 = 981;    // Radio 1 (loud)
unsigned int fav10 = 1003;  // classical (loud)
unsigned int fav11 = 1018;  // Heart 
unsigned int fav12 = 1053;  // Capital NE
//unsigned int fav12 = 1075;  // Smooth Radio NE (weak)

// no more than 9 letters to fit oled screen width
String station[] = {
"BBC 2", "Pride", "BBC 3", 
"BBC 4", "Tyneside", "Newcastle", 
"Metro", "Smooth", "BBC 1",
"Classical", "Heart", "Capital",
};

String fav        = "12342567890*/";
byte volume       = 8;
bool useVolPot    = true;
byte favNow       = 1;
unsigned int Channel;

#define RESET_PIN 2

#define MAX_DELAY_RDS 80   // 40ms - polling method
#define MAX_DELAY_STATUS   2000 

unsigned long rds_elapsed;
unsigned long status_elapsed;
unsigned long vstart;
unsigned long tstart;
volatile bool haveRds = false;

SI470X rx;

void irqDetect(){
haveRds = true;
}

void setup(){
  if (useSerial){
  Serial.begin(115200);
    while (!Serial) delay(100);
  }
  if (useSerial) Serial.println(F("\nPU2CLR SI470X Arduino Library."));
pinMode(potVs, OUTPUT);
  if (useVolPot) digitalWrite(potVs, HIGH);
pinMode(favP, INPUT_PULLUP);
pinMode(up, INPUT_PULLUP);
pinMode(down, INPUT_PULLUP);
pinMode(rdsP, INPUT_PULLUP);
pinMode(led, OUTPUT);
pinMode(GPIO2, INPUT_PULLUP);

Wire.begin();
Wire.beginTransmission(0x3C);
  if (Wire.endTransmission() == 0) {
  u8x8.begin();
  u8x8.setFont(u8x8_font_8x13B_1x2_f);
  u8x8.clear();
  u8x8.print(F("FM Radio"));
  haveOled = true;  
  }else{
    if (useSerial) Serial.println(F("No oled"));
  Wire.end();
  haveOled = false;
  }

rx.setup(RESET_PIN, A4);  // A4 SDA pin  for Arduino ATmega328
rx.setSpace(1);           // 01 - 100 kHz do not setSpace(1) after setBand(0) as reception stops
rx.setBand(0);              // Europe
rx.setRDS(true);          // Turns RDS on

  if (useVolPot) {
  potVolume();
  }else{
  rx.setVolume(volume);
  }

delay(500);

// Select a station with RDS service in your place
  if (useSerial) {Serial.print(F("\nTuning ")); Serial.println(fav1);}
setFav();
// Enable SDR
rx.setRds(true);
rx.setRdsMode(1);   // verbose
rx.setMono(false);

  if (useSerial) showHelp();
rds_elapsed     = millis();
status_elapsed  = rds_elapsed;
vstart          = rds_elapsed;
tstart          = rds_elapsed;
attachInterrupt(digitalPinToInterrupt(GPIO2), irqDetect, FALLING);
}

void loop() {
unsigned long tnow = millis();
  if (useRds){
    if (useIrq) {
    // check flag from interrupt line
      if (haveRds) {
      checkRDS(false);
      haveRds= false;
      }   
    }else{
    // polling rds
      if ((tnow - rds_elapsed) > MAX_DELAY_RDS ) {  
        if ( rx.getRdsReady() ) checkRDS(true);
      rds_elapsed = millis();
      }
    }
  }

  if ((haveOled) && (!lineInUse)) {
    if (clockSet){
    tnow = millis();
      if ((tnow - tstart) > 999){
      tstart = tnow;
      showTime();
      }
    }
  }

  if (useVolPot)              potVolume();
  if (digitalRead(favP) == 0) setFav();
  if (digitalRead(up) == 0)   setUp();
  if (digitalRead(down) == 0) setDown();
  if (digitalRead(rdsP) == 0) setRds();

tnow = millis();
  if ((tnow - status_elapsed) > MAX_DELAY_STATUS ) {
  status_elapsed = tnow;
  showStatus();
  }

  if(useSerial) checkSerial();

tnow = millis(); 
  if ((tnow - vstart) > 999) {
  vstart = tnow;
  batVolt();
  }
}

// *********************************************************
//   RDS
// *********************************************************

char *rdsMsg;
char *stationName;
char *rdsTime;

void showCount(int errcount, char LBL){
u8x8.setCursor(13,6);
  if(errcount == 0){
  u8x8.print(F("   ")); 
  }else{
  u8x8.print(LBL); u8x8.print(errcount);
    if((errcount < 10) && (errcount > -1)) u8x8.print(" ");
  }
}

void checkRDS(bool polling){
static String oldrdsMsg       = "";
static String oldstationName  = "";
int P;
String tmp;
  if (polling){
    if (!rx.getRdsReady()) return;
  }
// grab the error registers
int errorA    = rx.getRdsErrors();
int errorB    = rx.getRdsErrorsB();
int errorBCD  = rx.getRdsErrorsBCD();

  if ( (rdsMsg = rx.getRdsText2A()) != NULL) {      // Extended text
  tmp = clean(rdsMsg);    
    if (useSerial) {
    Serial.println(tmp); Serial.flush();
    }
    if (haveOled) {
    wipe(12); wipe(10); wipe(8);
      if(tmp == oldrdsMsg) {
        if (tmp.length() > 0) u8x8.print(tmp.substring(0, 16));
        if (tmp.length() > 15) {u8x8.setCursor(0,10); u8x8.print(tmp.substring(16, 32));}
        if (tmp.length() > 31) {u8x8.setCursor(0,12); u8x8.print(tmp.substring(32, 48));}
        if (tmp.length() > 47) {
        wipe(14); u8x8.setCursor(0,14); u8x8.print(tmp.substring(48, 64));
        lineInUse = true;
        }else lineInUse = false;
      }
      if (!lineInUse) {
        if (clockSet) showTime(); else wipe(14);
      }
    oldrdsMsg = tmp;
    showCount(errorA, 'T');   
    }
  }else{
    if ( (stationName = rx.getRdsText0A()) != NULL){  // get station name
    tmp = clean(stationName);
      if (useSerial) {
      Serial.println(tmp); Serial.flush();
      }
      if (haveOled) {
        if(tmp == oldstationName) {
        P = tmp.length();
        u8x8.setCursor(1, 6); u8x8.print(tmp);
          for (byte I = P; I < 12; I++) u8x8.print(" ");
        }else wipe(6);
      oldstationName = tmp;
      showCount(errorB, 'N');
      }
    }
  }

  if ( (rdsTime = rx.getRdsTime()) != NULL ) {
    if (errorBCD < 1) {
      if (useSerial)Serial.println(rdsTime);
      if (haveOled) {
      tmp = rdsTime;
      int myhour    = tmp.substring(0,2).toInt();
      int myminute  = tmp.substring(3,5).toInt();
      setTime(myhour, myminute, 0,0,0,0);
      clockSet = true;
      }
    }
  }
}

void setFav(){
  if (favNow == 1) Channel  = fav1;
  if (favNow == 2) Channel  = fav2;
  if (favNow == 3) Channel  = fav3;
  if (favNow == 4) Channel  = fav4;
  if (favNow == 5) Channel  = fav5;
  if (favNow == 6) Channel  = fav6;
  if (favNow == 7) Channel  = fav7;
  if (favNow == 8) Channel  = fav8;
  if (favNow == 9) Channel  = fav9;
  if (favNow == 10) Channel = fav10;
  if (favNow == 11) Channel = fav11;
  if (favNow == 12) Channel = fav12;
  if (haveOled) {u8x8.clear(); lineInUse = false;}
rx.setFrequency(Channel * 10);
  if (useSerial){
  Serial.print(F("\nFavourite ")); Serial.println(favNow);
  }
showStatus();
  if (haveOled){
  wipe(4); u8x8.print(F("Fav ")); u8x8.print(favNow);
  u8x8.setCursor(7,4); u8x8.print(station[favNow - 1]);
  }
favNow++;
  if (favNow == 13) favNow = 1;
  while(digitalRead(favP) == 0){
  delay(100);
  }
}

String clean(char *text){             // remove 0 and unprintable chacters
String tmp;
  for (byte I= 0; I < strlen(text); I++){
  char t = text[I];
    if ((t > 31) && (t < 127)) tmp += t; else tmp += " ";
  }
tmp.trim();
return tmp;  
}

void wipe(byte thisLine){
u8x8.setCursor(0, thisLine);
u8x8.print(F("                "));
u8x8.setCursor(0, thisLine);
}

void batVolt(){
float V = analogRead(bat);
V = m * V* 3.29 / 1024.0;
u8x8.setCursor(12, 0);
u8x8.print(V,1); u8x8.print("V");
}

void potVolume(){
static byte oldVolume = 255;
static int oldVal = -1000;
int newVal = analogRead(pot);
  if(abs(newVal - oldVal) < 5) return;  // allow for pot jitter
oldVal = newVal;
volume = map(newVal, 0, 960, 0, 15);    // map(value, fromLow, fromHigh, toLow, toHigh)
  if (volume != oldVolume){
  oldVolume = volume;
  rx.setVolume(volume);
  u8x8.setCursor(10, 2); u8x8.print(volume);
    if(volume < 9) u8x8.print(" ");
  }
}

void setRds(){
useRds = !useRds;
  if(useSerial){
  Serial.print(F("Use Rds: "));
  Serial.println(useRds);
  }
  if (!haveOled) return;

  if(!useRds){
  wipe(6); wipe(8); wipe(10); wipe(12); wipe(14);
  u8x8.setCursor(4, 12); u8x8.print(F("Rds off"));
  }else {
  u8x8.setCursor(4, 12); u8x8.print(F("Rds on "));
  haveRds = false;
  }
  while(digitalRead(rdsP) == LOW){
  delay(100);
  }
}

void setUp(){
  if (useSerial) Serial.println(F("Search Up"));
  if (haveOled) {
  lineInUse = false;
  u8x8.clear(); u8x8.setCursor(0,0);
  u8x8.print(F("Search Up"));
  }
rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_UP);
wipe(0);
showStatus();
}

void setDown(){
  if (useSerial) Serial.println(F("Search Down"));
  if (haveOled) {
  lineInUse = false;
  u8x8.clear(); u8x8.setCursor(0,0);
  u8x8.print(F("Search down"));
  }
rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_DOWN);
wipe(0);
showStatus();
}

void showHelp(){
Serial.println(F("Type U to increase and D to decrease the frequency"));
Serial.println(F("Type S or s to seek station Up or Down"));
Serial.println(F("Type + or - to volume Up or Down"));
Serial.println(F("Type 0 to show current status"));
Serial.println(F("Type ? to this help."));
Serial.println(F("=================================================="));
Serial.flush();
}

void showStatus(){
uint16_t F = rx.getFrequency();
int RSSI = rx.getRssi();
int V = rx.getVolume();
bool S = rx.isStereo();
  if (haveOled) {
  float freq = F;
  u8x8.setCursor(0, 0); u8x8.print(freq / 100, 1);  u8x8.print(F(" MHz "));

  wipe(2); u8x8.print(RSSI); u8x8.print(F(" dBuV"));
  u8x8.setCursor(10, 2); u8x8.print(V); u8x8.setCursor(15, 2);
    if (S) {
    u8x8.print("S");
    digitalWrite(led, HIGH);
    }else{
    u8x8.print("M");
    digitalWrite(led, LOW);
    }
  }

  if (useSerial) {
  char aux[80];
  sprintf(aux, "\nYou are tuned on %u MHz | RSSI: %3.3u dbUv | Vol: %2.2u | Stereo: %s\n", F, RSSI, V, (S) ? "Yes" : "No" );
  Serial.print(aux); Serial.flush();
  }
status_elapsed = millis();
}

void checkSerial(){
  if (Serial.available() > 0) {
  char key = Serial.read();
    switch (key) {       
      case '+':
        rx.setVolumeUp();
        break;
      case '-':
        rx.setVolumeDown();
        break;
      case 'U':
      case 'u':
        rx.setFrequencyUp();
        break;
      case 'D':
      case 'd':
        rx.setFrequencyDown();
        break;
      case 'S':
        rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_UP);
        break;
      case 's':
        rx.seek(SI470X_SEEK_WRAP, SI470X_SEEK_DOWN);
        break;
      case '?':
        showHelp();
        break;
      default:
        break;
    }
  showStatus();
  }
}

void showTime(){
String myseconds = digits(second());
String myminutes = digits(minute()); 
String myhour = digits(hour());
String tmp = myhour + ":" + myminutes + ":" + myseconds;
u8x8.setCursor(0,14); u8x8.print(F("        "));
u8x8.print(tmp);
}

String digits(int digits){
String tmp = String(digits);
  if(digits < 10) tmp = "0" + tmp;
return tmp;
}

David

fjansson commented 1 year ago

I've been playing with SDR on an Si4703 revision B16 chip (on a clone of the Sparkfun breakout board). I can only see station names but with frequent glitches, and I get nothing from rx.getRdsText2A or rx.getRdsText2B. This may of course be due to them not being broadcast.

Two questions: