前言

在嵌入式行业,有很多从业者。我们工作的主旋律是拿开源代码,拿厂家代码,完成产品的功能,提升产品的性能,进而解决各种各样的问题。或者是维护一个模块或方向,一搞就是好几年。

时间长了,中年润发现我们对从零开始编写驱动、应用、算法、系统、协议、文件系统等缺乏经验。没有该有的广度和深度。中年润也是这样,工作了很多年,都是针对某个问题点修修补补或者某个模块的局部删删改改。很少有机会去独自从零开始编写一整套完整的代码。

当然,这种现状对于企业来说是比较正常的,可以降低风险。但是对于员工本身,如果缺乏必要的规划,很容易工作多年却还是停留在单点的层面,而丧失了提升到较高层面的机会。随着时间的增长很容易丧失竞争力。

另外,根据中年润的经验,绝大多数公司对于0-5年经验从业者的定位主要是积极的问题解决者。而对于5-10经验从业者的定位主要是积极的系统规划者和引领者。在这种行业规则下,中年润认为,每个从业者都应该问自己一句,“5年后,我是否具备系统化把控软件的能力呢?”。

当前的这种行业现状,如果我们不做出一点改变,是没有办法突破的。有些东西,仅仅知道是不够的,还需要深思熟虑的思考和必要的训练,简单来说就是要知行合一。

也许有读者会有疑惑?这不就是重复造轮子么?我们确实是在重复造轮子,因为别人会造轮子那是别人的能力,我们自己会造轮子是我们自己的能力。在行业中,有太多的定制化需求是因为轮子本身有原生性缺陷,我们无法直接使用,或者需要对其进行改进,或者需要抽取开源代码的主体思想和框架,根据公司的需要定制自己的各项功能。设想,如果我们具备这种能力,必然会促使我们在行业中脱颖而出,而不是工作很多年一直在底层搬砖。底层搬砖没什么不好,问题是当有更廉价更激情的劳动力涌进来的时候,我们这些老的搬砖民工也就失去了价值。我们不会天天重复造轮子,我们需要通过造几个轮子使得自己具备造轮子的能力,从而更好的适应这个环境,适应这个世界。

针对当前行业现状,中年润经过深思熟虑,想为大家做点实实在在的事情,希望能够帮助大家在巩固基础的同时提升系统化把控软件的能力。当然,中年润的水平也有限,有些观点也只是一家之谈,希望大家独立思考,谨慎采用,如果写的有错误或者不对的地方还请读者们批评斧正,我们一起共同进步。

在这里简单介绍下中年润,中年润现在就职于一家大型国际化公司,工作经验6年,硕士毕业。曾经担任过组内的项目主管,项目经理,也曾经组建过新团队,带领大家冲锋陷阵。在工作中,有做的不错的地方,也有失误的地方,有激情的时刻,也有失落的时刻。现在偏安一隅,专心搞技术,目前个人规划的技术方向是嵌入式和AI基础设施建设,以及嵌入式和AI的融合发展。

最后,说了这么多,中年润希望,在未来的日子里和未知的领域里,你我同行,为我们的美好生活而努力奋斗。

总体目标

本篇文章的目标是介绍如何从自顶向下从零编写linux下的LCD驱动。着力从总体思路,需求端,分析端,实现端,详尽描述一个完整需求的开发流程,是中年润多年经验的提炼,希望读者能够有所收获。最后的实战目标,请读者尽量完成,这样读者才能形成自己的思路。

本示例采用arm920架构,天祥电子生产的tx2440a开发板,核心为三星的s3c2440。Linux版本为2.6.31,是已经移植好的版本。编译器为arm920t-eabi-4.1.2.tar。

总体思路

总体思路是严格遵循需求的开发流程来,不遗漏任何思考环节。读者在阅读时请先跟中年润的思路走一遍,然后再抛弃中年润的思路,按照自己的思路走一遍,如果遇到困难请先自己思考,实在不会再来参考中年润的思路和实现。

中年润在写代码的的总体思路如下:

需求描述—能够详细完整的描述一个需求。

需求分析—根据需求描述,提取可供实现的功能,需要有定量或者定性的指标。(从宏观上确定需要什么功能)。

需求分解—根据需求分析,考虑要实现需求所需要做的工作(根据宏观确定的功能,拆分成小的可单独实现的功能)。

编写思路—根据需求分解从总体上描述应该如何编写代码,(解决怎么在宏观上实现)。

详细步骤—根据编写思路,落实具体步骤,(解决怎么在微观上实现)。

编写框架—根据编写思路,实现总体框架(实现编写思路里主体框架,细节内容留在具体代码里编写)。

具体代码—根据编写框架,编写每一个函数里所需要实现的小功能,主要是实现驱动代码,测试代码。

Makefile—用来编译驱动代码。

目录结构—用来说明当完成编码后的结果。

测试步骤—说明如何对驱动进行测试,主要是加载驱动模块,执行测试代码。

执行结果—观察执行结果是否符合预期。

结果总结—回顾本节的思路,知识点,api,结构体。

实战目标—说明如何根据本文档训练。

需求描述

编写LCD 驱动,要求如下:

1能够执行echo hello > /dev/tty1,显示hello字符;

2能够执行cat lcd.ko > /dev/fb0,显示花屏;

3能够用按键执行ls命令并在lcd屏幕上显示。(需要结合按键模拟l,s,enter驱动)

需求分析

要分析出需要实现哪些功能,经过分析,可以得出需要实现以下功能。

1能够控制soc上的LCD控制器;

2能够通过控制LCD控制器让LCD屏显示图像;

3能够将LCD屏作为终端输出设备,显示终端输入的结果;

4需要利用linux提供的framebuffer模块相关api。

需求分解

根据需求分析的结果,分解出需要实现的功能:

1需要向内核注册和卸载LCD驱动;

2需要配置LCD相关寄存器(需要和LCD屏时序进行匹配);

3配置脚本使得LCD屏能够作为终端输入输出设备。

编写思路

LCD相关的具体硬件原理和linux机制,中年润会专门出两篇文章来分析和讲解,敬请读者关注。

1需要向内核注册和卸载LCD驱动

1.0编写代码框架,头文件,出入口函数,声明LICENSE

1.1需要注册fb驱动

1.1.1需要申请struct fb_info结构体

1.1.2需要填充struct fb_info结构体

1.1.3需要注册struct fb_info结构体

1.2需要卸载fb驱动

2需要配置LCD控制器

2.1构造LCD控制器寄存器数据结构

2.2配置LCD相关GPIO

2.3配置LCD相关寄存器

2.4设置显存

2.5启动LCD控制器及LCD屏幕

3配置脚本使得LCD屏能够作为终端输入输出设备

详细步骤

1向内核注册和卸载LCD驱动

1.1注册fb驱动

1.1.0编写代码框架,头文件,出入口函数,声明LICENSE

1.1.1申请struct fb_info结构体

1.1.2填充struct fb_info结构体

1.1.2.1设置固定参数fix字段

设置名字

设置帧缓冲的长度(单位,byte)

设置帧缓冲的类型

设置帧缓冲的可视化

设置lcd一行的长度(单位,byte)

设置帧缓冲内存的起始地址(可不设置)

1.1.2.2设置可变参数var字段

设置x方向分辨率

设置y方向分辨率

设置虚拟分辨率

设置虚拟分辨率和实际分辨率偏移值(可不设置)

设置每个像素占多少位

设置灰度值

设置red、green、blue字段,设置颜色的偏移和长度

设置激活字段

设置宽度和高度字段,为LCD物理尺寸,可不设置

1.1.2.3设置操作函数fbops字段

填充owner、fb_setcolreg、fb_fillrect、fb_copyarea、fb_imageblit字段

1.1.2.4其他设置

设置调色板

设置显存的虚拟地址(可留在2.4设置)

设置显存大小(单位,byte)

1.1.3注册struct fb_info结构体

1.2卸载fb驱动

1.2.1卸载fb驱动

1.2.2关闭lcd本身

1.2.3释放申请的帧缓冲区

1.2.4取消寄存器映射

1.2.5释放申请的fb_info结构体

2配置LCD控制器

2.1构造LCD控制器寄存器数据结构

2.2配置LCD相关GPIO

2.2.1映射物理地址为虚拟地址

2.2.2配置gpio用于LCD控制

2.3配置LCD相关寄存器

2.3.1映射物理地址为虚拟地址

2.3.2设置LCD控制器寄存器LCDCON1-LCDCON5

2.3.2.1设置LCDCON1来控制VCLK,vm触发频率,显示模式,像素格式等

2.3.2.2设置LCDCON2来控制垂直方向的时间参数

2.3.2.3设置LCDCON3来控制水平方向的时间参数

2.3.2.4设置LCDCON4来控制HSYNC同步信号的脉冲宽度

2.3.2.5设置LCDCON5来控制极性参数

2.4设置显存

2.4.1分配显存

2.4.2设置帧缓冲相关寄存器

2.4.2.1设置LCDSADDR1中的LCDBANK,LCDBASEU寄存器(说明缓冲区的开始地址)

2.4.2.2设置LCDSADDR2中的LCDBASEL寄存器(说明缓冲区的结束地址)

2.4.2.3设置LCDSADDR3中的PAGEWIDTH寄存器(说明虚拟屏幕页的宽度,单位,半字)

2.5启动LCD控制器及LCD本身

2.5.1使能LCD控制器

2.5.2使能LCD本身

3配置脚本使得LCD屏能够作为终端输入输出设备

3.1配置/etc/inittab

编写框架

/* 本文件是依照lcd 驱动<编写思路>章节编写,本文件* 的目的是编写代码框架,不做具体细节的编写*/
/* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 *//* 1.0编写代码框架,头文件,出入口函数,声明LICENSE */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>
#include <asm/mach/map.h>
#include <asm/arch/regs-lcd.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/fb.h>struct fb_info *s3c2440_lcd_info = NULL;/* 1.1需要注册fb驱动 */
static int lcd_init()
{/* 1.1.1需要申请struct fb_info结构体 */s3c2440_lcd_info = framebuffer_alloc(0, NULL);/* 1.1.2需要填充struct fb_info结构体 *//** 2需要配置LCD控制器* 2.1构造LCD控制器寄存器数据结构* 2.2配置LCD相关GPIO* 2.3配置LCD相关寄存器* 2.4设置显存* 2.5启动LCD控制器及LCD屏幕*//* 1.1.3需要注册struct fb_info结构体 */register_framebuffer(s3c2440_lcd_info);return 0;
}/* 1.2需要卸载fb驱动 */
static void lcd_exit()
{unregister_framebuffer(s3c2440_lcd_info);
}/* 1.0编写代码框架,头文件,出入口函数,声明LICENSE */module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

驱动代码

/* 本文件是依照lcd 驱动<详细步骤>章节编写,本文件* 的目的是编写代具体代码,不介绍框架*//* 本头文件是linux2.6.31内核所提供的,其他版本按需调整 */
/* 本文件参考的lcd 芯片手册为BL43014_SPEC *//* 1.1.0编写代码框架,头文件,出入口函数,声明LICENSE */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>
#include <asm/mach/map.h>/* 2.1构造LCD控制器寄存器数据结构 */
struct lcd_regs {unsigned long       lcdcon1;unsigned long       lcdcon2;unsigned long       lcdcon3;unsigned long       lcdcon4;unsigned long       lcdcon5;unsigned long       lcdsaddr1;unsigned long       lcdsaddr2;unsigned long       lcdsaddr3;unsigned long       redlut;unsigned long       greenlut;unsigned long       bluelut;unsigned long       reserved[9];unsigned long       dithmode;unsigned long       tpal;unsigned long       lcdintpnd;unsigned long       lcdsrcpnd;unsigned long       lcdintmsk;unsigned long       lpcsel;
};static u32 s3c2440_pseudo_palette[16];
static struct fb_info *s3c2440_lcd_info = NULL;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile struct lcd_regs *plcd_regs;/* from pxafb.c */
/* 将三原色的数值转换成可被设置的值 */
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{chan &= 0xffff;chan >>= 16 - bf->length;return chan << bf->offset;
}/* set color register */
/* 设置调色板中的颜色 */
int s3c2440_lcd_setcolreg(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned 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);s3c2440_pseudo_palette[regno] = val;return 0;
}static struct fb_ops s3c2440_lcd_ops = {.owner = THIS_MODULE,.fb_setcolreg = s3c2440_lcd_setcolreg,//设置颜色寄存器.fb_fillrect = cfb_fillrect,//填充一个矩形.fb_copyarea = cfb_copyarea,//拷贝一个区域的数据到另一个区域.fb_imageblit = cfb_imageblit,//向显示屏画一幅图像
};/* 1.1注册fb驱动 */
static int lcd_init()
{/* 1.1.1需要申请struct fb_info结构体 */s3c2440_lcd_info = framebuffer_alloc(0, NULL);/* 1.1.2需要填充struct fb_info结构体 *//* 1.1.2.1设置固定参数fix字段 *//* 设置名字 */strcpy(s3c2440_lcd_info->fix.id,"s3c2440_lcd");/* 设置帧缓冲长度 */s3c2440_lcd_info->fix.smem_len = 480 * 272 * 16 / 8;/* 设置帧缓冲内存的起始地址(可不设置) *///s3c2440_lcd_info->fix.smem_start = ;/* 设置帧缓冲的类型 */s3c2440_lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;/* 设置帧缓冲的可视化 */s3c2440_lcd_info->fix.visual = FB_VISUAL_TRUECOLOR;/* 设置lcd一行的长度(单位,byte) */s3c2440_lcd_info->fix.line_length = 480 * 2;/* 1.1.2.2设置可变参数var字段 *//* 设置x方向分辨率 */s3c2440_lcd_info->var.xres = 480;/* 设置y方向分辨率 */s3c2440_lcd_info->var.yres = 272;/* 设置虚拟分辨率 */s3c2440_lcd_info->var.xres_virtual = 480;s3c2440_lcd_info->var.yres_virtual = 272;/* 设置每个像素占多少位 */s3c2440_lcd_info->var.bits_per_pixel = 16;/* 设置red、green、blue字段,设置颜色的偏移和长度 */s3c2440_lcd_info->var.red.offset = 11;s3c2440_lcd_info->var.red.length = 5;s3c2440_lcd_info->var.green.offset = 5;s3c2440_lcd_info->var.green.length = 6;s3c2440_lcd_info->var.blue.offset = 0;s3c2440_lcd_info->var.blue.length = 5;/* 设置激活字段 */s3c2440_lcd_info->var.activate = FB_ACTIVATE_NOW;/* 设置宽度和高度字段,为LCD物理尺寸,可不设置 */s3c2440_lcd_info->var.width = 480;s3c2440_lcd_info->var.height = 272;/* 1.1.2.3设置操作函数fbops字段 */s3c2440_lcd_info->fbops = &s3c2440_lcd_ops;/* 1.1.2.4其他设置 *//* 设置调色板 */s3c2440_lcd_info->pseudo_palette = s3c2440_pseudo_palette;/* 设置显存的虚拟地址(可留在2.4设置) *///s3c2440_lcd_info->screen_base = ;/* 设置显存大小(单位,byte) */s3c2440_lcd_info->screen_size = 480 * 272 * 16 / 8;/* 主体思路* 2需要配置LCD控制器* 2.1构造LCD控制器寄存器数据结构* 2.2配置LCD相关GPIO* 2.3配置LCD相关寄存器* 2.4设置显存* 2.5启动LCD控制器及LCD本身*//* 2配置LCD控制器 *//* 2.1构造LCD控制器寄存器数据结构 *//* 2.2配置LCD相关GPIO *//* 2.2.1映射物理地址为虚拟地址 */gpccon = ioremap(0x56000020,4);gpdcon = ioremap(0x56000030,4);gpgcon = ioremap(0x56000060,4);/* 2.2.2配置gpio用于LCD控制 *//* 根据s3c2440手册配置 *//* config vd0 - vd7,lcd_lpcrevb,lcd_lpcrev,lcd_lpcode,vm,vframe,vline,vclk,lend*/*gpccon  = 0xaaaaaaaa;/* config vd8 - vd23 */*gpdcon  = 0xaaaaaaaa;/* config lcd_pwrdn */*gpgcon &= (~(3<<8))|(3<<8);/* 2.3配置LCD相关寄存器 *//* 2.3.1映射物理地址为虚拟地址 */plcd_regs = ioremap(0X4d000000,sizeof(struct lcd_regs));/* 2.3.2设置LCD控制器寄存器LCDCON1-LCDCON5 */#if 1/* 2.3.2.1设置LCDCON1来控制VCLK,vm触发频率,显示模式,像素格式等*//* configed by myself *//* CLKVAL bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2]*            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]*            CLKVAL = 4* MMODE bit[7]:0=each frame, toggle rate of VM* PNRMODE bit[6:5]: 0b11, TFT LCD* BPPMODE bit[4:1]: 0b1100, 16 bpp for TFT* ENVID bit[0]  : 0 = Disable the video output and the LCD control signal.*//* CLKVAL     CLKVAL_TFT,determine VCLK* MMODE  MVAL_USED,determine rates of VM* PNRMODE      dispaly mode* BPPMODE      bits per pixel* ENVID   LCD video output and the logic enable/disable* (CLKVAL<<8)|(MMODE<<7)|(PNRMODE<<5)|(BPPMODE<<1)|(ENVID<<0)*/plcd_regs->lcdcon1 = (4<<8) | (0<<7) | (3<<5) | (0x0c<<1);/* 2.3.2.2设置LCDCON2来控制垂直方向的时间参数*//* 垂直方向的时间参数* VBPD bit[31:24]:  VSYNC之后再过多长时间才能发出第1行数据*    LCD手册,Tvbp = 3,VBPD = 3 - 1 = 2* LINEVAL bit[23:14]: 多少行*    LCD 手册272, LINEVAL=272-1=271* VFPD bit[13:6] :  发出最后一行数据之后,再过多长时间才发出VSYNC*   LCD手册,Tvfp = 2,VFPD = 2 - 1 = 1* VSPW bit[5:0]  : VSYNC信号的脉冲宽度,*    LCD手册Tvwh=1, 所以VSPW=1-1=0*/  /* VBPD 垂直同步信号的后肩* VFPD 垂直同步信号的前肩* VSPW 垂直同步信号的脉宽* (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW)*/plcd_regs->lcdcon2 = (2<<24) | (271<<14) | (1<<6) | 0;/* 2.3.2.3设置LCDCON3来控制水平方向的时间参数*//* 水平方向的时间参数* HBPD bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据*   LCD手册,Thbp = 36, HBPD= 36 -1 =35* HOZVAL bit[18:8]: 多少列*    LCD手册 480, 所以HOZVAL=480-1=479* HFPD bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,* 再过多长时间才发出HSYNC*   LCD手册,Thfb = 4, 所以HFPD= 4 - 1= 3*/  /* HBPD 垂直同步信号的后肩* HFPD 垂直同步信号的前肩* HSPW 垂直同步信号的脉宽* (HBPD<<19)|(HOZVAL<<8)|(HFPD);*/plcd_regs->lcdcon3 = (35<<19) | (479<<8) | (3<<0);/* 2.3.2.4设置LCDCON4来控制HSYNC同步信号的脉冲宽度*//* 水平方向的同步信号* HSPW bit[7:0]    :  HSYNC信号的脉冲宽度,*   LCD手册Thwh = 1, 所以HSPW= 1 - 1 = 0*//* MVAL        VM 信号触发率* HSPW             垂直同步信号的脉宽* (MVAL<<8)|(HSPW)*/plcd_regs->lcdcon4 = 0;/* 2.3.2.5设置LCDCON5来控制极性参数*//* 信号的极性* bit[11]: 1=565 format* bit[10]: 0 = The video data is fetched at VCLK falling edge* bit[9] : 1 = HSYNC信号要反转,即低电平有效* bit[8] : 1 = VSYNC信号要反转,即低电平有效* bit[6] : 0 = VDEN不用反转* bit[3] : 0 = PWREN输出0* bit[1] : 0 = BSWP* bit[0] : 1 = HWSWP 2440手册P427*/plcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) |(0<<7) | (0<<6)| (1<<3) | (0<<1) |(1<<0);
#endif#if 0/* supported by vendor */plcd_regs->lcdcon1 = (4<<8) | (0<<7) | (3<<5) | (0x0c<<1);plcd_regs->lcdcon2 = (2<<24) | (271<<14) | (4<<6) | 8;plcd_regs->lcdcon3 = (10<<19) | (479<<8) | (19<<0);plcd_regs->lcdcon4 = (13<<8) | 30;plcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) |(0<<7) | (0<<6)| (1<<3) | (0<<1) |(1<<0);
#endif    /* 2.4设置显存 *//* 2.4.1分配显存 */s3c2440_lcd_info->screen_base = dma_alloc_writecombine(NULL,s3c2440_lcd_info->fix.smem_len,&s3c2440_lcd_info->fix.smem_start,GFP_KERNEL);/* 2.4.2设置帧缓冲相关寄存器 *//* 2.4.2.1设置LCDSADDR1中的LCDBANK,LCDBASEU寄存器(说明缓冲区的开始地址) *//* 取缓冲区开始地址的1-30bit */plcd_regs->lcdsaddr1 = (s3c2440_lcd_info->fix.smem_start >> 1) & ~(3<<30);/* 2.4.2.2设置LCDSADDR2中的LCDBASEL寄存器(说明缓冲区的结束地址) *//* 取缓冲区结束地址的1-21bit */plcd_regs->lcdsaddr2 = ((s3c2440_lcd_info->fix.smem_start +s3c2440_lcd_info->fix.smem_len) >> 1) & 0x1fffff;/* 2.4.2.3设置LCDSADDR3中的PAGEWIDTH寄存器(说明虚拟屏幕页的宽度,单位,半字) */plcd_regs->lcdsaddr3  = (480*16/16);  /* 一行的长度(单位: 2字节) */   /* 2.5启动LCD控制器及LCD本身 *//* 2.5.1使能LCD控制器 */plcd_regs->lcdcon1 |= (1<<0);/* 2.5.2使能LCD本身 */plcd_regs->lcdcon5 |= (1<<3);/* 1.1.3需要注册struct fb_info结构体 */register_framebuffer(s3c2440_lcd_info);return 0;
}/* 1.2需要卸载fb驱动 */
static void lcd_exit()
{/* 1.2.1卸载fb驱动 */unregister_framebuffer(s3c2440_lcd_info);/* 1.2.2关闭lcd本身 */plcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 *//* 1.2.3释放申请的帧缓冲区 */dma_free_writecombine(NULL, s3c2440_lcd_info->fix.smem_len,s3c2440_lcd_info->screen_base, s3c2440_lcd_info->fix.smem_start);/* 1.2.4取消寄存器映射 */iounmap(plcd_regs);iounmap(gpccon);iounmap(gpdcon);iounmap(gpgcon);/* 1.2.5释放申请的fb_info结构体 */framebuffer_release(s3c2440_lcd_info);
}/* 1.0编写代码框架,头文件,出入口函数,声明LICENSE */module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

测试代码

本示例无需测试代码,可以通过相关命令测试。

Makefile

KERN_DIR = /home/linux/tools/linux-2.6.31_TX2440Aall:make -C $(KERN_DIR) M=`pwd` modulesclean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderobj-m += lcd.o

目录结构

代码编写完成的目录结构如下所示。

├── lcd.c

├── lcd_skeleton.c

└── Makefile

Make执行完成后的目录结构体如下所示,其中cfb*.ko的生成请参考测试步骤。

├── cfbcopyarea.ko

├── cfbfillrect.ko

├── cfbimgblt.ko

├── lcd.c

├── lcd.ko

├── lcd_skeleton.c

└── Makefile

测试步骤

0 在linux下的makefile +180行处配置好arch为arm,cross_compile为arm-linux-(或者arm-angstrom-linux-gnueabi-)。

1在menuconfig中配置好内核源码的目标系统为s3c2440。

2 移植好的linux默认是有s3c2440 lcd模块的的,如果我们的驱动也加载进去,会调用原有的驱动。因此需要在menuconfig中去掉移植的lcd驱动。

make menuconfig

Location:

-> Device Drivers

-> Graphics support

-> Support for frame buffer devices (FB [=y])

S3C2410 LCD framebuffer support

3 make uImage 生成uImage,并使用新的内核启动。

4 配置三个依赖函数并执行make modules 将依赖的三个函数生成模块。

Lcd驱动需要依赖三个函数,因此需要修改Kconfig(drivers/video/Kconfig)将这三个模块配置成m并编译成.ko,通过insmod命令加载到系统中。系统默认是y,会编译成.o。

87 config FB_CFB_FILLRECT

88     tristate

89     depends on FB

90     default m

96 config FB_CFB_COPYAREA

97     tristate

98     depends on FB

99     default m

105 config FB_CFB_IMAGEBLIT

106     tristate

107     depends on FB

108     default m

5 在pc上将驱动程序编译生成.ko,命令:make。

6 挂载nfs,这样就可以在开发板上看到pc端的.ko文件和测试文件。

mount -t nfs -o nolock,vers=2 192.168.0.105:/home/linux/nfs_root  /mnt/nfs

7 加载ko。

[root@TX2440A 10lcd]# insmod cfbfillrect.ko

[root@TX2440A 10lcd]# insmod cfbcopyarea.ko

[root@TX2440A 10lcd]# insmod cfbimgblt.ko

[root@TX2440A 10lcd]# insmod lcd.ko

8在开发板执行如下命令,观察现象。

echo hello > /dev/tty1

cat lcd.ko > /dev/fb0

特别注意:以下为另一个测试步骤,基于上述步骤和buttons驱动模拟l,s,enter按键的驱动

9配合buttons驱动,让按键模拟l,s,enter,并显示输出到lcd屏幕上

9.1修改/etc/inittab,新增一行,如果读者的脚本无法直接在板子上修改,请自行修改自己的根文件系统并更新。

tty1::askfirst:-/bin/sh

并重新上电。

[root@TX2440A 08button]# insmod buttons.ko

input: Unspecified device as /class/input/input1

[root@TX2440A 10lcd]# insmod cfbfillrect.ko

[root@TX2440A 10lcd]# insmod cfbcopyarea.ko

[root@TX2440A 10lcd]# insmod cfbimgblt.ko

[root@TX2440A 10lcd]# insmod lcd.ko

Console: switching to colour frame buffer device 60x34

加载完成后,会在lcd屏幕显示一行,如下所示。

Please press Enter to activate this console.

9.2按下表示enter的按键,则会激活该lcd console。

9.3按下表示l,s,enter的按键,则会像在串口上输入ls+enter命令一样,显示当前目录下的文件。

执行结果

8在开发板执行如下命令,观察现象。

echo hello > /dev/tty1

cat lcd.ko > /dev/fb0

echo hello > /dev/tty1

该命令执行后,lcd屏幕上会显示hello字符,如下图所示

cat lcd.ko > /dev/fb0

该命令执行后,lcd屏幕上会花屏,且只会花屏一半左右,如下图所示

9配合buttons驱动,让按键模拟l,s,enter,并显示输出到lcd屏幕上

9.1重新上电后,lcd屏幕如下图所示

9.2按下enter后如下图所示

9.3按下l+s+enter按键后如下图所示

结果总结

在本篇文章中,中年润跟读者分享了lcd驱动的编写思路和方法,其中贯穿始终的有几个函数和关键数据结构,它们分别是:

struct fb_ops

struct fb_info

framebuffer_alloc

framebuffer_release

dma_alloc_writecombine

dma_free_writecombine

register_framebuffer

unregister_framebuffer

请读者尽力去了解这些函数的作用,入参,返回值。

问题汇总

为什么echo hello > /dev/tty1时会将hello字符显示在lcd屏幕上呢?

因为tty1就是用drivers/video/fbcon.c的驱动安装的,它也会用到我们编写的lcd驱动。当我们向tty1写入数据的时候,数据自然会被输出到lcd屏幕上了。

实战目标

1请读者根据《需求描述》章节,独立编写需求分析和需求分解。

2请读者根据需求分析和需求分解,独立编写编写思路和详细步骤。

3请读者根据编写思路,独立写出编写框架。

4请读者根据详细步骤,独立编写驱动代码和测试代码。

5请读者根据《Makefile》章节,独立编写Makefile。

6请读者根据《测试步骤》章节,独立进行测试。

7请读者抛开上述练习,自顶向下从零开始再编写一遍驱动代码,测试代码,makefile

8如果无法独立写出7,请重复练习1-6,直到能独立写出7。

参考资料

《linux设备驱动开发祥解》

《TX2440开发手册及代码》

《韦东山嵌入式教程》

《鱼树驱动笔记》

致谢

感谢在嵌入式领域深耕多年的前辈,感谢中年润的家人,感谢读者。没有前辈们的开拓,我辈也不能站在巨人的肩膀上看世界;没有家人的鼎力支持,我们也不能集中精力完成自己的工作;没有读者的关注和支持,我们也没有充足的动力来编写和完善文章。看完中年润的文章,希望读者学到的不仅仅是如何编写代码,更进一步能够学到一种思路和一种方法。

中年润后续有计划按照本模板编写linux下的常见驱动,敬请读者关注。

联系方式

微信群:见文章最底部,因微信群有效期只有7天,感兴趣的同学可以加下。微信群里主要是为初学者答疑解惑,也可以进行技术和非技术的交流,同时也欢迎和中年润志同道合的中年人加入。

微信订阅号:自顶向下学嵌入式

公众号微信:EmbeddedAIOT

CSDN博客:chichi123137

CSDN博客网址:https://blog.csdn.net/chichi123137?utm_source=blog_pc_recommand

QQ邮箱:834759803@qq.com

QQ群:766756075

更多原创文章请关注微信公众号。另外,中年润还代理销售韦东山老师的视频教程,欢迎读者咨询。在中年润这里购买了韦东山老师的视频教程,除了能得到韦东山官方的技术支持外,还能获得中年润细致入微的技术和非技术的支持和帮助。欢迎大家选购哦。

linux驱动篇-LCD相关推荐

  1. linux驱动篇之 driver_register 过程分析(二)bus_add_driver

    linux驱动篇之 driver_register 过程分析(二) 个人笔记,欢迎转载,请注明出处,共同分享 共同进步 http://blog.csdn.net/richard_liujh/artic ...

  2. 嵌入式学习之Linux驱动篇-迅为视频更新了

    想学习Linux驱动但是无从下手的同学,学习Linux驱动但是一直不能入门的同学,学习了很多视频和资料还是很懵的同学快来学习拉 https://www.bilibili.com/video/BV1Vy ...

  3. Linux驱动篇之内核模块

    Linux驱动篇之内核模块 1.基本概念 模块与驱动: Linux中,将设备分为三种基本的类型. 字符设备 块设备 网络接口 Linux中还有一个很重要的概念,模块.可在运行时添加到内核中的代码被称为 ...

  4. Linux驱动之LCD驱动编写

    在Linux驱动之内核自带的S3C2440的LCD驱动分析这篇博客中已经分析了编写LCD驱动的步骤,接下来就按照这个步骤来字尝试字节编写LCD驱动.用的LCD屏幕为tft屏,每个像素点为16bit.对 ...

  5. linux驱动篇之 driver_register 过程分析(一)

    linux驱动注册过程分析--driver_register(一) 个人笔记,欢迎转载,请注明出处,共同分享 共同进步 http://blog.csdn.net/richard_liujh/artic ...

  6. linux驱动篇-touchscreen-完整版

    Touchscreen 本篇文章为触摸屏驱动完整版本,为的是给时间充裕的同学详细讲解.如要时间有限可以看精简版,传送门在下面. https://blog.csdn.net/chichi123137/a ...

  7. linux驱动之LCD驱动框架

    软件框架: lcd框架其实与i2c/spi及其他驱动框架大同小异,都是由一个底层的platform驱动和一个较上层的抽象驱动组成.前者一般由厂商编写,而后者是内核框架提供的. 核心层 \linux-i ...

  8. linux驱动篇-button-int-poll

    本篇是linux下按键设备驱动,采用的中断法和poll机制,也是属于字符设备类的驱动,一起来动手吧.下面的话,老朋友可以跳过了直接从<需求描述>章节看起,新朋友可以试着看看. 前言 前言主 ...

  9. linux驱动篇-Button-中断法

    本篇是linux下按键设备驱动,采用的中断法,也是属于字符设备类的驱动,一起来动手吧.下面的话,老朋友可以跳过了直接从<需求描述>章节看起,新朋友可以试着看看. 前言 在嵌入式行业,有很多 ...

最新文章

  1. 【转载】大连商品交易所-新套利撮合算法FAQ
  2. oneshot单样本学习笔记
  3. 轻量级社会化分享openShare源码解析
  4. HarmonyOS快速开发入门
  5. 类库探源——System.ValueType
  6. python 修改文件属性 macos_Python中用MacFSEvents模块监视MacOS文件系统改变一例
  7. android camera 降低帧率_深入理解Camera 硬件抽象层
  8. 搜索和下载英文文献常用的网站
  9. vue + UEditor 上传图片(回显),上传附件 含token
  10. 【产品】蓝绿发布、滚动发布和灰度发布对比
  11. msl3等级烘烤时间_湿敏等级MSL Moisture sensitivity levels Classifications
  12. 记录阿里云虚拟主机FTP连接不上的解决办法
  13. python入门论坛_PythonTab:Python中文开发者社区门户
  14. cli模式下php会超时吗,php cli模式下调试
  15. 刷程序对车危害_汽车刷程序对车有什么影响
  16. Rancher 2.2 GA:企业进入应用跨多K8S集群、混合云部署新时代
  17. Windows禁用和启用触屏功能
  18. 自组网(Adhoc)和基础网(Infra)
  19. ESP32 ESP-IDF看门狗TWDT
  20. php案例分析百度云_【同布局】【同快照】百度首页双排案例分析【实战】

热门文章

  1. ab压测 apr_socket_recv: Connection reset by peer (104)错误解决方法
  2. 斗地主三步走——洗牌、发牌和看牌
  3. Linux下查看压缩文件内容的11种方法
  4. android 自定义下拉菜单
  5. 笔记-lxf官网面向对象高级编程
  6. 【oc0.8.0】b460+i7-10700kf+rx560黑苹果引导文件
  7. 开源IM项目OpenIM每周迭代版本发布-群管理 阅后即焚等-v2.0.6
  8. ppt计算机考试总结,计算机二级PPT考点与做题技巧汇总
  9. 一阶逻辑合式公式及解释
  10. Si39333有源 RFID 标签