Skip to content

Instantly share code, notes, and snippets.

@JonathanThorpe
Last active May 26, 2024 12:40
Show Gist options
  • Save JonathanThorpe/f23480c781ca62a28647 to your computer and use it in GitHub Desktop.
Save JonathanThorpe/f23480c781ca62a28647 to your computer and use it in GitHub Desktop.
Si473x i2c/i2s radio for Rasperrby Pi
#!/usr/bin/python3
import quick2wire.i2c as i2c
import time
import RPi.GPIO as GPIO
import alsaaudio
import threading
import logging
import sys
#Work in progress library for Si473x for Raspberry Pi by Jonathan Thorpe <jt@jonthorpe.net>
#SiPiRadio
#
#Wiring:
# RPi Si473x Module
# i2s SDA i2c SDIO
# i2s SCL i2c SCLK
#
# GPIO 15 RESET
# GPIO 4 RCLK
#
# PCM_CLK DCLK
# PCM_FS DFS
# PCM_IN DOUT
#
# GPIO 4 must be set to clock mode at the appropriate rate.
# Can't find a way to do this in Python yet - use the command line:
# gpio -g mode 4 clock
# gpio -g clock 4 34406
#
# Before running this program, you need to load the my_loader.c kernel module from here:
# https://www.raspberrypi.org/forums/viewtopic.php?f=44&t=91237
#
# Before compiling this module, ensure daifmt is set in clock and frame slave mode (Si47xx operates in slave only mode):
# .daifmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS
#
#
# TODO (essentially implement more of the programmer's guide):
# 1. Support AM/SW/LW
# 2. Get Status information
# 3. FM RDS information
# 4. Make sure I'm using ALSA properly - seems a bit hacky
#
# Acknowledgments:
# https://github.com/rickeywang/Si4737_i2c was useful for seeing how to program this device over i2c
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
# ===========================================================================
# Si473x I2C / I2S Class
# ===========================================================================
class SiPiRadio():
#GPIO pin for issuing the device reset
GPIO_RESET=15
#Si473x i2c address
I2C_ADDRESS=0x63
#The i2s Device as found from ALSA (my_loader.ko)
AUDIO_IN_DEV="hw:CARD=sndrpisimplecar,DEV=0"
#Default device to send audio out of
AUDIO_OUT_DEV="default"
#Audio sample rate
AUDIO_SAMPLE_RATE=48000
AUDIO_CHANNELS=2
AUDIO_BITS_SAMPLE=16
pcm_audio_in=None
pcm_audio_out=None
REFCLK_FREQ=34406
REFCLK_PRESCALE=1
#Constants
SI4735_CMD_POWER_UP=0x01
SI4735_CMD_GET_REV=0x10
SI4735_CMD_POWER_DOWN=0x11
SI4735_CMD_SET_PROPERTY=0x12
SI4735_CMD_GET_PROPERTY=0x13
SI4735_CMD_FM_TUNE_FREQ=0x20, 0x00
SI4735_CMD_FM_TUNE_STATUS=0x22
SI4735_CMD_GET_INT_STATUS=0x14
#Define Si4735 Output modes
SI4735_OUT_RDS=0x00 # RDS only
SI4735_OUT_ANALOG=0x05
SI4735_OUT_DIGITAL1=0x0B # DCLK, LOUT/DFS, ROUT/DIO
SI4735_OUT_DIGITAL2=0xB0 # DCLK, DFS, DIO
SI4735_OUT_BOTH=(SI4735_OUT_ANALOG | SI4735_OUT_DIGITAL2)
#Statuses
SI4735_STATUS_CTS=0x80
SI4735_STATUS_ERR=0x40
SI4735_STATUS_STCINT=0x01
#Properties
SI4735_PROP_REFCLK_FREQ=0x00, 0x02, 0x01
SI4735_PROP_REFCLK_PRESCALE=0x00, 0x02, 0x02
SI4735_PROP_DIGITAL_OUTPUT_SAMPLE_RATE=0x00, 0x01, 0x04
SI4735_PROP_DIGITAL_OUTPUT_FORMAT=0x00, 0x01, 0x02
SI4735_PROP_RX_VOLUME=0x40, 0x00
#Flags
SI4735_DIGITAL_I2S=0x01, 0x00
SI4735_FLG_INTACK=0x01
#Modes
SI4735_MODE_LW=0
SI4735_MODE_AM=1
SI4735_MODE_SW=2
SI4735_MODE_FM=3
mode=SI4735_MODE_FM
record_stop = threading.Event()
record_thread = None
def byteHigh(self, val):
return val >> 8
def byteLow(self, val):
return val & 0xFF
def sendCommand(self, cmd, *args):
with i2c.I2CMaster() as bus:
if (isinstance(cmd, int)):
bytesToSend=(cmd,) + args
else:
bytesToSend=cmd + args
logging.debug("Command: " + " ".join('0x%02x' % i for i in bytesToSend))
bus.transaction(i2c.writing_bytes(self.I2C_ADDRESS, *bytesToSend))
def getStatus(self):
with i2c.I2CMaster() as bus:
return bus.transaction(i2c.reading(self.I2C_ADDRESS, 1))[0][0]
def ctsWait(self):
status = 0
while not status & self.SI4735_STATUS_CTS:
status = self.getStatus()
logging.debug("Returned status is: {0:#04x}".format(status))
def intWait(self, interruptType):
status = 0
while not status & interruptType:
self.sendCommand(self.SI4735_CMD_GET_INT_STATUS, 0x00)
time.sleep(0.125)
status = self.getStatus()
if (not status & interruptType):
logging.debug('Still waiting. Got status {0:#04x}'.format(status))
def sendWait(self, cmd, *args):
self.sendCommand(cmd, *args)
self.ctsWait()
def setProperty(self, property, *args):
self.sendWait((self.SI4735_CMD_SET_PROPERTY,)+property+args)
def setupCaptureDevice(self):
logging.debug('Setting up capture device')
self.pcm_audio_in = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, self.AUDIO_IN_DEV)
self.pcm_audio_in.setchannels(self.AUDIO_CHANNELS)
self.pcm_audio_in.setrate(self.AUDIO_SAMPLE_RATE)
self.pcm_audio_in.setformat(alsaaudio.PCM_FORMAT_S16_LE)
def setupPlaybackDevice(self):
logging.debug('Setting up playback device')
self.pcm_audio_out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NONBLOCK)
self.pcm_audio_out.setchannels(self.AUDIO_CHANNELS)
self.pcm_audio_out.setrate(self.AUDIO_SAMPLE_RATE)
self.pcm_audio_out.setformat(alsaaudio.PCM_FORMAT_S16_LE)
logging.debug('Done setting up playback device')
def captureAudio(self, arg, stop_event):
self.pcm_audio_in.setperiodsize(160)
while(not self.record_stop.is_set()):
l, data = self.pcm_audio_in.read()
if l:
#logging.debug("Sending output audio...")
self.pcm_audio_out.write(data)
def powerUp(self):
logging.debug('Powering up si473x')
GPIO.output(self.GPIO_RESET, False)
time.sleep(0.01)
GPIO.output(self.GPIO_RESET, True)
time.sleep(0.01)
self.sendWait(self.SI4735_CMD_POWER_UP, 0x00, self.SI4735_OUT_BOTH)
#Configure REFCLK
self.setProperty(self.SI4735_PROP_REFCLK_FREQ,
self.byteHigh(self.REFCLK_FREQ),
self.byteLow(self.REFCLK_FREQ))
self.setProperty(self.SI4735_PROP_REFCLK_PRESCALE,
self.byteHigh(self.REFCLK_PRESCALE),
self.byteLow(self.REFCLK_PRESCALE))
self.record_thread.start()
self.setProperty(self.SI4735_PROP_DIGITAL_OUTPUT_SAMPLE_RATE,
self.byteHigh(self.AUDIO_SAMPLE_RATE),
self.byteLow(self.AUDIO_SAMPLE_RATE))
self.setProperty(self.SI4735_PROP_DIGITAL_OUTPUT_FORMAT, *self.SI4735_DIGITAL_I2S)
def setFrequency(self, freq):
if(self.mode == self.SI4735_MODE_FM):
logging.debug('Setting frequency to {0} ({1:#04x} {2:#04x})'.format(freq, self.byteHigh(freq), self.byteLow(freq)))
self.sendWait(self.SI4735_CMD_FM_TUNE_FREQ, self.byteHigh(freq), self.byteLow(freq), 0x00, 0x00)
logging.debug('Frequency set, just waiting for tuning to complete')
self.intWait(self.SI4735_STATUS_STCINT)
if(self.mode == self.SI4735_MODE_FM):
self.sendCommand(self.SI4735_CMD_FM_TUNE_STATUS, self.SI4735_FLG_INTACK)
def setVolume(self, vol):
logging.debug('Setting volume to {0:#04x}'.format(vol))
self.setProperty(self.SI4735_PROP_RX_VOLUME, vol)
def init(self):
self.setupCaptureDevice()
self.setupPlaybackDevice()
def __init__(self):
self.record_thread = threading.Thread(target=self.captureAudio, args=(1, self.record_stop))
radio = SiPiRadio()
GPIO.setmode(GPIO.BOARD)
GPIO.setup(radio.GPIO_RESET, GPIO.OUT)
radio.init()
radio.powerUp()
radio.setVolume(0x63)
radio.setFrequency(10570)
logging.debug("Execution Complete")
print("q=quit")
cmd = ""
while True:
cmd = input("Command: ")
if(cmd=="q"):
break
radio.record_stop.set()
GPIO.cleanup()
@JonathanThorpe
Copy link
Author

@iz2k, glad you find the code useful - unfortunately, I stopped maintaining it as the Si4735 have become hard to source after Silicon Labs discontinued this particular IC. May I ask what sort of project you're using this for?

Thanks for the hints regarding REFCLK.

@iz2k
Copy link

iz2k commented Mar 4, 2020

I am working on my flip-clock project: https://github.com/iz2k/flip-clock

Amongst others, I have used a USB RTL-SDR dongle to tune FM radio, and I was trying to build a new hat for the Pi including two MAX98357A I2S audio DAC (Left + Right) for the audio output, and connect the output of the SI4735 to the I2S input of the Pi. I am testing this with evaluation boards, and for now I can pipe the tuned radio incoming from I2S input to analog output of the Pi. I am confident I will get it piped to the I2S output, and thus to the 3W audio DACs.

I have noticed that the SI4735 is not easy to find, but I already have the unit in my Eval Kit which should be enough for my device. But just in case, do you happen to know a similar alternative that is not discontinued?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment