1. Introduction
The Inter-Integrated Circuit (I²C) is a widely used low-speed, two-wire communication protocol designed for communication between integrated circuits. In embedded systems, I²C is commonly used to connect sensors, EEPROMs, ADCs, and other peripherals.
[VxWorks 7] introduces a modern, scalable, and modular driver framework built on VxBus, supporting dynamic driver registration, device tree integration, and power management. For BSP (Board Support Package) developers, understanding how to create robust I²C device drivers is crucial for supporting various I²C-connected peripherals on custom hardware platforms.
This article is aimed at experienced VxWorks BSP developers who are already familiar with the BSP architecture, memory mapping, and device trees. It aims to demonstrate how to develop an I²C master controller driver, along with a basic I²C peripheral driver example using the VxWorks 7 driver model.
We will use a generic memory-mapped I²C master controller as a reference to illustrate key concepts and provide practical code snippets for building.
2. VxWorks 7 I2C Driver Architecture
2.1 Overview of VxBus
VxBus is the device driver framework of VxWorks that abstracts hardware details and provides a modular, layered approach to managing drivers and devices. It supports automatic device matching through the device tree and manages lifecycle callbacks such as probe, attach, and detach.
For I²C, VxBus distinguishes between two types of drivers:
- • I²C Controller Driver (Master/Bus Driver): Interfaces directly with the hardware (I²C controller). Registered as a bus and implements the
<span>vxbI2cDevXfer()</span>API. - • I²C Peripheral Driver (Client/Slave Driver): Communicates with devices on the I²C bus using the API exposed by the controller driver.
2.2 Key Components and Interfaces
- •
<span>vxbI2cLib.h</span>: I²C messaging and controller API - •
<span>VXB_I2C_BUS_METHODS</span>: Method table for I²C bus drivers - •
<span>vxbFdtLib.h</span>: Device tree parsing utilities
2.3 Typical Call Flow
- 1. Parse the device tree at startup
- 2. The controller driver is probed and attached
- 3. Register the bus using
<span>vxbI2cBusDevRegister()</span> - 4. Peripheral drivers communicate using
<span>vxbI2cDevXfer()</span>
3. Example I²C Controller: Generic I²C Master
3.1 Controller Register Layout
| Register | Offset | Description |
<span>CTRL</span> |
0x00 | Control Register |
<span>STATUS</span> |
0x04 | Status Register |
<span>DATA</span> |
0x08 | Data Register |
<span>CLK_DIV</span> |
0x0C | Clock Divider Register |
3.2 Register Bit Definitions
#define I2C_CTRL_START (1 << 0)
#define I2C_CTRL_STOP (1 << 1)
#define I2C_CTRL_READ (1 << 2)
#define I2C_CTRL_WRITE (1 << 3)
#define I2C_STATUS_BUSY (1 << 0)
#define I2C_STATUS_ACK (1 << 1)
#define I2C_REG_CTRL 0x00
#define I2C_REG_STATUS 0x04
#define I2C_REG_DATA 0x08
#define I2C_REG_CLK_DIV 0x0C
4. Writing an I²C Controller Driver
4.1 Register Access Helper Functions
#define I2C_READ_REG(base, offset) (*(volatile UINT32 *)((UINT8 *)(base) + (offset)))
#define I2C_WRITE_REG(base, offset, v) (*(volatile UINT32 *)((UINT8 *)(base) + (offset)) = (v))
4.2 Data Structure
typedef struct i2cGenDrvCtrl
{
VXB_DEV_ID dev;
void *regBase;
VXB_RESOURCE *pRes;
} I2C_GEN_DRV_CTRL;
LOCAL VXB_DRV_METHOD i2cGenDrvMethods[] =
{
{ VXB_DEVMETHOD_CALL(vxbDevProbe), i2cDrvProbe },
{ VXB_DEVMETHOD_CALL(vxbDevAttach), i2cDrvAttach },
{ 0, NULL }
};
4.3 Probe and Attach
LOCAL STATUS i2cDrvProbe(VXB_DEV_ID pDev)
{
return vxbFdtDevMatch(pDev, NULL);
}
LOCAL STATUS i2cDrvAttach(VXB_DEV_ID pDev)
{
I2C_GEN_DRV_CTRL *pDrvCtrl;
void *regBase;
pDrvCtrl = (I2C_GEN_DRV_CTRL *) vxbMemAlloc(sizeof(I2C_GEN_DRV_CTRL));
if (pDrvCtrl == NULL) return ERROR;
pDrvCtrl->dev = pDev;
regBase = (void *)vxFdtRegGet(pDev, 0);
if (regBase == NULL)
{
vxbMemFree(pDrvCtrl);
return ERROR;
}
pDrvCtrl->regBase = regBase;
vxbDevSoftcSet(pDev, pDrvCtrl);
return vxbI2cBusDevRegister(pDev);
}
4.4 Implementing <span>vxbI2cDevXfer()</span>
LOCAL STATUS i2cDevXfer(VXB_DEV_ID dev, VXB_I2C_MSG *msgs, int num)
{
I2C_GEN_DRV_CTRL *pDrvCtrl = vxbDevSoftcGet(dev);
void *base = pDrvCtrl->regBase;
for (int i = 0; i < num; i++)
{
VXB_I2C_MSG *msg = &msgs[i];
for (int j = 0; j < msg->len; j++)
{
I2C_WRITE_REG(base, I2C_REG_DATA, msg->buf[j]);
UINT32 ctrl = (msg->flags & VXB_I2C_M_RD) ? I2C_CTRL_READ : I2C_CTRL_WRITE;
if (j == 0) ctrl |= I2C_CTRL_START;
if (j == msg->len - 1) ctrl |= I2C_CTRL_STOP;
I2C_WRITE_REG(base, I2C_REG_CTRL, ctrl);
while (I2C_READ_REG(base, I2C_REG_STATUS) & I2C_STATUS_BUSY);
if (!(I2C_READ_REG(base, I2C_REG_STATUS) & I2C_STATUS_ACK))
return ERROR;
if (msg->flags & VXB_I2C_M_RD)
msg->buf[j] = I2C_READ_REG(base, I2C_REG_DATA);
}
}
return OK;
}
LOCAL VXB_I2C_BUS_METHODS i2cBusMethods =
{
.i2cDevXfer = i2cDevXfer,
.i2cDevXferTimeout = NULL
};
5. Device Tree Integration
5.1 Example Device Tree Fragment
i2c@4000f000 {
compatible="generic,i2c-master";
reg=<0x4000f000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
status="okay";
eeprom@50{
compatible="atmel,24c32";
reg=<0x50>;
};
};
6. Writing an Example I²C Peripheral Driver: EEPROM
6.1 Attach Routine
LOCAL STATUS eepromAttach(VXB_DEV_ID pDev)
{
VXB_I2C_MSG msg[2];
UINT8 addr = 0x00;
UINT8 data;
msg[0].addr = 0x50;
msg[0].flags = 0;
msg[0].buf = &addr
msg[0].len = 1;
msg[1].addr = 0x50;
msg[1].flags = VXB_I2C_M_RD;
msg[1].buf = &data
msg[1].len = 1;
if (vxbI2cDevXfer(pDev, msg, 2) == OK)
printf("EEPROM read success: 0x%02x\n", data);
else
printf("EEPROM read failed\n");
return OK;
}
7. Testing and Debugging
- • Common Commands:
- •
<span>i2cShow</span>: Displays registered I²C buses and devices - •
<span>vxbDevShow</span>: Displays all VxBus devices - • Debugging Tips:
- • Verify device tree compatibility strings
- • Check ACK and STOP conditions
- • Use a logic analyzer for signal tracing
8. Conclusion
In this article, we introduced the process of designing a generic I²C device driver for VxWorks 7, covering:
- • VxBus and I²C driver structure
- • Register-level I²C controller driver implementation
- • Device tree binding
- • Peripheral device communication
- • Testing and debugging
This basic framework can be extended by:
- • Supporting repeated START
- • Integrating interrupts/DMA
- • Supporting multiple buses
- • Complex peripheral drivers (e.g., sensors, codecs)