File Protocol Mailslot Protocol navigation bar

Table of Contents > QUARC > User's Guide > QUARC Communications Protocols

I2C Protocol

The I2C protocol supports sending and receiving data over an Inter-Integrated Circuit (I2C) connection.

Syntax

i2c://hostname:port?option=value,... % Use I2C channel "port" with specified options
    

Description

The I2C protocol supports sending and receiving data over an I2C connection. It is identified by using i2c as the protocol name in a URI. Appropriate hardware supporting I2C must be present on the system using the protocol.

The hostname in the URI is ignored, if specified. The port is an unsigned integer indicating which I2C port to use. Port 0 is the first I2C port in the system.

The options for the I2C protocol depend on the underlying device-specific I2C protocol used, but these options have been standardized for consistency. The options listed below are the standard options. Some of the options may not be available on some platforms.

The I2C protocol uses a master-slave paradigm. It is a synchronous protocol in which the master supplies the clock, often dubbed SCL, and the slave uses that clock. The master transmits and receives data over the serial data (SDA) line. Multiple slaves may be connected and are selected by the master using a 7-bit or 10-bit slave address. The slave address is transmitted as part of the I2C protocol.

Slave Address

The slave address is specified through the address option of the I2C URI. Hence, each URI uniquely identifies a single slave device on the I2C bus attached to a particular port.

Therefore, each slave device requires its own communication channel and its own Stream Call block or Stream Connect block in QUARC or stream_connect call in MATLAB.

Hint

Unlike a serial port, more than one communication stream can be opened on the same I2C port. Each communication stream should have a unique slave address configured via the address option of its URI. The QUARC I2C Multiple Devices Demo example illustrates multiple devices sharing the same I2C port.

Note that the slave address is not the same as the port. Just as the port of a serial URI determines the COM port over which serial communications are performed, so the port of the I2C URI determines which I2C port will be used in systems that support more than one I2C port. If there are multiple I2C devices attached to one port then each one of those slave devices will have its own address on that port.

Slave addresses are generally predetermined by the manufacturer of the slave device and are assigned by NXP Semiconductors. In order to support more than one slave device of the same type, slaves usually allow some of the least-significant bits of the address to be set via pins on the device.

The I2C protocol is a two-wire protocol. There are no read or write lines because a read/write bit is transmitted along with the slave address as part of the I2C protocol. Handling of the slave address and read/write bit is all done internally by the I2C driver.

Baud Rate

The baud rate used by the I2C master is determined by the baud option of the URI. Standard baud rates for I2C are:

Baud Rate

Description

100 kHz

Original speed.

400 kHz

Fast mode (Fm).

1 MHz

Fast mode plus (Fm+).

3.4 MHz

High-speed mode (Hs).

5 MHz

Ultra-Fast mode (UFm). Requires USDA and USCL lines, which do not use pull-up resistors.

Most devices only support the original 100 kHz mode or the 400 kHz Fast mode.

Warning Data transmitted by QUARC over an I2C connection is always sent eight bits at a time, with an acknowledgement after each byte. The standard requires that the most-significant bit of each byte be transmitted first. When sending or receiving words consisting of more than one byte, the order of the bytes is slave-dependent. The standard byte-ordering options of the Stream API may be used to automatically re-order the bytes if necessary so that 16-bit or 32-bit words may be sent or received even when the byte-ordering of the slave device does not match the native byte-ordering of the QUARC target.

The byte ordering may be configured using the Byte ordering option of the Stream Call block, for example.

Writing to an I2C Device

A sample transaction on the I2C bus is shown below:

I2C Protocol

The transaction begins with a START bit (shaded green) and is followed by a 7-bit slave address and a direction bit (labelled R). For a write operation, the direction bit is set to 0 to indicate a write. The slave acknowledges (ACK) the receipt of the address and direction bit and then the master provides the first byte of data. This initial byte is typically the offset of a register within the slave device. The slave acknowledges the receipt of this byte and then the master sends the actual data byte that will be written to the selected register in the slave. This byte too is acknowledged by the slave and then a STOP bit (shaded red) is sent by the master to complete the transaction and release the I2C bus. Any number of bytes may be written per transaction before the STOP bit. When multiple bytes are written the slave typically auto-increments the initial register offset and writes each data byte to successive registers. The table below shows a typical multi-byte write transaction:

Step

0

1

2

3

4

5

6

7

8

9

Master

START

ADDR+W

 

REG

 

DATA

 

DATA

 

STOP

Slave

 

 

SACK

 

SACK

 

SACK

 

SACK

 

where:

START

=

The START bit which begins an I2C transaction on the bus or a repeated START bit which indicates a new slave address and direction bit being sent.

ADDR+W

=

The slave address (predetermined by device manufacturer and optional address pins) plus the write bit (0).

SACK

=

The slave acknowledgement of the byte received by the slave.

REG

=

The offset of the slave register to which to write the data.

DATA

=

Data to write to the slave register, or successive registers.

STOP

=

The STOP bit which ends the I2C transaction on the bus.

Such a write transaction can be performed using a Stream Write block, configured to minimize latency (so that it always flushes the underlying stream buffer to the I2C bus). The input to the Stream Write block would be a 3-vector of uint8 elements containing the offset of the slave register, REG, followed by the two data bytes i.e. uint8([REG DATA DATA]). In the C language, a stream_send, or one of its variants, followed by a stream_flush is required.

Reading from an I2C Device

A sample transaction on the I2C bus is again shown below:

I2C Protocol

The transaction begins with a START bit (shaded green) and is followed by a 7-bit slave address and a direction bit (labelled R). For a read operation, the direction bit is set to 1 to indicate a read. The slave acknowledges (ACK) the receipt of the address and direction bit and then the slave provides the first byte of data. The master acknowledges the receipt of this byte and then the slave sends the next byte of data. When the master receives the last byte it requires then it responds with a negative acknowledgement to indicate that the slave should not send any more data, and then a STOP bit (shaded red) is sent by the master to complete the transaction and release the I2C bus. Any number of bytes may be read per transaction before the STOP bit. A simple read operation in which a single byte is read is depicted in tabular form below:

Step

0

1

2

3

4

5

Master

START

ADDR+R

 

 

NMACK

STOP

Slave

 

 

SACK

DATA

 

 

or for reading multiple bytes in the same transaction:

Step

0

1

2

3

4

5

6

7

8

9

Master

START

ADDR+R

 

 

MACK

 

MACK

 

NMACK

STOP

Slave

 

 

SACK

DATA

 

DATA

 

DATA

 

 

where:

ADDR+R

=

The slave address (predetermined by device manufacturer and optional address pins) plus the read bit (1).

MACK

=

Master acknowledgement of the byte received by the slave. Indicates the master received the byte and expects more bytes from the slave.

NMACK

=

No master acknowledgement of the last byte received by the slave to signal the slave that no more bytes will be read.

These read transactions may be performed using a Stream Read block. For the single byte read shown above, the Output data type parameter of the Stream Read block would be set to uint8 or int8 and the Dimensions parameter would be set to 1. Multiple bytes or different data types could be received by changing these two parameters appropriately. Note that if data types other than boolean, int8 or uint8 are being read then the byte order (which byte is the most-significant) will be interpreted according to the Byte ordering parameter of the Stream Call or Stream Connect block that created the stream. In the C language, the stream_receive function, or one of its variants, is used to receive data from the stream. The byte ordering is configured using the stream_set_byte_order or stream_set_swap_bytes function.

For an example that uses both the Stream Write and Stream Read blocks to interface to an I2C device, refer to the QUARC I2C Temperature Sensor Demo.

Reading Using a Combined Message from an I2C Device

Reading from an I2C device is typically more complicated than the read transactions in the previous section. For many slave devices, reading from a register within the slave devices involves a write operation to tell the slave which register is being read, followed by a read operation, all combined into a single I2C transaction. A typical combined write-read transaction is depicted below:

Step

0

1

2

3

4

5

6

7

8

9

10

Master

START

ADDR+W

 

REG

 

START

ADDR+R

 

 

NMACK

STOP

Slave

 

 

SACK

 

SACK

 

 

SACK

DATA

 

 

or for reading multiple bytes in the same combined write-read transaction:

Step

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

Master

START

ADDR+W

 

REG

 

START

ADDR+R

 

 

MACK

 

MACK

 

NMACK

STOP

Slave

 

 

SACK

 

SACK

 

 

SACK

DATA

 

DATA

 

DATA

 

 

The key thing to note about these transactions is that the master has exclusive access to the I2C bus as long as the STOP bit has not been sent. Transmitting the STOP bit releases the I2C bus so that other masters on the bus can perform their own transactions, since the I2C bus is a multi-master serial bus.

But how does the Stream API reproduce these combined transactions on the I2C bus? Writing to the stream using a Stream Write block, for example, configured to minimize latency, will send all the bytes in the signal at its input to the I2C bus in a single I2C multi-byte write transaction, which is terminated by a STOP bit. Hence, writes to the slave device are simple and straightforward.

Warning

When using the Advanced Stream blocks, such as Stream Send, the data will not be written to the I2C bus until the stream is flushed using the Stream Flush block. The Stream Write block combines these two blocks into one when it is configured to minimize latency.

Invoking the Stream Read block reads from the slave device, but it does not do the initial write of the slave register offset. What is required is a combination of the write and read into a single I2C bus transaction. There are two ways to combine a write and read operation into a single I2C transaction, as illustrated above. The easiest way is to use the Stream Write-Read block, which performs just such a transaction. The register offset is fed to the block's input and the value of the register is read at the block's output. The Output data type and Dimensions parameters should be set according to the data being read.

For an example that uses both the Stream Write and Stream Write-Read blocks to interface to an I2C device, refer to the QUARC I2C Gyroscope Demo.

Combining Operations for an I2C Device

A lower-level, but more flexible, technique requires using a Stream Write followed by a Stream Read, while exclusive access has been acquired for the I2C bus.

Exclusive access to the I2C bus is obtained using the Stream Set Property block. By using the Stream Set Property block to set the boolean STREAM_PROPERTY_IS_EXCLUSIVE property to true, exclusive access is gained to the I2C bus. All read and writes to the stream while exclusive access has been obtained will be combined into a single I2C bus transaction. When the combined message is complete, the I2C bus must be released again by setting the STREAM_PROPERTY_IS_EXCLUSIVE property to false with another Stream Set Property block.

The following table shows the required sequence of events for the typical read transactions illustrated above.

Step

Operation

Description

1

Stream Set Property

Set the STREAM_PROPERTY_IS_EXCLUSIVE property to true (1) to gain exclusive access to the bus.

2

Stream Write

Write the register offset as a uint8. Sometimes one bit of this offset is used to indicate whether the register offset should be auto-incremented when multiple values are read.

3

Stream Read

Read the contents of the slave register using whatever data type is appropriate (often int8 or int16). Note that multiple values may be read at the same time if the slave supports it.

4

Stream Set Property

Set the STREAM_PROPERTY_IS_EXCLUSIVE property to false (0) to release the bus. Do not omit this step!

This technique is exactly what the Stream Write-Read block does under the hood. However, it is more flexible because any combination of Stream Write and Stream Read blocks may be used while exclusive access to the bus has been acquired to perform more complex I2C combined messages.

Handling Multiple Data Types

Sometimes the data read from or written to a slave device involves a combination of different data types. For example, with the L3GD20 gyroscope one might be reading the Status Register (a uint8), the Temperature (an int8) and three axes (three int16s) in a single call to the Stream Write-Read block.

The best way to handle this situation is to use a Bus Object. Using a Bus Object allows the data to be read directly as the data types desired and to be output as a bus containing the expected values. It also handles byte order correctly. A Simulink Bus is essentially equivalent to a C structure, with each element of the bus corresponding to a field in the structure. Refer to Using Bus Objects with QUARC for more details on how to use bus objects to read mixed data types. An example using a bus object to read mixed data types from an I2C digital output gyroscope is provided by the QUARC I2C Gyroscope Mixed Types Demo.

Another way to handle this situation is to read data as a vector of bytes and then use the Demux block and Bitwise Concatenate block to extract the individual bytes and then combine the bytes for the axes into int16 values. However, this approach is not recommended as the use of Bus Objects is much cleaner and more reliable.

Limitations

Performance

Warning The performance of the I2C protocol depends on the performance of the underlying I2C device. Be sure to check the capabilities of your underlying hardware. Rates of 400 kHz are typical. Signal integrity issues also affect the maximum baud rate achievable. For example, at high rates the capacitance of the lines can slow down the signals and limit the baud rate attainable particularly since the I2C protocol uses open-drain clock and data lines with resistive pullups.

Also note that the baud rate refers to the number of bits transferred per second and not the number of bytes. The number of bytes per second is usually less than 1/9th of the baud rate due to the overhead (albeit small) of the protocol.

QUARC Target Manager

Warning The I2C protocol cannot be used for communicating with the target (via a target URI) because the I2C protocol does not allow multiple connections on the same port. Nor should it be used as a model URI.

Options

mode

The mode option configures the I2C device providing I2C services as either a master or slave. Valid values for this option are "master" or "slave" accordingly. The default mode is generally the "master" mode.

address

The address option specifies the address of the slave device with which the stream will be communicating. The address may be specified in hexadecimal by adding an '0x' prefix, such as '0x48', or in binary by adding an '0b' prefix, such as '0b1001000'. Slave addresses are 7-bit or 10-bit. If the data sheet for the slave device provides an 8-bit "address" then it is including the read/write bit as part of the address. In that case, only provide the upper 7 bits of the "address" since the read/write bit is handled automatically by the I2C protocol driver.

baud

The baud option specifies the frequency of the I2C clock (SCL) in Hertz. For slave devices, the I2C clock rate is determined by the master, but it should still be configured, in case the rate is used to configure oversampling or filtering.

The baud rate determines the number of bits transferred per second, not the number of bytes. The default baud rate depends on the underlying I2C device. Baud rates of 100 kHz and 400 kHz are generally supported, but some devices may support the higher rates.

delay

The delay option specifies the delay in seconds between the I2C data line (SDA) and the edge of the I2C clock (SCL). Normally this option need not be specified, because the default value is suitably chosen by the protocol based on the baud rate. However, the default can be overridden by specifying this option if necessary.

Warning The only target which currently supports this option is the QUARC Linux x64 target. Valid values range from 0 to 280e-9 (280 ns).

memsize

The memsize option determines the size of the send and receive buffer (in bytes) used for I2C transfers. This size determines the maximum number of bytes that can be transferred in one send or receive operation. It is not the same as the Stream API's send and receive buffers but is used in interfacing with the low-level drivers that implement the I2C protocol. The default buffer size depends on the underlying I2C device. Setting this option is equivalent to setting both the rcvsize and sndsize options at the same time.

rcvsize

The rcvsize option determines the size of the receive buffer (in bytes) used for I2C transfers. This size determines the maximum number of bytes that can be transferred in one receive operation. It is not the same as the Stream API's receive buffers but is used in interfacing with the low-level drivers that implement the I2C protocol. The default buffer size depends on the underlying I2C device.

sndsize

The sndsize option determines the size of the send buffer (in bytes) used for I2C transfers. This size determines the maximum number of bytes that can be transferred in one send operation. It is not the same as the Stream API's send buffers but is used in interfacing with the low-level drivers that implement the I2C protocol. The default buffer size depends on the underlying I2C device.

Driver

The driver supporting the I2C protocol is called qrt_i2c.

Targets

Target

Supported

Comments

QUARC Win32 Target

No

Not currently supported.

QUARC Win64 Target

No

Not currently supported.

QUARC Linux Nvidia Target

Yes

Fully supported.

QUARC Linux QBot Platform Target

No

Not currently supported.

QUARC Linux QCar 2 Target

No

Not currently supported.

QUARC Linux QDrone 2 Target

No

Not currently supported.

QUARC Linux Raspberry Pi 3 Target

Yes

Fully supported.

QUARC Linux Raspberry Pi 4 Target

No

Not currently supported.

QUARC Linux RT ARMv7 Target

No

Not currently supported.

QUARC Linux x64 Target

Yes

Fully supported.

QUARC Linux DuoVero Target

Yes

Fully supported.

QUARC Linux DuoVero 2016 Target

Yes

Fully supported.

QUARC Linux Verdex Target

No

Not currently supported.

QUARC QNX x86 Target

No

Last fully supported in QUARC 2018.

See Also

 

navigation bar