Name
Porting — Adding I²C support to new hardware
Description
Adding I²C support to an eCos port involves a number of steps. The
generic I²C package CYGPKG_IO_I2C
should be
included in the appropriate ecos.db target entry
or entries. Next cyg_i2c_device structures
should be provided for every device on the bus. Usually this is the
responsibility of the platform HAL. In the case of development boards
where the I²C SDA and SCL lines are accessible via an expansion
connector, more devices may have been added and it will be the
application's responsibility to provide the structures. Finally
there is a need for one or more cyg_i2c_bus
structures. Amongst other things these structures provide functions
for actually driving the bus. If the processor has dedicated I²C
hardware then this structure will usually be provided by a device
driver package. If the bus is implemented by bit-banging then the bus
structure will usually be provided by the platform HAL.
Adding a Device
The eCos I²C API works in terms of cyg_i2c_device structures, and these provide the information needed to access the hardware. A cyg_i2c_device structure contains the following fields:
-
cyg_i2c_bus*
i2c_bus
- This specifies the bus which the slave device is connected to. Most boards will only have a single I²C bus, but multiple buses are possible.
-
cyg_uint16
i2c_address
- For most devices this will be the 7-bit I²C address the device will respond to. There is room for future expansion, for example to support 10-bit addresses.
-
cyg_uint16
i2c_flags
- This field is not used at present. It exists for future expansion, for example to allow for fast mode or high-speed mode, and incidentally pads the structure to a 32-bit boundary.
-
cyg_uint32
i2c_delay
-
This holds the clock period which should be used when interacting with
the device, in nanoseconds. Usually this will be 10000 ns,
corresponding to a 100KHz clock, and the header
cyg/io/i2c.h
provides a#define
CYG_I2C_DEFAULT_DELAY
for this. Sometimes it may be desirable to use a slower clock, for example to reduce noise problems.
The normal way to instantiate a cyg_i2c_device
structure uses the CYG_I2C_DEVICE
macro, also
provided by cyg/io/i2c.h
:
#include <cyg/io/i2c.h> CYG_I2C_DEVICE(cyg_i2c_wallclock_ds1307, &hal_alaia_i2c_bus, 0x68, 0x00, CYG_I2C_DEFAULT_DELAY); CYG_I2C_DEVICE(hal_alaia_i2c_fs6377, &hal_alaia_i2c_bus, 0x58, 0x00, CYG_I2C_DEFAULT_DELAY);
The arguments to the macro are the variable name, an I²C bus pointer,
the device address, the flags field, and the delay field. The above
code fragment defines two I²C device variables,
cyg_i2c_wallclock_ds1307
and
hal_alaia_i2c_fs6377
, which can be used for the
first argument to the cyg_i2c_
functions. Both
devices are on the same bus. The device addresses are 0x68 and 0x58
respectively, and the devices do not have any special requirements.
When the platform HAL provides these structures it should also export
them for use by the application and other packages. Usually this
involves an entry in cyg/hal/plf_io.h
, which gets included
automatically via one of the main exported HAL header files cyg/hal/hal_io.h
. Unfortunately
exporting the structures directly can be problematical because of
circular dependencies between the I²C header and the HAL headers.
Instead the platform HAL should define a macro
HAL_I2C_EXPORTED_DEVICES
:
# define HAL_I2C_EXPORTED_DEVICES \ extern cyg_i2c_bus hal_alaia_i2c_bus; \ extern cyg_i2c_device cyg_i2c_wallclock_ds1307; \ extern cyg_i2c_device hal_alaia_i2c_fs6377;
This macro gets expanded automatically by cyg/io/i2c.h
once the data structures
themselves have been defined, so application code can just include
that header and all the buses and devices will be properly exported
and usable.
There is no single convention for naming the I²C devices. If the
device will be used by some other package then typically that
specifies the name that should be used. For example the DS1307
wallclock driver expects the I²C device to be called
cyg_i2c_wallclock_ds1307
, so failing to observe
that convention will lead to compile-time and link-time errors. If the
device will not be used by any other package then it is up to the
platform HAL to select the name, and as long as reasonable care is
taken to avoid name space pollution the exact name does not matter.
Bit-banged Bus
Some processors come with dedicated I²C hardware. On other hardware
the I²C bus involves simply connecting some GPIO pins to the SCL and
SDA lines and then using software to implement the I²C protocol. This
is usually referred to as bit-banging the bus. The generic I²C package
CYGPKG_IO_I2C
provides the main code for a
bit-banged implementation, requiring one platform-specific function
that does the actual GPIO pin manipulation. This function is usually
hardware-specific because different boards will use different pins for
the I²C bus, so typically it is left to the platform HAL to provide
this function and instantiate the I²C bus object. There is no point in
creating a separate package for this because the code cannot be
re-used for other platforms.
Instantiating a bit-banged I²C bus requires the following:
#include <cyg/io/i2c.h> static cyg_bool hal_alaia_i2c_bitbang(cyg_i2c_bus* bus, cyg_i2c_bitbang_op op) { cyg_bool result = 0; switch(op) { … } return result; } CYG_I2C_BITBANG_BUS(hal_alaia_i2c_bus, &hal_alaia_i2c_bitbang);
This gives a structure hal_alaia_i2c_bus
which can
be used when defining the cyg_i2c_device
structures. The second argument specifies the function which will
do the actual bit-banging. It takes two arguments. The first
identifies the bus, which can be useful if the hardware has multiple
I²C buses. The second specifies the bit-bang operation that should be
performed. To understand these operations consider how I²C devices
should be wired up according to the specification:
Figure 75.1. I²C wiring specification
Master and slave devices are interfaced to the bus in exactly the same way. The default state of the bus is to have both lines high via the pull-up resistors. Any device on the bus can lower either line, when allowed to do so by the protocol. Usually the SDA line only changes while SCL is low, but the start and stop conditions involve SDA changing while SCL is high. All devices have the ability to both read and write both lines. In reality not all bit-banged hardware works quite like this. Instead just two GPIO pins are used, and these are switched between input and output mode as required.
The bitbang function should support the following operations:
-
CYG_I2C_BITBANG_INIT
- This will be called during system initialization, as a side effect of a prioritized C++ static constructor. The bitbang function should ensure that both SCL and SDA are driven high.
-
CYG_I2C_BITBANG_SCL_HIGH
,CYG_I2C_BITBANG_SCL_LOW
,CYG_I2C_BITBANG_SDA_HIGH
,CYG_I2C_BITBANG_SDA_LOW
- These operations simply set the appropriate lines high or low.
-
CYG_I2C_BITBANG_SCL_HIGH_CLOCKSTRETCH
- In its simplest form this operation should simply set the SCL line high, indicating that the data on the SDA line is stable. However there is a complication: if a device is not ready yet then it can throttle back the master by keeping the SCL line low. This is known as clock-stretching. Hence for this operation the bitbang function should allow the SCL line to float high, then poll it until it really has become high. If a single pin is used for the SCL line then this pin should be turned back into a high output at the end of the call.
-
CYG_I2C_BITBANG_SCL_LOW_SDA_INPUT
- This is used when there is a change of direction and the slave device is about to start driving the SDA line. This can be significant if a single pin is used to handle both input and output of SDA, to avoid a situation where both the master and the slave are driving the SDA line for an extended period of time. The operation combines dropping the SCL line and switching SDA to an input in an atomic or near-atomic operation.
-
CYG_I2C_BITBANG_SDA_READ
- The SDA line is currently set as an input and the bitbang function should sample and return the current state.
The bitbang function returns a boolean. For most operations this
return value is ignored. For
CYG_I2C_BITBANG_SDA_READ
it should be the current
level of the SDA line.
Depending on the hardware some care may have to be taken when manipulating the GPIO pins. Although the I²C subsystem performs the required locking at the bus level, the device registers controlling the GPIO pins may get used by other subsystems or by the application. It is the responsibility of the bitbang function to perform appropriate locking, whether via a mutex or by briefly disabling interrupts around the register accesses.
Full Bus Driver
If the processor has dedicated I²C hardware then usually this will
involve a separate device driver package in the
devs/i2c
hierarchy of the eCos component
repository. That package should also be included in the appropriate
ecos.db target entry or entries. The device
driver may exist already, or it may have to be written from scratch.
A new I²C driver basically involves creating an cyg_i2c_bus structure. The device driver should supply the following fields:
-
i2c_init_fn
- This function will be called during system initialization to set up the I²C hardware. The generic I²C code creates a static object with a prioritized constructor, and this constructor will invoke the init functions for the various I²C buses in the system.
-
i2c_tx_fn
,i2c_rx_fn
,i2c_stop_fn
-
These functions implement the core I²C functionality. The arguments
and results are the same as for the transaction functions
cyg_i2c_transaction_tx
,cyg_i2c_transaction_rx
andcyg_i2c_transaction_stop
. -
void*
i2c_extra
- This field holds any extra information that may be needed by the device driver. Typically it will be a pointer to some driver-specific data structure.
To assist with instantiating a cyg_i2c_bus
object the header file cyg/io/i2c.h
provides a macro. Typical
usage would be:
struct xyzzy_data { … } xyzzy_object; static void xyzzy_i2c_init(struct cyg_i2c_bus* bus) { … } static cyg_uint32 xyzzy_i2c_tx(const cyg_i2c_device* dev, cyg_bool send_start, const cyg_uint8* tx_data, cyg_uint32 count, cyg_bool send_stop) { … } static cyg_uint32 xyzzy_i2c_rx(const cyg_i2c_device* dev, cyg_bool send_start, cyg_uint8* rx_data, cyg_uint32 count, cyg_bool send_nack, cyg_bool send_stop) { … } static void xyzzy_i2c_stop(const cyg_i2c_device* dev) { … } CYG_I2C_BUS(cyg_i2c_xyzzy_bus, &xyzzy_i2c_init, &xyzzy_i2c_tx, &xyzzy_i2c_rx, &xyzzy_i2c_stop, (void*) &xyzzy_object);
The generic I²C code contains these functions for a bit-banged I²C bus
device. It can be used as a starting point for new drivers. Note that
the bit-bang code uses the i2c_extra
field to hold
the hardware-specific bitbang function rather than a pointer to some
data structure.
2024-03-18 | Open Publication License |