Zephyr: Serial Communication
BME554L -Fall 2025 - Palmeri
Serial vs. Parallel
What is Serial Communication?
Send data one bit at a time, sequentially, over a single wire; in contrast to parallel communication, where multiple bits are sent as a whole, on multiple wires.
Common examples:
- RS-232
- USB
- SATA/SCSI
- PS/2
- Ethernet
- HDMI/DVI
- PCIe (not PCI!)
Why Serial over Parallel?
- Parallel communication can communicate more bits / clock cycle, but serial links can be clocked much faster.
- No clock skew between bits
- Fewer cables / connections (cheaper)
- Better isolation from noise / interference / crosstalk
Universal Asynchronous Receiver/Transmitter (UART)
UART is an asynchronous serial communication protocol. It is a common peripheral on microcontrollers, and is used to communicate with other devices, such as sensors, over a serial link.
Start bit
is usuallyLOW
, followed by 8 bits of data.- A
parity
bit–conveying an even of odd data stream–can be used for error checking.
Tx/Rx without Flow Control
RX
senses the start bit and then stores the subsequent bits in a shift register (making a “word”).- The data baud rate between
TX:RX
needs to be established between the two devices. This is typically 9600 or 115200 bits/s.
Tx/Rx with Flow Control
RTS
(Request to Send)CTS
(Clear to Send)- UART ISRs are triggered by
RTS
andCTS
events. - High-priority event helps avoid missing data, but can cause starvation of other tasks.
- Low-priority event can cause data loss, but allows other tasks to run.
- Multi-threaded RTOS can be much more robust than a single-threaded super-loop system.
UART Firmware API Events
Inter-Integrated Circuit (I2C)
- Widely-used 2-wire serial communication protocol.
- Limited to short distances.
SCL
: serial clockSDA
: serial data- Common data communication rates: 100, 400, & 1000 kbps
- Access data using simple
read()
andwrite()
functions. - Multiple devices, with unique addresses, can be connected to the same bus.
Alternative Master/Slave Terminology
- Primary/Secondary
- Controller/Peripheral
- Controller/Responder
- Parent/Child
Nordic TWI (Two-Wire Interface)
Nordic’s I2C implementation is called TWI (Two-Wire Interface).
UART vs. I2C
- UART is a point-to-point protocol, while I2C is a multi-point protocol.
- UART is asynchronous (2-way communication, full duplex), while I2C is synchronous (half duplex, needs
CLK
). - Both are “slow”, but I2C is faster.
- UART is simpler to implement, but I2C is more robust.
SenseWire (I3C)
- 2-pin superset of I2C (backward compatible)
- Lower power and space requirements
- Higher data rates
- Dynamic address assignment
- “Hot” peripheral connection
Serial Passing Interface (SPI)
- Like I2C, but allow for full duplex (concurrent send/receive of data).
- Needs 4 wires:
MOSI
: master out, slave inMISO
: master in, slave outSCLK
: serial clockSS
: slave select (LOW
to select)
SS
instead of device address
I2C vs. SPI
- Higher data speeds than I2C (e.g., SD card)
- More complicated connectivity scheme for multiple peripherals
Zephyr: Sensors
- The reading of data from sensors is so common that Zephyr provides a common API for accessing them.
- Channels: measurable quantities (e.g., temperature, humidity, acceleration, etc.)
Fetching & Getting Data
- Fetching data from a sensor is done using
sensor_sample_fetch()
function. Fetching stores the data in a buffer on the sensor. - Getting data from the sensor is then done using
sensor_sample_get()
. Getting reads the data from the buffer and returns it to the user.- A sensor can have multiple channels, and each channel can have multiple samples.
- Sensor channels are specified using a
sensor_channel
enum:sensor_channel
sensor_value
struct of 2 intsint32_t val1
: integer part of valueint32_t val2
: fractional part of value
Helper Functions
- Helper functions exist to convert values (units, struct -> float, etc.)
sensor_value_to_float()
Example Firmware
Example Connection of MCP9808 Temperature Sensor
Breakout Board | DK GPIO Pin |
---|---|
SDA |
P0.26 |
SCL |
P0.27 |
Vdd |
VDD |
GND |
GND |
prf.conf
=y CONFIG_SENSOR
nrf52833dk_nrf52833.overlay
/* I2C Pin Mapping
SCK: P0.27
SDA: P0.26
*/
&i2c0 {
18{
mcp9808@= "jedec,jc-42.4-temp";
compatible = <0x18>;
reg = "okay";
status };
};
main.c
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/sensor.h> // prf.conf -> CONFIG_SENSOR=y
#define MEASUREMENT_DELAY_MS 1000
(main, LOG_LEVEL_DBG);
LOG_MODULE_REGISTER
int read_temperature_sensor(const struct device *temp_sensor, float *temperature_degC);
// the microchip,mcp9808 cannot be accessed in the DT by alias
// instead, have to directly access the node name, with comma replaced by underscore
const struct device *const temp_sensor = DEVICE_DT_GET_ONE(jedec_jc_42_4_temp);
static float temperature_degC;
int main(void) {
int ret;
if (!device_is_ready(temp_sensor)) {
("Temperature sensor %s is not ready", temp_sensor->name);
LOG_ERRreturn -1;
}
else {
("Temperature sensor %s is ready", temp_sensor->name);
LOG_INF}
// read the temperature every MEASUREMENT_DELAY_MS
while (1) {
= read_temperature_sensor(temp_sensor, &temperature_degC);
ret if (ret != 0) {
("There was a problem reading the temperature sensor (%d)", ret);
LOG_ERRreturn ret;
}
("Temperature: %f", (double)temperature_degC);
LOG_INF(MEASUREMENT_DELAY_MS);
k_msleep}
return 0;
}
int read_temperature_sensor(const struct device *temp_sensor, float *temperature_degC) {
/* Fetch-n-get temperature sensor data
INPUTS:
temp_sensor (const struct device *) - temperature sensor device
temperature_degC (float *) - pointer to store temperature in degrees Celsius
RETURNS:
0 - success
Otherwise, error code
*/
struct sensor_value sensor_vals = {.val1 = 0, .val2 = 0};
int err = sensor_sample_fetch(temp_sensor);
if (err != 0) {
("Temperature sensor fetch(): %d", err);
LOG_ERRreturn err;
}
else {
// sensor channels: https://docs.zephyrproject.org/latest/doxygen/html/group__sensor__interface.html#gaaa1b502bc029b10d7b23b0a25ef4e934
= sensor_channel_get(temp_sensor, SENSOR_CHAN_AMBIENT_TEMP, &sensor_vals);
err if (err != 0) {
("Temperature sensor get(): %d", err);
LOG_ERRreturn err;
}
}
// data returned in kPa
*temperature_degC = sensor_value_to_float(&sensor_vals);
("Temperature (deg C): %f", (double)*temperature_degC);
LOG_INFreturn 0;
}
Resources
- Wikipedia: Serial Communication
- DevAcademy: UART
- DevAcademy: I2C
- All About Circuits: Master / Slave Terminology Reexamind
- Zephyr: Sensors
- GitHub: Zephyr MPR Sensor Sample