你好!这里是风筝的博客,

欢迎和我一起交流。


最近入手了一块spi接口的tft彩屏,想着在我的h3板子上使用framebuffer驱动起来。
我们知道:

Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。

帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据,CPU指定显示控制器工作, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。

所以参考了这篇文章:
68 linux framebuffer设备驱动之spi lcd屏驱动

27 在H5上实现spi-tft屏的简单驱动
【吐槽】结果问题就来了。。。。。。。
我把代码编译成模块,insmod 时就出现:Segmentation fault
段错误一般都是指针指向或者引用了错误的地址,dmesg查看信息也确实发现是这样:
错误忘记复制下来了,懒得复现了,只有一些截图:


查看dmesg可以发现,确实是指向虚拟地址的问题,而且就是使用register_framebuffer
函数引起的Segmentation fault。
我当时就纳闷了,以前在2440板子上使用register_framebuffer函数也没见啥问题啊,就百度了一下,发现有的帖子说直接把fb_notifier_call_chain函数注释掉…
这不是扯淡吗,fb_notifier_call_chain函数最后会调用到notifier_call_chain函数,在我的错误堆栈信息也看到确实调用了这个函数,这函数路径在kernel/notifier.c里。
kernel路径下的文件那是我等渣渣能乱动的吗!!
我试着注释掉此函数,就发现板子在这里本卡住了。。。。。。
最后为了这个问题,我只能自力更生了。
根据错误信息,追踪到bit_clear_margins函数:

static void bit_clear_margins(struct vc_data *vc, struct fb_info *info,int color, int bottom_only)
{unsigned int cw = vc->vc_font.width;unsigned int ch = vc->vc_font.height;unsigned int rw = info->var.xres - (vc->vc_cols*cw);unsigned int bh = info->var.yres - (vc->vc_rows*ch);unsigned int rs = info->var.xres - rw;unsigned int bs = info->var.yres - bh;struct fb_fillrect region;region.color = color;region.rop = ROP_COPY;if (rw && !bottom_only) {region.dx = info->var.xoffset + rs;region.dy = 0;region.width = rw;region.height = info->var.yres_virtual;info->fbops->fb_fillrect(info, &region);}if (bh) {region.dx = info->var.xoffset;region.dy = info->var.yoffset + bs;region.width = rs;region.height = bh;info->fbops->fb_fillrect(info, &region);}
}

这函数哪里会使用非法地址导致Segmentation fault呢?显而易见,就是:info->fbops->fb_fillrect(info, &region)!!!!
我就是听信了那篇文章作者的话,fb_ops留了空,就悲剧了。

struct fb_ops fops = { //这里不实现操作函数,使用fbmem.c里实现的功能函数//如果这里实现了功能函数,则会调用这里实现的函数
};

后面我参考以前写的文章:
嵌入式Linux驱动笔记(三)------LCD驱动程序
填充了fb_ops:

static struct fb_ops fops = {.owner     = THIS_MODULE,.fb_fillrect = cfb_fillrect,.fb_copyarea    = cfb_copyarea,.fb_imageblit   = cfb_imageblit,
};

然后编译,发现还是出现Segmentation fault,dmesg查看错误信息的输出,发现错误出现在cfb_imageblit函数里,打开函数一看,确实是:

void cfb_imageblit(struct fb_info *p, const struct fb_image *image)
{...fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color];...
}

我一看,确实是使用register_framebuffer(fbi);函数注册时,fb_info结构体的pseudo_palette成员没有填充,填充之后顺利解决,可以在根文件系统里发现/dev/fb设备!

可以尝试一下:
echo hello > /dev/tty1
这样,在LCD就会出现hello字样了


摆放问题,屏幕倒着的,转一下方向即可看出正确输出了。
附上代码一份:

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <sound/core.h>
#include <linux/spi/spi.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>#include <linux/gpio.h>
#include <linux/delay.h>#include <linux/fb.h>#include <linux/dma-mapping.h>
#include <linux/sched.h>
#include <linux/wait.h>#include <asm/mach/map.h>//#define USE_HORIZONTAL
//#define __DEBUG__ 1#ifdef __DEBUG__
#define DEBUG(format,...) \printk("DEBUG::"format,  ##__VA_ARGS__)
#else
#define DEBUG(format,...)
#endif#define LCD_X_SIZE          176
#define LCD_Y_SIZE          220#ifdef USE_HORIZONTAL//如果定义了横屏
#define X_MAX_PIXEL         LCD_Y_SIZE
#define Y_MAX_PIXEL         LCD_X_SIZE
#else//竖屏
#define X_MAX_PIXEL         LCD_X_SIZE
#define Y_MAX_PIXEL         LCD_Y_SIZE
#endifstatic int tft_lcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info);struct tft_lcd{struct gpio_desc *reset_gpio;   struct gpio_desc *rs_gpio;};struct tft_lcd_fb{struct spi_device *spi; //记录fb_info对象对应的spi设备对象struct task_struct *thread; //记录线程对象的地址,此线程专用于把显存数据发送到屏的驱动ic
};
static struct fb_ops fops = {.owner        = THIS_MODULE,.fb_setcolreg    = tft_lcdfb_setcolreg,.fb_fillrect = cfb_fillrect,.fb_copyarea    = cfb_copyarea,.fb_imageblit   = cfb_imageblit,
};struct regdata_t{u8  reg;u16 data;int delay_ms;
}regdatas[] = {{0x10, 0x0000, 0}, {0x11, 0x0000, 0}, {0x12, 0x0000, 0},{0x13, 0x0000, 0}, {0x14, 0x0000, 40},{0x11, 0x0018, 0}, {0x12, 0x1121, 0}, {0x13, 0x0063, 0},{0x14, 0x3961, 0}, {0x10, 0x0800, 10}, {0x11, 0x1038, 30},{0x02, 0x0100, 0},
#ifdef USE_HORIZONTAL//如果定义了横屏{0x01, 0x001c, 0}, {0x03, 0x1038, 0},
#else//竖屏{0x01, 0x011c, 0}, {0x03, 0x1030, 0},
#endif{0x07, 0x0000, 0}, {0x08, 0x0808, 0}, {0x0b, 0x1100, 0},{0x0c, 0x0000, 0}, {0x0f, 0x0501, 0}, {0x15, 0x0020, 0},{0x20, 0x0000, 0}, {0x21, 0x0000, 0},{0x30, 0x0000}, {0x31, 0x00db}, {0x32, 0x0000}, {0x33, 0x0000},{0x34, 0x00db}, {0x35, 0x0000}, {0x36, 0x00af}, {0x37, 0x0000},{0x38, 0x00db}, {0x39, 0x0000},{0x50, 0x0603}, {0x51, 0x080d}, {0x52, 0x0d0c}, {0x53, 0x0205},{0x54, 0x040a}, {0x55, 0x0703}, {0x56, 0x0300}, {0x57, 0x0400},{0x58, 0x0b00}, {0x59, 0x0017},{0x0f, 0x0701}, {0x07, 0x0012, 50}, {0x07, 0x1017},
}; static void Lcd_WriteIndex(struct spi_device *spi, u8 Index)
{struct tft_lcd *pdata = spi_get_drvdata(spi);gpiod_set_value(pdata->rs_gpio, 0); //高电平spi_write(spi, &Index, 1);
}
static void Lcd_WriteData_16Bit(struct spi_device *spi, u16 Data)
{   u8 buf[2];struct tft_lcd *pdata = spi_get_drvdata(spi);buf[0] = ((u8)(Data>>8));buf[1] = ((u8)(Data&0x00ff));gpiod_set_value(pdata->rs_gpio, 1); //高电平spi_write(spi, &buf[0], 1);spi_write(spi, &buf[1], 1);
}static void LCD_WriteReg(struct spi_device *spi, u8 Index, u16 Data)
{int addr;addr = Index;Lcd_WriteIndex(spi, addr);Lcd_WriteData_16Bit(spi, Data);
}static void Lcd_SetRegion(struct spi_device *spi, u8 xStar, u8 yStar,u8 xEnd,u8 yEnd)
{
#ifdef USE_HORIZONTAL//如果定义了横屏 LCD_WriteReg(spi,0x38,xEnd);LCD_WriteReg(spi,0x39,xStar);LCD_WriteReg(spi,0x36,yEnd);LCD_WriteReg(spi,0x37,yStar);LCD_WriteReg(spi,0x21,xStar);LCD_WriteReg(spi,0x20,yStar);
#else//竖屏   LCD_WriteReg(spi,0x36,xEnd);LCD_WriteReg(spi,0x37,xStar);LCD_WriteReg(spi,0x38,yEnd);LCD_WriteReg(spi,0x39,yStar);LCD_WriteReg(spi,0x20,xStar);LCD_WriteReg(spi,0x21,yStar);
#endifLcd_WriteIndex(spi,0x22); }static int lcd_dt_parse(struct spi_device *spi, struct tft_lcd *lcd_data)
{lcd_data->reset_gpio = devm_gpiod_get(&spi->dev, "rest", GPIOD_OUT_HIGH);if (IS_ERR(lcd_data->reset_gpio))goto err0;gpio_direction_output(desc_to_gpio(lcd_data->reset_gpio), 1);lcd_data->rs_gpio = devm_gpiod_get(&spi->dev, "rs", GPIOD_OUT_HIGH);if (IS_ERR(lcd_data->rs_gpio))goto err1;gpio_direction_output(desc_to_gpio(lcd_data->rs_gpio), 1);return 0;err1:devm_gpiod_put(&spi->dev, lcd_data->reset_gpio);
err0:DEBUG("[%s]:failed\n", __FUNCTION__);return -1;}static void lcd_init(struct spi_device *spi, struct tft_lcd *pdata)
{int i =0;gpiod_set_value(pdata->reset_gpio, 0); //设低电平msleep(100);gpiod_set_value(pdata->reset_gpio, 1); //设高电平msleep(50);for (i = 0; i < ARRAY_SIZE(regdatas); i++){LCD_WriteReg(spi, regdatas[i].reg, regdatas[i].data);if (regdatas[i].delay_ms)msleep(regdatas[i].delay_ms);}}void show_fb(struct fb_info *fbi, struct spi_device *spi)
{int x, y;u32 k;u32 *p = (u32 *)(fbi->screen_base);u16 c;u8 *pp;//addset(spi, 0, 0); //从屏的0,0坐标开始刷Lcd_SetRegion(spi, 0,0,X_MAX_PIXEL-1,Y_MAX_PIXEL-1);DEBUG("[%s] \n",__FUNCTION__);for (y = 0; y < fbi->var.yres; y++){for (x = 0; x < fbi->var.xres; x++){k = p[y*fbi->var.xres+x];//取出一个像素点的32位数据// rgb8888 --> rgb565       pp = (u8 *)&k;c = pp[0] >> 3; //蓝色c |= (pp[1]>>2)<<5; //绿色c |= (pp[2]>>3)<<11; //红色//发出像素数据的rgb565//write_data(spi, c >> 8);//write_data(spi, c & 0xff);Lcd_WriteData_16Bit(spi, c);}}}int thread_func_fb(void *data)
{struct fb_info *fbi = (struct fb_info *)data;struct tft_lcd_fb *lcd_fb = fbi->par;while (1){if (kthread_should_stop())break;show_fb(fbi, lcd_fb->spi);//}return 0;
}
static u32 pseudo_palette[16];
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{chan &= 0xffff;chan >>= 16 - bf->length;return chan << bf->offset;
}static int tft_lcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info)
{unsigned int val;if (regno > 16){DEBUG("[%S] the regno is %d !!\n",__FUNCTION__, regno);return 1;}/* 用red,green,blue三原色构造出val  */val  = chan_to_field(red,   &info->var.red);val |= chan_to_field(green, &info->var.green);val |= chan_to_field(blue,    &info->var.blue);//((u32 *)(info->pseudo_palette))[regno] = val;pseudo_palette[regno] = val;return 0;
}int tft_lcd_fb_register(struct spi_device *spi) //此函数在probe函数里被调用
{struct fb_info *fbi;u8 *v_addr;u32 p_addr;struct tft_lcd_fb *lcd_fb;v_addr = dma_alloc_coherent(NULL, LCD_X_SIZE*LCD_Y_SIZE*4, &p_addr, GFP_KERNEL);//额外分配lcd_data_t类型空间fbi = framebuffer_alloc(sizeof(struct tft_lcd_fb), &spi->dev);if(fbi == NULL)DEBUG("[%s]:framebuffer_alloc failed\n", __FUNCTION__);//lcd_fb = &fbi[1]; //data指针指向额外分配的空间lcd_fb = fbi->par; //data指针指向额外分配的空间lcd_fb->spi = spi;fbi->pseudo_palette = pseudo_palette;fbi->var.activate       = FB_ACTIVATE_NOW;fbi->var.xres = LCD_X_SIZE;fbi->var.yres = LCD_Y_SIZE;fbi->var.xres_virtual = LCD_X_SIZE;fbi->var.yres_virtual = LCD_Y_SIZE;fbi->var.bits_per_pixel = 32; // 屏是rgb565, 但QT程序只能支持32位.还需要在刷图时把32位的像素数据转换成rgb565fbi->var.red.offset = 16;fbi->var.red.length = 8;fbi->var.green.offset = 8;fbi->var.green.length = 8;fbi->var.blue.offset = 0;fbi->var.blue.length = 8;strcpy(fbi->fix.id, "myfb");fbi->fix.smem_start = p_addr; //显存的物理地址fbi->fix.smem_len = LCD_X_SIZE*LCD_Y_SIZE*4; fbi->fix.type = FB_TYPE_PACKED_PIXELS;fbi->fix.visual = FB_VISUAL_TRUECOLOR;fbi->fix.line_length = LCD_X_SIZE*4;fbi->fbops = &fops;fbi->screen_base = v_addr; //显存虚拟地址//fbi->screen_base = dma_alloc_writecombine(NULL, fbi->fix.smem_len, &fbi->fix.smem_start, GFP_KERNEL);fbi->screen_size = LCD_X_SIZE*LCD_Y_SIZE*4; //显存大小//spi_set_drvdata(spi, fbi);register_framebuffer(fbi);lcd_fb->thread = kthread_run(thread_func_fb, fbi, spi->modalias);return 0; }static void tft_fb_test(struct spi_device *spi)
{int i,j;u16 color = 0x001f; /* rgb565,  蓝色 */Lcd_SetRegion(spi, 0,0,X_MAX_PIXEL-1,Y_MAX_PIXEL-1); //设置从屏哪个坐标开始显示,到哪个坐标结束#define rgb(r, g, b)  ((((r>>3)&0x1f)<<11) | (((g>>2)&0x3f)<<5) | ((b>>3)&0x1f))for(i=0 ; i<Y_MAX_PIXEL/2 ; i++){color = rgb(255, 0, 255); for(j=0; j<X_MAX_PIXEL/2; j++)Lcd_WriteData_16Bit(spi, color);//(u8 *)&color = rgb(255, 255, 0);for(j=X_MAX_PIXEL/2; j<X_MAX_PIXEL; j++)Lcd_WriteData_16Bit(spi, color);}for(i=Y_MAX_PIXEL/2 ; i<Y_MAX_PIXEL; i++){color = rgb(0, 255, 255);for(j=0; j<X_MAX_PIXEL/2; j++)Lcd_WriteData_16Bit(spi, color);color = rgb(255, 0,0);for(j=X_MAX_PIXEL/2; j<X_MAX_PIXEL; j++)Lcd_WriteData_16Bit(spi, color);}
}static int tft_lcd_probe(struct spi_device *spi)
{int ret;struct tft_lcd *lcd_data = devm_kzalloc(&spi->dev, sizeof(struct tft_lcd), GFP_KERNEL);ret = lcd_dt_parse(spi, lcd_data);if(ret !=0)goto err0;DEBUG("[%s]:success\n", __FUNCTION__);spi_set_drvdata(spi, lcd_data);lcd_init(spi, lcd_data);tft_fb_test(spi);ret = tft_lcd_fb_register(spi); //fb设备初始化return 0;err0:devm_gpiod_put(&spi->dev, lcd_data->rs_gpio);DEBUG("[%s]:failed\n", __FUNCTION__);return ret;}int tft_lcd_remove(struct spi_device *spi)
{struct tft_lcd *pdata = spi_get_drvdata(spi);DEBUG("[%s]:success\n", __FUNCTION__);devm_gpiod_put(&spi->dev, pdata->rs_gpio);devm_gpiod_put(&spi->dev, pdata->reset_gpio);return 0;
}struct of_device_id tft_lcd_ids[] = {{.compatible = "nanopi,tft_lcd_spi"},{},
};struct spi_driver tft_lcd_drv = {.probe  = tft_lcd_probe,.remove = tft_lcd_remove,.driver = {.owner = THIS_MODULE,.name = "tft_lcd_drv",.of_match_table = tft_lcd_ids,},
};module_spi_driver(tft_lcd_drv);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TFT LCD SPI driver");

还是要感谢一下jklinux大佬的文章的~

但是这驱动只是简单刷一下屏幕,而且是在线程里全局刷新,即使界面无更改,也要刷新,利用率非常低,为了提高效率,每次刷新只要刷更改过的界面即可,也就是刷新重绘区。
可以参考这篇:嵌入式Linux驱动笔记(二十六)------framebuffer之使用spi-tft屏幕(下)

嵌入式Linux驱动笔记(二十四)------framebuffer之使用spi-tft屏幕(上)相关推荐

  1. 嵌入式Linux驱动笔记(二十五)------Input子系统框架

    你好!这里是风筝的博客, 欢迎和我一起交流. 一.Input子系统概述 二.Input子系统架构 三.Input子系统工作机制 3.1 核心层(input.c) 3.1.1 input_init函数 ...

  2. 嵌入式Linux驱动笔记(二十九)------内存管理之伙伴算法(Buddy)分析

    你好!这里是风筝的博客, 欢迎和我一起交流. 我们知道,在一个通用操作系统里,频繁申请内存释放内存都会出现一个非常著名的内存管理问题:内存碎片. 学过操作系统的都知道,有很多行之有效的方法(比如:记录 ...

  3. 嵌入式Linux驱动笔记(二十七)------中断子系统框架分析

    你好!这里是风筝的博客, 欢迎和我一起交流. 中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行. 从硬 ...

  4. 嵌入式Linux驱动笔记--转自风筝丶

    为了阅读学习方便,将系列博客的网址进行粘贴,感谢原博客的分享. 嵌入式Linux驱动笔记(一)------第一个LED驱动程序 嵌入式Linux驱动笔记(二)------定时器 嵌入式Linux驱动笔 ...

  5. 嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】

    转自:https://blog.csdn.net/Guet_Kite/article/details/78574781 权声明:本文为 风筝 博主原创文章,未经博主允许不得转载!!!!!!谢谢合作 h ...

  6. 嵌入式Linux驱动笔记(十六)------设备驱动模型(kobject、kset、ktype)

    ###你好!这里是风筝的博客, ###欢迎和我一起交流. 前几天去面试,被问到Linux设备驱动模型这个问题,没答好,回来后恶补知识,找了些资料,希望下次能答出个满意答案. Linux早期时候,一个驱 ...

  7. 嵌入式Linux驱动笔记(五)------学习platform设备驱动

    你好!这里是风筝的博客, 欢迎和我一起交流. 设备是设备,驱动是驱动. 如果把两个糅合写一起,当设备发生变化时,势必要改写整个文件,这是非常愚蠢的做法.如果把他们分开来,当设备发生变化时,只要改写设备 ...

  8. 嵌入式Linux驱动笔记(十一)------i2c设备之mpu6050驱动

    ###你好!这里是风筝的博客, ###欢迎和我一起交流. 上一节讲了i2c框架: 嵌入式Linux驱动笔记(十)------通俗易懂式了解i2c框架 这次就来写一写真正的i2c设备驱动: mpu605 ...

  9. Cty的Linux学习笔记(十四)

    Linux学习笔记--第十四篇 环境变量配置文件: /etc/profile:预设了几个重要的变量,例如PATH,USER,LOGNAME,MAIL,INPUTRC,HOSTNAME,HISTSIZE ...

最新文章

  1. 2021年浙软夏令营预推免面经
  2. linux 给用户tmp权限,linux用户以及权限
  3. DL之DNN:自定义2层神经网络TwoLayerNet模型(封装为层级结构)利用MNIST数据集进行训练、GC对比
  4. java 邮件模板_Spring Boot 优雅地发送邮件
  5. message There is no Action mapped for namespace [/] and action name [login] associated with context
  6. java 调制信号,常见调制技术汇总
  7. ng-show和ng-if的区别和使用场景
  8. jQuery插件实现表格隔行换色且感应鼠标高亮行变色
  9. 浏览器cookie被禁掉,该如何去解决
  10. 北理珠计算机专业在广东排第几,北理珠2019广东美术类生源位居全省同类院校第一!...
  11. 使用GSON解析JSON数据
  12. 第九周 棚拍静物——让你的照片更有味道
  13. echarts网络拓扑图动态流程图
  14. 【Excel】工作中会用到的excel操作和技巧
  15. 【React-music项目问题】The AudioContext was not allowed to start. It must be resumed (or created) after a
  16. 视频教程-jQuery交互式前端开发(第一季):初体验/选择器/事件绑定-jQuery
  17. 2022-02-13 机器学习基本概念
  18. 把计算机信息传到电视屏的方法,如何将电脑内容投屏到电视上?当贝市场完整版教程...
  19. BAT批处理脚本教程
  20. mysql查看表备注_mysql表中如何查看备注

热门文章

  1. echarts引入百度地图API,设置bmap属性问题
  2. 独立站聊天机器人定制,10个神奇的聊天机器人模板帮您解决咨询难题
  3. JavaCV常用工具
  4. 教你如何简便下载网站上的视频
  5. 集合框架背后的数据结构
  6. 手把手教你计算广义遗传力
  7. 【DSP数字信号处理学习笔记】—— 详细推导DFT的快速实现算法:FFT 基于库利-图基算法的实现
  8. Google入门到精通(搜索方法经典)
  9. cp 硬连接 linux,软链接、硬链接和引用链接,以及如何实现文件快速拷贝(cp --reflink)...
  10. kubekey部署k8s集群