当前位置:首页 > 技术 > 正文内容

Linux I2C设备驱动开发指南

访客 技术 2026年8月6日 1

本文将详细介绍如何在Linux环境下,针对i.MX6ULL mini开发板,为SSD1306 OLED屏幕编写一个I2C设备驱动。我们将基于野火提供的内核源码进行开发,重点在于理解和应用Linux I2C子系统。

SSD1306 OLED与I2C接口

SSD1306 OLED屏幕在嵌入式开发中十分常见,其小巧的尺寸和便捷的I2C通信方式使其成为DIY项目的热门选择。I2C总线作为嵌入式工程师必备的技能之一,其基本原理在此不再赘述。本文的重点在于如何在Linux中使用I2C子系统来操作此类I2C设备。

Linux I2C子系统架构

Linux内核的I2C子系统提供了一套标准的接口来管理I2C总线和设备。I2C总线实际上是构建在Platform总线之上的。要开发一个I2C设备驱动,通常需要修改设备树(Device Tree)和编写相应的驱动程序。以下是几个核心的数据结构,它们都定义在include/linux/i2c.h头文件中:

struct i2c_driver

此结构体用于注册一个I2C设备驱动。它包含了驱动与设备匹配、设备探测(probe)、移除(remove)以及其他回调函数。其中probe回调函数是设备被识别并绑定到驱动时执行的主要入口。

struct i2c_driver {
    unsigned int class;
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);
    int (*probe_new)(struct i2c_client *);
    void (*shutdown)(struct i2c_client *);
    void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol, unsigned int data);
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
    struct device_driver driver;
    const struct i2c_device_id *id_table;
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
    bool disable_i2c_core_irq_mapping;
};

struct i2c_client

struct i2c_client代表一个连接到I2C总线的具体I2C设备。它包含了设备的地址、名称以及指向其所连接的I2C适配器(总线控制器)的指针。在设备树机制下,i2c_client的信息通常由I2C核心从设备树中解析得到。

struct i2c_client {
    unsigned short flags;       /* Device flags */
    unsigned short addr;        /* 7-bit client address */
    char name[I2C_NAME_SIZE];   /* Chip name */
    struct i2c_adapter *adapter;/* I2C bus adapter */
    struct device dev;          /* Device node */
    int init_irq;               /* Initial IRQ number */
    int irq;                    /* IRQ number */
    struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
    i2c_slave_cb_t slave_cb;    /* Callback for slave mode */
#endif
};

struct i2c_adapterstruct i2c_algorithm

struct i2c_adapter代表一个物理I2C控制器(总线)。而struct i2c_algorithm则封装了具体I2C控制器的数据传输方法(如master_xfer, smbus_xfer等)。这部分通常由芯片厂商实现。作为驱动开发者,我们主要关注i2c_driveri2c_client

struct i2c_adapter {
    struct module *owner;
    unsigned int class;
    const struct i2c_algorithm *algo; /* Bus access algorithms */
    void *algo_data;
    /* ... other fields ... */
    struct device dev;              /* Adapter device */
    /* ... other fields ... */
};

struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
    int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags,
                       char read_write, u8 command, int size, union i2c_smbus_data *data);
    u32 (*functionality) (struct i2c_adapter *);
    /* ... slave mode callbacks ... */
};

编写SSD1306 I2C驱动示例

下面是一个简化的SSD1306 I2C驱动示例代码。该驱动包含发送命令和数据到OLED屏幕的函数,以及在设备探测时执行的初始化逻辑。


#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>
#include <linux/kernel.h>
#include <linux/delay.h>

// Function to send a command to the SSD1306
static int ssd1306_send_command(struct i2c_client *client, const u8 cmd)
{
    // Command byte format: 0x00 for command, followed by the command itself
    u8 msg_buffer[2] = {0x00, cmd};
    // Use i2c_transfer_buffer to send data
    int ret = i2c_transfer_buffer(client, msg_buffer, sizeof(msg_buffer), 0);
    if (ret < 0) {
        dev_err(&client->dev, "Failed to send command 0x%02x: %d\n", cmd, ret);
    }
    return ret;
}

// Function to send data to the SSD1306
static int ssd1306_send_data(struct i2c_client *client, const u8 data)
{
    // Data byte format: 0x40 for data, followed by the data byte
    u8 msg_buffer[2] = {0x40, data};
    // Use i2c_transfer_buffer to send data
    int ret = i2c_transfer_buffer(client, msg_buffer, sizeof(msg_buffer), 0);
    if (ret < 0) {
        dev_err(&client->dev, "Failed to send data 0x%02x: %d\n", data, ret);
    }
    return ret;
}

// Probe function: called when a matching device is found
static int ssd1306_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    dev_info(&client->dev, "SSD1306 detected at address 0x%02x!\n", client->addr);

    // --- SSD1306 Initialization Sequence ---
    // Introduce a small delay to ensure the device is ready
    msleep(100);

    // Turn display off
    ssd1306_send_command(client, 0xAE);
    // Set display clock divide ratio and oscillator frequency
    ssd1306_send_command(client, 0xD5); // Command: Set Display Clock
    ssd1306_send_command(client, 0x80); // Argument: Default divide ratio (0x80 is default)

    // Set multiplex ratio
    ssd1306_send_command(client, 0xA8); // Command: Set Multiplex Ratio
    ssd1306_send_command(client, 0x3F); // Argument: For 128x64 display, 0x3F (63)

    // Set display offset
    ssd1306_send_command(client, 0xD3); // Command: Set Display Offset
    ssd1306_send_command(client, 0x00); // Argument: No offset

    // Set start line
    ssd1306_send_command(client, 0x40); // Command: Set Display Start Line (0x40 means start from line 0)

    // Set segment re-map (for column 0 to 127)
    ssd1306_send_command(client, 0xA1); // Command: Set Segment Re-map (0xA1 remaps P0 to P127)

    // Set COM output scan direction (for row 0 to 63)
    ssd1306_send_command(client, 0xC8); // Command: Set COM Output Scan Direction (0xC8 scans from 63 down to 0)

    // Set COM pins hardware configuration
    ssd1306_send_command(client, 0xDA); // Command: Set COM Pins hardware configuration
    ssd1306_send_command(client, 0x12); // Argument: For 128x64, 0x12 is common

    // Set contrast control
    ssd1306_send_command(client, 0x81); // Command: Set Contrast Control
    ssd1306_send_command(client, 0x7F); // Argument: Max contrast (0x00 to 0xFF)

    // Set pre-charge period
    ssd1306_send_command(client, 0xD9); // Command: Set Pre-charge Period
    ssd1306_send_command(client, 0xF1); // Argument: Phase 2 period (0xF1 is common)

    // Set VCOMH deselect level
    ssd1306_send_command(client, 0xDB); // Command: Set VCOMH Deselect Level
    ssd1306_send_command(client, 0x40); // Argument: Common value

    // Enable internal regulator or external VCC
    ssd1306_send_command(client, 0xAD); // Command: Set External VCC enable (0xAD, 0x8B for internal)
    ssd1306_send_command(client, 0x8B); // Argument: 0x8B (internal VCC) or 0x89 (external VCC)

    // Turn display on
    ssd1306_send_command(client, 0xAF); // Command: Turn Display ON

    dev_info(&client->dev, "SSD1306 initialized successfully.\n");
    return 0; // Success
}

// Device ID table for matching
static const struct i2c_device_id ssd1306_id_table[] = {
    { "ssd1306", 0 },
    { } // Terminating entry
};
MODULE_DEVICE_TABLE(i2c, ssd1306_id_table);

// Device Tree overlay matching table
static const struct of_device_id of_ssd1306_match[] = {
    { .compatible = "ssd1306", },
    { } // Terminating entry
};
MODULE_DEVICE_TABLE(of, of_ssd1306_match);

// The I2C driver structure
static struct i2c_driver ssd1306_driver = {
    .probe = ssd1306_probe,
    .id_table = ssd1306_id_table,
    .driver = {
        .name = "ssd1306_drv", // Driver name
        .owner = THIS_MODULE,
        .of_match_table = of_ssd1306_match, // Match using Device Tree compatible string
    },
};

// Module initialization function
static int __init ssd1306_module_init(void)
{
    pr_info("Registering SSD1306 I2C driver...\n");
    return i2c_add_driver(&ssd1306_driver);
}

// Module exit function
static void __exit ssd1306_module_exit(void)
{
    pr_info("Unregistering SSD1306 I2C driver...\n");
    i2c_del_driver(&ssd1306_driver);
}

module_init(ssd1306_module_init);
module_exit(ssd1306_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("SSD1306 OLED I2C Driver for Linux");

驱动注册与注销

在驱动的初始化函数ssd1306_module_init中,我们调用i2c_add_driver()来向Linux I2C子系统注册我们的驱动。对应的,在退出函数ssd1306_module_exit中,使用i2c_del_driver()来注销驱动。

数据传输函数如i2c_master_send()i2c_transfer_buffer()(用于发送数据)在include/linux/i2c.h中声明。i2c_transfer_buffer()是一个更通用的接口,可以发送指定长度的数据。

// Function prototypes from kernel headers (simplified)
extern int i2c_add_driver(struct i2c_driver *driver);
extern void i2c_del_driver(struct i2c_driver *driver);

// Simplified view of i2c_transfer_buffer for clarity
int i2c_transfer_buffer(const struct i2c_client *client, char *buf, int count, int flags);

驱动编译与部署

将上述驱动代码(例如命名为ssd1306_i2c.c)放置在Linux内核源码的drivers/char/目录下。修改该目录下的Makefile文件,添加以下行:

obj-m += ssd1306_i2c.o

然后,返回到内核源码的根目录,执行编译命令:

$ make modules

编译成功后,您将在drivers/char/目录下找到ssd1306_i2c.ko模块文件。将此模块文件复制到您的开发板上,并加载它(例如使用insmod ssd1306_i2c.ko)。

设备树配置

为了让I2C核心能够识别并实例化SSD1306设备,您需要在设备树中添加相应的节点。假设您的I2C总线控制器节点名为&i2c1,则可以在其下添加SSD1306设备节点,指定其I2C地址:

// Example snippet within the &i2c1 node in your device tree source (e.g., .dts file)
&i2c1 {
    /* ... other i2c1 configurations ... */

    ssd1306@3c { // The '@3c' specifies the I2C address 0x3c
        compatible = "ssd1306"; // This string matches the of_match_table in the driver
        reg = <0x3c>;          // Also specifies the I2C address
        // Other properties might be needed depending on your specific hardware setup
    };
};

请注意,即使使用了设备树,i2c_device_id表也建议保留,尤其是在较旧的Linux内核版本中,它可能仍用于驱动与设备的匹配。

修改完设备树源文件后,需要重新编译设备树(在内核根目录下执行$ make dtbs),并将编译生成的新设备树文件(通常是.dtb格式)替换掉开发板上的设备树文件。重启开发板后,I2C核心将根据设备树实例化i2c_client,并调用我们驱动的probe函数。

验证

如果一切配置正确,在开发板启动并加载模块后,您应该能在内核日志中看到类似"SSD1306 detected at address 0x3c!"和"SSD1306 initialized successfully."的输出。同时,SSD1306 OLED屏幕应该会被点亮并完成初始化。

本文提供了一个基础的Linux I2C设备驱动开发示例。实际应用中可能需要根据SSD1306的具体功能和您的项目需求,进一步完善驱动的API,例如添加显示字符串、绘制图形等功能。

返回列表

上一篇:Web框架基础与实现

没有最新的文章了...

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。