数码相框项目之LCD模块

时间:2022-01-08 16:12:02

今天我就带着大家一起来分析这个数码相框的制作原理和详细过程,我会尽我最大的努力来讲解,毕竟能力有限,这期间肯定会有不少讲解错误的地方,希望朋们指出来。相互学习。

这个项目是用触摸屏作为输入设备,LCD作为显示设备,牵扯到的硬件驱动程序就只有LCD,触摸屏。我先把这个两个和硬件相关的模块讲解了,最后在系统的讲解怎么数码相框的构造,做项目需要很好的C语言功底和对整个框架的掌握。我先讲LCD模块。

LCD驱动程序在这篇文章文章中已经详细讲解:请点击这里!


下面中讲解应用程序怎么来调用这些驱动程序来获得硬件相关的知识,比如分辨率,像素深度等。

fb.c文件如下:

#include <config.h>
#include <disp_manager.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>

static int FBDeviceInit(void);                                    /* lcd初始化 */
static int FBShowPixel(int iX, int iY, unsigned int dwColor);    /* lcd像素显示*/
static int FBCleanScreen(unsigned int dwBackColor);     /* lcd清屏 */

static int  g_fd;

static struct fb_var_screeninfo g_tFBVar;    /*  fb_var_screeninfo这个结构体是定义在交叉编译工具链中的,很多朋友都在想这个个结构是在哪里定义的呢,编译的时候也没有添加想ts,m,freetype类型的库文件,开始我也很遗憾,后来我搜索了下,我大限交叉编译工具下面定义了这个结构体,具体路径如下:/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include/linux/fb.h这个文件里面 */
static struct fb_fix_screeninfo g_tFBFix;    /* 和上面是一样的 */
static unsigned char *g_pucFBMem;                /* 定义一个缓冲区,后面做为映射到内存的返回值,方便以后的操作 */
static unsigned int g_dwScreenSize;              /* lcd屏幕所占的字节数 */

static unsigned int g_dwLineWidth;         /* 一行所占得字节数,取值跟像素深度有关系,8bpp,16bpp,24bpp */
static unsigned int g_dwPixelWidth;       /* 像素宽度,8bpp就是1,,1bpp就是2 */

static T_DispOpr g_tFBOpr = {   /* 定义一个结构体来封装我们这个lcd模块,这里是赋值定义在.h文件中,后面讲解*/
.name        = "fb",
.DeviceInit  = FBDeviceInit,                 /*函数指针前面章节讲得很详细了 */
.ShowPixel   = FBShowPixel,
.CleanScreen = FBCleanScreen,
};

static int FBDeviceInit(void)
{
int ret;

g_fd = open(FB_DEVICE_NAME, O_RDWR);  /* 打开FB_DEVICE_NAME这个设备节点,配置文件写的是/dev/fb0  这里写成宏是方便移植 */
if (0 > g_fd)
{
DBG_PRINTF("can't open %s\n", FB_DEVICE_NAME);
}


ret = ioctl(g_fd, FBIOGET_VSCREENINFO, &g_tFBVar); /* ioctl函数来获取这个lcd驱动的可变参数,具体怎么实现我没有剧研究过,感兴趣的朋友可以自行研究。可以提出来相互探讨 */
if (ret < 0)
{
DBG_PRINTF("can't get fb's var\n");
return -1;
}


ret = ioctl(g_fd, FBIOGET_FSCREENINFO, &g_tFBFix);    /* ioctl函数来获取这个lcd驱动的固定参数 */
if (ret < 0)
{
DBG_PRINTF("can't get fb's fix\n");
return -1;
}

g_dwScreenSize = g_tFBVar.xres * g_tFBVar.yres * g_tFBVar.bits_per_pixel / 8;  /* 计算lcd所占的字节数 */
g_pucFBMem = (unsigned char *)mmap(NULL , g_dwScreenSize, PROT_READ | PROT_WRITE, MAP_SHARED, g_fd, 0);    /* 映射到一块内存上,方便操作 */
if (0 > g_pucFBMem)
{
DBG_PRINTF("can't mmap\n");
return -1;
}


g_tFBOpr.iXres       = g_tFBVar.xres;    /* 跟这个g_tFBOpr结构体赋值 */
g_tFBOpr.iYres       = g_tFBVar.yres;
g_tFBOpr.iBpp        = g_tFBVar.bits_per_pixel;


g_dwLineWidth  = g_tFBVar.xres * g_tFBVar.bits_per_pixel / 8;   /* 计算一行所占的字节数 */
g_dwPixelWidth = g_tFBVar.bits_per_pixel / 8;     /* 像素宽度 */

return 0;
}

static int FBShowPixel(int iX, int iY, unsigned int dwColor)   /* 重点掌握 */
{
unsigned char *pucFB;
unsigned short *pwFB16bpp;
unsigned int *pdwFB32bpp;
unsigned short wColor16bpp; /* 565 */
int iRed;
int iGreen;
int iBlue;

if ((iX >= g_tFBVar.xres) || (iY >= g_tFBVar.yres))  /* 显示在相框之外返回错误 */
{
DBG_PRINTF("out of region\n");
return -1;
}

pucFB      = g_pucFBMem + g_dwLineWidth * iY + g_dwPixelWidth * iX;  /* lcd映射到内存上像素的对应位置 */
pwFB16bpp  = (unsigned short *)pucFB;
pdwFB32bpp = (unsigned int *)pucFB;

switch (g_tFBVar.bits_per_pixel)   /* 选择我们lcd驱动程序指定的像素深度是多少 */
{
case 8:
{
*pucFB = (unsigned char)dwColor;  /* 取出第八位就行了,这里在驱动程序用到了调色板,显示颜色其实还是16位的,详解请看驱动程序 */
break;
}
case 16:
{
iRed   = (dwColor >> (16+3)) & 0x1f;   /* RGB=5:6:5,取出各个八位的前五位或者六位 */
iGreen = (dwColor >> (8+2)) & 0x3f;
iBlue  = (dwColor >> 3) & 0x1f;
wColor16bpp = (iRed << 11) | (iGreen << 5) | iBlue;
*pwFB16bpp = wColor16bpp;
break;
}
case 32:
{
*pdwFB32bpp = dwColor;     /* 32就不用转换直接赋值 */
break;
}
default :
{
DBG_PRINTF("can't support %d bpp\n", g_tFBVar.bits_per_pixel);
return -1;
}
}

return 0;
}

static int FBCleanScreen(unsigned int dwBackColor)  /* 把lcd屏幕设置成统一的dwBackColor颜色 */
{
unsigned char *pucFB;
unsigned short *pwFB16bpp;
unsigned int *pdwFB32bpp;
unsigned short wColor16bpp; /* 565 */
int iRed;
int iGreen;
int iBlue;
int i = 0;

pucFB      = g_pucFBMem;
pwFB16bpp  = (unsigned short *)pucFB;
pdwFB32bpp = (unsigned int *)pucFB;

switch (g_tFBVar.bits_per_pixel)   /* 和上面是样的 */
{
case 8:
{
memset(g_pucFBMem, dwBackColor, g_dwScreenSize);
break;
}
case 16:
{
iRed   = (dwBackColor >> (16+3)) & 0x1f;
iGreen = (dwBackColor >> (8+2)) & 0x3f;
iBlue  = (dwBackColor >> 3) & 0x1f;
wColor16bpp = (iRed << 11) | (iGreen << 5) | iBlue;
while (i < g_dwScreenSize)
{
*pwFB16bpp = wColor16bpp;
pwFB16bpp++;
i += 2;
}
break;
}
case 32:
{
while (i < g_dwScreenSize)
{
*pdwFB32bpp = dwBackColor;
pdwFB32bpp++;
i += 4;
}
break;
}
default :
{
DBG_PRINTF("can't support %d bpp\n", g_tFBVar.bits_per_pixel);
return -1;
}
}


return 0;
}


int FBInit(void) /* 重点掌握,当上面调用这个初始化函数的时候,记住现在只是把这个结构体放在一个链表中,对硬件的初始化还没有开始,也就是说FBDeviceInit函数还没有被调用,只是放进一链表中方便操作而已 */
{
return RegisterDispOpr(&g_tFBOpr);
}


Disp_manager.h文件如下:

#ifndef _DISP_MANAGER_H   /* 防止重复定义 */
#define _DISP_MANAGER_H

typedef struct DispOpr {   /* 这里就是我们前面提到的结构体 */

char *name;
int iXres;
int iYres;
int iBpp;
int (*DeviceInit)(void);
int (*ShowPixel)(int iPenX, int iPenY, unsigned int dwColor);
int (*CleanScreen)(unsigned int dwBackColor);
struct DispOpr *ptNext;
}T_DispOpr, *PT_DispOpr;


int RegisterDispOpr(PT_DispOpr ptDispOpr);    /* 函数声明统一放在.h文件当中,方便调用和管理 */
void ShowDispOpr(void);
int DisplayInit(void);
int FBInit(void);

#endif /* _DISP_MANAGER_H */

Disp_manager.c文件如下:

#include <config.h>      /* 配置文件,就是方便移植 */
#include <disp_manager.h>
#include <string.h>

static PT_DispOpr g_ptDispOprHead;   /* 这个PT_DispOpr类型的指针头,也就是链表头 */

int RegisterDispOpr(PT_DispOpr ptDispOpr)  /* FBInit初始化调用了这个函数,把定义DispOpr放进链表中 */
{
PT_DispOpr ptTmp;

if (!g_ptDispOprHead)  /* 判断是不是第一注册, */
{
g_ptDispOprHead   = ptDispOpr;  /* 是的话就把这个注册的结构体赋值为链表头 */
ptDispOpr->ptNext = NULL;        /* 结构体里面的指向下一个链表头的指针为空 */
}
else
{
ptTmp = g_ptDispOprHead;   /* 不是第一次注册,ptTmp临时变量方便找出这个链表的结尾 */
while (ptTmp->ptNext)  /* 找到链表的最后一项才推出循环,因为最后一项中的ptNext=NULL */
{
ptTmp = ptTmp->ptNext;   
}
ptTmp->ptNext  = ptDispOpr;  /* 把刚刚注册的结构体添加进链表尾部 */
ptDispOpr->ptNext = NULL;
}

return 0;
}

void ShowDispOpr(void)  /* 显示链表中有哪些成员 */
{
int i = 0;
PT_DispOpr ptTmp = g_ptDispOprHead;

while (ptTmp)  /* 循环查找 */
{
printf("%02d %s\n", i++, ptTmp->name);
ptTmp = ptTmp->ptNext;
}
}

PT_DispOpr GetDispOpr(char *pcName)  /* 找一个pcName 的结构体*/
{
PT_DispOpr ptTmp = g_ptDispOprHead;

while (ptTmp)
{
if (strcmp(ptTmp->name, pcName) == 0)
{
return ptTmp;
}
ptTmp = ptTmp->ptNext;
}
return NULL;
}


int DisplayInit(void)   /* 初始化,函数里面调用 FBInit()来注册结构体到链表当中去*/
{
int iError;
iError = FBInit();
return iError;
}



config.h配置文件如下:/* 就是为了方便移植和修改 */

#ifndef _CONFIG_H
#define _CONFIG_H


#include <stdio.h>

#define FB_DEVICE_NAME "/dev/fb0"

#define COLOR_BACKGROUND   0xE7DBB5  /* 泛黄的纸 */
#define COLOR_FOREGROUND   0x514438  /* 褐色字体 */

#define DBG_PRINTF(...)  
//#define DBG_PRINTF printf
#endif /* _CONFIG_H */


到此为止我们这个lcd显示模块就写完了,总结一下看我们提供了那些函数接口:

int DisplayInit(void)    /* 作用是注册一个结构体到链表中,并没有初始化 */

void ShowDispOpr(void)  /* 显示链表中有哪些成员 */
PT_DispOpr GetDispOpr(char *pcName)  /* 找一个pcName 的结构体*/

int RegisterDispOpr(PT_DispOpr ptDispOpr)   /* fb.c注册的时候会用到 */


int SelectAndInitDisplay(char *pcName)  /* 调用名字pcName结构体里面的初始化函数 ,这个函数放在了另一个文件里面的,这里暂时不讲解,反正实现功能就是下面这样的,房子啊哪里是无所谓的*/
{
int iError;
g_ptDispOpr = GetDispOpr(pcName);
if (!g_ptDispOpr)
{
return -1;
}
iError = g_ptDispOpr->DeviceInit();
return iError;
}


main函数中只需要调用DisplayInit() ,SelectAndInitDisplay(acDisplay),我们就完成了对LCD的初始化操作,函数调用的时候就可以直接操作那个链表进而来操作链表中结构里面的函数。


不知道我讲清楚没有,这个只是很小的一部分,也算是比较简单,当你开发一个项目的时候也不是那么容易,坚持下来你就成功了,下一篇文章中我们讲解触摸屏模块。