-
-
Save punx77/56a08047b075d64c3f24da0fe4d14cf9 to your computer and use it in GitHub Desktop.
Si473x i2c/i2s radio for Rasperrby Pi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 <[email protected]> | |
#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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment