Embedded Systems and Power Electronics

Total Pageviews

About Me

My photo
I am currently a PhD student at UC Berkeley, following a 6-year journey working at Apple after my undergrad years at Cornell University. I am a 2025 Paul & Daisy Soros fellow. I grew up in Dhaka, Bangladesh where my interest in electronics was cultivated, resulting in the creation of this blog.

BTemplates.com

Powered by Blogger.

Dec 28, 2015

Using an input device on Embedded Linux: An example with a USB mouse on the Intel Edison


The Intel Edison test board, along with the USB mouse


I have recently been using the Intel Edison for the Cornell robotics project team (which co-hosts the Intel-Cornell Cup USA competition). Building on my previous knowledge of embedded systems, I started learning to use and program on Linux. The distro used is Yocto (all information is available on the Intel Edison website).

One of the prototypes we worked on relied on using a wireless Playstation 4 controller for locomotion user interface. The concept of using an input device on Linux is not complicated, but can be a daunting task for someone new to Linux programming. Hence, I have decided to write this article giving an example of using an input device on an Embedded Linux platform. This demo application I am showing uses a USB mouse as the input device connected to the Intel Edison.

Prerequisite: I have assumed that you have a flashed Intel Edison board, know how to access the terminal (through the USB COM port, or through SSH) and have the Eclipse IDE installed and can program with it. Of course, if you don't have the IDE, you can compile the code through the terminal and I'll tell you how to do it at the end. If you are using a platform other than the Edison, details may change but the general idea is similar. Additionally, it is assumed that you have a basic understanding of C programming.

First thing to note when you connect the USB mouse is that the switch on the board (labelled SW1) must be switched towards the USB-A connector from the default position facing the microUSB port.

The device drivers in Linux abstract away the low-level nitty gritty details of the interface with the input device, presenting an input through file descriptors that can be interfaced with as files. The input devices can be viewed and read from in the Linux environment just like files, as mentioned before. The input device appears in the /dev/input directory. Initially, before the mouse is plugged in, you can see that there is an event0 and an event1 file. Upon connecting the mouse, you can see an event2 file.

 
Fig. 1: Input device files without mouse connected

 
Fig. 2: Input device files with mouse connected

By reading the event2 file, you can read the mouse data. To dump data from the file, you can use the od command (man page: http://man7.org/linux/man-pages/man1/od.1.html)

For example, to view the output dump in hex format:
od -x event2

Move the mouse around, press the buttons, scroll the wheel and you'll see data appear on the console:

Fig. 3: File event2 data dump using od command

Hit Ctrl+C when you're satisfied you've seen enough of the dump.

Now to make sense of this input, decipher it and meaningfully use it, I have written a simple C application. I'll walk you through the process of developing it before I provide the code.

First thing to do is to go through these references as part of the kernel documentation:
https://www.kernel.org/doc/Documentation/input/event-codes.txt
https://www.kernel.org/doc/Documentation/input/input.txt

Additionally, you should go through the linux/input.h header file. You can find a copy here:
http://lxr.free-electrons.com/source/include/linux/input.h?v=2.6.38

You can also type it into Eclipse, hit Ctrl on your keypad and left mouse click on the header file name to view the file itself.

From the kernel documentation and the input.h file, you should find that the data output happens such that every time an event occurs, it can be "fit" into the following structure (defined in linux/input.h):

struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};


You can find that this has a total length of 16 bytes. You can look through the different data types and add, and confirm using the sizeof function in Eclipse:
fprintf(stdout, "Size of event is: %d\n", sizeof(event));

Each event has a timestamp, type, code and value as you can guess from the input structure. Additionally events are separated by EV_SYN type events which are just markers. EV_SYN is defined as 0.

You can read the file in a C program and then just print out the values separated as fields in the input event structure to confirm that and observe the different types of data as you interact with the mouse. You can limit the type of event as you interact with your mouse. To understand the meaning of the numbers you receive, peruse the linux/input.h file and the kernel documentation linked above. You will see a section describing the events:

/*
 * Event types
 */

#define EV_SYN            0x00
#define EV_KEY            0x01
#define EV_REL            0x02
#define EV_ABS            0x03
#define EV_MSC            0x04
#define EV_SW             0x05
#define EV_LED            0x11
#define EV_SND            0x12
#define EV_REP            0x14
#define EV_FF             0x15
#define EV_PWR            0x16
#define EV_FF_STATUS      0x17
#define EV_MAX            0x1f
#define EV_CNT            (EV_MAX+1)


You can also find a section describing the different keys/buttons for a keyboard, gamepad, mouse, etc. The section describing the mouse is:

#define BTN_MOUSE       0x110
#define BTN_LEFT        0x110
#define BTN_RIGHT       0x111
#define BTN_MIDDLE      0x112
#define BTN_SIDE        0x113
#define BTN_EXTRA       0x114
#define BTN_FORWARD     0x115
#define BTN_BACK        0x116
#define BTN_TASK        0x117


/*
 * Relative axes
 */

#define REL_X            0x00
#define REL_Y            0x01
#define REL_Z            0x02
#define REL_RX           0x03
#define REL_RY           0x04
#define REL_RZ           0x05
#define REL_HWHEEL       0x06
#define REL_DIAL         0x07
#define REL_WHEEL        0x08
#define REL_MISC         0x09
#define REL_MAX          0x0f
#define REL_CNT          (REL_MAX+1)


You can compare these against the values you see to see if they make sense (they should!). Then, you can proceed to mold this to read the different codes, types and values based on these. This is what I have done in my demo application, which should be commented enough for you to understand. (Obviously, if you have questions, let me know in the comments section!)

One last thing that I haven't covered yet (but you may already know) is how to do the file read. I have used the low-level file IO functions open and read:

Opening the file:

// Use low-level Linux file IO operations
// Device is presented as a file, event2 in /dev/input for the Edison
int fid = open("/dev/input/event2", O_RDONLY);
if (fid == 0){
    fprintf(stderr, "Could not open event2 device!\n");
    return EXIT_FAILURE;
}
fprintf(stdout, "Opened event2 device!\n");


Reading the file:

int nbytes;
struct input_event event;

// Event type from <linux/input.h>
nbytes = read(fid, &event, sizeof(event));

The demo application prints out messages describing mouse motion, wheel motion and left, middle (wheel) and right button presses. See Fig. 4 below.

You can find the full code for my demo project here: https://drive.google.com/file/d/0B4SoPFPRNziHUUVCRHVPNjkwZ2s/view?usp=sharing

A typical output is shown below:
 
Fig. 4: Output of the demo application 


Programming without the Eclipse IDE: As I have mentioned before, even if you don't have the Eclipse IDE (which you should get), you can still program the Edison. Here are a few ways you can do so. You can copy-paste the code from a text editor to the terminal (using PuTTY, mouse right-click is paste), or even write the code on the terminal. Additionally, you can use a program such as WinSCP to transfer a C file. Be careful with Windows files since lines end in a newline and a carriage return character, whereas on Linux, they end with only a newline character. The carriage return character will be displayed as ^M if you open the file with the text editor. Once the file is on the Edison file system somewhere, cd into that folder and compile it:

gcc -o <output name> <source file name>
eg: gcc -o mouse mouse.c

Then you can run it:
eg: ./mouse

I have attempted to make the code self-explanatory and provide sufficient background detail here for you to understand what's going on. By changing the code and type checks, you can extend this to other devices. Hopefully you'll find this useful! Let me know what you think!

Aug 2, 2015

PIC32 Tic-Tac-Toe: Demonstration of using touch-screen, TFT and the Protothreads threading library






I had previously used the Adafruit TFT display using my library (ported from the Adafruit Arduino library). I decided to optimize the library to improve drawing speed. The same display I use comes with a 4-wire resistive touch-screen as well. I decided to write a simple library for the touch-screen and give Protothreads a try. To incorporate all this, I thought it would be cool if I used these to make a simple game. Tic-tac-toe came to mind as a fun little demo.


I'm sure everyone's familiar with the game so I won't explain the rules there. The touch-screen is simply two resistive sheets placed on top of each other on top of the TFT screen. When it is pressed down at a given place, the two sheets make contact and a voltage divider is formed. Using the IO's and the ADC, this voltage is read in the X and Y directions to register a touch.

Here is a very good pictorial depiction of the resistive touch screen (taken from the Atmel AVR341 document):

So in order to read the touch, the X+ and X- points are applied power, and one of Y+ or Y- is read to read the x-coordinate. Then Y+ and Y- are applied power and one of X+ or X- is read to read the y-coordinate. X+, X-, Y+ and Y- are connected to four GPIO pins on the PIC32 that are configured to outputs when driving the touch-screen and analog inputs when reading. Every time the IO pin switches state, a long delay is provided to allow the outputs to stabilize. Alternately, the ADC is significantly slowed down to negate effects of capacitive charging by high source impedance. The library is written in the form of a simple state machine cycling through its states every few milliseconds, decided by the application calling the library functions. In my application, I use 5 milliseconds.

To organize the game, I've made use of the Protothreads threading library. Protothreads is a very light-weight, stackless, threading library written entirely as C macros by Adam Dunkels. Bruce Land has ported Protothreads over for the PIC32. You can find more details on his excellent site: http://people.ece.cornell.edu/land/courses/ece4760//PIC32/index_Protothreads.html

There are two main executing threads, one is the main game thread and the other is a clock thread that keeps track of, and displays, time since the program was started. There is a third thread used to retrieve touch information. It is spawned by the main game thread when touch input is required. The main Protothreads functions (macros) I've made use of are:

PT_setup()
PT_INIT()
PT_SCHEDULE()
PT_SPAWN()
PT_WAIT_UNTIL()
PT_YIELD_TIME_msec()
PT_GET_TIME()

Pin connections:

BL (backlight): I left it unconnected, but you can connect it to 3.3V for backlight.
SCK: connected to RB14 on the PIC
MISO: left unconnected, since I'm not reading anything from the screen
MOSI: connected to RB11 on the PIC
CS: connected to RB1 on the PIC
SDCS: left unconnected as I'm not using the microSD card for this
RST: connected to RB2 on the PIC
D/C: connected to RB0 on the PIC
X+: connected to RA4 on the PIC
X-: connected to RB13 on the PIC
Y+: connected to RB5 on the PIC
Y-: connected to RB15 on the PIC
VIN: connected to 3.3V supply
GND: connected to gnd

Here is a demo of the game:



Besides the game itself, you can see the running clock on the bottom left right above the players' scores. To the bottom right you can see a flickering circle that is either green or red, depending on if it's player 1 or 2's turn, respectively. Once the game is over, you have the option of playing another game while score is being tracked.

Here is a link to the MPLABX project with all required header and source files:
https://drive.google.com/file/d/0B4SoPFPRNziHbURwVWVSN1c3VVE/view?usp=sharing

I have commented the code to make it fairly self-explanatory. If you have doubts or questions about anything, let me know and I'll add more detail. Let me know what you think!

Jan 7, 2015

Stereo audio player using the PIC32, MCP4822, microSD card and the MDDFS library


Oscilloscope screen capture of output from the audio player
Top - left channel
Bottom - right channel

Using the PIC32MX250F128B, I decided to make a simple audio player. I wanted to play back good quality audio from a large memory space - a microSD card. So, I made this WAV player that can play back 16-bit 44.1kHz WAV files with 12-bit stereo audio output. Of course that's not all it can play back. It is programmed for automatic period configuration so that the period is set on the fly based on the song sample rate. It can play back both 8-bit and 16-bit mono and stereo audio files and I have tested from 8kHz 8-bit mono to 44.1kHz 16-bit stereo. The player itself does not include an audio amplifier to drive speakers but can drive earphones. I've used an external stereo speaker for testing.

The hardware is fairly simple! Using the Microchip Memory Disk Drive File System (MDDFS) library, and my previous work using the MCP4822 dual 12-bit DAC, integrating these components to make a working audio player was quite fun and a good learning experience.

Here I share all my project files and source code, along with documentation regarding this project. Let me know what you think!
Schematic of PIC32-based audio player





Running demo of the audio player:



All project files and schematic can be downloaded from:
https://drive.google.com/file/d/0B4SoPFPRNziHRGE0bVNZYV9uVjQ/view?usp=sharing

Documentation can be downloaded from:
https://drive.google.com/file/d/0B4SoPFPRNziHaWpIWFQ3RkJFVzQ/view?usp=sharing

Oct 30, 2014

Interfacing a color TFT display with the PIC32MX250F128B


I have been working on interfacing the PIC32MX250F128B with a small 2.2" TFT display from Adafruit. It's a nice little display that is fairly easy to communicate with, using SPI communication. The display I'm using is: http://www.adafruit.com/product/1480

Adafruit provides nice open-source libraries for their products. However, they are for Arduino and thus cannot be directly reused for the PIC32. I went through the library and ported it over for the PIC32, in C. I have attached my project file as a .zip file and you can download it to go through the library header and source files, as well as the demo code. I've tried heavily commenting the code so that it is self-explanatory.

As far as hardware goes, with the demo code, the pin connections for the display are:

BL (backlight): I left it unconnected, but you can connect it to 3.3V for backlight.
SCK: connected to RB14 on the PIC
MISO: left unconnected, since I'm not reading anything from the screen
MOSI: connected to RB11 on the PIC
CS: connected to RB1 on the PIC
SDCS: left unconnected as I'm not using the microSD card for this
RST: connected to RB2 on the PIC
D/C: connected to RB0 on the PIC
VIN: connected to 3.3V supply
GND: connected to gnd

The pins I used are defined in the code and you can easily change them as required.

I used my custom proto-board for testing on a breadboard. You can find details here: http://tahmidmc.blogspot.com/2014/02/pic32-proto-board-details-schematic-pcb.html


Here's a video showing the PIC32 running the demo code and doing some simple graphics on the screen. You can see that it runs fairly quickly and quite smoothly.


Project files with all source and header files:
https://drive.google.com/file/d/0B4SoPFPRNziHdksweU1vUkZ4SHM/view?usp=sharing
http://www.4shared.com/zip/O-LcEU0Ace/Adafruit_TFTX.html

If you have any comments, questions or suggestions, do let me know!

Oct 16, 2014

PIC32 DMA+SPI+DAC : Analog output synthesis with zero CPU overhead


I have previously shown how to use the PIC32 SPI module to use the 12-bit DAC MCP4822: http://tahmidmc.blogspot.com/2014/10/pic32-spi-using-mcp4822-12-bit-serial.html. While that does allow you to generate analog outputs as desired, it requires you to use CPU cycles to process the timer interrupt and accordingly drive the SPI module.

Since the PIC32 contains DMA channels, the process can be completely offloaded from the CPU. For an idea of the PIC32 DMA module, refer to my previously written article: http://tahmidmc.blogspot.com/2014/05/simple-pic32-dma-example.html

Fig. 1 - The generated sine wave at fpwm = 400kHz and 32 elements in the sine table

So, the simple way of offloading the SPI update to the DMA module would be to let the DMA channel transfer data to the SPI buffer. The SPI module is configured for 16-bit data transfer (since the MCP4822 write command requires 2 bytes). This means that the cell size for the DMA channel has been set to 2 (2 bytes to transfer once triggered). The DMA transfer is triggered by a timer interrupt. Unlike the previous example where I used Timer 1 and its interrupt for the SPI transfer, here I use Timer 2. The reason for this is that, if I want the entire process to be offloaded from the CPU, the SS (Slave Select) or CS (Chip Select) also has to be done entirely in hardware. For that I used the Output Compare 1 module. To use the OC1 module, either Timer 2 or Timer 3 (or the combined 32-bit timer) has to be used. So, I just went with Timer 2.

Configuring the DMA module for transferring data to the SPI buffer was the simple part. I found the configuration of the SS/CS pin the more challenging part. The idea here was to use the OC module in "dual compare mode continuous output pulses" mode. The OC module in this mode generates continuous pulses - the output pin OC1 - which I used as CS/SS - is set high one PBCLK (peripheral bus clock) after the Timer value reaches OC1R; the OC1 pin is cleared one PBCLK after the Timer value reaches OC1RS. Since SS/CS is active low, I set ( (period register) - 4) to be OC1RS and a variable CSlength to be OC1R. CSlength was chosen to be 80% of the period register. What this meant was that. One PBCLK after the Timer reached the (period register - 4), the OC1 pin went low (CS/SS went low) "selecting" the DAC. After about 0.80*(period register) from there, the OC1 pin went high. This means that the CS pin is low for 80% of the period - it goes low a small time right before the DMA transfer happens and is raised high late enough, after the DMA transfer occurs. I determined that 80% the period was enough time since that is higher than the time required to shift out 16 bits of data at 20MHz SPI clock. See Fig. 2 below for an illustration of the operation of the output compare module in the "dual compare mode continuous output pulses" mode:

 Fig. 2 - Output Compare module in Dual Compare Mode: Continuous Output Pulse mode (taken from PIC32 reference manual, figure 16-16)

Beyond that, the idea is simple. There is a sine table that is the DMA source. The key thing to remember here is that the 12-bit data must be OR-ed with 0x3000 since that is required for the DAC (to set gain=1, shutdown = 0 and channel = A):

void generateTables(void){
    uint8_t i;
    for (i = 0; i<TABLE_SIZE; i++){
        sineTable[i] = (short) (2047.0 * sin(6.2832*((float)i)/(float)TABLE_SIZE));
        sineTable[i] = sineTable[i] + 2047;

        sineTable[i] = 0x3000 | sineTable[i];
    }
}

Since the entries in the sine table are of "short" data type (each element occupies two bytes), the source size (in bytes) is twice the number of elements. The destination source size is two bytes since I'm transferring two bytes to the SPI buffer register. The cell size is two bytes since the DMA channel has to transfer two bytes (16 bits) at a time to the SPI buffer register. The DMA configuration is:

    DmaChnOpen(0, 3, DMA_OPEN_AUTO);
    // (ch, ch priority, mode)
    DmaChnSetTxfer(0, sineTable, &SPI1BUF, TABLE_SIZE*2, 2, 2);
    // (ch, start virtual addr, dest virtual addr, source size, dest size, cell size)
    DmaChnSetEventControl(0, DMA_EV_START_IRQ(_TIMER_2_IRQ));
    // (ch, trigger irq)
    DmaChnEnable(0);

A point of note is that, the DmaChnSetTxfer( ) takes in the virtual addresses of the source and destination, not the physical addresses. The virtual-to-physical address conversion is done by the function itself as opposed to it being required manually when doing register level operations (as done in my previous DMA example article).

To demonstrate that the DAC control is done with no CPU overhead, in the main( ) function, I have an infinite loop that is just toggling RA0. The output is checked on an oscilloscope:

Fig. 3 - RA0 toggle being demonstrated

Observe that the frequency of the square wave is 4.0MHz - the same that would be observed if all the PIC was doing was the pin toggling.

This also gave better performance than my previous experiment where I did not use DMA. The output sine wave has a higher frequency than observed before and this matches exactly as expected: 400kHz/32 = 12.5kHz as seen (see Fig. 1).

The rest should be very easy to understand and self-explanatory. If you have any questions or suggestions, feel free to comment!

Here are the source and header files:
config.h: https://drive.google.com/file/d/0B4SoPFPRNziHcmlxMmg2Si0zWjQ/view?usp=sharing
main.c: https://drive.google.com/file/d/0B4SoPFPRNziHcmlxMmg2Si0zWjQ/view?usp=sharing

Oct 11, 2014

PIC32 SPI: Using the MCP4822 12-bit serial dual DAC


I recently got a few pieces of the MCP4822 DAC. You can check out the datasheet here: http://ww1.microchip.com/downloads/en/DeviceDoc/22249A.pdf

I found them to be neat little devices. In a small 8-pin PDIP package, the MCP4822 comes with 2 12-bit DACs, which you can easily configure over SPI. This was a great opportunity to get a simple PIC32 SPI application going. I worked on this today to see how fast I can get the DAC output going.

Here's the pinout of the MCP4822:
Fig. 1 - MCP4822 pinout (taken from datasheet)

The MCP4822 can be supplied a voltage in the range of 2.7V to 5.5V. Since I use 3.3V for my PIC32, I used the same 3.3V supply for the VDD for the MCP4822. Pin 5 is the active low signal LDAC that is used to synchronize the two DAC channels. When this pin is brought low, the data in the DAC's input register is copied to the output and both outputs are updated at the same time. I just had this tied to ground. VoutA and VoutB are the two output pins. The other pins are the regular pins for SPI communication - CS (active low) chip select, SCK serial clk, SDI serial data in.

The datasheet provides a nice diagram explaining the timing and pin functions very nicely:
Fig. 2 - Write command for MCP4822 (taken from datasheet)

The write command to the MCP4822 is a 16-bit wide command sent over SPI. Bit 15 (A/B) selects which channel you're sending data to: 1 signifies channel B, 0 signifies channel A. Bit 13 (GA) selects the gain. The MCP4822 has an internal 2.048V reference it uses for the DAC. So, that means that with a gain of 1, the maximum possible output voltage is 2.04975V (4095/4096 * Vref). If a higher output is required, the gain can be increased. The MCP4822 has a configurable gain of 1 or 2. When GA = 1, gain = 1; when GA = 0, gain = 2. Bit 12 SHDN is the shutdown signal. When SHDN is low, the output of the DAC is shut down. The 12 data bits from there on: D11 to D0 ([D11:D0], [bit11:bit0]) are the 12 data bits for the digital to analog conversion.

The transfer function is the very-simple, (almost) typical-for-DACs:
Vout = (D_12 / 4096) * 2.048V * Gain
D_12 is the digital value given by D11 to D0 in the write command, 4096 = 2^12 (12-bit resolution), 2.048V = internal voltage reference, gain = 1 or 2 depending on bit GA in write command.

While I typically use the registers and configure them manually, I decided to use the Microchip plib (peripheral library) here for the SPI module. The peripheral library consists of a lot of low-level functions and macros that essentially require understanding the peripheral but just make the code nicer and easier to read.

I decided to clock the MCP4822 at 20MHz (max for MCP4822) to get quick data transfers.

The SPI module can be initialized using one of 2 options:
1) Using the OpenSPIx(config1, config2) and SpiChnSetBrg(spi_channel, spi_brg) functions
2) Using the SpiChnOpen(spi_channel, config, spi_divider) function

These are both described in the plib documentation.

The DAC initialization routine I wrote is shown below:
//================================================================
// SS = PORTA4
#define SS      LATAbits.LATA4
#define dirSS   TRISAbits.TRISA4
void initDAC(void){
/* Steps:
 *    1. Setup SS as digital output.
 *    2. Map SDO to physical pin.
 *    3. Configure SPI control and clock with either of a or b:
 *        a. OpenSPIx(config1, config2) and SpiChnSetBrg(spi_channel, spi_brg)
 *        b. SpiChnOpen(spi_channel, config, spi_divider)
 */

    dirSS = 0;                    // make SS an output
    SS = 1;                        // set SS = 1 to deselect slave
    PPSOutput(2, RPB5, SDO2);    // map SDO2 to RB5

///////
#define config1 SPI_MODE16_ON | SPI_CKE_ON | MASTER_ENABLE_ON
    /*    FRAME_ENABLE_OFF
     *    ENABLE_SDO_PIN        -> SPI Output pin enabled
     *    SPI_MODE16_ON        -> 16-bit SPI mode
     *    SPI_SMP_OFF            -> Sample at middle of data output time
     *    SPI_CKE_ON            -> Output data changes on transition from active clock
     *                            to idle clock state
     *    SLAVE_ENABLE_OFF    -> Manual SW control of SS
     *    MASTER_ENABLE_ON    -> Master mode enable
     */
#define config2 SPI_ENABLE
    /*    SPI_ENABLE    -> Enable SPI module
     */
//    OpenSPI2(config1, config2);
    // see pg 193 in plib reference

#define spi_channel    2
    // Use channel 2 since channel 1 is used by TFT display

#define spi_brg    0
    // Divider = 2 * (spi_brg + 1)
    // Divide by 2 to get SPI clock of FPBDIV/2 -> max SPI clock

//    SpiChnSetBrg(spi_channel, spi_brg);
    // see pg 203 in plib reference

//////

//////

#define spi_divider 2
/* Unlike OpenSPIx(), config for SpiChnOpen describes the non-default
 * settings. eg for OpenSPI2(), use SPI_SMP_OFF (default) to sample
 * at the middle of the data output, use SPI_SMP_ON to sample at end. For
 * SpiChnOpen, using SPICON_SMP as a parameter will use the non-default
 * SPI_SMP_ON setting.
 */
#define config SPI_OPEN_MSTEN | SPI_OPEN_MODE16 | SPI_OPEN_DISSDI | SPI_OPEN_CKE_REV
    /*    SPI_OPEN_MSTEN        -> Master mode enable
     *    SPI_OPEN_MODE16        -> 16-bit SPI mode
     *    SPI_OPEN_DISSDI        -> Disable SDI pin since PIC32 to DAC is a
     *                            master-to-slave    only communication
     *    SPI_OPEN_CKE_REV    -> Output data changes on transition from active
     *                            clock to idle clock state
     */
//    SpiChnOpen(spi_channel, config, spi_divider);
//////
}
//================================================================

The code block above has code written using both the initialization functions (one is commented out). Essentially, the configuration is that SPI channel 2 is enabled as a master, it is configured for 16-bit data transmission, the SDI pin is disabled, it is configured for serial output change from active high (1) to active low (0) as required by the MCP4822 (see Fig. 2) and the clock divisor is set to 2 so that SPI clock = 20MHz.

The SCK pins for the PIC32 are mapped physically to the RB15 (SCK2) and RB14 (SCK1) pins (check pinout on page 4 in datasheet). The SDO and SDI pins are remappable pins and thus require you to map them to physical pins. The SDI pin here is unused since the PIC32-to-MCP4822 is a one way communication with the PIC32 as the master and the MCP4822 as the slave.

The datasheet provides the table for all the PPS (peripheral pin select) mappings. The relevant section for the SDO is:
Table 1 - PPS configuration for SDO

I decided to use RPB5:

PPSOutput(2, RPB5, SDO2);    // map SDO2 to RB5

The connections I used for the MCP4822 are:


Fig. 3 - MCP4822 connections


The chip select / slave select line (CS as shown on the MCP4822 pinout, see Fig. 1) has to be controlled manually. (For a hardware-based processor-free control scheme, see PIC32 DMA+SPI+DAC : Analog output synthesis with zero CPU overhead.) This is an active low chip select signal. When data is being sent to the MCP4822, while the SPI transaction is occurring, the CS pin must be kept low. Initially I checked to see if there was a way the hardware would take care of that. I looked into the framed SPI mode. However, this did not serve my purpose. From what I've read, the framed SPI mode is an SPI mode where the clock is always output (instead of only during transactions as traditionally done). However, the hardware generates a low sync signal (by pulling CS low) before the data is transmitted from the PIC32. The problem here was that the CS pin was pulled low and kept low for one SPI clock period before raising CS high as data is transmitted out from SDO. This isn't going to work for the MCP4822 since the chip requires that CS be held low during the entirety of data transmission (see Fig. 2). So I just decided to use RA4 as the pin for CS. Before starting any transmission, I pull CS low in software and raise it high at the end.

Here's the code for writing to the DAC:

inline void writeDAC(uint16_t data){
    SS = 0; // select slave device: MCP4822 DAC
    while (TxBufFullSPI2()); // ensure buffer is free before writing
    WriteSPI2(data);   // send the data through SPI
    while (SPI2STATbits.SPIBUSY); // blocking wait for end of transaction
    SS = 1; // deselect slave device, transmission complete
}

Since I called this function from the ISR in my code, I decided to make it an inline function to eliminate function call overhead.

In my test code, I just wanted to see how fast an output I can get. So I clocked the MCP4822 at 20MHz and tried to maximize the frequency of the output signal I generate: I used channel A to generate a sine wave output and channel B to generate a triangle wave output. I used a timer (Timer 1) to control the timing and in the ISR, I just called the writeDAC inline function to generate outputs as required from two pre-generated tables.

Here are the different waveforms and outputs I generated:

Fig. 4 - Generating the outputs with timer period = 400kHz, sine table entries = 64



Fig. 5 - Generating the outputs with timer period = 550kHz, sine table entries = 32



Fig. 6 - Generating the outputs with timer period = 550kHz, sine table entries = 64



Fig. 7 - SPI clock and data transmission
You can clearly see the SPI clock during transmission here. From the measurement panel on the right you can see that the SPI clock is 20MHz. There are also 16 pulses you can count - since a write command to the DAC consists of 16 bit transfers. CH2, on the bottom, shows the data being transmitted. Since the first signal (leftmost) is a 1, and since this corresponds to bit 15, this is a snapshot of data being transmitted to DAC channel B.


Fig. 8 - DAC steps - zoomed in


Fig. 9 - DAC steps - zoomed out


Fig. 10 - Hardware test setup

For the PIC32 proto board, I used my own custom proto board: http://tahmidmc.blogspot.com/2014/02/pic32-proto-board-details-schematic-pcb.html


Here are the header and source files for my test program:

config.h: https://drive.google.com/open?id=0B4SoPFPRNziHdk5KT2hWNUNCckE
main.c: https://drive.google.com/open?id=0B4SoPFPRNziHWFc0Y01qSW5XUkE


While this was a quick test I did to test the PIC32 SPI and the MCP4822, this is in no means a complete evaluation of the MCP4822. For example, I updated the DAC more quickly than I should have, to push for speed, as this didn't give enough of a settling time. However, I was satisfied with the results and so, stuck with that. I will do some further testing on this later.

For more details of the PIC32MX250F128B SPI module, refer to the datasheet and reference manual. For more details on the MCP4822, refer to its datasheet.

If you have any questions, feel free to ask. Let me know what you think, in the comments section below!

Note: Updated September 10, 2015