Summary
This page demonstrates the code needed for an MPLAB® Harmony application to read an Electrically Erasable Programmable Read-Only Memory (EEPROM). This example reads the contents of address 00 and address 01 from a 25LC256 EEPROM. This page shows the following:
- Use of the Serial Peripheral Interface (SPI) dynamic driver,
- The Interrupt mode for the driver,
- Implementing the protocol needed to read an EEPROM.
Hardware Environment
The simplified diagram on the below shows the technical details you will need to take into account when configuring this Harmony project:
- Configuring the external 8 MHz crystal to generate both the internal system clock and the SPI baud rate clock.
- Using PORTD pin12 (RD12) as the Chip Select Line.
- 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.
Protocol
The "256K SPI Bus Serial EEPROM" data sheet shows the steps needed for a Master SPI device to read an EEPROM:
- Assert the Chip Select Input (CS) signal.
- Send the read command.
- Send the address to read.
- Clock in the data from the EEPROM.
- De-assert the CS signal.
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 the 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 Serial Data Output (SO) to read in 16-bits of data on Serial Data Input (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. | Not applicable. | Not applicable. |
MPLAB Harmony Configurator (MHC)
Setting up the Clock
Setting up the I/O pin
SPI configuation
Example Code
- When the project is generated, the 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 the MHC and the application-specific components required to write to an EEPROM using SPI.
Code settings from the MHC
System Initialization
- Inside the file system-init.c the 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 registers.
- drvSpi0InitData is defined in system-init.c.
- The data elements used to contain the values for drvSpi0InitData are loaded into system-config.h by the MHC.
Interrupts
- When the Interrupt mode is selected, the MHC will cause the interrupt vector to be placed in interrupt.c.
- The Interrupt Service Routine (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. The 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 Application Programming Interfaces (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 variables are created by the 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 ( PortD 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, described in the "Hardware Environment" section, 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 */
}
}