JSON & XML in Embedded Linux: Full‑Stack Guide with Drivers & Middleware

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 0x00 holds 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-* via ioctl.
  • 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.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *