前序

之前实现了简单的spi驱动(刷屏else,类似于单片机刷点绘图),但是那些太麻烦,上了系统咱就干点系统的事,没必要一个一个点绘制 还要找字库,麻烦得很。接下来就来例举如何通过frambuffer把之前的spi驱动变高效率,即能够在上面运行QT程序。

前世

之前的基础连接blog如下:
Linux St7789 1.3寸屏驱动
Linux St7735 0.96寸屏驱动
参考的文章如下:感谢这几位老哥
连接一
连接二
主要是这篇:主要参考博客

今生

显示效果:
原子的QT QDesk demo

步骤一 配置设备树:

//功能引脚节点pinctrl_ipsRes: ipsRes {    //屏幕复位u引脚fsl,pins = <MX6UL_PAD_GPIO1_IO01__GPIO1_IO01        0x10B0 /* LED0 */>;};  pinctrl_ipsDc: ipsDc {      //屏幕dc(data or command)u引脚fsl,pins = <MX6UL_PAD_GPIO1_IO04__GPIO1_IO04        0x10B0 >;};..............//附加节点&ecspi3 {fsl,spi-num-chipselects = <1>;cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; /* cant't use cs-gpios! */pinctrl-names = "default";pinctrl-0 = <&pinctrl_ecspi3>;status = "okay";spidev: ipsTft@0 {compatible = "alientek,ipsTft";spi-max-frequency = <1000000000>;reg = <0>;};
};

其中,原子板子上的spi接口已经满了,唯一扩展出来的spi接口都已经接了磁力计,所以需要保留磁力计的引脚接口,在驱动程序probe时将其拉高(失效该器件),避免干扰。引脚接线可通过原理图以及核心板图配合查看(引脚功能最全的是核心板,底板原理图有些功能没标出来,例如spi3的接口,所以 需要配合查找该设备引脚)。其中St7789的 dc 以及 RES 引脚需要自定义,通过系统gpio pinctrl来进行控制(不在spi硬件接口内)。

步骤二 驱动程序编写:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include "ipsDriver.h"
#include "image.h"
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/uaccess.h> typedef struct {struct spi_device *spi; //记录fb_info对象对应的spi设备对象struct task_struct *thread; //记录线程对象的地址,此线程专用于把显存数据发送到屏的驱动ic
}lcd_data_t;
struct fb_info *fbi;
void show_fb(struct fb_info *fbi, struct spi_device *spi);
static int tft_lcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info);
void LCD_Clear(struct ipsTft_dev *dev);
struct fb_ops fops = {.owner       = THIS_MODULE,.fb_setcolreg    = tft_lcdfb_setcolreg,.fb_fillrect = cfb_fillrect,.fb_copyarea    = cfb_copyarea,.fb_imageblit   = cfb_imageblit,
};int thread_func(void *data)
{lcd_data_t *ldata = fbi->par;while (1){   if (kthread_should_stop())break;show_fb(fbi, ldata->spi);}return 0;
}static u32 pseudo_palette[240*240];
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){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);pseudo_palette[regno] = val;return 0;
}int myfb_new(struct spi_device *spi) //此函数在spi设备驱动的probe函数里被调用
{u8 *v_addr;u32 p_addr;lcd_data_t *data;/*coherent:连贯的分配连贯的物理内存*/v_addr = dma_alloc_coherent(NULL, X*Y*4, &p_addr, GFP_KERNEL);//额外分配lcd_data_t类型空间fbi = framebuffer_alloc(sizeof(lcd_data_t), NULL);if(fbi == NULL){printk("fbi allow error!\n");return -1;}data = fbi->par; //data指针指向额外分配的空间data->spi = spi;fbi->pseudo_palette = pseudo_palette;fbi->var.activate       = FB_ACTIVATE_NOW;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 = 11;// fbi->var.red.length = 5;// fbi->var.green.offset = 5;// fbi->var.green.length = 6;// fbi->var.blue.offset = 0;// fbi->var.blue.length = 5;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; //显存大小register_framebuffer(fbi);data->thread = kthread_run(thread_func, fbi, spi->modalias);return 0;
}void myfb_del(void) //此函数在spi设备驱动remove时被调用
{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);
}/** @description  : 向ipsTft多个寄存器写入数据* @param - dev:  ipsTft设备* @param - reg:  要写入的寄存器首地址* @param - val:  要写入的数据缓冲区* @param - len:  要写入的数据长度* @return       :   操作结果*/
static s32 ipsTft_write_regs(struct spi_device *spi,u8 *buf, u32 len)
{int ret;struct spi_message m;struct spi_transfer *t;t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */t->tx_buf = buf;          /* 要写入的数据 */t->len = len;               /* 写入的字节数 */spi_message_init(&m);       /* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m);    /* 同步发送 */kfree(t);                 /* 释放内存 */return ret;
}/** @description  : 向ipsTft指定寄存器写入指定的值,写一个寄存器* @param - dev:  ipsTft设备* @param - reg:  要写的寄存器* @param - data: 要写入的值* @return   :    无*/    static void ipsTft_write_onereg(struct spi_device *spi, u8 buf)
{ipsTft_write_regs(spi,&buf, 1);
}/*funciton: 写一个命令
*/
void write_command(struct spi_device *spi, u8 cmd)
{// dc , command:0gpio_set_value(ipsTftdev.dc_gpio, 0); ipsTft_write_onereg(spi,cmd);
}
/*funciton: 写一个数据
*/
void write_data(struct spi_device *spi, u8 data)
{gpio_set_value(ipsTftdev.dc_gpio, 1);ipsTft_write_onereg(spi,data);
}
/*funciton: 写多个数据
*/
static void write_datas(struct spi_device *spi, u8 *data,u32 len)
{gpio_set_value(ipsTftdev.dc_gpio, 1);ipsTft_write_regs(spi,data,len);
}/** @description      : 打开设备* @param - inode     : 传递给驱动的inode* @param - filp   : 设备文件,file结构体有个叫做pr似有ate_data的成员变量*                       一般在open的时候将private_data似有向设备结构体。* @return            : 0 成功;其他 失败*/
static int ipsTft_open(struct inode *inode, struct file *filp)
{filp->private_data = &ipsTftdev; /* 设置私有数据 */return 0;
}/** @description      : 关闭/释放设备* @param - filp   : 要关闭的设备文件(文件描述符)* @return             : 0 成功;其他 失败*/
static int ipsTft_release(struct inode *inode, struct file *filp)
{return 0;
}/* ipsTft操作函数 */
static const struct file_operations ipsTft_ops = {.owner = THIS_MODULE,.open = ipsTft_open,.release = ipsTft_release,
};void Address_set(struct spi_device *spi,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)
{ write_command(spi,0x2a);write_data(spi,x1>>8);write_data(spi,x1);write_data(spi,x2>>8);write_data(spi,x2);write_command(spi,0x2b);write_data(spi,y1>>8);write_data(spi,y1);write_data(spi,y2>>8);write_data(spi,y2);write_command(spi,0x2C);
}/*刷屏函数
*/
void LCD_Clear(struct ipsTft_dev *dev)
{u16 i,j;u16 *memory;memory = (u16 *)kzalloc(240*240*2, GFP_KERNEL);   /* 申请内存 */Address_set(dev->private_data,0,0,LCD_W-1,LCD_H-1);write_command(dev->private_data,0x2C);msleep(3000);//延迟显示,显示完上面钢铁侠图片memcpy(memory,gImage_q,240*240*2);int count = 0,split=10;while (count < split)//split 用于设置分批发送数据,有时候st7789spi速率跟不上容易丢数据,偶尔会提示spi2 send ....类似的错误{write_datas(dev->private_data,(u8 *)memory+count*240*240*2/split,240*240*2/split);count++;}kfree(memory);
}
/** ipsTft内部寄存器初始化函数 * @param      : 无* @return   : 无*/
void ipsTft_reginit(struct ipsTft_dev *dev)
{int i, j, n;gpio_set_value(ipsTftdev.res_gpio, 0);mdelay(20);gpio_set_value(ipsTftdev.res_gpio, 1);mdelay(20);n = 0; // n用于记录数据数组spi_lcd_datas的位置//发命令,并发出命令所需的数据for (i = 0; i < ARRAY_SIZE(cmds); i++) //命令{write_command(dev->private_data, cmds[i].reg_addr);for (j = 0; j < cmds[i].len; j++) //发出命令后,需要发出的数据if(cmds[i].len!=0)write_data(dev->private_data, spi_lcd_datas[n++]);if (cmds[i].delay_ms) //如有延时则延时mdelay(cmds[i].delay_ms);}n=0;LCD_Clear(dev);msleep(5000);printk("ips init finish!\n");
}//show frambuffer
//framebuffer线程刷屏函数
void show_fb(struct fb_info *fbi, struct spi_device *spi)
{int x, y;u32 k;u32 *p = (u16 *)(fbi->screen_base);u16 c;u8 *pp;u16 *memory;memory = kzalloc(240*2*240, GFP_KERNEL);   /* 申请内存 */Address_set(spi,0,0,LCD_W-1,LCD_H-1);write_command(spi,0x2C);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*((u16 *)memory+y*fbi->var.yres+x) = ((c&0xff)<<8)|((c&0xff00)>>8);}}//split 用于设置分批发送数据,有时候st7789spi速率跟不上容易丢数据,偶尔会提示spi2 send ....类似的错误int count = 0,split=1;  while (count < split){write_datas(spi,(u8 *)memory+count*fbi->var.yres*fbi->var.xres*2/split,fbi->var.yres*fbi->var.xres*2/split);count++;}kfree(memory);
}/** @description     : spi驱动的probe函数,当驱动与*                    设备匹配以后此函数就会执行* @param - client  : spi设备* @param - id      : spi设备ID* */
static int ipsTft_probe(struct spi_device *spi)
{int ret = 0;/* 1、构建设备号 */if (ipsTftdev.major) {ipsTftdev.devid = MKDEV(ipsTftdev.major, 0);register_chrdev_region(ipsTftdev.devid, ipsTft_CNT, ipsTft_NAME);} else {alloc_chrdev_region(&ipsTftdev.devid, 0, ipsTft_CNT, ipsTft_NAME);ipsTftdev.major = MAJOR(ipsTftdev.devid);}/* 2、注册设备 */cdev_init(&ipsTftdev.cdev, &ipsTft_ops);cdev_add(&ipsTftdev.cdev, ipsTftdev.devid, ipsTft_CNT);/* 3、创建类 */ipsTftdev.class = class_create(THIS_MODULE, ipsTft_NAME);if (IS_ERR(ipsTftdev.class)) {return PTR_ERR(ipsTftdev.class);}/* 4、创建设备 */ipsTftdev.device = device_create(ipsTftdev.class, NULL, ipsTftdev.devid, NULL, ipsTft_NAME);if (IS_ERR(ipsTftdev.device)) {return PTR_ERR(ipsTftdev.device);}/* 获取设备树中cs片选信号 */ipsTftdev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");if(ipsTftdev.nd == NULL) {printk("ecspi3 node not find!\r\n");return -EINVAL;}/* 2、 获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */ipsTftdev.cs_gpio = of_get_named_gpio(ipsTftdev.nd, "cs-gpio", 0);if(ipsTftdev.cs_gpio < 0) {printk("can't get cs-gpio");return -EINVAL;}ipsTftdev.nd = of_find_node_by_path("/ipsRes");if(ipsTftdev.nd == NULL) {printk("res-gpio node not find!\r\n");return -EINVAL;}ipsTftdev.res_gpio = of_get_named_gpio(ipsTftdev.nd, "res-gpio", 0);if(ipsTftdev.res_gpio < 0) {printk("can't get res-gpio");return -EINVAL;}ipsTftdev.nd = of_find_node_by_path("/ipsDc");if(ipsTftdev.nd == NULL) {printk("ipsDcgpio node not find!\r\n");return -EINVAL;}ipsTftdev.dc_gpio = of_get_named_gpio(ipsTftdev.nd, "dc-gpio", 0);if(ipsTftdev.dc_gpio < 0) {printk("can't get ipsDc-gpio");return -EINVAL;}/* 3、设置GPIO1_IO20为输出,并且输出高电平 */ret = gpio_direction_output(ipsTftdev.cs_gpio, 1);//失能板子上的磁力计if(ret < 0) {printk("can't set cs gpio!\r\n");}gpio_set_value(ipsTftdev.cs_gpio,1);ret = gpio_direction_output(ipsTftdev.res_gpio, 1);if(ret < 0) {printk("can't set res gpio!\r\n");}ret = gpio_direction_output(ipsTftdev.dc_gpio, 1);if(ret < 0) {printk("can't set dc gpio!\r\n");}/*初始化spi_device */spi->mode = SPI_MODE_2;   /*MODE0,CPOL=0,CPHA=0 //出问题的地方!!!*/spi_setup(spi);ipsTftdev.private_data = spi; /* 设置私有数据 *//* 初始化ipsTft内部寄存器 */ipsTft_reginit(&ipsTftdev);myfb_new(spi); //fb设备初始化        return 0;
}/** @description     : spi驱动的remove函数,移除spi驱动的时候此函数会执行* @param - client   : spi设备* @return          : 0,成功;其他负值,失败*/
static int ipsTft_remove(struct spi_device *spi)
{myfb_del();//注销fb/* 删除设备 */cdev_del(&ipsTftdev.cdev);unregister_chrdev_region(ipsTftdev.devid, ipsTft_CNT);/* 注销掉类和设备 */device_destroy(ipsTftdev.class, ipsTftdev.devid);class_destroy(ipsTftdev.class);return 0;
}/* 传统匹配方式ID列表 */
static const struct spi_device_id ipsTft_id[] = {{"alientek,ipsTft", 0},  {}
};/* 设备树匹配列表 */
static const struct of_device_id ipsTft_of_match[] = {{ .compatible = "alientek,ipsTft" },{ /* Sentinel */ }
};/* SPI驱动结构体 */
static struct spi_driver ipsTft_driver = {.probe = ipsTft_probe,.remove = ipsTft_remove,.driver = {.owner = THIS_MODULE,.name = "ipsTft",.of_match_table = ipsTft_of_match, },.id_table = ipsTft_id,
};/** @description : 驱动入口函数* @param       : 无* @return       : 无*/
static int __init ipsTft_init(void)
{return spi_register_driver(&ipsTft_driver);
}/** @description  : 驱动出口函数* @param       : 无* @return       : 无*/
static void __exit ipsTft_exit(void)
{spi_unregister_driver(&ipsTft_driver);
}module_init(ipsTft_init);
module_exit(ipsTft_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("raymond");

makefile文件

KERNELDIR := /home/raymond/linux/iMX6ULL/MyArmLinuxProgram/linuxKernal
CURRENT_PATH := $(shell pwd)
obj-m := ipsDriver.o
build: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

其中,image.h里面含有一张240*240的图片,用于开机显示图片,可自定义数组(用图像转换器转换)。
由于数组文件太长,我一并和转换图片软件放到一起,置于资料下载处吧;
资料下载链接如下:
资料下载地址

软件使用方法:
用画图软件打开图片,电机改变图片像素,取消自动保持纵横比,设置长宽为目标显示屏长宽,保存即可得到目标图片。
打开转换软件,打开修改的图片:
按照屏幕配置(St7789按图片配置即可),来选择进行转换。输出的数组替换image.h即可。

驱动测试

加载内核之后在/dev下会创建一个fb设备节点。

设置环境变量:

export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb1
指定qt显示在新创建的fb设备上。

运行qt程序,出现qt界面。

好了! enjoy it !

imx6ull Linux spi + frambuffer 实现st7789运行显示QT程序 且可实现大小屏双屏同显相关推荐

  1. linux下qt的文件打包,【最详细最完整】在Linux 下如何打包免安装的QT程序?

    版权声明:嵌入式linux相关的文章是我的学习笔记,基于Exynos 4412开发板,一部分内容是总结,一部分是查资料所得,大家可以自由转载,但请注明出处! https://blog.csdn.net ...

  2. STM32MP157 | 基于 Linux SPI 驱动M74HC595数码管显示

    一.M74HC595简介 M74HC595器件是采用硅栅C2MOS技术制作的具有输出锁存器(3态)的高速CMOS 8位移寄存器. 该设备包含一个8位串行进.并行出移位寄存器,它提供一个8位d型存储寄存 ...

  3. firefox linux脚本启动,在Linux终端中使用后台运行模式启动程序的方法

    这是一个篇幅不长但是十分有用的教程,可以帮助你在终端启动一个Linux应用程序,并且使终端窗口不会丢失焦点. 我们有很多可以在Linux系统中打开一个终端窗口的方法,这取决于你的选择以及你的桌面环境. ...

  4. Linux系统vscode断点单步运行调试C++程序

    安装vscode 安装比较简单略略略略!!!! 配置安装插件 Ctrl+Shift+X检索并安装C++.C++Clang.Cmake.Cmake Tools 创建launch.json文件 用vsco ...

  5. linux用gcc编译完怎么运行,linux下使用gcc编译运行C/C++程序

    编译C 首先,程序编译过程有: 1.预处理(展开宏,头文件,检查代码是否有误) 2.编译(将.c转为汇编代码.s) 3.汇编(将汇编代码.s转为机器代码.o) 4.链接(将所有机器代码.o和库文件链接 ...

  6. 在linux系统下如何编译运行C语言程序和C++程序

    文章目录 1:linux系统下编译运行C程序 2:linux系统下编译C++程序 1:linux系统下编译运行C程序 gcc -o a a.c gcc -o 想要的文件名 文件的名字.c 2:linu ...

  7. 在ARM开发板的嵌入式linux系统上运行的QT程序,必须得要在linux里用QT编吗

    https://blog.csdn.net/weixin_36060730/article/details/78359300

  8. 【Qt+VS】Qt图标不显示|Qt程序运行时图标不显示

    目录 1.右键项目添加新建项目[Qt Resource File]image.qrc 2.双击image.qrc使用[Qt Resource Editor]添加图标​ 3.右键image.qrc -& ...

  9. linux qt编译命令,linux下使用命令模式去编译Qt程序

    1.打开终端输入,qmake -v ,如果提示版本信息正,就可以编译程序了. 2.当前目录切换到程序源代码目录,cd /home/likewei/untitled2 3.生成untitled2.pro ...

最新文章

  1. Acwing第 28 场周赛【完结】
  2. 支付宝app支付java后台流程、原理分析(含nei wang chuan tou)
  3. 使用excel2003中的solver解决最优化问题
  4. Python实现计算图像RGB均值
  5. 第八章 (二)贪心法
  6. mysql5.7修改默认编码_mysql5.7设置默认编码
  7. MFC学习——环境安装
  8. android timepicker 设置颜色,android – 更改TimePicker文本颜色
  9. iosepub阅读器_epub格式电子书阅读器 iOS版
  10. Dacom G150双模耳机,为爱发声,呵护孩子听力健康成长
  11. python求平均工资_python实现求和,求平均值——函数
  12. 天津高清卫星影像数据包下载
  13. 将PostgreSQL插件移植到openGauss指导
  14. Python3爬取淘宝网商品数据!
  15. 移动端touch事件和click事件的区别
  16. 显示器的 VGA、HDMI、DVI 和DisplayPort接口有什么区别?
  17. 连快播王欣都要做区块链,蚂蚁金服为什么不碰ICO?
  18. 工行银企互联经验点滴
  19. 如何解决企业客户签收回执慢,缩短回款周期?
  20. Unity之xbox手柄控制交互逻辑

热门文章

  1. HTML标签的英文全称与中文释义
  2. android 支付宝集成错误,Android 支付宝快捷支付集成及ALI64错误的有效解决
  3. [Mattermost]Gauge测试UI+Jenkins流水线+Mattermost消息订阅(Mattermost篇)
  4. SIP协议解析与实现
  5. 网络安全等级测评师培训教材(初级)-2021版-第四章安全计算环境
  6. 走进产品经理(最佳培训教材)
  7. 市场调研-全球与中国在线工作协助软件市场现状及未来发展趋势
  8. 四大挑战——重新定义成功的现代物流配送中心
  9. 为 Cobalt Strike exe 木马添加图标
  10. 电脑重装系统注册表恢复方法