2.1 MCC Generated

To generate this project using MPLAB Code Configurator (MCC), follow the next steps:

  1. Create a new MPLAB X IDE project for PIC18F47Q10.
  2. Open MCC from the toolbar (more information on how to install the MCC plug-in can be found in the reference list).
  3. Go to Project Resources → System → System Module and do the following configuration:
    • Oscillator Select: HFINTOSC (default)
    • HF Internal Clock: 64 MHz (default)
    • Clock Divider: 1
    • In the Watchdog Timer Enable field in the WWDT tab, WDT Disabled has to be selected
    • In the Programming tab, Low-Voltage Programming Enable has to be checked
  4. From the Device Resources window, add Drivers → I2C → I2CSIMPLE, then do the following configuration:
    • I2C_host dependency selector: MSSP1
  5. Go to Project Resources → Drivers → MSSP → MSSP1, then do the following configuration:
    • Serial Protocol: I2C
    • Mode: Host
    • I2C Clock Frequency: 100000
  6. Go to the Pins Grid View, select UQFN40 in the MCU package field, and do the following pin configurations:
    Figure 2-3. Pin Mapping
  7. Go to Project Resources → System → Pin Module and set both pins, RB1 and RB2, to use the:
    • Internal pull-up, by checking the box in the Weak Pullup column.
    • Open drain, by checking the box in the open drain column.
    Ensure that for MSSP1, SCL is assigned to pin RB1 and SDA is assigned to RB2 in the pin manager grid view.
  8. In the Project Resources window, click the Generate button so that MCC will generate all the specified drivers and configurations.
  9. Edit the main.c file, as one of the following examples:

MCC Generated Example Codes

MSSP Driver Use Cases

Use Case Description

GPIO 8-bit I/O Expander

Setting up the I2C Client GPIO 8-bit I/O expander (MCP23008) as output and blinking its LEDs by writing 1 byte of data repeatedly.

10-bit DAC

Setting up the I2C Client 10-bit DAC (TC1321) and toggling its output between 0v and 2.5v by writing 2 bytes of data at a time.

9-12 bit Temperature Sensor

Setting up the I2C Client temperature sensor (MCP9800) resolution to 12-bits, then repeatedly read out its value.

EEPROM

Setting up the I2C Client EEPROM (24LC02B), storing a test set of data onto it, and repeatedly reading it back to the user. Using EUSART to display the data to the user.

GPIO 8-Bit I/O Expander

In this use case, the microcontroller is configured in I2C Host mode using the MSSP1 instance of the MSSP peripheral, and communicates with the client MCP23008, an 8-bit I/O expander that can be controlled through the I2C interface.

The extended pins are set as digital output with an I2C write operation in the client’s I/O Direction (IODIR) register.

After the pins are set, the program will repeatedly:

  • Set pins to digital low, with an I2C write operation in the GPIO register.
  • Set pins to digital high, with an I2C write operation in the GPIO register.
#include "mcc_generated_files/system/system.h"
#include "mcc_generated_files/i2c_host/i2c_host_types.h"
#include "mcc_generated_files/i2c_host/mssp1.h"

#define I2C_CLIENT_ADDR             0x20
#define MCP23008_REG_ADDR_IODIR     0x00
#define MCP23008_REG_ADDR_GPIO      0x09
#define PINS_DIGITAL_OUTPUT         0x00
#define PINS_DIGITAL_HIGH           0xFF
#define DATALENGTH                  2

const i2c_host_interface_t *I2C = &i2c1_host_interface;

int main(void)
{
    /* Initialize the device */
    SYSTEM_Initialize();
    __delay_ms(20);
    uint8_t data[DATALENGTH];
    
    /* Configure GPIO as output */
    data[0] = MCP23008_REG_ADDR_IODIR;
    data[1] = PINS_DIGITAL_OUTPUT;
    if (I2C->Write(I2C_CLIENT_ADDR, data, DATALENGTH))
    {
        while(I2C->IsBusy())
        {
            I2C->Tasks();
        }
        if (I2C->ErrorGet() == I2C_ERROR_NONE)
        {
            /* Write operation is successful */
        }
        else
        {
            /* Error handling */
        }
    }
    /* Write data to GPIO pins */
    data[0] = MCP23008_REG_ADDR_GPIO;
    data[1] = PINS_DIGITAL_HIGH;
    
    while(1)
    {
        if (I2C->Write(I2C_CLIENT_ADDR, data, DATALENGTH))
        {
            while(I2C->IsBusy())
            {
                I2C->Tasks();
            }
            if (I2C->ErrorGet() == I2C_ERROR_NONE)
            {
                /* Write operation is successful */
            }
            else
            {
                /* Error handling */
            }
            
            /* Toggle the output data */
            data[1] = ~data[1];
        } 
        /* Delay 1 second */
         __delay_ms(1000);
    }
}

10-Bit DAC

In this use case, the microcontroller is configured in I2C Host mode using the MSSP1 instance of the MSSP peripheral, and communicates with the client TC1321, a 10-bit DAC that can be controlled through the I2C interface.

The program will repeatedly:
  • Write data to the DAC which toggles between min(0V) and max(2.5V) output. The output can be measured with a multimeter.
#include "mcc_generated_files/system/system.h"
#include "mcc_generated_files/i2c_host/i2c_host_types.h"
#include "mcc_generated_files/i2c_host/mssp1.h"


#define I2C_CLIENT_ADDR 0x48 /* 7-bit Address */
#define TC1321_REG_ADDR 0x00
#define DATA_HIGH       0xFF
#define DATALENGTH      3

const i2c_host_interface_t *I2C = &i2c1_host_interface;


void main(void)
{
    /* Initialize the device */
    SYSTEM_Initialize();
    
    uint8_t data[DATALENGTH];
    data[0] = TC1321_REG_ADDR;
    data[1] = DATA_HIGH;
    data[2] = DATA_HIGH;
    
    while (1)
    {
        /* Write to DATA REGISTER in TC1321 */
        if (I2C->Write(I2C_CLIENT_ADDR, data, DATALENGTH))
        {
            while(I2C->IsBusy())
            {
                I2C->Tasks();
            }
            if (I2C->ErrorGet() == I2C_ERROR_NONE)
            {
                /* Write operation is successful */
            }
            else
            {
                /* Error handling */
            }
            
            /* Toggle the output data */
            data[1] = ~data[1];
            data[2] = ~data[2];  
        }
        /* Delay 1 second */
        __delay_ms(1000);
    }
}

9 to 12-Bit Temperature Sensor

In this use case, the microcontroller is configured in I2C Host mode using the MSSP1 instance of the MSSP peripheral, and communicates with the client MCP9800, a 9 to 12-bit temperature sensor that can be controlled through the I2C interface.

The program will:
  • Set the resolution of the temperature sensor to 12-bit with an I2C write operation in the client’s Configuration register.
After the resolution is set, the program will repeatedly:
  • Read the temperature, with an I2C read operation in the temperature registers.
#include "mcc_generated_files/system/system.h"
#include "mcc_generated_files/i2c_host/i2c_host_types.h"
#include "mcc_generated_files/i2c_host/mssp1.h"


#define I2C_CLIENT_ADDR                 0x49            /* 7-bit address */
#define MCP9800_REG_ADDR_CONFIG         0x01
#define MCP9800_REG_ADDR_TEMPERATURE    0x00
#define CONFIG_DATA_12BIT_RESOLUTION    0x60
#define RESOLUTION                      12
#define REGISTER_SIZE                   16
#define RIGHT_SHIFT_VALUE (REGISTER_SIZE - RESOLUTION)
#define DATALENGTH                      2

const i2c_host_interface_t *I2C = &i2c1_host_interface;

void main(void)
{
    /* Initialize the device */
    SYSTEM_Initialize();
    
    /* Declare variables */
    uint8_t     dataWrite[2];
    uint8_t     dataRead[2];
    uint16_t    rawTempValue;
    float       tempCelsius;
    
    /* Set the resolution to 12-bits */
    dataWrite[0] = MCP9800_REG_ADDR_CONFIG;
    dataWrite[1] = CONFIG_DATA_12BIT_RESOLUTION;
    
    if (I2C->Write(I2C_CLIENT_ADDR, dataWrite, DATALENGTH))
    {
        while(I2C->IsBusy())
        {
            I2C->Tasks();
        }
        if (I2C->ErrorGet() != I2C_ERROR_NONE)
        {
            /* Write operation is successful */
        }
        else
        {
            /* Error handling */
        }
    }
    
    
    dataWrite[0] = MCP9800_REG_ADDR_TEMPERATURE;
    while (1)
    {
        /* Read out the 12-bit raw temperature value */
        if (I2C->WriteRead(I2C_CLIENT_ADDR, dataWrite, DATALENGTH, dataRead, DATALENGTH))
        {
            while(I2C->IsBusy())
            {
                I2C->Tasks();
            }
            if (I2C->ErrorGet() != I2C_ERROR_NONE)
            {
                /* Write operation is successful */
            }
            else
            {
                /* Error handling */
            }
        }
        
        /* Combine the two 8-bit values into on 16-bit value */
        rawTempValue = (dataRead[0] << 8 | dataRead[1]);
        rawTempValue = rawTempValue >> RIGHT_SHIFT_VALUE;     
        
        /* Convert the raw temperature data to degrees Celsius according to the MCP9800 data sheet */
        tempCelsius = (float) (rawTempValue / 16.0);
        __delay_ms(500);
    }
}

EEPROM

In this use case, the microcontroller is configured in I2C Host mode using the MSSP1 instance of the MSSP peripheral, and communicates with the client 24LC02B, an 256B EEPROM that can be read from and written to through the I2C interface.

The program will:
  • Make a test array of arbitrary size
  • Write that array onto the EEPROM
  • Then repeatedly read back the data

To follow this code example we need to add the EUSART peripheral in the MPLAB Code Configurator (MCC). Follow the next steps:

  1. Open MCC from the toolbar
  2. From the Device Resources window, add Driver → EUSART→ EUSART2
  3. Go to Project Resources → Drivers → EUSART → EUSART2, then do the following configuration:
    • Redirect STDIO to UART: On
    • Mode: Asynchronous 8-bit mode
    • Baud Rate: 9600
    • Enable Transmit: On
    • Stop Bit Mode: Transmit 1 Stop bit, receiver verifies first Stop bit
  4. Go to the Pins Grid View, select UQFN40 in the MCU package field, and do the following pin configurations:
    Figure 2-4. Pin Mapping
  5. In the Project Resources window, click the Generate button so that MCC will generate all the specified drivers and configurations
#include "mcc_generated_files/system/system.h"
#include "mcc_generated_files/i2c_host/i2c_host_types.h"
#include "mcc_generated_files/i2c_host/mssp1.h"


#define MIN(x,y) (((x)<(y)) ? (x) : (y))
#define I2C_EEPROM_ADDR 0x50 /* 7-bit address */
#define PAGESIZE 8
#define TEST_SET_SIZE 20


/* Defining functions */
static uint8_t I2C1_writeNBytesEEPROM(uint8_t address, uint8_t memoryAddress, uint8_t*
data, uint8_t dataLength, uint8_t EEPROMPagesize);

/* Declare variables */
const i2c_host_interface_t *I2C = &i2c1_host_interface;
uint8_t dataWrite[TEST_SET_SIZE];
uint8_t dataRead[TEST_SET_SIZE];
uint8_t EEPROMMemoryRegister = 0x00;
uint8_t dataOperationSuccessful = 0;

int main(void)
{
    SYSTEM_Initialize();
    
    /* Constructing test data */
    for (uint8_t i = 0; i < TEST_SET_SIZE; i++)
    {
        dataWrite[i] = i;
    }
    
    /* Write N bytes of data to the EEPROM and return pointer to next EEPROM address*/
    EEPROMMemoryRegister = I2C1_writeNBytesEEPROM(I2C_EEPROM_ADDR, EEPROMMemoryRegister,
    dataWrite, TEST_SET_SIZE, PAGESIZE);
    
    if(!dataOperationSuccessful)
    {
        /* Handle error */
    }
    
    while(1)
    {
        /* Telling the EEPROM to read back from the beginning of the memory register */
        dataWrite[0] = 0x00;
        if (I2C->WriteRead(I2C_EEPROM_ADDR, dataWrite, 1, dataRead, 10))
        {
            while(I2C->IsBusy())
            {
                I2C->Tasks();
            }
            if (I2C->ErrorGet() == I2C_ERROR_NONE)
            {
                /* WriteRead operation is successful */
                printf("\r\n-----------------------------------");
                for (uint8_t i = 0; i < 10; i++)
                {
                    printf("\r\nValues read = %i", dataRead[i]);
                }
                printf("\r\n-----------------------------------");
            }
            else
            {
                /* Error handling */
                printf("\r\n Error Occurred");
            }
        }
        __delay_ms(5000);
    }
}

/* This function enables the user to write N bytes to an EEPROM without having to think about
pagesize and pagebuffer.
* However, it does not take care of end of memory space issues. E.g. what happens when we
try to write past the last memory address.
* Returns a value that corresponds to the last memory address written to */
static uint8_t I2C1_writeNBytesEEPROM(uint8_t address, uint8_t memoryAddress, uint8_t*
data, uint8_t dataLength, uint8_t EEPROMPagesize)
{
    uint8_t pageCounter = memoryAddress/EEPROMPagesize;
    uint8_t pageEnd = pageCounter + dataLength / EEPROMPagesize;
    uint8_t dataPerIteration = MIN(EEPROMPagesize - (memoryAddress%EEPROMPagesize),dataLength);
    uint8_t dataBuffer[8 + 1]; /* PAGESIZE + memoryAddress */
    /* Storing the memory pointer */
    dataBuffer[0] = memoryAddress;
    while(pageCounter <= pageEnd)
    {
        /* wait for page write buffer */
        __delay_ms(2);
        /* Loading the desired data onto the buffer */
        for (uint8_t i = 0; i < dataPerIteration; i++)
        {
            dataBuffer[i+1] = *data++;
        }
        
        dataOperationSuccessful = 1;
        /* Writing the memory address and data to EEPROM */
        if(I2C->Write(address, dataBuffer, dataPerIteration + 1))
        {
            while(I2C->IsBusy())
            {
                I2C->Tasks();
            }
            if (I2C->ErrorGet() == I2C_ERROR_NONE)
            {
                /* Write operation is successful */
            }
            else
            {
                /* Error handling */
                dataOperationSuccessful = 0;
            }
            
        }
        /* Updating variables for next iteration */
        dataLength -= dataPerIteration;
        dataBuffer[0] += dataPerIteration;
        dataPerIteration = MIN(EEPROMPagesize,dataLength);
        pageCounter++;
    }
    return dataBuffer[0];
}