New Software for Si4713 FM Transmitter with RDS
This posting describes a complete low power FM transmitter, based on the Si4713 IC, that can be built using 3 inexpensive modules. The result is a transmitter that testing has shown to work reliably and which provides best in class sound quality.
I use an FM transmitter for Christmas music in conjunction with an outdoor display. As an electrical engineer I have the test equipment to debug the problems that have been reported with the Si4713 boards, and I am sharing what I learned here. This is how I got a Si4713 FM stereo transmitter board working. I have tested this simple hardware and software modification successfully with 4 CJMCU-4713 boards and also with both Arduino UNO and Arduino NANO boards, using the latest version (2.3.7) of Arduino IDE. I have also tested the RDS option successfully. The boards start up reliably every time. The sound quality is excellent with the Si4713, noticeably better than other FM modulator ICs like the BH series. Unfortunately, the Si4713 IC has been obsoleted, and I assume the supply of ICs will eventually disappear.
CJMCU-4713 PC BOARD
The original Adafruit Si4713 breakout board is no longer available. But there are a lot of cheap clone boards, usually marked “CJMCU-4713,” available on Ebay and Amazon. It looks identical to the Adafruit board, but I am not sure it is 100% electrically identical.
LEVEL SHIFTER NEEDED ON RESET LINE!!
Most Arduinos work on 5 volts, but the Si4713 IC on these boards apparently works on 3.3 volts. The board apparently has suitable circuity so that it is compatible with a 5 volt power supply and 5 volt I2C data from the Arduino. But the reset line is the exception. Applying 5 volts to the RST pin on the board prevents the board from working, and might? cause permanent damage from overheating!
A simple 2 resistor level shifter can avoid the problem. Before applying power to the board, construct a level shifter with 2 resistors.
1) Connect one end of a 1Kohm (brown-black-red) resistor to Digital 12 pin on the Arduino.
2) Connect one end of a 2Kohm (red-black-red) resistor to GND on the Arduino board.
3) Solder or twist the free ends of the 2 resistors together, and connect this point to RST on the CJMCU-4713 board.
CONNECTIONS
In addition to the RST connection above, make these connections between the Arduino and the CJMCU-4713:
Arduino 5V to CJMCU-4713 Vin
Arduino GND to CJMCU-4713 GND
Arduino A5 to CJMCU-4713 SCL
Arduino A4 to CJMCU-4713 SDA
NEW SOFTWARE
I did not have success using the Adafruit/Arduino test software, and just rewrote a simplified version, working from the datasheets for the IC. To use this new software:
1) Open the Arduino IDE and select File > New sketch
2) Copy and paste one of the code options below into the new window
3) File > Save as, and give it a name
4) Set the frequency according to the directions in the software. Pick a frequency where you do not hear any FM stations. Interference to a local station could result in a complaint to a regulating body (FCC in U.S.).
5) Connect the Arduino and CJMCU-4713 board together as above.
6) Connect a 30 inch long piece of wire or other antenna to the “Ant” pad on the board
7) Plug in an audio source from your phone or other device.
8) Plug in the USB cable and Upload (right arrow at top left) to the Arduino
9) It should show a series of steps to set up the CJMCU-4713 with “OK” after each step
10) Now you should hear the audio at the frequency you selected on a nearby FM radio.
11) Adjust the volume of your source audio just high enough so the sound is about as loud as other FM stations, but not so loud that it is distorted.
Use this code for audio transmission only:
#include <Wire.h>
// SET THESE 5 VALUES TO CONTROL THE Si4713 IC:
int frequency = 10330; // set carrier frequency: 7600 (76.00MHz) to 10790 (107.90MHz) (Must be a multiple of 50 KHz: 7600, 7605, 7610, ..., 10790)
// Choose a frequency not used by any local-area FM station to prevent mutual interference.
// Observe government regulations for unlicensed devices in the FM band.
// US FM stations and FM receivers use odd multiples of 100 KHz from 88.1 to 107.9 MHz (88.1, 88.3, ..., 107.7, 107.9)
unsigned int REFCLK = 32768; // Normally set at 32768. Using a frequency counter, adjust up or down slightly to compensate any carrier frequency error.
// Each step will shift the carrier frequency by about 3000 Hz
byte RFout = 100; // set RF output voltage: 88 to 115 dBuV
byte Preemphasis = 0; // Set FM preemphasis: 0 = 75usec (US, Canada), 1 = 50usec (Europe, Australia, Japan), 2 = no preemphasis (for testing)
byte DynamicRange = 3; // Dynamic range compression: 3 = Compression on (Recommended. Less background noise., Used by nearly all FM stations),
// 2 = Peak limiting only (Natural dynamic range, but more background noise and requires careful control of audio input levels)
// 0 = Off (NOT recommended. May cause over-modulation, distortion, possible shutdown of si4713 IC)
void setup() {
byte args[8]; // for SendI2C
int addr = 0x63; // hex address of si4713 board. Do not change.
Serial.begin(9600);
// while (!Serial); // Wait for serial monitor - only for special cases
delay(1000); // wait for board to stabilize
Serial.println("Resetting Si4713\n");
pinMode(12, OUTPUT); // Sets the digital pin 12 as output
digitalWrite(12, HIGH); // Sets the digital pin 12 high
delay(50); // wait 50ms
digitalWrite(12, LOW); // Sets the digital pin 12 low
delay(10); // wait 10ms
digitalWrite(12, HIGH); // Sets the digital pin 12 high
delay(500); // Wait 500 ms
byte error;
Wire.begin();
Serial.println("\nChecking for si4713 board connected and responding");
// Uses the return value of
// the Write.endTransmisstion to see if
// a device did acknowledge to the address.
Wire.beginTransmission(addr);
error = Wire.endTransmission();
if (error == 0) {
Serial.println("Board found!");
}
else {
Serial.println("Board NOT found");
}
byte ret1, cmd, val1, val2;
String result;
size_t nargs;
Serial.println("\nPowering up");
args[0] = 0x01; // command code
args[1] = 0b00010010; // CTS interrupt disabled, GPO2 output disabled, boot normally,
// Use crystal oscillator, Transmit mode
args[2] = 0b01010000; // OPMODE: Analog audio inputs
nargs = 3;
result = SendI2C(args, nargs, addr);
delay(1000); // wait for crystal oscillator to stabilize
// get return code after oscillator has stabilized
size_t retcode, comp;
String str;
retcode = -1;
Wire.requestFrom(addr, 1); // Request 1 byte
retcode = Wire.read();
comp = retcode & 0xc0;
if(comp == 128) {
str = "0x" + String(retcode, HEX) + " OK";
}
else {
str = "0x" + String(retcode, HEX) + " ****ERROR****";
}
Serial.println(str);
Serial.println("\nSetting frequency");
args[0] = 0x30; // command code
args[1] = 0;
args[2] = (frequency >> 8) & 0xFF; // high byte
args[3] = frequency & 0xFF; // low byte
nargs = 4;
result = SendI2C(args, nargs, addr);
Serial.println(result);
Serial.print("\nSetting RF output power");
args[0] = 0x31; // command code
args[1] = 0;
args[2] = 0;
args[3] = RFout;
// Calculate antenna capacitor required for resonance
float myC, myF, myL;
unsigned char ANTCAP;
myL = 120.0e-9; // 120nH inductor
myF = frequency * 10000.0; // carrier frequency in Hz
myC = 1.0e12 / (39.478 * myF * myF * myL); // Capacitance needed for resonance in pF
ANTCAP = round(4.0 * myC);
Serial.print("\nCapacitance = ");
Serial.print(myC);
Serial.print("pF ANTCAP = ");
Serial.print(ANTCAP);
args[4] = ANTCAP;
nargs = 5;
result = SendI2C(args, nargs, addr);
Serial.println("\n" + result);
Serial.println("\nSetting preemphasis");
args[0] = 0x12; // SET_PROPERTY code
args[1] = 0x00; // always 0
args[2] = 0x21; // property code high byte
args[3] = 0x06; // property code low byte
args[4] = 0x00; // property value high byte
args[5] = Preemphasis; // property value low byte
nargs = 6;
result = SendI2C(args, nargs, addr);
Serial.println(result);
Serial.println("\nSetting dynamic range compression");
args[0] = 0x12; // SET_PROPERTY code
args[1] = 0x00; // always 0
args[2] = 0x22; // property code high byte
args[3] = 0x00; // property code low byte
args[4] = 0x00; // property value high byte
args[5] = DynamicRange; // property value low byte
nargs = 6;
result = SendI2C(args, nargs, addr);
Serial.println(result);
Serial.println("\nFine tuning carrier frequency (REFCLK)");
args[0] = 0x12; // SET_PROPERTY code
args[1] = 0x00; // always 0
args[2] = 0x02; // property code high byte
args[3] = 0x01; // property code low byte
args[4] = highByte(REFCLK); // property value high byte
args[5] = lowByte(REFCLK); // property value low byte
nargs = 6;
result = SendI2C(args, nargs, addr);
Serial.println(result);
// prototype for command calls
/*
Serial.println("\n");
args[0] = 0x; // command code
args[1] = 0;
args[2] = 0;
args[3] = 0;
args[4] = 0;
nargs = ;
result = SendI2C(args, nargs, addr);
Serial.println(result);
*/
}
// Function for I2C communication with CJMCU-4713 board
String SendI2C(byte args[8], size_t nargs, int addr) { // sends command to si4713; returns response: "OK" or "****ERROR****"
size_t retcode, comp;
String str;
Wire.beginTransmission(addr); // send commands to si4713
Wire.write(args, nargs);
retcode = Wire.endTransmission();
delay(100); // wait for operation to complete
retcode = -1;
Wire.requestFrom(addr, 1); // get status byte from si4713
retcode = Wire.read();
comp = retcode & 0xc0;
if(comp == 128) {
str = "0x" + String(retcode, HEX) + " OK";
}
else {
str = "0x" + String(retcode, HEX) + " ****ERROR****";
}
return str;
}
void loop() {
// put your main code here, to run repeatedly:
}
Alternatively, use this code instead for audio plus RDS transmission: (RDS is an international standard for short text transmission along with audio. Usually used to show station ID and song title and artist info.)
#include <Wire.h>;
#include <string.h>;
// SET THESE 5 VALUES TO CONTROL THE Si4713 IC:
int frequency = 10330; // Set carrier frequency: 7600 (76.00MHz) to 10790 (107.90MHz) (Must be a multiple of 50 KHz: 7600, 7605, 7610, ..., 10790)
// Choose a frequency not used by any local-area FM station to prevent mutual interference.
// Observe government regulations for unlicensed devices in the FM band.
// US FM stations and FM receivers use odd multiples of 100 KHz from 88.1 to 107.9 MHz (88.1, 88.3, ..., 107.7, 107.9)
unsigned int REFCLK = 32768; // Normally set at 32768. If a frequency counter is available, adjust up or down slightly to compensate any carrier frequency error.
// Each step will shift the carrier frequency by about 3000 Hz
byte RFout = 100; // set RF output voltage: 88 to 115 dBuV
byte Preemphasis = 0; // Set FM preemphasis: 0 = 75usec (US, Canada), 1 = 50usec (Europe, Australia, Japan), 2 = no preemphasis (for testing)
byte DynamicRange = 3; // Dynamic range compression: 3 = Compression on (Recommended. Less background noise., Used by nearly all FM stations),
// 2 = Peak limiting only (Natural dynamic range, but more background noise and requires careful control of audio input levels)
// 0 = Off (NOT recommended. May cause over-modulation, distortion, possible shutdown of si4713 IC)
byte RDS = 1; // 0 = RDS OFF; 1 = RDS ON
byte msg = 1; // 0 = Serial data messages OFF; 1 = messages ON. Once operation is verified in IDE environment, set to OFF to avoid crashes with incoming RDS data.
// Other global variables
char Station[16] = "PSdata "; // Program Service Name (PS) buffer. 8 characters maximum. Must have 3 blanks added at end
char Buffer[70] = "Radio text: The quick brown fox "; // RadioText (RT) buffer. 56 characters maximum. Must have 3 blanks added at end
int addr = 0x63; // hex address of si4713 board. Do not change.
byte args[8]; // for SendI2C
void setup() {
Serial.begin(9600);
// while (!Serial); // Wait for serial monitor
delay(1000); // wait for board to stabilize
Serial.println("Resetting Si4713\n");
pinMode(12, OUTPUT); // Sets the digital pin 12 as output
digitalWrite(12, HIGH); // Sets the digital pin 12 high
delay(50); // wait 50ms
digitalWrite(12, LOW); // Sets the digital pin 12 low
delay(10); // wait 10ms
digitalWrite(12, HIGH); // Sets the digital pin 12 high
delay(500); // Wait 500 ms
byte error;
Wire.begin();
Serial.println("\nChecking for si4713 board connected and responding");
// Uses the return value of
// the Write.endTransmisstion to see if
// a device did acknowledge to the address.
Wire.beginTransmission(addr);
error = Wire.endTransmission();
if (error == 0) {
Serial.println("Board found!");
} else {
Serial.println("Board NOT found");
}
byte ret1, cmd, val1, val2;
String result;
size_t nargs;
Serial.println("\nPowering up");
args[0] = 0x01; // command code
args[1] = 0b00010010; // CTS interrupt disabled, GPO2 output disabled, boot normally,
// Use crystal oscillator, Transmit mode
args[2] = 0b01010000; // OPMODE: Analog audio inputs
nargs = 3;
result = SendI2C(args, nargs);
delay(1000); // wait for crystal oscillator to stabilize
// get return code after oscillator has stabilized
size_t retcode, comp;
String str;
retcode = -1;
Wire.requestFrom(addr, 1); // Request 1 byte
retcode = Wire.read();
comp = retcode & 0xc0;
if (comp == 128) {
str = "0x" + String(retcode, HEX) + " OK";
} else {
str = "0x" + String(retcode, HEX) + " ****ERROR****";
}
Serial.println(str);
Serial.println("\nSetting frequency");
args[0] = 0x30; // command code
args[1] = 0;
args[2] = (frequency >> 8) & 0xFF; // high byte
args[3] = frequency & 0xFF; // low byte
nargs = 4;
result = SendI2C(args, nargs);
Serial.println(result);
Serial.print("\nSetting RF output power");
args[0] = 0x31; // command code
args[1] = 0;
args[2] = 0;
args[3] = RFout;
// Calculate antenna capacitor required for resonance
float myC, myF, myL;
unsigned char ANTCAP;
myL = 120.0e-9; // 120nH inductor
myF = frequency * 10000.0; // carrier frequency in Hz
myC = 1.0e12 / (39.478 * myF * myF * myL); // Capacitance needed for resonance in pF
ANTCAP = round(4.0 * myC);
Serial.print("\nCapacitance = ");
Serial.print(myC);
Serial.print("pF ANTCAP = ");
Serial.print(ANTCAP);
args[4] = ANTCAP;
nargs = 5;
result = SendI2C(args, nargs);
Serial.println("\n" + result);
Serial.println("\nSetting preemphasis");
args[0] = 0x12; // SET_PROPERTY code
args[1] = 0x00; // always 0
args[2] = 0x21; // property code high byte
args[3] = 0x06; // property code low byte
args[4] = 0x00; // property value high byte
args[5] = Preemphasis; // property value low byte
nargs = 6;
result = SendI2C(args, nargs);
Serial.println(result);
Serial.println("\nSetting dynamic range compression");
args[0] = 0x12; // SET_PROPERTY code
args[1] = 0x00; // always 0
args[2] = 0x22; // property code high byte
args[3] = 0x00; // property code low byte
args[4] = 0x00; // property value high byte
args[5] = DynamicRange; // property value low byte
nargs = 6;
result = SendI2C(args, nargs);
Serial.println(result);
Serial.println("\nFine tuning carrier frequency (REFCLK)");
args[0] = 0x12; // SET_PROPERTY code
args[1] = 0x00; // always 0
args[2] = 0x02; // property code high byte
args[3] = 0x01; // property code low byte
args[4] = highByte(REFCLK); // property value high byte
args[5] = lowByte(REFCLK); // property value low byte
nargs = 6;
result = SendI2C(args, nargs);
Serial.println(result);
if (RDS == 1) {
Serial.println("\nSetting main channel modulation");
unsigned int MainDev = 6375; // Set main channel modulation 85% (63.75KHz) to allow headroom for 9% stereo pilot & 6% RDS
args[0] = 0x12; // SET_PROPERTY code
args[1] = 0x00; // always 0
args[2] = 0x21; // property code high byte
args[3] = 0x01; // property code low byte
args[4] = highByte(MainDev); // property value high byte
args[5] = lowByte(MainDev); // property value low byte
nargs = 6;
result = SendI2C(args, nargs);
Serial.println(result);
Serial.println("\nSetting RDS subchannel modulation");
unsigned int RDSDev = 450; // Set RDS modulation 6% (4.5KHz)
args[0] = 0x12; // SET_PROPERTY code
args[1] = 0x00; // always 0
args[2] = 0x21; // property code high byte
args[3] = 0x03; // property code low byte
args[4] = highByte(RDSDev); // property value high byte
args[5] = lowByte(RDSDev); // property value low byte
nargs = 6;
result = SendI2C(args, nargs);
Serial.println(result);
Serial.println("\nEnabling RDS");
args[0] = 0x12; // SET_PROPERTY code
args[1] = 0x00; // always 0
args[2] = 0x21; // property code high byte
args[3] = 0x00; // property code low byte
args[4] = 0x00; // property value high byte
args[5] = 0x07; // property value low byte
nargs = 6;
result = SendI2C(args, nargs);
Serial.println(result);
}
// prototype for command calls
/*
Serial.println("\n");
args[0] = 0x; // command code
args[1] = 0;
args[2] = 0;
args[3] = 0;
args[4] = 0;
nargs = ;
result = SendI2C(args, nargs);
Serial.println(result);
*/
}
// Function for I2C communication with CJMCU-4713 board
String SendI2C(byte args[8], size_t nargs) { // sends command to si4713; returns response: "OK" or "****ERROR****"
size_t retcode, xERR, xCTS;
String str;
Wire.beginTransmission(addr); // send commands to si4713
Wire.write(args, nargs);
retcode = Wire.endTransmission();
xCTS = 0;
while (xCTS != 0x80) { // wait for CTS
delay(30);
Wire.requestFrom(addr, 1); // get status byte from si4713
retcode = Wire.read();
xCTS = retcode & 0x80;
}
xERR = retcode & 0x40;
if (xERR == 0) {
str = "0x" + String(retcode, HEX) + " OK";
} else {
str = "0x" + String(retcode, HEX) + " ****ERROR****";
}
return str;
}
void loop() {
String str, result;
size_t nargs, j;
byte offset;
size_t retcode;
unsigned int nblocks;
byte reps = 0;
byte state = 1; // 1=OK to read, nothing read yet; 2="+" symbol read; 3= "P" symbol read, read PS buffer; 4= "R" symbol read, read RT buffer
byte changed = 0; // 0 = no changes; 3 = PS buffer changed; 4 = RT buffer changed
char rc; // character read from port
int8_t ndx = -1; // String index
if (RDS == 1) {
// START OF SERIAL DATA SECTION
// Receives data for the Program Service Name (PS) and RadioText (RT) buffers from a computer
// via USB using the same virtual serial port used by the Arduino IDE.
// The port number will be shown in the USB connection box at top left of the IDE screen.
// The computer program must open the port at the same baud rate as the serial monitor, usually 9600.
// Computers generally allow only one app to access a serial port. Therefore, you will need to close the Arduino
// IDE before opening the port in another app.
// The computer must send a string consisting of:
// 1) A two-character ASCII header: "+P" to load the Program Service Name (PS) buffer (Usually station ID),
// OR "+R" to load the RadioText (RT) buffer (Usually Song title and artist).
// 2) The ASCII string to be loaded. (Maximum 8 characters for PS buffer; 56 characters for RT buffer)
// 3) A termination character /n or /r or both. (/n = ASCII 10 = 0x0a; /r = ASCII 13 = 0x0d)
// Test by typing the string as above into the box at top of the of the Arduino IDE Serial Monitor window
// If serial data is not used, then comment out this section, and set values of variables changed, Station, and Buffer strings some other way.
getserial:
while (Serial.available() > 0) {
rc = Serial.read();
if ((rc == 0x0d) || (rc == 0x0a)) { // termination character \r or \n
changed = state;
state = 1;
ndx = -1;
} else if (state == 1) {
if (rc == 0x2b) { // "+"
state = 2;
}
} else if (state == 2) {
if (rc == 0x50) { // "P"
state = 3;
Station[0] = '\0'; // clear string
} else if (rc == 0x52) { // "R"
state = 4;
Buffer[0] = '\0'; // clear string
} else {
state = 1; // header not found
ndx = -1;
}
} else if (state == 3) { // read PS buffer
ndx += 1;
if (ndx <= 7) { // only 8 bytes allowed
Station[ndx] = rc; // append the character
Station[ndx + 1] = 0x20; // append 3 blanks
Station[ndx + 2] = 0x20;
Station[ndx + 3] = 0x20;
Station[ndx + 4] = '\0'; // null terminate
}
} else if (state == 4) { // read RT buffer
ndx += 1;
if (ndx <= 55) { // only 56 bytes allowed
Buffer[ndx] = rc; // append the character
Buffer[ndx + 1] = 0x20; // append 3 blanks
Buffer[ndx + 2] = 0x20;
Buffer[ndx + 3] = 0x20;
Buffer[ndx + 4] = '\0'; // null terminate
}
} else {
state = 0; // An error occurred
ndx = -1;
}
}
if (state > 1) { // Serial input is pending; wait and go back for more input data instead of sending partial data
delay(100);
reps += 1;
if (reps < 5) { // timeout after 4 tries (400 msec)
goto getserial;
}
}
state = 1; // just to be sure
ndx = -1;
reps = 0;
// END OF SERIAL DATA SECTION
if (changed == 3) {
changed = 0;
offset = 0;
nblocks = strlen(Station) / 4;
for (j = 0; j < nblocks; j++) { // Set Station
args[0] = 0x36; // command code
args[1] = j;
args[2] = Station[offset];
args[3] = Station[1 + offset];
args[4] = Station[2 + offset];
args[5] = Station[3 + offset];
nargs = 6;
result = SendI2C(args, nargs);
if(msg == 1){Serial.println(result);}
offset += 4;
}
if(msg == 1){Serial.println("PS: " + String(Station) + "\n");}
} else if (changed == 4) {
changed = 0;
offset = 0;
nblocks = strlen(Buffer) / 4;
for (j = 0; j < nblocks; j++) { // Set Buffer
args[0] = 0x35; // command code
args[1] = 0x04;
if (j == 0) { args[1] = 0x06; }
args[2] = 0x20;
args[3] = j;
args[4] = Buffer[offset];
args[5] = Buffer[1 + offset];
args[6] = Buffer[2 + offset];
args[7] = Buffer[3 + offset];
nargs = 8;
result = SendI2C(args, nargs);
if(msg == 1){Serial.println(result);}
offset += 4;
}
if(msg == 1){Serial.println("RT: " + String(Buffer) + "\n");}
}
}
}
TROUBLESHOOTING
If the above procedure does not work, first check that everything is wired correctly. Check for loose connections and shorts. All wires should be soldered or connected with good quality breadboard jumpers. Check for bad solder joints. Finally, try another CJMCU-4713 board if possible. One of the 5 I bought was a dud.
RF POWER OUTPUT
The Si4713 datasheet says maximum RF Output voltage is 120 dBuV (1.0 volt), but it does not specify the load impedance. That would be 20 mW or +13 dBm into a 50 ohm load. But, in actuality, the CJMCU-4713 boards can only put out about 45 mV into a 50 ohm load. That is 0.041 mW or -14 dBm. Usually, some additional RF amplification will be needed to achieve a reception range of 200 ft. as suggested as the maximum by the U.S. FCC. A small low noise amplifier (LNA) board will work nicely for this application.
UNEXPECTED SHUTDOWNS
There are many reports online of the Si4713 shutting down unexpectedly, and I have observed that also in initial testing. Most of what I have seen seems related to 1) power issues, 2) mismatched antenna or sudden antenna changes, or 3) over-modulation. So here are some suggestions, mostly gathered from an internet search, to resolve the problem:
a) The CJMCU-4713 board seems to be much more stable when not taking its power from a USB port. Once the Arduino is programmed; it does not need the USB cable connected to a computer. Instead, plug it into a 5 volt USB charging adapter. Or better yet, use an external 5 volt DC power supply capable of at least 1 amp output connected to the 5V terminal on the Arduino.
Though not strictly necessary, it is a good idea when using an external power supply to cut the VUSB (+5V) wire in the USB cable to prevent any possibility of DC power flowing back through the cable and possibly damaging connected equipment. Note that it is not always the red wire; color codes vary. Also, keep the USB cable unplugged when not needed in case a stray signal could cause a crash.
b) Solder all connections if possible. Loose and intermittent connections are common in breadboard setups.
c) Use a rigid telescoping antenna adjusted to about ¼ wavelength long (30 inches or 75 cm). Don’t let anything touch the antenna during operation. A power off reboot is the only known way to recover from an antenna fault.
d) Keep the dynamic range compression and peak limiting turned on, and avoid over-modulation. Adjust the audio level going into the device just high enough that the volume sounds similar to licensed FM stations.
e) Plugging in the audio cable or opening the Arduino IDE with USB connected can cause a shutdown. A power off reboot is the only known way to recover from these 2 cases.
f) If connecting to a coax cable, connect the shield to the RF ground point (see photo). You have to scrape the mask layer off the PC board with an X-Acto knife so you can solder to the bare copper trace.
g) A 3 or 6 dB coaxial attenuator in the coax line might help isolate the board from unsuitable RF load impedances.
TEST MODEL
The model I built implements many of the ideas above and adds a few more components for stability and protection. The output power is about 1 mW or 0 dBm. All connections are soldered, and it is mounted in a metal box for mechanical and electrical stability. A wiring diagram and a photo of the test model are below for anyone interested.
TEST RESULTS
The test model started up correctly every time in 30+ trials. In a 24-hour-long test with audio modulation there was not a single unexpected shutdown or other glitch. In other words, all the reported problems seem to be gone with this design.
CONCLUSION
The test results show that the combination of this new software and the construction methods used here can result in an inexpensive transmitter that works reliably and is capable of best in class audio quality.
DATASHEETS for Si4713:
Datasheet: https://datasheet4u.com/pdf-down/S/i/4/Si4712-B30-SiliconLaboratories.pdf
Programming guide: AN332 Revision 1.2: 4735.zip — Yandex Disk
- Here are the wiring diagram and a photo of the test model for anyone interested:

