Linux中文件描述符fb和文件指针FILE*的区别

本文介绍了Linux中文件描述符(fd)的概念及其用途,并解释了C语言中文件指针(FILE*)的工作原理。同时对比了文件描述符与文件指针之间的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.文件描述符:
linux中,当一个进程打开一个文件或者是创建一个新文件时,内核向进程返回一个文件描述符来标示该文件。文件描述符是一个非负整数,实际上它是一个索引,指向内核为进程所维护的一个文件记录表。
这里写图片描述
任何程序运行起来都会打开三个默认的流,标准输入流,标准输出流,标准错误流通常情况下对应的硬件为键盘,显示器,显示器。
三个流的文件标示符为 0,1,2
因为默认的三个流已经设置好,后面打开的文件,文件描述符从3,4,5开始依次向后。
文件描述符重定向。
当close调三个默认流后,文件的描述符会从当前位置最小的开始。
2.文件指针FILE*:
文件指针变量标示符,一般形式为大写,可以看出是系统定义的一个结构体,该结构体中含有一系列文件名,文件状态,当前位置信息,文件描述符fd,还有缓冲区等。
在编写源程序的时候不需要关系FILE结构的细节。
C语言定义一个FILE*的对象时:
FILE *fp;
表示fp是一个指向FILE结构体的指针,通过fp可以读写该文件,或者操作文件,习惯的称为fp为文件指针。
3.文件标示符fd和文件指针FILE*的区别
FILE*中的内容包含文件描述符。
使用fopen、fclose、fread、fwirte对文件进行操作时,在lib层。
而文件描述符在系统调用层。系统调用的接口为write/read、close/open
它们的返回值为fd。
这里写图片描述

根据lcd.c #include "lcd.h" #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <string.h> #include <stdlib.h> //全局变量 int fd_lcd; //帧缓冲设备文件文件描述符 int fb_size = 800*480*4; //帧缓冲设备文件的大小 unsigned int *pmap; //映射区首地址 /******************************************** * 功能:初始化LCD显示屏 * 返回值: * -1:失败 * 0:成功 * 参数:无 ********************************************/ int lcdInit() { //打开帧缓冲设备文件(/dev/fb0) fd_lcd = open("/dev/fb0",O_RDWR); if (-1 == fd_lcd) { perror("open lcd error"); return -1; } //映射 pmap = mmap(NULL, fb_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd_lcd, 0); if (MAP_FAILED == pmap) { //映射失败 perror("mmap error"); close(fd_lcd); return -1; } return 0; } /******************************************** * 功能:关闭LCD显示屏 * 返回值:无 * 参数:无 ********************************************/ void lcdClose() { //解除映射,释放内存 munmap(pmap, fb_size); //关闭帧缓冲设备文件 close(fd_lcd); } /******************************************** * 功能:显示背景颜色 * 返回值:无 * 参数: * color:要显示的颜色值 ********************************************/ void lcdBrushBG(unsigned int color) { for (int y = 0; y < 480; y++) { for (int x = 0; x < 800; x++) { *(pmap + y*800 + x) = color; } } } /******************************************** * 功能:在指定的位置像素点 * 返回值:无 * 参数: * x,y:像素点的坐标 * color:像素点显示的颜色值 ********************************************/ void lcdDrawPoint(int x,int y,unsigned int color) { if (x >= 0 && x < 800 && y >= 0 && y < 480) { //是屏幕范围内的点,画像素点 *(pmap + y*800 + x) = color; } } /******************************************** * 功能:在指定的位置画矩形(实心) * 返回值:无 * 参数: * x0,y0:矩形显示的位置(左上顶点的坐标) * w,h:矩形的宽度高度 * color:矩形的颜色值 ********************************************/ void lcdDrawRect(int x0,int y0,int w,int h,unsigned int color) { for (int y = y0; y < y0+h; y++) { for (int x = x0; x < x0+w; x++) { lcdDrawPoint(x,y,color); } } } /******************************************** * 功能:在指定的位置画圆形(实心) * 返回值:无 * 参数: * x0,y0:圆心的坐标 * r:半径 * color:圆的颜色值 ********************************************/ void lcdDrawCircle(int x0,int y0,int r,unsigned int color) { for (int y = y0-r; y < y0+r; y++) { for (int x = x0-r; x < x0+r; x++) { if ((y-y0)*(y-y0)+(x-x0)*(x-x0) <= r*r) { //这个坐标到圆心的距离小于等于半径(是圆内的点) lcdDrawPoint(x,y,color); } } } } /******************************************** * 功能:在指定的位置显示文字 * 返回值:无 * 参数: * x0,y0:文字显示的位置(左上顶点的坐标) * word:文字取模结果的首地址 * w,h:文字的宽度高度(像素点个数) * color:文字的颜色值 ********************************************/ void lcdDrawWord(int x0,int y0,unsigned char *word,int w,int h,unsigned int color) { //遍历取模结果 int bytes_line = w/8; //每一行数据需要用多少个字节来保存 for (int i = 0; i < w/8*h; i++) { //每一个字节的数据对应8个像素点的状态 for (int j = 0; j <= 7; j++) { if(word[i]>>7-j & 0x01) { //是文字上的点,画像素点 int x = j + (i%bytes_line)*8 + x0; int y = i/bytes_line + y0; lcdDrawPoint(x,y,color); } } } } /******************************************** * 功能:在指定的位置显示字符串(每个汉字占3个字节) * 返回值:无 * 参数: * x0,y0:字符串显示的位置(第一字符左上顶点的坐标) * count:要显示的字符个数 * word:字符串取模结果的首地址 * w,h:字符的宽度高度(像素点个数) * color:字符的颜色值 ********************************************/ void lcdDrawStr(int x0,int y0,int count,unsigned char *word,int w,int h,unsigned int color) { //逐个显示字符 for (int i = 0; i < count; i++) { lcdDrawWord(x0+i*w,y0,word+i*w*h/8,w,h,color); } } /******************************************** * 功能:在指定的位置显示数字(正整数) * 返回值:无 * 参数: * x0,y0:数字显示的位置(左边第一个数字左上顶点的坐标) * number:要显示的数字值 * word:数字0~9取模结果的首地址 * w,h:数字的宽度高度(像素点个数) * color:数字的颜色值 ********************************************/ void lcdDrawNumber(int x0,int y0,int number,unsigned char *word,int w,int h,unsigned int color) { if (number < 0) { return; } //提取数字各位的值 char bits[20] = {0}; //保存数字各位的值 int wei = 0; //记录数字的位数 //如果数字是0,那么就是一个一位数 if (number == 0) { wei = 1; } //循环获取数字个位的数,直到number为0 while (number) { bits[wei++] = number%10; //提取个位数 number /= 10; //去除个位数 } //显示数字 for (int i = wei-1; i >= 0; i--) { lcdDrawWord(x0+(wei-1-i)*w,y0,word+bits[i]*w*h/8,w,h,color); } } /******************************************** * 功能:在指定的位置显示BMP图片 * 返回值:无 * 参数: * x0,y0:图片显示的位置(左上顶点的坐标) * pic_path:要显示的图片文件的路径名 ********************************************/ void lcdDrawBMP(int x0,int y0,char *pic_path) { //打开图片文件 int fd_pic = open(pic_path,O_RDONLY); if (-1 == fd_pic) { perror("open pic error"); return; } //读取图片的参数(魔数、像素数组文件偏移量、宽度、高度、色深) short MS,depth; //魔数、色深 int offset,w,h; //像素数组的文件偏移量、图片宽度、图片高度 read(fd_pic,&MS,2); //判断当前图片是不是BMP图片 if (0x4D42 != MS) { //不是BMP图片 printf("this pic is not BMP!\n"); close(fd_pic); return; } lseek(fd_pic,0x0A,SEEK_SET); read(fd_pic,&offset,4); lseek(fd_pic,0x12,SEEK_SET); read(fd_pic,&w,4); read(fd_pic,&h,4); lseek(fd_pic,0x1C,SEEK_SET); read(fd_pic,&depth,2); if (depth < 24) { //该BMP图片是24位以下的BMP图片,当前代码不支持显示 printf("The depth of this image is less than 24 bits and thus cannot be displayed.\n"); close(fd_pic); return; } // printf("MS=0x%x,offset=0x%x,w=%d,h=%d,deepth=%d\n",MS,offset,w,h,depth); //读取像素数组数据 int full_bytes_line = (4-(w*depth/8)%4)%4; //每一行填充的字节数 = (4 - 一行像素点颜色值的有效字节数%4)%4 int buf_size = (w*depth/8+full_bytes_line)*abs(h); //像素数组的大小 = 像素点颜色值的有效字节数+填充字节数 unsigned char buf[buf_size]; //用来保存像素数组数据 lseek(fd_pic,offset,SEEK_SET); read(fd_pic,buf,buf_size); //遍历图片上的每个像素点,将其颜色值提取出来按照argb的顺序重新排列组合,并显示到屏幕上去 unsigned char a,r,g,b; //4个颜色分量 unsigned char *p = buf; //为了方便访问数据,通过指针来访问 for (int y = 0; y < abs(h); y++) { for (int x = 0; x < w; x++) { //获取一个像素点的4个颜色分量值 b = *p++; g = *p++; r = *p++; if (depth == 24) { //是一个24位的BMP图片,只有3个颜色分量,a需要手动赋值 a = 0; } else if (depth == 32) { //是一个32位的BMP图片,有4个颜色分量,a可以直接获取 a = *p++; } //将这4个颜色分量按照 a r g b 的顺序重新排列组合成一个32位的颜色值 // a: 1001 1101 // r: 0101 0000 // g: 1010 0010 // b: 0000 1111 // a << 24: // 1010 1101 0000 0000 0000 0000 0000 0000 // r << 16: // 0101 0000 0000 0000 0000 0000 // g << 8: // 1010 0010 0000 0000 // b << 0: // 0000 1111 // a << 24 | r << 16 | g << 8 | b: // 1010 1101 0101 0000 1010 0010 0000 1111 unsigned int color = a << 24 | r << 16 | g << 8 | b; //将该像素点颜色值显示到屏幕上去 if (h > 0) { //图像在保存的时候是从下往上保存的,显示也要从下往上显示 lcdDrawPoint(x+x0,h-1-y+y0,color); } else { lcdDrawPoint(x+x0,y+y0,color); } } //每显示完一行像素点的颜色值,就需要跳过后面的填充数据 p += full_bytes_line; } //关闭图片文件 close(fd_pic); } main.c: #include <string.h> #include <termios.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> //初始化串口 //file: 串口所对应的文件名 //baudrate:波特率 //返回值:成功返回串口设备文件描述符,失败返回-1 int init_serial(const char *file, int baudrate) { int fd; fd = open(file, O_RDWR); if (fd == -1) { perror(“open device error:”); return -1; } struct termios myserial; //清空结构体 memset(&myserial, 0, sizeof (myserial)); //O_RDWR myserial.c_cflag |= (CLOCAL | CREAD); //设置控制模式状态,本地连接,接受使能 //设置 数据位 myserial.c_cflag &= ~CSIZE; //清空数据位 myserial.c_cflag &= ~CRTSCTS; //无硬件流控制 myserial.c_cflag |= CS8; //数据位:8 myserial.c_cflag &= ~CSTOPB;// //1位停止位 myserial.c_cflag &= ~PARENB; //不要校验 //myserial.c_iflag |= IGNPAR; //不要校验 //myserial.c_oflag = 0; //输入模式 //myserial.c_lflag = 0; //不激活终端模式 switch (baudrate) { case 9600: cfsetospeed(&myserial, B9600); //设置波特率 cfsetispeed(&myserial, B9600); break; case 115200: cfsetospeed(&myserial, B115200); //设置波特率 cfsetispeed(&myserial, B115200); break; case 19200: cfsetospeed(&myserial, B19200); //设置波特率 cfsetispeed(&myserial, B19200); break; } /* 刷新输出队列,清除正接受的数据 / tcflush(fd, TCIFLUSH); / 改变配置 */ tcsetattr(fd, TCSANOW, &myserial); return fd; } int main() { //初始化串口(如:设备文件/dev/ttySAC1,波特率9600) int fd_gy39 = init_serial(“/dev/ttySAC1”,9600); if (-1 == fd_gy39) { printf(“GY39 init error!\n”); return -1; } //发送命令(如:0xA5 0x83 0x28) unsigned char cmd[3] = {0xA5,0x83,0x28}; //同时获取光照强度数据温湿度数据 //往设备文件中写入命令(发送命令) write(fd_gy39,cmd,3); //循环读取传感器数据 ssize_t ret; while (1) { sleep(1); unsigned char data[9+15] = {0}; //光照强度数据9个字节,温湿度数据15个字节 ret = read(fd_gy39,data,sizeof(data)); if (-1 == ret) { //读取GY39数据失败 perror(“read data from GY39 error”); close(fd_gy39); return -1; } else if (ret == 0) { //没有读到数据 printf(“No data was read.\n”); sleep(1); continue; } else if (sizeof(data) != ret) { //读取到的数据长度不正确,数据不完整,放弃这条数据,重新读 printf(“The data is incomplete.\n”); printf(“sizeof(data)=%d,ret=%ld\n”,sizeof(data),ret); continue; } //分析计算传感器数据 if (data[2] == 0x15) { //光照强度数据在前面 if (data[0] != 0x5A && data[1] != 0x5A && data[9] != 0x5A && data[10] != 0x5A) { //数据不是以0x5A开头,数据不完整,放弃这条数据,重新读 printf(“Error in data: data[0]=0x%x data[1]=0x%x,data[9]=0x%x,data[10]=0x%x”); continue; } //计算光照强度数据 float LUX = (data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7])/100; //计算温度 float T = (data[13] << 8 | data[14])/100; //打印或者显示数据 printf(“LUX=%.2f,T=%.2f\n”,LUX,T); } else { //温湿度等数据在前面 if (data[0] != 0x5A && data[1] != 0x5A && data[15] != 0x5A && data[16] != 0x5A) { //数据不是以0x5A开头,数据不完整,放弃这条数据,重新读 // printf(“data[0]=0x%x data[1]=0x%x,data[9]=0x%x,data[10]=0x%x”); continue; } } } //关闭串口文件 close(fd_gy39); return 0; } 在此程序上修改获取传感器数据,并显示到GEC6818开发板屏幕上去,要显示名称单位, 实现手控灯自控灯
最新发布
07-13
根据你提供的 `main.c` `lcd.c`,我们可以将程序扩展为: --- ## ✅ 功能目标 1. **通过串口获取 GY39 传感器数据(光照强度温度)** 2. **使用 `lcdDrawStr()` 函数在 GEC6818 开发板的 LCD 屏幕上显示带名称、单位的数据** 3. **实现手动控制 LED 灯(按键盘切换)** 4. **实现自动控制 LED 灯(根据光照强度自动开关)** --- ## 📦 所需修改内容 - 在 `main.c` 中: - 引入 `lcd.h` - 初始化 LCD - 显示传感器数据到屏幕 - 控制灯逻辑(模拟 GPIO) - 添加非阻塞键盘输入函数 `kbhit()` --- ## 💡 修改后的完整代码如下: ### ✅ main.c(已修改) ```c #include <string.h> #include <termios.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include "lcd.h" // 声明 kbhit 函数(用于检测按键) int kbhit(void); // 初始化串口 int init_serial(const char *file, int baudrate) { int fd = open(file, O_RDWR); if (fd == -1) { perror("open device error:"); return -1; } struct termios myserial; memset(&myserial, 0, sizeof(myserial)); myserial.c_cflag |= (CLOCAL | CREAD); myserial.c_cflag &= ~CSIZE; myserial.c_cflag &= ~CRTSCTS; myserial.c_cflag |= CS8; myserial.c_cflag &= ~CSTOPB; myserial.c_cflag &= ~PARENB; switch (baudrate) { case 9600: cfsetospeed(&myserial, B9600); cfsetispeed(&myserial, B9600); break; case 115200: cfsetospeed(&myserial, B115200); cfsetispeed(&myserial, B115200); break; case 19200: cfsetospeed(&myserial, B19200); cfsetispeed(&myserial, B19200); break; } tcflush(fd, TCIFLUSH); tcsetattr(fd, TCSANOW, &myserial); return fd; } // 模拟LED控制(实际设备请替换为GPIO操作) void control_led(int on) { printf("LED %s\n", on ? "ON" : "OFF"); // 实际开发中可以写入 /dev/leds 或者 mmap GPIO寄存器 } int main() { // 初始化LCD if (lcdInit() != 0) { printf("LCD初始化失败\n"); return -1; } lcdBrushBG(0x000000); // 清屏为黑色 // 初始化串口 int fd_gy39 = init_serial("/dev/ttySAC1", 9600); if (-1 == fd_gy39) { printf("GY39 init error!\n"); return -1; } unsigned char cmd[3] = {0xA5, 0x83, 0x28}; write(fd_gy39, cmd, 3); // 灯状态变量 int light_on = 0; int auto_mode = 1; // 默认自动模式 // 字符数组用于显示文本 unsigned char word_buf[1024]; // 用于存放字符取模数据 while (1) { sleep(1); unsigned char data[24] = {0}; ssize_t ret = read(fd_gy39, data, sizeof(data)); if (ret < 0) { perror("read data from GY39 error"); continue; } else if (ret == 0) { printf("No data was read.\n"); continue; } if (data[2] == 0x15) { float LUX = (data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]) / 100.0; float T = (data[13] << 8 | data[14]) / 100.0; // 自动控制灯 if (auto_mode) light_on = (LUX < 100); control_led(light_on); // 清屏 lcdBrushBG(0x000000); // 显示文字 const char *lux_str = "Light Intensity:"; const char *temp_str = "Temperature:"; const char *unit_lux = "lux"; const char *unit_temp = "°C"; const char *mode_str = auto_mode ? "Auto Mode" : "Manual Mode"; const char *light_status = light_on ? "ON" : "OFF"; // 使用取模字体显示字符串(假设已加载) // 示例:每个汉字占3个字节,宽度为24,高度为24 int font_w = 24, font_h = 24; // 显示标题 lcdDrawStr(50, 50, strlen(lux_str), (unsigned char*)lux_str, font_w, font_h, 0xFF0000FF); // 蓝色 lcdDrawStr(50, 100, strlen(temp_str), (unsigned char*)temp_str, font_w, font_h, 0xFFFF0000); // 红色 // 显示数值 char value_lux[30], value_temp[30]; sprintf(value_lux, "%.2f", LUX); sprintf(value_temp, "%.2f", T); lcdDrawStr(300, 50, strlen(value_lux), (unsigned char*)value_lux, font_w, font_h, 0xFFFFFFFF); // 白色 lcdDrawStr(300, 100, strlen(value_temp), (unsigned char*)value_temp, font_w, font_h, 0xFFFFFFFF); // 单位 lcdDrawStr(500, 50, strlen(unit_lux), (unsigned char*)unit_lux, font_w, font_h, 0xFF00FF00); // 绿色 lcdDrawStr(500, 100, strlen(unit_temp), (unsigned char*)unit_temp, font_w, font_h, 0xFF00FF00); // 模式 lcdDrawStr(50, 150, strlen(mode_str), (unsigned char*)mode_str, font_w, font_h, 0xFFFFFF00); // 黄色 // 灯状态 lcdDrawStr(50, 200, 11, (unsigned char*)"Light: ", font_w, font_h, 0xFF00FFFF); // 青色 lcdDrawStr(200, 200, strlen(light_status), (unsigned char*)light_status, font_w, font_h, 0xFFFFFFFF); } // 键盘处理 if (kbhit()) { char c = getchar(); if (c == 'l') { if (!auto_mode) { light_on = !light_on; control_led(light_on); } } else if (c == 'a') { auto_mode = 1; } else if (c == 'm') { auto_mode = 0; } } } close(fd_gy39); lcdClose(); return 0; } ``` --- ### ✅ kbhit 函数定义(添加到任意头文件或 main.c 顶部) ```c #include <termios.h> #include <sys/ioctl.h> int kbhit(void) { struct termios oldt, newt; int ch; int oldf; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); oldf = fcntl(STDIN_FILENO, F_GETFL, 0); fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); ch = getchar(); tcsetattr(STDIN_FILENO, TCSANOW, &oldt); fcntl(STDIN_FILENO, F_SETFL, oldf); if (ch != EOF) { ungetc(ch, stdin); return 1; } return 0; } ``` --- ## 🧩 注意事项 1. **字符取模**: - `lcdDrawStr()` 函数依赖于预取模的字体库; - 你需要准备一个 24x24 或 16x16 的点阵字体数组,并将其赋值给 `word_buf`; - 如果没有现成的取模数据,可以使用工具如 [PCtoLCD2002](http://www.fmz.com/pcdlcd.html) 生成。 2. **LED 控制**: - 示例中使用了打印语句模拟控制; - 实际开发中应使用 `/dev/gpio` 或 mmap 写入 GPIO 寄存器来控制硬件 LED。 3. **屏幕刷新优化**: - 可以只重绘变化区域以提高性能; - 当前每帧全屏刷新适用于演示。 --- ##
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值