I2C Example - PalouseRobosub/SUBLIBinal GitHub Wiki

I2C Example

This page contains information describing the I2C example program. All example files can be located within the example directory in the repository.

/********************************************************
 *   File Name: i2c_ex.c
 *
 *   Description:
 *              Main file
 *
 *
 *********************************************************/

/*************************************************************************
 System Includes
 ************************************************************************/
#include "sublibinal.h"
#include "sublibinal_config.h"

//forward declarations
void timer_callback(void);
void i2c_callback(I2C_Node node);

//global variables
uint8 sensor_data[6]; //buffer for sensor data to be stored

/*************************************************************************
 Main Function
 ************************************************************************/
int main(void) {

    //buffer for uart ISRs
    uint8 uart_tx_buffer[128], uart_rx_buffer[128];
    uint8 i2c_tx_buffer[128], i2c_rx_buffer[128];

    //structures for configuring peripherals
    UART_Config uart_config = {0};
    Timer_Config timer_config = {0};
    Packetizer_Config packet_config = {0};
    I2C_Config i2c_config = {0};
    
    //temp buffer
    uint8 blah;
    
    //setup peripherals
    timer_config.frequency = 1000; //Set the timer to 1KHz
    timer_config.pbclk = PB_CLK; //Specify the speed of the peripheral bus clock
    timer_config.which_timer = Timer_1; //Specify timer 1 as our selected device
    timer_config.callback = &timer_callback; //Specify the timer callback function
    timer_config.enabled = 1; //enable the timer
    initialize_Timer(timer_config); //Initialize the timer with our specifications

    uart_config.which_uart = UART_CH_1; //Specify uart channel 1 for our use
    uart_config.pb_clk = PB_CLK; //tell the module the clock speed
    uart_config.speed = 115200; //inform the module of the desired baud rate
    uart_config.tx_buffer_ptr = uart_tx_buffer; //Provide a pointer to a data buffer
    uart_config.tx_buffer_size = sizeof(uart_tx_buffer); //provide the size of the data buffer
    uart_config.tx_en = 1; //enable the uart
    uart_config.tx_pin = Pin_RPB15; //select the TX pin

    packet_config.control_byte = 0x0A; //set the control byte for the packetizer
    packet_config.which_channel = PACKET_UART_CH_1; //specify use of UART channel 1 for the packetizer
    packet_config.uart_config = uart_config; //specify the UART configuration to reference
    packet_config.callback = NULL; //Set there to be no callback
    initialize_packetizer(packet_config); //Initialize the packetizer module
    
    i2c_config.callback = NULL; //No I2C callback function
    i2c_config.channel = I2C_CH_1; //Using I2C channel 1
    i2c_config.pb_clk = PB_CLK; //specify the peripheral bus clock
    i2c_config.rx_buffer_ptr = i2c_rx_buffer; //provide a pointer to rx buffer
    i2c_config.rx_buffer_size = sizeof(i2c_rx_buffer); //provide size of rx buffer
    i2c_config.tx_buffer_ptr = i2c_tx_buffer; //provide a pointer to the tx buffer
    i2c_config.tx_buffer_size = sizeof(i2c_tx_buffer); //provide the size of the tx buffer
    initialize_I2C(i2c_config); //initialize the I2C
 
    //Global interrupt enable. Do this last!
	enable_Interrupts();

    while (1) {
        //put background processes here
        bg_process_I2C();
    }

    return 0;
}

void timer_callback(void)
{
    I2C_Node node = {0};

    node.device_id = 0x01; //arbitrary-ish device ID for clarification of data results
    node.device_address = 0x45; //address of the sensor that we are talking to
    node.sub_address = 0x12; //sub address on sensor to read in the sensors memory
    node.data_buffer = sensor_data; //provide a buffer to some data for the sensor, data must remain in scope!!!
    node.data_size = 6; //reading 6 bytes from the sensor
    node.mode = READ; //we are reading and not writing
    node.callback = &i2c_callback; //provide a callback for the data

    send_I2C(I2C_CH_1, node); //enqueue a read of the device on the I2C
}

void i2c_callback(I2C_Node node)
{
    uint8 send_data[7];
    uint8 i;

    //form data to go in the packet
    send_data[0] = node.device_id;
    for(i = 1; i < 7; ++i)
    {
        send_data[i] = node.data_buffer[i-1];
    }    

    //send the packet
    send_packet(PACKET_UART_CH_1, send_data, sizeof(send_data));
}

In this program, a timer specifies the rate at which an external sensor is read using the I2C protocol. When the I2C transaction has finished, the sensor data is transmitted upon the UART in a packetized format.

There are a number of important subsections of this code that will be described on. Other peripherals not pertaining to I2C have been omitted. Please refer to other peripheral examples for an in-depth description of their functionality.

    
    i2c_config.callback = NULL; //No I2C callback function
    i2c_config.channel = I2C_CH_1; //Using I2C channel 1
    i2c_config.pb_clk = PB_CLK; //specify the peripheral bus clock
    i2c_config.rx_buffer_ptr = i2c_rx_buffer; //provide a pointer to rx buffer
    i2c_config.rx_buffer_size = sizeof(i2c_rx_buffer); //provide size of rx buffer
    i2c_config.tx_buffer_ptr = i2c_tx_buffer; //provide a pointer to the tx buffer
    i2c_config.tx_buffer_size = sizeof(i2c_tx_buffer); //provide the size of the tx buffer
    initialize_I2C(i2c_config); //initialize the I2C
 

In this code section, we fill out the I2C configuration structure and then initialize the I2C peripheral using the initialization function. We specify that there is no callback. This is because callbacks for I2C should be specified with an I2C node and not the I2C module itself. A callback function is not needed to be implemented within the I2C module configuration structure. We then provide the peripheral bus clock speed and pointers to the RX and TX buffers, along with their respective sizes. These buffers must remain in scope throughout execution of the program. Any modification of the contents of these buffers during program execution can cause a malfunction of the I2C module.


//global variables
uint8 sensor_data[6]; //buffer for sensor data to be stored

//...

void timer_callback(void)
{
    I2C_Node node = {0};

    node.device_id = 0x01; //arbitrary-ish device ID for clarification of data results
    node.device_address = 0x45; //address of the sensor that we are talking to
    node.sub_address = 0x12; //sub address on sensor to read in the sensors memory
    node.data_buffer = sensor_data; //provide a buffer to some data for the sensor, data must remain in scope!!!
    node.data_size = 6; //reading 6 bytes from the sensor
    node.mode = READ; //we are reading and not writing
    node.callback = &i2c_callback; //provide a callback for the data

    send_I2C(I2C_CH_1, node); //enqueue a read of the device on the I2C
}

The first important point is to notice that sensor_data is declared as a global variable. This is to ensure that the data does not go out of scope. The next point to notice is that an I2C Node structure is created within the callback. The provided device_id is used for end-user differentiation of I2C Nodes within the I2C node callback function. The device address is specified in external device data sheets and determines which I2C device should be talked to. The sub_address parameter specifies the location in device specific memory that should be read. The data_buffer parameter is a pointer to where the data will be placed after the I2C transaction has taken place. The data_size parameter specifies the size in bytes that sensor_data points to. We also tell the node that this will be a read and will utilize the i2c_callback function. Finally, we enqueue the node in the I2C work buffer by calling the send_I2C function.

The next portion of code that is of importance is the i2c callback function.

void i2c_callback(I2C_Node node)
{
    uint8 send_data[7];
    uint8 i;

    //form data to go in the packet
    send_data[0] = node.device_id;
    for(i = 1; i < 7; ++i)
    {
        send_data[i] = node.data_buffer[i-1];
    }    

    //send the packet
    send_packet(PACKET_UART_CH_1, send_data, sizeof(send_data));
}

In this portion of code, the first piece of information to make note of is the definition of the function, void i2c_callback(I2C_Node node). The callback function does not return any values and accepts a single parameter of an I2C_Node structure. This node is actually the node that contains a pointer to the callback function. We then take the data that has been placed into the data buffer and construct a packet to be transmitted across the UART.

    while (1) {
        //put background processes here
        bg_process_I2C();
    }

This is the last portion of the program that is important. The background process for the I2C is crucial. The background process removes nodes from the results buffer of the I2C and calls the callback functions that the nodes have. If no callback function is specified within the node, all data within the node is lost. The callback function must be in the embedded loop to be able to remove nodes from the buffer and process the data.