今天我就帶著大家一起來分析這個數碼相框的制作原理和詳細過程,我會盡我最大的努力來講解,畢竟能力有限,這期間肯定會有不少講解錯誤的地方,希望朋們指出來。相互學習。
這個項目是用觸摸屏作為輸入設備,LCD作為顯示設備,牽扯到的硬件驅動程序就只有LCD,觸摸屏。我先把這個兩個和硬件相關的模塊講解了,最后在系統的講解怎么數碼相框的構造,做項目需要很好的C語言功底和對整個框架的掌握。我先講LCD模塊。
LCD驅動程序在這篇文章文章中已經詳細講解:[請點擊這里!](http://blog.csdn.net/qq_21792169/article/details/50427961)
下面中講解應用程序怎么來調用這些驅動程序來獲得硬件相關的知識,比如分辨率,像素深度等。
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的初始化操作,函數調用的時候就可以直接操作那個鏈表進而來操作鏈表中結構里面的函數。
不知道我講清楚沒有,這個只是很小的一部分,也算是比較簡單,當你開發一個項目的時候也不是那么容易,堅持下來你就成功了,下一篇文章中我們講解觸摸屏模塊。