Using Harmony's Dynamic SPI Driver

 Summary

This page demonstrates the code needed for a Harmony application to read an EEPROM. This example reads the contents of address 00 and address 01 from a 25LC256 EEPROM. This page shows the following:

  • Use of the SPI Dynamic Driver
  • The Interrupt Mode for the Driver
  • Implementing the protocol needed to read an EEPROM

Hardware Environment

The simplified diagram on the right shows the technical details you will need to take into account when configuring this Harmony project:

  1. Configuring the external 8MHZ crystal to generate both internal system clock and the SPI Baud rate clock.
  2. Using PORTD pin12 (RD12) as the Chip Select Line.
  3. Setting up SPI channel 2 ( SCK2, SDO2, & SDI2) to communicate with the EEPROM.

The SPI will be configured to work in Interrupt Mode in this example.

spi-circuit.png

Protocol

The 25LC256 EEPROM data sheet shows the steps needed for a master SPI device to read an EEPROM:

  1. Assert the CS Signal
  2. Send the Read Command
  3. Send the Address to read
  4. Clock in the data from the EEPROM
  5. De-assert the CS Signal
To implement the read function the main application state machine is designed with 6 separate states. The chart below describes the sequence of the states and the actions taken by the states.
State Action Taken Transition Next State
APP_STATE_INIT Opens the SPI channel, Asserts CS When SPI channel is successfully opened APP_SEND_READ_COM
APP_SEND_READ_CMD Transmits out READ command and EEPROM address to read When function call competes APP_WAIT_FOR_REPLY
APP_WAIT_FOR_REPLY Checks transmit buffer status When status indicates transmit buffer has been emptied APP_GET_DATA
APP_GET_DATA clocks out 16 bits of random data on SO to read in 16 bits of data on SI When function call completes. APP_WAIT_FOR_DATA
APP_WAIT_FOR_DATA Checks buffer status When status indicates transmit buffer has been emptied. APP_READ_COMPLETE
APP_READ_COMPLETE De-Asserts CS signal n/a n/a

MPLAB Harmony Configurator (MHC)

Setting up the Clock


Setting up the I/O pin


SPI configuation


Example Code

  • When the project is generated MHC will configure the project files.
  • The application developer will have to create the application's data structures and functions.
  • The developer is also responsible for generating the code for the application state machine.

The code below demonstrates the code generated by MHC and the application specific components required to write to an EEPROM using SPI

Code settings from MHC

System Initialization

  • Inside the file system-init.c MHC places the call to the SPI initialization routine.
  • The system initialization function also set the interrupt priorities for SPI.

system-init.c


/* SPI Driver Index 0 initialization*/
SYS_INT_VectorPrioritySet(DRV_SPI_INT_VECTOR_IDX0, DRV_SPI_INT_PRIORITY_IDX0);
SYS_INT_VectorSubprioritySet**(DRV_SPI_INT_VECTOR_IDX0, DRV_SPI_INT_SUB_PRIORITY_IDX0);
sysObj.spiObjectIdx0 = DRV_SPI_Initialize(0, (const SYS_MODULE_INIT * const)&drvSpi0InitData);

  • drvSpi0InitData is the data structure containing the values to be loaded into the SPI special function regiisters.
  • drvSpi0InitData is defined in system-init.c.
  • The data elements used to contain the values for drvSpi0InitData are loaded into system-config.h by MHC.

Interrupts

  • When the interrupt mode is selected MHC will cause the interrupt vector to be placed in interrupt.c.
  • The ISR will call the Harmony Generated function DRV_SPI_Tasks with a pointer to the object for the SPI instance being accessed.
  • sysObj.spiObjectIdx0 is the data object for SPI instance 0.

system-interupt.c


// *
// *
// Section: System Interrupt Vector Functions
// *
// *

void __ISR(_SPI_2_VECTOR, ipl3AUTO) _IntHandlerSPIInstance0(void)
{
DRV_SPI_Tasks(sysObj.spiObjectIdx0);
}

  • DRV_SPI_Tasks invokes a callback function. MHC determines which callback to use depending on if an enhanced SPI buffer is being used.
  • The selection of the callback routine presents no considerations for the developer, the APIs and program flow are identical.

]
drv_spi.c


void DRV_SPI_Tasks ( SYS_MODULE_OBJ object )
{
struct DRV_SPI_DRIVER_OBJECT * pDrvObject = (struct DRV_SPI_DRIVER_OBJECT *)object;
(*pDrvObject->vfMainTask)(pDrvObject);
}

  • The main system while() loop does not call DRV_SPI_Tasks.

system-tasks.c


void SYS_Tasks ( void )
{
/* Maintain system services */
SYS_DEVCON_Tasks(sysObj.sysDevcon);

… … …

/* Maintain the application's state machine. */
APP_Tasks();
}

Application Data Structures and Functions

Variables and Data Structures

  • The application needs several variables to be defined.

app.h


/* Application current state */
APP_STATES state;

/* SPI Driver Handle */
DRV_HANDLE SPIHandle;

/* Write buffer handle */
DRV_SPI_BUFFER_HANDLE Write_Buffer_Handle;

/* Read buffer handle */
DRV_SPI_BUFFER_HANDLE Read_Buffer_Handle;

/* SPI Driver TX buffer */
SPI_DATA_TYPE TXbuffer[6];

/* SPI Driver RX buffer */
SPI_DATA_TYPE RXbuffer[6];

The data types used for SPI variable are created by MHC
Data Type Definition File of Origin
APP_STATES enumeration app.h
DRV_HANDLE typedef uintptr_t DRV_HANDLE; driver_common.h
DRV_SPI_BUFFER_HANDLE typedef uintptr_t DRV_SPI_BUFFER_HANDLE; drv_spi.h
SPI_DATA_TYPE typedef unsigned char SPI_DATA_TYPE; drv_spi.h



Required Functions

  • To allow the Chip Select Pin to be controlled by RD12 ( Port D Pin 12):

app.h


#define SPI_CS_PORT_ID PORT_CHANNEL_D

#define SPI_CS_PORT_PIN PORTS_BIT_POS_12

#define APP_SPI_CS_SELECT() \
SYS_PORTS_PinClear(PORTS_ID_0,SPI_CS_PORT_ID,SPI_CS_PORT_PIN)

#define APP_SPI_CS_DESELECT() \\
SYS_PORTS_PinSet(PORTS_ID_0,SPI_CS_PORT_ID,SPI_CS_PORT_PIN)

Application State Machine

  • The code below shows the initialization of the application and the progression of the Application State Machine.
  • The protocol, describe at the top of this page, reads 2 bytes from the EEPROM beginning at address 00.

app.c


void APP_Initialize ( void )
{
APP_SPI_CS_DESELECT();

state = APP_STATE_INIT;
}


...

void APP_Tasks ( void )
{
switch(state)
{
case APP_STATE_INIT: /* opens the SPI channel.
If the SPI channels is successfully opened
the CS line is asserted and the State is set
to APP_SEND_READ_CMD*/.

{
SPIHandle = DRV_SPI_Open(DRV_SPI_INDEX_0, DRV_IO_INTENT_READWRITE );

if(SPIHandle != (uintptr_t)NULL)
{
APP_SPI_CS_SELECT();
state = APP_SEND_READ_CMD;
}
break;
}

case APP_SEND_READ_CMD: ]/* Loads the transmit buffer with the Read command
(0x03), the 16-bit address to read ( 0x0000),
and one byte of don't cares. ( 0x00).
The data buffer is sent to the SPI peripheral
using DRV_SPI_BufferAddWrite. The state
is incremented after the call to the transmit function.*/

{
TXbuffer[0] = 3; /* EEPROM Read Opcode */
TXbuffer[1] = 0; /* Address - LSB */
TXbuffer[2] = 0; /* Address - MSB */
TXbuffer[3] = 0; /* Dummy byte */

Write_Buffer_Handle = DRV_SPI_BufferAddWrite(SPIHandle,
(SPI_DATA_TYPE *)&TXbuffer[0], 4, 0, 0);
state = APP_WAIT_FOR_REPLY;
break;
}

case APP_WAIT_FOR_REPLY: /* Verifies the transmit function has completed before
changing the state to APP_GET_DATA */

{
if(DRV_SPI_BUFFER_EVENT_COMPLETE &
DRV_SPI_BufferStatus(Write_Buffer_Handle))
state = APP_GET_DATA;
break;
}

case APP_GET_DATA: /* Sends out 16 clock signals (4 bytes of don't care data) to
receive the contents of the address 0x00, and 0x01 from the EEPROM.
Increments the state upon return from DRV_SPI_BufferAddRead */

{
Read_Buffer_Handle = DRV_SPI_BufferAddRead( SPIHandle,
(SPI_DATA_TYPE *)&RXbuffer[0], 4, 0, 0);
state = APP_WAIT_FOR_DATA;
break;
}

case APP_WAIT_FOR_DATA: /* Checks to see if the SPI peripheral has finished receiving the data.
Upon successful completion, the data will be in the receive buffer. The CS line
will be de-selected.*/

{ if(DRV_SPI_BUFFER_EVENT_COMPLETE &
DRV_SPI_BufferStatus (Read_Buffer_Handle))
{
APP_SPI_CS_DESELECT();
state = APP_READ_COMPLETE;
}
break;
}

case APP_READ_COMPLETE:
break; /* end of function */
}
}

20th Annual
Microchip MASTERs Conference 2016
Register now - Deadline: July 29

JW Marriott Desert Ridge Resort-Phoenix, AZ

© 2016 Microchip Technology, Inc.
Information contained on this site regarding device applications and the like is provided only for your convenience and may be superseded by updates. It is your responsibility to ensure that your application meets with your specifications. MICROCHIP MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND WHETHER EXPRESS OR IMPLIED, WRITTEN OR ORAL, STATUTORY OR OTHERWISE, RELATED TO THE INFORMATION, INCLUDING BUT NOT LIMITED TO ITS CONDITION, QUALITY, PERFORMANCE, MERCHANTABILITY OR FITNESS FOR PURPOSE. Microchip disclaims all liability arising from this information and its use. Use of Microchip devices in life support and/or safety applications is entirely at the buyer's risk, and the buyer agrees to defend, indemnify and hold harmless Microchip from any and all damages, claims, suits, or expenses resulting from such use. No licenses are conveyed, implicitly or otherwise, under any Microchip intellectual property rights.