嵌入式linux_C应用学习之API函数

1.文件IO

1.1 open打开文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
  • pathname:字符串类型,用于标识需要打开或创建的文件,可以包含路径信息,如:“./src_file”、"/home/dengtao/hello.c"等;如果 pathname 是一个符号链接,会对其进行解引用。
  • flags:调用 open 函数时需要提供的标志,包括文件访问模式标志以及其它文件相关标志,如O_RDONLY,O_CREAT,O_RDWR,O_RDWR,O_EXCL,O_NOFOLLOW
  • mode:此参数用于指定新建文件的访问权限,只有当 flags 参数中包含 O_CREAT 或 O_TMPFILE 标志时才有效(O_TMPFILE 标志用于创建一个临时文件)如:S_IRUSR 允许文件所属者读文件,S_IWUSR 允许文件所属者写文件,S_IXUSR 允许文件所属者执行文件
  • 使用:
int fd = open("/home/dengtao/hello", O_RDWR | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
if (-1 == fd)//使用 open 函数打开一个指定的文件,如果该文件不存在则创建该文件,
return fd;

int fd = open("./app.c", O_RDWR)//使用 open 函数打开一个已经存在的文件(例如当前目录下的 app.c 文件),使用可读可写方式打开
if (-1 == fd)
return fd;

1.2 write写文件

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

调用 write 函数可向打开的文件写入数据

  • fd:文件描述符。关于文件描述符,前面已经给大家进行了简单地讲解,这里不再重述!我们需要将进行写操作的文件所对应的文件描述符传递给 write 函数。
  • buf:指定写入数据对应的缓冲区。
  • count:指定写入的字节数。
  • 返回值:如果成功将返回写入的字节数(0 表示未写入任何字节),如果此数字小于 count 参数,这不是错误,譬如磁盘空间已满,可能会发生这种情况;如果写入出错,则返回-1。

1.3 read 读文件

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
  • 返回值:如果读取成功将返回读取到的字节数,实际读取到的字节数可能会小于 count 参数指定的字节数,也有可能会为 0

1.4 close 关闭文件

#include <unistd.h>
int close(int fd);

1.5 lseek

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
  • whence:用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):
    ⚫ SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算);

⚫ SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
⚫ SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。

  • 返回值:成功将返回从文件头部开始算起的位置偏移量(字节为单位),也就是当前的读写位置;发生错误将返回-1。

使用示例

off_t off = lseek(fd, 0, SEEK_SET);//将读写位置移动到文件开头处:
off_t off = lseek(fd, 0, SEEK_END);//将读写位置移动到文件末尾:
off_t off = lseek(fd, 100, SEEK_SET);//将读写位置移动到偏移文件开头 100 个字节处:
off_t off = lseek(fd, 0, SEEK_CUR);//获取当前读写位置偏移量:

1.6 strerror函数

  • 前面说到了 errno 变量仅仅只是一个错误编号,还需要对比源码中对此编号的错误定义。strerror()函数可以将对应的 errno 转换成适合我们查看的字符串信息,其函数原型如下所示(可通过"man 3 strerror"命令查看,注意此函数是 C 库函数,并不是系统调用):
#include <string.h>
char *strerror(int errnum);
  • errnum:错误编号 errno。
  • 返回值:对应错误编号的字符串描述信息。
    使用:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void)
{
	int fd;
	/* 打开文件 */
	fd = open("./test_file", O_RDONLY);
	if (-1 == fd) {
		printf("Error: %s\n", strerror(errno));
		return -1;}
	close(fd);
	return 0;
}
/*
运行结果:Error:No such file or dictory
*/

1.7 perror函数

  • 除了 strerror 函数之外,还可以使用 perror 函数来查看错误信息,调用此函数不需要传入 errno,函数内部会自己去获取 errno 变量的值,调用此函数会直接将错误提示字符串打印出来,而不是返回字符串,除此之外还可以在输出的错误提示字符串之前加入自己的打印信息,函数原型如下所示(可通过"man 3 perror"命令查看):
#include <stdio.h>
void perror(const char *s);
  • s:在错误提示字符串信息之前,可加入自己的打印信息,也可不加,不加则传入空字符串即可。
    使用:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
	int fd;
	/* 打开文件 */
	fd = open("./test_file", O_RDONLY);
	if (-1 == fd) {
		perror("open error");
		return -1;}
	close(fd);
	return 0;
}
/*
运行结果:open error: No such file or dictory
*/

1.8 _exit()和_Exit()函数

  • main 函数中使用 return 后返回,return 执行后把控制权交给调用函数,结束该进程。调用_exit()函数会清除其使用的内存空间,并销毁其在内核中的各种数据结构,关闭进程的所有文件描述符,并结束进程、将控制权交给操作系统。_exit()函数原型如下所示:
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);
  • 调用函数需要传入 status 状态标志,0 表示正常结束、若为其它值则表示程序执行过程中检测到有错误发生。_exit()和_Exit()两者等价,用法作用是一样的,使用示例如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
	int fd;
	/* 打开文件 */
	fd = open("./test_file", O_RDONLY);
	if (-1 == fd) {
		perror("open error");
		_exit(-1);}
	close(fd);
	_exit(0);
}

1.9 exit()函数

exit()函数_exit()函数都是用来终止进程的,exit()是一个标准 C 库函数,而_exit()和_Exit()是系统调用。执行 exit()会执行一些清理工作,最后调用_exit()函数。该函数是一个标准 C 库函数,该函数的用法和_exit()/_Exit()是一样的,exit()函数原型如下:

#include <stdlib.h>
void exit(int status);

1.10 空洞文件

  • 文件如果只有400kb,使用lseek便宜600K开始写也能正常运行,400到600之间就属于文件空洞,改文件叫做空洞文件。但是文件显示的大小是总的大小如620k,如迅雷多线程下载文件时,还未下载成功就占用的全部文件大小,就是在分段下载,其中就是出现文件空洞。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)//新建一个文件把它做成空洞文件
{
 	int fd,ret,i;
	char buffer[1024];c
 	/* 打开文件 */
 	fd = open("./hole_file", O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP |S_IROTH);
 	if (-1 == fd) {
 		perror("open error");
 		exit(-1);}
 	/* 将文件读写位置移动到偏移文件头 4096 个字节(4K)处 */
 	ret = lseek(fd, 4096, SEEK_SET);
	if (-1 == ret) {
 		perror("lseek error");
 		goto err;}
 	/* 初始化 buffer 为 0xFF */
 	memset(buffer, 0xFF, sizeof(buffer));
 	/* 循环写入 4 次,每次写入 1K */
 	for (i = 0; i < 4; i++) {
 		ret = write(fd, buffer, sizeof(buffer));
 		if (-1 == ret) {
 			perror("write error");
 			goto err;}
 	}
 	ret = 0;
	err:
 	/* 关闭文件 */
 	close(fd);
 	exit(ret);
}
/*
示例代码中,我们使用 open 函数新建了一个文件 hole_file,在 Linux 系统中,新建文件大小是 0,也就是没有任何数据写入,此时使用lseek函数将读写偏移量移动到4K字节处,再使用write函数写入数据0xFF,每次写入 1K,一共写入 4 次,也就是写入了 4K 数据,也就意味着该文件前 4K 是文件空洞部分,而后 4K数据才是真正写入的数据。
*/

使用 ls 命令查看到空洞文件的大小是 8K,使用 ls 命令查看到的大小是文件的逻辑大小,自然是包括了空洞部分大小和真实数据部分大小;当使用 du 命令查看空洞文件时,其大小显示为 4K,du 命令查看到的大小是文件实际占用存储块的大小。
在这里插入图片描述

1.11 O_TRUNC、O_APPEND标志

  • O_TRUNC 这个标志的作用非常简单,如果使用了这个标志,调用 open 函数打开文件的时候会将文件原本的内容全部丢弃,文件大小变为 0。
  • 使用:fd = open(“./test_file”, O_WRONLY | O_TRUNC);
  • 如果 open 函数携带了 O_APPEND 标志,调用 open 函数打开文件,当每次使用 write()函数对文件进行写操作时,都会自动把文件当前位置偏移量移动到文件末尾,从文件末尾开始写入数据,也就是意味着每次写入数据都是从文件末尾开始。
  • 使用:fd = open(“./test_file”, O_RDWR | O_APPEND);

1.12 复制文件描述符

  • 在 Linux 系统中,open 返回得到的文件描述符 fd 可以进行复制,复制成功之后可以得到一个新的文件描述符,使用新的文件描述符和旧的文件描述符都可以对文件进行 IO 操作,复制得到的文件描述符和旧的文件描述符拥有相同的权限,譬如使用旧的文件描述符对文件有读写权限,那么新的文件描述符同样也具有读写权限;在 Linux 系统下,可以使用 dup 或 dup2 这两个系统调用对文件描述符进行复制。
  • dup 函数用于复制文件描述符,此函数原型如下所示(可通过"man 2 dup"命令查看):
#include <unistd.h>
int dup(int oldfd);
  • oldfd:需要被复制的文件描述符。
  • 返回值:成功时将返回一个新的文件描述符,由操作系统分配,分配置原则遵循文件描述符分配原则;如果复制失败将返回-1,并且会设置 errno 值。
  • *由前面的介绍可知,复制得到的文件描述符与原文件描述符都指向同一个文件表,所以它们的文件读写偏移量是一样的,那么是不是可以在不使用O_APPEND标志的情况下,通过文件描述符复制来实现接续写,接下来我们编写一个程序进行测试:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
 	unsigned char buffer1[4], buffer2[4];
 	int fd1, fd2,ret,i;
 	/* 创建新文件 test_file 并打开 */
 	fd1 = open("./test_file", O_RDWR | O_CREAT | O_EXCL,S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
 	if (-1 == fd1) {
 		perror("open error");
 		exit(-1);}
 	/* 复制文件描述符 */
 	fd2 = dup(fd1);
 	if (-1 == fd2) {
 		perror("dup error");
 		ret = -1;
 		goto err1;}
 	printf("fd1: %d\nfd2: %d\n", fd1, fd2);
 	/* buffer 数据初始化 */
 	buffer1[0] = 0x11;
 	buffer1[1] = 0x22;
 	buffer1[2] = 0x33;
 	buffer1[3] = 0x44;
 	buffer2[0] = 0xAA;
 	buffer2[1] = 0xBB;
 	buffer2[2] = 0xCC;
 	buffer2[3] = 0xDD;
 	/* 循环写入数据 */
 	for (i = 0; i < 4; i++) {
 		ret = write(fd1, buffer1, sizeof(buffer1));
 		if (-1 == ret) {
 		perror("write error");
 		goto err2;}
 	ret = write(fd2, buffer2, sizeof(buffer2));
	 if (-1 == ret) {
		 perror("write error");
 		goto err2; }}
 	/* 将读写位置偏移量移动到文件头 */
	 ret = lseek(fd1, 0, SEEK_SET);
 	if (-1 == ret) {
 		perror("lseek error");
 		goto err2;
 	}
 	/* 读取数据 */
	for (i = 0; i < 8; i++) {
 		ret = read(fd1, buffer1, sizeof(buffer1));
 		if (-1 == ret) {
 			perror("read error");
 			goto err2;}
 		printf("%x%x%x%x", buffer1[0], buffer1[1],
 		buffer1[2], buffer1[3]);
 	}
 	printf("\n");
 	ret = 0;
err2:
 	close(fd2);
err1:
 	/* 关闭文件 */
 	close(fd1);
 	exit(ret);
}
/*
运行结果:由打印信息可知,fd1 等于 6,复制得到的新的文件描述符为 7(遵循 fd 分配原则),打印出来的数据显示为接续写,所以可知,通过复制文件描述符可以实现接续写
*/
  • dup 系统调用分配的文件描述符是由系统分配的,遵循文件描述符分配原则,并不能自己指定一个文件描述符,这是 dup 系统调用的一个缺陷;而 dup2 系统调用修复了这个缺陷,可以手动指定文件描述符,而不需要遵循文件描述符分配原则,当然在实际的编程工作中,需要根据自己的情况来进行选择。dup2 函数原型如下所示(可以通过"man 2 dup2"命令查看):
#include <unistd.h>
int dup2(int oldfd, int newfd);

1.13 原子操作:pread()、 pwrite()

  • pread()和 pwrite()都是系统调用,与 read()、write()函数的作用一样,用于读取和写入数据。区别在于,pread()和 pwrite()可用于实现原子操作,调用 pread 函数或 pwrite 函数可传入一个位置偏移量 offset 参数,用于指定文件当前读或写的位置偏移量,所以调用 pread 相当于调用 lseek 后再调用 read;同理,调用 pwrite相当于调用 lseek 后再调用 write。所以可知,使用 pread 或 pwrite 函数不需要使用 lseek 来调整当前位置偏移量,并会将“移动当前位置偏移量、读或写”这两步操作组成一个原子操作。pread、pwrite 函数原型如下所示(可通过"man 2 pread"或"man 2 pwrite"命令来查看):
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
  • fd、buf、count 参数与 read 或 write 函数意义相同。
  • offset:表示当前需要进行读或写的位置偏移量。
  • 返回值:返回值与 read、write 函数返回值意义一样。
  • 虽然 pread(或 pwrite)函数相当于 lseek 与 pread(或 pwrite)函数的集合,但还是有下列区别:
    ⚫ 调用 pread 函数时,无法中断其定位和读操作(也就是原子操作);
    ⚫ 不更新文件表中的当前位置偏移量。

1.14 fcntl和ioctl函数

*fcntl()函数可以对一个已经打开的文件描述符执行一系列控制操作,譬如复制一个文件描述符(与 dup、dup2 作用相同)、获取/设置文件描述符标志、获取/设置文件状态标志等,类似于一个多功能文件描述符管理工具箱。fcntl()函数原型如下所示(可通过"man 2 fcntl"命令查看):

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ )

fd:文件描述符。
cmd:操作命令。此参数表示我们将要对 fd 进行什么操作,cmd 参数支持很多操作命令,大家可以打
开 man 手册查看到这些操作命令的详细介绍,这些命令都是以 F_XXX 开头的,譬如 F_DUPFD、F_GETFD、F_SETFD 等,不同的 cmd 具有不同的作用,cmd 操作命令大致可以分为以下 5 种功能:
⚫ 复制文件描述符(cmd=F_DUPFD 或 cmd=F_DUPFD_CLOEXEC);
⚫ 获取/设置文件描述符标志(cmd=F_GETFD 或 cmd=F_SETFD);
⚫ 获取/设置文件状态标志(cmd=F_GETFL 或 cmd=F_SETFL);
⚫ 获取/设置异步 IO 所有权(cmd=F_GETOWN 或 cmd=F_SETOWN);
⚫ 获取/设置记录锁(cmd=F_GETLK 或 cmd=F_SETLK);

  • 返回值:执行失败情况下,返回-1,并且会设置 errno;执行成功的情况下,其返回值与 cmd(操作命令)有关,譬如 cmd=F_DUPFD(复制文件描述符)将返回一个新的文件描述符、cmd=F_GETFD(获取文件描述符标志)将返回文件描述符标志、cmd=F_GETFL(获取文件状态标志)将返回文件状态标志等。
    fcntl 使用示例
    (1)复制文件描述符
    当 cmd=F_DUPFD 时,它的作用会根据 fd 复制出一个新的文件描述符,此时需要传入第三个参数,第三个参数用于指出新复制出的文件描述符是一个大于或等于该参数的可用文件描述符(没有使用的文件描述符);如果第三个参数等于一个已经存在的文件描述符,则取一个大于该参数的可用文件描述符。
    测试代码如下所示:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
 	int fd1, fd2;
 	int ret;
 	/* 打开文件 test_file */
 	fd1 = open("./test_file", O_RDONLY);
 	if (-1 == fd1) {
 		perror("open error");
 		exit(-1);
 	}
 	/* 使用 fcntl 函数复制一个文件描述符 */
 	fd2 = fcntl(fd1, F_DUPFD, 0);
 	if (-1 == fd2) {
 		perror("fcntl error");
 		ret = -1;
 		goto err;
 	}
 	printf("fd1: %d\nfd2: %d\n", fd1, fd2);
 	ret = 0;
	 close(fd2);
err:
	 /* 关闭文件 */
 	close(fd1);
	 exit(ret);
}
  • cmd=F_GETFL 可用于获取文件状态标志,cmd=F_SETFL 可用于设置文件状态标志,cmd=F_GETFL 时不需要传入第三个参数,返回值成功表示获取到的文件状态标志,cmd=F_SETFL 时,需要传入第三个参数,此参数表示需要设置的文件状态标志。这些标志指的就是我们在调用 open 函数时传入的 flags 标志,可以指定一个或多个(通过位或 | 运算符组合),但是文件权限标志(O_RDONLY、O_WRONLY、O_RDWR)以及文件创建标志(O_CREAT、O_EXCL、O_NOCTTY、O_TRUNC)不能被设置、会被忽略;在 Linux 系统中,只有 O_APPEND、O_ASYNC、O_DIRECT、O_NOATIME 以及 O_NONBLOCK 这些标志可以被修改,这里面有些标志并没有给大家介绍过,后面我们在用到的时候再给大家介绍。
  • ioctl()可以认为是一个文件 IO 操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件或硬件外设,此函数将会在进阶篇中使用到,譬如可以通过 ioctl 获取 LCD 相关信息等.
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

1.15 截断文件

使用系统调用 truncate()或 ftruncate()可将普通文件截断为指定字节长度,其函数原型如下所示:

#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

标准I/O函数

2.1 fopen()和fclose()

在文件 I/O 中,使用 open()系统调用打开或创建文件,而在标准 I/O 中,使用库函数fopen()打开或创建文件。

#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
  • mode:参数 mode 指定了对该文件的读写权限,是一个字符串;
  • r 以只读方式打开文件。r+ 以可读、可写方式打开文件。w以只写方式打开文件,如果参数 path 指定的文件存在,将文件长度截断为 0;如果指定文件不存在则创建该文件。w+以可读、可写方式打开文件,如果参数 path 指定的文件存在,将文件长度截断为 0;如果指定文件
    不存在则创建该文件。a以只写方式打开文件,打开以进行追加内容(在文件末尾写入),如果文件不存在则创建该文件。a+以可读、可写方式打开文件,以追加方式写入(在文件末尾写入),如果文件不存在则创建该文件。
    *fclose()关闭文件,调用 fclose()库函数可以关闭一个由 fopen()打开的文件,其函数原型如下所示:
#include <stdio.h>
int fclose(FILE *stream);

2.2读文件和写文件

当使用 fopen()库函数打开文件之后,接着我们便可以使用 fread()和 fwrite()库函数对文件进行读、写操
作了,函数原型如下所示:

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • ptr:fread()将读取到的数据存放在参数 ptr 指向的缓冲区中;
  • size:fread()从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大小为 nmemb * size 个字节。
  • nmemb:参数 nmemb 指定了读取数据项的个数。
  • stream:FILE 指针。

2.3fseek 定位

库函数 fseek()的作用类似于 2.7 小节所学习的系统调用 lseek(),用于设置文件读写位置偏移量,lseek()用于文件 I/O,而库函数 fseek()则用于标准 I/O,其函数原型如下所示:

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);

函数参数和返回值含义如下:
stream:FILE 指针。
offset:与 lseek()函数的 offset 参数意义相同。
whence:与 lseek()函数的 whence 参数意义相同。

v4l2摄像头

程序:

/***************************************************************

 Copyright © ALIENTEK Co., Ltd. 1998-2021. All rights reserved.

 文件名 : v4l2_camera.c

 作者 : 邓涛

 版本 : V1.0

 描述 : V4L2摄像头应用编程实战

 其他 : 无

 论坛 : www.openedv.com

 日志 : 初版 V1.0 2021/7/09 邓涛创建

 ***************************************************************/



#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <sys/ioctl.h>

#include <string.h>

#include <errno.h>

#include <sys/mman.h>

#include <linux/videodev2.h>

#include <linux/fb.h>



#define FB_DEV              "/dev/fb0"      //LCD设备节点

#define FRAMEBUFFER_COUNT   3               //帧缓冲数量



/*** 摄像头像素格式及其描述信息 ***/

typedef struct camera_format {

    unsigned char description[32];  //字符串描述信息

    unsigned int pixelformat;       //像素格式

} cam_fmt;



/*** 描述一个帧缓冲的信息 ***/

typedef struct cam_buf_info {

    unsigned short *start;      //帧缓冲起始地址

    unsigned long length;       //帧缓冲长度

} cam_buf_info;



static int width;                       //LCD宽度

static int height;                      //LCD高度

static int line_length;

static unsigned short *screen_base = NULL;//LCD显存基地址

static int fb_fd = -1;                  //LCD设备文件描述符

static int v4l2_fd = -1;                //摄像头设备文件描述符

static cam_buf_info buf_infos[FRAMEBUFFER_COUNT];

static cam_fmt cam_fmts[10];

static int frm_width, frm_height;   //视频帧宽度和高度



static int fb_dev_init(void)

{

    struct fb_var_screeninfo fb_var = {0};

    struct fb_fix_screeninfo fb_fix = {0};

    unsigned long screen_size;



    /* 打开framebuffer设备 */

    fb_fd = open(FB_DEV, O_RDWR);

    if (0 > fb_fd) {

        fprintf(stderr, "open error: %s: %s\n", FB_DEV, strerror(errno));

        return -1;

    }



    /* 获取framebuffer设备信息 */

    ioctl(fb_fd, FBIOGET_VSCREENINFO, &fb_var);

    ioctl(fb_fd, FBIOGET_FSCREENINFO, &fb_fix);



    screen_size = fb_fix.line_length * fb_var.yres;

    width = fb_var.xres;

    height = fb_var.yres;

    line_length = fb_fix.line_length / (fb_var.bits_per_pixel / 8);



    /* 内存映射 */

    screen_base = mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);

    if (MAP_FAILED == (void *)screen_base) {

        perror("mmap error");

        close(fb_fd);

        return -1;

    }



    /* LCD背景刷白 */

    memset(screen_base, 0xFF, screen_size);

    return 0;

}



static int v4l2_dev_init(const char *device)

{

    struct v4l2_capability cap = {0};



    /* 打开摄像头 */

    v4l2_fd = open(device, O_RDWR);

    if (0 > v4l2_fd) {

        fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));

        return -1;

    }



    /* 查询设备功能 */

    ioctl(v4l2_fd, VIDIOC_QUERYCAP, &cap);



    /* 判断是否是视频采集设备 */

    if (!(V4L2_CAP_VIDEO_CAPTURE & cap.capabilities)) {

        fprintf(stderr, "Error: %s: No capture video device!\n", device);

        close(v4l2_fd);

        return -1;

    }



    return 0;

}



static void v4l2_enum_formats(void)

{

    struct v4l2_fmtdesc fmtdesc = {0};



    /* 枚举摄像头所支持的所有像素格式以及描述信息 */

    fmtdesc.index = 0;

    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FMT, &fmtdesc)) {



        // 将枚举出来的格式以及描述信息存放在数组中

        cam_fmts[fmtdesc.index].pixelformat = fmtdesc.pixelformat;

        strcpy(cam_fmts[fmtdesc.index].description, fmtdesc.description);

        fmtdesc.index++;

    }

}



static void v4l2_print_formats(void)

{

    struct v4l2_frmsizeenum frmsize = {0};

    struct v4l2_frmivalenum frmival = {0};

    int i;



    frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    for (i = 0; cam_fmts[i].pixelformat; i++) {



        printf("format<0x%x>, description<%s>\n", cam_fmts[i].pixelformat,

                    cam_fmts[i].description);



        /* 枚举出摄像头所支持的所有视频采集分辨率 */

        frmsize.index = 0;

        frmsize.pixel_format = cam_fmts[i].pixelformat;

        frmival.pixel_format = cam_fmts[i].pixelformat;

        while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FRAMESIZES, &frmsize)) {



            printf("size<%d*%d> ",

                    frmsize.discrete.width,

                    frmsize.discrete.height);

            frmsize.index++;



            /* 获取摄像头视频采集帧率 */

            frmival.index = 0;

            frmival.width = frmsize.discrete.width;

            frmival.height = frmsize.discrete.height;

            while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival)) {



                printf("<%dfps>", frmival.discrete.denominator /

                        frmival.discrete.numerator);

                frmival.index++;

            }

            printf("\n");

        }

        printf("\n");

    }

}



static int v4l2_set_format(void)

{

    struct v4l2_format fmt = {0};

    struct v4l2_streamparm streamparm = {0};



    /* 设置帧格式 */

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type类型

    fmt.fmt.pix.width = width;  //视频帧宽度

    fmt.fmt.pix.height = height;//视频帧高度

    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;  //像素格式 RGB565 -> YUYV

    if (0 > ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt)) {

        fprintf(stderr, "ioctl error: VIDIOC_S_FMT: %s\n", strerror(errno));

        return -1;

    }



    /*** 判断是否已经设置为我们要求的RGB565像素格式

    如果没有设置成功表示该设备不支持RGB565像素格式 */

    if (V4L2_PIX_FMT_YUYV != fmt.fmt.pix.pixelformat) {

        fprintf(stderr, "Error: the device does not support YUYV format!\n");

        return -1;

    }



    frm_width = fmt.fmt.pix.width;  //获取实际的帧宽度

    frm_height = fmt.fmt.pix.height;//获取实际的帧高度

    printf("视频帧大小<%d * %d>\n", frm_width, frm_height);



    /* 获取streamparm */

    streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ioctl(v4l2_fd, VIDIOC_G_PARM, &streamparm);



    /** 判断是否支持帧率设置 **/

    if (V4L2_CAP_TIMEPERFRAME & streamparm.parm.capture.capability) {

        streamparm.parm.capture.timeperframe.numerator = 1;

        streamparm.parm.capture.timeperframe.denominator = 30;//30fps

        if (0 > ioctl(v4l2_fd, VIDIOC_S_PARM, &streamparm)) {

            fprintf(stderr, "ioctl error: VIDIOC_S_PARM: %s\n", strerror(errno));

            return -1;

        }

    }



    return 0;

}



static int v4l2_init_buffer(void)

{

    struct v4l2_requestbuffers reqbuf = {0};

    struct v4l2_buffer buf = {0};



    /* 申请帧缓冲 */

    reqbuf.count = FRAMEBUFFER_COUNT;       //帧缓冲的数量

    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    reqbuf.memory = V4L2_MEMORY_MMAP;

    if (0 > ioctl(v4l2_fd, VIDIOC_REQBUFS, &reqbuf)) {

        fprintf(stderr, "ioctl error: VIDIOC_REQBUFS: %s\n", strerror(errno));

        return -1;

    }



    /* 建立内存映射 */

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

    for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++) {



        ioctl(v4l2_fd, VIDIOC_QUERYBUF, &buf);

        buf_infos[buf.index].length = buf.length;

        buf_infos[buf.index].start = mmap(NULL, buf.length,

                PROT_READ | PROT_WRITE, MAP_SHARED,

                v4l2_fd, buf.m.offset);

        if (MAP_FAILED == buf_infos[buf.index].start) {

            perror("mmap error");

            return -1;

        }

    }



    /* 入队 */

    for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++) {



        if (0 > ioctl(v4l2_fd, VIDIOC_QBUF, &buf)) {

            fprintf(stderr, "ioctl error: VIDIOC_QBUF: %s\n", strerror(errno));

            return -1;

        }

    }



    return 0;

}



static int v4l2_stream_on(void)

{

    /* 打开摄像头、摄像头开始采集数据 */

    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;



    if (0 > ioctl(v4l2_fd, VIDIOC_STREAMON, &type)) {

        fprintf(stderr, "ioctl error: VIDIOC_STREAMON: %s\n", strerror(errno));

        return -1;

    }



    return 0;

}



unsigned short yuyv_to_rgb565(unsigned short y, unsigned short u,unsigned short v) {  

    int16_t r, g, b;  

  

    // 将Y, U, V从8位无符号转换为带符号的16位值  

    int16_t Yr = y - 16;  

    int16_t U = u - 128;    

    int16_t V = v - 128;  

        r = y + 1.4075 *(v-128);

        g = y - 0.3455 *(u-128) - 0.7169 *(v -128);

        b = y + 1.779 *(u - 128);

    

    // 将r, g, b值限制在0-255的范围内  

    r = r < 0 ? 0 : (r > 255 ? 255 : r);  

    g = g < 0 ? 0 : (g > 255 ? 255 : g);  

    b = b < 0 ? 0 : (b > 255 ? 255 : b);  

  

    // 将r, g, b值转换为RGB565格式  

    return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);  

}



static void v4l2_read_data(void)

{

    struct v4l2_buffer buf = {0};

    unsigned short *base;

    unsigned short *start;

    int min_w, min_h;

    int j;



    if (width > frm_width)

        min_w = frm_width;

    else

        min_w = width;

    if (height > frm_height)

        min_h = frm_height;

    else

        min_h = height;



    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

    for ( ; ; ) {

        for(buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++) {

                 

            ioctl(v4l2_fd, VIDIOC_DQBUF, &buf);     //出队

            for (j = 0, base=screen_base, start=buf_infos[buf.index].start;

                        j < min_h; j++) {



        for (int i = 0; i+3 < min_w*2; i += 4) {  

            unsigned short  y0 = start[i] & 0x00ff;  

            unsigned short u  = start[i] >> 8;  

            unsigned short y1 = start[i + 1]  & 0x00ff;  

            unsigned short v  = start[i + 1] >> 8;  

            

            unsigned short rgb0 = yuyv_to_rgb565(y0, u, v); // 第一个像素  

            unsigned short rgb1 = yuyv_to_rgb565(y1, u, v); // 第二个像素  

            //printf("y0:%d   ;u:%d   ,y1:%d; v:%d  rgb0:%d;  rgb1:%d\n",y0,u,y1,v,rgb0,rgb1);

            // 将RGB565值复制到目标缓冲区  

            *(unsigned short *)base = rgb0;  

            base += 2;  

            *(unsigned short *)base = rgb1;  

            base += 2;  

    }               

                //memcpy(base, start, min_w * 2); //RGB565 一个像素占2个字节

                //base += line_length;  //LCD显示指向下一行

                base = screen_base+(j+1)* line_length;

                start += frm_width;//指向下一行数据

            }



            // 数据处理完之后、再入队、往复

            ioctl(v4l2_fd, VIDIOC_QBUF, &buf);

        }

        

    }

}



int main(int argc, char *argv[])

{

    if (2 != argc) {

        fprintf(stderr, "Usage: %s <video_dev>\n", argv[0]);

        exit(EXIT_FAILURE);

    }



    /* 初始化LCD */

    if (fb_dev_init())

        exit(EXIT_FAILURE);



    /* 初始化摄像头 */

    if (v4l2_dev_init(argv[1]))

        exit(EXIT_FAILURE);



    /* 枚举所有格式并打印摄像头支持的分辨率及帧率 */

    v4l2_enum_formats();

    v4l2_print_formats();



    /* 设置格式 */

    if (v4l2_set_format())

        exit(EXIT_FAILURE);



    /* 初始化帧缓冲:申请、内存映射、入队 */

    if (v4l2_init_buffer())

        exit(EXIT_FAILURE);



    /* 开启视频采集 */

    if (v4l2_stream_on())

        exit(EXIT_FAILURE);
    /* 读取数据:出队 */
    v4l2_read_data();       //在函数内循环采集数据、将其显示到LCD屏
    exit(EXIT_SUCCESS);
}

Tips

man 2 open查看命令介绍

man 命令后面跟着两个参数,数字 2 表示系统调用,man 命令除了可以查看系统调用的帮助信息
外,还可以查看 Linux 命令(对应数字 1)以及标准 C 库函数(对应数字 3)所对应的帮助信息;最后一个
参数 open 表示需要查看的系统调用函数名。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 嵌入Linux C语言应用程序设计是指在嵌入设备上使用C语言编写应用程序,以实现特定功能的过程。华清远见培训PDF是一种培训资料,用于指导学习如何进行嵌入Linux C语言应用程序设计的相关技术。 在嵌入Linux C语言应用程序设计中,我们需要了解Linux操作系统的基本概念和原理,以及嵌入系统的特点和应用场景。同时,我们也需要掌握C语言的基本语法和常用的编程技巧。 这门培训课程的目标是教授学员如何使用C语言开发嵌入应用程序,并深入理解Linux系统的底层原理。培训材料以PDF形提供,方便学员在自己的电脑上随时学习和参考。 在课程中,我们将学习如何利用Linux的系统调用和API来开发应用程序,如文件操作、进程管理、网络通信等。同时,我们也将学习如何进行硬件编程,与设备进行交互,如GPIO控制、串口通信等。 除了理论学习,这门培训将通过一些实践项目,帮助学员更好地掌握所学知识。学院将提供实验环境和开发板,供学员进行实践操作。通过这些实践项目,学员可以加深对嵌入Linux C语言应用程序设计的理解,并提高自己的编程能力。 总而言之,嵌入Linux C语言应用程序设计华清远见培训PDF为学员提供了学习嵌入开发的理论知识和实践项目,帮助学员掌握如何用C语言编写嵌入应用程序,并更好地理解Linux系统的工作原理和特点。这对于从事嵌入开发的人员来说,是一门非常有价值的课程。 ### 回答2: 华清远见培训提供了一门关于嵌入Linux C语言应用程序设计的PDF教程。这门教程专注于嵌入Linux系统的C语言编程,旨在帮助学员掌握在嵌入设备上开发应用程序的技能。 教程内容主要包括以下几个方面: 1. 嵌入系统概述:介绍了嵌入系统的基本概念和特点,以及嵌入Linux系统的结构和运行环境。 2. Linux内核编程:讲解了如何进行Linux内核的配置和编译,以及如何开发内核模块和驱动程序。 3. 嵌入应用程序开发:涵盖了Linux下的C语言开发工具链的使用,包括编译、调试和测试等方面的内容。同时,还介绍了常用的嵌入开发库和函数,以及如何进行文件系统的管理和操作。 4. 嵌入设备的外设驱动开发:详细讲解了如何开发各种外设的驱动程序,包括串口、SPI、I2C等接口的使用和操作。 5. 实际项目案例:通过一些实际的嵌入项目案例,帮助学员将所学知识运用到实际的应用中。 这门教程适合已经具备一定编程基础的学员,尤其是对嵌入系统和Linux有一定了解的人群。通过学习这门课程,学员将能够掌握嵌入Linux系统的应用程序设计和开发技巧,提高自己在嵌入领域的竞争力。 ### 回答3: 《嵌入Linux C语言应用程序设计》是华清远见培训机构提供的一门培训课程,主要面向对嵌入Linux系统开发有兴趣的学习者。 嵌入Linux是一种嵌入操作系统,它是将Linux操作系统适配到嵌入设备中的一种实现方嵌入系统是一种专门为具体应用设计的计算机系统,通常它具有小型化、低功耗、实时性要求等特点。而嵌入Linux作为一种开源操作系统,具有稳定性强、适应性广等优点,因此在嵌入设备的开发中得到了广泛应用。 C语言作为一种常用的编程语言,在嵌入系统开发中也是广泛使用的工具。学习嵌入Linux C语言应用程序设计》课程可以帮助学习者掌握在嵌入Linux环境下进行C语言程序设计的技能。课程内容包括嵌入Linux系统的搭建与配置、嵌入设备的驱动程序编写、应用程序的开发等方面的知识。 在课程中,学习者将通过理论学习和实践操作相结合的方,了解嵌入Linux系统的基本原理和运行机制,并学习如何使用C语言进行嵌入系统的开发。学习者将掌握Linux系统的搭建与配置方法,了解嵌入设备的驱动程序开发流程,并学会使用C语言进行应用程序的编写。通过实际操作,学习者能够熟悉开发工具的使用,掌握调试技巧,提高代码质量和效率。 通过学习嵌入Linux C语言应用程序设计》课程,学习者能够全面了解嵌入Linux系统的开发流程和技术要点,掌握驱动程序开发和应用程序编写的基本技能,为以后从事嵌入Linux系统开发工作打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值