I2C子系统之驱动SSD1306 OLED

Posted hackfun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了I2C子系统之驱动SSD1306 OLED相关的知识,希望对你有一定的参考价值。

理解I2C设备驱动框架,主要围绕四个结构体去分析就容易了。

struct i2c_algorithm:提供I2C协议的实现的操作,如:master_xfer实现数据收发的最基本方法。

struct i2c_adapter:每一个i2c_adapter都代表一个I2C物理接口,一个cpu可以有多个I2C接口(i2c_adapter),i2c_algorithm就是为i2c_adapter提供I2C协议的实现。每增加一个i2c接口,即是向i2c_bus_type (i2c_core.c)注册一个i2c_adapter

struct i2c_driver:代表着一类I2C从机设备的驱动,比如:at24cxx的驱动,不同类型的I2C从机需要注册不同的i2c_driver,如:ssd1306的驱动不同于at24cxx的驱动。每增加一个类型的I2C从机设备,都要向i2c_bus_type (i2c_core.c)注册一个i2c_driver

struct i2c_client:代表具体的某一个I2C从机设备,如:at24cxx系列的设备,有at24c01,at24c02等,每增加一个at24cxx设备,都要注册一个i2c_client。只有I2C从机设备被探测到,i2c_client才会被注册。

这四者的关系可以分为:i2c_algorithm和i2c_adapter一起驱动I2C总线,i2c_driver和i2c_client一起实现设备驱动。

注:linux目前只支持I2C主机模式。本文引用内核源码中i2c-algo-bit.c和i2c-gpio.c文件来讲解, i2c_driver由驱动开发者根据特定的设备提供,这里引用作者提供的ssd1306.c。i2c-algo-bit.c和i2c-gpio.c共同实现IO模拟I2C。

i2c-algo-bit.c提供了一个i2c_algorithm,i2c-gpio.c提供了一个i2c_adapter。

i2c-algo-bit.c通过以下代码绑定到i2c-gpio.c

i2c-algo-bit.c

 1 static const struct i2c_algorithm i2c_bit_algo = {
 2     .master_xfer    = bit_xfer,
 3     .functionality  = bit_func,
 4 };
 5 
 6 static int i2c_bit_prepare_bus(struct i2c_adapter *adap)
 7 {
 8     ... ...
 9     adap->algo = &i2c_bit_algo;
10     ... ...
11     return 0;
12 }
13 
14 int i2c_bit_add_bus(struct i2c_adapter *adap)
15 {
16     ... ...
17     err = i2c_bit_prepare_bus(adap);
18     ... ...
19     return i2c_add_adapter(adap);
20 }

i2c-gpio.c

 1 static int __init i2c_gpio_probe(struct platform_device *pdev)
 2 {
 3     struct i2c_gpio_platform_data *pdata;
 4     struct i2c_algo_bit_data *bit_data;
 5     struct i2c_adapter *adap;
 6     ... ...
 7     pdata = pdev->dev.platform_data;
 8     ... ...
 9     i2c_bit_add_bus(adap);
10     ... ...
11 }

 

这里就注册了一个i2c_adapter。

要驱动ssd1306,因此对应地要提供一个i2c_driver,与i2c_adapter建立关系。

ssd1306.c

 1 static struct i2c_driver ssd1306_driver = {
 2     .driver = {
 3             .name   = "ssd1306",
 4         },
 5         .id     = I2C_DRIVERID_I2CDEV,
 6         .attach_adapter = ssd1306_attach_adapter,
 7         .detach_client  = ssd1306_detach_client,
 8 };
 9 
10 static int ssd1306_module_init(void)
11 {
12     i2c_add_driver(&ssd1306_driver);
13     return 0;
14 }

i2c_driver和i2c_adapter是怎样建立关系的呢?

i2c_bus_type (i2c_core.c)负责桥接i2c_driver和i2c_adapter建立关系,在i2c_driver和i2c_adapter注册的时候,两者都会调用driver->attach_adapter(adapter)

 1 int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
 2 {
 3     ... ...
 4     driver->attach_adapter(adapter);
 5     ... ...
 6     return 0;
 7 }
 8 
 9 static int i2c_register_adapter(struct i2c_adapter *adap)
10 {
11     ... ...
12     driver->attach_adapter(adap);
13     ... ...
14 }

 

driver->attach_adapter(adapter)实际上调用

1 static int ssd1306_attach_adapter(struct i2c_adapter *adapter)
2 {
3     return i2c_probe(adapter, &addr_data, ssd1306_detect);
4 }

 

I2c_probe()函数的作用就是,探测是否存在ssd1306这个设备,是怎样探测的呢?就是通过发送从机地址到ssd1306,如果ssd1306返回应答信号,就认为探测到了。

 1 int i2c_probe(struct i2c_adapter *adapter,
 2           struct i2c_client_address_data *address_data,
 3           int (*found_proc) (struct i2c_adapter *, int, int))
 4 {
 5     ... ...
 6     i2c_probe_address(adapter,
 7                     address_data->probe[i + 1],
 8                     -1, found_proc);
 9     ... ...
10 }

 

代码太多,简化函数调用关系如下:

1 i2c_probe_address()
2     i2c_smbus_xfer()
3         i2c_smbus_xfer_emulated();
4             i2c_transfer();
5                 adap->algo->master_xfer(adap,msgs,num);

adap->algo->master_xfer(adap,msgs,num);实际调用的是bit_xfer()

 

探测到ssd1306后,其实也就说明了探测到的I2C地址有效, 还需要注册一个描述SSD1306的i2c_client。

 1 static int ssd1306_detect(struct i2c_adapter *adapter, int address, int kind)
 2 {   
 3     printk("ssd1306_detect\\n");
 4 
 5     ssd1306_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
 6     ssd1306_client->addr    = address;
 7     ssd1306_client->adapter = adapter;
 8     ssd1306_client->driver  = &ssd1306_driver;
 9     strcpy(ssd1306_client->name, "ssd1306");
10     
11     i2c_attach_client(ssd1306_client);
12     
13     ... ...
14 }   

 

先转下话题。

在i2c-gpio.c中,

1 static int __init i2c_gpio_init(void)
2 {
3     ... ...
4     ret = platform_driver_probe(&i2c_gpio_driver, i2c_gpio_probe);
5     ... ...
6 }

 

这里实际上是注册了一个platform_driver,我们还要对应的为他注册一个platform_device,

这个platform_device提供了硬件相关的设置,如指定那两个io口为SCL和SDA。

I2c_gpio_dev.c中

 1 static struct i2c_gpio_platform_data i2c_dev = {
 2     .sda_pin = S3C2410_GPG6,
 3     .scl_pin = S3C2410_GPG5,
 4     .udelay = 0,
 5     .timeout = 0,
 6     .sda_is_open_drain = 1,
 7     .scl_is_open_drain = 1,
 8     .scl_is_output_only = 1
 9 };
10 
11 static struct platform_device i2c_platform_dev = {
12     .name         = "i2c-gpio",
13     .id           = -1,
14     .dev = { 
15         .release = i2c_dev_release,
16         .platform_data = (void *)&i2c_dev,
17     },
18 };
19 
20 static int i2c_dev_init(void)
21 {
22     platform_device_register(&i2c_platform_dev);
23     return 0;
24 }

 

如果platform_device和platform_driver匹配,就会调用i2c_gpio_probe()

 1 static int __init i2c_gpio_probe(struct platform_device *pdev)
 2 {
 3     struct i2c_gpio_platform_data *pdata;
 4     struct i2c_algo_bit_data *bit_data;
 5     struct i2c_adapter *adap;
 6     ... ...
 7     pdata = pdev->dev.platform_data;
 8     ... ...
 9     i2c_bit_add_bus(adap);
10     ... ...
11 }

 

只有platform_device和platform_driver匹配才能注册i2c_adapter。

到这里,就可以操作ssd1306了。ssd1306写一个字节的操作:

 1 static void ssd1306_write_byte(uint8_t chData, uint8_t chCmd) 
 2 {
 3     uint8_t cmd = 0x00;
 4     
 5     if (chCmd) {
 6         cmd = 0x40;
 7     } else {
 8         cmd = 0x00;
 9     }
10 
11     i2c_smbus_write_byte_data(ssd1306_client, cmd, chData);
12 }

 

实际上调用了i2c_smbus_write_byte_data()  

I2c_core.c提供了几个I2C的读写函数:

1 s32 i2c_smbus_write_byte_data(struct i2c_client *client, u8 command, u8 value);
2 s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 command);
3 ... ...
4 s32 i2c_smbus_read_i2c_block_data(struct i2c_client *client, u8 command, u8 *values);
5 s32 i2c_smbus_write_i2c_block_data(struct i2c_client *client, u8 command,
6                    u8 length, const u8 *values)

 

运行代码

注:由于源码的i2c-gpio-bit.c只支持具有开漏输入输出功能的IO模拟I2C, 而我的开发板已经没有具有开漏输入输出功能的IO了,只能使用普通的上啦输入输出IO,对SDA的读写操作,需要切换输入输出方向。因此我把i2c-gpio-bit.c改成普通IO操作SDA,命名为my-i2c-gpio-bit.c,同时i2c-gpio-bit.h和i2c-gpio.c也要做相应改动,分别改为my-i2c-gpio-bit.h和my-i2c-gpio.c。如果使用具有开漏输入输出功能的IO,可以直接使用i2c-gpio-bit.c,i2c-gpio-bit.h,i2c-gpio.c。

 

 

代码

i2c_gpio_dev.c

 1 #include <linux/module.h>
 2 #include <linux/version.h>
 3 
 4 #include <linux/init.h>
 5 
 6 #include <linux/kernel.h>
 7 #include <linux/types.h>
 8 #include <linux/interrupt.h>
 9 #include <linux/list.h>
10 #include <linux/timer.h>
11 #include <linux/init.h>
12 #include <linux/serial_core.h>
13 #include <linux/platform_device.h>
14 #include <linux/gpio_keys.h>
15 #include <linux/input.h>
16 #include <linux/irq.h>
17 #include <linux/i2c-gpio.h>
18 
19 #include <asm/gpio.h>
20 #include <asm/io.h>
21 #include <asm/arch/regs-gpio.h>
22 
23 
24 /* [cgw]:  */
25 
26 static struct i2c_gpio_platform_data i2c_dev = {
27     .sda_pin = S3C2410_GPG6,
28     .scl_pin = S3C2410_GPG5,
29     .udelay = 0,
30     .timeout = 0,
31     .sda_is_open_drain = 1,
32     .scl_is_open_drain = 1,
33     .scl_is_output_only = 1
34 };
35 
36 static void i2c_dev_release(struct device * dev)
37 {
38     printk("i2c_dev_release! \\n");
39 }
40 
41 /* [cgw]: 分配一个平台设备 */
42 static struct platform_device i2c_platform_dev = {
43     .name         = "i2c-gpio",
44     .id           = -1,
45     .dev = { 
46         .release = i2c_dev_release,
47         .platform_data = (void *)&i2c_dev,
48     },
49 };
50 
51 
52 static int i2c_dev_init(void)
53 {
54     /* [cgw]: 注册i2c_platform_dev平台设备 */
55     platform_device_register(&i2c_platform_dev);
56     return 0;
57 }
58 
59 static void i2c_dev_exit(void)
60 {
61     /* [cgw]: 注销i2c_platform_dev平台设备 */
62     platform_device_unregister(&i2c_platform_dev);
63 }
64 
65 module_init(i2c_dev_init);
66 module_exit(i2c_dev_exit);
67 
68 MODULE_LICENSE("GPL");


ssd1306.c

  1 #include <linux/kernel.h>
  2 #include <linux/init.h>
  3 #include <linux/module.h>
  4 #include <linux/slab.h>
  5 #include <linux/jiffies.h>
  6 #include <linux/i2c.h>
  7 #include <linux/mutex.h>
  8 #include <linux/fs.h>
  9 #include <asm/uaccess.h>
 10 
 11 
 12 #define SSD1306_CMD    0
 13 #define SSD1306_DAT    1
 14 
 15 #define SSD1306_WIDTH    128
 16 #define SSD1306_HEIGHT   64
 17 
 18 static uint8_t s_chDispalyBuffer[128][8];
 19 
 20 const uint8_t c_chFont1608[95][16] = {      
 21 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
 22 {0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xCC,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/
 23 {0x00,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x00,0x00},/*""",2*/
 24 {0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x00,0x00},/*"#",3*/
 25 {0x00,0x00,0x0E,0x18,0x11,0x04,0x3F,0xFF,0x10,0x84,0x0C,0x78,0x00,0x00,0x00,0x00},/*"$",4*/
 26 {0x0F,0x00,0x10,0x84,0x0F,0x38,0x00,0xC0,0x07,0x78,0x18,0x84,0x00,0x78,0x00,0x00},/*"%",5*/
 27 {0x00,0x78,0x0F,0x84,0x10,0xC4,0x11,0x24,0x0E,0x98,0x00,0xE4,0x00,0x84,0x00,0x08},/*"&",6*/
 28 {0x08,0x00,0x68,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"\'",7*/
 29 {0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x18,0x18,0x20,0x04,0x40,0x02,0x00,0x00},/*"(",8*/
 30 {0x00,0x00,0x40,0x02,0x20,0x04,0x18,0x18,0x07,0xE0,0x00,0x00,0x00,0x00,0x00,0x00},/*")",9*/
 31 {0x02,0x40,0x02,0x40,0x01,0x80,0x0F,0xF0,0x01,0x80,0x02,0x40,0x02,0x40,0x00,0x00},/*"*",10*/
 32 {0x00,0x80,0x00,0x80,0x00,0x80,0x0F,0xF8,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x00},/*"+",11*/
 33 {0x00,0x01,0x00,0x0D,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*",",12*/
 34 {0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80},/*"-",13*/
 35 {0x00,0x00,0x00,0x0C,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*".",14*/
 36 {0x00,0x00,0x00,0x06,0x00,0x18,0x00,0x60,0x01,0x80,0x06,0x00,0x18,0x00,0x20,0x00},/*"/",15*/
 37 {0x00,0x00,0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"0",16*/
 38 {0x00,0x00,0x08,0x04,0x08,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"1",17*/
 39 {0x00,0x00,0x0E,0x0C,0x10,0x14,0x10,0x24,0x10,0x44,0x11,0x84,0x0E,0x0C,0x00,0x00},/*"2",18*/
 40 {0x00,0x00,0x0C,0x18,0x10,0x04,0x11,0x04,0x11,0x04,0x12,0x88,0x0C,0x70,0x00,0x00},/*"3",19*/
 41 {0x00,0x00,0x00,0xE0,0x03,0x20,0x04,0x24,0x08,0x24,0x1F,0xFC,0x00,0x24,0x00,0x00},/*"4",20*/
 42 {0x00,0x00,0x1F,0x98,0x10,0x84,0x11,0x04,0x11,0x04,0x10,0x88,0x10,0x70,0x00,0x00},/*"5",21*/
 43 {0x00,0x00,0x07,0xF0,0x08,0x88,0x11,0x04,0x11,0x04,0x18,0x88,0x00,0x70,0x00,0x00},/*"6",22*/
 44 {0x00,0x00,0x1C,0x00,0x10,0x00,0x10,0xFC,0x13,0x00,0x1C,0x00,0x10,0x00,0x00,0x00},/*"7",23*/
 45 {0x00,0x00

以上是关于I2C子系统之驱动SSD1306 OLED的主要内容,如果未能解决你的问题,请参考以下文章

用于 OLED 屏幕的 Debian I2C 驱动程序无法正常工作。 [ssd1306]

STC单片机驱动ssd1306 I2C oled屏幕

Arduino RP2040 驱动ssd1306 I2C OLED屏幕

STC8单片机基于开源库驱动ssd1306 i2c oled例程

STC单片机硬件I2C驱动ssd1306 OLED显示DS18B20温度

Arduino STM32F103C8T6 驱动ssd1306 I2C oled