Tuesday 13 November 2012

Interfacing PIC with Serial DAC MCP4921

In this tutorial, PIC18F4550 is interfaced with dac MCP4921.
The benefit of using a SPI DAC is that we require only 3 microcontroller pins instead of 10-12 pins as in conventional parallel DAC.
In this tutorial we will be using an IC manufactured by Microchip named MCP4921.
MCP4921 is a 12-bit Voltage Output Digital to Analog Converter IC with SPI Interface whereas PIC18F4550 is a 8-bit microcontroller. Therefore we need to convert the 8 bit output of PIC microcontroller into a 12 bit output.
Then why don't we directly use a 8 bit DAC? The reason is that Proteus does not have 8 bit MCP4901 or 10 bit MCP4911 ICs. So we cannot simulate these ICs in Proteus. Hence we use 12 bit DAC MCP4921 and convert the 8 bit PIC microcontroller output into 12 bit output.

PIC18F4550 interfacing with serial DAC MCP4921
Proteus Simulation Snapshot

Now lets understand the hardware connections first 

  1. Connect the clock pins of PIC microcontroller and MCP4921 i.e. SCK pins of both ICs.
  2. Connect the SDO of PIC i.e. RC7 to SDI of DAC. The serial data from PIC to the MCP4921 will pass through this connection.
  3. The LDAC (latch DAC synchronization input) pin is used to transfer the input latch register to the DAC register (output latch, VOUT). When this pin is low, VOUT is updated with input register content. Hence we tie this pin to low (VSS) so that VOUT is updated at the rising edge of the CS pin..
  4.  VREF is the voltage reference input for MCP4921. This pin is  tied to VDD so that the input voltage will range from VSS(ground) to VDD(5V).
  5. Connect a high value register to VOUT of MCP4921 so that the current requirements are low. 

The Program is given below :

#include<p18f4550.h>
#pragma config FOSC = HS              //High Speed Crystal Oscillator
#pragma config WDT=OFF               //Watch Dog Timer disabled
#pragma config LVP=OFF                 //Single-Supply ICSP disabled
#pragma config MCLRE = OFF        //RE3 input pin enabled; MCLR pin disabled
. So now we dont need                                                   
                                                             to give High Logic on this pin to keep the PIC functioning
#define  cs PORTBbits.RB0  
void dac(unsigned int);
void delay(unsigned int time);

void main()
{
 int i;
 TRISB=0;                            // PORTB is configured as an output port
 TRISC=0;                            // PORTC is configured as an output port
 PORTC=0;
 PORTB=0;

 SSPSTAT=0xC0;              //Status Register SSPSTAT=11000000
 SSPCON1=0x20;             //Enables serial port pins & set the SPI clock as clock = FOSC/4
 while(1)
   {
      dac(255);  delay(1000);
      dac(127);  delay(1000);
      dac(63);   delay(1000);
   }
}

void dac(unsigned int data)
{
  unsigned int c ;
  unsigned int lower_bits;
  unsigned int upper_bits; 
  c = ((data+1)*16) -1;                       // here we obtain 12 bit data

  //first obtain the upper 8 bits
  upper_bits = c/256;                          // obtain the upper 4 bits
  upper_bits = (48) | upper_bits;         // append 0011 to the above 4 bits


  //now obtain the lower 8 bits
  lower_bits = 255 & c;                      // ANDing
separates the lower 8 bits
  
  cs=0;
  SSPBUF=upper_bits;                      // sending the upper 8 bits serially    
  while(!SSPSTATbits.BF);                // wait until the upper 8 bits are sent
  SSPBUF=lower_bits;                      // sending the lower 8 bits serially  
  while(!SSPSTATbits.BF);                // wait until the lower 8 bits are sent
  cs=1;
}


void delay(unsigned int time)
{
 unsigned int i,j;
 for(i=0;i<time;i++)
   for(j=0;j<120;j++);
}

Now we will understand how the program works. 

#include<p18f4550.h>
#pragma config FOSC = HS              //High Speed Crystal Oscillator
#pragma config WDT=OFF               //Watch Dog Timer disabled
#pragma config LVP=OFF                 //Single-Supply ICSP disabled
#pragma config MCLRE = OFF        //RE3 input pin enabled; MCLR pin disabled 
#define  cs PORTBbits.RB0  

void dac(unsigned int);
void delay(unsigned int time);

void main()
{
 int i;
 TRISB=0;                   // PORTB is configured as an output port
 TRISC=0;                   // PORTC is configured as an output port
 PORTC=0;
 PORTB=0;

 First we set all the general configurations and set PORTB & PORTC of PIC microcontroller as output ports.

 SSPSTAT=0xC0;         // configure SPI settings
 SSPCON1=0x20;        //Enables SPI mode & set the SPI clock as clock = FOSC/4

Then we enable the SPI mode of PIC and set the SPI clock as Fosc/4. We do this by setting the following values:
    Status Register SSPSTAT = 11000000  i.e. SSPSTAT = 0xC0;
    MSSP CONTROL REGISTER 1  SSPCON1 = 00100000  i.e.SSPCON1 = 0x20;


while(1)
   {
      dac(255);  delay(1000);
      dac(127);  delay(1000);
      dac(63);   delay(1000);
   }
}

Then we can send any 8 bit value (0 to 255) to the dac function which will result in the voltage output ranging from 0 to 5 volt.
Here we have sent 255 which will result in 5V; 127 will give 2.5V and 63 will result in 1.25V.

The most important part is the 'dac' function. As PIC is an 8 bit microcontroller while the MCP4921 accepts 12 bit data, we need to convert the 8 bit into 12 bit
.

void dac(unsigned int data)
{
  unsigned int c ;
  unsigned int lower_bits;
  unsigned int upper_bits; 
  c=((data+1)*16) -1;                          // here we obtain 12 bit data

We do this by multiplying the data by 16. Now we get a 12 bit data.

Another problem is that we need to send 8 bits of data at a time; first the upper 8 bits and then the lower 8 bits. So in total we need to send 16 bits; but we have only 12 bits. Therefore we will append the MSB of our 12 bit data by 0011. Therefore our new 16 bit data will be 0011xxxxxxxxxxxx. Refer to the datasheet of MCP4921 to know why specifically '0011'.

Now we need to seperate the upper & lower bits from our 12 bit data which is held in variable 'c'.

  upper_bits = c / 256;                          // obtain the upper 4 bits
  upper_bits = (48) | upper_bits;          // append 0011 to the above 4 bits

First we obtain the upper 4 bits by dividing the data by 256 and append '0011' to it by ORing it with 48. By this way we get our required upper 8 bits held in 'upper_bits' variable.

Then we seperate the lower 8 bits from the 12 bit data held in 'c' by ANDing it with 255.

 lower_bits = 255 & c;
Hence we get our lower 8 bits data and we store it in variable named 'lower_bits'.

Then we enable the DAC
MCP4921 by sending logic low on pin 'cs'. After doing so we send our 16 bits of data held in 'upper_bits' & 'lower_bits' variable using SSPBUF buffer. Placing the data in this buffer, sends the data serially through SDO pin of PIC microcontroller.
  cs=0;
  SSPBUF=upper_bits;                      // sending the upper 8 bits serially    
  while(!SSPSTATbits.BF);                 // wait until the upper 8 bits are sent
  SSPBUF=lower_bits;                       // sending the lower 8 bits serially  
  while(!SSPSTATbits.BF);                 // wait until the lower 8 bits are sent
  cs=1;
}

After sending values, we need to set 'cs' pin of
MCP4921 to Logic High so as to disable the DAC from accepting any new value. The DAC will retain the previous value on the output pin until we send a new value on the input pin of DAC MCP4921.
So, call the function 'dac' and pass any 8 bit value as arguments to get the corresponding analog voltage output.


Download Proteus, MPLAB & Source code Files of PIC and DAC MCP4921 interfacing Here