Linux驱动开发_倒车影像项目介绍

471 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情

介绍在嵌入式Linux下完成倒车影像小项目的流程,学习PWM驱动蜂鸣器,编写超声波驱动,读取壁障距离,读取摄像头的画面实时显示。

倒车影像项目

模拟: 汽车中控台---倒车影像。

组成部分:

【1】LCD屏: 实时显示摄像头采集的数据。

【2】摄像头: 放在车尾,采集图像传输给LCD屏进行显示。

【3】 倒车雷达: 超声波测距--->测量车尾距离障碍物的距离。

【4】蜂鸣器: 根据倒车雷达测量的距离,控制频率。

img

1.1 超声波测距模块

声波测距: 已知声音在空气中传播的速度。

img

img

img

img

硬件接线:

img

ECHO------->GPX1_0 (开发板第9个IO口): 中断引脚----->检测回波----输入

TRIG ------->GPB_7 (开发板第8个IO口): 输出触发信号。

超声波驱动读取距离:

 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/miscdevice.h>
 #include <linux/fs.h>
 #include <linux/uaccess.h>
 #include <linux/io.h>
 #include <linux/irq.h>
 #include <linux/delay.h>
 #include <linux/workqueue.h>
 #include <linux/gpio.h>
 #include <mach/gpio.h>
 #include <plat/gpio-cfg.h>
 #include <linux/timer.h>
 #include <linux/wait.h>
 #include <linux/sched.h>
 #include <linux/poll.h>
 #include <linux/fcntl.h>
 #include <linux/interrupt.h>
 #include <linux/ktime.h>
 ​
 static unsigned int distance_irq; /*存放中断号*/
 static u32 *GPB_DAT=NULL;
 static u32 *GPB_CON=NULL;
 ​
 static u32 distance_time_us=0; /*表示距离的时间*/
 ​
 /*
 工作队列处理函数: 
 */
 static void distance_work_func(struct work_struct *work)
 {
     u32 time1,time2;
     time1=ktime_to_us(ktime_get()); /*获取当前时间,再转换为 us 单位*/
 ​
     /*等待高电平时间结束*/
     while(gpio_get_value(EXYNOS4_GPX1(0))){}
     
     time2=ktime_to_us(ktime_get()); /*获取当前时间,再转换为 us 单位*/
 ​
     distance_time_us=time2-time1;
     //printk("us=%d\n",time2-time1);   /*us/58=厘米*/
 }
 ​
 /*静态方式初始化工作队列*/
 static DECLARE_WORK(distance_work,distance_work_func);
 ​
 /*
 中断处理函数: 用于检测超声波测距的回波
 */
 static irqreturn_t distance_handler(int irq, void *dev)
 {
     /*调度工作队列*/
     schedule_work(&distance_work);
     return IRQ_HANDLED;
 }
 ​
 static void distance_function(unsigned long data);
 /*静态方式定义内核定时器*/
 static DEFINE_TIMER(distance_timer,distance_function,0,0);
 ​
 /*内核定时器超时处理函数: 触发超声波发送方波*/
 static void distance_function(unsigned long data)
 {
     static u8 state=0;
     state=!state;
     
     /*更改GPIO口电平*/
     if(state)
     {
         *GPB_DAT|=1<<7;
     }
     else
     {
         *GPB_DAT&=~(1<<7);
     }
     
     /*修改定时器的超时时间*/
     mod_timer(&distance_timer,jiffies+msecs_to_jiffies(100));
 }
 ​
 static int distance_open(struct inode *inode, struct file *file)
 {
     return 0;
 }
 ​
 #define GET_US_TIME 0x45612
 static long distance_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long argv)
 {
     u32 *us_data=(u32*)argv;
     int err;
     u32 time_us=distance_time_us;
     switch(cmd)
     {
         case GET_US_TIME:
             err=copy_to_user(us_data,&time_us,4);
             if(err!=0)printk("拷贝失败!\n");
             break;
     }
     return 0;
 }
 ​
 static int distance_release(struct inode *inode, struct file *file)
 {
     return 0;
 }
 ​
 /*定义文件操作集合*/
 static struct file_operations distance_fops=
 {
     .open=distance_open,
     .unlocked_ioctl=distance_unlocked_ioctl,
     .release=distance_release
 };
 ​
 ​
 /*定义杂项设备结构体*/
 static struct miscdevice distance_misc=
 {
     .minor=MISC_DYNAMIC_MINOR,
     .name="tiny4412_distance",
     .fops=&distance_fops
 };
 ​
 static int __init tiny4412_distance_dev_init(void) 
 {
     int err;
     /*1. 映射GPIO口地址*/
     GPB_DAT=ioremap(0x11400044,4);
     GPB_CON=ioremap(0x11400040,4);
 ​
     *GPB_CON&=~(0xF<<4*7);
     *GPB_CON|=0x1<<4*7; /*配置输出模式*/
     
     /*2. 根据GPIO口编号,获取中断号*/
     distance_irq=gpio_to_irq(EXYNOS4_GPX1(0));
     
     /*3. 注册中断*/
     err=request_irq(distance_irq,distance_handler,IRQ_TYPE_EDGE_RISING,"distance_device",NULL);
     if(err!=0)printk("中断注册失败!\n");
     else printk("中断:超声波测距驱动安装成功!\n");
 ​
     /*4. 修改定时器超时时间*/
     mod_timer(&distance_timer,jiffies+msecs_to_jiffies(100));
 ​
     /*杂项设备注册*/
     misc_register(&distance_misc);
     return 0;
 }
 ​
 static void __exit tiny4412_distance_dev_exit(void) 
 {
     /*5. 注销中断*/
     free_irq(distance_irq,NULL);
 ​
     /*6. 停止定时器*/
     del_timer(&distance_timer);
     
     /*7. 取消IO映射*/
     iounmap(GPB_DAT);
     iounmap(GPB_CON);
 ​
     /*注销杂项设备*/
     misc_deregister(&distance_misc);
     
     printk("中断:超声波测距驱动卸载成功!\n");
 }
 ​
 module_init(tiny4412_distance_dev_init);
 module_exit(tiny4412_distance_dev_exit);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("tiny4412 wbyq");

1.2 PWM方波控制蜂鸣器

PWM方波:

img

img

img

内核自带的PWM方波驱动

img

APP代码:

 #include <stdio.h>
 #include <linux/fb.h>
 #include <sys/ioctl.h>
 ​
 #define PWM_IOCTL_SET_FREQ      1
 #define PWM_IOCTL_STOP          0
 ​
 /*
 主程序
 */
 int main(int argc,char**argv)
 {
      int fb;
      fb=open("/dev/pwm",2);
     if(fb<0)
     {
        printf("pwm打开失败!\n");
        return -1;   
     }
     
     ioctl(fb,PWM_IOCTL_SET_FREQ,100);
     sleep(5);
     ioctl(fb,PWM_IOCTL_STOP,0);
     return 0;
 }

1.3 UVC免驱摄像头编程框架: V4L2

编程的框架: v4l2--->全称: video4linux2

V4L2 : 针对UVC免驱USB设备设计框架。专用于USB摄像头的数据采集。

免驱 : 驱动已经成为标准,属于内核自带源码的一部分。

V4L2框架本身注册的也是字符设备,设备节点: /dev/videoX

V4L2 框架: 提供ioctl接口,提供了有很多命令,可以通过这些命令对摄像头做配置。

比如: 输出的图像尺寸,输出图像格式(RGB、YUV、JPG),申请采集数据的缓冲区。

img

img

配置摄像头采集队列步骤:

img

Mmap函数映射。

摄像头代码,读取摄像头画面:

 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <sys/ioctl.h>
 #include <poll.h>
 #include <signal.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <signal.h>
 #include <pthread.h>
 #include <linux/videodev2.h>
 #include <stdlib.h>
 #include <sys/mman.h>
 #include <string.h>
 #include "framebuffer.h"
 ​
 #define PWM_DEVICE "/dev/pwm"  /*PWM方波设备文件*/
 #define DISTANCE_DEVICE "/dev/tiny4412_distance" /*超声波测距设备文件*/
 #define UVC_VIDEO_DEVICE "/dev/video15"  /*UVC摄像头设备节点*/
 ​
 #define GET_US_TIME 0x45612     /*获取超声波测量的距离: ioctl命令*/
 #define PWM_IOCTL_SET_FREQ      1 /*控制PWM方波频率: ioctl命令*/
 #define PWM_IOCTL_STOP          0 /*停止PWM方波输出: ioctl命令*/
 ​
 int distance_fd;  /*超声波设备的文件描述符*/
 int pwm_fd;       /*PWM方波设备的文件描述符*/
 int uvc_video_fd; /*UVC摄像头设备文件描述符*/
 int Image_Width;  /*图像的宽度*/
 int Image_Height; /*图像的高度*/
 ​
 ​
 unsigned char *video_memaddr_buffer[4]; /*存放摄像头映射到进程空间的缓冲区地址*/
 ​
 /*
 函数功能: 用户终止了进程调用
 */
 void exit_sighandler(int sig)
 {
     //停止PWM波形输出,关闭蜂鸣器
     ioctl(pwm_fd,PWM_IOCTL_STOP,0);
     close(pwm_fd);
     close(distance_fd);
     exit(1);
 }
 ​
 /*
 函数功能: 读取超声波数据的线程
 */
 void *distance_Getpthread_func(void *dev)
 {
     /*1. 打开PWM方波驱动*/
     pwm_fd=open(PWM_DEVICE,O_RDWR);
     if(pwm_fd<0) //0 1 2
     {
         printf("%s 设备文件打开失败\n",PWM_DEVICE);
         /*退出线程*/
         pthread_exit(NULL); 
     }
 ​
     /*2. 打开超声波测距设备*/
     distance_fd=open(DISTANCE_DEVICE,O_RDWR);
     if(distance_fd<0) //0 1 2
     {
         printf("%s 设备文件打开失败\n",DISTANCE_DEVICE);
         /*退出线程*/
         pthread_exit(NULL); 
     }
 ​
     /*3. 循环读取超声波测量的距离*/
     struct pollfd fds;
     fds.fd=distance_fd;
     fds.events=POLLIN;
     int data;
     while(1)
     {
         poll(&fds,1,-1);
         ioctl(distance_fd,GET_US_TIME,&data);
         printf("距离(cm):%0.2f\n",data/58.0);
         data=data/58;
         if(data>200) /*200厘米: 安全区域*/
         {
             //停止PWM波形输出,关闭蜂鸣器
             ioctl(pwm_fd,PWM_IOCTL_STOP,0);
         }
         else if(data>100) /*100厘米: 警告区域*/
         {
             printf("警告区域!\n");
             ioctl(pwm_fd,PWM_IOCTL_SET_FREQ,2);
         }
         else /*小于<100厘米: 危险区域*/
         {
             printf(" 危险区域!\n");
             ioctl(pwm_fd,PWM_IOCTL_SET_FREQ,10);
         }
         
         //ioctl(pwm_fd,PWM_IOCTL_SET_FREQ,pwm_data);
         /*倒车影像: 测距有3个档位*/
     }
 }
 ​
 ​
 /*
 函数功能: UVC摄像头初始化
 返回值: 0表示成功
 */
 int UVCvideoInit(void)
 {
     /*1. 打开摄像头设备*/
     uvc_video_fd=open(UVC_VIDEO_DEVICE,O_RDWR);
     if(uvc_video_fd<0)
     {
         printf("%s 摄像头设备打开失败!\n",UVC_VIDEO_DEVICE);
         return -1;
     }
     
     /*2. 设置摄像头的属性*/
     struct v4l2_format format;
     memset(&format,0,sizeof(struct v4l2_format));
     format.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*表示视频捕获设备*/
     format.fmt.pix.width=800;  /*预设的宽度*/
     format.fmt.pix.height=480; /*预设的高度*/
     format.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; /*预设的格式*/
     format.fmt.pix.field=V4L2_FIELD_ANY; /*系统自动设置: 帧属性*/
     if(ioctl(uvc_video_fd,VIDIOC_S_FMT,&format)) /*设置摄像头的属性*/
     {
         printf("摄像头格式设置失败!\n");
         return -2;
     }
     
     Image_Width=format.fmt.pix.width;
     Image_Height=format.fmt.pix.height;
         
     printf("摄像头实际输出的图像尺寸:x=%d,y=%d\n",format.fmt.pix.width,format.fmt.pix.height);
     if(format.fmt.pix.pixelformat==V4L2_PIX_FMT_YUYV)
     {
         printf("当前摄像头支持YUV格式图像输出!\n");
     }
     else
     {
         printf("当前摄像头不支持YUV格式图像输出!\n");
         return -3;
     }
 ​
     /*3. 请求缓冲区: 申请摄像头数据采集的缓冲区*/
     struct v4l2_requestbuffers req_buff;
     memset(&req_buff,0,sizeof(struct v4l2_requestbuffers));
     req_buff.count=4; /*预设要申请4个缓冲区*/
     req_buff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/
     req_buff.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/
     if(ioctl(uvc_video_fd,VIDIOC_REQBUFS,&req_buff)) /*申请缓冲区*/
     {
         printf("申请摄像头数据采集的缓冲区失败!\n");
         return -4;
     }
     printf("摄像头缓冲区申请的数量: %d\n",req_buff.count);
 ​
     /*4. 获取缓冲区的详细信息: 地址,编号*/
     struct v4l2_buffer buff_info;
     memset(&buff_info,0,sizeof(struct v4l2_buffer));
     int i;
     for(i=0;i<req_buff.count;i++)
     {
         buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/
         buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/
         if(ioctl(uvc_video_fd,VIDIOC_QUERYBUF,&buff_info)) /*获取缓冲区的详细信息*/
         {
             printf("获取缓冲区的详细信息失败!\n");
             return -5;
         }
         /*根据摄像头申请缓冲区信息: 使用mmap函数将内核的地址映射到进程空间*/
         video_memaddr_buffer[i]=mmap(NULL,buff_info.length,PROT_READ|PROT_WRITE,MAP_SHARED,uvc_video_fd,buff_info.m.offset); 
         if(video_memaddr_buffer[i]==NULL)
         {
             printf("缓冲区映射失败!\n");
             return -6;
         }
     }
 ​
     /*5. 将缓冲区放入采集队列*/
     memset(&buff_info,0,sizeof(struct v4l2_buffer));
     for(i=0;i<req_buff.count;i++)
     {
         buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/
         buff_info.index=i; /*缓冲区的节点编号*/
         buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/
         if(ioctl(uvc_video_fd,VIDIOC_QBUF,&buff_info)) /*根据节点编号将缓冲区放入队列*/
         {
             printf("根据节点编号将缓冲区放入队列失败!\n");
             return -7;
         }
     }
 ​
     /*6. 启动摄像头数据采集*/
     int Type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
     if(ioctl(uvc_video_fd,VIDIOC_STREAMON,&Type))
     {
         printf("启动摄像头数据采集失败!\n");
         return -8;
     }
     return 0;
 }
 ​
 /*
 函数功能: 将YUV数据转为RGB格式
 函数参数:
 unsigned char *yuv_buffer: YUV源数据
 unsigned char *rgb_buffer: 转换之后的RGB数据
 int iWidth,int iHeight   : 图像的宽度和高度
 */
 void yuv_to_rgb(unsigned char *yuv_buffer,unsigned char *rgb_buffer,int iWidth,int iHeight)
 {
     int x;
     int z=0;
     unsigned char *ptr = rgb_buffer;
     unsigned char *yuyv= yuv_buffer;
     for (x = 0; x < iWidth*iHeight; x++) 
     {
         int r, g, b;
         int y, u, v;
 ​
         if (!z)
         y = yuyv[0] << 8;
         else
         y = yuyv[2] << 8;
         u = yuyv[1] - 128;
         v = yuyv[3] - 128;
 ​
         r = (y + (359 * v)) >> 8;
         g = (y - (88 * u) - (183 * v)) >> 8;
         b = (y + (454 * u)) >> 8;
 ​
         *(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
         *(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
         *(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
             
         if(z++)
         {
             z = 0;
             yuyv += 4;
         }
     }
 }
 ​
 int main(int argc,char **argv)
 {
     int data;
     
     /*1. 注册将要捕获的信号*/
     signal(SIGINT,exit_sighandler);
 ​
     /*2. 创建线程: 采集超声波测量的距离*/
     pthread_t threadID;
     pthread_create(&threadID,NULL,distance_Getpthread_func,NULL);
     pthread_detach(threadID); //设置分离属性
 ​
     /*3. 初始化摄像头*/
     UVCvideoInit();
 ​
     /*4. 初始化LCD屏*/
     framebuffer_Device_init();
     
     /*5. 循环采集摄像头的数据*/
     struct pollfd fds;
     fds.fd=uvc_video_fd;
     fds.events=POLLIN;
 ​
     struct v4l2_buffer buff_info;
     memset(&buff_info,0,sizeof(struct v4l2_buffer));
     int index=0; /*表示当前缓冲区的编号*/
     unsigned char *rgb_buffer=NULL;
 ​
     /*申请空间:存放转换之后的RGB数据*/
     rgb_buffer=malloc(Image_Width*Image_Height*3);
     if(rgb_buffer==NULL)
     {
         printf("RGB转换的缓冲区申请失败!\n");
         exit(0);
     }
     
     while(1)
     {
         /*1. 等待摄像头采集数据*/
         poll(&fds,1,-1); 
 ​
         /*2. 取出一帧数据: 从采集队列里面取出一个缓冲区*/
         buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;   /*视频捕获设备*/
         ioctl(uvc_video_fd,VIDIOC_DQBUF,&buff_info); /*从采集队列取出缓冲区*/
         index=buff_info.index;
         //printf("采集数据的缓冲区的编号:%d\n",index);
 ​
         /*3. 处理数据: YUV转RGB\显示到LCD屏*/
         //video_memaddr_buffer[index]; /*当前存放数据的缓冲区地址*/
 ​
         /*3.1 将YUV数据转为RGB格式*/
         yuv_to_rgb(video_memaddr_buffer[index],rgb_buffer,Image_Width,Image_Height);
 ​
         /*3.2 将RGB数据实时刷新到LCD屏幕上*/
         framebuffer_DisplayImages((800-Image_Width)/2,0,Image_Width,Image_Height,rgb_buffer);
         
         /*4. 将缓冲区再次放入采集队列*/
         buff_info.memory=V4L2_MEMORY_MMAP;  /*支持mmap内存映射*/
         buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/
         buff_info.index=index; /*缓冲区的节点编号*/
         ioctl(uvc_video_fd,VIDIOC_QBUF,&buff_info); /*根据节点编号将缓冲区放入队列*/
     }
     return 0;
 }