-
-
Save JonathanThorpe/f23480c781ca62a28647 to your computer and use it in GitHub Desktop.
#!/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() |
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?
@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.