In this example, bare metal I2C drivers for the MSSP peripheral on the PIC16F15244 family of MCUs will be created.
To run this project, a PIC16F15243/PIC16F15244/PIC16F15245 is required, but other devices with an MSSP should also function with adjustments to the pin assignments.
This project was tested and built on a PIC16F15244 Curiosity Nano.
Alternatively, these tools should also be compatible:
Important! External Pull-up resistors were used. Internal Pull-up resistors can be enabled by uncommenting #define USE_INTERNAL_PULLUPS
in either mssp1_client.h
or mssp1_host.h
.
To use the I2C host driver, an I2C client device is required. This can be created using another MCU in client mode, or by using a MikroElektronika (mikroE) Click adapter board. A number of compatible Click boards are available from mikroE.
For testing, a PIC16F15245 running the Advanced I/O Expander example was used to test host mode operation.
To use the I2C client driver, a device capable of generated I2C host communication is required. This can be another MCU configured as a client, or a stand-alone device, such as an MCP2221A USB-I2C Breakout Module (ADM00559), which was used for testing.
This example uses pins RB4 and RB6 for I2C communication. These are the default pins used on the Curiosity Nano Adapter board for I2C.
Pin | Function |
---|---|
RB4 | SDA |
RB6 | SCL |
The I2C Host Driver is a polled driver that can initiate communication with I2C clients. This driver is composed of 2 files: mssp1_host.h and mssp1_host.c
The driver is initialized by calling the functions:
//Init I/O Settings
initI2CPins();
//Init MSSP Module in I2C Host Mode
MSSP_HostInit();
There are 2 functions that write data to the client device:
bool MSSP_WriteByte(uint8_t deviceADDR, uint8_t data);
bool MSSP_WriteBlock(uint8_t deviceADDR, uint8_t* blockMem, uint8_t memSize);
The MSSP_WriteByte
function sends the 7-bit address in deviceADDR
to the client. If the client ACKs this address, then data
is sent.
Similiarly, the MSSP_WriteBlock
function sends the 7-bit address in deviceADDR
to the client. If the client ACKs this address, then memSize
bytes are sent from blockMem
.
If the client does not ACK, then the function returns false (and no data is sent).
There are 3 functions that are designed to read data from the client.
bool MSSP_RegisterSelectAndRead(uint8_t deviceADDR, uint8_t registerADDR, uint8_t* blockMem, uint8_t memSize);
bool MSSP_ReadByte(uint8_t deviceADDR, uint8_t* data);
uint8_t MSSP_ReadByteNoWarn(uint8_t deviceAddr);
bool MSSP_ReadBlock(uint8_t deviceADDR, uint8_t* blockMem, uint8_t memSize);
MSSP_RegisterSelectAndRead
is a special function in this driver. Unlike the other read functions, this one starts by starting an I2C write to the device at deviceADDR
. Then, registerADDR
is written to the device to select the address to read from. After writing that byte, the I2C bus is restarted, and the client is readdressed, but in read mode. memSize
bytes of data are read from the device and stored in blockMem
.
If the client NACKs at either of the addressing steps in this function, the operation will be aborted (and the function will return false).
bool MSSP_ReadByte(uint8_t deviceADDR, uint8_t* data);
uint8_t MSSP_ReadByteNoWarn(uint8_t deviceADDR);
bool MSSP_ReadBlock(uint8_t deviceADDR, uint8_t* blockMem, uint8_t memSize);
These functions all operate on a nearly identical basis. A client device is addressed with deviceADDR
in read mode. Then, either a single byte or memSize
bytes of data are read from the device.
No read occurs if the client NACKs communication. Boolean functions return false in this case. The function MSSP_ReadByteNoWarn
is a wrapper for the function MSSP_ReadByte
. If the client NACKs, then 0x00 is returned, rather than true or false values.
Function Definition | Description |
---|---|
void initI2CPins(void) | Initializes the I2C pins for the MSSP. Uses RB4 and RB6. |
void MSSP_HostInit(void) | Initializes the driver in I2C host mode. |
bool MSSP_RegisterSelectAndRead(uint8_t deviceADDR, uint8_t registerADDR, uint8_t* blockMem, uint8_t memSize) | Addresses a client device at deviceADDR , writes 1 byte of data (registerADDR ), RESTARTs the bus, readdresses the client device, and reads memSize bytes back. Returns true if successful or false if the client NACKed. |
bool MSSP_WriteByte(uint8_t deviceADDR, uint8_t data) | Addresses a client device at deviceADDR and writes 1 byte (data ) to the client. Returns true if successful or false if the client NACKed. |
bool MSSP_ReadByte(uint8_t deviceADDR, uint8_t* data) | Addresses a client device at deviceADDR and reads a single byte of data from this device into data . Returns true if successful or false if the client NACKed. |
uint8_t MSSP_ReadByteNoWarn(uint8_t deviceADDR) | Addresses a client device at deviceADDR and returns a single byte of data read from the device. If the client NACKed, the byte returned will be 0x00. |
bool MSSP_WriteBlock(uint8_t deviceADDR, uint8_t* blockMem, uint8_t memSize) | Addresses a client device at deviceADDR and writes memSize bytes of data from blockMem to it. Returns true if successful or false if the client NACKed. |
bool MSSP_ReadBlock(uint8_t deviceADDR, uint8_t* blockMem, uint8_t memSize) | Addresses a client device at deviceADDR and reads memSize bytes from the client into blockMem . Returns true if successful or false if the client NACKed. |
I2C clients are devices that respond to a read/write request from an I2C host. Since a host does not communicate continuously, interrupt driven operation is crucial for a client device.
This driver can be used either in byte mode or block mode (via an included middleware library).
Byte Driver ⇆ Application Code
Byte Driver ⇆ Block Memory Middleware ⇆ Application Code
Byte mode is designed to call a user-defined function for all byte read/write events and for the I2C stop event. If one of these user defined functions is not set (or set to NULL), the driver will perform a default action, as shown below, to maintain functionality.
Event | Default Action |
---|---|
Read | Writes 0x00 to the Host |
Write | Discards Received Byte |
Stop | Does Nothing |
To write a function that can be called for these events, they must have the following definition:
Event | Required Function Definition |
---|---|
Read | uint8_t myReadFunction(void) |
Write | void myWriteFunction(uint8_t data) |
Stop | void myStopFunction(void) |
Like all interrupt handlers, the functions associated should be as small as possible and non-blocking.
Note: For Read Events, n + 1 events will occur, due to the host ACKing the communication before the STOP event. The block mode driver corrects it's index for this event.
Function Definition | Description |
---|---|
void initI2CPins(void) | Initializes the I2C pins for the MSSP. Uses RB4 and RB6. |
void MSSP_ClientInit(uint8_t address) | Initializes the driver in I2C client mode. |
void _MSSP_ProcessInterrupt(void) | This function is called from the ISR function to process MSSP interrupts. Do not call this function. |
void assignMSSPByteWriteHandler(void (*writeHandler)(uint8_t)) | This sets the function that is called for I2C write events. |
void assignMSSPByteReadHandler(uint8_t (*readHandler)(void)) | This sets the function that is called for I2C read events. |
void assignMSSPStopHandler(void (*stopHandler)(void)) | This sets the function that is called for I2C stop events. |
Block mode simplifies development by implementing block memory transfers, with support for both incremental transfers (byte 0, 1, 2, etc...) and addressed transfers (set index to 4, RESTART/STOP, read 4, read 5, read 6, etc...).
To disable addressed transfers, comment out #define FIRST_BYTE_ADDR
in mssp1_blockData.h.
To use the block mode middleware, it must be attached to the byte mode driver, shown below:
//Init the I2C Pins on the Device
initI2CPins();
//Initialize the I2C Driver
MSSP_ClientInit(0x60);
//Assign ISRs for block mode operation
assignMSSPByteWriteHandler(&_MSSP_StoreByte);
assignMSSPByteReadHandler(&_MSSP_RequestByte);
assignMSSPStopHandler(&_onMSSPStop);
The functions _MSSP_StoreByte
, _MSSP_RequestByte
, and _onMSSPStop
are functions provided in the library. These functions are responsible for storing and retrieving bytes of data from the user defined memory blocks. The stop function (_onMSSPStop
) is used to adjust (or reset) the memory access index.
Note: In the event that the memory blocks are not initialized, or a read/write overflow occurs, the driver will discarded any further received bytes (write) or will return 0x00 to the driver (read).
This library supports separated read and write memory blocks for I2C with varying sizes between each block. The functions in the library verify the I2C does not exceed the size of each memory block.
Two functions are provided to set the blocks, as shown below.
//Setup a Read Buffer
setupReadBuffer(&buffer[0], 8);
//Setup a Write Buffer. (For testing, set to smaller than read.)
setupWriteBuffer(&buffer[0], 4);
In the above example, both the read and write buffers point to the same memory pool, however separate arrays can also be used. If the write function should only modify a select number of values, the address of the buffer can be modified as such:
//Setup a Read Buffer
setupReadBuffer(&buffer[0], 8);
//Setup a Write Buffer
setupWriteBuffer(&buffer[4], 4);
In this configuration, memory writes will occur from the 4th byte to the 7th byte, rather than starting at 0 and going to 3.
Function Definition | Description |
---|---|
void _MSSP_StoreByte(uint8_t data) | Called by the byte mode driver to handle bytes received. Do not call this function. |
uint8_t _MSSP_RequestByte(void) | Called by the byte mode driver to get the next byte to send. Do not call this function. |
void _onMSSPStop(void) | Called by the byte mode driver on an I2C stop to adjust or reset the memory indexes. Do not call this function. |
void setupReadBuffer(volatile uint8_t* buffer, uint8_t size) | This function sets the read buffer to SEND data from the client to the host. |
void setupWriteBuffer(volatile uint8_t* buffer, uint8_t size) | This function sets the write buffer to RECEIVE data from the host. |
This example provides a simple bare-metal I2C driver for the MSSP peripheral to integrate into other projects.