Go back to the examples list

I2C simple transfer

This simple example demonstrates how to communicate with an I2C bus between two microcontrollers (here with Carbides).

Here is the wiring to follow :

wiring_schema

The master is on the left and the slave on the right. They must share the ground potential and their SDA (PB00) and SCL (PB01) pins must be connected. These lines must also be pulled-up by 2.2k resistors. These pull-ups are connected to the master's Vcc, which can also be shared with the slave (in this case, make sure to not turn on the slave to avoid electrical conflicts, it will be powered by the master).

The master and the slave each have their own code :

Master

master.cpp [download]
wget https://libtungsten.io/static/code/examples/i2c_simple_transfer/i2c_master/i2c_master.cpp

#include <core.h>
#include <gpio.h>
#include <i2c.h>
#include <carbide.h>

// I2C address of the slave
const uint8_t ADDR = 0x10;

// I2C port
const I2C::Port PORT = I2C::Port::I2C1;

// Input and output buffers
const int BUFFER_SIZE = 10;
uint8_t bufferIn[BUFFER_SIZE];
uint8_t bufferOut[BUFFER_SIZE];

// Every time the user presses the button, send some data to the I2C slave
// at the specified address and ask for an anwser
int main() {
    // Initialize the microcontroller
    Carbide::init();

    // Enable the I2C port in Master mode
    I2C::enableMaster(PORT);

    // Initial value of the counter
    uint8_t counter = 0x80;

    while (true) {

        // Fill the output buffer with dummy data
        for (int i = 0; i < BUFFER_SIZE; i++) {
            bufferOut[i] = counter;
            counter++;
        }

        // Wait until the user presses the button
        while (!Carbide::buttonRisingEdge());

        // Send the content of the output buffer to the slave
        I2C::write(PORT, ADDR, bufferOut, BUFFER_SIZE);

        // Ask the slave for an answer and store it in the input buffer
        I2C::read(PORT, ADDR, bufferIn, BUFFER_SIZE); 
    }
}

Makefile [download]
wget https://libtungsten.io/static/code/examples/i2c_simple_transfer/i2c_master/Makefile

NAME=i2c_master

BOOTLOADER=true
CARBIDE=true

# Available modules : adc dac eic gloc i2c spi tc trng usart
# Some modules such as gpio and flash are already compiled by default
# and must not be added here.
MODULES=i2c

# The toolchain's bin/ path, don't forget to customize it
# If this directory is already in your PATH, comment this line.
TOOLCHAIN_PATH=/opt/arm-none-eabi/bin/

# Include the main lib makefile
include libtungsten/Makefile

Slave

slave.cpp [download]
wget https://libtungsten.io/static/code/examples/i2c_simple_transfer/i2c_slave/i2c_slave.cpp

#include <core.h>
#include <gpio.h>
#include <i2c.h>
#include <carbide.h>

// I2C address of the slave
const uint8_t ADDR = 0x10;

// I2C port
const I2C::Port PORT = I2C::Port::I2C1;

// Input and output buffers
const int BUFFER_SIZE = 10;
uint8_t bufferIn[BUFFER_SIZE];
uint8_t bufferOut[BUFFER_SIZE];
int availableOutput = 0;

// Callback called with the asynchronous I2C::read() has finished,
// meaning that the master sent some data which has been stored in
// the input buffer
void onI2CReadFinished() {
    // Arbitrarily fill the output buffer based on the received content
    // At this stage I2C::getAsyncReadCursor() returns the number of 
    // bytes that has been stored in the input buffer
    int n = I2C::getAsyncReadCounter(PORT);
    for (int i = 0; i < n; i++) {
        bufferOut[i] = 0xFF - bufferIn[i];
    }

    // Update the content of the output buffer
    availableOutput = n;
    I2C::write(PORT, bufferOut, availableOutput, true);

    // Start a new read sequence to receive the next frame of data
    I2C::read(PORT, bufferIn, BUFFER_SIZE, true);
}

// Callback called with the asynchronous I2C::write() has finished,
// meaning that the master has asked for some data and the content
// of the output buffer has been sent
void onI2CWriteFinished() {
    // Send the same output buffer again the next time the master
    // asks for some data
    I2C::write(PORT, bufferOut, availableOutput, true);
}

// Start I2C slave callbacks to asynchronously send and receive some data
// from a master
int main() {
    // Initialize the microcontroller
    Carbide::init();

    // Enable the I2C port in Slave mode
    I2C::enableSlave(PORT, ADDR);

    // Register a callback to be triggered when an asynchronous read has finished
    I2C::enableInterrupt(PORT, onI2CReadFinished, I2C::Interrupt::ASYNC_READ_FINISHED);

    // Initialize a read into the input buffer. This is necessary to be able to correctly receive
    // the first frame the master will send, and to tell the driver in which buffer to store it.
    // The next reads will be started inside the onI2CReadFinished() callback registered above.
    I2C::read(PORT, bufferIn, BUFFER_SIZE, true);

    // Register a callback to be triggered when an asynchronous write has finished
    I2C::enableInterrupt(PORT, onI2CWriteFinished, I2C::Interrupt::ASYNC_WRITE_FINISHED);

    // There is no need to initialize a write from the output buffer, because it does not
    // contain meaningful data yet. If the master requests some data the driver will automatically
    // send as many padding bytes (0xFF) as requested.

    // Act like we're busy
    bool ledState = false;
    while (true) {
        Carbide::setLedR(ledState);
        ledState = !ledState;
        Core::sleep(300);
    }
}

Makefile [download]
wget https://libtungsten.io/static/code/examples/i2c_simple_transfer/i2c_slave/Makefile

NAME=i2c_slave

BOOTLOADER=true
CARBIDE=true

# Available modules : adc dac eic gloc i2c spi tc trng usart
# Some modules such as gpio and flash are already compiled by default
# and must not be added here.
MODULES=i2c

# The toolchain's bin/ path, don't forget to customize it
# If this directory is already in your PATH, comment this line.
TOOLCHAIN_PATH=/opt/arm-none-eabi/bin/

# Include the main lib makefile
include libtungsten/Makefile