43.Linux LCD驱动

43.Linux LCD驱动

头文件为

c 复制代码
include/linux/fb.h

linux的LCD驱动主要就是打点/读点

完成上面的功能在搭配GUI库来实现丰富多彩的ui界面

我们本次需要实现linux驱动实现在LCD上打点

比如imx6ull出厂自带了evk开发版的屏幕驱动

/dev/fb0 -》 提供给上层应用程序的文件接口,可以通过对该设备的读写来控制LCD屏幕

Framebuffer设备

RGB LCD屏幕,framebuffe是一种机制,应用程序操作驱动里面的LCD显存的一种机制,因为应用程序需要通过操作显存来在LCD上显示字符,图片等信息。

通过framebuffer机制将底层的lcd抽象为/dev/fbX ,X=1,2,3...,应用程序可以通过操作/dev/fbX来操作屏幕。

我们只需要完成/dev/fbX的驱动。

对于使用lcd是非常简单的,LCD控制器已经出厂完成了,购买其他的屏幕也会有厂家写好LCD控制器,而我们只需要修改屏幕具体的参数即可

在设备树im6ull.dtsi中

c 复制代码
lcdif: lcdif@021c8000 {
				compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
				reg = <0x021c8000 0x4000>;
				interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
					 <&clks IMX6UL_CLK_LCDIF_APB>,
					 <&clks IMX6UL_CLK_DUMMY>;
				clock-names = "pix", "axi", "disp_axi";
				status = "disabled";
			};

可以看到对应的屏幕驱动的适配为

"fsl,imx6ul-lcdif"

"fsl,imx28-lcdif"

全局搜索,找到其驱动文件为

c 复制代码
drivers/video/fbdev/mxsfb.c

文件的最后:

c 复制代码
static const struct of_device_id mxsfb_dt_ids[] = {
	{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
	{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
	{ /* sentinel */ }
};
...
static struct platform_driver mxsfb_driver = {
	.probe = mxsfb_probe,
	.remove = mxsfb_remove,
	.shutdown = mxsfb_shutdown,
	.id_table = mxsfb_devtype,
	.driver = {
		   .name = DRIVER_NAME,
		   .of_match_table = mxsfb_dt_ids,
		   .pm = &mxsfb_pm_ops,
	},
};

module_platform_driver(mxsfb_driver);

MODULE_DESCRIPTION("Freescale mxs framebuffer driver");
MODULE_AUTHOR("Sascha Hauer, Pengutronix");
MODULE_LICENSE("GPL");

可以看到是一个标准的platform框架驱动

而Framebuffer是linux抽象出来管理LCD屏幕的结构体为fb_info

c 复制代码
struct fb_info {
	atomic_t count;
	int node;
	int flags;
	struct mutex lock;		/* Lock for open/release/ioctl funcs */
	struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields */
	struct fb_var_screeninfo var;	/* Current var */
	struct fb_fix_screeninfo fix;	/* Current fix */
	struct fb_monspecs monspecs;	/* Current Monitor specs */
	struct work_struct queue;	/* Framebuffer event queue */
	struct fb_pixmap pixmap;	/* Image hardware mapper */
	struct fb_pixmap sprite;	/* Cursor hardware mapper */
	struct fb_cmap cmap;		/* Current cmap */
	struct list_head modelist;      /* mode list */
	struct fb_videomode *mode;	/* current mode */

#ifdef CONFIG_FB_BACKLIGHT
	/* assigned backlight device */
	/* set before framebuffer registration, 
	   remove after unregister */
	struct backlight_device *bl_dev;

	/* Backlight level curve */
	struct mutex bl_curve_mutex;	
	u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	struct delayed_work deferred_work;
	struct fb_deferred_io *fbdefio;
#endif

	struct fb_ops *fbops;
	struct device *device;		/* This is the parent */
	struct device *dev;		/* This is this fb device */
	int class_flag;                    /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
	struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
	char __iomem *screen_base;	/* Virtual address */
	unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */ 
	void *pseudo_palette;		/* Fake palette of 16 colors */ 
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
	u32 state;			/* Hardware state i.e suspend */
	void *fbcon_par;                /* fbcon use-only private area */
	/* From here on everything is device dependent */
	void *par;
	/* we need the PCI or similar aperture base/size not
	   smem_start/size as smem_start may just be an object
	   allocated inside the aperture so may not actually overlap */
	struct apertures_struct {
		unsigned int count;
		struct aperture {
			resource_size_t base;
			resource_size_t size;
		} ranges[0];
	} *apertures;

	bool skip_vt_switch; /* no VT switch on suspend/resume required */
};

对该结构体进行初始化

对应的字符设备操作集为fb_ops

该结构体的注册函数为:

c 复制代码
int register_framebuffer(struct fb_info *fb_info)

fb_info:需要上报的 fb_info。

返回值: 0,成功;负值,失败

在系统中的体现

向内核注册之后,用户态的udev/mdev(嵌入式)根据内核事件生成创建 /dev/fb0 设备文件,原理如下:

  1. 内核发送 uevent 事件 设备注册成功后,内核会通过 kobject_uevent 发送一个 add 事件,包含设备类型、编号等信息(如 MAJOR=29MINOR=0DEVNAME=fb0)。

  2. udev 规则匹配与节点创建 udev 守护进程监听 uevent 事件,根据预设规则(通常在 /lib/udev/rules.d/ 中)处理:

    • 识别帧缓冲设备的主设备号(固定为 29,Linux 约定);

    • 根据次设备号0、1等在/dev目录下面创建字符设备文件

    c 复制代码
      mknod /dev/fb0 c 29 0

    其中 c 表示字符设备,29 是主设备号,0 是次设备号。

注销函数:

c 复制代码
int unregister_framebuffer(struct fb_info *fb_info);

fb_info 结构体的成员变量很多,我们重点关注 var、 fix、 fbops、 screen_base、 screen_size和 pseudo_palette。

mxsfb_probe 函数的主要工作内容为:

①、申请 fb_info。

②、初始化 fb_info 结构体中的各个成员变量。

③、初始化 eLCDIF 控制器。

④、使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。

修改设备树将屏幕驱动

我们需要修改设备树将屏幕驱动起来

设备中中"fsl,imx28-lcdif"关键字的设备为

im6ull.dtsi中

c 复制代码
			lcdif: lcdif@021c8000 {
				compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
				reg = <0x021c8000 0x4000>;
				interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
					 <&clks IMX6UL_CLK_LCDIF_APB>,
					 <&clks IMX6UL_CLK_DUMMY>;
				clock-names = "pix", "axi", "disp_axi";
				status = "disabled";
			};

imx6ull-alientek.dts

c 复制代码
&lcdif {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_lcdif_dat
		     &pinctrl_lcdif_ctrl>;
	display = <&display0>;
	status = "okay";

        display0: display {
                bits-per-pixel = <16>;
                bus-width = <24>;

                display-timings {
                        native-mode = <&timing0>;
                        timing0: timing0 {
                        clock-frequency = <35500000>;
                        hactive = <800>;
                        vactive = <480>;
                        hfront-porch = <210>;
                        hback-porch = <46>;
                        hsync-len = <20>;
                        vback-porch = <23>;
                        vfront-porch = <22>;
                        vsync-len = <3>;

                        hsync-active = <0>;
                        vsync-active = <0>;
                        de-active = <1>;
                        pixelclk-active = <1>;
                        };
                };
        };
};

这个设备树可以在480*800的屏幕上驱动,并显示一只linux企鹅

-> Device Drivers -> Graphics support

​ -> Bootup logo (LOGO [=y])

可以进行更改

回到设备树

c 复制代码
pinctrl-0 = <&pinctrl_lcdif_dat
		     &pinctrl_lcdif_ctrl>;
c 复制代码
pinctrl_lcdif_dat: lcdifdatgrp {
			fsl,pins = <
				MX6UL_PAD_LCD_DATA00__LCDIF_DATA00  0x79
				MX6UL_PAD_LCD_DATA01__LCDIF_DATA01  0x79
				MX6UL_PAD_LCD_DATA02__LCDIF_DATA02  0x79
				MX6UL_PAD_LCD_DATA03__LCDIF_DATA03  0x79
				MX6UL_PAD_LCD_DATA04__LCDIF_DATA04  0x79
                ...
			>;
		};
c 复制代码
pinctrl_lcdif_ctrl: lcdifctrlgrp {
			fsl,pins = <
				MX6UL_PAD_LCD_CLK__LCDIF_CLK	    0x79
				MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE  0x79
				MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC    0x79
				MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC    0x79
			>;
		};

这些参数与LCD屏幕驱动所对应起来

修改值电气值 0x79 -> 0x49 因为考虑到0x79的最强驱动可能会影响网络的驱动电路

对于display0节点的具体含义,去看绑定文档 Documentatio/devicetree/bindings/fb有解释这些参数

我们需要修改其中的参数,根据自己板子的屏幕设置成相应的参数。

c 复制代码
display0: display {
                bits-per-pixel = <32>;
                bus-width = <24>;

                display-timings {
                        native-mode = <&timing0>;
                        timing0: timing0 {
                        clock-frequency = <51200000>;
                        hactive = <1024>;
                        vactive = <600>;
                        hfront-porch = <160>;
                        hback-porch = <140>;
                        hsync-len = <20>;
                        vback-porch = <20>;
                        vfront-porch = <1>;
                        vsync-len = <3>;
						
						//四根数据线 有效值 根据屏幕驱动时序图
                        hsync-active = <0>;
                        vsync-active = <0>;
                        de-active = <1>;
                        pixelclk-active = <1>;
                        };
                };
        };

修改完成编译重启开发板

可以看到logo

1.操作该驱动

c 复制代码
echo hello world > /dev/tty1

可以看到屏幕打印了字符串hello world

我们可以将Linux默认输出为屏幕

c 复制代码
vi etc/inittab

修改

c 复制代码
#etc/inittab
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
tty1:askfrist:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a

重启,修改bootargs

c 复制代码
setenv bootargs 'console=ttymxc0,115200 rw root=/dev/nfs nfsroot=192.168.3.15:/home/alientek/Desktop/nfs/rootfs ip=192.168.3.55:192.168.3.15:192.168.3.1:255.255.255.0::eth0:off'

改为

c 复制代码
setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:/home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off'

console=ttymxc0,115200 改为 console=tty1 console=ttymxc0,115200

2.背光

依靠PWM控制亮度,一般测试屏幕的时候直接将背光引脚

c 复制代码
cd /sys/devices/platform/backlight/backlight/backlight 
ls
actual_brightness  device             subsystem
bl_power           max_brightness     type
brightness         power              uevent

echo 5 > brightness
echo 1 > brightness
echo 7 > brightness   

对应设备树内容

c 复制代码
backlight {
		compatible = "pwm-backlight";
		pwms = <&pwm1 0 5000000>;
		brightness-levels = <0 4 8 16 32 64 128 255>;
		default-brightness-level = <7>;
		status = "okay";
	};

同时修改屏幕休眠时间

driver/tty/vt/vt.c

c 复制代码
static int blankinterval = 10*60;
修改为0即可

修改重新编译内核,重新加载zImage

后面想使用显示文字、图片、UI界面 可以搜索关键字linux fb编程 、 mmap、QT

简单示例

前面说了设备注册后会在/dev下面创建一个framebuffer的对外接口,即/dev/fb0,我们就可以直接使用简单的应用程序进行控制

c 复制代码
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>

static int fd_fb;						//帧缓冲设备文件描述符
static struct fb_var_screeninfo var;	/* Current var *///存储屏幕可变信息的结构体(分辨率、像素格式等)
static int screen_size;					//屏幕缓冲区总大小(字节)
static unsigned char *fb_base;			//映射到用户空间的帧缓冲内存首地址
static unsigned int line_width;			//屏幕一行的字节数
static unsigned int pixel_width;		//每个像素占用的字节数

/**********************************************************************
 * 函数名称: lcd_put_pixel
 * 功能描述: 在LCD指定位置上输出指定颜色(描点)支持不同的像素格式(8/16/32 位):
 * 输入参数: x坐标,y坐标,颜色
 * 输出参数: 无
 * 返 回 值: 会
 ***********************************************************************/ 
void lcd_put_pixel(int x, int y, unsigned int color)
{
	unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;
	unsigned short *pen_16;	
	unsigned int *pen_32;	

	unsigned int red, green, blue;	

	pen_16 = (unsigned short *)pen_8;
	pen_32 = (unsigned int *)pen_8;

	switch (var.bits_per_pixel)
	{
		case 8:
		{
			*pen_8 = color;
			break;
		}
		case 16:
		{
			/* 565 */
			red   = (color >> 16) & 0xff;
			green = (color >> 8) & 0xff;
			blue  = (color >> 0) & 0xff;
			color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
			*pen_16 = color;
			break;
		}
		case 32:
		{
			*pen_32 = color;
			break;
		}
		default:
		{
			printf("can't surport %dbpp\n", var.bits_per_pixel);
			break;
		}
	}
}

int main(int argc, char **argv)
{
	int i;
	
	fd_fb = open("/dev/fb0", O_RDWR);  //以读写模式打开 /dev/fb0 设备,获取文件描述符
	if (fd_fb < 0)
	{
		printf("can't open /dev/fb0\n");
		return -1;
	}
	if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))		//获取屏幕信息如分辨率 xres/yres、像素位深 bits_per_pixel 等,保存到var变量中
	{
		printf("can't get var\n");
		return -1;
	}
	
	
	//通过获得的变脸计算相关参数
	line_width  = var.xres * var.bits_per_pixel / 8;
	pixel_width = var.bits_per_pixel / 8;
	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
	
	//内存映射:将帧缓冲设备的内存映射到用户空间,使得应用程序可以直接通过指针操作显示内存,无需频繁的 read/write 系统调用。
	fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if (fb_base == (unsigned char *)-1)
	{
		printf("can't mmap\n");
		return -1;
	}

	/* 清屏: 全部设为白色 使用 memset 将整个帧缓冲填充为白色(0xff) */
	memset(fb_base, 0xff, screen_size);

	/* 随便设置出100个为红色 */
	for (i = 0; i < 100; i++)
		lcd_put_pixel(var.xres/2+i, var.yres/2, 0xFF0000);
	
	//资源释放:
	munmap(fb_base , screen_size);		//解除内存映射
	close(fd_fb);						//关闭设备文件
	
	return 0;	
}
相关推荐
宇钶宇夕1 小时前
西门子S7-1200/1500除尘器系统编程全解:从IO映射到安全联锁(思路解析)
运维·自动化
w***H6501 小时前
Springboot项目:使用MockMvc测试get和post接口(含单个和多个请求参数场景)
java·spring boot·后端
橘子编程1 小时前
仓颉语言:华为新一代编程利器
java·c语言·开发语言·数据库·python·青少年编程
Dobby_051 小时前
【Go】 Go Modules 依赖管理
运维·golang
a***13141 小时前
Spring Boot 条件注解:@ConditionalOnProperty 完全解析
java·spring boot·后端
axihaihai1 小时前
maven的构建问题
java·linux·maven
tgethe1 小时前
Java注解
java·后端
稚辉君.MCA_P8_Java1 小时前
DeepSeek Java 多线程打印的12种实现方法
java·linux·后端·架构·maven
z***75151 小时前
Linux系统离线部署MySQL详细教程(带每步骤图文教程)
linux·mysql·adb