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]