背景:
芯片:imx6ull
通信协议:IIC
外设:SSD1396(128*64)
设备树配置忽略,根据硬件参数配置
【驱动】
probe函数注册相关的dev设备,主要是文件操作的相关接口
/*字符设备操作函数集*/
static struct file_operations oled_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = oled_open,
.write = oled_write,
.unlocked_ioctl = oled_ioctl,
.release = oled_release,
};
open操作函数集
/*字符设备操作函数集,open函数实现*/
static int oled_open(struct inode *inode, struct file *filp)
{
// printk("\n oled_open \n");
/*向 oled 发送配置数据,让oled处于正常工作状态*/
oled_init();
return 0;
}
/*初始化i2c
*返回值,成功,返回0。失败,返回 -1
*/
static int oled_init(void)
{
printk(KERN_EMERG"oled_init");
int error = 0;
error |= i2c_oled_send_cmd(0xAE);//--display off
error |= i2c_oled_send_cmd(0x00);//---set low column address
error |= i2c_oled_send_cmd(0x10);//---set high column address
error |= i2c_oled_send_cmd(0x40);//--set start line address
error |= i2c_oled_send_cmd(0xB0);//--set page address
error |= i2c_oled_send_cmd(0x81); // contract control
error |= i2c_oled_send_cmd(0xFF);//--128
error |= i2c_oled_send_cmd(0xA1);//set segment remap
error |= i2c_oled_send_cmd(0xA6);//--normal / reverse
error |= i2c_oled_send_cmd(0xA8);//--set multiplex ratio(1 to 64)
error |= i2c_oled_send_cmd(0x3F);//--1/32 duty
error |= i2c_oled_send_cmd(0xC8);//Com scan direction
error |= i2c_oled_send_cmd(0xD3);//-set display offset
error |= i2c_oled_send_cmd(0x00);//
error |= i2c_oled_send_cmd(0xD5);//set osc division
error |= i2c_oled_send_cmd(0x80);//
error |= i2c_oled_send_cmd(0xD8);//set area color mode off
error |= i2c_oled_send_cmd(0x05);//
error |= i2c_oled_send_cmd(0xD9);//Set Pre-Charge Period
error |= i2c_oled_send_cmd(0xF1);//
error |= i2c_oled_send_cmd(0xDA);//set com pin configuartion
error |= i2c_oled_send_cmd(0x12);//
error |= i2c_oled_send_cmd(0xDB);//set Vcomh
error |= i2c_oled_send_cmd(0x30);//
error |= i2c_oled_send_cmd(0x8D);//set charge pump enable
error |= i2c_oled_send_cmd(0x14);//
error |= i2c_oled_send_cmd(0xAF);//--turn on oled panel
printk(KERN_EMERG"oled_init finish");
if (error < 0)
{
/*初始化错误*/
printk(KERN_DEBUG "\n oled_init error \n");
return -1;
}
return 0;
}
write函数,没有怎么使用上;写文件操作,主要使用ioctl函数
ioctl的定义
#define IOCTL_CLEAN_SCREEN _IO('d', 1)
#define IOCTL_W_CMD _IOW('d', 2, char)
#define IOCTL_W_DATA _IOW('d', 3, char)
ioctl注册的函数集实现
static long oled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
#if 1
char tmp = NULL;
int ret = 0;
switch(cmd){
case IOCTL_CLEAN_SCREEN:
OLED_ClearFullScreen();
break;
case IOCTL_W_CMD:
tmp = arg;
i2c_oled_send_cmd(tmp);
break;
case IOCTL_W_DATA:
tmp = arg;
i2c_oled_send_data(tmp);
break;
default:
printk(KERN_EMERG"IOCMD not found");
return -ENOTTY; // 当未定义的命令号时返回错误
}
#endif
return 0;
}
SSD1306的写操作主要用到两种命令
写数据:0x00+data
写命令:0x40+cmd
//oled写命令
static int i2c_oled_send_cmd(u8 cmd)
{
int error = 0;
error |= i2c_write_oled(oled_client,0x00,cmd);
if(error){
printk(KERN_EMERG "\n i2c_oled_send_cmd error \n");
return -1;
}
//printk(KERN_EMERG "i2c_oled_send_cmd cmd:0x%x\n",cmd);
return 0;
}
//oled写数据
static int i2c_oled_send_data(u8 data)
{
int error = 0;
error |= i2c_write_oled(oled_client,0x40,data);
if(error){
printk(KERN_DEBUG "\n i2c_oled_send_data error \n");
return -1;
}
//printk(KERN_EMERG "i2c_oled_send_data data:0x%x\n",data);
return 0;
}
static int i2c_write_oled(struct i2c_client *oled_client, u8 address, u8 data)
{
int error = 0;
u8 write_data[2];
struct i2c_msg send_msg; //要发送的数据结构体
/*设置要发送的数据*/
write_data[0] = address;
write_data[1] = data;
printk(KERN_EMERG"data0:0x%x data1:0x:%x\n",write_data[0],write_data[1]);
/*发送 iic要写入的地址 reg*/
send_msg.addr = oled_client->addr; //mpu6050在 iic 总线上的地址
send_msg.flags = 0; //标记为发送数据
send_msg.buf = write_data; //写入的首地址
send_msg.len = 2; //reg长度
/*执行发送*/
error = i2c_transfer(oled_client->adapter, &send_msg, 1);
if (error != 1)
{
if(error == -ENXIO){
printk(KERN_EMERG "\n i2c_transfer ENXIO \n");
}else if(error == -EIO){
printk(KERN_EMERG "\n i2c_transfer EIO \n");
}else if(error == -ETIMEDOUT){
printk(KERN_EMERG "\n i2c_transfer ETIMEDOUT \n");
}else{
printk(KERN_EMERG "\n i2c_error\n");
}
printk(KERN_EMERG "\n i2c_transfer error11:%d \n",error);
return -1;
}
//printk(KERN_EMERG "\n i2c_transfer error:%d \n",error);
return 0;
}
【应用】
#define IOCTL_CLEAN_SCREEN _IO('d', 1)
#define IOCTL_W_CMD _IOW('d', 2, char)
#define IOCTL_W_DATA _IOW('d', 3, char)
#define OLED_CMD 1
#define OLED_DATA 0
#define Max_Column 128
#define Max_Row 64
void OLED_WR_Byte(unsigned char dat,unsigned int cmd)
{
char temp_data;
if(cmd){
temp_data = dat;
ioctl(fd, IOCTL_W_CMD,temp_data);
printf("[app]cmd:0x%x\n",temp_data);
}else{
temp_data = dat;
ioctl(fd, IOCTL_W_DATA,temp_data);
printf("[app]data:0x%x\n",temp_data);
}
}
void OLED_Set_Pos(unsigned char x, unsigned char y)
{ OLED_WR_Byte(0xb0+y,OLED_CMD);
OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
OLED_WR_Byte((x&0x0f),OLED_CMD);
}
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
{
unsigned char c=0,i=0;
c=chr-' ';//得到偏移后的值
if(x>Max_Column-1){x=0;y=y+2;}
if(Char_Size ==16)
{
OLED_Set_Pos(x,y);
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
OLED_Set_Pos(x,y+1);
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
}
else {
OLED_Set_Pos(x,y);
for(i=0;i<6;i++)
OLED_WR_Byte(F6x8[c][i],OLED_DATA);
}
}
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
{
unsigned char j=0;
while (chr[j]!='\0')
{ OLED_ShowChar(x,y,chr[j],Char_Size);
x+=8;
if(x>120){x=0;y+=2;}
j++;
}
}
void main(void)
{
fd = open("/dev/I2C2_oled", O_RDWR);
if(fd < 0)
{
printf("open file : %s failed !\n", argv[0]);
return -1;
}
if (ioctl(fd, IOCTL_CLEAN_SCREEN, 0) < 0)
{
printf("ioctl IOCTL_CLEAN_SCREEN failed\n");
}
OLED_ShowString(6,3,"0.96' OLED TEST",16);
leep(2);
/*关闭文件*/
close(fd);
}
【调试】
1、加载完设备树,可以先检查设备树目录有没添加成功;
2、用i2cdetect -y -a 0检测设备有没连接成功
当时我调试有个异常,说明书写着设备地址为0x78,但我一直找不到相应的设备,怀疑i2cdetect只支持最多显示0x77地址的所有设备,我强硬地往0x78上写入数据也提示写入失败;
后来我反复拔插设备,用i2cdetect 发现0x3c地址也跟随变化,我往0x3c写入数据也是正常,后来也可以正常显示
3、该应用程序太过粗糙,应该优化为ioctl通过结构体的方式,与驱动通信