一、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容量: 1G0x40000000 ~ 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著

linux驱动系列学习之OLED(i2c接口)(八)相关推荐

  1. linux驱动系列学习之i2c子系统(四)

    一.i2c子系统简介 1. i2c总线 i2c总线因为只用SCL.SDA两根线就实现了设备之间的数据互传,极大的简化PCB布线,因此,2c总线在EEPROM.小型LCD等设备中应用极光.i2c的相关时 ...

  2. linux驱动系列学习之Framebuffer子系统(三)

    一.Framebuffer子系统简介         Framebuffer(帧缓冲)时Linux系统位显示设备提供的一个接口.属于偏底层的显示接口.它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上 ...

  3. Linux驱动之 字符设备 ioctl接口使用

    字符设备ioctl接口使用: Linux驱动编写除了对设备进行读写数据之外,通常还希望可以对设备进行控制. 从应用层传递一些命令参数,并在驱动层实现相应设备操作,这时候就用到了 ioctl函数: 应用 ...

  4. linux驱动系列学习之input子系统(二)

    一.input子系统简介 linux系统支持的输入设备众多,例如键盘.鼠标.按键.触摸屏等,linux系统通过抽象出一个input子系统去支持众多的输入设备.input子系统分为三层:上层:输入事件处 ...

  5. linux驱动系列学习之DRM(十)

    一.DRM简介 DRM,全称Direct Rending Manger.是目前Linux主流的图形显示框架.相比较传统的Framebuffer,DRM更能适应现代硬件.支持GPU.3D渲染显示等.DR ...

  6. linux驱动系列学习之阻塞与非阻塞IO(六)

    一. 阻塞与非阻塞IO概念     阻塞操作是指在执行设备操作时,若不能获取资源,则挂起进程进入休眠状态,等待可满足条件后进行操作.被挂起的进程从调度器队列移动到挂起队列(睡眠状态).当操作驱动程序r ...

  7. Linux的I2C 设备驱动 -- mini2440 上i2c接口触摸屏驱动

    本篇记录在友善之臂 mini2440 平台上挂载I2C接口触摸屏的驱动开发过程. 内核版本linux-2.6.32.2, 平台是ARM9 S3C2440+I2C接口的触摸屏 如上篇 Linux的I2C ...

  8. platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

    platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...

  9. Linux驱动开发(十八):I2C驱动

    简介 I2C是我们在单片机开发时时常会用到的通讯接口,用来与一些字符型设备进行通信,比如:陀螺仪.温度传感器等等,同样的在Linux下I2C驱动也是十分重要的.有了操作系统的加持,我们不用像在32上那 ...

最新文章

  1. SQL Server 2005 Express附加(Attach)的数据库为“只读”的解决方法
  2. 节点对象图与DOM树形图
  3. mysql 创建视图的时候语法易错点
  4. 用C语言做一个横板过关类型的控制台游戏
  5. SetRegistryKey的作用
  6. KVM虚拟化的简单概论
  7. 浅谈JQuery中$('.classname').get(0); $('.classname').eq(0); $('.classname')[0]三者的区别
  8. C++变量未初始的后果
  9. positive definite quadratic form and positive definite matrix
  10. css整张背景 多边形,JS/CSS3 低多边形大红绸缎全屏背景图(无图片)
  11. 使用Julia进行图像处理--JuliaImages介绍与基础使用
  12. java runnable 匿名_Java 开发者最困惑的四件事
  13. C++文件操作的HelloWorld
  14. linux tar 命令安装,Linux tar 命令 command not found tar 命令详解 tar 命令未找到 tar 命令安装 - CommandNotFound ⚡️ 坑否...
  15. RC / RL串联电路计算
  16. OpenCV玩九宫格数独(一)——九宫格图片中提取数字
  17. cocos2d 由导出文件.csb反推出cocosUI工程
  18. Vulnhub靶场渗透测试系列bulldog(命令注入和sudo提权)
  19. 蓝色至深蓝色固体CY5.5琥珀酰亚胺脂Cyanine5.5 NHS ester,Cyanine5.5 SE,CY5.5 NHS,1469277-96-0
  20. dss nginx 403 forbidden

热门文章

  1. XSSFWorkbook 设置单元格样式_欺骗你眼睛的立体单元格~~
  2. mybatis-plus的${ew.sqlSegment},${ew.sqlSelect},${ew.sqlSet},${ew.customSqlSegment}使用与区别
  3. 华为鸿蒙手机曝光,华为鸿蒙手机新特性曝光:充电期间系统将进行深度优化
  4. 比Teambition、Worktile 更适合研发团队的几大工具盘点
  5. 2.修道士和野人问题
  6. 创世战车服务器维护,《创世战车》5月24日版本更新内容公告
  7. Bootstrap 一篇就够 快速入门使用(中文文档)
  8. WEB页面或者H5页面如何打开高德或者百度地图APP导航(实战向)
  9. 日语笔记(2) 动词ます形
  10. DNS解析和优化(操作与实践,一分钟就能实现DNS优化)