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

news/2025/1/11 4:12:43/

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

欢迎和我一起交流。


最近入手了一块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查看信息也确实发现是这样:
错误忘记复制下来了,懒得复现了,只有一些截图:
error
register
查看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字样了
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屏幕(下)


http://www.ppmy.cn/news/905192.html

相关文章

[个人笔记] WinSrv批量添加DNS记录和条件转发器记录

Windows Server - 运维篇 第三章 WinSrv批量添加DNS记录和条件转发器记录 Windows Server - 运维篇系列文章回顾WinSrv批量添加DNS记录和条件转发器记录单条记录添加DNS条件转发器记录批量新增DNS条件转发器记录批量导出DNS条件转发器 参考来源 系列文章回顾 第一章 域控使用C…

Arduino UNO驱动合宙1.8‘TFT SPI屏幕示例演示(含资料包)

Arduino UNO驱动合宙1.8"TFT SPI屏幕示例演示 效果展示 驱动参考资料包 来源于&#xff1a;http://www.lcdwiki.com/zh/1.8inch_Arduino_SPI_Module_ST7735S_SKU:MAR1801资料包中2种驱动方式&#xff1a;模拟SPI和硬件SPI驱动方式 1.8inch Arduino SPI Module ST7735S…

linux驱动tft屏幕,Linux2.6.26.5加入TFT-LCD驱动

首先下载内核源码&#xff0c;我这里用的是linux-2.6.26.5的内核源码。本文引用地址&#xff1a;http://www.eepw.com.cn/article/148334.htm (1) 在arch/arm/mach-s3c2410/mach-smdk2410.c里添加头文件&#xff1a; #include (2) 在arch/arm/ plat-s3c24xx / commON-smdk.c文件…

microPython驱动tft屏幕显示中文终极解决方案

microPython驱动tft屏幕显示中文终极解决方案 一、运行效果 二、实现原理 原理同上篇文章一样&#xff0c;用在线汉字取模工具获取点阵的字节信息&#xff0c;通过st7789py.py驱动程序显示出来。 上次的程序只能显示部分汉字&#xff0c;需要显示哪些字自己去在线网站取模&a…

STM32驱动ST7789V2 tft屏幕

一 . 简介 本次教程使用的是1.54寸240*240像素的tft屏幕&#xff0c;其接口协议为SPI协议。在使用的过程中仅需要四根数据即可驱动点亮屏幕。然后硬件使用的是STM32F103C8T6核心板&#xff0c;用的是SPI2。一般购买屏幕的话它们会提供对应的例程&#xff0c;直接拿过来修改即可…

TFT屏幕使用(CUBEMX+SPI)

目 录 1.SPI 2.SPIDMA 1.SPI 使用SPI通信协议的器件有很多&#xff0c;这里以TFT屏驱动为ST7735为例。 设置SPI&#xff0c;因为我使用的是STM32F107ZGT的板子所以我的SPI 速度能达到 42M&#xff0c;还需要设置其它的控制引脚。 引脚设置如下所示。 /*GPIO的宏定义…

OLED TFT屏幕相关

文章目录 0.tft espi1.oled1.1 字体4.2 模拟spi oled4.3 IIC oled 2ips tft 0.tft espi 镜像设置 1.oled 1.1 字体 1206-----1608-------2412 4.2 模拟spi oled 我用阿里云盘分享了「OLED」&#xff0c;你可以不限速下载&#x1f680; 复制这段内容打开「阿里云盘」App 即…

ESP32开发板连接TFT屏幕

前言 esp32连接tft屏幕踩了很多坑&#xff0c;也查阅了很多资料&#xff0c;这里简单总结一下&#xff0c;希望能为像我一样的小白排一下坑。 ESP32的引脚 关于esp32的引脚功能&#xff0c;官方给出的文档写的比较多&#xff0c;官方文档如下&#xff1a; ESP32WROOM32技术指…