2021
HOME

Daten über die Stabantenne: UKW/FM Radio Data System
RDS-RADIO-CLOCK
FM-Radiochip empfängt Datum und Uhrzeit mit dem Arduino - Daten ohne Internet -

Prototyp RDS-Uhr
RDS-CLOCK-DISPLAY
Die oberen 2 Zeilen sind RDS-Daten. Im unteren Teil läuft die synchronisierte Arduino-Uhr.
Als Sonntagnachmittagvergnügen soll ein sehr preiswerter FM-Radioempfängerbaustein dazu bewogen werden die aktuelle Uhrzeit und das Datum zu zeigen. In stilvollem Acrylgehäuse für ein OLED-Display schmückt das Ergebnis dann die Schreibtischplatte.

Da der Baustein RDA5807M schon länger auf dem Markt ist, sollte das Projekt mit wenigen Klicks und Copy/Paste aus dem Internet realisierbar sein. Da der Rheinturm nicht weit entfernt ist, stehen auch mehrere FM-Sender am Standort bereit. Folgende Hardware soll Verwendung finden.

  • RDM5807M Breakout
  • Arduino Uno bzw. Pro Mini
  • Pegelwandler für Prototyp mit Uno
  • Acrylgehäuse
Der Prototyp benutzt den Arduino Uno und einen Pegelwandler 5V/3,3V, da das Radio mit 3 Volt angegeben ist. Das autarke System soll später, wie der Arduino-Morse-Decoder, mit LiPo-Akku betrieben werden. Eine Stabantenne erscheint zu schwer für den Aufbau, so dass ein Stück Draht als Antenne dienen soll. Das kleine Breakoutboard bekam einen breadboardfreundlichen Steckadapter mit 2,4 mm Abstand mittels eines kleinen Teils einer Lochrasterplatine. Mit 16 Watt-Kolben und Fädeldraht erfolgt die Adaption. Die Audioausgänge bleiben unbenutzt, da für Dudelfunk andere Hardware vorhanden ist. In diesem Entwurf geht es nur um RDS-Zeit-Daten, also Datum und Uhrzeit. Als Nebenprodukt entstand der Dudelfunk-Blocker.
Übersicht

Software aus fremder Feder und erstes Fazit
Die Suche im Netz ergibt schnell verschiedene Quellen mit Bibliotheken für das RDA5807M-Radio. Folgende Funktionen sind hier von Interesse: Sendereinstellung, Datum und Uhrzeit - mehr nicht. Gedacht ist dabei an einen fest eingestellten Ortssender, einen optionalen Suchlauf und natürlich die für eine Uhr benötigten Informationen. Erste Bibliotheken zeigten lauter defekte RDS-Daten. Eine relativ aufwändige C++-Bibliothek, gleich für mehrere Chips konzipiert, lieferte zwar ab und zu etwas, was einer Uhrzeit zugeordnet werden konnte, aber ein Datum war nicht ohne größere Recherche und zusätzlichem Zeitaufwand zu bekommen. Nach längeren Versuchen war der Sonntagnachmittag vorbei und die Lage erschien hoffnungslos - ziemlich enttäuschend, wenn man bedenkt, wie lange der Baustein schon am Markt ist, ja quasi schon outdatet erscheint. Mein Exemplar hat erhebliche RDS-Probleme.

Diese ersten Versuche ergeben folgende Situation:

  • Das Projekt braucht mehr Zeit oder muss fallen gelassen werden
  • Die Bibliotheken im Netz entsprechen überwiegend nicht den Anforderungen
  • Der Radiobaustein ist relativ unempfindlich bezogen auf den Empfang
  • Das RDS-Signal ist nochmals schwächer und wird leicht gestört, z.B. von OLEDs
  • Der I2C-Radiobaustein verträgt sich nicht mit der Adafruit-OLED-I2C-Bibliothek
  • Ein Nachbau erfordert vermutlich eine gehobene Frustationsschwelle

Ausgestattet mit Datenblatt und etwas Programmierkenntnis wird das Projekt weiter geführt, um das oben angegebene Ergebnis zu erhalten. Dabei wird die Entwicklung in mehreren Schritten aufgeführt mit entsprechendem Quelltext. Am Anfang steht die Kontaktaufnahme mit dem Radio, am Ende das dargestellte Ergebnis mit OLED. Dazwischen erfolgen die Aufgaben nur seriell am Monitor.


Software CHIP-ID auslesen - 3434 Bytes (10%)
Der I2C-Baustein lässt mit der eingebauten
RDA5807M
Pegel
Arduino
VCC

LV

3,3V
HV

5 V
SDA

LV1
SCL

LV2
HV1

SDA
HV2

SCL
Gnd

GND

Gnd
ANT
Arduino-Wire-Bibliothek über die Adresse 0x11 (hex) ansprechen. Im Chip befinden sich Speicherplätze, die Register genannt werden mit einer Breite von 16 Bit bzw. 2 Bytes. Diese meist in hexadezimaler Schreibweise angegeben Speicherplätze können mit I2C-Routinen gelesen und geschrieben werden. In Adresse 0x00, also einfach 0, steht bei der Chipfamilie RDA580xx der feste ID-Wert 0x58 (88dez). Damit kann per Software überprüft werden, ob die Kommunikation funktioniert und der Aufbau stimmt. Mit getRegister() erhält man den Inhalt des übergebenen Registers.
#include <Wire.h>
#define ADDR 0x11 // random access

unsigned int getRegister(byte reg)
{Wire.beginTransmission(ADDR); 
 Wire.write(reg);             
 Wire.endTransmission();      
 Wire.requestFrom(ADDR,2);     
 unsigned int x = 256 * Wire.read() + Wire.read();          
 Wire.endTransmission();   
 return x;
}

void setup() 
{Serial.begin(9600);
 delay(200);
 Wire.begin(); 
 Serial.print("\n\nFind RDA580x FM Tuner: ");
 int id = getRegister(0)/256; //HiByte
 Serial.println(id == 0x58 ? "Succes!" : "FAIL!");
}

void loop() {}

Hier würde auch das Lesen eines einzelnen Bytes reichen und das Programm kürzen, aber die Funktion soll unten generell zur Abfrage einzelner Register dienen. Falls alles richtig aufgebaut ist sollte Succes im seriellen Monitor erscheinen.

Software BandScan mit dem seriellen Plotter - 3996 Bytes (12%)
Eine wesentliche Funktion eines Radios ist die Senderwahl. Durch das Übertragen bestimmter im Datenblatt aufgeführten Registerinhalte kann eine Frequenzeinstellung und Abfrage erfolgen. Die Empfangsstärke eines eingestellten Senders ist abfragbar, so dass ein BandScan auf Softwarebasis nichts im Wege steht. Da nun das Radio wirklich auch als solches betrieben werden soll, ist eine einmalige Initialisierung der Register 0x02 bis 0x07 erforderlich. Neben einem Softreset erfolgen die entsprechenden Einstellungen wie Stereo, RDS, usw. Dies erfolgt hier noch im setup().

Mit dem Seriellen Plotter der Arduino IDE kann zwar kein Wasserfall, aber ein Spektrum gezeichnet werden. Der Bereich 87,5 MHz bis 108 MHz stellt sich am Standort entsprechend dar. Von links folgen die Peaks für WDR5, WDR3, DKultur, WDR2, WDR-Cosmo, Antenne und 1LIVE. Einzig Antenne Düsseldorf ragt gerade über die 55-Linie hinaus. Das folgende Listing erzeugt diese Grafik über serielle Ausgaben. Die einzelnen Routinen sind nun getRegister(), setRegister() und damit setFrequency(), getFrequency() und getRSSI(). In setup() erfolgt die Initialisierung und der einmalige BandScan nach einem Reset. Die Arduino-Schleife loop() bleibt wieder leer.

#include <Wire.h>
#define ADDR 0x11  // RDA5807M Random Access
  
unsigned int getRegister(byte reg)
{Wire.beginTransmission(ADDR); // random access 
 Wire.write(reg);             
 Wire.endTransmission();      
 Wire.requestFrom(ADDR,2);     
 unsigned int x = 256*Wire.read()+Wire.read();          
 Wire.endTransmission();   
 return x;
}

void setRegister(byte reg,unsigned int val)
{Wire.beginTransmission(ADDR);
 Wire.write(reg); Wire.write(val >> 8); Wire.write(val & 0xFF);
 Wire.endTransmission();
}

void setFrequency(int freq) //88.8 as 888
{word ch = freq-870;  // Reg 0x03 10 bit 15:6
 ch = ch << 6;        // SHIFT
 bitSet(ch,4);        // TUNE ON
 setRegister(0x03,ch); 
}

unsigned getFrequency(void)
{word data = getRegister(0x0A);
 unsigned channel = data & 0b111111111;
 return channel+870;
}

unsigned getRSSI(void)
{word data = getRegister(0x0B);
 return (data >> 10) & 0b111111;
}

void setup() 
{Serial.begin(9600);
 delay(200);
 Wire.begin(); //INIT RDA REGISTER
 setRegister(0x02,0b1100000000001011); //RESET
 setRegister(0x03,0b0000000000000000);
 setRegister(0x04,0b0000101000000000);
 setRegister(0x05,0b1000100000001111); 
 setRegister(0x06,0b0000000000000000);
 setRegister(0x07,0b0100001000000010);
 setRegister(0x02,0b1100000000001101); //NO RESET
 for(int i=0;i<875;i++)
 {Serial.print(55);Serial.print('\t');Serial.println(0);}
 for(int f=875;f<1080;f+=1)
 {setFrequency(f);
  delay(200);
  Serial.print(55);Serial.print('\t');Serial.println(2*getRSSI());
 }
}

void loop() {}

Nach der Initialisierung wird der Serial-Plotter auf Position 875 gefahren, damit die x-Achs als Frequenzachse zu erkennen ist. Eine blaue Linie für Pegel 55 und die gemessene und noch verdoppelte Feldstärke in rot folgt für den FM-Rundfunkbereich. Da einige Dinge im Datenblatt nicht immer so wie zu erwarten funktionieren, wird einfach 200 ms gewartet, bis sich die Feldstärke als brauchbarer Wert nach einem Frequenzwechsel darstellt.


Software BandScan mit dem seriellen Monitor - 5442 Bytes (16%)
=BANDSCAN=
88.80 54
95.10 52
96.50 47
99.20 48
101.30 53
102.40 43
103.30 51
104.20 60
106.70 52
==========
Der BandScan kann natürlich auch einfach in Textform erfolgen. Da das RDA-Radio mit einer Scan-Funktion ausgestattet ist, kann das quasi die Hardware übernehmen. Zur Durchführung eines Scans zum nächsten Sender muss ein Bit in Register 0x02 gesetzt sein. Das Bit 8 in Register 0x0B zeigt, ob ein Sender ausreichender Feldstärke gefunden ist. Das numerische Ergebnis zeigt sich wie rechts aufgeführt. Für eine RDS-RADIO-CLOCK mit diesem Baustein sollte unbedingt der stärkste Sender eingestellt sein.

Der Quelltext des BandScan in Textform und Chip-Scan:

void bandscan()
{boolean done=false;int last,f;
 setFrequency(875);delay(500);
 while(!done)
 {//SEEK UP CONT      xxxxxx1xxxxxxxxx 
   setRegister(0x02,0b1100001100001101);   
  while(bitRead(getRegister(0x0B),8)==0); //FM TRUE   
  delay(200);      
  last=f;
  f=getFrequency();
  done=f<last;
  if(done)continue;
  Serial.print(f);Serial.print('\t');Serial.println(2*getRSSI());
 }   
 Serial.println("==========");
}

Ein einmaliger Scan konnte nur ohne Registerabfrage realisiert werden, da es Widersprüche im Verhalten bei den Registerbits oder im Verständnis des Datenblatts gab.


Software Informationen aus dem Radio - ohne RDS-Decoder - 7496 Bytes (23%)
Die Register des Radios liefern viele Informationen, worunter auch RDS-Daten. Um die Steuerung und Abfrage allgemein zu testen ist eine Kommunikation per Tastatur und serieller Schnittstelle vorgesehen. Allerdings ohne Lautstärke und Monoumschaltung, da hier kein Audio benutzt werden soll. Der Baustein kann mit der I2C-Adresse 0x10 auch sequenziell auf den Speicher zugreifen. So lassen sich hintereinander liegende Register in einem Stück auslesen. Die Radiodaten liegen in den Registern 0x0A bis 0x10. Das sind 6 Register oder 12 Bytes. Der Sketch wird angereichert mit den Routinen und entsprechenden globalen Variablen.
  • radioRead(), auslesen der 6 Register mit den Radiodaten
  • radioStatus(), Informationen aus den Daten isolieren
  • radioiInit(), die Initialisierung jetzt als eigene Routine; übersichtliches setup()
  • loop(), Benutzerschnittstelle über eingehende serielle Daten

Eine Beispielausgabe sieht wie folgt aus:

104.20 MHz
f=1042 RSSI=65 Stereo Tune_Complete=1 FM_READY=1
PI=D09E RDS=0 BLERA=0 BLERB=0 BLERC=0 BLERD=0
f=1042 RSSI=33 Stereo Tune_Complete=1 FM_READY=1
PI=D09E RDS=1 BLERA=0 BLERB=0 BLERC=0 BLERD=0

Die Arduino-Schleife loop() fragt nun eingegende Zeichen ab und verzweigt entsprechend. Folgende Zeichen bewirken Aktionen:

  • D - Einstellen der Frequenz Deutschlandfunk 102,8 MHz (kein Empfang)
  • W - Einstellen der Frequenz WDR5 auf 88,8 MHz
  • A - Einstellen auf Antenne Düsseldorf als stärkster Sender vor Ort
  • 4 - Suchlauf abwärts
  • 6 - Suchlauf aufwärts
  • 0 - Feldstärke
  • f - aktuelle Frequenz in MHz
  • Enter - Informationen zu einzelnen Flags wie Mono, RDS-SenderID, RDS-Fehler, usw.

Dieses Listing, welches noch in seiner vollen Länge hier erscheint, liefert diese Funktionalität.


#include <Wire.h>
#define ADDR 0x11
#define WDR5 888
#define DLF 1028
#define ANTENNE 1042
#define STRONGSTATION ANTENNE

unsigned int data[7]; //Register 0A-10 RadioRead
boolean Stereo, RDS_Ready, Tune_Complete, Seek_Fail, RDS_Sync, FM_TRUE, FM_READY;
byte    Channel, RSSI, BLERA,BLERB,BLERC,BLERD;

unsigned int getRegister(byte reg)
{Wire.beginTransmission(ADDR); // Device 0x11 for random access
 Wire.write(reg);             
 Wire.endTransmission();      
 Wire.requestFrom(ADDR,2);     
 unsigned int x = 256*Wire.read()+Wire.read();          
 Wire.endTransmission();   
 return x;
}

void setRegister(byte reg,unsigned int val)
{Wire.beginTransmission(ADDR);
 Wire.write(reg); Wire.write(val >> 8); Wire.write(val & 0xFF);
 Wire.endTransmission();
 delay(50);
}

void setFrequency(int freq) //88.8 as 888
{word ch = freq-870;  // Reg 0x03 10 bit 15:6
 ch = ch << 6;        // SHIFT
 bitSet(ch,4);        // TUNE ON
 setRegister(0x03,ch); 
}

unsigned getFrequency(void)
{radioRead();
 return Channel+870;
}

int radioRead() // READ REGISTER 0A-10
{Wire.requestFrom(ADDR-1, 14); //Sequence Addr
 for (int i=0; i<7; i++) { data[i] = 256*Wire.read ()+Wire.read(); }
 Wire.endTransmission();
 radioStatus();
}

void radioStatus()
{RDS_Ready      = bitRead(data[0],15); //Register 0x0A

 Tune_Complete  = bitRead(data[0],14);
 Seek_Fail      = bitRead(data[0],13);
 RDS_Sync       = bitRead(data[0],12);
 Stereo         = bitRead(data[0],10);
 Channel        = data[0] & 0b111111111;
 RSSI           = (data[1] >> 10) & 0b111111; //Register 0x0B

 FM_TRUE        = bitRead(data[1],8);
 FM_READY       = bitRead(data[1],7); 
 BLERA          = data[1] >> 2 & 3;
 BLERB          = data[1] >> 0 & 3;
 BLERC          = data[6] >> 14 & 3; //Register 0x10
 BLERD          = data[6] >> 12 & 3;
}

void radioInit()
{setRegister(0x02,0b1100000000001011); //RESET
 setRegister(0x03,0b0000000000000000);
 setRegister(0x04,0b0000101000000000);
 setRegister(0x05,0b1000100000001111); 
 setRegister(0x06,0b0000000000000000);
 setRegister(0x07,0b0100001000000010);
 setRegister(0x02,0b1100000000001101); //NO RESET
}

void setup()
{Serial.begin(9600);
 delay(200);
 Wire.begin(); 
 radioInit(); 
 setFrequency(STRONGSTATION);
 delay(100);
 Serial.print(getFrequency()/10.0);
 Serial.println(" MHz");
}

void loop()
{char s[80]; 
 if(Serial.available()>0)
  {radioRead();
   switch(Serial.read())
   {case 'D':setFrequency(DLF);break;
    case 'W':setFrequency(WDR5);break;
    case 'A':setFrequency(ANTENNE);break;
    case '4'://SEEK DOWN        xxxxxx0xxxxxxxxx
             setRegister(0x02,0b1100000100001101);
             break;//DOWN BIT 9
    case '0':Serial.println(RSSI);
             break;
    case '6'://SEEK UP          xxxxxx1xxxxxxxxx 
             setRegister(0x02,0b1100001100001101);   
             break;  
    case 'f':Serial.println(getFrequency()/10.0);
             break;
    default: sprintf(s,"f=%d RSSI=%d %s Tune_Complete=%d FM_READY=%d",
             Channel+870,RSSI,Stereo?"Stereo":"Mono",Tune_Complete,FM_READY);
             Serial.println(s);
             sprintf(s,"PI=%04X RDS=%d BLERA=%d BLERB=%d BLERC=%d BLERD=%d",
             data[2],RDS_Ready,BLERA,BLERB,BLERC,BLERD);
             Serial.println(s);
             break;
  }
 }   
 delay(100);
}


Software Informationen aus dem Radio - mit RDS-Decoder - 8680 Bytes (26%)
SI4735-Radio aus 2012Das obige Listing erinnert etwas an den Beitrag Si4735 - Digitales Analogradio, der auf dieser Seite erschienen ist. Das ist kein Zufall, denn während der Bibliothekssuche kamen einige Erinnerungen an dieses Projekt hoch. Die RDS-Dekodierung war damals nicht perfekt, aber zumindest Datum und Uhrzeit waren integriert, wie die alte Abbildung zeigt. Da RDS immer noch nach dem gleichen Prinzip ausgestrahlt wird, stellt sich die Frage, ob der alte Code recycelbar ist und so zum Ziel führt. Unten auf der dortigen Seite, die die RDS-Einbindung zur damaligen Zeit beschreibt, ist unter dem Titel Radiobausatz ein zip-Archiv mit allen Routinen abrufbar. Die dort enthaltene Routine RDS() wird übernommen und so verändert, dass alles über globale Variablen läuft. Dadurch entfallen die Parameter und der Sketch kann überall auf die decodierten RDS-Daten zugreifen und kommt weiterhin ohne Bibliothek aus. Lediglich die Registerinhalte, die mit radioRead() vom RDA-Chip geholt werden müssen in die damals benutzen Byte-Variablen R[] umkopiert werden. Die jetzigen globalen RDS-Variablen sind
// GLOBAL RDS VARS
char Station[9]="Radiuino";
char RadioText[65]="RadioTXT";
char Time[12]="UTC  /Time ";
char Date[11]="Date      ";
word PID;
word ABFlag=0,r;
bool AFflag=false;
byte R[16];// Response is 12 bytes
word A,B,C,D; //RDS Blocks
word PTY,TP,GT,AB,TA,MS,C12,T16;
/* ---------------------------------------------------------------*/

mit 64 Bytes für Radiotext, 9 Bytes für den Sendernamen, 12 Bytes für die Zeit in UTC und Lokale Zeit, 11 Bytes für den Datumstring. Die 5 Register müssen in die 12 Bytes von R[] umkopiert werden. darum ändert sich nur der Anfang der RDS()-Routine. C-Strings enden mit dem Byte 0, darum ein Byte länger.

int RDS() //Page 77
{int i,loopcount=1;
 while(loopcount--)
 {// added lines
  radioRead();
  R[4]=data[2]/256;
  R[5]=data[2]&255;
  R[6]=data[3]/256;
  R[7]=data[3]&255;
  R[8]=data[4]/256;
  R[9]=data[4]&255;
  R[10]=data[5]/256;
  R[11]=data[5]&255;
  // end added lines
...

Das gesamte Listing der Erweiterung wird nun zu lang für diese Seite und ist unter der Überschrift gespeichert. Der schnellste Weg ist das Kopieren des Textes aus dem Browser in einen neuen, leeren Sketch der Arduino-IDE. Das Ergebnis bei der Eingabe der Taste Enter kann nun wie folgt aussehen:

104.20 MHz
UTC /Time Date D09E/64/0 ANTENNE
Backstreet Boys - I Want It That Way
UTC /Time Date D09E/64/0 ANTENNE
(*E street Boys - I Want It That Way
UTC /Time Date D09E/64/0 ANTENNE
Backstreet Boys - I Want It That Way
15:43/16:43 25.03.2021 D09E/64/0 ANTENNE
Backstreet Boys - I Want It That Way

Etwa zur vollen Minute überträgt der RDS-Sender die Daten zur Uhrzeit. Somit steht diese Information dem Arduino zur Verfügung. Es ergibt sich eine weitere Variante zur Synchronisierung der Rheinturmuhr. Neben DCF77, GPS und Internet jetzt auch RDS.


Software Radio mit RDS-Decoder und automatische Zeitausgabe - 8848 Bytes (27%)
Soll ohne Steuerung über die Tastatur die Zeit zur Anzeige kommen, so kann ein Vergleich mit einer alten Zeit entscheiden, ob eine neue Zeit vorliegt. Diese decodierte Zeit kann bei fehlerhaften und gestörten RDS-Empfang falsch sein, darum sollen einige Faktoren herangezogen werden, die Fehlerhafte Zeit zu unterdrücken. Nur wenn eine neue Zeit vorliegt UND das erste und das letzte Zeichen im Sendernamen unterschiedlich ist UND alle RDS-Fehler laut Chip in der Summe 0 ergeben, dann könnte die Zeit auch bei Störungen stimmen und etwa zu vollen Minute tröpfelt bei gutem Empfang die gewünschte Ausgabe

104.20 MHz
OK 15:53/16:53 25.03.2021 D09E/64/0 ANTENNE
www.antenneduesseldorf.de

Dieser Gesamtsketch ist in der Überschrift verlinkt und hier abgelegt. Vor delay(100) in loop() wird der Aufruf RDS() darin erweitert zu

...
 char oldTime[12];
 strcpy(oldTime,Time);
 RDS();
 if(strcmp(Time,oldTime))
  if(Station[0]!=Station[7])
   if(BLERA+BLERB+BLERC+BLERD==0)
   {Serial.print("OK ");Serial.print('\t');
    Serial.print(Time);Serial.print('\t');
    Serial.print(Date);Serial.print('\t');
    Serial.print(PID,HEX);Serial.print('/');
    Serial.print(RSSI);Serial.print('/');
    Serial.print(BLERA+BLERB+BLERC+BLERD);
    Serial.print(' ');Serial.println(Station);
    Serial.println(RadioText);
   }
...
 delay(100);


Software Radio-Uhr aus dem RDS-Decoder am OLED - 24530 Bytes (76%)
Die Einbindung der Adafruit_SSD1306-Master Bibliothek bringt eine Überraschung. Beide I2C Busteilnehmer mögen sich nicht. Im Netz findet man Forenbeiträge, die dieses Phänomen bestätigen. Auch zeigt sich, dass dieses Display vermutlich Störungen verursacht, die den RDS-Empfang des unempfindlichen Tuners nochmals deutlich einschränken. Versuche mit Lösungen aus dem Netz lieferten nur zum Teil Abhilfe. So klappt eine verzerrte Anzeige mit der Bibliothek, wenn einige Parameter weggelassen werden. Eine weitere Suche nach einer BitBang-Lösung, also das eigenständige Schalten von alternativen I/O-Leitungen entsprechend dem I2C-Protokoll, sollte es ermöglichen die Unverträglichkeit der beiden Hardwarekomponenten zu umgehen. Die Störungen bleiben dadurch bestehen, aber es gibt eine entsprechende Anzeige.

Wegen der Störungen ist es nicht möglich die RDS-Zeit mit Datum einfach zu übernehmen und anzuzeigen. Vielmehr kann die RDS-Zeit nur als sporadische Synchronisationsquelle herangezogen werden, wenn fehlerfreie Daten vorliegen. Dieses Prinzip wohnt ja auch DCF-Uhren inne. Durch die verschiedenen Rheinturmuhrvarianten auf dieser Seite ist die TimeLib-Bibliothek vertraut. Sie bildet eine eigene Zeit und Datumsquelle ab, die mit externen Quellen synchronisiert werden kann. Sie beginnt am Donnerstag, den 1.1.1970 um 0:00 Uhr. Wird diese Arduino-Uhr mit der RDS-Uhr gestellt, stehen auch Wochentag und Sekunden zur Verfügung. All dies benötigt folgende Bibliotheken von github.com

Mit Multi-Oled/BitBang lassen sich viele Anzeigen mit gleicher Adresse an verschiedenen I2C Leitungen betreiben. Es stehen Zeichensätze in drei Größen zur Verfügung. Der Initialisierungteil zu den benutzten drei Bibliotheken enthält auch die Anschlüsse in rot:

// OLED MULTI BITBANG
#include <Multi_BitBang.h>
#include <Multi_OLED.h>

#define NUM_DISPLAYS 2
#define NUM_BUSES 1
// I2C bus info
uint8_t scl_list[NUM_BUSES] = {5};
uint8_t sda_list[NUM_BUSES] = {6};
int32_t speed_list[NUM_BUSES] = {1000000L};
// OLED display info
uint8_t bus_list[NUM_DISPLAYS] = {0,0}; // multiple displays per bus
uint8_t addr_list[NUM_DISPLAYS] = {0x3c, 0x3d};
uint8_t type_list[NUM_DISPLAYS] = {OLED_132x64, OLED_132x64};
uint8_t flip_list[NUM_DISPLAYS] = {0,0};
uint8_t invert_list[NUM_DISPLAYS] = {0,0};

// TIMELIB ARDUINO CLOCK
#include <TimeLib.h>
char *days[]={"...","Sonntag","Montag","Dienstag","Mittwoch",
                    "Donnerstag","Freitag","Sonnabend"};

In der Arduino loop() findet dauerhaft die Darstellung der Arduino-Uhr statt. Falls die Bedingungen es zulassen wird die Uhr nach der RDS-Zeit gestellt und die komplette Anzeige einmal gelöscht. Der Ausschnitt:

    // OLED
    Multi_OLEDFill(0, 0); //CLEAR
    int hh=atoi(strncpy(s,&Time[6],2));
    int mm=atoi(strncpy(s,&Time[9],2));
    int dd=atoi(strncpy(s,&Date[0],2));
    int mt=atoi(strncpy(s,&Date[3],2));
    int yy=atoi(strcpy(s,&Date[6]));
    setTime(hh,mm,0,dd,mt,yy); //ARDUINO CLOCK = RDS CLOCK
   }
 // OLED LIVE RDS INFO LINES
 int f=getFrequency()/10;int g=getFrequency()%10;
 sprintf(s, "%s %d.%d %04X %02d", Station,f,g,PID,RSSI);
 Multi_OLEDWriteString(0, 0, 0, s, FONT_SMALL, 0);
 sprintf(s, "%s %s%c%c%c%c%c", &Time[6],Date, Stereo?'S':'M',
  TP?'T':'t',TA?'A':'a',RDS_Ready?'R':'r',RDS_Sync?'S':'s');
 Multi_OLEDWriteString(0, 0, 1, s, FONT_SMALL, 0);
 Multi_OLEDWriteString(0, 0, 2, (char *)"---------------------", 
   FONT_SMALL, 0);
RDS-CLOCK-DISPLAY // OLED CLOCK
 int ss=second();
 if(ss!=last)
 {sprintf(s, "%s ",days[weekday()]);
  Multi_OLEDWriteString(0, 0, 7, s, FONT_SMALL, 0);
  sprintf(s, "%02d.%02d.%04d",day(),month(),year());
  Multi_OLEDWriteString(0, 66, 7, s, FONT_SMALL, 0);
  sprintf(s, "%2d:%02d:%02d", hour(),minute(),ss);
  Multi_OLEDWriteString(0, 0, 3, s, FONT_LARGE, 0);
  last=ss; 
 }
 delay(100);
}

Dieser Gesamtsketch ist hier abgelegt.

Software Finale Version mit Sommer-/Winterzeit am OLED - 25696 Bytes (79%)
Die finale Version berücksichtigt die kritischen Stunden zwischen Mitternacht und 2:00 Uhr, wenn sich Weltzeit und Lokalzeit in Mitteleuropa im Datum unterscheiden. Das unvollkommene RDS() dekodiert zwar UTC/Lokal-Zeit, aber nur das UTC-Datum. Frühaufsteher merken das nicht, aber die Nachteule stutzt. Die Lösung dieser kleinen Ungenauigkeit kann der GPS-Uhr hier auf der Seite entnommen werden. Die Arduino-Bibliothek TimeZone.h löst dieses Problem für die Arduino-Uhr. Für die Zeitzone in Mitteleuropa gilt dann
// TIMELIB TIMEZONE ARDUINO CLOCK
#include <TimeLib.h>
#include <Timezone.h> 
TimeChangeRule CEST = {"", Last, Sun, Mar, 2, 120};     
TimeChangeRule CET = {"", Last, Sun, Oct, 3, 60}; 
Timezone CE(CEST, CET);
TimeChangeRule *tcr; 

und die Synchonisation der Arduino-Uhr mit der RDS-UTC-Zeit hat dann folgendes Aussehen:
    //CLOCK SYNC
    int hh=atoi(strncpy(s,&Time[0],2));//UTC
    int mm=atoi(strncpy(s,&Time[3],2));
    int dd=atoi(strncpy(s,&Date[0],2));
    int mt=atoi(strncpy(s,&Date[3],2));
    int yy=atoi(strcpy(s,&Date[6]));
    setTime(hh,mm,1,dd,mt,yy); //UTC
    time_t cet=CE.toLocal(now(),&tcr);
    setTime(cet);//TimeZone: UTC --> LOCAL
In der zweiten Zeile der Anzeige steht nun ebenfalls UTC, während die Großanzeige die Arduino-Uhr mit Lokalzeit/Datum zeigt. Eine weitere Ergänzung versucht fehlerhaften Empfang dieses benutzten Exemplars weiter zu analysieren. So wird ein Zähler pids hochgezählt, wenn sich die RDS-Kennung des Senders nicht ändert. Der Zähler läuft von 0 bis 999 und könnte als Übernahmekriterium der Zeit herangezogen werden.

Die obere Zeile besteht jetzt aus

8
Zeichen RDS-Sendername
ANTENNE
5
Zeichen Frequenz in MHz
104.2
4
Zeichen PID-Zähler
999
2
Zeichen RSSI-Wert
63

Die zweite Zeile zeigt Ausschnitte aus dem Maschinenraum des RDS-Decoders:

5
Zeichen UTC
16:50
10
Zeichen Datum (UTC)
30.03.2021
1
Zeichen Stereo/Mono
S/s
1
Zeichen Verkehrsfunksender
T/t
1
Zeichen Verkehrsfunkansage
A/a
1
Zeichen RDS Ready
R/r
1
Zeichen RDS Sync
S/s

Wechselt das R/r für RDS Ready vor sich hin, so ist der Empfang in Ordnung.
Der finale Sketch als RDA_OLED_FINAL.ino


Möglicherweise ging hier ein besonders unempfindliches Exemplar des RDA an den Start - das Sonntagsnachmittagprojekt mit drei Klicks hat sich dann doch um einige Tage verlängert. Mit Ausflügen zu anderen Bibliotheken und eigenen Routinen aus früheren Tagen funktioniert die RDS-Uhr dann doch noch - meist - so, wie sie sollte. Auch entstand ein Dudelfunk-Blocker.


Arduino goes Radio I
Arduino goes Radio II
Rheinunio77
GPS-Uhr-Zeit
Funkuhr mit dem Radio
Arduino Morse-Decoder
Mehr Software

.
Startseite Bücher Software Digital RTV Musik Kontakt

Für Inhalt und weitere Verzweigung externer Links sind die Betreiber der dortigen Seiten verantwortlich - H.-J. Berndt