The Pip-Boy FM FM Radio upgrade module was an optional addon by by The Wand Company to their Pip-Boy 2000 Mk VI. I bought mine quite a few years ago now for £30 GBP but I’ve since seen them go for almost 10 times that on eBay which has kind of put me off hacking on mine given the scarcity of them.
That said, I’ve always been curious about the four exposed pads in a hole on one end—could I power it from them? Could I use the amplifier inside to play my own audio? Recently, I spoke with Richard at The Wand Company about the exposed pads on the FM Radio upgrade. He shared a picture showing how the modules are positioned for production testing, where I assume pogo pins make contact with the pads for automation of testing. This reignited my interest in hacking the module, so I took the bold step of cracking it open to uncover its secrets.
Hardware
I’ll be honest, I was really surprised by what I found inside, I thought this module would have been a relatively simple FM tuner with nothing too complicated going on inside, what I was not expecting to find was an Arm Cortex based microcontroller.
Integrated Circuits
The whole board is really well labelled but with no schematics we’ll need to reverse engineer a little bit to understand how this thing is put together. I started by writing down each IC reference and then using a combination of educated guesswork and some Google-fu to work out which parts were which, I’ve laid out the results of my guesswork below:
Ref | Part | Notes |
---|---|---|
IC1 | LD398 (Maybe) | 5-pin chip right near where the battery terminals are and adjacent to some capacitors and diodes which lead me to believe this was likely some sort of voltage regulator, based on what I could read on the IC and some best judgment I took a stab that it was a LD3985M33R or something adjacent. |
IC2 | STM32 F030F4 | This was probably the easiest one to work out, it’s an STM32 microcontroller, it took a little bit of datasheet digging to work out the exact model number but not too bad. |
IC3 | AT25SF161 | Flash memory chip adjacent to the MCU. |
IC4 | TS4890 | Audio amplifier, this particular IC appears to be made by ISSI and from a table I found I believe to be actually an equivalent to an TS4890 IDT or something along those lines, that seemed to be the most logical fit. |
IC5 | DRV5053 | Hall effect sensor, DRV5053. This one stumped me for a while, it’s 3 pin and adjacent to the battery terminals so I had assumed it was part of the power circuit and I could not track down the 4 letter code on the IC. Eventually through a bit of luck I stumbled on the hall effect sensor and then it all made sense, it’s also adjact to where the “tuning” knob is that has a magnet on the shaft. |
IC6 | Si4705-D60 | FM Radio. Another one that took a little bit of tracking down due to the actual labelling on the IC, but by process of elimination I knew this was likely the FM radio, seeing that it was a QFN type package and taking some educated guesses at how it might be connected up etc. I eventually found the datasheets which confirmed the top markings. |
I2C Bus Exploration
So the one big initial question I had around this board was, what are those exposed pads, and diving inside the case I pretty quickly traced that these are wired back around to the main board to the pins marked up as SDA, SCL and V+, so these pads are actually I2C, not UART as I had previously assumed. On the other side of the main board, adjacent to the I2C hookup wires, there are pads labeled GND, RST, DIO, and CLK. Could these potentially be SPI, with RST likely being the usual STM32 reset pin. I’ll need to probe CLK and DIO with an oscilloscope to determine their purpose. They might be Serial Wire Debug (SWD), though I would have expected DIO to be labeled differently if that were the case—perhaps something more like SWDIO.
Anyway, now that I know the mystery pads are I2C, I soldered some short leads to a JST connector and plugged it up to a Raspberry Pi do some investigation. What is good to know though is that I can power the module this way, I’ve removed the 3x AAA batteries and now just power the module with 3.3V over V+. So anyway, what’s on that I2C bus?
Doing an i2cdetect
I can see that there are two addresses appearing on the bus, there’s an 0x16
and an 0x63
.
$ i2cdetect
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- 16 -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- 63 -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
We’ll have to do some best guesses here again, my guesswork and some initial i2cdump
’s suggests that 0x63
could be the FM radio, and 0x16
some kind of memory. The flash memory chip we identified earlier is an SPI connection according to the datasheet, so perhaps is not that, but it could just be something that’s being exposed by the STM32 instead. The FM radio datasheet suggests that the control interface to this IC is either 2-wire or 3-wire, I’m making the assumption that 2-wire is synonymous with I2C in this context and that’s what’s at address 0x63
but how can we find out for sure?
Who lives at address 0x63?
Taking another approach to work out what’s happening on the I2C bus I hooked up my scope and performed a few actions on the device to see what would trigger events. The first event was to simply boot the device and capture any activity. The below shows a snippet of that boot sequence:
But what does it mean? We can zoom a bit and start stepping through and attempting to decode some of the data.
Initially we can see something write to 0x63
, the write contains the data 01 90 05
followed by a series of reads all containing 00 00 00 00 00 00 00 00
. Then 60ms or so later there’s another block of writes, each of the writes are listed below:
W 12 00 00 01 01 C1
W 12 00 02 01 80 00
W 12 00 02 02 00 01
W 12 00 11 07 00 00
W 12 00 11 00 00 02
W 12 00 18 00 00 7F
W 12 00 40 00 00 32
W 12 00 11 08 00 32
W 12 00 12 01 00 0C
W 12 00 12 02 00 00
W 12 00 14 01 2A 30
W 12 00 14 00 22 2E
W 12 00 14 02 00 0A
W 12 00 14 03 00 03
W 12 00 14 04 00 0A
W 12 00 40 00 00 11
If this is the radio, the datasheet lists a command under 0x12
labeled SET_PROPERTY
. Following that, there’s a byte always set to 0x00
, and the next two bytes could represent the property being set. If that’s the case, it suggests the boot sequence configures a series of properties on the FM radio, which makes perfect sense.
Now, digging more into the datasheet it’s clear this is definitely the radio, the command structure in the datasheet is listed as:
CMD 0x12 SET_PROPERTY
ARG1 0x00
ARG2 (PROP) 0x??
ARG2 (PROP) 0x??
ARG3 (PROPD) 0x??
ARG5 (PROPD) 0x??
Status -->0x80 Reply Status. Clear-to-send high
This perfectly matches up with the data we see on the scope of 6 bytes being written followed by 16 bytes being read where 0x80
being read indicates the clear-to-send (CTS) reply status. Now we’re relatively confident in what we’re looking at lets go over the captured data again and work out what is being sent on boot.
Boot Sequence
Data | Property | Value | Notes |
---|---|---|---|
00-01 01-C1 | GPO_IEN Enables interrupt sources. | 0x01C1 | |
02-01 80-00 | REFCLK_FREQ Sets frequency of the reference clock. | 32.768 kHz | Default value. |
02-02 00-01 | REFCLK_PRESCALE Sets the prescaler value for RCLK input. | 0x01 | Default value. |
11-07 00-00 | FM_ANTENNA_INPUT Selects the antenna type and the pin. | 0x0000 | Default value. |
11-00 00-02 | FM_DEEMPHASIS Sets deemphasis time constant. | 75 μs | Default value. |
18-00 00-7F | FM_BLEND_RSSI_STEREO_THRESHOLD Sets RSSI threshold for stereo blend. | 127 dBμV | According to the datasheet, setting this value to 127 forces mono, which makes sense in this application. |
40-00 00-32 | RX_VOLUME Sets the output volume | 50 | Not this unit is, max value is 63 though. |
11-08 00-32 | FM_MAX_TUNE_ERROR | 50 kHz | |
12-01 00-0C | FM_RSQ_SNR_HI_THRESHOLD | 12 dB | |
12-02 00-00 | FM_RSQ_SNR_LO_THRESHOLD | 0 dB | |
14-01 2A-30 | FM_SEEK_BAND_TOP Sets the top of the FM band for seek. | 108 MHz | |
14-00 22-2E | FM_SEEK_BAND_BOTTOM Sets the bottom of the FM band for seek. | 87.5 MHz | |
14-02 00-0A | FM_SEEK_FREQ_SPACING Selects frequency spacing for FM seek. | 100 kHz | Default value. |
14-03 00-03 | FM_SEEK_TUNE_SNR_THRESHOLD Sets the SNR threshold for a valid FM Seek/Tune. | 3 dB | Default value. |
14-04 00-0A | FM_SEEK_TUNE_RSSI_TRESHOLD Sets the RSSI threshold for a valid FM Seek/Tune. | 10 dBμV | Default value is 20 dBμV. I assume the lower threshold here is set due to the smaller antenna. |
40-00 00-11 | RX_VOLUME Sets the output volume. | 17 | Same as before, previously was set to 50 now set to 17. |
Tuning
I’m experimenting more with the device’s interactions. When a button is pressed to scan forwards or backwards, the radio tunes through FM channels. Monitoring the I2C bus during this process, I observe a write of 20 04
, followed by periodic 14
writes every 50ms for approximately 3 seconds.
Scanning again over the datasheet we can see that the command 0x20
is FM_SEEK_START
and the 0x04
sets the ARG WRAP
which says it determines whether the seek should wrap or halt when it hits the band limit.
The subsequent calls to 0x14
are GET_INT_STATUS
to which the docs state “…this command should be periodically called to monitor the STATUS
byte”. In one example I got 0x80
which translates to a status of CTS, no ERR and nothing on the interrupts.
I also managed to catch a write to 22 01
which is an FM_TUNE_STATUS
which queries that status of a previous FM_SEEK_START
call, the arg of 0x01
here is an INTACK
to that “if set, clears the seek/tune complete interrupt status indicator.” After the usual 0x80
CTS there’s 8 bytes of data 80 01 22 9C 14 03 00 01
, for fun let’s see what that means:
Byte | Meaning |
---|---|
0x80 | Standard status part, CTS, no errors. |
0x01 | The only set bit here is position 0 which means “Valid Channel”, it’s telling us that the station seek found something! |
0x22 0x9C | The next two bytes are the frequency that was tuned in 10kHz, in this case 88.6 MHz (a quick Google suggests this is BBC Radio 2 in the UK.) |
0x14 | The Received Signal Strength Indicator. 20 dBμV in this case, a pretty low strength, which is expected for this tiny antenna. |
0x03 | The signal to noise ratio. 3 dB here, also very low. |
0x00 | This is apparently the “multipath metric”, 0 in this case. |
0x01 | This byte contains the current antenna tuning capacitor value. |
So What?
You might wonder if all this exploration was pointless—what’s the purpose, and what’s the use? Well, we’ve now confirmed the exact FM radio chip in use and established that it’s controlled over I2C by the STM32. But here’s the big question: can I also control it?
The simplest test I could think of was this: can I turn it off? To try, I tuned the radio, turned up the volume so I could hear it clearly, and then used the Raspberry Pi connected to the I2C bus to send the command i2cset 0x63 0x11
. Sure enough, it audibly clicked off!
So, what else can we query? There’s a command to retrieve the part number, chip revision, firmware version, patch revision, and component revision numbers. That could be extremely useful for pinpointing exactly what we’re dealing with:
$ i2cget 0x63 0x10 i 9
0x80 0x05 0x36 0x30 0x00 0x00 0x37 0x30 0x44
Interpreting the data from the datasheet, the last two digits of the part number are 05
, which confirms our earlier assumption that this is an Si4705—neat! The firmware version is reported as v54.48.000, with the component revision listed as v55.48 and the chip revision as 68.
I also tested sending the command i2cset 0x63 0x21 0x04
, which successfully triggered the radio to seek the next station, mimicking the functionality of the side buttons. After seeking, we can replicate the scope output we observed earlier and use the FM_TUNE_STATUS
command to determine which station it tuned to:
$ i2cget 0x63 0x22 i 9
0x81 0x01 0x23 0x78 0x13 0x04 0x00 0x01 0x01
In this case, 90.8 MHz (according to a Google this could be BBC Radio 3 where I’m located), with a pretty poor signal, as expected again.
So who is device 0x16?
I will come back to that in a part 2, as I think this has already gone on quite long enough, thank you for reading if you got this far.