Tuto. 2 - SPI DAC

PIC tutorial 2 - SPI DAC

Print
Category: PIC Tutorials
Published Date Written by Francois

Tutorial 1a

Fig. 1 Board 4 with Dual 8 bits DAC

Description:

For the second tutorial, we will cover basic SPI communication, and how to connect SPI devices to a PIC board.
It ouputs a analog voltage on a digital to analog converter, using the Dual 8 bits DAC, which has an SPI interface.

The SPI interface across all tutorials is the same, and connects to boards with 8 pins: power supply, Master In Slave Out, Master Out Slave In, Clock and 3 Chip Select lines. The 3 CS lines can be used to connect up to 3 SPI devices on the bus, or allow a device to use external interrupts.

The tutorial will update the output voltage of both DAC on the board at every interrupt by timer 0.

You can download the files for this tutorial in the zip file pic_tutorial2.zip.

The boards can be powered by one of the PSU boards (powered from USB port), or between 2.5 to 5V.

Requirements:

Processor boardAny board (board 1 in example)

 Ideally with hardware SPI interface

Extension(s)Dual 8 bits DAC

Power supplyPSU 1, 2 or 3. Depends on board used.

LanguageCCS C

ProgrammerPicKIT 2


Program:

#define board_id 4

#define CS_DAC CS_SPI0 // Chip Select line used/configured on the DAC board

#include "../lib/Boards/mainboard.c"
#include "../lib/Analog/MAX549.c" // load MAX549 library

#priority RTCC

BYTE DAC1=0,DAC2=0; // Values for DAC A and DAC B

#int_rtcc // timer 0 (or RTCC) interrupt
void Timer0Interrupt ( void ) {
  DAC1++;
  DAC2--;
}

void main() {
  boot_up(); // Initialise board
  Init_SPI(); // Initialise SPI interface
  setup_timer_0 ( RTCC_INTERNAL|RTCC_DIV_256|RTCC_8_BIT );
  enable_interrupts ( INT_TIMER0 ); // enable Timer0 interrupts
  enable_interrupts ( GLOBAL ); // enable all interrupts
  while (TRUE) { // main loop
    DAC_write (0, DAC1); // update dac 1 with DAC1 byte
    DAC_write (1, DAC2); // and dac 2 with DAC2 byte
  }
}
#define board_id 4 // Variable board_id needs to be defined with the processor board number used for the tutorial

#include "lib\mainboard.c"
#include "lib\spi_lib.c"
#include "lib\MAX549_lib.c"

#priority RTCC

char DAC1=0,DAC2=0; // Values for DAC 1 and DAC 2

#int_rtcc
void Timer0Interrupt ( void ) {
  DAC1++;
  DAC2--;
}

void main() {
  boot_up();
  Init_SPI(); // Init SPI interface
  setup_timer_0 ( RTCC_INTERNAL|RTCC_DIV_256|RTCC_8_BIT );
  enable_interrupts ( INT_RTCC ); // enable Timer0 interrupt
  enable_interrupts ( GLOBAL ); // enable all interrupts
  while (TRUE) {
    DAC_write (0, DAC1);
    DAC_write (1, DAC2);
  }
}

As usual, the program defined the processor board used, and loads library files.
The MAX549 library defines functions to communicate and control the SPI DAC chip.

Timer0Interrupt runs for every interrupt generated by timer 0. It simply update the values to output on each DAC.

The main program initialise the board with the function boot_up, initialise the SPI interface, setup timer 0 and interrupts, and enter a loop that update each DAC of the chip.
Timer 0 is setup with a predivider set to 8. On the PIC 18F family, timer 0 is an 16 bits counter (can be setup as 8 bits), while it is an 8 bits counter on the 16F processors.

Timer 0 is incremented by the internal clock (crystal clock /4). Every time timer 0 overflows, it generates an interrupts, which updates the variables to send to the DAC.
The fornula to determines the time between interrupts is: T = (4*PRESCALER*2^n)/CRYSTAL_FREQ. n is the resolution of timer 0 (8 or 16 bits)
On 18F processor, the default resolution is 16 bits. To set it to 8 bits, you need to specify it when setting up timer 0 with RTCC_8_BIT.

In 8 bits mode, 256 prescaler and 20MHz clock, the values are updated every  13ms. The DAC values will cycle every 3.3s (256 x 13ms)

The board used for this tutorial is board 4, with a PIC 18F2320 processor. The libraries for each main/processor board are supplied in the zip file, with the source code.

The MAX549 library loads the various functions to control the chip.

#ifndef _MAX549_LIB // make sure the library hasn't been already loaded
  #define _MAX549_LIB // if it was, just skip it

  #include "../lib/Common/spi.c" // load SPI generic library

  #ifndef CS_DAC // define default CS line of not defined earlier
    #define CS_DAC CS_SPI0 // CS pin for DAC module to CS0 if not already defined
  #endif

  void DAC_write (BOOLEAN dac, BYTE output) { // function to update DAC output value
    BYTE cmd;
    if (dac) { // DAC B is selected if dac is not 0
      cmd = 0x0A;
    } else { // DAC A if dac is 0
      cmd = 0x09;
    }
    output_low(CS_DAC); // Enable DAC with Chip Select
    SPI_out(cmd); // Sends command byte
    SPI_out(output); // Then sends DAC value
    output_high(CS_DAC); // Release chip
  }
#endif

First, it loads the SPI generic library, to have the basic SPI routines (read, write, setup SPI interface...)
Then it defines a default value for the Chip Select signal for the DAC. It default to CS_SPI0, which is defined in the SPI library, and depends on the mainboard used.

The function DAC_write is the function used to update the output value of the DAC. It takes 2 input variables:

  • dac: is a boolean (1 bit) value. If true, it will set DAC B. Else it will work with DAC A.
  • output: is a byte, which is the value to send to the selected DAC channel.

It does what is typically done for SPI chips: select the chip by setting its Chip Select signal low, sends 2 bytes (command + value) and release the chip by setting Chip Select high. The MAX549 datasheet explains in detail how to communicate with the chip.
One simple function is implemented, which allows to update individually each DAC channel.

The MAX549 frst loaded the generic SPI library, which is explained below: 

#ifndef _SPI_LIB // make sure library is not already loaded
  #define _SPI_LIB

  #include "../lib/Common/SPI.h"

  void Init_SPI (void) { // Function to initialise SPI interface
    output_high(MOSI);
    output_high(SCK);
    input(MISO);
    #if defined(HARDWARE_SPI) // if HARDWARE_SPI variable defined, use built-in SPI function available for device with hardware SPI interface
      setup_spi(SPI_MASTER | SPI_MODE_0_0 | SPI_CLK_DIV); // Setup SPI as master, mode 0 and speed defined in SPI.h
    #else
      #use spi(DI=MISO, DO=MOSI, CLK=SCK, BITS=8, MODE=0) // same as above, but for software emulated SPI interface
    #endif
    #ifdef CS_SPI0 // Set each Chip Select line to high (chip not selected) if defined
      output_high(CS_SPI0);
    #endif
    #ifdef CS_SPI1
      output_high(CS_SPI1);
    #endif
    #ifdef CS_SPI2
      output_high(CS_SPI2);
    #endif
  }

  void SPI_out(BYTE dataout) { // output byte on SPI
    #if defined(HARDWARE_SPI) // again, use hardware function if possible
      spi_write(dataout);
    #else
      spi_xfer(dataout);
    #endif
  }

  BYTE SPI_in(BYTE dataout) { // reads a byte from SPI
    BYTE datain;
    #if defined(HARDWARE_SPI)
      datain = spi_read(dataout);
    #else
      datain = spi_xfer(dataout);
    #endif
    return datain;
  }

  void SPI_out16(WORD dataout) { // functions for 16 bits value
    SPI_out(dataout>>8);
    SPI_out(dataout&0xff);
  }

  WORD SPI_in16(WORD dataout) {
    WORD datain=0;
    datain = spi_in(dataout >> 8);
    datain <<= 8;
    datain |= spi_in(dataout & 0xff);
    return datain;
  }
#endif

The SPI library defines basic functions to use the SPI interface of a board. Typical communication with SPI devices is on 8 or 16 bits. The basic functions are the 8 bits ones, which implement hardware SPI if the variable HARDWARE_SPI is defined. Else it will use software function to emulate the SPI bus.

  • Init_SPI: this initialise the SPI port as a master.
  • SPI_out: send a byte on the SPI interface
  • SPI_in: read a byte from the SPI interface, while clocking out the byte dataout
  • SPI_out16: send a 16 bits value on the SPI interface
  • SPI_in16: read a 16 bits value from the SPI interface while clocking the 16 bits dataout

The library loaded a header file, which defines variables based on the processor board used. It is worth noticing that the board_id variable needs to be defined, so the library knows how to implement the SPI interface, as each is wired differently on different board.

It is easy to add yor own board_id value, and base the varaible definition on an existing board. It makes adding your own projects/board very easy, without modifying the library files.

It is possible to define the variables BEFORE loading the header, in the main program for example. The values would then not be overwritten by the header values.

#ifndef _SPI_H
  #define _SPI_H

  #include "../lib/Common/TypeDefs.c" // Load custom data types

  #define SPI_MODE_0_0 (SPI_L_TO_H | SPI_XMIT_L_TO_H) // defines various SPI modes
  #define SPI_MODE_0_1 (SPI_L_TO_H)
  #define SPI_MODE_1_0 (SPI_H_TO_L)
  #define SPI_MODE_1_1 (SPI_H_TO_L | SPI_XMIT_L_TO_H)

  #ifdef board_id // 1: 16F819, 2: 16F628, 3: 16F873A, 4: 18F2320, 5: 18F26J11
    #if (board_id == 1 ) // Board 16F819
      #ifndef MISO // Master SDI, Slave SDO
        #define MISO PIN_B1
      #endif
      #ifndef MOSI // Master SDO, Slave SDI
        #define MOSI PIN_B2
      #endif
      #ifndef SCK // SPI clock signal
        #define SCK PIN_B4
      #endif
      #ifndef CS_SPI0
        #define CS_SPI0 PIN_B5 // SPI CS0 (SS Slave Select signal, to be able to use Slave mode of board)
      #endif
      #ifndef CS_SPI1
        #define CS_SPI1 PIN_B0 // SPI CS1 (Interrupt signal, so SPI device could trigger external interrupts on the board)
      #endif
      #ifndef CS_SPI2
        #define CS_SPI2 PIN_B3 // SPI CS2
      #endif
      #define SPI_CLK_DIV SPI_CLK_DIV_4 // define SPI clock speed in master mode
      #ifndef HARDWARE_SPI
        #define HARDWARE_SPI
      #endif
    #elif (board_id == 2) // Board 16F628 (no hardware SPI interface
      #ifndef MISO
        #define MISO PIN_B1
      #endif
      #ifndef MOSI
        #define MOSI PIN_B2
      #endif
      #ifndef SCK
        #define SCK PIN_B4
      #endif
      #ifndef CS_SPI0
        #define CS_SPI0 PIN_B5
      #endif
      #ifndef CS_SPI1
        #define CS_SPI1 PIN_B0
      #endif
      #ifndef CS_SPI2
        #define CS_SPI2 PIN_B3
      #endif
    #elif (board_id == 3) // Board 16F873A
      #ifndef MISO
        #define MISO PIN_C4
      #endif
      #ifndef MOSI
        #define MOSI PIN_C5
      #endif
      #ifndef SCK
        #define SCK PIN_C3
      #endif
      #ifndef CS_SPI0
        #define CS_SPI0 PIN_C0
      #endif
      #ifndef CS_SPI1
        #define CS_SPI1 PIN_C1
      #endif
      #ifndef CS_SPI2
        #define CS_SPI2 PIN_C2
      #endif
      #define SPI_CLK_DIV SPI_CLK_DIV_16
      #ifndef HARDWARE_SPI
        #define HARDWARE_SPI
      #endif
    #elif (board_id == 4) // Board 18F2320
      #ifndef MISO
        #define MISO PIN_C4
      #endif
      #ifndef MOSI
        #define MOSI PIN_C5
      #endif
      #ifndef SCK
        #define SCK PIN_C3
      #endif
      #ifndef CS_SPI0
        #define CS_SPI0 PIN_A5
      #endif
      #ifndef CS_SPI1
        #define CS_SPI1 PIN_B0
      #endif
      #ifndef CS_SPI2
        #define CS_SPI2 PIN_C2
      #endif
      #define SPI_CLK_DIV SPI_CLK_DIV_64
      #ifndef HARDWARE_SPI
        #define HARDWARE_SPI
      #endif
    #elif (board_id == 5) // Board 18F26J11
      #ifndef MISO
        #define MISO PIN_C4
      #endif
      #ifndef MOSI
        #define MOSI PIN_C5
      #endif
      #ifndef SCK
        #define SCK PIN_C3
      #endif
      #ifndef CS_SPI0
        #define CS_SPI0 PIN_A5
      #endif
      #ifndef CS_SPI1
        #define CS_SPI1 PIN_B0
      #endif
      #ifndef CS_SPI2
        #define CS_SPI2 PIN_C0
      #endif
      #define SPI_CLK_DIV SPI_CLK_DIV_16
      #ifndef HARDWARE_SPI
        #define HARDWARE_SPI
      #endif
    #else
      #error "Board board_id not supported"
    #endif
  #endif
  #if !defined(MISO) || !defined(MOSI) || !defined(SCK)
    #error MISO, MOSI and SCK must all be defined
  #endif
  #ifdef HARDWARE_SPI
    #if !defined(SPI_CLK_DIV)
      #define SPI_CLK_DIV SPI_CLK_DIV_16
    #endif
  #endif

  // function prototypes
  void Init_SPI (void);
  void SPI_out(BYTE dataout);
  BYTE SPI_in(BYTE dataout);
  void SPI_out16(WORD dataout);
  WORD SPI_in16(WORD dataout);
#endif

Compile the project and program the target with a PicKIT 2 programmer for example. Connect the processor board to the Dual 8 bits DAC board on the SPI connector. The CS line is defined to CS_SPI0, so make sure CS line on the DAC board is setup up on CS0 with JP1.

Power up the boards.
The output voltage on DAC A will increment, and decrement on DAC B. You can check this with a multimeter or oscilloscope.


Files and links:

Tutorial 2 source files.
Processor boards.
Dual 8 bits DAC.

Post your comments...

    Copyright 2011. Poker Games. Copyright © 2012 riaDesign