Friday, September 15, 2017

Wave player using PIC18F4550 microcontroller


Making an audio player (.wav files) using PIC microcontroller is not complicated especially when the MCU has a PWM module. This topic shows how to build an audio player using PIC18F4550 microcontroller where the the file is stored in an SD card with FAT16 or FAT32 file system.
In this project I used a wave file with sample rate of 16000 Hz and 2 channels (stereo). The file I used originally it was an MP3 file and I converted it to 8-bit wav format using an audio converter named audacity, it is a free and open source audio software (site: http://www.audacityteam.org/).
Hardware Required:
  • PIC18F4550 microcontroller
  • SD card
  • ASM1117 3.3 voltage regulator
  • Audio amplifier (ex: PC speaker, LM386 ......)
  • Speaker
  • 8 MHz crystal oscillator
  • 2 x 22pF ceramic capacitors
  • 3 x 3.3K ohm resistor
  • 3 x 2.2K ohm resistor
  • 10K ohm resistor
  • 1K ohm resistor
  • 2 x 10uF polarized capacitor
  • 100nF ceramic capacitor
  • 5V Power source
  • Breadboard
  • Jumper wires

The Circuit:
Wave player using PIC18F4550 microcontroller circuit

The AMS1117 3.3V voltage regulator is used to supply the SD card with 3.3V. Also 3 voltage dividers are used to step down the 5V which comes from the microcontroller (come from RD2, RC7 and RB1) to about 3V which is sufficient for the SD card. Each voltage divider consists of 2K2 and 3K3 resistors. Pin RB0 of the microcontroller is connected directly to the SD card MISO pin with a pull-up resistor of 10K ohm.
Hardware SPI module is used by the microcontroller to read data from the SD card, the SPI pins of the PIC18F4550 MCU are:
  • SD0 (RC7): connected to pin MOSI of the SD card
  • SCK (RB1): connected to pin SCK of the SD card
  • SDI (RB0): connected to pin MISO of the SD card
There is an other pin which is CS (Chip Select or slave select) can be connected to any digital output pin (defined in the code), this pin is connected to SS pin of the SD cards.
The audio is generated using PWM technique, basically the PIC18F4550 has two PWM modules and their output are RC2 and RC1 for PWM1 and PWM2 respectively. An audio amplifier is needed to amplify the audio. In my circuit I used PC speaker as an amplifier. If the audio is stereo (2 channels) connect the same amplification circuit and use PWM2 output pin (RC1).
In this project PIC18F4550 MCU runs with 8 MHz crystal oscillator and MCLR pin function is disabled.
The C code:
The C code below was tested with CCS C compiler versions 5.051 and 5.070.
The microcontroller runs at 48MHz (8MHz + PLL).
In this project I used the FAT library (FAT16 and FAT32), its source file can be found in the the following topic:
SD card FAT library for CCS C compiler
I tested this project with FAT32 8 GB and FAT16 2 GB micro-SD card.
The name of the wave audio file which I used was audio (audio.wav with the extension), its sample rate is 16000 Hz with 2 channels (stereo).
First of all I initialized the SD card using the function: sdcard_init(); this function return 0 if the initialization was OK and non-zero if there was an error. After the initialization of the SD card I initialized the FAT file system using the function fat_init(); and then I opened the wave audio file with the pre-selected name audio.wav, all the three previous function returns 0 if OK and no-zero if error.
If the initialization of the SD card, the FAT system and opening of the file were OK that means the variable which named ok = 0 and playing the wave file starts using the function play(); .
The wave file header size is 44 bytes which I skipped using the variable file_pointer. The variable file_pointer belongs to the FAT library, this variable can be between 0 and the opened file size, for example if a file of size 500 byte is opened the variable file_pointer starts from 0 and ends to 500, it is incremented with the function fat_read_file according to the selected size. Generally it can be neglected in this project and the line below can be removed from the code:
file_pointer += 44;
To detect if the wave file is mono (1 channel) or stereo (2 channels), I read the byte 22 of the wave file using the function :
sdcard_read_byte(address_pointer + 22, &channel_count);
where address_pointer also belongs to the FAT library, this variable allows me to know the starting address of the wave audio file. If the wave file is mono ==> channel_count =1 and if it is stereo ==> channel_count = 2.
I set the data buffer to 32 so each time the microcontroller reads 32 bytes from the SD card. The data buffer can be less or higher than 32.
The function fat_read_data(32, data) keeps reading file data from the SD card until it returns 1 which means end of file is reached.


The wave file must be 8-bit and for that I configured the PWM outputs to give the maximum frequency with 8-bit resolution, for that I configured Timer2 module as shown below:
setup_timer_2(T2_DIV_BY_1, 63, 1);
The resolution of the PWM signal can be calculated using the function:
PWM Resolution = Log[(PR2 + 1)*4]/Log(2) = Log[(63 + 1)*4]/Log(2) = 8
The PWM frequency should be as higher as possible and with the previous configuration I got a PWM frequency of 187.5 KHz. It can be calculated with the function below:
PWM_Frequency = Fosc/[(PR2 + 1)*4*TMR2_Prescaler] = 48*10^6/[(63 + 1)*4*1] = 187.5 KHz.
If channel_count = 2 the 2nd PWM duty cycle also will be updated and the sound will be generated from PWM1 (RC2) and PWM2 (RC1) outputs (left and right).
Now how did I used Timer1 module and the wave file sample rate (16000 Hz):
the PWM duty cycles have to be updated every 62.5 us ( = 1/16000Hz), for that I used Timer1 to make the MCU waits for 62.5 us. In this example I didn't use Timer1 interrupt.
I configured Timer1 module to increment on every MCU cycle (about 83 ns) and to compute Timer1 value (values between 2 updates) I used the function:
Fosc/[sample rate * 4) = 48 * 10^6/(16000 * 4) = 750
where sample rate = 16000 and Fosc = 48 * 10^6 .
In this example I used the value 650 instead of 750 because I got a slow audio streaming (i.e: some instructions are spent on loops).
The complete C code is the one below.
// Wave player using PIC18F4550 microcontroller and SD card.
// http://ccspicc.blogspot.com/
// electronnote@gmail.com

// SD Card module connections
#define   SDCARD_SPI_HW
#define   SDCARD_PIN_SELECT  PIN_D2
// End SD card module connections

#include <18F4550.h>
#fuses NOMCLR HSPLL PLL2 CPUDIV1
#use delay(clock = 48MHz)
#use fast_io(D)
#include <FAT_Lib.c>

const int8 *wav = "audio.wav";
int1 ok = 0;
int8 i, j, data[32], channel_count;

void play(){
  file_pointer += 44;                        // Skip wave file header (44 bytes)
  sdcard_read_byte(address_pointer + 22, &channel_count);       // Read number of channels
  while(fat_read_data(32, data) == 0){
    for(i = 0; i < 32; i++){
      set_timer1(0);
      j = data[i];
      set_pwm1_duty((int16)j);                   // Update PWM1 duty cycle
      if(channel_count == 2){                    // If 2-channel wave file (stereo)
        i++;                                     // increment i
        j = data[i];
        set_pwm2_duty((int16)j);                 // Update PWM2 duty cycle
      }
      while(get_timer1() < 650);                 // Wait some time (about 62.5us) to update the duty cycles
    }
  }
}
void main(){
  delay_ms(2000);
  setup_ccp1(CCP_PWM);                           // Configure CCP1 as a PWM
  setup_ccp2(CCP_PWM);                           // Configure CCP2 as a PWM
  set_pwm1_duty(0);                              // set PWM1 duty cycle to 0
  set_pwm2_duty(0);                              // set PWM2 duty cycle to 0
  setup_timer_2(T2_DIV_BY_1, 63, 1);             // Set PWM frequency to maximum with 8-bit resolution
  setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 );
  ok |= sdcard_init();                           // Initialize the SD card module
  ok |= fat_init();                              // Initialize FAT library
  ok |= fat_open_file(wav);                      // Open the wave file
  if(ok == 0){
    play();
  }
  set_pwm1_duty(0);                              // set PWM1 duty cycle to 0
  set_pwm2_duty(0);                              // set PWM2 duty cycle to 0
}   // End
Wave player using PIC18F4550 microcontroller video: