前面驱动的spi lcd仅仅是刷了一下图而已, 如果要让QT图形程序在此lcd上显示的话,还需要实现标准的framebuffer设备驱动才可以.
实现一个fb设备驱动好, QT程序就可以在显存里显示出来。 只需要在设备驱动把显存的数据通过spi控制器发送到屏的驱动ic,就可以让QT程序在spi lcd屏上显示出来. 但显存的数据有可能经常发生变化(界面切换), spi lcd屏也应跟着显示出改变过的画面。
在设备驱动里用一个内核线程, 循环把显存的数据通过spi控制发送到屏的驱动ic. 这样应用程序只需改变显存里的数据就可以了,无需考虑屏幕的更新.
同时, QT程序不支持16位色的fb设备里显示,所以只能在设备驱动把32位色的显存转换成rgb565后,再把数据发送到屏的驱动ic.
/////////////////////////////////////////////////////////////////
设备驱动由两个文件组成:
fb_model.c主要实现fb设备驱动模型.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/spi/spi.h>
#include <linux/dma-mapping.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#define X 240
#define Y 320
typedef struct {
struct spi_device *spi; //记录fb_info对象对应的spi设备对象
struct task_struct *thread; //记录线程对象的地址,此线程专用于把显存数据发送到屏的驱动ic
}lcd_data_t;
struct fb_ops fops = {
};
extern void show_fb(struct fb_info *fbi, struct spi_device *spi);
int thread_func(void *data)
{
struct fb_info *fbi = (struct fb_info *)data;
lcd_data_t *ldata = fbi->par;
while (1)
{
if (kthread_should_stop())
break;
show_fb(fbi, ldata->spi);
//
}
return 0;
}
int myfb_new(struct spi_device *spi) //此函数在spi设备驱动的probe函数里被调用
{
struct fb_info *fbi;
u8 *v_addr;
u32 p_addr;
lcd_data_t *data;
v_addr = dma_alloc_coherent(NULL, X*Y*4, &p_addr, GFP_KERNEL);
//额外分配lcd_data_t类型空间
fbi = framebuffer_alloc(sizeof(lcd_data_t), NULL);
//data = &fbi[1]; //data指针指向额外分配的空间
data = fbi->par; //data指针指向额外分配的空间
data->spi = spi;
fbi->var.xres = X;
fbi->var.yres = Y;
fbi->var.xres_virtual = X;
fbi->var.yres_virtual = Y;
fbi->var.bits_per_pixel = 32; // 屏是rgb565, 但QT程序只能支持32位.还需要在刷图时把32位的像素数据转换成rgb565
fbi->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 = X*Y*4;
fbi->fix.type = FB_TYPE_PACKED_PIXELS;
fbi->fix.visual = FB_VISUAL_TRUECOLOR;
fbi->fix.line_length = X*4;
fbi->fbops = &fops;
fbi->screen_base = v_addr; //显存虚拟地址
fbi->screen_size = X*Y*4; //显存大小
spi_set_drvdata(spi, fbi);
register_framebuffer(fbi);
data->thread = kthread_run(thread_func, fbi, spi->modalias);
return 0;
}
void myfb_del(struct spi_device *spi) //此函数在spi设备驱动remove时被调用
{
struct fb_info *fbi = spi_get_drvdata(spi);
lcd_data_t *data = fbi->par;
kthread_stop(data->thread); //让刷图线程退出
unregister_framebuffer(fbi);
dma_free_coherent(NULL, fbi->screen_size, fbi->screen_base, fbi->fix.smem_start);
framebuffer_release(fbi);
}
///////////////////
test.c 主要实现spi lcd的操作
#include
#include
#include
#include
#include
#include
struct myspi_lcd_pdata {
int dc_io;
int reset_io;
};
struct spi_lcd_cmd{
u8 reg_addr; // command
u8 len; //需要从spi_lcd_datas数组里发出数据字节数
int delay_ms; //此命令发送数据完成后,需延时多久
}cmds[] = {
{0xCB, 5, 0},
{0xCF, 3, 0},
{0xEB, 3, 0},
{0xEA, 2, 0},
{0xED, 4, 0},
{0xF7, 1, 0},
{0xC0, 1, 0},
{0xC1, 1, 0},
{0xC5, 2, 0},
{0xC7, 1, 0},
{0x36, 1, 0},
{0x3A, 1, 0},
{0xB1, 2, 0},
{0xB6, 3, 0},
{0xF2, 1, 0},
{0x26, 1, 0},
{0xE0, 15, 0},
{0xE1, 15, 0},
{0x11, 0, 120},
{0x29, 0, 0},
{0x2c, 0, 0},
};
u8 spi_lcd_datas[] = {
0x39, 0x2c, 0x00, 0x34, 0x20, // command: 0xCB要发出的数据
0x00, 0xC1, 0x30, // command: 0xCF
0x85, 0x00, 0x78, // command: 0xEB
0x00, 0x00, // command: 0xEA
0x64, 0x03, 0x12, 0x81, // command: 0xED
0x20, // command: 0xF7
0x23, // command: 0xC0
0x10, // command: 0xC1
0x3e, 0x28, // command: 0xC5
0x86, // command: 0xC7
0x48, // command: 0x36
0x55, // command: 0x3A
0x00, 0x18, // command: 0xB1
0x08, 0x82, 0x27, // command: 0xB6
0x00, // command: 0xF2
0x01, // command: 0x26
0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, //command: 0xE0
0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, //command: 0xE1
};
extern int myfb_new(struct spi_device *);
extern void myfb_del(struct spi_device *);
void write_command(struct spi_device *spi, u8 cmd)
{
struct myspi_lcd_pdata *pdata = spi->dev.platform_data;
// dc , command:0
gpio_direction_output(pdata->dc_io, 0);
spi_write(spi, &cmd, 1);
}
void write_data(struct spi_device *spi, u8 data)
{
struct myspi_lcd_pdata *pdata = spi->dev.platform_data;
// dc , data:1
gpio_direction_output(pdata->dc_io, 1);
spi_write(spi, &data, 1);
}
//初始化spi_lcd
void spi_lcd_init(struct spi_device *spi)
{
struct myspi_lcd_pdata *pdata = spi->dev.platform_data;
int i, j, n;
// 屏复位
gpio_direction_output(pdata->reset_io, 0);
mdelay(100);
gpio_set_value(pdata->reset_io, 1);
mdelay(100);
n = 0; // n用于记录数据数组spi_lcd_datas的位置
//发命令,并发出命令所需的数据
for (i = 0; i < ARRAY_SIZE(cmds); i++) //命令
{
write_command(spi, cmds[i].reg_addr);
for (j = 0; j < cmds[i].len; j++) //发出命令后,需要发出的数据
write_data(spi, spi_lcd_datas[n++]);
if (cmds[i].delay_ms) //如有延时则延时
mdelay(cmds[i].delay_ms);
}
}
//设置要刷屏的开始坐标
void addset(struct spi_device *spi, unsigned int x,unsigned int y)
{
write_command(spi, 0x2a); //发出x坐标
write_data(spi, x>>8);
write_data(spi, x&0xff);
write_command(spi, 0x2b); //发出y坐标
write_data(spi, y>>8);
write_data(spi, y&0xff);
write_command(spi, 0x2c);
}
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坐标开始刷
// gpio_direction_output(pdata->dc_io, 1);
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);
}
}
}
int myprobe(struct spi_device *spi)
{
struct myspi_lcd_pdata *pdata = spi->dev.platform_data;
int ret;
int x, y;
u16 color0 = 0x001f; // RGB565, blue
u16 color1 = 0xf800; // red
u16 color2 = 0x07e0; // green
u16 color3 = 0xffff; // white
u16 color;
ret = gpio_request(pdata->reset_io, spi->modalias);
if (ret < 0)
goto err0;
ret = gpio_request(pdata->dc_io, spi->modalias);
if (ret < 0)
goto err1;
spi_lcd_init(spi); //初始化屏
printk("probe ...%s\n", spi->modalias);
return myfb_new(spi); //fb设备初始化
err1:
gpio_free(pdata->reset_io);
err0:
return ret;
}
int myremove(struct spi_device *spi)
{
struct myspi_lcd_pdata *pdata = spi->dev.platform_data;
myfb_del(spi); //fb设备回收
gpio_free(pdata->dc_io);
gpio_free(pdata->reset_io);
printk("%s remove\n", spi->modalias);
return 0;
}
struct spi_device_id ids[] = {
{"myspi_lcd"},
{},
};
struct spi_driver myspi_drv = {
.driver = {
.owner = THIS_MODULE,
.name = "myspi_drv",
},
.probe = myprobe,
.remove = myremove,
.id_table = ids,
};
module_spi_driver(myspi_drv);
MODULE_LICENSE("GPL");
///////
Makefile:
obj-m += test_fb.o
test_fb-objs := test.o fb_model.o
KSRC := /disk3/myown/h3/orangepi_sdk/source/linux-3.4.112/
export ARCH := arm
export CROSS_COMPILE := arm-linux-gnueabihf-
all:
make -C $(KSRC) modules M=`pwd`
.PHONY : clean
clean:
make -C $(KSRC) modules clean M=`pwd`
//////////////////////////////////////////
编译模块后,加载模块。/dev/目录下多生成一个fb8设备文件.
设置QT环境变量: export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb8