Hello CircuitDojo Community,
My name is Ted, and I am coming up to speed on Zephyr device driver development. I’m working with device tree overlays, and an accelerometer which does not appear in either Zephyr nor Nordic’s Connect SDK drivers.
My general question is, when crafting or extending a new Zephyr device driver, are we allowed to call the Zephyr I2C API directly? If yes, and in early driver development stage, would a fair place to do this be the project root dir, in ‘drivers/new_driver.c’?
My question applies to Zephyr’s SPI peripheral API as well.
A little context and info about where I’m working: colleagues recently shared Marti Bolivar’s Zephyr device tree talk,
I also recently found and read – and found very helpful! – Jared Wolff’s instructional blog post:
My first device driver work is in a modified copy of ncsv1.6.1 Zephyr branch samples, a copy of <code>samples/basic/blinky</code>. Following Marti’s on-line talk and class, I was able to create and successfully compile an overlay file. I haven’t written any custom driver routines yet, but I am able to call Zephyr’s macro pairing to assign a device type pointer to the overlaid device:
# Project: blinky-modified
# File: app.overlay
# Target board: Sparkfun nRF9160 Thing Plus
# Description: Zephyr device tree overlay for Kionix KX123 sensor
&i2c1 {
compatible = "nordic,nrf-twim";
status = "okay";
sda-pin = <26>;
scl-pin = <27>;
kionix_sensor: kx132-1211@1f {
compatible = "kionix,kx132-1211";
reg = <0x1F>;
label = "KX132-1211";
};
};
Alongside app.overly, in <code>src/main.c</code> these lines declare and assign a pointer to a Zephyr type device to the device expressed in the above overlay file:
const struct device *dev_accelerometer;
dev_accelerometer = DEVICE_DT_GET(DT_NODELABEL(kionix_sensor));
if (dev_accelerometer == NULL) { . . . }
At run time I see this call succeed.
With a valid device pointer I am ready to start writing (or use existing Zephyr) API code. In other, non-Zephyr RTOS environs I am used to calling an I2C API more or less directly. But here in Zephyr, this layer of function calls is obscured to me. I’ve followed the ncs example code for the LIS2DH accelerometer, and there is no obvious place where I see driver code calling I2C API functions. (This sensor also supports SPI bus connection.)
Jared Wolff’s blog post describes the purpose and use of DEVICE_AND_API_INIT, what looks like a Zephyr macro. Marti Bolivar’s overlay example shows a sensor added to device tree using two nodes, one for I2C and one for SPI. He goes on to show that no C code changes are needed to switch between the two bus types, which is pretty slick. But what happens when I need to configure a new sensor, or take a reading from it, in a manner that’s not enumerated in <$ZEPHYR_BASE>/include/drivers/sensor.h? There’s a long enumeration there which I see LIS2DH driver use some of its elements:
/**
* @brief Sensor channels.
*/
enum sensor_channel {
/** Acceleration on the X axis, in m/s^2. */
SENSOR_CHAN_ACCEL_X,
/** Acceleration on the Y axis, in m/s^2. */
SENSOR_CHAN_ACCEL_Y,
/** Acceleration on the Z axis, in m/s^2. */
SENSOR_CHAN_ACCEL_Z,
/** Acceleration on the X, Y and Z axes. */
SENSOR_CHAN_ACCEL_XYZ,
/** Angular velocity around the X axis, in radians/s. */
SENSOR_CHAN_GYRO_X,
.
.
.
How and where does our new driver code connect with Zephyr’s I2C and SPI bus APIs? I’ll be starting out as Jared does in his blog post, with driver work in a ‘drivers’ directory alongside src/main.c and app.overlay.
Thanks ahead of time for any help on this question!