优快云仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2018/05/18/Linux摄像头驱动3——LCD显示/#more
Linux摄像头驱动学习第三篇,在Tiny4412的LCD上显示摄像头采集图像。
前面的UVC驱动,实现了在Ubuntu主机上显示摄像头采集的图像,但那不是最终目的,最终目的是在嵌入式设备上显示图像。
本篇博客尝试写一个应用程序,实现USB摄像头采集的图像在Tiny4412的LCD上显示。所以本篇算不上驱动开发,更多的是Linux环境下编程。
1.将驱动加入内核
在开始应用编程之前,需要先准备好驱动,在LCD上显示摄像头图像,至少需要三个驱动:LCD驱动、背光驱动、UVC驱动。
这三个驱动都在前面写过了,只需要加载即可。以前都是使用的insmod xx.ko
进行动态加载驱动,每次开发板上电后,都需要手动/脚本里加载驱动,有点麻烦。
反观内核自带的驱动,使用make menuconfig
进入图形配置界面里,找到对应的驱动,可以设置为Y
(编译到内核)、M
(编译成模块)、N
(不编译)。当设置为Y
后,进入系统后,就自带了该驱动,不再需要手动加载。
前者常用于调试阶段,就算驱动有问题,内核崩溃了,下次内核还能正常启动,修改驱动后重新加载,很方便。
后者常用于发布阶段,加入到内核,就不能再修改了,也就少了一些加载驱动的操作。
本次就仿照内核的方式,使用make menuconfig
将驱动直接加到内核里。
在这之前需要理解三个文件:
- Kconfig:
driver/
下的每个目录都有,在内核配置时候,提供配置选项; - Makefile:
driver/
下的每个目录都有,在编译的时候,判断是否加入内核; - .config:在源码根目录下,作为最终的内核编译的依据;
因此,以上三个文件,是主要影响内核模块编译的文件,后面只需要修改这三个文件即可。
-
1.创建驱动目录
在driver/
目录下新建一个hceng_drv
目录作为存放自己驱动源码的目录。 -
2.创建底层配置文件
在driver/hceng_drv/
下创建Kconfig
和Makefile
,并将驱动源码backlight_drv.c
、lcd_drv.c
、uvc_drv.c
,也放在里面。
编辑Kconfig
如下:
#
# Backlight && LCD && UVC device configuration
#
menu "Hceng add driver"
config BACKLIGHT
tristate "Backlight support"
default y
help
This is backlight driver to tiny4412 from hceng.
config LCD
tristate "LCD support"
depends on BACKLIGHT
default y
help
This is LCD driver to tiny4412 from hceng.
config UVC
tristate "UVC support"
depends on BACKLIGHT && LCD
default y
help
This is UVC driver to tiny4412 from hceng.
endmenu
包含在menu
/endmenu
中的内容会成为Hceng add driver
的子菜单;
每一个子菜单项都是由config
来定义的;
congfig
下方的tristate
、depends on
、default
、help
等为config
的属性,用于定义该菜单项的类型、依赖项、默认值、帮助信息等;
编辑Makefile
如下:
obj-$(CONFIG_BACKLIGHT) += backlight_drv.o
obj-$(CONFIG_LCD) += lcd_drv.o
obj-$(CONFIG_UVC) += uvc_drv.o
根据CONFIG_*
是y
、m
还是n
,再决定是否将后面的*.o
编译到内核。
这里的CONFIG_*
就是由最后的.config
决定。
- 3.编辑上级配置文件
这里我的hceng_drv/
上级是driver/
,因此修改driver/
下的Kconfig
和Makefile
。
修改Kconfig
,在menu
/endmenu
之间的任意位置添加:
source "drivers/hceng_drv/Kconfig"
这里的写的位置,会影响在make menuconfig
,即这里写得比较靠前,在配置界面也是比较靠前。
修改Makefile
,在任意位置添加:
obj-$(CONFIG_BACKLIGHT) += hceng_drv/
obj-$(CONFIG_LCD) += hceng_drv/
obj-$(CONFIG_UVC) += hceng_drv/
- 4.配置
执行make menuconfig
,在配置界面找到:
Device Drivers --->
Hceng add driver --->

将添加的三个驱动勾选上,最后保存、退出,就将更改的内容写到了.config
。
最后重新make
编译,也得到添加自己驱动的内核。
也可以直接修改.config
配置文件,加入:
CONFIG_BACKLIGHT=y
CONFIG_LCD=y
CONFIG_UVC=y
再make
,也一样得到添加自己驱动的内核。
2.软件框架
①:首先从摄像头获取数据放入video_buf
,数据的格式(YUV、MJPEG、RGB)和分辨率可能有多种;
②:Tiny4412的LCD仅支持RGB格式,因此需要数据格式转换;
③:Tiny4412的LCD分辨率是800X480,因此可能需要大小缩放;
④:根据LCD显示流程,必须要将显示数据写入显存(FrameBuffer);
⑤:最后LCD控制器会将显存数据自动搬运到LCD/VGA等显示设备上;

在应用编程中,要习惯面向对象编程(Object Oriented Programming),也就是把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数,在C语言中常常用结构体(struct
)来实现。
关于Linux内核C语言中的面向对象的实现,可以参考这篇博客,介绍了如何C语言实现面向对象,也通过这个能稍微理解Linux驱动中的操作函数的原理。
这里,模仿内核的编程框架,为每个模块实现一个管理链表,模块对应加入链表,再调用对应的操作函数,框架如下:

这个框架,在这里暂时不能体会到它的优势,以后接触多了,应该就能感受了。
简单说明下这个框架,主要有四个模块:
video
用于摄像头数据采集,convert
用于格式转换、process
用于缩放等操作、dispaly
用于显示。
以convert
为例,有一个manager
管理每个子模块,将每个子模块放入链表,向上提供统一的操作接口,调用对应文件的操作函数。
3.编程_获取摄像头数据
使用结构体video_device
来表示摄像头设备,包含了设备的文件句柄、像素格式、分辨率、buf信息、操作函数等:
{% codeblock lang:c %}
typedef struct video_device {
int fd; //文件句柄
int pixel_format; //像素格式
int width, height; //分辨率:宽*高
int buf_count; //buf数量
int buf_maxlen; //每个buf最大长度
int buf_cur_index; //当前buf索引
unsigned char *video_buf[VIDEO_BUFFER_NUM]; //每个video buf的地址
//操作函数
p_video_operations p_video_fops;
}video_device, *p_video_device;
//摄像头设备的操作函数
typedef struct video_operations {
char *name;
int (*init_device)(char *dev_name, p_video_device p_video_dev);
int (*exit_device)(p_video_device p_video_dev);
int (*get_format)(p_video_device p_video_dev);
int (*get_frame)(p_video_device p_video_dev, p_video_buffer p_video_buf);
int (*put_frame)(p_video_device p_video_dev, p_video_buffer p_video_buf);
int (*start_device)(p_video_device p_video_dev);
int (*stop_device)(p_video_device p_video_dev);
struct video_operations *p_next;
}video_operations, *p_video_operations;
{% endcodeblock %}
使用结构体video_buffer
来表示摄像头采集的数据,包含每帧数据信息、像素格式等:
{% codeblock lang:c %}
//图片像素的数据
typedef struct pixel_datas {
int width; //宽度: 一行有多少个像素
int height; //高度: 一列有多少个像素
int bpp; //一个像素用多少位来表示
int line_bytes; //一行数据有多少字节
int total_bytes; //所有字节数
unsigned char *pixel_datas_addr; //像素数据存储的地址
}pixel_datas, *p_pixel_datas;
//摄像头的数据
typedef struct video_buffer {
pixel_datas pixel_datas; //图片像素的数据
int pixel_format; //像素的格式
}video_buffer, *p_video_buffer;
{% endcodeblock %}
3.1 video_manager.c
video_manager.c
主要功能是操作video_operations
构成的链表,涉及的函数有:
{% codeblock lang:c %}
int register_video_ops(p_video_operations p_video_ops);
void show_video_ops(void);
p_vid