当前位置:首页>Linux>字节跳动笔试重点题:嵌入式Linux下的I2C设备驱动与温湿度传感器数据读取

字节跳动笔试重点题:嵌入式Linux下的I2C设备驱动与温湿度传感器数据读取

  • 2026-09-30 13:32:01
字节跳动笔试重点题:嵌入式Linux下的I2C设备驱动与温湿度传感器数据读取

大家好,我是一个爱分享的牛马程序员,工作中碰到,加上自己理解,很高兴给大家分享。

-begin-

题目:在嵌入式Linux系统中,编写一个I2C设备驱动及应用程序,实现以下功能:

1.驱动层:支持I2C温湿度传感器(假设型号为SHT30,I2C地址0x44),实现设备探测、初始化及数据读取函数;
2.应用层:通过设备文件(如/dev/sht30)读取传感器数据,打印温度(℃)和湿度(%RH);
3.驱动需支持动态加载,且能正确处理I2C通信错误。

详细分析过程:

一、核心考点拆解

本题是字节跳动嵌入式Linux岗位笔试的重点题,主要考察:

•I2C驱动框架(i2c_driver结构体、probe/remove函数);
•I2C设备通信(i2c_transfer发送命令、读取数据);
•字符设备接口(file_operations实现read方法,向应用层返回数据);
•传感器数据解析(根据SHT30数据格式转换温度和湿度值)。

二、关键设计思路

4.驱动层设计:
◦I2C驱动注册:定义i2c_driver结构体,指定支持的设备ID(SHT30的I2C地址0x44),实现probe(设备探测成功时初始化)和remove(设备移除时清理)函数;
◦传感器初始化:在probe函数中,通过i2c_transfer发送SHT30初始化命令(如软复位),确保传感器进入正常工作模式;
◦数据读取逻辑:实现read方法,向传感器发送“读取数据”命令,接收6字节响应(温度高8位、温度低8位、温度校验位、湿度高8位、湿度低8位、湿度校验位);
◦错误处理:对i2c_transfer返回值判断,通信失败时返回错误码(如-EIO)。
5.应用层设计:
◦打开/dev/sht30设备文件,调用read函数读取数据(缓冲区至少8字节,存储温度和湿度的浮点值);
◦解析读取到的温度和湿度,格式化打印(保留2位小数);
◦处理read返回的错误(如设备未就绪时重试)。
6.数据解析:
◦SHT30温度计算公式:温度(℃) = (温度原始值 / 65535.0) * 175.0 - 45.0;
◦湿度计算公式:湿度(%RH) = (湿度原始值 / 65535.0) * 100.0;
◦原始值由接收的2字节数据拼接而成(高8位<<8 | 低8位)。

三、代码实现

1. 驱动代码(sht30_drv.c)

#include <linux/init.h>

#include <linux/module.h>

#include <linux/i2c.h>

#include <linux/fs.h>

#include <linux/cdev.h>

#include <linux/uaccess.h>

#include <linux/device.h>

#define SHT30_ADDR 0x44        // SHT30 I2C地址

#define DEV_NAME "sht30"       // 设备名

#define DEV_COUNT 1            // 设备数量

// 命令定义

#define SHT30_SOFT_RESET 0x30A2  // 软复位命令

#define SHT30_READ_DATA 0x2400   // 读取数据命令

// 设备结构体

struct sht30_dev {

   struct i2c_client *client;  // I2C客户端

   struct cdev cdev;           // 字符设备

   dev_t devno;                // 设备号

   struct class *class;        // 设备类

};

static struct sht30_dev *sht30_device;

// SHT30发送命令

static int sht30_send_cmd(struct i2c_client *client, uint16_t cmd) {

   uint8_t buf[2] = {cmd >> 8, cmd & 0xFF};  // 命令分高8位和低8位

   struct i2c_msg msg = {

       .addr = client->addr,

       .flags = 0,  // 写操作

       .len = 2,

       .buf = buf

   };

   return i2c_transfer(client->adapter, &msg, 1);  // 发送1条消息

}

// 读取温湿度数据

static int sht30_read_data(struct i2c_client *client, float *temp, float *hum) {

   uint8_t data[6];  // 存储接收的6字节数据

   struct i2c_msg msg = {

       .addr = client->addr,

       .flags = I2C_M_RD,  // 读操作

       .len = 6,

       .buf = data

   };

   int ret;

   // 发送读取命令

   ret = sht30_send_cmd(client, SHT30_READ_DATA);

   if (ret != 1) {  // i2c_transfer成功返回消息数

       dev_err(&client->dev, "send read cmd failed: %d\n", ret);

       return -EIO;

   }

   // 等待传感器准备数据(约10ms)

   msleep(10);

   // 读取数据

   ret = i2c_transfer(client->adapter, &msg, 1);

   if (ret != 1) {

       dev_err(&client->dev, "read data failed: %d\n", ret);

       return -EIO;

   }

   // 解析温度(前2字节)

   uint16_t temp_raw = (data[0] << 8) | data[1];

   *temp = (temp_raw / 65535.0f) * 175.0f - 45.0f;

   // 解析湿度(中间2字节,跳过第3字节校验位)

   uint16_t hum_raw = (data[3] << 8) | data[4];

   *hum = (hum_raw / 65535.0f) * 100.0f;

   return 0;

}

// 字符设备read方法

static ssize_t sht30_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {

   struct sht30_dev *dev = filp->private_data;

   float temp, hum;

   char kernel_buf[32];  // 存储格式化后的字符串

   int ret;

   // 读取传感器数据

   ret = sht30_read_data(dev->client, &temp, &hum);

   if (ret) {

       return ret;

   }

   // 格式化数据(温度+湿度)

   snprintf(kernel_buf, sizeof(kernel_buf), "%.2f,%.2f\n", temp, hum);

   // 拷贝到用户空间

   if (copy_to_user(buf, kernel_buf, strlen(kernel_buf)) != 0) {

       return -EFAULT;

   }

   return strlen(kernel_buf);

}

// 文件操作结构体

static const struct file_operations sht30_fops = {

   .owner = THIS_MODULE,

   .read = sht30_read,

};

// 设备探测函数

static int sht30_probe(struct i2c_client *client, const struct i2c_device_id *id) {

   int ret;

   // 分配设备结构体内存

   sht30_device = devm_kzalloc(&client->dev, sizeof(struct sht30_dev), GFP_KERNEL);

   if (!sht30_device) {

       return -ENOMEM;

   }

   // 初始化设备结构体

   sht30_device->client = client;

   i2c_set_clientdata(client, sht30_device);

   // 申请设备号

   ret = alloc_chrdev_region(&sht30_device->devno, 0, DEV_COUNT, DEV_NAME);

   if (ret) {

       dev_err(&client->dev, "alloc chrdev failed: %d\n", ret);

       return ret;

   }

   // 初始化字符设备

   cdev_init(&sht30_device->cdev, &sht30_fops);

   sht30_device->cdev.owner = THIS_MODULE;

   ret = cdev_add(&sht30_device->cdev, sht30_device->devno, DEV_COUNT);

   if (ret) {

       dev_err(&client->dev, "cdev add failed: %d\n", ret);

       unregister_chrdev_region(sht30_device->devno, DEV_COUNT);

       return ret;

   }

   // 创建设备类

   sht30_device->class = class_create(THIS_MODULE, DEV_NAME);

   if (IS_ERR(sht30_device->class)) {

       dev_err(&client->dev, "class create failed\n");

       cdev_del(&sht30_device->cdev);

       unregister_chrdev_region(sht30_device->devno, DEV_COUNT);

       return PTR_ERR(sht30_device->class);

   }

   // 创建设备节点

   device_create(sht30_device->class, &client->dev, sht30_device->devno, NULL, DEV_NAME);

   // 传感器软复位

   ret = sht30_send_cmd(client, SHT30_SOFT_RESET);

   if (ret != 1) {

       dev_err(&client->dev, "soft reset failed: %d\n", ret);

       // 此处简化处理,实际应清理资源

   }

   msleep(10);  // 复位后等待稳定

   dev_info(&client->dev, "sht30 probe success\n");

   return 0;

}

// 设备移除函数

static int sht30_remove(struct i2c_client *client) {

   struct sht30_dev *dev = i2c_get_clientdata(client);

   // 销毁设备节点

   device_destroy(dev->class, dev->devno);

   // 销毁设备类

   class_destroy(dev->class);

   // 移除字符设备

   cdev_del(&dev->cdev);

   // 释放设备号

   unregister_chrdev_region(dev->devno, DEV_COUNT);

   dev_info(&client->dev, "sht30 remove success\n");

   return 0;

}

// 支持的设备ID表

static const struct i2c_device_id sht30_id[] = {

   {DEV_NAME, 0},

   {}

};

MODULE_DEVICE_TABLE(i2c, sht30_id);

// I2C驱动结构体

static struct i2c_driver sht30_driver = {

   .driver = {

       .name = DEV_NAME,

       .owner = THIS_MODULE,

   },

   .probe = sht30_probe,

   .remove = sht30_remove,

   .id_table = sht30_id,

};

module_i2c_driver(sht30_driver);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("牛马程序员");

MODULE_DESCRIPTION("SHT30 I2C Temperature and Humidity Sensor Driver");

2. 应用层代码(sht30_app.c)

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <fcntl.h>

#define DEV_PATH "/dev/sht30"  // 设备文件路径

int main() {

   int fd;

   char buf[32];

   float temp, hum;

   int ret;

   // 打开设备文件

   fd = open(DEV_PATH, O_RDONLY);

   if (fd == -1) {

       perror("open failed");

       return -1;

   }

   printf("开始读取温湿度数据(按Ctrl+C退出)...\n");

   while (1) {

       // 读取数据

       ret = read(fd, buf, sizeof(buf) - 1);

       if (ret <= 0) {

           perror("read failed");

           sleep(1);

           continue;

       }

       buf[ret] = '\0';

       // 解析温度和湿度(格式:"temp,hum")

       if (sscanf(buf, "%f,%f", &temp, &hum) != 2) {

           printf("数据解析失败: %s\n", buf);

           sleep(1);

           continue;

       }

       // 打印结果

       printf("温度: %.2f℃, 湿度: %.2f%%RH\n", temp, hum);

       sleep(2);  // 每2秒读取一次

   }

   close(fd);

   return 0;

}

四、代码解析与考点总结

7.驱动层核心:
◦i2c_driver结构体是I2C驱动的核心,probe函数在设备匹配时被调用,完成初始化(申请设备号、创建设备节点等);
◦i2c_transfer是I2C通信的关键函数,通过i2c_msg结构体描述消息(地址、读写标志、长度、数据),支持多消息传输;
◦字符设备接口file_operations的read方法实现数据读取,将传感器数据格式化后通过copy_to_user拷贝到用户空间。
8.数据解析要点:
◦SHT30返回的原始数据为16位无符号整数,需按公式转换为实际物理量;
◦通信时需注意命令和数据的字节序(SHT30使用大端模式,与多数嵌入式系统一致)。
9.易错点:
◦I2C地址需确认(SHT30可能有0x44或0x45,取决于硬件引脚);
◦i2c_transfer返回值为成功传输的消息数,而非字节数,需判断是否等于发送的消息数;
◦传感器操作后需预留足够的延迟时间(如复位后等待10ms),否则读取数据可能失败。
10.字节跳动考点延伸:面试中可能追问“如何实现I2C设备的多实例支持?”——需在probe函数中为每个设备分配独立结构体,使用devno区分;或问“如何处理I2C总线错误(如断线)?”——可在read方法中增加重试机制,超过次数后返回错误。

I2C是嵌入式系统中常用的低速通信总线,字节跳动等大厂重视对设备驱动框架和硬件交互细节的掌握,本题覆盖了从驱动注册到数据解析的全流程,对理解外设驱动开发具有重要意义。

-end-

如果文章对你有提升,帮忙点赞,分享,关注。十分感谢

最新文章

随机文章