基于 Linux 设计的倒车雷达系统
- 2023-06-24 湖北
本文字数:6558 字
阅读完需:约 22 分钟
一、项目背景介绍
随着社会的不断发展,人们对于汽车的安全性要求越来越高,而倒车雷达系统就是为了增强汽车驾驶者的安全性而被广泛使用。在这种情况下,我们开发了一个基于 Linux 设计的倒车雷达系统,该系统可以采用迅为 4412 主控板,运行 Linux3.5 内核,使用 USB 摄像头、TFT 真彩显示屏、超声波测距模块和蜂鸣器等硬件。
二、创新点
本项目的创新点包括:
采用开源 Linux 系统:采用 Linux 系统,具有很好的可扩展性和灵活性,可以实现更为丰富和复杂的功能。
多个模块的协同工作:本项目涉及到了摄像头模块、超声波测距模块、处理模块、告警模块和显示模块等多个模块,这些模块需要协同工作才能实现完整的功能。
实现了告警功能:本项目采用了蜂鸣器来实现告警功能,根据距离调整 PWM 输出给出不同级别的告警声音,能够提醒驾驶者注意障碍物。
三、使用技术介绍
迅为 4412 主控板:本项目采用了迅为 4412 主控板,该主控板具有较高的性能和稳定性,能够满足系统的运行要求。
Linux 操作系统:本项目采用了开源的 Linux 操作系统,具有很好的可扩展性和灵活性,适合进行自定义开发。
V4L2 协议:V4L2 协议是 Linux 下的一个视频设备驱动程序接口,可以方便地实现 USB 摄像头的图像采集。
超声波测距模块:通过 GPIO 口与主控板相连,使用定时器产生超声波并计算与接收到回波之间的时间差,从而计算出与障碍物之间的距离。
PWM 输出控制:根据距离调整 PWM 输出给出不同级别的告警声音。
TFT 真彩显示屏:本项目采用了 TFT 真彩显示屏,能够实现高清晰度的图像显示。
系统管理模块:负责管理整个系统的启动、配置和错误处理等操作。例如,可以将系统的启动脚本写在/etc/rc.local 中,通过调用 shell 脚本来实现系统的初始化和启动。
四、系统架构
整个系统由以下几个模块组成:
摄像头模块:负责采集车尾环境图像,并传输给处理模块。
超声波测距模块:负责与障碍物之间的距离测量,并将结果传输给处理模块。
处理模块:负责视频显示、距离信息的处理、告警功能的实现。
告警模块:负责根据距离调整 PWM 输出给出不同级别的告警声音,并使用蜂鸣器输出告警信息。
显示模块:负责将处理模块生成的图像显示在 TFT 真彩显示屏上。
系统管理模块:负责管理整个系统的启动、配置和错误处理等操作。
五、功能设计
摄像头模块 由于 Linux 系统具有很好的驱动支持,因此可以直接使用 V4L2 协议来获取 USB 摄像头的图像。获取图像后,需要通过 DMA 方式将数据传输给处理模块进行处理。
超声波测距模块 超声波测距模块可以通过 GPIO 口与主控板相连,使用定时器产生超声波并计算与接收到回波之间的时间差,从而计算出与障碍物之间的距离。
处理模块 处理模块负责将摄像头采集到的图像和超声波测距模块测得的距离信息整合处理,并根据距离信息来控制告警模块。
告警模块 告警模块根据处理模块传递过来的距离信息来控制 PWM 输出,并使用蜂鸣器输出告警信息。
显示模块 显示模块负责将处理模块生成的图像显示在 TFT 真彩显示屏上,并实现显示屏的刷新和管理操作。
系统管理模块 系统管理模块负责管理整个系统的启动、配置和错误处理等操作。例如,可以将系统的启动脚本写在/etc/rc.local 中,通过调用 shell 脚本来实现系统的初始化和启动。
六、摄像头图像显示应用代码
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <pthread.h>
#include <linux/fb.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <dirent.h>
#include <stdlib.h>
#include <fcntl.h>
#include <poll.h>
#include <linux/videodev2.h>
#include <sys/time.h>
#include <assert.h>
#include <sys/select.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/fb.h>
/* 图片的象素数据 */
typedef struct PixelDatas {
int iWidth; /* 宽度: 一行有多少个象素 */
int iHeight; /* 高度: 一列有多少个象素 */
int iBpp; /* 一个象素用多少位来表示 */
int iLineBytes; /* 一行数据有多少字节 */
int iTotalBytes; /* 所有字节数 */
unsigned char *VideoBuf; //存放一帧摄像头的数据
//指向了存放摄像头数据的空间地址
}T_PixelDatas;
T_PixelDatas Pixedata; //存放实际的图像数据
/*
USB摄像头相关参数定义
*/
struct v4l2_buffer tV4l2Buf;
int iFd;
int ListNum;
unsigned char* pucVideBuf[4]; // 视频BUFF空间地址
void camera_pthread(void);
//LCD屏相关的参数
unsigned char *lcd_mem=NULL; /*LCD的内存地址*/
struct fb_fix_screeninfo finfo; /*固定形参*/
struct fb_var_screeninfo vinfo; /*可变形参*/
void LCD_Init(void);
void show_pixel(int x,int y,int color);
int main(int argc ,char *argv[])
{
if(argc!=2)
{
printf("./app /dev/videoX\n");
return -1;
}
LCD_Init(); //LCD屏初始化
camera_init(argv[1]); //摄像头设备初始化
//开始采集摄像头数据,并实时显示在LCD屏幕上
camera_pthread();
return 0;
}
//LCD屏初始化
void LCD_Init(void)
{
/*1.打开设备文件*/
int fd=open("/dev/fb0",O_RDWR);
if(fd<0)
{
printf("/dev/fb0设备文件打开失败!\n");
return;
}
/*2. 读取LCD屏的参数*/
ioctl(fd,FBIOGET_FSCREENINFO,&finfo);//固定参数
printf("映射的长度:%d\n",finfo.smem_len);
ioctl(fd,FBIOGET_VSCREENINFO,&vinfo);//可变参数,32位
printf("分辨率:%d*%d,%d\n",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel);
/*3. 映射LCD的地址到进程空间*/
lcd_mem=mmap(NULL,finfo.smem_len,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
if(lcd_mem==NULL)
{
printf("lcd_mem映射失败!\n");
return;
}
memset(lcd_mem,0xFFFFFFFF,finfo.smem_len);
}
/*画点*/
void show_pixel(int x,int y,int color)
{
unsigned long *show32 = NULL;
/* 定位到 LCD 屏上的位置*/
show32 =(unsigned long *)(lcd_mem + y*vinfo.xres*vinfo.bits_per_pixel/8 + x*vinfo.bits_per_pixel/8);
*show32 =color; /*向指向的 LCD 地址赋数据*/
}
//显示摄像头的数据
void Show_VideoData(int w,int h,unsigned char *rgb)
{
int i,j;
int x0,x=0,y=0;
int color; //颜色 值
unsigned char r,g,b;
x0=x;
for(i=0;i<h;i++)
{
for(j=0;j<w;j++)
{
b=rgb[0];
g=rgb[1];
r=rgb[2];
color=r<<16|g<<8|b; //合成颜色
show_pixel(x0++,y,color); //显示一个像素点
rgb+=3;
}
y++;
x0=x;
}
}
//摄像头设备的初始化
int camera_init(char *video)
{
int i=0;
int cnt=0;
//定义摄像头驱动的BUF的功能捕获视频
int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* 1、打开视频设备 */
iFd = open(video,O_RDWR);
if(iFd < 0)
{
printf("摄像头设备打开失败!\n");
return 0;
}
struct v4l2_format tV4l2Fmt;
/* 2、 VIDIOC_S_FMT 设置摄像头使用哪种格式 */
memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //视频捕获
//设置摄像头输出的图像格式
tV4l2Fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;
/*设置输出的尺寸*/
tV4l2Fmt.fmt.pix.width = 1000;
tV4l2Fmt.fmt.pix.height = 1000;
tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY;
//VIDIOC_S_FMT 设置摄像头的输出参数
ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);
//打印摄像头实际的输出参数
printf("Support Format:%d\n",tV4l2Fmt.fmt.pix.pixelformat);
printf("Support width:%d\n",tV4l2Fmt.fmt.pix.width);
printf("Support height:%d\n",tV4l2Fmt.fmt.pix.height);
/* 3、VIDIOC_REQBUFS 申请buffer */
/* 初始化Pixedata结构体,为转化做准备 */
Pixedata.iBpp =24;
//高度 和宽度的赋值
Pixedata.iHeight = tV4l2Fmt.fmt.pix.height;
Pixedata.iWidth = tV4l2Fmt.fmt.pix.width;
//一行所需要的字节数
Pixedata.iLineBytes = Pixedata.iWidth*Pixedata.iBpp/8;
//一帧图像的字节数
Pixedata.iTotalBytes = Pixedata.iLineBytes * Pixedata.iHeight;
Pixedata.VideoBuf=malloc(Pixedata.iTotalBytes); //申请存放图片数据空间
//v412请求命令
struct v4l2_requestbuffers tV4l2ReqBuffs;
memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
/* 分配4个buffer:实际上由VIDIOC_REQBUFS获取到的信息来决定 */
tV4l2ReqBuffs.count = 4; /*在内核空间里开辟4个空间*/
/*支持视频捕获功能*/
tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* 表示申请的缓冲是支持MMAP(内存映射) */
tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;
/* 为分配buffer做准备 */
ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
printf("tV4l2ReqBuffs.count=%d\n",tV4l2ReqBuffs.count);
for (i = 0; i < tV4l2ReqBuffs.count; i++)
{
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = i; // 0 1 2 3
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /*支持视频捕获*/
tV4l2Buf.memory = V4L2_MEMORY_MMAP; /*支持内存映射*/
/* 6、VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap */
ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
//映射空间地址
pucVideBuf[i] = mmap( /*返回用户空间的地址*/
0, /*表示系统自己制定地址*/
tV4l2Buf.length, /*映射的长度*/
PROT_READ, /*空间数据只读*/
MAP_SHARED, /*空间支持共享*/
iFd, /*将要映射的文件描述符*/
tV4l2Buf.m.offset /*从哪个位置开始映射,表示起始位置*/
);
printf("mmap %d addr:%p\n",i,pucVideBuf[i]);
}
/* 4、VIDIOC_QBUF 放入队列*/
for (i = 0; i <tV4l2ReqBuffs.count; i++)
{
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = i;
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP;
ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf); //放入队列 queue
}
/*5、启动摄像头开始读数据*/
ioctl(iFd,VIDIOC_STREAMON, &iType);
return 0;
}
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++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
if(z++)
{
z = 0;
yuyv += 4;
}
}
}
/*采集摄像头数据*/
void camera_pthread(void)
{
int error;
int cnt=0;
int i=0;
int ListNum;
/* 8.1、等待是否有数据 */
fd_set readfds;
while(1)
{
FD_ZERO(&readfds);
FD_SET(iFd,&readfds);
select(iFd+1,&readfds,NULL,NULL,NULL); /*检测文件描述符是否发生了读写事件*/
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //类型
tV4l2Buf.memory = V4L2_MEMORY_MMAP; //存储空间类型
/* 9、VIDIOC_DQBUF 从队列中取出 */
error = ioctl(iFd, VIDIOC_DQBUF, &tV4l2Buf); //取出一帧数据
ListNum = tV4l2Buf.index; //索引编号 0
//将YUV转换为RGB
yuv_to_rgb(pucVideBuf[ListNum],Pixedata.VideoBuf,Pixedata.iWidth,Pixedata.iHeight);
//在LCD屏显示图像
Show_VideoData(Pixedata.iWidth,Pixedata.iHeight,Pixedata.VideoBuf);
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = ListNum;
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP;
error = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf); /*将缓冲区再次放入队列*/
}
}
七、超声波测距驱动代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <linux/input.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/io.h>
static int chaoshengbo_irq=0;
#define GPB_CON 0x11400040
#define GPB_DAT 0x11400044
static u32 *gpb_con=NULL;
static u32 *gpb_dat=NULL;
/*
硬件连接:
输出脚: GPX1_0 ---ECHO
输入脚: GPB_7
*/
static void tiny4412_work_func(struct work_struct *work)
{
printk("GPB=%d\n",*gpb_dat&1<<7);
ktime_t my_time1,my_time2;
unsigned int i,j;
unsigned int time_cnt=0;
my_time1=ktime_get(); //获取当前时间
i=ktime_to_us(my_time1); //转 us
while(gpio_get_value(EXYNOS4_GPX1(0))){} //高电平卡住
my_time2=ktime_get(); //获取当前时间
j=ktime_to_us(my_time2); //转 us
printk("us:%d\n",j-i);
}
static DECLARE_WORK(caoshengbotiny4412_work,tiny4412_work_func);
/*
中断处理函数
*/
irqreturn_t irq_handler_chaoshengbo(int irq, void *dev)
{
schedule_work(&caoshengbotiny4412_work); /*正常情况下: 是在中断服务函数里面*/
return IRQ_HANDLED; /*表示中断已经处理过了*/
}
static void timer_function(unsigned long data);
static DEFINE_TIMER(timer_caoshengbo, timer_function,0,0);
static void timer_function(unsigned long data)
{
static u8 state;
state=!state;
if(state)*gpb_dat|=1<<7;
else *gpb_dat&=~(1<<7);
mod_timer(&timer_caoshengbo,jiffies+msecs_to_jiffies(500));
}
static int __init tiny4412_chaoshengbo_init(void)
{
int i,err;
/*获取中断号*/
chaoshengbo_irq=gpio_to_irq(EXYNOS4_GPX1(0));
printk("中断号:%d\n",chaoshengbo_irq);
/*外部中断注册*/
err=request_irq(chaoshengbo_irq,irq_handler_chaoshengbo,IRQ_TYPE_EDGE_RISING,"tiny4412_chaoshengbo",NULL);
if(err!=0)printk("中断注册失败!\n");
/*映射内存*/
gpb_con=ioremap(GPB_CON,4);
gpb_dat=ioremap(GPB_DAT,4);
/*配置模式*/
*gpb_con&=~(0xF<<4*7); //输出模式
*gpb_con|=0x1<<4*7;
mod_timer(&timer_caoshengbo,jiffies+msecs_to_jiffies(100));
return 0;
}
static void __exit tiny4412_chaoshengbo_exit(void)
{
free_irq(chaoshengbo_irq,NULL);
iounmap(gpb_con);
iounmap(gpb_dat);
del_timer(&timer_caoshengbo);
}
module_init(tiny4412_chaoshengbo_init); /*指定驱动的入口函数*/
module_exit(tiny4412_chaoshengbo_exit); /*指定驱动的出口函数*/
MODULE_LICENSE("GPL"); /*指定驱动许可证*/
版权声明: 本文为 InfoQ 作者【DS小龙哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/268c3b69a49fa0aace1776964】。文章转载请联系作者。
DS小龙哥
之所以觉得累,是因为说的比做的多。 2022-01-06 加入
熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域
评论