Introduction
Learn how JSON and XML flow through embedded Linux from hardware registers to applications. Includes APIs, and debugging strategies.
In embedded Linux, data isn’t just bits on a wire – it’s the lifeblood of systems that power sensors, IoT devices, and industrial automation. Choosing the right format for configuration and communication (JSON or XML) can make the difference between a system that is lean, reproducible, and developer‑friendly versus one that is bloated and fragile.
This article takes you layer by layer – from hardware registers to application logic – showing how structured data flows through an embedded Linux stack. We’ll use a sensor data example to ground the discussion.
1. Hardware Layer
- Reality: Sensors output raw binary values (e.g., 12‑bit temperature).
- API/Interface: Datasheet defines register map (TMP102 -> register
0x00holds temperature). - Code (conceptual):
uint16_t raw_temp = read_register(0x00);
- Debugging Tools: Oscilloscope, logic analyzer.
- Hardware is the foundation. Validate wiring and signal integrity before touching software.
2. Bus Layer (I²C/SPI/UART)
- Reality: Bus moves bytes between sensor and SoC.
- Linux API:
/dev/i2c-*viaioctl. - Code (I²C read in C):
int fd = open("/dev/i2c-1", O_RDWR);
ioctl(fd, I2C_SLAVE, 0x48);
uint8_t buf[2];
read(fd, buf, 2);
int temp_raw = (buf[0] << 4) | (buf[1] >> 4);
- Debugging Tools:
i2cdetect -y 1-> scan bus.i2cdump -y 1 0x48-> dump registers.- Bus reliability is critical – errors here cascade upward.
3. Driver Layer
- Reality: Kernel driver abstracts bus into sysfs/char devices.
- Kernel API:
i2c_smbus_read_word_data()inside driver. - Code (sysfs exposure):
static int tmp102_read_temp(struct i2c_client *client)
{
int ret;
u8 buf[2];
ret = i2c_smbus_read_i2c_block_data(client, TMP102_REG_TEMP, 2, buf);
if (ret < 0) {
pr_err("Failed to read temperature\n");
return ret;
}
int raw = (buf[0] << 4) | (buf[1] >> 4);
int temp_c = raw * 0.0625; // TMP102scale
return temp_c;
}
static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
int temp = tmp102_read_temp(client);
return sprintf(buf, "%d\n", temp);
}
static DEVICE_ATTR_RO(temp);
cat /sys/bus/i2c/devices/1-0048/temp1_input
# Output: 42000 (millidegrees Celsius)
- Debugging Tools:
dmesg, kernel logs,lsmod. - Drivers are translators. Keep them lean, modular, and upstream‑friendly.
4. Kernel Level
- Reality: Kernel provides syscalls (
open,read,ioctl). - Code (POSIX syscalls):
int fd = open("/dev/sensor0", O_RDONLY);
char buf[16];
read(fd, buf, sizeof(buf));
printf("Raw sensor data: %s\n", buf);
- Debugging Tools:
strace ./app,perf. - Kernel is binary‑oriented – parsing belongs in user space.
5. glibc Layer
- Reality: glibc wraps syscalls into C APIs.
- Code (glibc read):
FILE *fp = fopen("/sys/class/hwmon/hwmon0/temp1_input", "r");
int temp;
fscanf(fp, "%d", &temp);
printf("Temperature: %.2f C\n", temp / 1000.0);
- Debugging Tools:
ltrace ./app. - glibc is plumbing – invisible but critical. Respect its boundaries.
6. Middleware Layer
- Reality: Middleware daemons convert raw values into structured formats.
- Code (MQTT publish JSON):
char payload[64];
sprintf(payload, "{ \"sensor\": \"temp\", \"value\": %d }", temp);
mosquitto_publish(mosq, NULL, "sensors/temp", strlen(payload), payload, 0, false);
- Code (XML format):
<sensor>
<type>temperature</type>
<value>42</value>
<unit>C</unit>
</sensor>
- Debugging Tools:
mosquitto_sub -t sensors/#,dbus-monitor. - Middleware is where design choices matter – JSON for speed, XML for schema validation.
7. Application Layer
- Reality: Applications parse JSON/XML and apply logic.
- Code (JSON parse with cJSON):
cJSON *root = cJSON_Parse(buffer);
int value = cJSON_GetObjectItem(root,"value")->valueint;
printf("Temperature: %d C\n", value);
- Code (XML parse with libxml2):
xmlDoc *doc = xmlReadFile("sensor.xml", NULL, 0);
xmlNode *root = xmlDocGetRootElement(doc);
for (xmlNode *cur = root->children; cur; cur = cur->next) {
if (cur->type == XML_ELEMENT_NODE) {
printf("%s: %s\n", cur->name, xmlNodeGetContent(cur));
}
}
- Debugging Tools:
gdb,valgrind. - Applications are where user experience meets engineering. Parsing must be robust, error‑tolerant, and well‑tested.
🛠 Debugging Strategy (Layer by Layer)
- Hardware: Oscilloscope, logic analyzer.
- Bus:
i2cdump,i2cdetect. - Driver/Kernel:
dmesg,strace. - glibc:
ltrace. - Middleware:
mosquitto_sub,dbus-monitor. - Application:
gdb,valgrind.
📊 Performance Considerations
- JSON: Faster parsing, smaller footprint.
- XML: Slower, memory‑intensive, but strict validation.
- Benchmarking: Always measure parsing speed and memory usage on target hardware (ARM, RISC‑V).
✅Takeaway
- Hardware/Bus: Ensure reliability.
- Driver/Kernel/glibc: Provide clean abstractions.
- Middleware: Choose formats strategically.
- Application: Deliver robust, user‑friendly logic.
👉 JSON/XML are user‑space constructs. They don’t exist at hardware or kernel levels – they’re introduced at middleware/application layers to make raw data portable, human‑readable, and interoperable.


Leave a Reply