This class represents the I2C devices, e.g.: an LCD panel hooked up to the MCU’s I2C bus. It is built on top of the JSAPI_I2C_WIRE class. An I2C bus can have multiple masters and multiple slaves. All messages originate from a master node and address one of the slave nodes. The hardware layer supports arbitration among the master nodes. In an embedded system, the microcontroller is usually the (sole) I2C master.

I2C runs at standard speeds of 10 kHz, 100 kHz, and 400 kHz, although some devices implement a fast mode of 1 MHz bit rate. I2C is commonly used for low-speed external modules such as LCD, RTC (Real Time Clock), extra I/O expansion, etc.


1.       Only I2C master mode is supported currently.

2.       Some slave devices, e.g. EEPROM, may require a delay (e.g. 5-10 milliseconds) after a memory write for the write to complete.

To demonstrate the I2C features, you need to attach additional hardware to the ST-Nucleo. The provided example assumes the JumpStart MicroBox is being used, with an EEPROM being the target I2C slave device.


enum I2C_SPEED {






Make an I2C device

void MakeI2C(

    JSAPI_I2C_WIRE *i2c_wire,

    unsigned slave_address_7bits,

    enum I2C_SPEED ispeed,

    _Bool _2byte_command


Acquire the I2C bus

int AcquireI2C(void);

Release the I2C bus

int ReleaseI2C(void);

Read an I2C transaction with default address. It does a complete I2C transactions, generating         START(/RESTART) - data0... datan - STOP.

int ReadWithDefaultAddress(

    unsigned char *data,

    int len


Read an I2C transaction with specified address

int Read(

    unsigned command,

    unsigned char *data,

    int len


Write a stream of date with specified address. It does a complete I2C transaction, generating

START - data0... datan - STOP.

int Write(

    unsigned command,

    unsigned char *data,

    int len


Interrupt-driven read

int AsyncRead(

    unsigned command,

    int (*readHandler)(unsigned char),

    _Bool *status


Interrupt-driven write

int AsyncWrite(

    unsigned command,

    int (*writeHandler)(unsigned char *data),

    _Bool *status



How to Use

To use the JumpStart I2C API:

1.       Associate GPIO pins with a JSAPI_I2C_WIRE (e.g. i2c1 or i2c2) using the JSAPI_I2C_WIRE object’s SetPins method, e.g.

i2c1.SetPins(&portb, 8, 4, &portb, 9, 4);

2.       Define a JSAPI_I2C object, e.g.

JSAPI_I2C i2c_eeprom;

3.       Call the JSAPI_I2C object’s MakeI2C method with the JSAPI_I2C_WIRE object and other I2C parameters such as the device slave address, the I2C bus speed, and whether the slave device uses 8-bit or 16-bit commands. See below for further explanation.

4.       For polled-mode read and write, call the JSAPI_I2C object’s Read and Write methods and bracket the transactions with the AcquireI2C and ReleaseI2C calls.

5.       For interrupt-driven read and write, use the AsyncRead and AsyncWrite method calls.

Each I2C slave device has a “slave” address, as noted in its documentation. Depending on the vendor, sometimes the document uses a 7-bit address and sometimes it uses an 8-bit address format [4]. With JumpStart API, you use the 7-bit address format. There are also I2C devices that support 10-bit addresses. These are not currently supported by the JumpStart API.

When reading from an I2C slave, the “command” is usually either an address (e.g. for an I2C memory address, it would be the memory location you wish to access), or a command specific to the slave device (e.g. perhaps a command to enable graphic mode on an I2C LCD panel). With memory I2C devices, usually they maintain an internal address pointer such that it can perform additional read from incrementing addresses. JumpStart API implements this via the ReadWithDefaultAddress function.

When you communicate to the I2C device using polled (“normal”) read/write commands, you must first call AcquireI2C to gain access to the I2C bus. When you are done with the device, you call ReleaseI2C to release the I2C bus. Note that the acquiring and releasing calls only affect the internal state within the I2C_WIRE object. This allows different I2C devices to use different bus speeds, and also if you are using an RTOS, this scheme allows you to access different I2C devices from different tasks.

Interrupt-Driven I2C Read/Write

JumpStart API also provides interrupt-driven I2C read/write API. This allows the slower I2C devices to work independently with the user program. With the AsyncRead and AsyncWrite routines, you respectively supply a read handler and a write handler function, which will be called whenever a byte is read from or needs to be written to the I2C bus. Please see the Doxygen API document for details. Note that with asynchronous read/write I2C functions, you do not need to call the AcquireI2C/ReleaseI2C functions.


Here are some example code fragments demonstrating the basic I2C functions. Note that the interrupt-driven code fragments busy-wait until the read or write is finished, which removes the advantage of asynchronous access. In real code, the program would go on and perform other tasks while the I2C transactions are happening separately.

// Initial setup

    i2c1.SetPins(&portb, 8, 4, &portb, 9, 4);


    int stat;

    JSAPI_I2C i2c_memory;


    i2c_memory.MakeI2C(&i2c1, 0b1010000, _400KHZ, true);


// Normal write


    unsigned char datebuf[DATEBUF_LEN];

    i2c_memory.Write(0x0, datebuf, DATEBUF_LEN);




// Normal read


    unsigned char barray[DATEBUF_LEN];

    i2c_memory.Read(0x0, barray, DATEBUF_LEN);



// Async write

    _Bool write_done = false;

    stat = i2c_memory.AsyncWrite(0, writeHandler, &write_done);

    printf("write stat %d\n", stat);

    while (!write_done)





// Async read

    _Bool read_done = false;

    stat = i2c_memory.AsyncRead(0, readHandler, &read_done);

    printf("read stat %d\n", stat);


    while (!read_done)