dsMP3, my LW/MW/FM/SW radio with MP3 recording/playback [Part 1 – Hardware Design]
Wondering why even high-end multiband radios such as Tecsun H501 or PL990 do not support recording, I decided to build a multiband radio with support for recording as well as MP3 playback, using the dsPIC33EP512MC502 microcontroller, the Si4735 chipset, and the CH376 USB controller, to find out what it takes to build such a radio, and experience first hand the challenges that modern radio designers face, in a world that is full of RF interference flooding many parts of the traditional radio spectrum.
Demo prototype
The entire project took around six months to complete, with the stripboard prototype and firmware development phases taking slightly over two months. The PCB design phase, using KiCad and JLCPCB, took another couple of weeks and a few rounds of shipping from China. Believe it or not, no matter how careful I checked the PCB design prior to submission, there would always be errors which required fixing by cutting a trace or soldering a jumper wire. When the PCB for the final design arrived, it took me another day to solder all the chips and the discrete components (capacitors, resistors, switches) before being able to enjoy the fruits of my hard work. Take note that to ensure that the board can be later fitted into a custom-designed 3D-printed case, the SD card socket and the TFMS5400 infrared receiver (for the remote control) are not soldered directly onto the board but rather connected via jumper headers.
This is another photo of the circuit board with various connectors and cables:
This is a close-up photo of just the circuit board with soldered components:
Design constraints
The entire design, runs on an input supply between 6-12VDC, fits on a 10cm x 10cm PCB, which can be ordered at $2 for 5 pieces (excluding shipping) from JLCPCB. Decoding MP3 is done with the help of the doku library, ported to the dsPIC. Stereo MP3 playback is done via PWM (output at pin 23 and pin 25), passing through a double RC low-pass filter (cutoff at 22kHz) to remove PWM harmonics and improve audio quality. To support 128Kbps stereo MP3 files, the dsPIC will have to be overclocked to around 180MHz, which is not recommended but still works well with several dsPIC in DIP-28 packages that I have tested. In radio mode (except recording), such a high speed is not needed, and the dsPIC can happily run at 16MHz.
To make the design as hobbyist-friendly as possible, I have chosen the DIP28 form factor for the dsPIC33EP512MC502. The Si4735 is SSOP whereas the rest of the chips are SOT or SOIC, which can be soldered with a normal Yotec 936P soldering iron as long as you are careful enough. To successfully solder the Si4735, you will need an SMD soldering station, soldering paste, and a lot of practice. In my case, I bought cheap SSOP ICs from AliExpress and sacrificed one board (from the packs of 5 manufactured by JLCPCB) to practice until I gained enough confidence to solder the actual Si4735 (which is $10 a piece).
This is the PCB layout of the final PCB:
This is the output of KiCad’s 3D Viewer for the PCB. Compared with the final PCB with soldered components, it’s not too far from the truth:
Audio amplifier circuit
A TDA1308 stereo headphone driver, connected in the summing amplifier configuration, is used as a pre-amplifier and also to mix the audio output from the different sources (MP3, radio, line-in, etc) into two single audio channels, for the left and right speakers:
The TDA1308 stereo output can then drive a headphone, connected via a 3.5mm socket directly, or can be fed into two NS8002 amplifiers in order to be able to drive a pair of two powerful 8-ohm stereo speakers:
It should be noted that the voltage level of the audio output for the Si4735 is very low and if fed directly into the NS8002, will result in very soft audio output, even with strong speakers at maximum volume. A pre-amplifier such as the TDA1308 is needed even if you do not intend to use headphones. To ensure that the speaker will turn off when headphones are connected, a 5-pin 3.5mm plug is used together with an NE556 timer configured as a nested non-inverting buffer to generate a signal which turns high when a headphone is connected:
The choice of the amplifier is worth further discussion here. Initially I picked a PAM8403, which generated very good audio quality, but then realized that my radio could only perform well on FM stations and failed to detect many strong AM/SW stations. It was not long before I found out that the cause was the RFI generated by the PAM8403. I tried many things such as shielding the PAM8403 or moving it away from the SI4735 but could only achieve partial improvements. The proper solution is to realize that the PAM8403 is a Class D switching amplifier, generating switching digital noises which badly affects radio reception, and replace it with the NS8002, a class AB amplifier, which does not create as much RFI and allows the radio to perform well in all bands. The TDA1308 that I have chosen is a class AB amplifier, working well together with the NS8002 to form the audio section of the circuit.
Si4735 radio module
The Si4735 datasheet suggests implementing an antenna tuning mechanism (via capacitor C49) as well as a pair of ESD diodes (via the CM1213), to prevent static electricity from accidentally discharging through the antenna and destroying the rest of the circuit. With experiments, I found that the tuning mechanism is only useful in very low-noise environment for improving weak station reception. This is the schematics that I used for the Si4735, operating in I2C mode:
Because the Si4735 has different input pins for AM (referring to LW/MW/SW) and FM bands, a switch has to be implemented in order to connect the external antenna to the correct input pins depending on the selected band. However, I have found that this is only needed for weak stations. After all, antenna does not have to be connected to have some kind of effects, and a strong signal (whether AM or FM) will be received well enough by the Si4735 to produce clear audio, if the antenna is simply brought close to the chip. Of course, if the signal is weak, selecting the correct position of the switch is important for the best possible reception.
Crystal oscillator
Another issue I want to highlight is the accuracy of the 32.768kHz crystal, required by the Si4735 for tuning of radio stations. In a Real Time Clock (RTC) circuit, the accuracy of this crystal will affect the accuracy of the clock as it runs over time. For example, with a 100-ppm tolerance 32.768-kHz crystal, the clock error after 30 days is approximately 4 minutes. To achieve better accuracy, you can purchase crystals with lower ppm (e.g. 5ppm – 20ppm). In a radio tuning circuit such as the Si4735, the accuracy of the crystal is even far more important as it affects the frequency which the Si4735 thinks it is currently at. Early during the development I observed that my radio could only find very few stations (whether AM or FM or SW), and the displayed frequency was nowhere near the actual station frequency (e.g. the Si4735 reported 93MHz for a station at 94MHz). After a lot of debugging, including building my own 32768Hz oscillator circuit and measuring the output on a frequency counter, it turned out that I had been using faulty crystals which oscillates at 33kHz!
This is the schematics of the oscillator that I used, providing 3Vpp output, consuming 70uA current with 5 second powerup time. Build it on a stripboard and as neat as possible if you want it to work – stray capacitances (such as those from a breadboard) will result in the circuit not oscillating:
Low-power LCD display
A good radio should have a good display (to show time and station information, for example), which in turn should have a high enough resolution, back-lit if necessary, without consuming excessive amount of power, and obviously not too expensive. To satisfy all these requirements, I have chosen the GMG12864-06D v2.0 available for $5 for AliExpress, which has a 6cm x 4cm monochrome display at 128×64 resolution that runs on a ST7565 controller, with a backlight that can be separately controlled. The original LCD module has an integrated EEPROM chip onboard (which can be accessed via separate pins) which consumes extra current. Removing this chip (highlighted in red below), and the LCD current consumption is just 100uA with backlight off:
In this photo, the LCD is showing the radio playing an FM station at 94.20MHz, the current time (23:02), as well as temperature (32°C) and humidity (95%), measured by a DHT11:
As can be seen in the above photo, the firmware also supports FM RDS (shown as WARNA942, the station name). However, as FM RDS is best implemented using an interrupt (which allows the Si4735 to tell us when RDS data is available) and we don’t have a spare pin here (unless the CH376 and not the dsPIC is used to read the SD card, see later), I have to implement it using polling (which means reading the RDS buffer periodically to see when RDS data is available). This technique will only work with strong signals, which in turns produce reliable RDS result, and with RDS text which does not change frequently. A station that produces scrolling RDS text, or one with weak signal, will produce missing or repeated characters, using my polling implementation.
Voltage rails
The circuit operates on four different voltage rails, 3V3PIC, 3.3VSD, 5VSPK, and 5VUSB supplied by several LT1763. The 5VSPK rail is for the audio section (TDA1308, NS8002, NE556) whereas the 5VUSB is for, well, the USB thumbdrive. The reason the USB thumbdrive requires a dedicated supply rail is to reduce interference caused by the audio section, especially when playing loud sounds. Without this, when loud MP3 songs are played through the speakers, current consumption could momentarily spike and exceed the regulating capability of the LT1763, which in turn results in momentary voltage drop on the USB thumbdrive and affects the USB bus connectivity. Further, as the LT1763 can only provide around 500mA and some USB thumbdrive could consume as much as 200mA, it is necessary to separate the voltage rails so as not to overload the regulator. Read this if you want to find out more about USB thumbdrive consumption.
The rest of the peripherals will happily work on a single 3V3SD rail provided by a single LT1763. Power supply to the dsPIC is provided by a dedicated 3V3PIC rail. The SHDN pin (active low) of the LT1763 for the 3V3PIC rail will be permanently connected to VCC (since the 3V3PIC rail is always on) whereas the SHDN pin of the regulator for the 3V3SD/5VSPK/5VUSB rail will be connected to pin 15 (RB6) of the dsPIC, allowing the PIC to decide when to turn on or off power to the rest of the circuit.
Reverse polarity protection is also provided by the LT1763, which will just output 0V if connected in reverse, causing no short-circuit or damages. However, take note that if you want reverse polarity protection to really work, the decoupling capacitor on the input side of the regulator has to be non-polarized. If you use an electrolytic capacitor there, it will still short if power is applied in reverse, defeating the purposes of the LT1763.
dsPIC pin configuration
This diagram shows how individual pins in the dsPIC are utilized:
Pin 21 (RB10) of the dsPIC, labelled LCDRS_U1TX_DHT is worth mentioning as it is shared between several different purposes, namely, to read temperature/humidity from the DHT11, to tell the LCD whether command or data is being sent, or to output UART data for debugging purposes. Usually, sharing a single pin between different devices is not recommended but in this case, the objective can be achieved. The specific LCD module that I have picked will not bother about changes in states of the R/S pins without state changes on the clock lines (SCK). Further, although the DHT11 may be confused if we toggle the R/S pins to talk to the LCD, we simply need to reset the DHT11 prior to any attempt to read temperature/humidity and all will be good once again. Finally, using RB10 to communicate with the LCD and the DHT11 might cause garbage to be printed on the debug console, which shouldn’t matter anyway as debug text can still be easily recognized.
To measure battery voltage and detect low battery condition, VCC is connected to a voltage divider before being fed into an analog input channel (AN1) of the dsPIC. On the same I2C bus as the Si4735, I also connect an 24LC256 EEPROM to store system settings. Although I have previously come up with ways to emulate EEPROM on the dsPIC (which doesn’t have EEPROM), eventually I figured that using external EEPROM is still the most reliable method and hence decided to go with this method. This is the diagram of the 24LC256. Take note that C21 and C23 are for capacitance matching on the I2C bus and should be adjusted or removed to suit the actual circuit:
Keypad and remote control
The radio accepts input via an analog keypad as well as a remote control (decoded with the TFMS5400 decoder). I purchased the remote control for $5 from AliExpress – the same remote control is used in many cheap Android TV boxes:
For the keypad, I purchased two pieces from AliExpress, one with the AUTO/MENU/LEFT/RIGHT/SELECT/POWER buttons and the other with INC/DEC/BEFORE/NEXT/SELECT buttons. These keypad buttons allow me to control the radio user interface conveniently:
The digital output of the TFMS5400 is fed into pin 26 of the dsPIC while the output pins of the analog keypad are connected to a resistor ladder before being fed into the dsPIC:
With this design, only a single pin is needed to interface multiple keypad buttons. The downside is that you will have to spend efforts calibrating the range of the ADC count value (for example 500-800) for each key press (e.g. the ON button). Multiple decoupling capacitors will have to be introduced so as to reduce noises on the supply and improve the accuracy of the ADC; otherwise the keypad detection will be inaccurate.
CH376 USB controller
The CH376 USB controller is used to interface with USB thumbdrives. Although the CH376 can also read SD cards, to reduce current consumption, SD cards are still interfaced directly to the dsPIC. If you wish to save some pins, feel free to connect the SD card to the CH376, and update the code. This is the schematics I used for the CH376 – you can read more about it here:
Audio recording
Recording is achieved by feeding the mono output of the TDA1308 (REC_INP) into an analog channel of the dsPIC (pin 2, AN0). A variable potentiometer (RV1) is installed so that the input can be manually adjusted to a suitable level before its value is retrieved from the ADC. Radio audio recording is then done by reading the ADC values into a buffer at a sampling frequency of 8kHz, and then by writing this buffer into a WAV file periodically, e.g. every 4096 samples, depending on available memory. This is a 16-bit 8kHz mono WAV file, recorded from a Chinese shortwave radio station on my radio. The recorded audio is satisfactory and frankly cannot be better given the technology used. At this point, I am satisfied with the result of my 6-month hard work.
Power On Self Test (POST)
A good device should be able to tell its users about its own health, e.g. whether it is working properly or if any component needs servicing. Most computers perform POST upon powering on and my radio should not be any different. At boot-up, it tests several component (EEPROM, SD card, USB, RTC) and informs the user if there are any issues:
As the dsPIC33EP512MC502 has no hardware RTC, RTC is implemented in software with the codes I previous shared here, using the clock from a separate oscillator circuit fed into pin 12 (T1CK). The POST will delay for 2 seconds, and check if the RTC has advanced by 2 seconds, by comparing it with other timer sources. To check if the audio section is working properly, the code records the audio generated from the power-on beep and performed a Goertzel transformation (an implementation of the Fast Fourier Transform) from the recorded sound samples. A POST error will be generated if the transformation results does not reveal the expected frequency of the beep. From the knowledge I gained after watching multiple series of Maday: Air Crash Investigation, the same technique is used on some aircraft to detect cockpit voice recorder issues:
unsigned int goertzel_mag() { // simplified hard-coded calculations based on current constant value, otherwise usage of float functions // will cause const linker section to grow large and allocated outside x-memory, which will cause crash // 8000 samples per second, tiny beep last for 100ms, #define NUM_SAMPLES 800 // 8000 samples per section, each sample is approximate 125ms // reduce value to cater for calculation time // adjust value a bit for highest return magnitude for the sampling frequency // around 104-106, peak at 105 (for current code only) #define SAMPLE_DELAY_US 104 // float coeff = 2.0 * cos(2.0 * 3.14 * TARGET_FREQUENCY / SAMPLING_RATE); // TARGET_FREQUENCY is 2000, SAMPLING_RATE is 8000, so cos() is just 0! #define COEFF 0.0 float s_prev = 0.0; float s_prev2 = 0.0; float s = 0.0; // if numSamples is calculated correctly, this for will last approximately the duration of the beep unsigned int i; for (i=0; i < NUM_SAMPLES; i++) { s = readAnalogANPinSigned(RECORDER_INPUT_AN_PIN) + COEFF * s_prev - s_prev2; // recording input on RA0 s_prev2 = s_prev; s_prev = s; delay_us(SAMPLE_DELAY_US); } return (unsigned int) (2 * sqrt(s_prev2*s_prev2 + s_prev*s_prev - COEFF*s_prev*s_prev2) / ((float)NUM_SAMPLES)); }
Demonstration videos
This is the radio LCD in shortwave mode, showing the station frequency, name and distance (based on a database hard-coded into flash memory):
This is the radio LCD when playing an MP3 songs, showing the file name, format and duration:
This is the radio display in standby mode, consuming around 400uA of current. To achieve this low current consumption, all unused voltage rails (except for 3V3PIC) have been shutdown and the dsPIC set to run on its Low Power RC oscillator (LPRC) at a nominal frequency of 32kHz, allowing the radio to last for many months before the batteries have to be replaced. An alarm clock feature is also implemented, just like other radios.
Obviously, current consumption will be higher when the radio is in operation. For example, in radio mode with no recording, consumption will be around 60-70mA. In MP3 mode using SD card at reasonable volume, average consumption will be around 100mA. In MP3 mode using USB, average consumption will be around 130mA (to cater for the CH376). In recording mode, current consumption will be higher, up to 150mA since both the Si4735 and the SD card or the CH376/USB have to be activated. For greater flexibility, the design also supports recording from a LINE IN source in radio mode, just that the radio volume will have to be muted.
It took me around two weeks to tweak the circuit to achieve satisfactory audio recordings, spending most of my time on two main challenges. First, due to the need to write the audio buffer to SD card (or USB) periodically (which is essentially every second as the dsPIC does not have a lot of memory), there are not a lot of CPU cycles left for other tasks such as handling of the radio UI. This necessitates the need to simplify the codes as much as possible, sometimes at the expenses of error checking, so that the CPU can be freed for other tasks. Second, running at 184MHz, the dsPIC produced a lot of RFI all over the shortwave bands, and once again, my radio could only receive strong stations. I fixed the issue by covering the dsPIC with a shield which I removed from an old RF modulator module. After all these fixes, the audio recording quality was finally good enough.
The following video shows my radio scanning the FM bands as well as playing several MP3 songs. Using 10-bit hardware PWM for audio output, audio quality is good enough in my opinion:
The following video shows the radio scanning the shortwave band. It was captured during daytime, with just a telescopic antenna (not shown in video), and several Chinese radio stations were found:
Although not shown in the above video, my radio also supports MW, LW as well as single side band (SSB) reception. There are no long wave stations where I live; however, an old function generator with its output connected to a long telescopic antenna operating in external AM modulation mode is all that is needed to simulate an LW (or MW, SW) station that can broadcast within a distance of 10-20m. Simulating SSB is more tricky, you will have to set the function generator to continuous wave mode (e..g. 6000kHz) and set your radio to a frequency a distance away from this frequency (e.g. 5800 or 6200kHz). Set the radio to either upper side band (USB) or lower side band (LSB) and with some luck, you should be able to hear a 200Hz tone on your radio. In my design, I used the Si4735-D60, which supports SSB by loading a patch that can be sent via I2C after the chip has been initialized. The Tecsun PL365 and similar radios have a delay of up to 5 seconds when activating/de-activating SSB mode, and I suspect that they are using the same patch.
Circuit schematics
This is the full schematics with complete explanations – you can click on it to view the high resolution version. The entire project is named dsMP3, short for dsPIC + MP3, in the lack of a better word.
All in all, I am very pleased with the final product, and will proceed with designing a 3D case so that the circuit board will fit nicely. In my next post, I will share more on how the firmware was developed, and some of the programming challenges I faced during the process. Meanwhile, if you have any feedback or suggestions, feel free to share in the comment section below.
See also
dsMP3, my LW/MW/FM/SW radio with MP3 recording/playback [Part 2 – Firmware Design]