In my never ending (it seems) quest to make my Pi talk to all of the devices I had talking to my Arduino, I have finally gotten around to one of the funner, yet more frustrating devices. Funner because it was challenging, frustrating because it was challenging. There was a lack of documentation on the device, the detailed programming manual was in Chinese, the register specs were limited, and it just didn't seem to work like I expected it to.
This device was the Parallax FM Radio Receiver Module (#27984) , from www.parallax.com. According to the documentation:
"The FM Radio Receiver Module uses an RDA5807SS FM stereo radio tuner chip, which provides an easy way for your microcontroller to receive local FM radio statons. Using the onboard antenna and headphone jack, you can easily create your own portable radio! "
I can't disagree with any of that, except for maybe the easy part, but it should be easy now that I have prepared this blog post!
I've seen a few other posts on this chip/module, but they have mostly taken from the perspective of the TEA5767, which is a mode that the RDS5807 emulates, but I wanted to control it natively.
A few prerequisites are required, you need to enable the I2C on your pi. You can do this by following the first 1/2 of a related post here: Enabling RTC on the Raspberry Pi 2
You can stop when you get to the part about i2cdetect, and return here.
Speaking of i2cdetect, when this device is connected properly, and I type
sudo i2cdetect -y 1
I find 3 i2c devices
at addresses 0x10, 0x11, and 0x60. This is part of what makes this device strange, and where the documentation starts to get a little fuzzy.
It seems like accessing the device at address 0x60 is how you perform the TEA5767 compatible commands, so this is the last I will mention it, I didn't find that in documentation, and I found no mention in the docs regarding TEA5767, it just seems to be data I've found here and there.
Address 0x10 seems to be an address where you write to the I2C device without specifying a register address, the device starts to write the bytes to the 16 bit registers starting at register 0x02, high byte first, then it increments the register counter internally and writes to the next register, and it does this until you have finished writing. This turned out to be a little challenging, but I was able to get it done. I'll explain it when I get to the code.
Address 0x11 purports to behave like a regular I2C address in that it expects a register number to be included in the command, I was not able to successfully write to the device in this manner, but I was able to successfully read from the device registers, so that's something.
OK. On to the code, this is just a simple driver with a rudimentary interface, I'm not a big bells and whistles guy, I just wanted to write the methods to access the device and exercise it. I didn't take advantage of the interrupt capabilities of the module, they are there, it should be a trivial exercise to take advantage of it.
Another prerequisite. You need wiringPi. The best place to get that is here Raspberry Pi | Wiring | Download & Install | Wiring Pi . That should get you all set up, then you can return here.
This code was pretty much a port of an arduino sketch I had written. I renamed the setup() function to main(), and added a call to loop(). Replaced Serial.println() functions with printf() functions, and had to rewrite the individual functions that talked to the device registers. But other than that, it was much the same. I had isolated the register functions so it was trivial to replace those as I only had to change the code in 2 places, the read and the write.
unsigned int readRegister(int regAddr) { return be16toh(wiringPiI2CReadReg16(statusFd,regAddr)); }
The readRegister() method was pretty simple. I just called the wiringPiFunction wiringPiI2CReadReg16(), passing in the fd I got from opening the I2C device at address 0x11, along with the register number. I noticed that the results returned were reversed from what I expected. That is the low byte from the read was being stored in the upper byte and the high byte was being returned in the lower byte, so when I expected something like 0xA55A, I was instead getting 0x5AA5, so I used a system call be16toh from endian.h, to convert the data from big endian to host format. This accomplished what I needed.
void writeConfigRegisters() { int index,ii=0; unsigned char tempRegisters[CONFIGREGLEN * 2]; for(index = 0;index < CONFIGREGLEN;index++) { tempRegisters[ii++] = (configRegisters[index] >> 8) & 0xff; tempRegisters[ii++] = configRegisters[index] & 0xff; } write(deviceFd,tempRegisters,CONFIGREGLEN * 2); }
I handled writing the config registers as a block. This seemed to be how the device liked it. I maintain a copy of the state of the registers in an integer array named configRegisters, then I just write that out to the device, I first convert the integer array to an array of characters, then just using the write function, I write out the data all at once to the fd I got from opening the I2C device at address 0x10.
I mentioned the fd I got from the devices a couple of times. Here is an example of how I opened them using the wiringPi libraries.
#define CONFIGADDR 0x10 #define STATUSADDR 0x11 #define CONFIGREGLEN 5 int deviceFd; int statusFd; int main() { deviceFd = openDevice(CONFIGADDR); statusFd = openDevice(STATUSADDR); ... ... ... } int openDevice(int i2cAddress) { int fd; if((fd = wiringPiI2CSetup(i2cAddress)) < 0) { printf("Error opening device at I2C Address %x\n",i2cAddress); exit(fd); } return fd; }
Once they were opened I was able to use the file descriptors all over the program.
After that is was just a matter of bit twiddling to write to the registers and get the device in the state I wanted. My initial config registers looked like the following:
unsigned int configRegisters[] ={ 0xc001,0x0000,0x0400,0x86d3,0x4000};
I'll leave it up to the reader to decode, this was the document I used as a reference https://www.parallax.com/sites/default/files/downloads/27984-FM-Radio-Receiver-v1.0.pdf
the elements of the array correspond to registers 0x02-0x05 on the device. So as an example 0xc001 sets register 2 as follows
High Impedance - Normal Operation
Mute - Normal Operation
Mono - Stereo Mode
Bass Boost - Not Enabled
Power up - Enabled
an example of how I used the config registers is as follows
void powerOn() { configRegisters[1] |= 0x10; configRegisters[0] |= 0x1; writeConfigRegisters(); configRegisters[1] &= 0xffef; }
and
void tuneToChannel(int frequency) { frequency -= 870; configRegisters[1] |= 0x10; configRegisters[1] = (configRegisters[1] & 0x3f) | (frequency << 6); writeConfigRegisters(); configRegisters[1] &= 0xffef; checkTuneComplete(); }
so I would change my local copy of the config registers, write them out to the device, and restore any of the bits I needed.
I have attached the code necessary so if you have this device and would like to learn more, feel free to download it and compile and run it.
To extract use:
tar xvzf radio.tgz
that should give you 2 files
radio.c and
radio.h
the compile command line is
gcc -Wall -o radio radio.c -lwiringPi
then run it with
./radio (if you have an issue, use sudo ./radio)
you can look in the loop() function for the commands, a few are
+ - Seek Up
- - Seek Down
r - reset tuning to inital station (103.7)
x - power off
o - power on
v - volume up
c - volume down
d - display register values
follow any of the commands with the enter key.
just look in the big ugly switch statement for the rest
There does seem to be a bit of a bug in the channel display after a seek, but if you hit d to dump the registers, it should show the proper tuning.
Hope you enjoy.
Top Comments