Linux驱动开发(十二)---树莓派framebuffer学习(改造OLED)

54 篇文章 37 订阅
22 篇文章 80 订阅

前文回顾

《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
《Linux驱动开发(九)—树莓派I2C设备驱动开发(BME280)》
《Linux驱动开发(十)—树莓派输入子系统学习(红外接收)》
《Linux驱动开发(十一)—树莓派SPI驱动学习(OLED)》

继续宣传一下韦老师的视频

70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】

在这里插入图片描述

framebuffer介绍

framebuffer从字面上理解是“帧缓冲”,一般有如下理解:

  • 单纯的把framebuffer看作一块内存,这部分内存包含了将要scan out显示的数据。
  • 等价于framebuffer driver。通常作为LCD控制器或者其他显示设备的驱动,FrameBuffer驱动是一个字符设备,设备节点是/dev/fbX,主设备号为29,次设备号递增,用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。Framebuffer设备为上层应用程序提供系统调用,也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己。所以可以看成是一个graphic hardware-independent抽象层,上面对接应用层,下面对接LCD等硬件的驱动。
  • 有一种说法是操纵lcd显示就是操纵framebuffer,表面上来看是这样的。实际上是frambuffer就是linux内核驱动申请的一片内存空间,然后lcd内有一片sram,cpu内部有个lcd控制器,它有个单独的dma用来将frambuffer中的数据拷贝到lcd的sram中去 拷贝到lcd的sram中的数据就会显示在lcd上,LCD驱动和framebuffer驱动没有必然的联系,它只是驱动LCD正常工作的,比如有信号传过来,那么LCD驱动负责把信号转成显示屏上的内容,至于什么内容这就是应用层要处理的。

我理解的就是它对应一个设备/dev/fbx,通过它就可以直接写屏幕。后面的操作不需要我们操心,系统自动完成。
在这里插入图片描述

OLED

其实我们的oled并不具备这种控制器,所以它没有提供显存的地址,这里用oled测试,也只是注册好fb之后,手动将fb的数据通过SPI驱动,更新到屏幕上去。
在这里插入图片描述

驱动中的核心步骤

其中最关键重点步骤,就是分配,设置,注册fb_info结构体

static int oled_probe(struct spi_device *spi)
{	
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	oled_dev = spi;

	/* 分配/设置/注册 fb_info */
	oled_fb_info = framebuffer_alloc(0, &spi->dev);


	/* 1.2 设置fb_info */
	/* a. var : LCD分辨率、颜色格式 */
	oled_fb_info->var.xres_virtual = oled_fb_info->var.xres = 128;//宽度
	oled_fb_info->var.yres_virtual = oled_fb_info->var.yres = 64;//高度
	
	oled_fb_info->var.bits_per_pixel = 1;  //1位表示一个像素
	
	/* b. fix */
	strcpy(oled_fb_info->fix.id, "pgg_oled");
	oled_fb_info->fix.smem_len = oled_fb_info->var.xres * oled_fb_info->var.yres * oled_fb_info->var.bits_per_pixel / 8;//显存占据的地址大小,要除以8变成字节数

	/* c. 分配显存 */
	oled_fb_info->screen_base = dma_alloc_wc(&spi->dev, oled_fb_info->fix.smem_len, &phy_addr_fb, GFP_KERNEL);
	oled_fb_info->fix.smem_start = phy_addr_fb;  /* fb的物理地址 */
	oled_fb_info->fix.smem_len = oled_fb_info->fix.smem_len;
	
	oled_fb_info->fix.type = FB_TYPE_PACKED_PIXELS;//表示像素类型
	oled_fb_info->fix.visual = FB_VISUAL_MONO10;//表示单色屏幕

	oled_fb_info->fix.line_length = oled_fb_info->var.xres * oled_fb_info->var.bits_per_pixel / 8;//每行的长度

	/* d. fbops */
	oled_fb_info->fbops = &myfb_ops;
	oled_fb_info->pseudo_palette = pseudo_palette;	

	register_framebuffer(oled_fb_info);

	/* spi oled init */
	oled_dc = gpiod_get(&spi->dev, "dc", GPIOD_OUT_HIGH);
	oled_rst = gpiod_get(&spi->dev, "rst", GPIOD_OUT_HIGH);

	data_buf = dma_alloc_wc(&spi->dev, oled_fb_info->fix.smem_len, &phy_addr_user, GFP_KERNEL);
	
	oled_hardware_init();

	OLED_DIsp_Clear();

	/* 创建1个内核线程,用来把Framebuffer的数据通过SPI控制器发送给OLED */
	oled_kthread = kthread_create(oled_thread, NULL, "pgg_oled_pd");
	wake_up_process(oled_kthread);
	
	return 0;
}

在这里插入图片描述

来详细说一下这个步骤

申请framebuffer

oled_fb_info = framebuffer_alloc(0, &spi->dev);

设置framebuffer的变量参数,这里根据实际屏幕的真实参数决定。oled是128*64像素,每个像素用1bit表示,只有亮和灭。

oled_fb_info->var.xres_virtual = oled_fb_info->var.xres = 128;//宽度
oled_fb_info->var.yres_virtual = oled_fb_info->var.yres = 64;//高度
oled_fb_info->var.bits_per_pixel = 1;  //1位表示一个像素

再设置固定参数

strcpy(oled_fb_info->fix.id, "pgg_oled");
oled_fb_info->fix.smem_len = oled_fb_info->var.xres * oled_fb_info->var.yres * oled_fb_info->var.bits_per_pixel / 8;//显存占据的地址大小,要除以8变成字节数

分配实际的内存地址

oled_fb_info->screen_base = dma_alloc_wc(&spi->dev, oled_fb_info->fix.smem_len, &phy_addr_fb, GFP_KERNEL);
oled_fb_info->fix.smem_start = phy_addr;  /* fb的物理地址 */
oled_fb_info->fix.smem_len = oled_fb_info->fix.smem_len;

这里注意配置屏幕类型,并且每行的字节数,还是需要除以8,这个和oled的屏幕内存并不一样,oled是竖着存放一个字节表示第一列的8个bit。所以后面刷屏的时候,还有一个变化。

oled_fb_info->fix.type = FB_TYPE_PACKED_PIXELS;//表示像素类型
oled_fb_info->fix.visual = FB_VISUAL_MONO10;//表示单色屏幕
oled_fb_info->fix.line_length = oled_fb_info->var.xres * oled_fb_info->var.bits_per_pixel / 8;//每行的长度

在这里插入图片描述

这里注册几个操作函数,是必须要有的,还有后面的调色板。

oled_fb_info->fbops = &myfb_ops;
oled_fb_info->pseudo_palette = pseudo_palette;	

最后注册framebuffer

register_framebuffer(oled_fb_info);

卸载函数,内部需要释放一些内存,然后注销framebuffer。

static int oled_remove(struct spi_device *spi)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	kthread_stop(oled_kthread);

	dma_free_wc(&spi->dev,oled_fb_info->fix.smem_len,oled_fb_info->screen_base,phy_addr_fb);
	dma_free_wc(&spi->dev,oled_fb_info->fix.smem_len,data_buf,phy_addr_user);
	/* 反注册 fb_info */
	unregister_framebuffer(oled_fb_info);

	gpiod_put(oled_dc);
	gpiod_put(oled_rst);
	return 0;
}

这个framebuffer目前来说还和硬件没有任何关系。我们只是申请到了一块显存。
在这里插入图片描述

然后就需要把显存中的数据,通过spi方式,不断的刷新到oled显示屏中

static int get_pixel(int x, int y)
{
	unsigned char byte = *(oled_fb_info->screen_base + y*oled_fb_info->fix.line_length + (x>>3));
	int bit = x & 0x7;
	if (byte & (1<< bit))
		return 1;
	else 
		return 0;
}

static void convert_fb_to_oled(void)
{
	unsigned char data;
	int i = 0;
	int x, page;
	int bit;

	for (page = 0; page < 8; page++)
	{
		for (x = 0; x < 128; x++)
		{
			data = 0;
			for (bit = 0; bit < 8; bit++)
			{
				data |= (get_pixel(x, page*8 + bit) << bit);
			}
			data_buf[i++] = data;
		}		
	}
	
	
}

static int oled_thread(void *data)
{
    unsigned char y;
	while (1)
	{
		/* 把Framebuffer的数据刷到OLED上去 */
		convert_fb_to_oled();
		for (y = 0; y < 8; y++)
		{
			OLED_DIsp_Set_Pos(0, y);
			oled_write_datas(&data_buf[y*128], 128);
		}
		
		set_current_state(TASK_INTERRUPTIBLE);
		schedule_timeout(HZ);

		if (kthread_should_stop()) {
			set_current_state(TASK_RUNNING);
			break;
		}
		
	}
	return 0;
}

在这里插入图片描述

用户侧程序

用户侧程序参考的是韦老师的代码,用来显示一个字符A,需要理解的就是mmap函数的用法。
mmap函数是一个比较神奇的函数,它可以把文件映射到进程的虚拟内存空间。通过对这段内存的读取和修改,可以实现对文件的读取和修改,而不需要用read和write函数。

void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);

在这个函数原型中:

参数addr:指定映射的起始地址,通常设为NULL,由内核来分配 参数length:代表将文件中映射到内存的部分的长度。
参数prot:映射区域的保护方式。可以为以下几种方式的组合: PROT_EXEC 映射区域可被执行 PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入 PROT_NONE 映射区域不能存取 参数flags:映射区的特性标志位,常用的两个选项是:
MAP_SHARD:写入映射区的数据会复制回文件,且运行其他映射文件的进程共享
MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制,对此区域的修改不会写会原文件
参数fd:要映射到内存中的文件描述符,有open函数打开文件时返回的值。
参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。
函数返回值:实际分配的内存的起始地址。

在用户侧操作Framebuffer的使用步骤

  1. 打开设备文件 /dev/fbx
  2. 获取当前设备信息
  3. mmap做映射
  4. 填充framebuffer
    在这里插入图片描述

问题

韦老师的代码中
在这里插入图片描述
这个参数为空了,好像会导致申请失败,这里的视频尚未完结,这个bug可能还没解决。
在这里插入图片描述

遗留问题

这个模块的卸载,目前还有问题。
另外,内核会有一个告警信息
在这里插入图片描述
不过没有影响使用,不知道是不是问题。
这两个问题后续继续解决。
在这里插入图片描述

效果

这里的代码只是写了一个字符A的显示。
在这里插入图片描述
代码下载
《传送门》

结束语

今天更新完就学习完了韦老师的视频,驱动知识刚刚入门。后续的学习不能停止,继续搜罗资料,进行进一步的学习,丰富种类,学习块设备,网络设备的驱动。
在这里插入图片描述
昨天看了魔女2,动作场面还是挺过瘾,但是就是情节不是很清楚,可能是翻译的不太准确罢了。新的女主也挺好看,旧的女主也出来了一面,不过不如1中可爱了。电影看完就忘了,只记得里面令人尴尬的中国话。
在这里插入图片描述
今天看《汉江怪物》,肯定差不了。在这里插入图片描述

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖哥王老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值