0%

Linux驱动学习实战(7) 使用 I2C 通信协议点亮屏幕

回顾

  我们首先来回顾以下 Linux 内核下的 I2C 驱动的基本框架:I2C 在 Linux 内核中分为设备驱动和适配器驱动,我们主要做的就是编写设备驱动,其包含了基本的设备地址、设备名称等信息。我们需要在文件中创建 i2c_driver 结构体,其中包含了 probe、remove 函数。在 probe 函数中我们完成基本的初始化并且创建设备。当设备被移除,或者驱动卸载时,内核会调用 remove 函数。在 remove 函数里面,就要完成和 probe 相反的清理工作。

  一个典型的 I2C 设备驱动编写流程可以概括为:先定义设备私有结构体,用来保存驱动运行过程中需要的各种信息;然后实现底层的寄存器读写函数,常用的接口有 i2c_master_send、i2c_master_recv,或者更常见、更灵活的 i2c_transfer;接着实现 probe 和 remove;最后定义并注册 i2c_driver 结构体。这样,一个最基本的 I2C 设备驱动框架就搭建完成了。

硬件分析

  和 24、25 次日志中一样,使用 I2C1 作为总线。
  所使用的屏幕驱动芯片硬件地址为 0x3c。

代码编写

  修改设备树,打开 imx6ull-hcw-emmc.dtsi,在 i2c1 子节点下方添加设备,代码如下:

1
2
3
4
5
6
oled@3c{
compatible = "hcw,oled";
reg = <0x3c>;
vdd-supply = <&reg_peri_3v3>;
vddio-supply = <&reg_peri_3v3>;
};

  使用新的设备树启动内核,可以看到屏幕设备成功挂载到了 i2c 总线上。

图 1 设备树修改成功

  创建文件夹,创建 oled.c、oled_i2c.c 文件,我们来一步一步写。打开 oled_i2c.c 文件,我们先来写几个常用的 i2c 通信函数。
  在 oled_i2c.c 文件中写入以下代码,为 I2C 寄存器写入函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/*
* @Author: 胡城玮
* @FilePath: oled_i2c.c
* @Date: 2026-03-17
* @Description:
* @Version: 0.1
*/

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "oled.h"


// oled 不需要读取寄存器,所以只保留 write 函数

static int i2c_write_regs(struct oled_dev *dev, u8 reg, u8 *buf, int len)
{
u8 write_buf[128];
int ret;
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;

write_buf[0] = reg;
memcpy(&write_buf[1], buf,len);

msg.addr = client->addr;
msg.flags = 0;
msg.buf = write_buf;
msg.len = len + 1;

ret = i2c_transfer(client->adapter, &msg, 1);
if(ret != 1)
{
if (ret >= 0)
ret = -EIO;
return ret;
}

return 0;
}

int oled_write_reg(struct oled_dev *dev, u8 reg, u8 *buf, int len)
{
int ret = 0;
ret = i2c_write_regs(dev, reg, buf, len);
if (ret != 0)
{
if (ret == -EIO)
{
printk("write error code:-EIO!!\r\n");
return -EIO;
}
return ret;
}
return 0;
}

  在 oled.c 文件中写入以下内容,只包含了基础的初始化等函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*
* @Author: 胡城玮
* @FilePath: oled.c
* @Date: 2026-03-17
* @Description:
* @Version: 0.1
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "oled.h"
#include "oled_i2c.h"

#define OLED_CNT 1
#define OLED_NAME "HCW_OLED"

static struct oled_dev oleddev;

struct file_operations oled_fops = {

};

static int oled_init(void)
{
oled_write_cmd(&oleddev, 0xAE);
oled_write_cmd(&oleddev, 0x00); // 设置列起始地址低4位
oled_write_cmd(&oleddev, 0x10); // 设置列起始地址高4位
oled_write_cmd(&oleddev, 0x40); // 设置行起始地址
oled_write_cmd(&oleddev, 0xB0); // 设置页起始地址
oled_write_cmd(&oleddev, 0x81); // 对比度设置
oled_write_cmd(&oleddev, 0xFF); // 对比度值(0~255,FF最亮)
oled_write_cmd(&oleddev, 0xA1); // 段重映射(0xA0左右反,0xA1正常)
oled_write_cmd(&oleddev, 0xA6); // 显示模式:0xA6正常显示,0xA7反显
oled_write_cmd(&oleddev, 0xA8); // 多路复用率
oled_write_cmd(&oleddev, 0x3F); // 64行显示
oled_write_cmd(&oleddev, 0xC8); // COM扫描方向(0xC0上下反,0xC8正常)
oled_write_cmd(&oleddev, 0xD3); // 显示偏移
oled_write_cmd(&oleddev, 0x00); // 无偏移
oled_write_cmd(&oleddev, 0xD5); // 时钟分频
oled_write_cmd(&oleddev, 0x80); // 默认值
oled_write_cmd(&oleddev, 0xD9); // 预充电周期
oled_write_cmd(&oleddev, 0xF1); // 默认值
oled_write_cmd(&oleddev, 0xDA); // COM引脚配置
oled_write_cmd(&oleddev, 0x12); // 默认值
oled_write_cmd(&oleddev, 0xDB); // VCOMH电压
oled_write_cmd(&oleddev, 0x40); // 默认值
oled_write_cmd(&oleddev, 0x8D); // 电荷泵
oled_write_cmd(&oleddev, 0x14); // 开启电荷泵
oled_write_cmd(&oleddev, 0xAF); // 开启显示

printk("oled Init success!!!\r\n");
return 0;
}

static int oleddev_probe(struct i2c_client *client, const struct i2c_device_id *id){
printk("matched success\r\n");
int ret = 0;

oleddev.private_data = client;
msleep(500);
oled_init();

oleddev.device = device_create(oleddev.class, NULL, oleddev.devid, NULL, OLED_NAME);
if(IS_ERR(oleddev.device))
{
return PTR_ERR(oleddev.device);
}

printk("OLED%d probe success!",MINOR(oleddev.devid));
return 0;
}

static void oleddev_remove(struct i2c_client *client)
{
device_destroy(oleddev.class, oleddev.devid);
return;
}


static struct of_device_id oled_drv_match_table[] = {
{.compatible = "hcw,oled"},
{},
};

static struct i2c_driver oled_drv = {
.probe = oleddev_probe,
.remove = oleddev_remove,
.driver =
{
.name = "OLED_DRV",
.of_match_table = oled_drv_match_table,
.owner = THIS_MODULE
}
};

static int __init oled_drv_init(void)
{
int ret = 0;

if(oleddev.major)
{
oleddev.devid = MKDEV(oleddev.major, 0);
register_chrdev_region(oleddev.devid, OLED_CNT, OLED_NAME);
}
else
{
alloc_chrdev_region(&oleddev.devid, 0, OLED_CNT, OLED_NAME);
oleddev.major = MAJOR(oleddev.devid);
}

cdev_init(&oleddev.cdev, &oled_fops);
cdev_add(&oleddev.cdev, oleddev.devid, OLED_CNT);

oleddev.class = class_create(THIS_MODULE, OLED_NAME);
if(IS_ERR(oleddev.class))
{
return PTR_ERR(oleddev.class);
}
ret = i2c_add_driver(&oled_drv);

if (ret)
goto destroy_class;

return 0;

destroy_class:
class_destroy(oleddev.class);
cdev_del(&oleddev.cdev);
unregister_chrdev_region(oleddev.devid, OLED_CNT);

return ret;

return 0;
}

static void __exit oled_drv_exit(void)
{
i2c_del_driver(&oled_drv);
unregister_chrdev_region(oleddev.devid, OLED_CNT);
cdev_del(&oleddev.cdev);
class_destroy(oleddev.class);
}

module_init(oled_drv_init);
module_exit(oled_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("hcw");

  编译模块,装载,可以看到 OLED 成功被初始化了,并且点亮。