先上结果
一、利用i2c_board_info结构体描述具体设备
oled设备地址应为0x78 = 0111 1000 。
linux中i2c地址只有7位,应该为 011 1100 =0x3c 。
static struct i2c_board_info oled_i2c_devices = {
I2C_BOARD_INFO("oled", 0x3c),
};
采用的引脚是U4_Tx ,U4_Rx,即I2C1_SCL,I2C_SDA。
注意:如果是在IMX6ULL上验证,应当在设备树中把oled的0x3c地址注释掉,否则会注册失败!
二、核心代码
Linux中I2C设备有4种添加方法:静态注册、动态注册、用户空间注册、i2c驱动扫描注册。本次使用动态注册。
static int __init oled_init(void)
{
int ret = 0;
struct i2c_adapter *adapter;
adapter = i2c_get_adapter(0);
my_i2c_client = i2c_new_device(adapter, &oled_i2c_devices);
i2c_put_adapter(adapter);
i2c_add_driver(&oled_driver);
return ret;
}
观察在STM32或者其他的单片机中oled的程序,我们发现都是调用以下两个函数来实现写数据。我们把HAL库库函数改成我们自己的发送函数即可。
void WriteCmd(unsigned char I2C_Command)//写命令
{
HAL_I2C_Mem_Write(&hi2c2,OLED_ADDRESS,0x00,I2C_MEMADD_SIZE_8BIT,&I2C_Command,1,100);
}
void WriteDat(unsigned char I2C_Data)//写数据
{
HAL_I2C_Mem_Write(&hi2c2,OLED_ADDRESS,0x40,I2C_MEMADD_SIZE_8BIT,&I2C_Data,1,100);
}
修改后的发送函数如下
void WriteCmd(unsigned char I2C_Command)//写命令
{
oled_write_reg( &oleddev, 0x00, I2C_Command);
}
void WriteDat(unsigned char I2C_Data)//写数据
{
oled_write_reg( &oleddev, 0x40, I2C_Data);
}
最后在static int oled_probe(struct i2c_client *client, const struct i2c_device_id *id)函数调用我们的测试程序
OLED_REGInit();
mdelay(1000);
OLED_ShowStr(30,2,"for long ",1);
OLED_ShowStr(0,4,"made by banana-peel-x ",1);
三、程序验证
四、程序源代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.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 "codetab.h"
#define OLED_ADDRESS 0x78 //通过调整0R电阻,屏可以0x78和0x7A两个地址 -- 默认0x78
void I2C_Configuration(void);
void I2C_WriteByte(uint8_t addr,uint8_t data);
void WriteCmd(unsigned char I2C_Command);
void WriteDat(unsigned char I2C_Data);
void OLED_REGInit(void);
void OLED_SetPos(unsigned char x, unsigned char y);
void OLED_Fill(unsigned char fill_Data);
void OLED_CLS(void);
void OLED_ON(void);
void OLED_OFF(void);
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize);
void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N);
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[]);
/***************************************************************
文件名 : driver_oled.c
作者 : 徐建波
版本 : V1.0
描述 : OLED驱动程序
其他 : 无
***************************************************************/
#define OLED_CNT 1
#define OLED_NAME "oled"
struct oled_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
};
static struct oled_dev oleddev;
/*
* @description : 向oled多个寄存器写入数据
* @param - dev: oled设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 oled_write_regs(struct oled_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* ap3216c地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
/*
* @description : 向oled指定寄存器写入指定的值,写一个寄存器
* @param - dev: oled设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void oled_write_reg(struct oled_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
oled_write_regs(dev, reg, &buf, 1);
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int oled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &oleddev;
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static int oled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[10];
//u8 reg;
//struct oled_dev *dev = (struct oled_dev *)filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
if(databuf[0]==0)
{
OLED_CLS();
}
else{
OLED_ShowStr(30,4,databuf,2);
}
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int oled_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* oled操作函数 */
static const struct file_operations oled_ops = {
.owner = THIS_MODULE,
.open = oled_open,
.write= oled_write,
.release = oled_release,
};
/*
* @description : i2c驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
* @return : 0,成功;其他负值,失败
*/
static int oled_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* 1、构建设备号 */
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);
}
/* 2、注册设备 */
cdev_init(&oleddev.cdev, &oled_ops);
cdev_add(&oleddev.cdev, oleddev.devid, OLED_CNT);
/* 3、创建类 */
oleddev.class = class_create(THIS_MODULE, OLED_NAME);
if (IS_ERR(oleddev.class)) {
return PTR_ERR(oleddev.class);
}
/* 4、创建设备 */
oleddev.device = device_create(oleddev.class, NULL, oleddev.devid, NULL, OLED_NAME);
if (IS_ERR(oleddev.device)) {
return PTR_ERR(oleddev.device);
}
oleddev.private_data = client;
OLED_REGInit();
mdelay(1000);
OLED_ShowStr(30,2,"for long ",1);
OLED_ShowStr(0,4,"made by banana-peel-x ",1);
return 0;
}
/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @return : 0,成功;其他负值,失败
*/
static int oled_remove(struct i2c_client *client)
{
/* 删除设备 */
cdev_del(&oleddev.cdev);
unregister_chrdev_region(oleddev.devid, OLED_CNT);
/* 注销掉类和设备 */
device_destroy(oleddev.class, oleddev.devid);
class_destroy(oleddev.class);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id oled_id[] = {
{"bananapeelx,oled", 0},
{}
};
static struct i2c_board_info oled_i2c_devices = {
I2C_BOARD_INFO("oled", 0x3c),
};
/* i2c驱动结构体 */
static struct i2c_driver oled_driver = {
.probe = oled_probe,
.remove = oled_remove,
.driver = {
.owner = THIS_MODULE,
.name = "oled",
//.of_match_table = oled_of_match,
},
.id_table = oled_id,
};
void WriteCmd(unsigned char I2C_Command)//写命令
{
oled_write_reg( &oleddev, 0x00, I2C_Command);
}
void WriteDat(unsigned char I2C_Data)//写数据
{
oled_write_reg( &oleddev, 0x40, I2C_Data);
}
void OLED_REGInit(void)
{
mdelay(100); //这里的延时很重要
WriteCmd(0xAE); //display off
WriteCmd(0x20); //Set Memory Addressing Mode
WriteCmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
WriteCmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7
WriteCmd(0xc8); //Set COM Output Scan Direction
WriteCmd(0x00); //---set low column address
WriteCmd(0x10); //---set high column address
WriteCmd(0x40); //--set start line address
WriteCmd(0x81); //--set contrast control register
WriteCmd(0xff); //亮度调节 0x00~0xff
WriteCmd(0xa1); //--set segment re-map 0 to 127
WriteCmd(0xa6); //--set normal display
WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
WriteCmd(0x3F); //
WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
WriteCmd(0xd3); //-set display offset
WriteCmd(0x00); //-not offset
WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
WriteCmd(0xf0); //--set divide ratio
WriteCmd(0xd9); //--set pre-charge period
WriteCmd(0x22); //
WriteCmd(0xda); //--set com pins hardware configuration
WriteCmd(0x12);
WriteCmd(0xdb); //--set vcomh
WriteCmd(0x20); //0x20,0.77xVcc
WriteCmd(0x8d); //--set DC-DC enable
WriteCmd(0x14); //
WriteCmd(0xaf); //--turn on oled panel
printk("oled reginit................\r\n");
}
void OLED_SetPos(unsigned char x, unsigned char y) //设置起始点坐标
{
WriteCmd(0xb0+y);
WriteCmd(((x&0xf0)>>4)|0x10);
WriteCmd((x&0x0f)|0x01);
}
void OLED_Fill(unsigned char fill_Data)//全屏填充
{
unsigned char m,n;
for(m=0;m<8;m++)
{
WriteCmd(0xb0+m); //page0-page1
WriteCmd(0x00); //low column start address
WriteCmd(0x10); //high column start address
for(n=0;n<128;n++)
{
WriteDat(fill_Data);
}
}
}
void OLED_CLS(void)//清屏
{
OLED_Fill(0x00);
}
//--------------------------------------------------------------
// Prototype : void OLED_ON(void)
// Calls :
// Parameters : none
// Description : 将OLED从休眠中唤醒
//--------------------------------------------------------------
void OLED_ON(void)
{
WriteCmd(0X8D); //设置电荷泵
WriteCmd(0X14); //开启电荷泵
WriteCmd(0XAF); //OLED唤醒
}
//--------------------------------------------------------------
// Prototype : void OLED_OFF(void)
// Calls :
// Parameters : none
// Description : 让OLED休眠 -- 休眠模式下,OLED功耗不到10uA
//--------------------------------------------------------------
void OLED_OFF(void)
{
WriteCmd(0X8D); //设置电荷泵
WriteCmd(0X10); //关闭电荷泵
WriteCmd(0XAE); //OLED休眠
}
//--------------------------------------------------------------
// Prototype : void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)
// Calls :
// Parmeters : x,y -- 起始点坐标(x:0~127, y:0~7); ch[] -- 要显示的字符串; TextSize -- 字符大小(1:6*8 ; 2:8*16)
// Description : 显示codetab.h中的ASCII字符,有6*8和8*16可选择
//--------------------------------------------------------------
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)
{
unsigned char c = 0,i = 0,j = 0;
switch(TextSize)
{
case 1:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 126)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0;i<6;i++)
WriteDat(F6x8[c][i]);
x += 6;
j++;
}
}break;
case 2:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 120)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i]);
OLED_SetPos(x,y+1);
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i+8]);
x += 8;
j++;
}
}break;
}
}
//--------------------------------------------------------------
// Prototype : void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N)
// Calls :
// Parameters : x,y -- 起始点坐标(x:0~127, y:0~7); N:汉字在codetab.h中的索引
// Description : 显示codetab.h中的汉字,16*16点阵
//--------------------------------------------------------------
void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N)
{
unsigned char wm=0;
unsigned int adder=32*N;
OLED_SetPos(x , y);
for(wm = 0;wm < 16;wm++)
{
WriteDat(F16x16[adder]);
adder += 1;
}
OLED_SetPos(x,y + 1);
for(wm = 0;wm < 16;wm++)
{
WriteDat(F16x16[adder]);
adder += 1;
}
}
//--------------------------------------------------------------
// Prototype : void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[]);
// Calls :
// Parameters : x0,y0 -- 起始点坐标(x0:0~127, y0:0~7); x1,y1 -- 起点对角线(结束点)的坐标(x1:1~128,y1:1~8)
// Description : 显示BMP位图
//--------------------------------------------------------------
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{
unsigned int j=0;
unsigned char x,y;
if(y1%8==0)
y = y1/8;
else
y = y1/8 + 1;
for(y=y0;y<y1;y++)
{
OLED_SetPos(x0,y);
for(x=x0;x<x1;x++)
{
WriteDat(BMP[j++]);
}
}
}
static struct i2c_client *my_i2c_client;
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init oled_init(void)
{
int ret = 0;
struct i2c_adapter *adapter;
adapter = i2c_get_adapter(0);
my_i2c_client = i2c_new_device(adapter, &oled_i2c_devices);
i2c_put_adapter(adapter);
i2c_add_driver(&oled_driver);
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit oled_exit(void)
{
i2c_del_driver(&oled_driver);
i2c_unregister_device(my_i2c_client);
}
module_init(oled_init);
module_exit(oled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bananapeelx");
五、总结
庠序宏开千秋业!