在早期的输出显示设备中,大部分为CRT显示器,随着技术的不断发展,现在大部分使用的是液晶显示器。这些显示设备在linux中统称为Frame Buffer设备,即帧缓冲设备,简称FB。
需要说明的是,并不是只有显示屏这种设备才是帧缓存设备,实际上帧缓冲设备只是一种显示原理,将显示缓冲区中的数据重定向到输出设备中,也许有些输出设备仅仅只是一个虚拟的设备,那为何叫帧缓冲设备?这主要是因为帧缓冲设备的显示原理是在内存中开辟一块特定的暂存区,这块暂存区记录着所有像素数据,在一定的时间内将这个数据一一对应的写入到输出设备中,这样输出设备上就是我们需要显示的信息了。
对于我购买的3.5寸TFT显示屏,其驱动芯片是ILI9488,采用的4线制spi,
其数据传输方式如下图所示
驱动主要是根据厂商提供的stm32例程来改的,在该例程里面有如下函数
从这里可以看出他是RGB565转的RGB666,在编写代码的时候也采用同样的思路
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/io.h> //含有 ioremap 函数 iounmap 函数
#include <asm/uaccess.h> //含有 copy_from_user 函数和含有 copy_to_user 函数
#include <linux/device.h> //含有类相关的设备函数
#include <linux/cdev.h>
#include <linux/platform_device.h> //包含 platform 函数
#include <linux/of.h> //包含设备树相关函数
#include <linux/fb.h> //包含 frame buffer
#include <linux/spi/spi.h>
#include <linux/regmap.h>
#include <linux/gpio.h>
static struct fb_info *myfb; //定义一个fb_info
static struct task_struct *fb_thread; //定义一个线程刷新屏幕
static struct spi_device *fbspi; //保存spi驱动指针
static struct gpio_desc *dc_pin; //dc引脚
static struct gpio_desc *reset_pin; //reset引脚
static u32 pseudo_palette[16]; //调色板缓存区
/*
@function : write an 8-bit command to the LCD screen
@param : reg: comnmand value to be written
@return : none
*/
static void fb_write_reg(struct spi_device *spi, u8 reg)
{
gpiod_set_value(dc_pin, 0); //低电平,命令
spi_write(spi, ®, 1);
}
/*
@function : write an 16-bit data into registers,rgb565->rgb666
@param : data:data to be written
@return : none
*/
static void fb_write_data_u16(struct spi_device *spi, u16 data)
{
u8 buf[3];
buf[0] = ((u8)(data>>8))&0xF8; //RED
buf[1] = ((u8)(data>>3))&0xFC; //GREEN
buf[2] = ((u8)(data<<3)); //BLUE
gpiod_set_value(dc_pin, 1); //高电平,数据
spi_write(spi, &buf[0], 1);
spi_write(spi, &buf[1], 1);
spi_write(spi, &buf[2], 1);
}
/*
@function :write an 8-bit data to the lcd screen
@param :data:data value to written
@return : none
*/
static void fb_write_data_u8(struct spi_device *spi, u8 data)
{
gpiod_set_value(dc_pin, 1); //高电平,数据
spi_write(spi, &data, 1);
}
/*
@function :setting lcd display window
@param :xstar: the begining x coordinate of the lcd display window
ystar: the begining y coordinate of the lcd display window
xEnd: the ending x coordinate of the lcd display window
yEnd: the ending y coordinate of the lcd display window
*/
static void fb_set_win(struct spi_device *spi, u16 xStar, u16 yStar, u16 xEnd, u16 yEnd){
fb_write_reg(spi, 0x2a);
fb_write_data_u8(spi, xStar >> 8);
fb_write_data_u8(spi, xStar & 0xFF);
fb_write_data_u8(spi, xEnd >> 8);
fb_write_data_u8(spi, xEnd & 0xff);
fb_write_reg(spi, 0x2b);
fb_write_data_u8(spi, yStar >> 8);
fb_write_data_u8(spi, yStar & 8);
fb_write_data_u8(spi, yEnd >> 8);
fb_write_data_u8(spi, yEnd & 0xff);
fb_write_reg(spi, 0x2c); // 开始写入GRAM,即往lcd的GRAM写数据,LCD随即显示到屏幕上
}
static void myfb_init(struct spi_device *spi){
gpiod_set_value(reset_pin, 0); //设置低电平
msleep(100);
gpiod_set_value(reset_pin, 1); //设高电平
msleep(50);
/* 写寄存器,初始化*/
fb_write_reg(spi, 0xf7);
fb_write_data_u8(spi, 0xa9);
fb_write_data_u8(spi, 0x51);
fb_write_data_u8(spi, 0x2c);
fb_write_data_u8(spi, 0x82);
fb_write_reg(spi, 0xc0);
fb_write_data_u8(spi, 0x11);
fb_write_data_u8(spi, 0x09);
fb_write_reg(spi, 0xc1);
fb_write_data_u8(spi, 0x41);
fb_write_reg(spi, 0xc5);
fb_write_data_u8(spi, 0x00);
fb_write_data_u8(spi, 0x0a);
fb_write_data_u8(spi, 0x80);
fb_write_reg(spi, 0xb1);
fb_write_data_u8(spi, 0xB0);
fb_write_data_u8(spi, 0x11);
fb_write_reg(spi, 0xb4);
fb_write_data_u8(spi, 0x02);
fb_write_reg(spi, 0xb6);
fb_write_data_u8(spi, 0x02);
fb_write_data_u8(spi, 0x42);
fb_write_reg(spi, 0xb7);
fb_write_data_u8(spi, 0xc6);
fb_write_reg(spi, 0xbe);
fb_write_data_u8(spi, 0x00);
fb_write_data_u8(spi, 0x04);
fb_write_reg(spi, 0xe9);
fb_write_data_u8(spi, 0x00);
fb_write_reg(spi, 0x36);
fb_write_data_u8(spi, (1<<3)|(0<<7)|(1<<6)|(1<<5));
fb_write_reg(spi, 0x3a);
fb_write_data_u8(spi, 0x66);
fb_write_reg(spi, 0xe0);
fb_write_data_u8(spi, 0x00);
fb_write_data_u8(spi, 0x07);
fb_write_data_u8(spi, 0x10);
fb_write_data_u8(spi, 0x09);
fb_write_data_u8(spi, 0x17);
fb_write_data_u8(spi, 0x0b);
fb_write_data_u8(spi, 0x41);
fb_write_data_u8(spi, 0x89);
fb_write_data_u8(spi, 0x4b);
fb_write_data_u8(spi, 0x0a);
fb_write_data_u8(spi, 0x0c);
fb_write_data_u8(spi, 0x0e);
fb_write_data_u8(spi, 0x18);
fb_write_data_u8(spi, 0x1b);
fb_write_data_u8(spi, 0x0f);
fb_write_reg(spi, 0xe1);
fb_write_data_u8(spi, 0x00);
fb_write_data_u8(spi, 0x17);
fb_write_data_u8(spi, 0x1a);
fb_write_data_u8(spi, 0x04);
fb_write_data_u8(spi, 0x0e);
fb_write_data_u8(spi, 0x06);
fb_write_data_u8(spi, 0x2f);
fb_write_data_u8(spi, 0x45);
fb_write_data_u8(spi, 0x43);
fb_write_data_u8(spi, 0x02);
fb_write_data_u8(spi, 0x0a);
fb_write_data_u8(spi, 0x09);
fb_write_data_u8(spi, 0x32);
fb_write_data_u8(spi, 0x36);
fb_write_data_u8(spi, 0x0f);
fb_write_reg(spi, 0x11);
mdelay(50);
fb_write_reg(spi, 0x29);
mdelay(200);
}
void fb_refresh(struct fb_info *fbi, struct spi_device *spi){
int x, y;
u16 *p =(u16 *)(fbi->screen_base );
fb_set_win(spi, 0, 0, 479, 319);
for (y = 0; y < fbi->var.yres; y++){
for (x = 0; x < fbi->var.xres; x++){
fb_write_data_u16(spi, *p++);
}
}
}
int thread_func_fb(void *data){
struct fb_info *fbi = (struct fb_info *)data;
while(1){
if(kthread_should_stop())
break;
fb_refresh(fbi,fbspi);
}
return 0;
}
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 u32 pseudo_palette[16];
static int myfb_setcolreg(u32 regno, u32 red, u32 green, u32 blue, u32 transp, struct fb_info *info){
unsigned int val;
if (regno > 16)
return 1;
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
pseudo_palette[regno] = val;
return 0;
}
static struct fb_ops myfb_ops = {
.owner = THIS_MODULE,
.fb_write = fb_sys_write,
.fb_setcolreg = myfb_setcolreg, /* 设置颜色寄存器 */
.fb_fillrect = sys_fillrect, /* 用像素行填充矩形框,通用库函数 */
.fb_copyarea = sys_copyarea, /* 将屏幕的一个矩形区域复制到另一个区域,通用库函数*/
.fb_imageblit = sys_imageblit, /* 显示一幅图像,通用库函数*/
};
static void myfb_update(struct fb_info *fbi, struct list_head *pagelist){
/* 比较粗暴的方式,直接全部刷新 */
fbi->fbops->fb_pan_display(&fbi->var,fbi); /*将应用层数据刷新到FrameBuffer缓存中*/
}
static struct fb_deferred_io myfb_defio = {
.delay = HZ/20,
.deferred_io = &myfb_update,
};
static struct fb_var_screeninfo myfb_var ={
.rotate = 0,
.xres =480,
.yres =320,
.xres_virtual =480,
.yres_virtual =320,
.bits_per_pixel =16, //每个像素的位数
.nonstd =1,
/*rgb565*/
.red.offset = 11,
.red.length = 5,
.green.offset = 5,
.green.length =6,
.blue.offset = 0,
.blue.length = 5,
.transp.offset = 0,
.transp.length = 0,
.activate =FB_ACTIVATE_NOW,
.vmode =FB_VMODE_NONINTERLACED,
};
static struct fb_fix_screeninfo myfb_fix ={
.type =FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_TRUECOLOR, //
.line_length =480*2, // width *bpp /8 就是宽度乘每个像素需要的位数除8 ,就是每行需要的字节数
.accel = FB_ACCEL_NONE, //没有使用硬件加速
.id = "myfb",
};
static int myfb_probe(struct spi_device *spi){
int ret;
void *gmem_addr;
u32 gmem_size;
fbspi = spi; //保存spi驱动指针
printk(KERN_ERR"register myfb_spi_probe!\n");
//申请GPIO用作DC引脚
dc_pin = devm_gpiod_get(&spi->dev,"dc",GPIOF_OUT_INIT_LOW);
if(IS_ERR(dc_pin)){
printk(KERN_ERR"fail to request dc_gpios!\n");
}
//申请GPIO用作RESET引脚
reset_pin =devm_gpiod_get(&spi->dev,"reset",GPIOF_OUT_INIT_HIGH);
if(IS_ERR(reset_pin)){
printk(KERN_ERR"fail to request reset-gpios!\n");
return -1;
}
gpiod_direction_output(dc_pin, 0); //设置输出方向
gpiod_direction_output(reset_pin, 1); //设置输出方向
printk(KERN_INFO"register myfb_probe dev !\n");
myfb = framebuffer_alloc(sizeof(struct fb_info), &spi->dev); //向内核申请fb_info结构体
//初始化底层操作结构体
myfb->fbops =&myfb_ops; //指定底层操作结构体
gmem_size =320*480*2; //设置显存大小,16bit 占2字节
gmem_addr =kmalloc(gmem_size,GFP_KERNEL); //分配Frame Buffer 显存
if(!gmem_addr){
printk(KERN_ERR"fail to alloc fb buffer!\n");
}
myfb->pseudo_palette = pseudo_palette;
//假的16色调色板,里面存放了16色的数据,可以通过8bpp数据来找到调色板里面的16色颜色索引值,
//模拟出16色颜色来,节省内存,不需要的话,指向一个不用的数组即可
myfb->var =myfb_var; // 设置分辨率参数
myfb->fix =myfb_fix; // 设置显示参数
myfb->screen_buffer =gmem_addr; //设置显存地址
myfb->screen_size = gmem_size; //设置显存大小
myfb->fix.smem_len = gmem_size; //设置应用层显存大小
myfb->fix.smem_start =(u32) gmem_addr; //设置应用层数据地址
memset((void *)myfb->fix.smem_start, 0,myfb->fix.smem_len); //清楚数据缓存
myfb_init(spi); //初始化显示屏
myfb->fbdefio =&myfb_defio; //设置刷新参数
fb_deferred_io_init(myfb); //初始化刷新机制
ret = register_framebuffer(myfb); //注册fb驱动
if(ret)
{
framebuffer_release(myfb);
unregister_framebuffer(myfb);
devm_gpiod_put(&spi->dev, dc_pin);
devm_gpiod_put(&spi->dev, reset_pin);
printk(KERN_ERR"fail to register fb dev!\n");
return -1;
}
//开启一个线程用来刷新显示屏
fb_thread = kthread_run(thread_func_fb,myfb,spi->modalias);
return 0;
}
int myfb_remove(struct spi_device *spi){
fb_deferred_io_cleanup(myfb); //清除刷新机制
unregister_framebuffer(myfb);
devm_gpiod_put(&spi->dev,dc_pin);
devm_gpiod_put(&spi->dev,reset_pin);
return 0;
}
struct of_device_id myfb_match[] ={
{.compatible = "ilitek,ili9488"},
{},
};
struct spi_driver myfb_drv = {
.driver ={
.owner = THIS_MODULE,
.name = "myfb_spi_driver",
.of_match_table = myfb_match,
},
.probe =myfb_probe,
.remove = myfb_remove,
} ;
module_spi_driver(myfb_drv);
MODULE_LICENSE("GPL"); //不加的话加载会有错误提醒
MODULE_AUTHOR("[email protected]"); //作者
MODULE_VERSION("0.1"); //版本
MODULE_DESCRIPTION("myfb_spi_driver"); //简单的描述
在bcm2711-rpi-4-b.dts设备树修改&spi0
&spi0 {
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
status = "okay";
ili9488@0{
status = "okay";
compatible = "ilitek,ili9488";
reg = <0>; /* CE0 Ƭѡ*/
#address-cells = <1>;
#size-cells = <0>;
spi-max-frequency = <32000000>;
dc-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio 18 GPIO_ACTIVE_HIGH>;
rotation =<90>;
};
spidev1: spidev@1{
compatible = "spidev";
reg = <1>; /* CE1 */
#address-cells = <1>;
#size-cells = <0>;
spi-max-frequency = <125000000>;
};
};
使用insmod加载ili9488.ko
加载完成后,在/dev目录下可以看到fb0,此为驱动里面注册的帧缓冲区
/dev/tty0是默认映射到/dev/fb0上的,所以,我们通过操作/dev/tty0即可操作屏幕
对于调色板myfb_setcolreg()函数,这里并未使用,是直接使用的rgb。
调色板的概念:假如LCD的数据位为16位,那么framebuffer应该每个像素占据16bit,但是为了节省空间,使用8bit表示一个像素,这时候需要引入调色板才能正确传输16位的数据给LCD(正确传输每个像素的数据)。调色板其实就是一片内存,这里面每一格存放16bit的数据。当LCD控制器从framebuffer中取出8bit的数据后,不是直接传给LCD,而是用这个8bit作为索引,从调色板中取出16bit的数据,然后发给LCD。所以,在使用8BPP(每个像素点的位数)格式时,framebuffer中存放的是伪彩色,16BPP或者24BPP格式时,framebuffer中存放的才是真彩色。所以在使用8BPP格式时,首先要设置调色板。
此部分来源于链接: link
链接: [link](链接: link)