linux驱动系列学习之OLED(i2c接口)(八)

一、OLED简介

本次使用的开发板正点原子Linux阿波罗。屏幕是i2c接口的四针、分辨率为128×64的oled液晶屏。通信接口为i2c。具体的i2c框架使用请参考前面的文章。oled的详细简介请参考其他博主的文章。

网上很多使用linux驱动i2c接口的oled屏幕,大多数都不不涉及framebuffer驱动相关。本次使用oled,将其看作两个驱动相结合去控制oled。

首先,oled是一个屏幕,使用framebuffer框架进行驱动,它的通信方式为i2c,使用i2c框架与oled硬件ssd1306芯片通信。因此,主要流程就是申请framebuffer、i2c设备,进行注册,将它们结合起来。实际中使用并无特别大价值,Linux系统里面也提供了fbtft框架去驱动非标准的液晶屏(相对RGB888、RGB565等屏幕,使用例如SPI通信方式),这里的驱动作为学习使用,仅供参考。

二、查看、配置i2c设备树

i2c设备树在之前写i2c驱动的时候已经配置好,这里放张截图。按照下面截图修改好设备树,编译出来,烧录到板子里面。

 

          从图中看到,oled的设备树节点compatible属性是htq,oled,i2c的地址是<0x3c>(reg属性),依赖于i2c1这个节点(第二张截图未显示),在第一张截图中,有i2c1的信息,oled的两根i2c引脚连接到板子上的U4_RX、U4_TX即可。

        使用i2cdetect指令(i2c-tools相关工具),可以看到总线上已经有了oled设备。

查看放入设备树之后的板子信息,可以看到有htq,oled。这个信息将用来匹配设备树的节点。

 三、代码

        将设备树相关信息修改好之后,就可以写驱动了。首先在入口函数里面注册i2c设备和framebuffer设备。

static int __init oled_init(void)
{
	int ret = 0;
	ret = i2c_add_driver(&oled_driver);  
	printk("i2c ret: %d\n",ret);
	ret = register_framebuffer(&oled_info);
	printk("framebuffer ret: %d\n",ret);
	return ret;
}

其中oled_driver、oled_info是对应的结构体,源码如下。

//i2c框架	
static const struct i2c_device_id oled_id_table[] = {
	{"htq,oled", 0},  
};

static const struct of_device_id oled_of_match_table[] = {
	{ .compatible = "htq,oled", },
};

static  struct i2c_driver oled_driver = {
	.probe = oled_probe,
	.remove = oled_remove,
	.driver = {
			.owner = THIS_MODULE,
			.name = "oled",
			.of_match_table = oled_of_match_table, 
		   },
	.id_table = oled_id_table,
};

//frmebuffer框架

static struct fb_ops oled_fbops = {
	.fb_open      = oled_fb_open,
	.fb_release   = oled_fb_release,
	.fb_mmap      = oled_fb_mmap,
  	.fb_ioctl     = oled_fb_ioctl,
};

static struct fb_info oled_info = {
	.var ={
		.xres = OLED_WIDTH, 		    //屏幕宽
		.yres = OLED_HEIGHT,            //高
		.xres_virtual = OLED_WIDTH, 	//虚拟屏幕宽、高 	
		.yres_virtual = OLED_HEIGHT,
		.bits_per_pixel = 1,           //每像素点占用多少位
	},
	.fix={
		.smem_len = OLED_HEIGHT * OLED_WIDTH / 8,  //一帧使用多少内存,单位Byte		
		.line_length = 128,                        //一行使用多少内存,单位位
	},
	.fbops = &oled_fbops,    //文件句柄
};
		

注册之后,两个设备驱动的probe函数会自动用,进行匹配。i2c框架根据compatible属性与设备树的compatible进行匹配,得到设备树节点信息。framebuffer框架根据oled_info得到屏幕的相关信息。在oled_fbops结构体中有fb_open、fb_release、fb_mmap、fb_ioctl相关指针。在fb_open里面可以设置显存,这样应用程序写入的数据首先写入到缓存里,之后调用fb_ioctl将显存数据刷新到oled屏幕里面(需要事先做好fb_ioctl相关命令代码)。因此,需要在fb_open函数里面申请显存区域,这样fb_mmap就可以将应用程序的mmap函数得到的指针和显存产生联系,进而使用。

oled_mmap_buffer = kzalloc(4096,GFP_ATOMIC);
if(oled_mmap_buffer == NULL){
	printk("oled framebuffer alloc fail!\n");
	return -1;
}

显存是互斥访问,因此使用GFP_ATOMIC(原子操作)。每一个应用程序都有自己的显存(实际上一般也只有一个应用程序使用,多线程访问不考虑)。

一般的RGB屏幕使用framebuffer框架,屏幕是映射到相应的内存上的,对内存的可以直接显示到屏幕上,oled本身不支持类似的操作。

fb_mmap函数如下:

static int oled_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
	printk("oled_fb_mmap\n");

	vma->vm_flags |= VM_IO;//表示对设备 IO 空间的映射
	vma->vm_flags |= (VM_DONTEXPAND | VM_DONTDUMP);//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,
                                                   //应该保留起来,不能随便被别的虚拟页换出
	if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里
						vma->vm_start,//虚拟空间的起始地址
						virt_to_phys(oled_mmap_buffer)>>PAGE_SHIFT,//与物理内存对应的页帧号,物理地址右移 12 位
						vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍
						vma->vm_page_prot))//保护属性,
	{
		return -EAGAIN;
	}
	
	printk("(drv)映射的长度:%d\n",vma->vm_end - vma->vm_start);
	printk("物理地址:0x%X\n",virt_to_phys(oled_mmap_buffer));
	/*
		开发板的DDR容量: 1G
		0x40000000 ~ 0x80000000 
		0x10000000=256M
	*/
	printk("oled_fb_mmap ok\n");
	return 0;
}

i2c的probe函数如下

static int oled_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	//1. 构建设备号
	if(oled_device.major){
		oled_device.dev_id = MKDEV(oled_device.major, 0);
		register_chrdev_region(oled_device.dev_id, 1, OLED_NAME);
	} else {
		alloc_chrdev_region(&oled_device.dev_id, 0,1, OLED_NAME);
		oled_device.major = MAJOR(oled_device.dev_id);
	}

	printk("device major: %d\n",oled_device.major);
	//2. 字符设备
	cdev_init(&oled_device.cdev, &oled_fops);
	cdev_add(&oled_device.cdev, oled_device.dev_id, 1);

	//3. 创建类
	oled_device.class = class_create(THIS_MODULE, OLED_NAME);
	if(IS_ERR(oled_device.class)){
		return PTR_ERR(oled_device.class);
	}
	
	//4. 创建设备
	oled_device.device = device_create(oled_device.class,NULL, oled_device.dev_id, NULL, OLED_NAME);
	if(IS_ERR(oled_device.device)){
		return PTR_ERR(oled_device.device);
	}

	oled_device.privare_data = client;
	printk("oled iic addr:0x%x %d\n",client->addr,client->addr);
	//mdelay(100);
	//oled_init_reg();
	//mdelay(100);
	//printk("oled clear 0x0f\n");

	return 0;	
}

大概操作就是申请设备节点、注册字符设备、创建类之类的操作。

这样,i2c接口的oled驱动主要的内容写好。这样的驱动,应用程序既可以使用i2c框架访问,也可以使用framebuffer框架访问。

最后放几张结果图。

 这张图里面可以看到驱动程序加载进去之后和打开显示的相应数据。 

 这是最开始亮的图片,之后应用程序写的就不放了。

四 总结:

        Linux系统中使用框架开发驱动会大大减少开发精力。本次使用的i2c接口的oled只是一个多框架结合的示例。在实际中,并无太大用处。但一个物理设备需要使用多个框架开发功能是常见的,比如现在常用的电容屏,驱动除了要使用framebuffer框架显示,还有触摸驱动在里面进行,检测触摸点的位置,返回给系统或应用。        

       针对非标准的液晶屏, Linux系统里面也提供了fbtft框架去驱动,这个等以后有时间再更新。本次的驱动也未考虑多线程/多进程并发访问的相关问题,仅作学习使用。

五 环境和参考

硬件:正点原子Linux开发板

环境:ubuntu18

参考:Linux设备驱动开发详解(基于最新的Linux4.0内核) 宋宝华著

           Linux设备驱动程序   J & G著  

猜你喜欢

转载自blog.csdn.net/zichuanning520/article/details/124789694
今日推荐