1. 前言
倒車影像已經(jīng)是現(xiàn)在汽車的標(biāo)配功能了,基本很多車出廠都是360全景影像,倒車影像又稱泊車輔助系統(tǒng),這篇文章就采用Linux開(kāi)發(fā)板完成一個(gè)倒車影像的功能。
要完成倒車影像整個(gè)項(xiàng)目,需要準(zhǔn)備一個(gè)LCD屏,一個(gè)攝像頭,一個(gè)超聲波測(cè)距模塊,一個(gè)蜂鳴器;攝像頭采集車尾的實(shí)時(shí)畫(huà)面,在LCD屏上完成顯示,超聲波測(cè)距模塊用于測(cè)量車尾距離障礙物的距離,根據(jù)設(shè)置的距離跳轉(zhuǎn)PWM操作蜂鳴器實(shí)現(xiàn)報(bào)警提示。
當(dāng)前Linux開(kāi)發(fā)板采用友善之臂的Tiny4412開(kāi)發(fā)板,CPU是三星的EXYNOS4412,板子上帶有8G的EMMC,2G的DRR,運(yùn)行的Linux版本是3.5,根文件系統(tǒng)采用busybox制作,這個(gè)系統(tǒng)是精簡(jiǎn)的最小系統(tǒng)。
攝像頭采用USB免驅(qū)是攝像頭,所有不需要編寫(xiě)驅(qū)動(dòng),LCD屏是友善之臂自己的7寸電容觸摸屏,驅(qū)動(dòng)是官方內(nèi)核自帶的,也不需要編寫(xiě);剩下的超聲波模塊,蜂鳴器,需要自己填寫(xiě)驅(qū)動(dòng)。
2. 案例代碼
下面就將倒車影像項(xiàng)目的整個(gè)項(xiàng)目核心代碼依次展示出來(lái)。
2.1 聲波測(cè)距模塊
采用的超聲波模塊在淘寶上很容易買(mǎi)到,它的實(shí)物圖和測(cè)距原理如下:
整個(gè)模塊只需要接4根線,兩根電源線,一個(gè)輸出觸發(fā)腳,一個(gè)回波輸出腳。輸出腳是高電平有效,為了方便計(jì)算時(shí)間,驅(qū)動(dòng)代碼里需要將這個(gè)引腳注冊(cè)為中斷,設(shè)置為上升沿觸發(fā),當(dāng)收到回波時(shí),直接進(jìn)中斷調(diào)用工作隊(duì)列,在工作函數(shù)里阻塞等待引腳電平變低,然后高電平持續(xù)的計(jì)算時(shí)間,最后進(jìn)行換算就得到了距離。
驅(qū)動(dòng)代碼如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static unsigned int distance_irq; /*存放中斷號(hào)*/
static u32 *GPB_DAT=NULL;
static u32 *GPB_CON=NULL;
static u32 distance_time_us=0; /*表示距離的時(shí)間*/
static u8 distance_flag=0;
/*聲明等待隊(duì)列頭*/
static DECLARE_WAIT_QUEUE_HEAD(distance_wait_queue);
/*
工作隊(duì)列處理函數(shù):
*/
static void distance_work_func(struct work_struct *work)
{
u32 time1,time2;
time1=ktime_to_us(ktime_get()); /*獲取當(dāng)前時(shí)間,再轉(zhuǎn)換為 us 單位*/
/*等待高電平時(shí)間結(jié)束*/
while(gpio_get_value(EXYNOS4_GPX1(0))){}
time2=ktime_to_us(ktime_get()); /*獲取當(dāng)前時(shí)間,再轉(zhuǎn)換為 us 單位*/
distance_flag=1;
distance_time_us=time2-time1;
/*喚醒休眠的進(jìn)程*/
wake_up(&distance_wait_queue);
//printk("us=%d\n",time2-time1); /*us/58=厘米*/
}
/*靜態(tài)方式初始化工作隊(duì)列*/
static DECLARE_WORK(distance_work,distance_work_func);
/*
中斷處理函數(shù): 用于檢測(cè)超聲波測(cè)距的回波
*/
static irqreturn_t distance_handler(int irq, void *dev)
{
/*調(diào)度工作隊(duì)列*/
schedule_work(&distance_work);
return IRQ_HANDLED;
}
static void distance_function(unsigned long data);
/*靜態(tài)方式定義內(nèi)核定時(shí)器*/
static DEFINE_TIMER(distance_timer,distance_function,0,0);
/*內(nèi)核定時(shí)器超時(shí)處理函數(shù): 觸發(fā)超聲波發(fā)送方波*/
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);
}
/*修改定時(shí)器的超時(shí)時(shí)間*/
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 unsigned int distance_poll(struct file *file, struct poll_table_struct *table)
{
unsigned int mask = 0;
poll_wait(file,&distance_wait_queue,table);
if(distance_flag)
{
distance_flag=0;
mask|=POLL_IN;
}
return mask;
}
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,
.poll=distance_poll,
.release=distance_release
};
/*定義雜項(xiàng)設(shè)備結(jié)構(gòu)體*/
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. 根據(jù)GPIO口編號(hào),獲取中斷號(hào)*/
distance_irq=gpio_to_irq(EXYNOS4_GPX1(0));
/*3. 注冊(cè)中斷*/
err=request_irq(distance_irq,distance_handler,IRQ_TYPE_EDGE_RISING,"distance_device",NULL);
if(err!=0)printk("中斷注冊(cè)失敗!\n");
else printk("中斷:超聲波測(cè)距驅(qū)動(dòng)安裝成功!\n");
/*4. 修改定時(shí)器超時(shí)時(shí)間*/
mod_timer(&distance_timer,jiffies+msecs_to_jiffies(100));
/*雜項(xiàng)設(shè)備注冊(cè)*/
misc_register(&distance_misc);
return 0;
}
static void __exit tiny4412_distance_dev_exit(void)
{
/*5. 注銷中斷*/
free_irq(distance_irq,NULL);
/*6. 停止定時(shí)器*/
del_timer(&distance_timer);
/*7. 取消IO映射*/
iounmap(GPB_DAT);
iounmap(GPB_CON);
/*注銷雜項(xiàng)設(shè)備*/
misc_deregister(&distance_misc);
printk("中斷:超聲波測(cè)距驅(qū)動(dòng)卸載成功!\n");
}
module_init(tiny4412_distance_dev_init);
module_exit(tiny4412_distance_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("tiny4412 wbyq");
復(fù)制代碼
2.2 蜂鳴器驅(qū)動(dòng)代碼
蜂鳴器需要根據(jù)測(cè)量的距離調(diào)整不同的頻率,以告訴駕駛員,后方障礙物的距離。
static int s3c_pwm_suspend(struct platform_device *pdev, pm_message_t state)
{
struct pwm_device *pwm = platform_get_drvdata(pdev);
pwm->period_ns = 0;
pwm->duty_ns = 0;
?
return 0;
}
?
static int s3c_pwm_resume(struct platform_device *pdev)
{
struct pwm_device *pwm = platform_get_drvdata(pdev);
unsigned long tcon;
?
/* Restore invertion */
tcon = __raw_readl(S3C2410_TCON);
tcon |= pwm_tcon_invert(pwm);
__raw_writel(tcon, S3C2410_TCON);
?
return 0;
}
?
static struct platform_driver s3c_pwm_driver = {
.driver = {
.name = "s3c24xx-pwm",
.owner = THIS_MODULE,
},
.probe = s3c_pwm_probe,
.remove = __devexit_p(s3c_pwm_remove),
.suspend = s3c_pwm_suspend,
.resume = s3c_pwm_resume,
};
?
static int __init pwm_init(void)
{
int ret;
?
clk_scaler[0] = clk_get(NULL, "pwm-scaler0");
clk_scaler[1] = clk_get(NULL, "pwm-scaler1");
?
if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) {
printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__);
return -EINVAL;
}
?
ret = platform_driver_register(&s3c_pwm_driver);
if (ret)
printk(KERN_ERR "%s: failed to add pwm driver\n", __func__);
?
return ret;
}
復(fù)制代碼
2.3 應(yīng)用層框架代碼
應(yīng)用層使用了兩個(gè)線程:
線程1實(shí)時(shí)讀取USB攝像頭的圖像,在LCD屏上實(shí)時(shí)顯示,免驅(qū)攝像頭框架V4L2有一套標(biāo)準(zhǔn)的ioctl指令,可以對(duì)攝像頭進(jìn)行配置,比如:輸出分辨率、幀率、格式等。然后再通過(guò)mmap映射攝像頭采集緩沖區(qū)到應(yīng)用層,最后通過(guò)poll函數(shù)監(jiān)聽(tīng)攝像頭的數(shù)據(jù),讀取實(shí)時(shí)顯示。
線程2采集超聲波測(cè)量的距離距離,根據(jù)測(cè)量的距離調(diào)整PWM占空比,控制蜂鳴器的頻率。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "framebuffer.h"
#define PWM_DEVICE "/dev/pwm" /*PWM方波設(shè)備文件*/
#define DISTANCE_DEVICE "/dev/tiny4412_distance" /*超聲波測(cè)距設(shè)備文件*/
#define UVC_VIDEO_DEVICE "/dev/video15" /*UVC攝像頭設(shè)備節(jié)點(diǎn)*/
#define GET_US_TIME 0x45612 /*獲取超聲波測(cè)量的距離: ioctl命令*/
#define PWM_IOCTL_SET_FREQ 1 /*控制PWM方波頻率: ioctl命令*/
#define PWM_IOCTL_STOP 0 /*停止PWM方波輸出: ioctl命令*/
int distance_fd; /*超聲波設(shè)備的文件描述符*/
int pwm_fd; /*PWM方波設(shè)備的文件描述符*/
int uvc_video_fd; /*UVC攝像頭設(shè)備文件描述符*/
int Image_Width; /*圖像的寬度*/
int Image_Height; /*圖像的高度*/
unsigned char *video_memaddr_buffer[4]; /*存放攝像頭映射到進(jìn)程空間的緩沖區(qū)地址*/
/*
函數(shù)功能: 用戶終止了進(jìn)程調(diào)用
*/
void exit_sighandler(int sig)
{
//停止PWM波形輸出,關(guān)閉蜂鳴器
ioctl(pwm_fd,PWM_IOCTL_STOP,0);
close(pwm_fd);
close(distance_fd);
exit(1);
}
/*
函數(shù)功能: 讀取超聲波數(shù)據(jù)的線程
*/
void *distance_Getpthread_func(void *dev)
{
/*1. 打開(kāi)PWM方波驅(qū)動(dòng)*/
pwm_fd=open(PWM_DEVICE,O_RDWR);
if(pwm_fd<0) //0 1 2
{
printf("%s 設(shè)備文件打開(kāi)失敗\n",PWM_DEVICE);
/*退出線程*/
pthread_exit(NULL);
}
/*2. 打開(kāi)超聲波測(cè)距設(shè)備*/
distance_fd=open(DISTANCE_DEVICE,O_RDWR);
if(distance_fd<0) //0 1 2
{
printf("%s 設(shè)備文件打開(kāi)失敗\n",DISTANCE_DEVICE);
/*退出線程*/
pthread_exit(NULL);
}
/*3. 循環(huán)讀取超聲波測(cè)量的距離*/
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厘米: 安全區(qū)域*/
{
//停止PWM波形輸出,關(guān)閉蜂鳴器
ioctl(pwm_fd,PWM_IOCTL_STOP,0);
}
else if(data>100) /*100厘米: 警告區(qū)域*/
{
printf("警告區(qū)域!\n");
ioctl(pwm_fd,PWM_IOCTL_SET_FREQ,2);
}
else /*小于<100厘米: 危險(xiǎn)區(qū)域*/
{
printf(" 危險(xiǎn)區(qū)域!\n");
ioctl(pwm_fd,PWM_IOCTL_SET_FREQ,10);
}
//ioctl(pwm_fd,PWM_IOCTL_SET_FREQ,pwm_data);
/*倒車影像: 測(cè)距有3個(gè)檔位*/
}
}
/*
函數(shù)功能: UVC攝像頭初始化
返回值: 0表示成功
*/
int UVCvideoInit(void)
{
/*1. 打開(kāi)攝像頭設(shè)備*/
uvc_video_fd=open(UVC_VIDEO_DEVICE,O_RDWR);
if(uvc_video_fd<0)
{
printf("%s 攝像頭設(shè)備打開(kāi)失敗!\n",UVC_VIDEO_DEVICE);
return -1;
}
/*2. 設(shè)置攝像頭的屬性*/
struct v4l2_format format;
memset(&format,0,sizeof(struct v4l2_format));
format.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*表示視頻捕獲設(shè)備*/
format.fmt.pix.width=800; /*預(yù)設(shè)的寬度*/
format.fmt.pix.height=480; /*預(yù)設(shè)的高度*/
format.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; /*預(yù)設(shè)的格式*/
format.fmt.pix.field=V4L2_FIELD_ANY; /*系統(tǒng)自動(dòng)設(shè)置: 幀屬性*/
if(ioctl(uvc_video_fd,VIDIOC_S_FMT,&format)) /*設(shè)置攝像頭的屬性*/
{
printf("攝像頭格式設(shè)置失敗!\n");
return -2;
}
Image_Width=format.fmt.pix.width;
Image_Height=format.fmt.pix.height;
printf("攝像頭實(shí)際輸出的圖像尺寸:x=%d,y=%d\n",format.fmt.pix.width,format.fmt.pix.height);
if(format.fmt.pix.pixelformat==V4L2_PIX_FMT_YUYV)
{
printf("當(dāng)前攝像頭支持YUV格式圖像輸出!\n");
}
else
{
printf("當(dāng)前攝像頭不支持YUV格式圖像輸出!\n");
return -3;
}
/*3. 請(qǐng)求緩沖區(qū): 申請(qǐng)攝像頭數(shù)據(jù)采集的緩沖區(qū)*/
struct v4l2_requestbuffers req_buff;
memset(&req_buff,0,sizeof(struct v4l2_requestbuffers));
req_buff.count=4; /*預(yù)設(shè)要申請(qǐng)4個(gè)緩沖區(qū)*/
req_buff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*視頻捕獲設(shè)備*/
req_buff.memory=V4L2_MEMORY_MMAP; /*支持mmap內(nèi)存映射*/
if(ioctl(uvc_video_fd,VIDIOC_REQBUFS,&req_buff)) /*申請(qǐng)緩沖區(qū)*/
{
printf("申請(qǐng)攝像頭數(shù)據(jù)采集的緩沖區(qū)失敗!\n");
return -4;
}
printf("攝像頭緩沖區(qū)申請(qǐng)的數(shù)量: %d\n",req_buff.count);
/*4. 獲取緩沖區(qū)的詳細(xì)信息: 地址,編號(hào)*/
struct v4l2_buffer buff_info;
memset(&buff_info,0,sizeof(struct v4l2_buffer));
int i;
for(i=0;i> 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. 注冊(cè)將要捕獲的信號(hào)*/
signal(SIGINT,exit_sighandler);
/*2. 創(chuàng)建線程: 采集超聲波測(cè)量的距離*/
pthread_t threadID;
pthread_create(&threadID,NULL,distance_Getpthread_func,NULL);
pthread_detach(threadID); //設(shè)置分離屬性
/*3. 初始化攝像頭*/
UVCvideoInit();
/*4. 初始化LCD屏*/
framebuffer_Device_init();
/*5. 循環(huán)采集攝像頭的數(shù)據(jù)*/
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; /*表示當(dāng)前緩沖區(qū)的編號(hào)*/
unsigned char *rgb_buffer=NULL;
/*申請(qǐng)空間:存放轉(zhuǎn)換之后的RGB數(shù)據(jù)*/
rgb_buffer=malloc(Image_Width*Image_Height*3);
if(rgb_buffer==NULL)
{
printf("RGB轉(zhuǎn)換的緩沖區(qū)申請(qǐng)失敗!\n");
exit(0);
}
while(1)
{
/*1. 等待攝像頭采集數(shù)據(jù)*/
poll(&fds,1,-1);
/*2. 取出一幀數(shù)據(jù): 從采集隊(duì)列里面取出一個(gè)緩沖區(qū)*/
buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*視頻捕獲設(shè)備*/
ioctl(uvc_video_fd,VIDIOC_DQBUF,&buff_info); /*從采集隊(duì)列取出緩沖區(qū)*/
index=buff_info.index;
//printf("采集數(shù)據(jù)的緩沖區(qū)的編號(hào):%d\n",index);
/*3. 處理數(shù)據(jù): YUV轉(zhuǎn)RGB\顯示到LCD屏*/
//video_memaddr_buffer[index]; /*當(dāng)前存放數(shù)據(jù)的緩沖區(qū)地址*/
/*3.1 將YUV數(shù)據(jù)轉(zhuǎn)為RGB格式*/
yuv_to_rgb(video_memaddr_buffer[index],rgb_buffer,Image_Width,Image_Height);
/*3.2 將RGB數(shù)據(jù)實(shí)時(shí)刷新到LCD屏幕上*/
framebuffer_DisplayImages((800-Image_Width)/2,0,Image_Width,Image_Height,rgb_buffer);
/*4. 將緩沖區(qū)再次放入采集隊(duì)列*/
buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap內(nèi)存映射*/
buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*視頻捕獲設(shè)備*/
buff_info.index=index; /*緩沖區(qū)的節(jié)點(diǎn)編號(hào)*/
ioctl(uvc_video_fd,VIDIOC_QBUF,&buff_info); /*根據(jù)節(jié)點(diǎn)編號(hào)將緩沖區(qū)放入隊(duì)列*/
}
return 0;
};i++)>
審核編輯:湯梓紅
-
lcd
+關(guān)注
關(guān)注
34文章
4411瀏覽量
167101 -
Linux
+關(guān)注
關(guān)注
87文章
11230瀏覽量
208931 -
倒車影像
+關(guān)注
關(guān)注
1文章
42瀏覽量
5719
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論