Name
I²C Functions — allow applications and other packages to access I²C devices
Synopsis
#include <cyg/io/i2c.h>
cyg_uint32 cyg_i2c_tx(
const cyg_i2c_device* device, const cyg_uint8* tx_data, cyg_uint32 count)
;
cyg_uint32 cyg_i2c_rx(
const cyg_i2c_device* device, cyg_uint8* rx_data, cyg_uint32 count)
;
void cyg_i2c_transaction_begin(
const cyg_i2c_device* device)
;
cyg_bool cyg_i2c_transaction_begin_nb(
const cyg_i2c_device* device)
;
cyg_uint32 cyg_i2c_transaction_tx(
const cyg_i2c_device* device, cyg_bool send_start, const cyg_uint8* tx_data, cyg_uint32 count, cyg_bool send_stop)
;
cyg_uint32 cyg_i2c_transaction_rx(
const cyg_i2c_device* device, cyg_bool send_start, cyg_uint8* rx_data, cyg_uint32 count, cyg_bool send_nack, cyg_bool send_stop)
;
void cyg_i2c_transaction_stop(
const cyg_i2c_device* device)
;
void cyg_i2c_transaction_end(
const cyg_i2c_device* device)
;
Description
All I²C functions take a pointer to a cyg_i2c_device structure as their first argument. These structures are usually provided by the platform HAL. They contain the information needed by the I²C bus driver to interact with the device, for example the device address.
An I²C transaction involves the following stages:
- Perform thread-level locking on the bus. Only one thread at a time is allowed to access an I²C bus. This eliminates the need to worry about locking at the bus driver level. If a platform involves multiple I²C buses then each one will have its own lock.
- Generate a start condition, send the address and direction bit, and wait for an acknowledgement from the addressed device.
- Either transmit data to or receive data from the addressed device.
- The previous two steps may be repeated several times, allowing data to move in both directions during a single transfer.
- Generate a stop condition, ending the current data transfer. It is now possible to start another data transfer while the bus is still locked, if desired.
- End the transaction by unlocking the bus, allowing other threads to access other devices on the bus.
The simple functions cyg_i2c_tx
and
cyg_i2c_rx
perform all these steps in a single
call, making them suitable for many I/O operations. The alternative
transaction-oriented functions provide greater control when
appropriate, for example if a repeated start is necessary for a
bi-directional data transfer.
With the exception of
cyg_i2c_transaction_begin_nb
all the functions
will block until completion. The tx routines will return 0 if the
specified device does not respond to its address, or the number of
bytes actually transferred. This may be less than the number requested
if the device sends an early nack, for example because it has run out
of buffer space. The rx routines will return 0 or the number of bytes
received. Usually this will be the same as the
count
parameter. A slave device has no way of
indicating to the master that no more data is available, so the rx
operation cannot complete early.
I²C operations should always be performed at thread-level or during system initialization, and not inside an ISR or DSR. This greatly simplifies locking. Also a typical ISR or DSR should not perform a blocking operation such as an I²C transfer.
Simple Transfers
cyg_i2c_tx
and cyg_i2c_rx
can be used for simple data transfers. They both go through the
following steps: lock the bus, generate the start condition, send the
device address and the direction bit, either send or receive the data,
generate the stop condition, and unlock the bus. At the end of a
transfer the bus is back in its idle state, ready for the next
transfer.
cyg_i2c_tx
returns the number of bytes actually
transmitted. This may be 0 if the device does not respond when its
address is sent out. It may be less than the number of bytes requested
if the device generates an early nack, typically because it has run
out of buffer space.
cyg_i2c_rx
returns 0 if the device does not
respond when its address is sent out, or the number of bytes actually
received. Usually this will be the number of bytes requested because
an I²C slave device has no way of aborting an rx operation early.
Transactions
To allow multiple threads to access devices on the I²C some locking is
required. This is encapsulated inside transactions. The
cyg_i2c_tx
and cyg_i2c_rx
functions implicitly use such transactions, but the functionality is
also available directly to application code. Amongst other things
transactions can be used for more complicated interactions with I²C
devices, in particular ones involving repeated starts.
cyg_i2c_transaction_begin
must be used at the
start of a transaction. This performs thread-level locking on the bus,
blocking if it is currently in use by another thread.
cyg_i2c_transaction_begin_nb
is a non-blocking
variant, useful for threads which cannot afford to block for an
indefinite period. If the bus is currently locked the function returns
false immediately. If the bus is not locked then it acts as
cyg_i2c_transaction_begin
and returns true.
Once the bus has been locked it is possible to perform one or more
data transfers by calling
cyg_i2c_transaction_tx
,
cyg_i2c_transaction_rx
and
cyg_i2c_transaction_stop
. Code should ensure that
a stop condition has been generated by the end of a transaction.
Once the transaction is complete
cyg_i2c_transaction_end
should be called. This
unlocks the bus, allowing other threads to perform I²C I/O to devices
on the same bus.
As an example consider reading the registers in an FS6377 programmable clock generator. The first step is to write a byte 0 to the device, setting the current register to 0. Then a repeated start condition should be generated and it is possible to read the 16 byte-wide registers, starting with the current one. Typical code for this might look like:
cyg_uint8 tx_data[1]; cyg_uint8 rx_data[16]; cyg_i2c_transaction_begin(&hal_alaia_i2c_fs6377); tx_data[0] = 0x00; cyg_i2c_transaction_tx(&hal_alaia_i2c_fs6377, true, tx_data, 1, false); cyg_i2c_transaction_rx(&hal_alaia_i2c_fs6377, true, rx_data, 16, true, true); cyg_i2c_transaction_end(&hal_alaia_i2c_fs6377);
Here hal_alaia_i2c_fs6377
is a
cyg_i2c_device structure provided by the
platform HAL. A transaction is begun, locking the bus. Then there is a
transmit for a single byte. This transmit involves generating a start
condition and sending the address and direction bit, but not a stop
condition. Next there is a receive for 16 bytes. This also involves a
start condition, which the device will interpret as a repeated start
because it has not yet seen a stop. The start condition will be
followed by the address and direction bit, and then the device will
start transmitting the register contents. Once all 16 bytes have been
received the rx routine will send a nack rather than an ack, halting
the transfer, and then a stop condition is generated. Finally the
transaction is ended, unlocking the bus.
The arguments to cyg_i2c_transaction_tx
are as
follows:
-
const cyg_i2c_device*
device
- This identifies the I²C device that should be used.
-
cyg_bool
send_start
If true, generate a start condition and send the address and direction bit. If false, skip those steps and go straight to transmitting the actual data. The latter can be useful if the data to be transmitted is spread over several buffers. The first tx call will involve generating the start condition but subsequent tx calls can skip this and just continue from the previous one.
send_start
must be true if the tx call is the first operation in a transaction, or if the previous call was an rx or stop.-
const cyg_uint8*
tx_data
, cyg_uint32count
- These arguments specify the data to be transmitted to the device.
-
cyg_bool
send_stop
- If true, generate a stop condition at the end of the transmit. Usually this is done only if the transmit is the last operation in a transaction.
The arguments to cyg_i2c_transaction_rx
are as
follows:
-
const cyg_i2c_device*
device
- This identifies the I²C device that should be used.
-
cyg_bool
send_start
If true, generate a start condition and send the address and direction bit. If false, skip those steps and go straight to receiving the actual data. The latter can be useful if the incoming data should be spread over several buffers. The first rx call will involve generating the start condition but subsequent rx calls can skip this and just continue from the previous one. Another use is for devices which can send variable length data, consisting of an initial length and then the actual data. The first rx will involve generating the start condition and reading the length, a subsequent rx will then just read the data.
send_start
must be true if the rx call is the first operation in a transaction, if the previous call was a tx or stop, or if the previous call was an an rx and thesend_nack
flag was set.-
cyg_uint8*
rx_data
, cyg_uint32count
- These arguments specify how much data should be received and where it should be placed.
-
cyg_bool
send_nack
-
If true generate a nack instead of an ack for the last byte received.
This causes the slave to end its transmit. The next operation should
either involve a repeated start or a stop.
send_nack
should be set to false only ifsend_stop
is also false, the next operation will be another rx, and that rx does not specifysend_start
. -
cyg_bool
send_stop
- If true, generate a stop condition at the end of the transmit. Usually this is done only if the transmit is the last operation in a transaction.
The final transaction-oriented function is
cyg_i2c_transaction_stop
. This just generates a
stop condition. It should be used if the previous operation was a tx
or rx that, for some reason, did not set the
send_stop
flag. A stop condition must be generated
before the transaction is ended.
Initialization
The generic package CYGPKG_IO_I2C
arranges for all
I²C bus devices to be initialized via a single prioritized C++ static
constructor. This constructor will run early on during system startup,
before any application code, with priority
CYG_INIT_BUS_I2C
. Other code should not try to
access any of the I²C devices until after the buses have been
initialized.
2024-03-18 | Open Publication License |