数码相框项目之触摸屏模块

最后更新于:2022-04-01 16:27:19

触摸屏驱动程序我在这篇文章有讲解:[请点击这里!](http://blog.csdn.net/qq_21792169/article/details/48750695)   有些朋会很奇怪,你这个驱动程序不是jz2440的,内核用的版本也不一样,我想说的是你都开始做项目了,如果连这个小问题都不能解决的,我劝你还是赶快回去从头开始学习。还是那句老话,我只提供思路和框架,万变不离其中这个道理你应该明白吧。如果你一味的去追求别人全部跟你做好了,你只是copy上去,编译成功。你学到多少呢?扯远了。 如果你驱动移植成功了,可以点击这里用tslib来测试,校验,查看打印坐标位置,我们后面用的到,测试触摸屏按下和松开两点的举例时候会用的到,所以这个tslib必须测试吧,编译的时候还会用到tslib库。[tslib测试请点击这里!](http://blog.csdn.net/qq_21792169/article/details/50408577) 下面就开始上代码touchScreen.c文件如下: ~~~ #include <config.h> #include <input_manager.h> #include <stdlib.h>  #include <tslib.h>    /* tslib里面的头文件,在次强调一定要安装tslib */ #include <draw.h>    /* 参考tslib里的ts_print.c */   /* 可以打印两点的距离 */ static struct tsdev *g_tTSDev; static int giXres; static int giYres; static T_InputOpr  g_tTouchScreenOpr;   /* 定义T_InputOpr类型的结构体,这个结构体定义在input_manager.h中 */ /* 注意: 由于要用到LCD的分辨率, 此函数要在SelectAndInitDisplay之后调用 */ static int TouchScreenDevInit(void)   /* 初始化TouchScreen */ { char *pcTSName = NULL; if ((pcTSName = getenv("TSLIB_TSDEVICE")) != NULL )   /* 获取环境变量,在测试tslib时候指定的 */ { g_tTSDev = ts_open(pcTSName, 1); } else { g_tTSDev = ts_open("/dev/event0", 1);  /* 没有指定环境变量就打开/dev/event0 */ } if (!g_tTSDev) {   DBG_PRINTF("ts_open error!\n"); return -1; } if (ts_config(g_tTSDev)) {    /* 目前我也不知道是干嘛用的 ,猜测是做一些初始化工作,或者配置之类的*/ DBG_PRINTF("ts_config error!\n"); return -1; } if (GetDispResolution(&giXres, &giYres))   /* 获得lcd的分辨率 */ { return -1; } g_tTouchScreenOpr.iFd = ts_fd(g_tTSDev); /* 获得这个文件的句柄,后面调用select函数监测 */ return 0; } static int TouchScreenDevExit(void) { return 0; } static int isOutOf500ms(struct timeval *ptPreTime, struct timeval *ptNowTime)  /* 延时500ms,防止触摸屏不停的操作 */ { int iPreMs; int iNowMs; iPreMs = ptPreTime->tv_sec * 1000 + ptPreTime->tv_usec / 1000; iNowMs = ptNowTime->tv_sec * 1000 + ptNowTime->tv_usec / 1000; return (iNowMs > iPreMs + 500); } static int TouchScreenGetInputEvent(PT_InputEvent ptInputEvent) /* 从触摸屏获取数据 */ { struct ts_sample tSamp; int iRet; static struct timeval tPreTime; iRet = ts_read(g_tTSDev, &tSamp, 1);  /* 把读取到的值存放在tSamp这个结构体中 */ if (iRet < 0) { return -1; } /* 处理数据 */ if (isOutOf500ms(&tPreTime, &tSamp.tv)) { /* 如果此次触摸事件发生的时间, 距上次事件超过了500ms */ tPreTime = tSamp.tv; ptInputEvent->tTime = tSamp.tv; ptInputEvent->iType = INPUT_TYPE_TOUCHSCREEN;  /* 触摸屏类型,不是串口终端或者按键, */ if (tSamp.y < giYres/3) { ptInputEvent->iVal = INPUT_VALUE_UP;  /* 向上翻页 */ } else if (tSamp.y > 2*giYres/3) { ptInputEvent->iVal = INPUT_VALUE_DOWN;  /* 想下翻页 */ } else { ptInputEvent->iVal = INPUT_VALUE_UNKNOWN;  /* 点击中间部分就是没反应 */ } return 0; } else { return -1; } return 0; } static T_InputOpr g_tTouchScreenOpr = { /* 给这个类型的结构 赋值,定义在头文件中定义*/ .name          = "touchscreen", .DeviceInit    = TouchScreenDevInit, .DeviceExit    = TouchScreenDevExit, .GetInputEvent = TouchScreenGetInputEvent, }; int TouchScreenInit(void)  /* 和上篇的lcd是一样的 */ { return RegisterInputOpr(&g_tTouchScreenOpr); } input_manager.c文件如下:/* 不详细讲解了,和lcd框架是一模一样 */ #include <config.h> #include <input_manager.h> #include <string.h> #include <sys/select.h> static PT_InputOpr g_ptInputOprHead; static fd_set g_tRFds; static int g_iMaxFd = -1; int RegisterInputOpr(PT_InputOpr ptInputOpr) { PT_InputOpr ptTmp; if (!g_ptInputOprHead) { g_ptInputOprHead   = ptInputOpr; ptInputOpr->ptNext = NULL; } else { ptTmp = g_ptInputOprHead; while (ptTmp->ptNext) { ptTmp = ptTmp->ptNext; } ptTmp->ptNext = ptInputOpr; ptInputOpr->ptNext = NULL; } return 0; } void ShowInputOpr(void)  /* 显示链表中有哪些成员 */ { int i = 0; PT_InputOpr ptTmp = g_ptInputOprHead; while (ptTmp) { printf("%02d %s\n", i++, ptTmp->name); ptTmp = ptTmp->ptNext; } } int AllInputDevicesInit(void)   /* 调用链表中结构体里面的初始化函数 */ { PT_InputOpr ptTmp = g_ptInputOprHead; int iError = -1; FD_ZERO(&g_tRFds); while (ptTmp) { if (0 == ptTmp->DeviceInit()) { FD_SET(ptTmp->iFd, &g_tRFds);   /* 这里还是为了select函数做的一些初始化操作 */ if (g_iMaxFd < ptTmp->iFd) g_iMaxFd = ptTmp->iFd; iError = 0; } ptTmp = ptTmp->ptNext; } g_iMaxFd++; return iError; } int GetInputEvent(PT_InputEvent ptInputEvent) { /* 用select函数监测stdin,touchscreen,   有数据时再调用它们的GetInputEvent或获得具体事件 */ PT_InputOpr ptTmp = g_ptInputOprHead; fd_set tRFds; int iRet; tRFds = g_tRFds; iRet = select(g_iMaxFd, &tRFds, NULL, NULL, NULL);  /* 没数据读的时候就阻塞 */ if (iRet > 0) { while (ptTmp) { if (FD_ISSET(ptTmp->iFd, &tRFds)) { if(0 == ptTmp->GetInputEvent(ptInputEvent))  /* 在链表中区找tTmp->iFd这个ID的结构体中的出读取函数 */ { return 0; } } ptTmp = ptTmp->ptNext; } } return -1; } int InputInit(void)  /* 初始化。仅仅是放进链表中 */ { int iError; iError = StdinInit(); iError |= TouchScreenInit(); return iError; } input_manager.h文件如下: #ifndef _INPUT_MANAGER_H #define _INPUT_MANAGER_H #include <sys/time.h> #define INPUT_TYPE_STDIN        0 #define INPUT_TYPE_TOUCHSCREEN  1 #define INPUT_VALUE_UP          0    #define INPUT_VALUE_DOWN        1 #define INPUT_VALUE_EXIT        2 #define INPUT_VALUE_UNKNOWN     -1 typedef struct InputEvent { struct timeval tTime; int iType;  /* stdin, touchsceen */ int iVal;   /*  */ }T_InputEvent, *PT_InputEvent; typedef struct InputOpr { char *name; int iFd; int (*DeviceInit)(void); int (*DeviceExit)(void); int (*GetInputEvent)(PT_InputEvent ptInputEvent); struct InputOpr *ptNext; }T_InputOpr, *PT_InputOpr; int InputInit(void); int RegisterInputOpr(PT_InputOpr ptInputOpr); void ShowInputOpr(void); int AllInputDevicesInit(void); int GetInputEvent(PT_InputEvent ptInputEvent); int StdinInit(void); int TouchScreenInit(void); #endif /* _INPUT_MANAGER_H */ ~~~
';

数码相框项目之LCD模块

最后更新于:2022-04-01 16:27:17

今天我就带着大家一起来分析这个数码相框的制作原理和详细过程,我会尽我最大的努力来讲解,毕竟能力有限,这期间肯定会有不少讲解错误的地方,希望朋们指出来。相互学习。 这个项目是用触摸屏作为输入设备,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的初始化操作,函数调用的时候就可以直接操作那个链表进而来操作链表中结构里面的函数。 不知道我讲清楚没有,这个只是很小的一部分,也算是比较简单,当你开发一个项目的时候也不是那么容易,坚持下来你就成功了,下一篇文章中我们讲解触摸屏模块。
';

Linux中线程使用详解

最后更新于:2022-04-01 16:27:14

**Linux下多线程详解pdf文档下载:[点击这里!](http://download.csdn.net/detail/qq_21792169/9380962)** 线程与进程 为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。 使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。 使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。 除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:   1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。   2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。 3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。   一、**线程标识** - 线程有ID, 但不是系统唯一, 而是进程环境中唯一有效. - 线程的句柄是pthread_t类型, 该类型不能作为整数处理, 而是一个结构. 下面介绍两个函数: - 头文件: <pthread.h> - 原型: int pthread_equal(pthread_t tid1, pthread_t tid2); - 返回值: 相等返回非0, 不相等返回0. - 说明: 比较两个线程ID是否相等.   - 头文件: <pthread.h> - 原型: pthread_t pthread_self(); - 返回值: 返回调用线程的线程ID. 二、**线程创建**  在执行中创建一个线程, 可以为该线程分配它需要做的工作(线程执行函数), 该线程共享进程的资源. 创建线程的函数pthread_create() - 头文件: <pthread.h> - 原型: int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg); - 返回值: 成功则返回0, 否则返回错误编号. - 参数: - tidp: 指向新创建线程ID的变量, 作为函数的输出. - attr: 用于定制各种不同的线程属性, NULL为默认属性(见下). - start_rtn: 函数指针, 为线程开始执行的函数名.该函数可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由 pthread_join()获取 - arg: 函数的唯一无类型(void)指针参数, 如要传多个参数, 可以用结构封装. linux下多线程程序的编译方法:        因为pthread的库不是linux系统的库,所以在进行编译的时候要加上     -lpthread        # gcc filename -lpthread  //默认情况下gcc使用c库,要使用额外的库要这样选择使用的库   例:thread_create.c ~~~ #include <stdio.h> #include <pthread.h>  //包线程要包含 void *mythread1(void) {    int i;    for(i=0;i<100;i++)    {       printf("this is the 1st pthread,created by zieckey.\n");       sleep(1);    } }  void *mythread2(void) {     int i; for(i=0;i<100;i++)    {       printf("this is the 2st pthread,created by zieckey.\n");       sleep(1);    } } int main() {     int ret=0;     pthread_tid1,id2;    ret=pthread_create(&id1,NULL,(void*)mythread1,NULL);     if(ret)     {         printf("create pthread error!\n");          return -1;      }    ret=pthread_create(&id2,NULL,(void*)mythread2,NULL);     if(ret)     {         printf("create pthread error!\n");          return  -1;      }    pthread_join(id1,NULL);    pthread_join(id2,NULL);       return 0; } 编译步骤:gcc thread_create .c -lpthread -othread_create 例2: thread_int.c  //向线程函数传递整形参数 #include <stdio.h> #include <pthread.h> #include <unistd.h> void *create(void *arg) {     int *num;     num=(int *)arg;    printf("create parameter is %d \n",*num);     return (void *)0; } int main(int argc,char *argv[]) {    pthread_t tidp;     int error;     int test=4;     int*attr=&test;     error=pthread_create(&tidp,NULL,create,(void*)attr);     if(error)      {        printf("pthread_create is created is not created...\n");        return -1;     }    sleep(1);   printf("pthread_create is created...\n");    return 0; } ~~~ 注:字符串,结构参数,一样道理 三、**[线程](http://blog.csdn.net/shanzhizi)属性** pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括以下几项:  __detachstate,表示新**线程**是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到  PTHREAD_CREATE_JOINABLE状态。   __schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和  SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过  pthread_setschedparam()来改变。   __schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。   __inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。    __scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:  PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。   pthread_attr_t结构中还有一些值,为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、 pthread_attr_destroy()和与各个属性相关的pthread_attr_get(),pthread_attr_set()函数。     pthread_create()中,第二个参数(pthread_attr_t)为将要创建的thread属性。通常情况下配置为NULL,使用缺省设置就可以了。但了解这些属性,有利于更好的理解thread. 属性对象(pthread_attr_t)是不透明的,而且不能通过赋值直接进行修改。系统提供了一组函数,用于初始化、配置和销毁每种对象类型。  创建属性: int pthread_attr_init(pthread_attr_t *attr); 创建的属性设定为缺省设置。  销毁属性: int pthread_attr_destroy(pthread_attr_t *attr);   **一:设置分离状态:** **线程**的分离状态有2种:**PTHREAD_CREATE_JOINABLE**(非分离状态), **PTHREAD_CREATE_DETACHED**(分离状态) 分离状态含义如下: 如果使用 **PTHREAD_CREATE_JOINABLE** 创建非分离线程,则假设应用程序将等待线程完成。也就是说,程序将对线程执行 **pthread_join。** 非分离线程在终止后,必须要有一个线程用 join 来等待它。否则,不会释放该线程的资源以供新线程使用,而这通常会导致内存泄漏。因此,如果不希望线程被等待,请将该线程作为分离线程来创建。   如果使用 **PTHREAD_CREATE_DETACHED** 创建分离thread**,**则表明此thread在退出时会自动回收资源和thread ID.   Sam之前很喜欢使用分离thread. 但现在慢慢使用中觉得这样是个不好的习惯。因为分离thread有个问题:主程序退出时,很难确认子thread已经退出。只好使用全局变量来标明子thread已经正常退出了。 另外:不管创建分离还是非分离的thread.在子thread全部退出之前退出主程序都是很有风险的。**如果主thread选择return,或者调用exit()退出,则所有thread都会被kill掉。这样很容易出错。**Sam上次出的问题其实就是这个。但如果主thread只是调用pthread_exit().则仅主线程本身终止。进程及进程内的其他线程将继续存在。所有线程都已终止时,进程也将终止。   intpthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate); int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); 得到当前和分离状态和设置当前的分离状态。   **二:设置栈溢出保护区大小:** 栈溢出概念: 溢出保护可能会导致系统资源浪费。如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。通过关闭溢出保护区,可以节省系统资源。 线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。 int pthread_attr_getguardsize(const pthread_attr_t *restrictattr,size_t *restrict guardsize); int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize); 设置和得到栈溢出保护区。如果guardsize设为0。则表示不设置栈溢出保护区。**guardsize** 的值向上舍入为PAGESIZE 的倍数。   **三:设置thread竞用范围:** 竞用范围(PTHREAD_SCOPE_SYSTEM 或 PTHREAD_SCOPE_PROCESS)指 使用 PTHREAD_SCOPE_SYSTEM 时,此线程将与系统中的所有线程进行竞争。使用 PTHREAD_SCOPE_PROCESS 时,此线程将与进程中的其他线程进行竞争。   int pthread_attr_getscope(const pthread_attr_t *restrict attr,int*restrict contentionscope); int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);   **四:设置线程并行级别:** int pthread_getconcurrency(void); int pthread_setconcurrency(int new_level); **Sam不理解这个意思。**   **五:设置调度策略:** POSIX 标准指定 SCHED_FIFO(先入先出)、SCHED_RR(循环)或 SCHED_OTHER(实现定义的方法)的调度策略属性。 ·                     SCHED_FIFO 如果调用进程具有有效的用户 ID 0,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM) 的先入先出线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,则会继续处理该线程,直到该线程放弃或阻塞为止。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS)) 的线程或其调用进程没有有效用户 ID 0 的线程,请使用 SCHED_FIFO。SCHED_FIFO 基于 TS 调度类。 ·                     SCHED_RR 如果调用进程具有有效的用户 ID 0,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM)) 的循环线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,并且这些线程没有放弃或阻塞,则在系统确定的时间段内将一直执行这些线程。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS) 的线程,请使用SCHED_RR(基于 TS 调度类)。此外,这些线程的调用进程没有有效的用户ID 0。 SCHED_FIFO 是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR 与 FIFO 相似,不同的是前者的每个线程都有一个执行时间配额。   int pthread_attr_getschedpolicy(const pthread_attr_t *restrictattr,int *restrict policy); int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);   **六:设置优先级:** int pthread_attr_getschedparam(const pthread_attr_t *restrictattr,struct sched_param *restrict param); int pthread_attr_setschedparam(pthread_attr_t *restrict attr,               conststruct sched_param *restrict param); 比较复杂,Sam没去研究。   **七:设置栈大小:** 当创建一个thread时,会给它分配一个栈空间,**线程**栈是从页边界开始的。任何指定的大小都被向上舍入到下一个页边界。不具备访问权限的页将被附加到栈的溢出端(第二项设置中设置)。 指定栈时,还应使用 PTHREAD_CREATE_JOINABLE 创建线程。在该线程的 pthread_join() 调用返回之前,不会释放该栈。在该线程终止之前,不会释放该线程的栈。了解这类线程是否已终止的唯一可靠方式是使用pthread_join。 一般情况下,不需要为线程分配栈空间。系统会为每个**线程**的栈分配指定大小的虚拟内存。  #ulimit -a可以看到这个缺省大小 **四、线程终止** 如果进程中的任一*[线程](http://blog.csdn.net/shanzhizi)*调用了exit,_Exit或者_exit,那么整个进程就会终止。与此类似,如果信号的默认动作是终止进程,那么,把该信号发送到线程会终止整个进程。 单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流。 (1):从启动例程中返回,返回值是线程的退出码 (2):线程可以被同一进程中的其他线程取消 (3):线程调用pthread_exit() pthread_exit函数: - 原型: void pthread_exit(void *rval_ptr); - 头文件: <pthread.h> - 参数: rval_ptr是一个无类型指针, 指向线程的返回值存储变量.  pthread_join函数: - 原型: int pthread_join(pthread_t thread, void **rval_ptr); - 头文件: <pthread.h> - 返回值: 成功则返回0, 否则返回错误编号. - 参数: - thread: 线程ID. - rval_ptr: 指向返回值的指针(返回值也是个指针). - 说明: - 调用线程将一直阻塞, 直到指定的线程调用pthread_exit, 从启动例程返回或被取消. - 如果线程从它的启动例程返回, rval_ptr包含返回码. - 如果线程被取消, 由rval_ptr指定的内存单元置为: PTHREAD_CANCELED. - 如果对返回值不关心, 可把rval_ptr设为NULL. 实例: ~~~ #include <pthread.h> #include <stdio.h> /* print process and thread IDs */ void printids(const char *s) {    pid_t pid, ppid;    pthread_t tid;      pid= getpid();    ppid = getppid();    tid = pthread_self();     printf("%16s pid %5u ppid %5u tid %16u (0x%x) ",             s, (unsigned int)pid, (unsigned int)ppid,             (unsigned int)tid, (unsigned int)tid); }  /* thread process */ void *thread_func(void *arg); {    printids("new thread: ");    return (void *)108; }  /* main func */ int main() {    int err;    void *tret; /* thread return value */    pthread_t ntid;    err = pthread_create(&ntid, NULL, thread_func, NULL);    if (err != 0)        perror("can't create thread");      err = pthread_join(ntid, &tret);    if (err != 0)        perror("can't join thread");     printids("main thread: ");    printf("thread exit code: %d ", (int)tret);    sleep(1);    return 0; } ~~~ 这段代码是通过前一个实例改编的, 执行流程如下: - 首先创建一个新线程, 该线程在打印完IDs后, 返回108. - 然后用pthread_join获取该线程返回值, 存于tret中. - 主线程打印IDs. - 主线程打印tret, 即新线程的返回值.   线程取消的定义: 一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。   [线程](http://blog.csdn.net/shanzhizi)取消的语义: 线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。 线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。   取消点定义: 根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、  pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程([http://blog.csdn.net/shanzhizi](http://blog.csdn.net/shanzhizi))从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用  pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段: pthread_testcancel();      retcode = read(fd, buffer, length);      pthread_testcancel();    程序设计方面的考虑: 如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。即如下代码段: ~~~ While(1) {     ………     pthread_testcancel(); } ~~~   与线程取消相关的pthread函数: intpthread_cancel(pthread_t thread):线程可以通过调用pthread_cancel函数来请求取消同一进程中的其他进程。 发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。注意pthread_cancel并不等待线程终止,它仅仅提出请求。  int pthread_setcancelstate(int state, int*oldstate): 设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和  PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为 NULL则存入原来的Cancel状态以便恢复。   int pthread_setcanceltype(int type, int*oldtype) 设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和  PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入原来的取消动作类型值。   void pthread_testcancel(void) 检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。  [http://blog.csdn.net/shanzhizi](http://blog.csdn.net/shanzhizi) 线程可以安排它退出时需要调用的函数,这与进程可以用atexit函数安排进程退出时需要调用的函数是类似的。线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时的顺序相反。 ~~~ #include <pthread.h> void pthread_cleanup_push(void(*rtn)(void*),void *arg); void pthread_cleanup_pop(int execute); ~~~ 当线程执行以下动作时调用清理函数,调用参数为arg,清理函数的调用顺序用pthread_cleanup_push来安排。 调用pthread_exit时 响应取消请求时 用非0的execute参数调用pthread_cleanup_pop时。 如果线程是通过从它的启动例程中返回而终止的话,那么它的清理处理程序就不会被调用,还要注意清理处理程序是按照与它们安装时相反的顺序被调用的。   int pthread_detach(pthread_t tid); 可以用于使线程进入分离状态。  
';

Linux中线程和进程的区别

最后更新于:2022-04-01 16:27:12

**1.定义** 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位 线程是进程的一个实体,行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.是CPU调度和分派的基本单位,它是比进程更小的能独立运 **2.关系** 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。 **3.区别** 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。 1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程. 2) 线程的划分尺度小于进程,使得多线程程序的并发性高。 3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。 4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。 **4.优缺点** 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
';

libjpeg编译使用详解

最后更新于:2022-04-01 16:27:10

**一、交叉编译libjpeg** ~~~ # tar -xzvf libjpeg-turbo-1.2.1.tar.gz  #cd libjpeg-turbo-1.2.1 #mkdir tmp # ./configure --prefix=$PWD/tmp --host=arm-linux #make #make install                /* 会安装在当前目录下面tmp目录里面 */  ~~~ **二、交叉编译jpg2rgb.c** 方法一:编译器后面直接跟上头文件,库文件。路径是我们开始编译出来的路径。 arm-linux-gcc -o jpg2rgb jpg2rgb.c -I /home/book/workspace/project/libjpeg-turbo-1.2.1/tmp/include  -L /home/book/workspace/project/libjpeg-turbo-1.2.1/tmp/lib  -ljpeg cp jpg2rgb /work/nfs_root/fs_mini_mdev_new cp libjpeg-turbo-1.2.1/tmp/lib/*so* /work/nfs_root/fs_mini_mdev_new/lib/ -d 方法二:把库文件,头文件放在交叉编译里面库文件,头文件的路径下面。 把编译出来的头文件应该放入:/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include cd  /home/book/workspace/project/libjpeg-turbo-1.2.1/tmp/include cp  *   /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include 把编译出来的库文件应该放入:/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib cd  /home/book/workspace/project/libjpeg-turbo-1.2.1/tmp/lib cp *so*  -d   /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib  cp *so* /work/nfs_root/      /* 把库文件复制到开发板lib目录下,我们用的是动态库所以需要拷贝 */ arm-linux-gcc -o jpg2rgb jpg2rgb.c -ljpeg      /* 要指定jpeg库,数学库则指定m */  jpg2rgb.c文件如下: ~~~ #include <stdio.h> #include "jpeglib.h" #include <setjmp.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> #include <stdlib.h> #define FB_DEVICE_NAME "/dev/fb0"   /* 指定lcd驱动自动创建的设别节点名 */ #define DBG_PRINTF  printf                /* 方便调试打印 */ static int g_fd; static struct fb_var_screeninfo g_tFBVar;    /* lcd相关的参数,在这篇文章中重点是libjpeg */ static struct fb_fix_screeninfo g_tFBFix; static unsigned char *g_pucFBMem; static unsigned int g_dwScreenSize; static unsigned int g_dwLineWidth; static unsigned int g_dwPixelWidth; static int FBDeviceInit(void) { int ret; g_fd = open(FB_DEVICE_NAME, O_RDWR); if (0 > g_fd) { DBG_PRINTF("can't open %s\n", FB_DEVICE_NAME); } ret = ioctl(g_fd, FBIOGET_VSCREENINFO, &g_tFBVar); if (ret < 0) { DBG_PRINTF("can't get fb's var\n"); return -1; } ret = ioctl(g_fd, FBIOGET_FSCREENINFO, &g_tFBFix); 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; 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_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; pwFB16bpp  = (unsigned short *)pucFB; pdwFB32bpp = (unsigned int *)pucFB; switch (g_tFBVar.bits_per_pixel) { case 8: { *pucFB = (unsigned char)dwColor; break; } case 16: { iRed   = (dwColor >> (16+3)) & 0x1f; iGreen = (dwColor >> (8+2)) & 0x3f; iBlue  = (dwColor >> 3) & 0x1f; wColor16bpp = (iRed << 11) | (iGreen << 5) | iBlue; *pwFB16bpp= wColor16bpp; break; } case 32: { *pdwFB32bpp = dwColor; 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) { 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; } static int FBShowLine(int iXStart, int iXEnd, int iY, unsigned char *pucRGBArray) { int i = iXStart * 3; int iX; unsigned int dwColor; if (iY >= g_tFBVar.yres) return -1; if (iXStart >= g_tFBVar.xres) return -1; if (iXEnd >= g_tFBVar.xres) { iXEnd = g_tFBVar.xres; } for (iX = iXStart; iX < iXEnd; iX++) { /* 0xRRGGBB */ dwColor = (pucRGBArray[i]<<16) + (pucRGBArray[i+1]<<8) + (pucRGBArray[i+2]<<0); i += 3; FBShowPixel(iX, iY, dwColor); } return 0; } /* 下面才是本文的重点 */ /* Allocate and initialize a JPEG decompression object    // 分配和初始化一个decompression结构体 Specify the source of the compressed data (eg, a file) // 指定源文件 Call jpeg_read_header() to obtain image info  // 用jpeg_read_header获得jpg信息 Set parameters for decompression  // 设置解压参数,比如放大、缩小 jpeg_start_decompress(...);   // 启动解压:jpeg_start_decompress while (scan lines remain to be read) jpeg_read_scanlines(...);  // 循环调用jpeg_read_scanlines jpeg_finish_decompress(...);  // jpeg_finish_decompress Release the JPEG decompression object  // 释放decompression结构体 */ /* Uage: jpg2rgb <jpg_file>  */ int main(int argc, char **argv) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; FILE * infile; int row_stride; unsigned char *buffer; if (argc != 2) { printf("Usage: \n"); printf("%s <jpg_file>\n", argv[0]); return -1; } if (FBDeviceInit())     /* 初始化Lcd */ { return -1; } FBCleanScreen(0);      /* 清屏lcd */ // 分配和初始化一个decompression结构体 cinfo.err = jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); // 指定源文件 if ((infile = fopen(argv[1], "rb")) == NULL) { fprintf(stderr, "can't open %s\n", argv[1]); return -1; } jpeg_stdio_src(&cinfo, infile); // 用jpeg_read_header获得jpg信息 jpeg_read_header(&cinfo, TRUE); /* 源信息 */ printf("image_width = %d\n", cinfo.image_width); printf("image_height = %d\n", cinfo.image_height); printf("num_components = %d\n", cinfo.num_components); // 设置解压参数,比如放大、缩小 printf("enter scale M/N:\n"); scanf("%d/%d", &cinfo.scale_num, &cinfo.scale_denom); printf("scale to : %d/%d\n", cinfo.scale_num, cinfo.scale_denom); // 启动解压:jpeg_start_decompress jpeg_start_decompress(&cinfo); /* 输出的图象的信息 */ printf("output_width = %d\n", cinfo.output_width); printf("output_height = %d\n", cinfo.output_height); printf("output_components = %d\n", cinfo.output_components); // 一行的数据长度 row_stride = cinfo.output_width * cinfo.output_components; buffer = malloc(row_stride); // 循环调用jpeg_read_scanlines来一行一行地获得解压的数据 while (cinfo.output_scanline < cinfo.output_height)  { (void) jpeg_read_scanlines(&cinfo, &buffer, 1); // 写到LCD去 FBShowLine(0, cinfo.output_width, cinfo.output_scanline, buffer); } free(buffer); jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); return 0; } ~~~ 上文中红色以下的才是本文的重点,lcd驱动才以前驱动部分已经讲得很明白了。
';

freetype安装使用详解

最后更新于:2022-04-01 16:27:08

freetype下载地址:http://sourceforge.net/projects/freetype/files/ 在PC: tar xjf freetype-2.4.10.tar.bz2  ./configure make sudo make install                     /* 默认安装在根目录下 */ gcc -o example1 example1.c  -I /usr/local/include/freetype2 -lfreetype -lm -I  后面为指定头文件路径  -lfreetype  为指定freetype库  -lm 指定m库(数学函数)    加了汉字就得指定字符集 gcc -finput-charset=GBK -fexec-charset=UTF-8 -o example1 example1.c  -I /usr/local/include/freetype2 -lfreetype -lm ./example1 ./simsun.ttc abc 交叉编译: tar -xjvf  freetype-2.4.10.tar.bz2  ./configure --host=arm-linux make make DESTDIR=$PWD/tmp install          /* 指定的固定格式 */ 编译出来的头文件应该放入: /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include 编译出来的库文件应该放入: /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib 把tmp/usr/local/lib/*  复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib sudo cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d -rf cp *so* /work/nfs_root/    -d 当你在开发板上使用freetype编译出来的程序的时候,需要用到动态库,我们需要把lib/*.so* 文件复制到开发板的lib目录下,如果用的是静态链接,就不用复制库,建议不用静态链接,这会是文件很大、浪费。 其实也可以不用放进交叉编译中,直接编译的时候跟上库路径,头文件路径也是可以的,但是相对麻烦一点。
';

智能家居网络系统的设计(一)

最后更新于:2022-04-01 16:27:05

今天开始学习制作智能家居网络系统,这个项目相对简单的多,但对于新手还是有难度的,所以今天我写出来,尽我最大努力写详细一点。 讲解过程中所涉及全部代码下载地址:[智能家居网络系统.rar](http://download.csdn.net/detail/qq_21792169/9215059) 写制作过程之前我先讲解一下基本的框架和思路。(主要是框架,自己掌握知识了自己就可以添加其他的功能,比如显示温度湿度,气体浓度,光照强度,摄像头模块,这些都只需要添加相应的驱动就可以了,其他的基本一样)。 首先在开发板上搭建好服务器,然后自己写一个HTML表单,HTML主要是用来作为人际界面来交互信息,比如账户登陆,数据显示,传递数据,仅仅是一个界面,这些数据是要用一个叫CGI脚本文件来处理的,脚本文件怎么源程序里面是有的,CGI脚本文件是可以嵌套的,这些都是放在服务器端,只要服务器运行boa服务器就可以在局域网任意一台电脑访问这个服务器的IP地址来访问这个网页,在网页上输入账户,密码在进入到控制界面,这些数据处理全是放在脚本文件,里面有一些配置文件,下面我们来具体分析下这些代码。 !!!在做这个项目的前提是你的开发板能够正常跑起来。 首先是搭建boa服务器: 下载boa源码,[boa-0.94.13](http://download.csdn.net/detail/qq_21792169/9214991) ~~~ # tar -xzvf boa-0.94.13.tar.tar  # cd boa-0.94.13/src/ # ./configure  # vi Makefile         // : = gcc 和 CPP = gcc -E, 后 改 成 : = arm-linux-gcc 和CPP =arm-linux-gcc   -E #vi src/compat.h      /*把 120 行改为如下内容:   #define TIMEZONE_OFFSET(foo) foo->tm_gmtoff*/ # vi boa.c  /* if (setuid(0) != -1) { DIE(”icky Linux kernel bug!”); } 即修改为: #if 0 if (setuid(0) != -1) { DIE(”icky Linux kernel bug!”); } #endif */ /*下面红色这部分有些系统需要修改,有些不需要修改,你可以先不加,如果出错了在修改,错误打印在日志文件中*/  if (passwdbuf == NULL) {             DIE("getpwuid");         }         if (initgroups(passwdbuf->pw_name, passwdbuf->pw_gid) == -1) {             DIE("initgroups");         } #endif #if 0  if (passwdbuf == NULL) {             DIE("getpwuid");         }         if (initgroups(passwdbuf->pw_name, passwdbuf->pw_gid) == -1) {             DIE("initgroups");         } #endif #endif #make    #arm-linux-strip boa            /* 这里的优化就是去除 boa 中的调试信息: */ ~~~ 把boa复制到根文件系统usr/bin目录下,这是一个应用程序。 配置boa.conf文件: 1 去掉boa.conf  ServerName www.your.org.here 前的注释# 2 修改访问权限:修改User nobody 为 User 0 ; 修改Group nogroup 为 Group 0 3,修改DocumentRoot为DocumentRoot   /etc/boa/www        /* 存放网页的地方 */ DirectoryIndex index.html                            /* 网页名字,和这里要保持一致 */ ScriptAlias /cgi-bin//etc/boa/cgi-bin/       /* 存放cgi脚本的位置 */ 4:在开发板/etc创建boa,在boa下创建www(用来放网页文件)和cgi-bin两个文件夹,并复制boa.conf到boa下,  在var文件下建立/log/boa文件下(这两个下面放ErrorLog /var/log/boa/error_log  AccessLog /var/log/boa/access_log ) 拷贝PC上/etc/mime.types文件到开发板/etc目录下 现在boa服务器就搭建成功了,你可以拷贝 一个网页命名为index.html到开发板的/etc/boa/www下,记得把这些文件加可执行权限,在pc机上访问我们开发板,输入210.41.141.155(我的开发板ip是这么多,你只需要改成你的就可以了,但是移动要开发板和Pc机在同一个网段)就可以显示你写的网页了。 下一编文章中我们将介绍怎么写html网页。 下篇文章的地址:[http://blog.csdn.net/qq_21792169/article/details/50418560](http://blog.csdn.net/qq_21792169/article/details/50418560)
';

智能家居网络系统设计(五)

最后更新于:2022-04-01 16:27:03

led配置文件: config.ini: led1=0,led2=1,led3=0,led4=1                      /* 这个可以自己设置led初始状态,但是一定要按照这个格式写,因为我们 main.cgi中读取配置文件就是按照的这种格式 */ main_html文件编写如下: ~~~ <script> function   AddDataPost(sUserId,sUserName)      {    var obj = new ActiveXObject("Microsoft.XMLHTTP"); sUserId = escape(sUserId); sUserName = escape(sUserName); var userInfo = "userid="+sUserId+"&username="+sUserName; obj.open("POST","getData.asp",false); obj.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); obj.send(userInfo); return   unescape(obj.responseText); }  function show() {  var date = new Date(); var now = "";  now = date.getFullYear()+"年";  now = now + (date.getMonth()+1)+"月"; now = now + date.getDate()+"日";  now = now + date.getHours()+"时";  now = now + date.getMinutes()+"分";  now = now + date.getSeconds()+"秒";  document.getElementById("nowDiv").innerHTML = now; setTimeout("show()",1000);  }  </script> <html> <head> <title>智能网络家电主页面</title> </head> <body bgcolor="Pink" > <center> <h2 align=center><font color=Blue><body onload="show()"> <div id="nowDiv"></div></h2></font>  </body> <br> <h1 align="center"><font color="crimson">智能网络家居系统</h1> <br><br><br> <FORM  METHOD="post"> <p><font color="red"><h2 align=center> 温&nbsp;&nbsp;&nbsp;&nbsp;度 $FLAG1$</h2><b> <p><font color="red"><h2 align=center> 湿&nbsp;&nbsp;&nbsp;&nbsp;度 $FLAG2$</h2><b> <p><font color="red"><h2 align=center> 粉尘浓度 $FLAG3$</h2><b>     <br><br><br> <input type="submit" name="view" value=" 刷 新 "> </FORM> <br></b> <h1 align="center"><font color="orangered">电灯控制</h1> <br> <FORM  METHOD="post"> $FLAG4$ <br> <input type="submit" name="led1" value=" 卧  室 ">   <input type="submit" name="led2" value=" 厨  房 ">   <input type="submit" name="led3" value=" 客  厅 ">   <input type="submit" name="led4" value=" 卫生间 ">   <br> </FORM>                             </center> </body> </html> <a href="http://www.pzhu.cn"> < 返  回 > </a> <p><font color="black" size="4"><center>攀枝花学院 2015-08-11 @四川</center></p> ~~~ Makefile的编写如下: obj-m :=led_drv.o KERNELDIR ?= /home/work/Linux/linux-2.6.28.7        /* 编译驱动的时候取药制定内核的路径 */ PWD := $(shell pwd) default:         arm-linux-gcc login.c -o login.cgi                          /* 编译脚本文件 */         arm-linux-gcc main.c -o main.cgi /* 编译脚本文件 */         $(MAKE) -C $(KERNELDIR) M=$(PWD) modules   /* 编译驱动 */ clean:         rm -f  *o  *.mod.o  *mod.c  *.symvers *.order  *.cgi  在这里说一下,我只给大家提供思路,基本框架是这样的,具体细节还是得靠大家去学习,眼睛痛的不行,需要休息下,所以就冲冲结束了,需要添加其他的功能也可以自己添加,比如摄像头,温度,湿度,GPS定位系统设计。这些我写出来的话估计得花一周,写这个只能家居网络系统设计主要给大家一个思路,如果发现错误,希望读者告知一声,小弟一定改动。补充一下这个用到的javascript可以用CSS代替,这个是以前做的智能家居网络系统,很多地方不是很成熟,很多地方写得不是很简洁,我也不想用我学的新知识来重新写这些代码,比较时间还是有限。如果代码有地方不是很清楚的可以看源码,源码的下载地址在第一篇文章已经给出来了,如果制作过程性遇到什么问题可以邮箱跟我,相互学习。
';

智能家居网络系统设计(四)

最后更新于:2022-04-01 16:27:01

上一篇文章我们刚好可以登录用户了,如果登录成功就是调用main.cgi这个java脚本在这一小节中我们就来学习main.c这个文件。 在这里我得强调一下,在网页中控制开发板上的开设,这个项目在所有开发板上基本通用,只是你的外设驱动程序不一样吧了,这里我写出我开发板上led驱动程序,如果你在网页上想显示温度,气体浓度,这需要加相应传感器和驱动就可以了,不同内核有可能用函数或者宏不一样,自己做相应的修改就好了。 led_drv.c驱动程序:(既然你都开始做项目了,我相信这个驱动程序应该难不倒你吧) ~~~ #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/device.h> #include <linux/miscdevice.h> #include <linux/delay.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/uaccess.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <linux/device.h> #include <linux/gpio.h> #define DEVICE_NAME "led1" ~~~ static struct class *led_1;   /* 定义一个;类,用来自动创建设备节点 */ ~~~ static unsigned long led_table [] = { S3C2410_GPB5, S3C2410_GPB6, S3C2410_GPB8, S3C2410_GPB10, }; static unsigned int led_cfg_table [] = { S3C2410_GPB5_OUTP, //0x01<<10 defined in refg-gpio.h S3C2410_GPB6_OUTP, S3C2410_GPB8_OUTP, S3C2410_GPB10_OUTP, }; static int s3c2440_leds_ioctl( struct inode *inode, struct file *file,unsigned int cmd,unsigned long arg) { switch(cmd) { case 0: case 1:    if (arg > 4)     {    return -EINVAL;    } s3c2410_gpio_setpin(led_table[arg], !cmd); return 0; default: return -EINVAL; } } static struct file_operations s3c2440_leds_fops = { .owner = THIS_MODULE, .ioctl = s3c2440_leds_ioctl, }; static int major; static int __init s3c2440_leds_init(void) {   int i;  major= register_chrdev(0, DEVICE_NAME, &s3c2440_leds_fops);  /* 自动分配主设备号 */   led_1 = class_create(THIS_MODULE, DEVICE_NAME);               /* 创建一个类 */ device_create(led_1, NULL, MKDEV(major, 0), NULL, "led");/* 在类下面创建一个设备节点,名字为led,应用程序就可以调用open("/dev/led", O_RDWR来访问驱动程序。 */ for (i = 0; i < 4 ; i++)  { s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);//设置输出输入 s3c2410_gpio_setpin(led_table[i], 1); } printk(DEVICE_NAME " initialized\n"); return 0; } static void __exit s3c2440_leds_exit(void) { unregister_chrdev(major, DEVICE_NAME);        /* 取消注册 */ device_destroy(led_1,MKDEV(major, 0));            /* 删除类下面的设备节点 */ class_destroy(led_1);/* 删除类 */ } module_init(s3c2440_leds_init); module_exit(s3c2440_leds_exit); MODULE_LICENSE("Dual BSD/GPL"); ~~~ 上面是我led驱动程序,在main.cgi中会用到。下面我们来看看main.c怎么编写的。 main.c文件: ~~~ #include<string.h> #include <stdio.h> #include <stdlib.h> #include<fcntl.h> void config(int *led_config,int led_num);          /* led配置文件 */ void led_fun(int *led_config);/*调用led驱动函数*/ int main() { int led_num=0; int led_config[4]={0,0,0,0}; FILE *fp_html; char buf[512]=""; char *len; printf("%s\r\n\r\n","Content-Type:text/html"); len = getenv("CONTENT_LENGTH"); if(len != NULL) {  unsigned int content_len = 0; char *data; content_len = atoi(len); data = malloc(content_len + 1); read(0, data, content_len); sscanf(data,"led%d",&led_num);   /* 把那个led状态改变读出来 */ free(data); } else  { led_num=0; } config(led_config,led_num); led_fun(led_config); if( (fp_html=fopen("main_html","r")) == NULL)   /*这里注意,我们不能再写一个html网页,只能通过printf来打印一个网页,为了方便,我们把这个网页读取出来放在一个数组中,在用printf来打印,这个网页是我们用来人机控制界面,开始我们设置了一个用户登录界面*/ { exit(1); } while(fgets(buf,512,fp_html)) { if(strncmp(buf,"$FLAG4$",7)==0)  /* 这里用到一个变量 */ { int i=0; for(i=0; i < 4; i++) { if(led_config[i]==0) { printf("<img src=\"../www/image/off.jpg\" width=30 height=30 hspace=45 >");/* 图片的切换 */ } else  { printf("<img src=\"../www/image/on.jpg\" width=30 height=30 hspace=45 >"); } } } else  printf("%s",buf); } return 0; } void config(int *led_config,int led_num) { FILE *fp; if((fp=fopen("config.ini","r+"))==NULL)  /*这里很重要,开始我实现这个功能的时候,总是不成功,后来找到原因了,当我们点击网页上的按钮时候,这个脚本就会重新刷新一次,从开始重新执行,我们的led状态就会恢复初值,后来才想到一个办法,把led的状态保存在一个配置文件config.ini中,*/ { printf("<p>fopen Error!<a href=\"/cgi-bin/main.cgi\">Return/a>"); exit(1); } fscanf(fp,"led1=%d,led2=%d,led3=%d,led4=%d",led_config,led_config+1,led_config+2,led_config+3); /* fscanf是把配置文件的状态读取出来,来控制led */ if(led_num > 0) { if(led_config[led_num-1]==1) { led_config[led_num-1] = 0; } else  { led_config[led_num-1] = 1; } fseek(fp,7*(led_num-1)+5,SEEK_SET); fprintf(fp,"%d",led_config[led_num-1]);  /* 把改变的状态重新存放在配置文件,注意看文件句柄fp */ } fclose(fp); } void led_fun(int *led_config) { unsigned int led_num = 0; int led_fd = open("/dev/led", O_RDWR);   /* 打开驱动程序 */ for (led_num = 0; led_num < 4; led_num++)    /*传入我们的led配置文件*/ { ioctl(led_fd, led_config[led_num], led_num); } close(led_fd); } ~~~ 下一篇文章中将写出led配置文件,main_html网页文件源代码。
';

智能家居网络系统设计(三)

最后更新于:2022-04-01 16:26:59

我还是按照这个程序的顺序来讲解,方便大家理解 一点,在这里得说明下boa服务器只是一个平台,只要搭建好了就可以不用去管它,我们只需要写cgi-bin和html文件,我们在上一篇文章中网页已经做好了,当我们点击提交按钮的时候,程序会去执行/etc/boa/cgi-bin目录下的login.cgi脚本文件,(为什么是这个文件,我们在上篇文章已经制定了这个脚本来处理数据),下面我们就来写这个脚本程序。 login.c文件: ~~~ #include <stdio.h> #include <stdlib.h> int main(void) { char *str_len=NULL; int len=0; char buf[100]=""; char user[20]=""; char passwd[20]=""; ~~~ printf("%s\r\n\r\n","Content-Type:text/html");   / * 固定格式,不用去管他,但是注意这条语句前后要空两行 * / printf("<html>\n<head>\n<title>CGI3:登录结果</title></head><br>\n"); / *  可以用printf打印网页,但是要按照 html格式打印,也可以网页保存在一个数组中来打印,后面文章中将会讲解,这里是打印新网页的标题 * / str_len = getenv("CONTENT_LENGTH");/ *  这个很重要获取环境变量,是获取我们开始登陆信息  * / if( (str_len==NULL)  || (sscanf(str_len, "%d", &len)!=1) || (len>80) )  / * 这里注意下sscanf的用法 * / printf("sorry!error!"); fgets(buf, len+1, stdin); sscanf(buf, "name=%[^&]&password=%s", user,passwd);   / * 把用户名保存在user中,把账户保存在passwd,观察这里的name,password。这里不是和网页里面设置的变量对应起来了么,这里只是提取出来吧了 * / if( (strncmp(user,"root",4)==0) && (strncmp(passwd, "111111", 6)==0) )/* 比较登陆用户是否正确  * / { printf("<script language=\"JavaScript\">self.location='main.cgi';</script>");/ *  调用一个新的main.cgi脚本  * / } else printf("Sorry! 用户名或密码错误!"); return 0; } 编译: #  arm-linux-gcc login.c -o login.cgi  (后面我会统一写进一个Makefile中) 在这一章可以这样测试,你在if( (strncmp(user,"root",4)==0) && (strncmp(passwd, "111111", 6)==0) )下面添加打印语句密码正确,和密码错误测试。注释掉打印脚本的那条语句,因为你都还没有写这个脚本,你怎么跳转呢。一定要把脚本文件复制到/etc/boa/cgi-bin目录下面,这是我们之前搭建boa服务器时候规定的文件加。
';

智能家居网络系统设计(二)

最后更新于:2022-04-01 16:26:56

这一篇文章我将讲解html网页编程的基本规范,其实在我的html网页基础里面也讲解的很明白,这里我大概讲一下基本用法。下面写把代码写上来然后在一步一步讲解,后面的注释一定不要添加进去!!! ~~~ <html>                            /* 网页固定格式 */ <head> /* 网页头部 */ <title>智能网络家居系统--网络人VS灰鸽子制作</title>/* 网页上的标题栏 */ </head> <body  background="./image/back.jpg">/* 网页背景图片./image/back.jpg是存放的路径,*/ <br> <br> <h1 align="center"><font color="red">    /* 网页上的字体居中显示红色 */ /* 为了方便布局加的空格 */ 智能网络家居系统</font></h1> <p><center><b><font size="5">                          /* 网页上的字体居中 */ 请登录</font></b></center></p><BR><BR> <form action="../cgi-bin/login.cgi" method="post"><center>   /* 创建form表单设置第一条脚本路径和名字,post方式 */ 用户名: <input type="text" name="name"><br> /* 输入你的登录账户,字符串存放在name */ 密码: <input type="password" name="password"><br><br>/* 输入你的登录密码 ,字符串存放在password*/ <input type="submit" value=" 登 录 ">          /* 登录提交按钮,这是html基本格式,这里不详解了 */ <input type="reset" value="重新输入">   /* 重新输入按钮 */ </center></form> <br><br><br><br><br><br><br><br><br><br>        /*下面都是一些字体的显示,和前面基本一样的*/ <p><center><font color="brown"> 智能网络家居系统项目 基于FL2440制作</center><p> <p><center> 2015-08-11&nbsp; by 网络人VS灰鸽子</center></p> <p><center> 个人博客:http://blog.csdn.net/qq_21792169</center></p> </body> </html> ~~~ 在这里网页就写完了,其实网页也挺简单的,我们的主要工作是放在cgi脚本的处理,这里才是重点,我们可以测试下,./image/back.jpg,你可以在开发板上/etc/boa/www下创建一个image文件夹,该文件加专门用来存放图片,这里要可开始背景图片路径对应,不然网页找不到图片的位置。 然后开PC机上访问开发板的IP地址就OK了。 下文在是这个项目的核心内容,对C库函数,JAVA脚本,放心我都会总体讲解,主要还是框架,不懂发邮箱给我。
';

前言

最后更新于:2022-04-01 16:26:54

> 原文出处:[嵌入式应用开发](http://blog.csdn.net/column/details/linuxzl.html) 作者:[qq_21792169](http://blog.csdn.net/qq_21792169) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # 嵌入式应用开发 > 励志成为一名优秀的嵌入式开发人员,CSDN博客将记录我成长的点点滴滴。
';