《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
《Linux驱动开发(九)—树莓派I2C设备驱动开发(BME280)》
《Linux驱动开发(十)—树莓派输入子系统学习(红外接收)》
《Linux驱动开发(十一)—树莓派SPI驱动学习(OLED)》
继续宣传一下韦老师的视频
70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】
framebuffer从字面上理解是“帧缓冲”,一般有如下理解:
我理解的就是它对应一个设备/dev/fbx,通过它就可以直接写屏幕。后面的操作不需要我们操心,系统自动完成。
其实我们的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的使用步骤
韦老师的代码中
这个参数为空了,好像会导致申请失败,这里的视频尚未完结,这个bug可能还没解决。
这个模块的卸载,目前还有问题。
另外,内核会有一个告警信息
不过没有影响使用,不知道是不是问题。
这两个问题后续继续解决。
这里的代码只是写了一个字符A的显示。
代码下载
《传送门》
今天更新完就学习完了韦老师的视频,驱动知识刚刚入门。后续的学习不能停止,继续搜罗资料,进行进一步的学习,丰富种类,学习块设备,网络设备的驱动。
昨天看了魔女2,动作场面还是挺过瘾,但是就是情节不是很清楚,可能是翻译的不太准确罢了。新的女主也挺好看,旧的女主也出来了一面,不过不如1中可爱了。电影看完就忘了,只记得里面令人尴尬的中国话。
今天看《汉江怪物》,肯定差不了。