程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux驱动移植-I2C驱动移植(OLED SSD1306)

在之前Mini2440裸机开发之SPI(OLED SSD1306)中我们介绍了关于OLED SSD1306相关的知识,这里我们将会学习以内核驱动的方式去控制OLED。

无论是AT24C08设备驱动还是这一节将要学习的OLED驱动,整体框架大体都是一样的。

一、OLED128x64(SSD1306)

对于OLED来说,采用I2C协议,要比SPI协议少了片选信号、以及命令数据选择引脚。当采用I2C通信时,只需要SCL、SDA、GND、VCC即可。

1.1 设备地址

SSD 1306设备地址:

b7 b6 b5 b4 b3 b2 b1 b0
0 1 1 1 1 0 SA0 R/W

“SA0”位为从属地址提供扩展位。“0111100”或“0111101”,可以是选择为SSD1306的从地址。D/C#引脚用作从地址选择的SA0。这里我们购买的芯片SA0=0。所以从设备7位地址为0x3C.

“R/W#”位用于确定I2C总线接口的操作模式。R/W#=1,处于读模式,模式R/W#=0,处于写入模式。

1.2 写命令

I2C总线接口允许将数据和命令写入设备,如图所示:

SPI通信时,写命令需要将DC设置为低电平,然后发送一个字节的命令即可;而I2C通信则需要发送两个字节数据,第一个字节是0x00,第二个字节才是命令:

/*************************************************************
*
*  Function  : 通过I2C协议写命令
*  Input     : data 数据
*  Return    : 写入字节长度  负数写入失败
*
**************************************************************/
static int oled_write_cmd(unsigned char cmd)
{

    struct i2c_msg msg[1];
    unsigned char cmds[2];

    cmds[0] = 0x00;
    cmds[1] = cmd;

    /* 数据传输三要素:源,目的,长度 */
    msg[0].addr = oled_client->addr;     /* 目的 */
    msg[0].buf    = cmds;                 /**/
    msg[0].len    = 2;                    /* buf长度 */
    msg[0].flags= 0;                     /* 0表示写 */

    if(i2c_transfer(oled_client->adapter,msg,1) == 1)
        return 2;
    else
        return ERROR;
}

1.3 写数据

SPI通信时,写数据需要将DC设置为高电平,然后发送一个字节的数据即可;而I2C通信则需要发送两个字节数据,第一个字节是0x40,第二个字节才是数据:

/*************************************************************
*
*  Function  : 通过I2C协议写数据
*  Input     : data 数据
*  Return    : 写入字节长度  负数写入失败
*
**************************************************************/
static int oled_write_data(unsigned char data)
{
    struct i2c_msg msg[1];
    unsigned char datas[2];

    datas[0] = 0x40;
    datas[1] = data;

    /* 数据传输三要素:源,目的,长度 */
    msg[0].addr = oled_client->addr;  /* 目的 */
    msg[0].buf    = datas;              /**/
    msg[0].len    = 2;                 /*  buf长度 */
    msg[0].flags= 0;                   /* 0表示写 */

    if(i2c_transfer(oled_client->adapter,msg,1) == 1)
        return 2;
    else
        return ERROR;
}

 1.4 其它

针对OLED的其他操作,比如清屏、初始化、设置坐标等,I2C协议和SPI协议的时序都是一样。这里也就不重复介绍了。

二、OLED设备驱动

2.1 项目结构

我们在/work/sambashare/drivers路径下创建项目21.oled_dev。

2.2 I2C驱动编写流程

2.2.1 模块入口函数
  • 声明i2c_driver结构体变量:调用i2c_add_driver注册I2C驱动;
    • 设置成员probe:当驱动和设备信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中;
    • 设置成员remove:设备被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中;
    • 设置成员driver:设置驱动名称、owner等;
    • 设置成员address_list:里面存放当前驱动需要探测的从设备的地址;
    • 设置成员detect:当address_list里面中的从设备地址探测成功后,会回调detect函数,在该函数里面我们需要初始化i2c_board_info成员type(I2C从设备名称,需要与I2C驱动的名称匹配);
    • 设置成员id_table:id列表,用于和I2C从设备名称进行匹配;
    • 设置成员class:一般设置为I2C_CLASS_HWMON | I2C_CLASS_SPD即可;
2.2.2  模块出口函数
  • 调用i2c_del_driver卸载I2C驱动;
2.2.3 probe函数

一般就是字符设备的注册流程:

  • 动态分配字符设备编号;
  • 字符设备初始化以及注册;
  • 创建类、以及在/dev路径下生成设备节点;
  • 保存当前i2c_client;
2.2.4 remove函数

一般就是字符设备的卸载流程:

  • 注销类、以及类下设备;
  • 移除字符设备;
  • 注销设备编号;
2.2.5 字符设备读写等函数

由于我们为I2C从设备注册了字符设备驱动,这样我们通过对字符设备节点读写,就可实现对硬件的读写操作。

我们需要实现字符设备的操作集合。这里由于我们的设备是OLED,也就是对各种I2C设备的写操作,这一部分是通过控制I2C适配器来实现的,我们按照芯片写指令时序进行I2C传输控制即可。

当然如果我们想对OLED设备进行一些其它的控制,比如清屏、设置当前坐标,则可以使用ioctl函数来实现。具体可以参考Linux驱动之字符设备ioctl接口使用

2.3 oled_drv.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>

/*
 全局静态变量:希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在
 定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用
 */
#define OK   (0)
#define ERROR  (-1)

/* I2C设备驱动 */
static struct i2c_driver oled_driver;

/* I2C从设备 */
static struct i2c_client *oled_client;

 /* i2c设备地址 */
static unsigned short addr = 0x3C;

/* i2c驱动需要探测的从设备地址 */
static unsigned short address_list[] = { 0x3c,0xfffeU };  // 0xfffeU 结束标志

/* 设备编号 */
static dev_t devid;

/* 设备class */
static struct class *oled_class;

 /* oled字符设备哦 */
static struct cdev i2c_cdev;



/*****************************************************************************************************
 *
 *        从ASCII0x20开始   取模方向:逐列式  每列1个字节,共6列    即一个字节占8行6列
 *      取模走向:低位在前
 *
 *****************************************************************************************************/
static const u8 ASCII6x8[][6] =
{
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },   // sp          ASCII 0x20
    { 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 },   // !
    { 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 },   // "
    { 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 },   // #
    { 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 },   // $
    { 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 },   // %
    { 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 },   // &
    { 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 },   // '
    { 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 },   // (
    { 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 },   // )
    { 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 },   // *
    { 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 },   // +
    { 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 },   // ,
    { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 },   // -
    { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 },   // .
    { 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 },   // /
    { 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E },   // 0
    { 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 },   // 1
    { 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 },   // 2
    { 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 },   // 3
    { 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 },   // 4
    { 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 },   // 5
    { 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 },   // 6
    { 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 },   // 7
    { 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 },   // 8
    { 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E },   // 9
    { 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 },   // :
    { 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 },   // ;
    { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 },   // <
    { 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 },   // =
    { 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 },   // >
    { 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 },   // ?
    { 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E },   // @
    { 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C },   // A
    { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 },   // B
    { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 },   // C
    { 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C },   // D
    { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 },   // E
    { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 },   // F
    { 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A },   // G
    { 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F },   // H
    { 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 },   // I
    { 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 },   // J
    { 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 },   // K
    { 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 },   // L
    { 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F },   // M
    { 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F },   // N
    { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E },   // O
    { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 },   // P
    { 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E },   // Q
    { 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 },   // R
    { 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 },   // S
    { 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 },   // T
    { 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F },   // U
    { 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F },   // V
    { 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F },   // W
    { 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 },   // X
    { 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 },   // Y
    { 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 },   // Z
    { 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 },   // [
    { 0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 },   // 55
    { 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 },   // ]
    { 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 },   // ^
    { 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 },   // _
    { 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 },   // '
    { 0x00, 0x20, 0x54, 0x54, 0x54, 0x78 },   // a
    { 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 },   // b
    { 0x00, 0x38, 0x44, 0x44, 0x44, 0x20 },   // c
    { 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F },   // d
    { 0x00, 0x38, 0x54, 0x54, 0x54, 0x18 },   // e
    { 0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 },   // f
    { 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C },   // g
    { 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 },   // h
    { 0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 },   // i
    { 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 },   // j
    { 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 },   // k
    { 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 },   // l
    { 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 },   // m
    { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 },   // n
    { 0x00, 0x38, 0x44, 0x44, 0x44, 0x38 },   // o
    { 0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 },   // p
    { 0x00, 0x18, 0x24, 0x24, 0x18, 0xFC },   // q
    { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 },   // r
    { 0x00, 0x48, 0x54, 0x54, 0x54, 0x20 },   // s
    { 0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 },   // t
    { 0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C },   // u
    { 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C },   // v
    { 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C },   // w
    { 0x00, 0x44, 0x28, 0x10, 0x28, 0x44 },   // x
    { 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C },   // y
    { 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 },   // z
    { 0x14, 0x14, 0x14, 0x14, 0x14, 0x14 }    // horiz lines
};

/*************************************************************
*
*  Function  : 通过I2C协议写命令
*  Input     : data 数据
*  Return    : 写入字节长度  负数写入失败
*
**************************************************************/
static int oled_write_cmd(unsigned char cmd)
{

    struct i2c_msg msg[1];
    unsigned char cmds[2];

    cmds[0] = 0x00;
    cmds[1] = cmd;

    /* 数据传输三要素:源,目的,长度 */
    msg[0].addr = oled_client->addr;     /* 目的 */
    msg[0].buf    = cmds;                       /**/
    msg[0].len    = 2;                    /*  buf长度 */
    msg[0].flags= 0;                     /* 0表示写 */

    if(i2c_transfer(oled_client->adapter,msg,1) == 1)
        return 2;
    else
        return ERROR;
}

/*************************************************************
*
*  Function  : 通过I2C协议写数据
*  Input     : data 数据
*  Return    : 写入字节长度  负数写入失败
*
**************************************************************/
static int oled_write_data(unsigned char data)
{
    struct i2c_msg msg[1];
    unsigned char datas[2];

    datas[0] = 0x40;
    datas[1] = data;

    /* 数据传输三要素:源,目的,长度 */
    msg[0].addr = oled_client->addr;  /* 目的 */
    msg[0].buf    = datas;              /**/
    msg[0].len    = 2;                 /*  buf长度 */
    msg[0].flags= 0;                   /* 0表示写 */

    if(i2c_transfer(oled_client->adapter,msg,1) == 1)
        return 2;
    else
        return ERROR;
}

/*************************************************************
*
*  Function  : 坐标设定
*  Input     : x x坐标    0x00~0x7F
*              y y坐标    0~7
*
**************************************************************/
static void oled_pos(u8 x,u8 y)
{
    oled_write_cmd(0xB0+y);
    oled_write_cmd(((x&0xF0)>>4)|0x10);            /* 设置列地址高四位 */
    oled_write_cmd(x&0x0F);                        /* 设置列地址低四位 */
}

/*************************************************************
*
*  Function  : 清屏
*
**************************************************************/
static void oled_clear(void)
{
    u8 x;
    u8 y;
    for(y=0;y<8;y++)
    {
        oled_write_cmd(0xB0+y);            /* 选择页 */
        oled_write_cmd(0x00);              /* 设置列地址低四位 */
        oled_write_cmd(0x10);              /* 设置列地址高四位 */
        for(x=0;x<0x80;x++)
            oled_write_data(0x00);           /* 每次清1列 */
    }
}


/***************************************************************************************************
 *
 *    Function   : 写入一组标准ASCII字符串 一个字节占8行6列
 *    Input      : x 设置列地址0~0X7F
 *                 y 设置页地址0~7
 *                 str 要显示的字符
 *
 **************************************************************************************************/
static void oled_p6x8str(u8 x,u8 y,u8 *str)
{
  u8 i=0;
  u8 j=0;
  u8 k=0;
  while (str[j]!='\0')
  {
    while((str[j]<0x20)||(str[j]>0x80))     /* 当写入的没有对应的点阵时 显示空格 */
          str[j]=32;
    k =str[j]-32;
    if(x>121)
    {
         x=0;
         y++;
    }
    oled_pos(x,y);                           /* 选中坐标 */
    for(i=0;i<6;i++)
         oled_write_data(ASCII6x8[k][i]);        /* 写入一个字节 */
      x+=6;
      j++;
  }
}


/***************************************************************************************************
 *
 * 打开设备
 *
 **************************************************************************************************/
static int oled_open(struct inode *inode, struct file *filp)
{
     oled_write_cmd(0xAE);            /* 显示关 */
     oled_write_cmd(0x00);            /* 设置列低位地址 */
     oled_write_cmd(0x10);            /* 设置列高位地址 */
     oled_write_cmd(0x40);            /* set start line address  Set Mapping RAM Display Start Line (0x00~0x3F) */
     oled_write_cmd(0x81);            /* 设置对比度 */
     oled_write_cmd(0xCF);            /* 值越大 越亮 */
     oled_write_cmd(0xA1);            /* 设置列左右反置     0xa0左右反置 0xa1正常 */
     oled_write_cmd(0xC8);            /* 设置行上下反置     0xc0上下反置 0xc8正常 */
     oled_write_cmd(0xA6);            /* 设置正常显示 */
     oled_write_cmd(0x20);            /* 设置页地址模式 (0x00/0x01/0x02) */
     oled_write_cmd(0x02);            /* 页寻址 */
     oled_write_cmd(0x8D);            /* 设置电荷磊开关 */
     oled_write_cmd(0x14);            /* 电荷磊开 */
     oled_write_cmd(0xA4);            /* 字符显示开关 0xA4:开  0xA5:关 */
     oled_write_cmd(0xA6);            /* 背景色显示开关  0xA6:关   0xA7:开 */
     oled_write_cmd(0xAF);            /* 显示开 */

    oled_clear();                     /* 初始清屏 */
    oled_pos(0,0);

    printk(KERN_INFO "oled open\n");
    return 0;
}

/***************************************************************************************************
 *
 *   Function:    oled写函数
 *   Input       : buffer 2个字节 第一个字节oled片内地址0~0xFF,第二个字节为要写入的数据
 *                 size  写数据的长度  两个字节
 *
 **************************************************************************************************/
static ssize_t oled_write(struct file *filp, const char __user *buffer, size_t size, loff_t *off)
{
    // 一共可以显示168字
    unsigned char data[168];
    copy_from_user(data,buffer,168);
    oled_p6x8str(0,0,data);
    return 0;
}

/*
 * oled操作集
 */
static struct file_operations oled_fops = {
        .owner = THIS_MODULE,
        .write = oled_write,
        .open = oled_open,
};

/*
* 当驱动和设备信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中;
*/
static int oled_probe(struct i2c_client *client, const struct i2c_device_id *i2c_device)
{
        int result;
         /* 动态分配字符设备编号: (major,0) */
        if(alloc_chrdev_region(&devid, 0, 1, "oled") == OK){
             printk(KERN_INFO "alloc device number : major:[%d], minor:[%d] succeed!\n", MAJOR(devid), MINOR(devid));
        }else{
              printk(KERN_INFO "register device number error!\n");
              return ERROR;
         }

        /* 字符设备初始化以及注册 */
        cdev_init(&i2c_cdev, &oled_fops);
        cdev_add(&i2c_cdev, devid, 1);


        /* 创建类,它会在sys目录下创建/sys/class/oled_class这个类  */
        oled_class = class_create(THIS_MODULE, "oled_class");
        if(IS_ERR(oled_class)){
            printk("can't create class\n");
            return ERROR;
       }

        /*  在/sys/class/oled_class下创建oled设备,然后mdev通过这个自动创建/dev/oled这个设备节点 */
        device_create(oled_class, NULL, devid, NULL, "oled");
        printk(KERN_INFO "create device file 'oled' succeed!\n");

        /* 保存当前i2c_client */
        oled_client = client;
        printk(KERN_INFO "get i2c_client, client name = %s, addr = 0x%x\n", oled_client->name, oled_client->addr);
        printk(KERN_INFO "get i2c_adapter, adapter name = %s\n", oled_client->adapter->name);

        printk(KERN_INFO "oled probe\n");
        return 0;
}

/*
 * 设备被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中
 */
static int oled_remove(struct i2c_client *client)
{
        /* 注销类、以及类设备 /sys/class/oled_class会被移除*/
        device_destroy(oled_class, devid);
        class_destroy(oled_class);

        /* 移除字符设备 */
        cdev_del(&i2c_cdev);

        /* 注销设备编号 */
        unregister_chrdev_region(devid, 1);
        printk(KERN_INFO "oled remove\n");
        return 0;
}


/*
 * oled设备探测到的回调函数
 */
static int oled_detect(struct i2c_client *client, struct i2c_board_info *info)
{
    printk(KERN_INFO "oled detect success\n");

    // 设置I2C从设备名称 必须要和i2c_driver.idtable里面名称匹配
   strcpy(info->type,"oled");
   return OK;
}


/* i2c设备id列表 */
static const struct i2c_device_id oled_id[] = {
        { "oled", 0 },
        { } /* 最后一个必须为空,表示结束 */
};


/*
 * i2c驱动入口函数
*/
static struct i2c_driver oled_driver = {
        .probe = oled_probe,
        .remove = oled_remove,
        .driver = {
                .name = "oled",      /* 驱动名称 */
                .owner = THIS_MODULE,
        },
        .address_list = &address_list,   /* 从设备列表 */
        .detect = oled_detect,
        .id_table = oled_id, /* id列表 */
        .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,       /* 内存设备 */
};


/*
 *init入口函数
*/
static int __init oled_init(void)
{
        i2c_add_driver(&oled_driver); /* 将i2c_driver注册到系统中去 */
        printk(KERN_INFO "oled i2c_driver was added into the system.\n");
        return 0;
}

/*
 * exit出口函数
 */
static void __exit oled_exit(void)
{
        i2c_del_driver(&oled_driver);
        printk(KERN_INFO "oled i2c_driver was deleted from the system.\n");
        return ;
}


module_init(oled_init);
module_exit(oled_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("oled device driver, 2023-02-21");

2.4 Makefile

KERN_DIR :=/work/sambashare/linux-5.2.8
all:
    make -C $(KERN_DIR) M=`pwd` modules
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order

obj-m += oled_drv.o

2.5 修改I2C适配器驱动

由于S3C2440 I2C适配器class配置为了I2C_CLASS_DEPRECATED,如果设置为这个,表示该I2C控制将不支持自动检测I2C从设备,即不再支持i2c_detect。

因此需要修改drivers/i2c/busses/i2c-s3c2410.c文件,将s3c24xx_i2c_probe中 i2c->adap.class = I2C_CLASS_DEPRECATED,修改为:I2C_CLASS_HWMON |  I2C_CLASS_DDC | I2C_CLASS_SPD。

i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_DDC | I2C_CLASS_SPD;

这样当我们去注册I2C驱动的时候,将会遍历I2C总线上的所有适配器,查看有没有适配器与自身i2c_driver的class成员匹配,如果有将会使用i2c_detect_address函数来确定能否找到address_list里的从设备,如果找到了,则会回调detect方法,并会自动注册新的i2c_client从设备。

三、OLED驱动测试应用程序

在21.oled_dev下创建test文件夹,保存测试应用程序。

3.1 main.c

#include <stdio.h>
#include <fcntl.h>

int main(int argc,char **argv)
{
    int fd; 
    unsigned char buf[168] = {0};
    int i;
    fd = open("/dev/oled",O_RDWR);   /* 打开字符设备/dev/oled */
    if(fd < 0){
        printf(" can't open /dev/oled \n\r");
        return -1;
    }
 
    write(fd,argv[1],168);        /* 将接收的第二个字符串传入内核的字符设备 */
 
    return 0;
 
}

3.2 Makefile

all:
    arm-linux-gcc -march=armv4t -o main main.c
clean:
    rm -rf *.o main

四、烧录开发板测试

4.1 编译内核

编译内核:

make s3c2440_defconfig
make V=1 uImage

将uImage复制到tftp服务器路径下:

root@zhengyang:/work/sambashare/linux-5.2.8# cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

4.2 烧录内核

开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,可以通过print查看uboot中已经设置的环境变量。

设置开发板ip地址,从而可以使用网络服务:

SMDK2440 # set ipaddr 192.168.0.105
SMDK2440 # save
Saving Environment to NAND...
Erasing NAND...

Erasing at 0x40000 -- 100% complete.
Writing to NAND... OK
SMDK2440 # ping 192.168.0.200
dm9000 i/o: 0x20000000, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 08:00:3e:26:0a:5b
operating at unknown: 0 mode
Using dm9000 device
host 192.168.0.200 is alive

设置tftp服务器地址,也就是我们ubuntu服务器地址:

set serverip 192.168.0.200
save

下载内核到内存,并写入NAND FLASH:

tftp 30000000 uImage
nand erase.part kernel
nand write 30000000 kernel
bootm

4.3 编译驱动

执行make命令编译驱动,并将驱动程序拷贝到nfs文件系统:

root@zhengyang:/# cd /work/sambashare/drivers/21.oled_dev/
root@zhengyang:/work/sambashare/drivers/21.oled_dev# make
root@zhengyang:/work/sambashare/drivers/21.oled_dev# cp oled_drv.ko /work/nfs_root/rootfs/

安装驱动:

[root@zy:/]# insmod oled_drv.ko
oled detect success
alloc device number : major:[248], minor:[0] succeed!
create device file 'oled' succeed!
get i2c_client, client name = oled, addr = 0x3c
get i2c_adapter, adapter name = s3c2410-i2c
oled probe
oled i2c_driver was added into the system.

查看设备节点文件:

[root@zy:/]# ls /dev/oled -l
crw-rw----    1 0        0         248,   0 Jan  1 00:11 /dev/oled
[root@zy:/]# ls /sys/devices/platform/s3c2440-i2c.0/i2c-0/ -l
total 0
drwxr-xr-x    3 0        0                0 Jan  1 00:11 0-003c
drwxr-xr-x    3 0        0                0 Jan  1 00:00 0-0050
--w-------    1 0        0             4096 Jan  1 00:00 delete_device
lrwxrwxrwx    1 0        0                0 Jan  1 00:00 device -> ../../s3c2440-i2c.0
-r--r--r--    1 0        0             4096 Jan  1 00:00 name
--w-------    1 0        0             4096 Jan  1 00:00 new_device
drwxr-xr-x    2 0        0                0 Jan  1 00:00 power
lrwxrwxrwx    1 0        0                0 Jan  1 00:00 subsystem -> ../../../../bus/i2c
-rw-r--r--    1 0        0             4096 Jan  1 00:00 uevent
[root@zy:/]# ls /sys/devices/platform/s3c2440-i2c.0/i2c-0/0-003c/ -l
total 0
lrwxrwxrwx    1 0        0                0 Jan  1 00:16 driver -> ../../../../../bus/i2c/drivers/oled
-r--r--r--    1 0        0             4096 Jan  1 00:16 modalias
-r--r--r--    1 0        0             4096 Jan  1 00:16 name
drwxr-xr-x    2 0        0                0 Jan  1 00:16 power
lrwxrwxrwx    1 0        0                0 Jan  1 00:16 subsystem -> ../../../../../bus/i2c
-rw-r--r--    1 0        0             4096 Jan  1 00:11 uevent
[root@zy:/]# ls /sys/bus/i2c/devices -l
total 0
lrwxrwxrwx    1 0        0                0 Jan  1 00:16 0-003c -> ../../../devices/platform/s3c2440-i2c.0/i2c-0/0-003c
lrwxrwxrwx    1 0        0                0 Jan  1 00:16 0-0050 -> ../../../devices/platform/s3c2440-i2c.0/i2c-0/0-0050
lrwxrwxrwx    1 0        0                0 Jan  1 00:16 i2c-0 -> ../../../devices/platform/s3c2440-i2c.0/i2c-0
[root@zy:/]# ls /sys/class/oled_class -l
total 0
lrwxrwxrwx    1 0        0                0 Jan  1 00:16 oled -> ../../devices/virtual/oled_class/oled

4.4 编译测试应用程序

执行make命令编译测试应用程序,并将测试应用程序拷贝到nfs文件系统:

root@zhengyang:/work/sambashare/drivers/21.oled_dev# cd test
root@zhengyang:/work/sambashare/drivers/21.oled_dev/test# make
arm-linux-gcc -march=armv4t -o main main.c
root@zhengyang:/work/sambashare/drivers/21.oled_dev/test# cp ./main /work/nfs_root/rootfs

在开发板终端运行应用程序:

./main  "hello zhengyang"

 运行结果如下:

4.5 卸载OLED驱动

通过用lsmod可以查看当前安装了哪些驱动:

[root@zy:/]# lsmod
oled_dev 3930 0 - Live 0xbf000000 (O)

卸载时直接运行:

[root@zy:/]# rmmod oled_drv
oled remove
oled i2c_driver was deleted from the system.

五、代码下载

Young / s3c2440_project[drivers]

posted @ 2023-02-22 21:54  大奥特曼打小怪兽  阅读(1230)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步