VxWorks 7 I2C Driver Guide: Complete Example and Best Practices
Developing device drivers in VxWorks 7 requires a solid understanding of the VxBus framework, hardware abstraction, and system configuration via the device tree. Among common peripheral interfaces, I2C (Inter-Integrated Circuit) remains one of the most widely used buses for connecting sensors, EEPROMs, PMICs, and other low-speed devices.
This guide provides a structured, production-oriented walkthrough of writing an I2C device driver in VxWorks 7, covering architecture, implementation, transactions, debugging, and best practices.
🔌 Why I2C Matters in Embedded Systems #
I2C continues to be a foundational interface in embedded platforms due to its simplicity and flexibility:
- Two-wire interface (SDA, SCL) minimizes pin usage
- Address-based communication supports multiple devices on a single bus
- Broad ecosystem support across sensors and control devices
- Ideal for configuration, monitoring, and low-bandwidth data exchange
In RTOS environments like VxWorks, I2C is frequently used for system bring-up, board management, and peripheral control.
🧱 I2C Architecture in VxWorks 7 #
VxWorks 7 implements a layered model for I2C support, separating responsibilities across components:
Controller Driver #
- Manages the physical I2C bus hardware
- Handles timing, arbitration, and signaling (start/stop conditions)
- Typically SoC-specific
Device Driver #
- Implements logic specific to a peripheral device
- Uses controller APIs to perform transactions
- Encapsulates register access and device behavior
VxBus Framework #
- Provides standardized lifecycle methods (
probe,attach,detach) - Handles driver binding via device tree
- Ensures portability and modularity
This separation allows reuse of controller drivers while developing multiple device drivers on top.
🧾 Device Tree Configuration #
Hardware description in VxWorks 7 relies on the device tree. A typical I2C configuration looks like:
&i2c0 {
status = "okay";
clock-frequency = <400000>;
temperature-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
Key Elements #
i2c0: I2C controller nodeclock-frequency: bus speed (e.g., 400 kHz Fast Mode)reg: device addresscompatible: used for driver matching
Accurate device tree configuration is critical—mismatches here are a common source of driver issues.
🧩 Driver Skeleton Implementation #
A minimal VxBus I2C driver defines probe and attach routines along with a method table:
#include <vxWorks.h>
#include <hwif/vxBus.h>
#include <subsys/i2c/vxbI2cLib.h>
LOCAL STATUS myI2cProbe(VXB_DEV_ID pDev)
{
return OK;
}
LOCAL STATUS myI2cAttach(VXB_DEV_ID pDev)
{
printf("I2C device attached at address 0x%02x\n",
vxbDevUnitGet(pDev));
return OK;
}
LOCAL VXB_DRV_METHOD myI2cMethods[] = {
{ VXB_DEVMETHOD_CALL(vxbDevProbe), (FUNCPTR)myI2cProbe },
{ VXB_DEVMETHOD_CALL(vxbDevAttach), (FUNCPTR)myI2cAttach },
VXB_DEVMETHOD_END
};
VXB_DRV myI2cDrv = {
{ NULL },
"myI2c",
"Example I2C Device",
VXB_BUSID_I2C,
0, 0,
myI2cMethods,
NULL,
};
VXB_DRV_DEF(myI2cDrv)
STATUS myI2cDrvRegister(void)
{
return vxbDrvAdd(&myI2cDrv);
}
This structure integrates the driver into the VxBus ecosystem and enables automatic binding via the device tree.
🔄 I2C Transactions: Read and Write #
Most I2C devices follow a register-based communication model. VxWorks provides vxbI2cDevXfer() for master transfers.
Read Example #
STATUS myI2cRead(VXB_DEV_ID pDev, UINT8 devAddr, UINT8 reg, UINT8 *buf, int len)
{
VXB_I2C_MSG msgs[2];
msgs[0].addr = devAddr;
msgs[0].flags = 0;
msgs[0].buf = ®
msgs[0].len = 1;
msgs[1].addr = devAddr;
msgs[1].flags = I2C_M_RD;
msgs[1].buf = buf;
msgs[1].len = len;
return vxbI2cDevXfer(pDev, msgs, 2);
}
Write Example #
STATUS myI2cWrite(VXB_DEV_ID pDev, UINT8 devAddr, UINT8 reg, UINT8 *buf, int len)
{
UINT8 txBuf[1 + len];
txBuf[0] = reg;
memcpy(&txBuf[1], buf, len);
VXB_I2C_MSG msg = {
.addr = devAddr,
.flags = 0,
.buf = txBuf,
.len = sizeof(txBuf),
};
return vxbI2cDevXfer(pDev, &msg, 1);
}
This pattern is widely applicable across EEPROMs, sensors, and control ICs.
🐞 Debugging Techniques #
Efficient debugging is essential when working with low-level drivers:
- Use
-> devsin the kernel shell to verify device registration - Add trace logs in
probeandattachpaths - Validate
compatiblestrings against the device tree - Inspect bus activity using a logic analyzer or oscilloscope
These steps help isolate issues across software configuration and hardware signaling.
⚠️ Common Pitfalls #
Clock Stretching Issues #
Some controllers have limited support. Lowering bus frequency often resolves instability.
Addressing Errors #
Confusion between 7-bit and 8-bit addressing frequently leads to NACK failures.
Driver Binding Conflicts #
Multiple drivers may match the same compatible string—ensure uniqueness.
Concurrency Problems #
Use synchronization (mutexes) when multiple tasks access the same I2C bus.
✅ Best Practices #
- Separate generic driver logic from device-specific functionality
- Use method tables for modular and extensible design
- Validate all device tree inputs before use
- Provide IOCTL interfaces for flexible user interaction
- Maintain clear documentation of register maps and behavior
📌 Conclusion #
Writing an I2C driver in VxWorks 7 involves more than just implementing read/write routines—it requires proper integration with VxBus, accurate device tree configuration, and disciplined debugging.
Once these fundamentals are in place, the development of reusable, maintainable I2C drivers becomes straightforward and scalable across projects.