Table of ContentsThis tutorial provides a comprehensive guide for working with I2C sensors on the Luckfox Pico platform, covering both low-level I2C communication and high-level kernel driver approaches. The examples are production-ready and can be easily adapted for other sensors.
- 1. Setup
- 1.1 MLX90614 Sensor Introduction
- 1.2 Hardware Connections
- 1.3 Enable I2C Bus in Device Tree
- 1.4 Enable i2c-tools
- 1.5 Choose Your Implementation Approach
- 2. Read Sensor Data via Generic I2C Interface
- 2.1 Use i2c-tools to Read Registers
- 2.2 Shell Script to Print Sensor Data
- 2.3 C Program to Log Data
- 3. Read Sensor Data using Kernel Driver
- 3.1 Enable Driver in SDK using Kernel Config Tool
- 3.2 Specify Driver in Device Tree
- 3.3 Shell Commands to Log Sensor Data via Kernel Driver
- 3.4 C Program to Log Sensor Data via Kernel Driver
- 4. Conclusion
The MLX90614 is a simple I2C non-contact temperature sensor.
I2C Address: 0x5A
Registers of Interest:
- 0x06 - Ambient Temperature (Ta)
- 0x07 - Object Temperature (To)
- 0x0E - Sensor ID
Temperature Conversion:
Code: Select all
Temperature (°C) = (raw_value × 0.02) - 273.15Product Page: Waveshare Infrared Temperature Sensor
1.2 Hardware Connections
Choose I2C connectors from: Luckfox Pico I2C Documentation
Our Choice: I2C2 M0
- SDA: Pin 3 (GPIO1_C7)
- SCL: Pin 5 (GPIO1_D0)
- VCC: 3.3V
- GND: Ground
1.3 Enable I2C Bus in Device Tree
Edit the device tree to enable I2C2. We choose I2C2 M0 since only M0 is exposed on the dev board.
Note: config/dts_config is a symlink and should be ignored by git. To find the actual file to edit, use:
Code: Select all
$ file config/dts_config # Example output:
config/dts_config: symbolic link to ../sysdrv/source/kernel/arch/arm64/boot/dts/rockchip/rv1106g-luckfox-pico-ultra-w.dtsCode: Select all
/**********I2C2**********/
&i2c2 {
status = "okay";
clock-frequency = <100000>;
};1.4 Enable i2c-tools
This installs i2cget, i2cset, i2cdetect, and other I2C utilities on the target system.
Enable i2c-tools in Buildroot
Start Docker Container
Note: If you haven't set up the Docker environment yet, follow the Luckfox Pico SDK Docker setup guide first.
Code: Select all
$ sudo docker start -ai luckfox
root@container:/#
# Navigate to build directory
root@container:/home# cd /home/
root@container:/home# Method 1: GUI Configuration (Recommended)
Run the following command to open a GUI window (ensure your terminal window has enough height):
Code: Select all
root@container:/home# ./build.sh buildrootconfigEnable i2c-tools package
Save and exit
Method 2: Direct File Edit
Alternatively, you can directly edit the buildroot configuration file:
Code: Select all
root@container:/home# echo "BR2_PACKAGE_I2C_TOOLS=y" >> config/buildroot_defconfigCode: Select all
$ grep I2C_TOOLS config/buildroot_defconfig
BR2_PACKAGE_I2C_TOOLS=yInside Docker container:
Code: Select all
root@container:/home# ./build.sh allCode: Select all
$ sudo ./build.sh flashCode: Select all
ssh root@<board-ip>Code: Select all
root@luckfox:~# ls /dev/i2c*
/dev/i2c-2 /dev/i2c-3 /dev/i2c-4Verify Sensor Detection
Code: Select all
root@luckfox:~# i2cdetect -y 2Code: Select all
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- 5a -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- If you see UU instead of 5a:
This means the sensor is being used by a kernel driver. To use i2c-tools, you need to unload the driver first:
Code: Select all
root@luckfox:~# rmmod mlx906141.5 Choose Your Implementation Approach
This tutorial covers two different methods for reading data from the MLX90614 temperature sensor on the Luckfox Pico:
Method 1: Generic I2C Interface (Sections 2.x)
- Best for: Learning I2C fundamentals, debugging, custom implementations
- Pros: Direct control, no kernel dependencies, works with any I2C device
- Cons: Manual data conversion, more complex error handling
- Tools: i2c-tools, custom C programs using Linux I2C API
- Best for: Production applications, standardized sensor interfaces
- Pros: Automatic data conversion, standardized interface, better error handling
- Cons: Requires kernel configuration, more complex setup
- Tools: IIO device files, kernel driver modules
2.1 Use i2c-tools to Read Registers
Now that we have i2c-tools installed and can detect our MLX90614 sensor, let's use the tools to read the sensor's registers and extract temperature data.
Reading Multiple Registers at Once
You can use i2cdump to read a range of registers and get an overview of all sensor data:
Code: Select all
root@luckfox:~# i2cdump -y 2 0x5a w
0,8 1,9 2,a 3,b 4,c 5,d 6,e 7,f
00: 2629 0098 XXXX 1a15 XXXX 0008 39b3 3998
08: 3bc4 0000 0008 XXXX 04b1 0000 3998 XXXX
10: 8067 1b58 040d 0007 XXXX 0002 XXXX 0000
18: 01ff 801c 04a9 XXXX 0012 0162 XXXX 01ba
20: 9993 62e3 0201 f71c ffff aff4 6b11 6bb5
28: 6ef6 XXXX 95f5 1100 XXXX 20b9 XXXX 079a
30: 9e69 75d3 53e3 8021 8020 1d2b 00c1 XXXX
38: 0013 0000 XXXX 8011 e806 e560 1123 e1d0
40: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
48: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
50: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
58: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
60: XXXX XXXX 44b7 XXXX XXXX 44b8 XXXX 0000
68: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
70: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
78: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
80: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
88: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
90: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
98: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
a0: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
a8: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
b0: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
b8: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
c0: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
c8: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
d0: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
d8: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
e0: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
e8: XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
f0: f701 40e6 40da 0024 40dd 40e2 40df 0000
f8: 40d9 2d14 XXXX d790 XXXX 40d9 XXXX XXXX - Register 0x06: 39b3 (ambient temperature)
- Register 0x07: 3998 (object temperature)
- Register 0x0E: 2629 (sensor ID)
Reading Individual Registers
The i2cget command allows us to read specific registers from the sensor. Since the MLX90614 uses 16-bit registers, we need to read 2 bytes at a time.
Code: Select all
# Read the three main registers from MLX90614 sensor
root@luckfox:~# i2cget -y 2 0x5a 0x06 w # Ambient Temperature register
0x39c2 # Raw value: 14786 decimal → 22.42°C
root@luckfox:~# i2cget -y 2 0x5a 0x07 w # Object Temperature register
0x39cb # Raw value: 14795 decimal → 22.64°C
root@luckfox:~# i2cget -y 2 0x5a 0x0e w # Sensor ID register
0x3c65 # Raw value: 15461 decimal → Sensor IDNote: Raw register values need to be converted to actual temperatures using the formula:
Code: Select all
Temperature (°C) = (raw_value × 0.02) - 273.15Using i2cget, we can use the script to log sensor data. For simplicity, we create read_mlx90614.sh directly in the luckfox /root/ directory:
Code: Select all
#!/bin/bash
I2C_BUS="2"
SENSOR_ADDR="0x5a"
TEMP_AMBIENT_REG="0x06"
TEMP_OBJECT_REG="0x07"
# Check if i2c-tools is installed
if ! command -v i2cget &> /dev/null; then
echo "Error: i2c-tools not found. Install with: opkg install i2c-tools"
exit 1
fi
# Check if I2C device exists
if [ ! -e "/dev/i2c-$I2C_BUS" ]; then
echo "Error: I2C bus /dev/i2c-$I2C_BUS not found"
exit 1
fi
echo "MLX90614 Temperature Reader (Ctrl+C to exit)"
echo "============================================="
while true; do
# Read ambient temperature
ambient_raw=$(i2cget -y $I2C_BUS $SENSOR_ADDR $TEMP_AMBIENT_REG w 2>/dev/null)
if [ $? -eq 0 ]; then
ambient_c=$(echo "scale=1; ($(printf "%d" $ambient_raw) * 0.02) - 273.15" | bc -l)
echo "Ambient: ${ambient_c}°C"
fi
# Read object temperature
object_raw=$(i2cget -y $I2C_BUS $SENSOR_ADDR $TEMP_OBJECT_REG w 2>/dev/null)
if [ $? -eq 0 ]; then
object_c=$(echo "scale=1; ($(printf "%d" $object_raw) * 0.02) - 273.15" | bc -l)
echo "Object: ${object_c}°C"
fi
echo "---"
sleep 2
doneCode: Select all
root@luckfox:~# ./read_mlx90614.sh
MLX90614 Temperature Reader (Ctrl+C to exit)
=============================================
Ambient: 22.33°C
Object: 22.17°C
---
Ambient: 22.31°C
Object: 22.07°C
---
Ambient: 22.35°C
Object: 21.95°C
---
...Note: This script uses bc for floating-point calculations. In this Buildroot configuration, bc is provided by busybox, if you are using systemd you can enable bc in buildroot config:
Code: Select all
# Inside Docker container
root@container:/home# ./build.sh buildrootconfigEnable the bc package and save the configuration.
2.3 C Program to Log Data
While shell scripts are convenient for quick testing, C programs offer better performance and more control over I2C communication. Let's create a C program that directly interfaces with the I2C bus.
The C Program
Project Structure:
Code: Select all
project/app/read_MLX90614/
├── read_mlx90614.c # C program source code
├── read_mlx90614.sh # Shell script version
├── Makefile # Build configuration
└── i2c_read_tutorial.md # This tutorialCreate read_mlx90614.c:
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <errno.h>
#include <string.h>
#include <byteswap.h>
#define I2C_BUS 2
#define SENSOR_ADDR 0x5a
#define TEMP_AMBIENT_REG 0x06
#define TEMP_OBJECT_REG 0x07
int i2c_fd = -1;
// Function to read 16-bit register from I2C device using SMBus protocol
int read_register(int reg) {
struct i2c_smbus_ioctl_data args;
union i2c_smbus_data data;
args.read_write = I2C_SMBUS_READ;
args.command = reg;
args.size = I2C_SMBUS_WORD_DATA;
args.data = &data;
if (ioctl(i2c_fd, I2C_SMBUS, &args) < 0) {
return -1;
}
// MLX90614 returns data in big-endian format, but i2c_smbus_read_word_data
// already handles the byte swapping, so we return the value as-is
return data.word;
}
// Function to convert raw temperature to Celsius
float raw_to_celsius(int raw_value) {
return (raw_value * 0.02) - 273.15;
}
int main() {
char i2c_device[20];
int ambient_raw, object_raw;
float ambient_c, object_c;
// Check if I2C device exists
snprintf(i2c_device, sizeof(i2c_device), "/dev/i2c-%d", I2C_BUS);
if (access(i2c_device, F_OK) != 0) {
printf("Error: I2C bus %s not found\n", i2c_device);
return 1;
}
// Open I2C device
i2c_fd = open(i2c_device, O_RDWR);
if (i2c_fd < 0) {
printf("Error: Cannot open %s: %s\n", i2c_device, strerror(errno));
return 1;
}
// Set slave address
if (ioctl(i2c_fd, I2C_SLAVE, SENSOR_ADDR) < 0) {
printf("Error: Cannot set slave address 0x%02x: %s\n", SENSOR_ADDR, strerror(errno));
close(i2c_fd);
return 1;
}
printf("MLX90614 Temperature Reader (Ctrl+C to exit)\n");
printf("=============================================\n");
while (1) {
// Read ambient temperature
ambient_raw = read_register(TEMP_AMBIENT_REG);
if (ambient_raw >= 0) {
ambient_c = raw_to_celsius(ambient_raw);
printf("Ambient: %.1f°C\n", ambient_c);
} else {
printf("Ambient: Error reading sensor\n");
}
// Read object temperature
object_raw = read_register(TEMP_OBJECT_REG);
if (object_raw >= 0) {
object_c = raw_to_celsius(object_raw);
printf("Object: %.1f°C\n", object_c);
} else {
printf("Object: Error reading sensor\n");
}
printf("---\n");
sleep(2);
}
close(i2c_fd);
return 0;
}Code: Select all
# Include Luckfox SDK build parameters
ifeq ($(APP_PARAM), )
APP_PARAM := ../Makefile.param
include $(APP_PARAM)
endif
export LC_ALL := C
SHELL := /bin/bash
CURRENT_DIR := $(shell pwd)
# Project configuration
PKG_NAME := read_MLX90614
PKG_BIN ?= out
# Compiler flags for cross-compilation
READ_MLX90614_CFLAGS := $(RK_APP_OPTS) $(RK_APP_CROSS_CFLAGS)
READ_MLX90614_LDFLAGS := $(RK_APP_OPTS) $(RK_APP_LDFLAGS)
PKG_TARGET := read_MLX90614-build
# Main build target
all: $(PKG_TARGET)
@echo "build $(PKG_NAME) done"
# Build the project: compile C program and copy shell script
read_MLX90614-build:
@rm -rf $(PKG_BIN); \
mkdir -p $(PKG_BIN)/bin
# Compile the C program using cross-compiler
$(RK_APP_CROSS)-gcc $(READ_MLX90614_CFLAGS) read_mlx90614.c -o $(PKG_BIN)/bin/read_MLX90614 $(READ_MLX90614_LDFLAGS)
# Copy shell script and make it executable
cp read_mlx90614.sh $(PKG_BIN)/bin/
chmod +x $(PKG_BIN)/bin/read_mlx90614.sh
# Copy built files to app output directory
$(call MAROC_COPY_PKG_TO_APP_OUTPUT, $(RK_APP_OUTPUT), $(PKG_BIN))
# Clean build output
clean:
@rm -rf $(PKG_BIN)
distclean: clean
# Show build information
info:
@echo "This is $(PKG_NAME) for $(RK_APP_CHIP) with $(RK_APP_CROSS)"Expected files after build and flash:
After building and flashing, you should find these files on the target system:
Code: Select all
# On the target board (via SSH)
root@luckfox:~# pwd
/oem/usr/bin
root@luckfox:~# ls read_*
read_mlx90614 read_mlx90614.shSample output:
Code: Select all
# Run the shell script
root@luckfox:~# ./read_mlx90614.sh
MLX90614 Temperature Reader (Ctrl+C to exit)
=============================================
Ambient: 23.21°C
Object: 23.05°C
---
Ambient: 23.19°C
Object: 23.11°C
---
^C
# Run the C program
root@luckfox:~# ./read_mlx90614
MLX90614 Temperature Reader (Ctrl+C to exit)
=============================================
Ambient: 23.2°C
Object: 22.8°C
---
Ambient: 23.2°C
Object: 23.1°C
---
^CThe MLX90614 kernel driver is already included in the Luckfox Buildroot SDK. The source code is located in the kernel source tree:
Code: Select all
# Driver source file
sysdrv/source/kernel/drivers/iio/temperature/mlx90614.cWhy is the I2C sensor driver under IIO?
IIO (Industrial I/O) is a Linux kernel framework designed specifically for sensors and data acquisition devices. IIO device drivers are purpose-built for sensors - they provide automatic data conversion (raw values to physical units), standardized sensor interfaces, and create consistent user-space files like in_temp_ambient_raw for easy access. Whereas I2C device drivers are generic communication drivers that only handle raw data transfer and would require custom interfaces for each sensor type, making them too low-level for sensor applications.
Kernel Configuration Options
There are 3 options for a Kernel Driver Configuration:
- n (No): Driver is not compiled
- m (Module): Driver is compiled as a loadable kernel module (recommended)
- y (Yes): Driver is compiled directly into the kernel (built-in)
Enable the MLX90614 Driver
Configure Kernel:
Run the build process in the Luckfox Docker container:
(See the official guide on setting up the Docker environment)
There are two ways to enable the MLX90614 driver:
Method 1: GUI Configuration (Recommended)
In /home/ of the docker container, run the following command to open a GUI window (ensure your terminal window has enough height):
Code: Select all
root@container:/home# ./build.sh kernelconfigMethod 2: Direct File Edit
Alternatively, you can directly edit the kernel configuration file:
Code: Select all
root@container:/home# echo "CONFIG_MLX90614=m" >> config/kernel_defconfigCode: Select all
root@container:/home# grep MLX90614 config/kernel_defconfig
CONFIG_MLX90614=mBuild the kernel and firmware, then flash to the board.
Verify Driver Loading
SSH to the board and check if the module file exists:
Code: Select all
root@luckfox:~# ls /oem/usr/ko/mlx*
ko/mlx90614.koCode: Select all
root@luckfox:~# insmod /oem/usr/ko/mlx90614.koCheck if driver is loaded:
Code: Select all
root@luckfox:~# lsmod | grep mlx90614
mlx90614 4331 0Code: Select all
root@luckfox:~# ls /sys/bus/iio/devices/
iio:device03.2 Specify Driver in Device Tree
The MLX90614 device tree configuration is already included in the Luckfox SDK. The driver needs to know which I2C bus and address to use, which is specified in the device tree.
Existing Device Tree Configuration
The Luckfox SDK already includes the MLX90614 device tree configuration in rv1106g-luckfox-pico-ultra-w.dts:
Code: Select all
/**********I2C2**********/
&i2c2 {
status = "okay";
clock-frequency = <100000>;
// Add the section below
mlx90614@5a { // Device node for MLX90614 sensor
compatible = "melexis,mlx90614"; //Matches the driver's compatible string
reg = <0x5a>; //I2C address
};
};After build, flash, and SSH, verify the MLX90614 sensor is now detected:
Load the MLX90614 module with insmod
Code: Select all
root@luckfox:~# insmod /oem/usr/ko/mlx90614.koCode: Select all
root@luckfox:~# lsmod | grep mlx90614
mlx90614 4331 0Code: Select all
root@luckfox:~# ls /sys/bus/iio/devices/
iio:device0 iio:device1Code: Select all
root@luckfox:~# cat /sys/bus/iio/devices/iio:device1/name
mlx90614Check sensor files:
Code: Select all
root@luckfox:~# ls /sys/bus/iio/devices/iio:device1/
in_temp_ambient_raw in_temp_object_raw name power subsystem uevent
in_temp_offset in_temp_scaleRead sensor values using kernel driver:
Code: Select all
# Read ambient temperature (raw value)
root@luckfox:~# cat /sys/bus/iio/devices/iio:device1/in_temp_ambient_raw
14811
# Read object temperature (raw value)
root@luckfox:~# cat /sys/bus/iio/devices/iio:device1/in_temp_object_raw
14821
# Read temperature scale factor
root@luckfox:~# cat /sys/bus/iio/devices/iio:device1/in_temp_scale
20
# Read temperature offset
root@luckfox:~# cat /sys/bus/iio/devices/iio:device1/in_temp_offset
-13657.500000Formula: (raw + offset) * scale / 1000
Code: Select all
# Ambient temperature: (14811 + (-13657.5)) * 20 / 1000 = 22.7°C
root@luckfox:~# echo "scale=1; (($(cat /sys/bus/iio/devices/iio:device1/in_temp_ambient_raw) + $(cat /sys/bus/iio/devices/iio:device1/in_temp_offset)) * $(cat /sys/bus/iio/devices/iio:device1/in_temp_scale)) / 1000" | bc -l
22.7
# Object temperature: (14821 + (-13657.5)) * 20 / 1000 = 22.9°C
root@luckfox:~# echo "scale=1; (($(cat /sys/bus/iio/devices/iio:device1/in_temp_object_raw) + $(cat /sys/bus/iio/devices/iio:device1/in_temp_offset)) * $(cat /sys/bus/iio/devices/iio:device1/in_temp_scale)) / 1000" | bc -l
22.9When you're done testing with the kernel driver, you can unload it to free up the I2C bus for other applications:
Code: Select all
# Unload the MLX90614 kernel driver
root@luckfox:~# rmmod mlx90614
# Verify the driver is unloaded
root@luckfox:~# lsmod | grep mlx90614
# (no output means driver is unloaded)
# Check that I2C bus is now available for i2c-tools
root@luckfox:~# i2cdetect -y 2
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- 5a -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- This confirms that iio:device1 is indeed the MLX90614 sensor and shows how to read temperature values using the kernel driver.
3.3 Shell Commands to Log Sensor Data via Kernel Driver
Now that we have the MLX90614 kernel driver loaded and the IIO interface available, we can create a shell script that reads temperature data directly from the IIO device files.
IIO Shell Script
Create read_mlx90614_iio.sh:
Code: Select all
#!/bin/bash
# MLX90614 IIO Temperature Reader Script
# This script reads temperature data from MLX90614 sensor via IIO interface
# Configuration
IIO_DEVICE="/sys/bus/iio/devices/iio:device1"
OBJECT_RAW="$IIO_DEVICE/in_temp_object_raw"
AMBIENT_RAW="$IIO_DEVICE/in_temp_ambient_raw"
SCALE="$IIO_DEVICE/in_temp_scale"
OFFSET="$IIO_DEVICE/in_temp_offset"
# Function to read temperature from IIO
read_temperature() {
local raw_file=$1
local temp_name=$2
if [ ! -f "$raw_file" ]; then
echo "Error: $raw_file not found"
return 1
fi
# Read raw value
local raw_value=$(cat "$raw_file")
# Read scale and offset
local scale=$(cat "$SCALE")
local offset=$(cat "$OFFSET")
# Convert to temperature in Celsius
# Formula: (raw_value + offset) * scale / 1000
local temp_c=$(echo "scale=2; ($raw_value + $offset) * $scale / 1000" | bc -l)
echo "$temp_name Temperature: ${temp_c}°C"
}
# Function to check if sensor is present
check_sensor() {
if [ ! -d "$IIO_DEVICE" ]; then
echo "✗ MLX90614 sensor not found at $IIO_DEVICE"
echo "Make sure the module is loaded: modprobe mlx90614"
return 1
else
echo "✓ MLX90614 sensor found at $IIO_DEVICE"
return 0
fi
}
# Main execution
echo "MLX90614 Temperature Reader (Ctrl+C to exit)"
echo "============================================="
# Check if sensor is present
if ! check_sensor; then
exit 1
fi
echo ""
# Read temperatures
while true; do
read_temperature "$OBJECT_RAW" "Object" # Read object temperature
read_temperature "$AMBIENT_RAW" "Ambient" # Read ambient temperature
echo "---"
sleep 2 # Wait 2 seconds before next reading
doneKey Differences from I2C Script:
- Uses IIO Interface: Reads from /sys/bus/iio/devices/iio:device1/ instead of raw I2C registers
- Automatic Conversion: Uses the driver-provided scale and offset values for accurate temperature conversion
- Error Handling: Checks for IIO device presence and file availability
- Standard Formula: Uses the IIO standard formula (raw + offset) * scale / 1000
The script uses the formula provided by the MLX90614 driver:
Code: Select all
Temperature (°C) = (raw_value + offset) * scale / 1000- raw_value: Raw sensor reading from IIO device
- offset: -13657.5 (Kelvin offset from driver)
- scale: 20 (represents 0.02 Kelvin per raw unit)
Important: Before running the IIO script, you must load the MLX90614 kernel driver:
Code: Select all
# Load the MLX90614 kernel module
root@luckfox:~# insmod /oem/usr/ko/mlx90614.ko
# Verify the driver is loaded
root@luckfox:~# lsmod | grep mlx90614
mlx90614 4331 0
# Check that the IIO device is created
root@luckfox:~# ls /sys/bus/iio/devices/
iio:device0 iio:device1
# Verify the sensor name
root@luckfox:~# cat /sys/bus/iio/devices/iio:device1/name
mlx90614
# Now run the IIO script
root@luckfox:~# ./read_mlx90614_iio.sh
MLX90614 Temperature Reader (Ctrl+C to exit)
=============================================
✓ MLX90614 sensor found at /sys/bus/iio/devices/iio:device1
Object Temperature: 22.95°C
Ambient Temperature: 22.70°C
---
Object Temperature: 22.97°C
Ambient Temperature: 22.72°C
---
Object Temperature: 22.93°C
Ambient Temperature: 22.68°C
---
^CWhile the IIO shell script is convenient for quick testing, a C program offers better performance and more control over sensor data reading. Let's create a C program that reads temperature data directly from the IIO device files.
The IIO C Program
Project Structure:
Code: Select all
project/app/read_MLX90614/
├── read_mlx90614.c # I2C C program source code
├── read_mlx90614_iio.c # IIO C program source code
├── read_mlx90614.sh # I2C shell script version
├── read_mlx90614_iio.sh # IIO shell script version
├── Makefile # Build configuration
└── i2c_read_tutorial.md # This tutorialThe IIO device number (e.g., iio:device1) can vary depending on system configuration and the order in which drivers are loaded. Using dynamic discovery makes your code more robust and portable.
Create read_mlx90614_iio.c with dynamic device discovery:
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>
#include <dirent.h>
#define IIO_DEVICES_PATH "/sys/bus/iio/devices"
#define MAX_PATH_LEN 256
// Function to find MLX90614 device dynamically
int find_mlx90614_device(char *device_path, size_t path_size) {
DIR *dir = opendir(IIO_DEVICES_PATH);
if (!dir) {
printf("Error: Cannot open %s\n", IIO_DEVICES_PATH);
return -1;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "iio:device", 10) == 0) {
char name_path[MAX_PATH_LEN];
snprintf(name_path, sizeof(name_path),
"%s/%s/name", IIO_DEVICES_PATH, entry->d_name);
char name[32];
if (read_file_value(name_path, name, sizeof(name)) == 0) {
name[strcspn(name, "\n")] = 0;
if (strcmp(name, "mlx90614") == 0) {
snprintf(device_path, path_size,
"%s/%s", IIO_DEVICES_PATH, entry->d_name);
closedir(dir);
return 0;
}
}
}
}
closedir(dir);
return -1;
}
// Function to read a value from a file (used for IIO device files)
// Returns 0 on success, -1 on error
int read_file_value(const char *filepath, char *buffer, size_t buffer_size) {
FILE *file = fopen(filepath, "r");
if (file == NULL) {
return -1; // File not found or permission denied
}
if (fgets(buffer, buffer_size, file) == NULL) {
fclose(file);
return -1; // Failed to read from file
}
fclose(file);
return 0; // Success
}
// Function to read temperature from IIO device
// Reads raw value, scale, and offset from IIO device files and converts to Celsius
// Returns 0 on success, -1 on error
int read_temperature(const char *raw_file, const char *scale_file, const char *offset_file, const char *temp_name, double *temperature) {
char raw_str[32];
char scale_str[32];
char offset_str[32];
// Read raw temperature value from IIO device
if (read_file_value(raw_file, raw_str, sizeof(raw_str)) != 0) {
printf("Error: Cannot read %s\n", raw_file);
return -1;
}
// Read scale factor (converts raw units to Kelvin)
if (read_file_value(scale_file, scale_str, sizeof(scale_str)) != 0) {
printf("Error: Cannot read scale\n");
return -1;
}
// Read offset value (Kelvin offset from raw value)
if (read_file_value(offset_file, offset_str, sizeof(offset_str)) != 0) {
printf("Error: Cannot read offset\n");
return -1;
}
// Convert strings to numbers
int raw_value = atoi(raw_str);
double scale = atof(scale_str);
double offset = atof(offset_str);
// Calculate temperature using IIO standard formula: (raw + offset) * scale / 1000
// This converts from raw sensor units to Celsius
*temperature = (raw_value + offset) * scale / 1000.0;
printf("%s Temperature: %.2f°C\n", temp_name, *temperature);
return 0;
}
int main() {
char iio_device_path[MAX_PATH_LEN];
char object_raw_path[MAX_PATH_LEN];
char ambient_raw_path[MAX_PATH_LEN];
char scale_path[MAX_PATH_LEN];
char offset_path[MAX_PATH_LEN];
double object_temp, ambient_temp;
printf("MLX90614 Temperature Reader (Ctrl+C to exit)\n");
printf("=============================================\n");
// Find MLX90614 device dynamically
if (find_mlx90614_device(iio_device_path, sizeof(iio_device_path)) != 0) {
printf("✗ MLX90614 sensor not found\n");
printf("Make sure the module is loaded: insmod /oem/usr/ko/mlx90614.ko\n");
return 1;
}
printf("✓ MLX90614 sensor found at %s\n", iio_device_path);
// Set up file paths
snprintf(object_raw_path, sizeof(object_raw_path), "%s/in_temp_object_raw", iio_device_path);
snprintf(ambient_raw_path, sizeof(ambient_raw_path), "%s/in_temp_ambient_raw", iio_device_path);
snprintf(scale_path, sizeof(scale_path), "%s/in_temp_scale", iio_device_path);
snprintf(offset_path, sizeof(offset_path), "%s/in_temp_offset", iio_device_path);
printf("\n");
// Read temperatures continuously
while (1) {
// Read object temperature
if (read_temperature(object_raw_path, scale_path, offset_path, "Object", &object_temp) != 0) {
printf("Object: Error reading sensor\n");
}
// Read ambient temperature
if (read_temperature(ambient_raw_path, scale_path, offset_path, "Ambient", &ambient_temp) != 0) {
printf("Ambient: Error reading sensor\n");
}
printf("---\n");
sleep(2); // Wait 2 seconds before next reading
}
return 0;
}The Makefile has been updated to compile both C programs:
Code: Select all
read_mlx90614-build:
@rm -rf $(PKG_BIN); \
mkdir -p $(PKG_BIN)/bin
# Compile I2C C program
$(RK_APP_CROSS)-gcc $(READ_MLX90614_CFLAGS) read_mlx90614.c -o $(PKG_BIN)/bin/read_MLX90614 $(READ_MLX90614_LDFLAGS)
# Compile IIO C program
$(RK_APP_CROSS)-gcc $(READ_MLX90614_CFLAGS) read_mlx90614_iio.c -o $(PKG_BIN)/bin/read_MLX90614_iio $(READ_MLX90614_LDFLAGS)
# Copy shell scripts and make them executable
cp read_mlx90614.sh $(PKG_BIN)/bin/
chmod +x $(PKG_BIN)/bin/read_mlx90614.sh
cp read_mlx90614_iio.sh $(PKG_BIN)/bin/
chmod +x $(PKG_BIN)/bin/read_mlx90614_iio.sh
# Copy built files to app output directory
$(call MAROC_COPY_PKG_TO_APP_OUTPUT, $(RK_APP_OUTPUT), $(PKG_BIN))After building and flashing, you should find these files on the target system:
Code: Select all
# On the target board (via SSH)
root@luckfox:~# ls /oem/usr/bin/read_*
read_mlx90614 read_mlx90614_iio read_mlx90614.sh read_mlx90614_iio.shCode: Select all
# Load the driver first
root@luckfox:~# insmod /oem/usr/ko/mlx90614.ko
# Run the IIO C program
root@luckfox:~# ./read_mlx90614_iio
MLX90614 Temperature Reader (Ctrl+C to exit)
=============================================
✓ MLX90614 sensor found at /sys/bus/iio/devices/iio:device1
Object Temperature: 22.95°C
Ambient Temperature: 22.70°C
---
Object Temperature: 22.97°C
Ambient Temperature: 22.72°C
---
^CThis tutorial has demonstrated two comprehensive approaches for reading data from the MLX90614 temperature sensor on the Luckfox Pico platform:
What We've Covered
Setup and Configuration
- Hardware connections and pin assignments
- Device tree configuration for I2C2 bus
- Enabling i2c-tools and kernel drivers
- Building and flashing the complete system
- Generic I2C Interface: Direct communication using i2c-tools and custom C programs
- Kernel Driver with IIO: Production-ready approach using standardized sensor interfaces
- Dynamic device discovery for robust operation
- Multiple implementation options (C programs and shell scripts)
- Comprehensive error handling and validation
- Real-time temperature monitoring with proper conversion
Now that you have a working MLX90614 sensor interface, you can:
- Extend the functionality: Add data logging, web interfaces, or integration with other sensors
- Apply the concepts: Use the I2C and IIO techniques with other sensors
- Optimize performance: Implement custom sampling rates or filtering algorithms
- Integrate with applications: Connect the sensor data to your main application logic
By completing this tutorial, you've gained practical experience with:
- I2C communication protocols and debugging
- Linux kernel driver development and IIO framework
- Cross-compilation and embedded Linux development
- Device tree configuration and hardware integration
- Professional embedded software development practices
