前言
尽管每辆汽车都有后视镜,但不可避免地都存在一个后视镜的盲区,倒车雷达则可一定程度帮助驾驶员扫除视野死角和视线模糊的缺陷,提高驾驶安全性。上一节已经分析清倒车雷达的语音模块(上一节),本节将深入分析测距模块的设计。
一、倒车雷达的发展
第0代倒车雷达:“倒车请注意”!只要司机挂上倒档,它就会响起。(然并卵)
第1代倒车雷达:在距车1.5~1.8m处有障碍物,蜂鸣器就会工作,距离越近蜂鸣器越急促。(没有显示,考验司机耳力)
第2代倒车雷达:数码管显示距离数字,3色波段绿、黄、红分别表示安全、警告、危险。(可视化,但不美观)
第3代倒车雷达:采用液晶屏绘制一个汽车,实时动态显示周围障碍物距离,抗干扰不强。(美观,性能下降)
第4代倒车雷达:综合前面几代的杂交体,性能和美观都有提高,价格稍高。(综合体)
第5代倒车雷达:整合影音系统,土豪专用,后有摄像头。(高端机型)
ps:左为第2代产品,右为第5代产品
二、雷达测距原理
即使是上面的第5代土豪套餐也是基于超声波测距的,和某些采用多探头或摄像头进行三维重建进行刹车安全制动方案想比,只是小巫见大巫。这里只介绍基于40kHz的超声波测距方案。
>_<: (为何一般都选取40kHz?)不要拿蝙蝠导航来忽悠我(我小时候就知道了)!那时候我们知道蝙蝠采用超声波能在黑暗的山洞里自由飞翔,这里的超声波是频率超过20kHz的声波。超声波的波长很短因此具有一些类似光线的物理性质:①超声波类似光线,遵循几何光学的规律,具有反射、折射现象,也能聚焦,因此可以利用这些性质进行测量、定位、探伤等处理,在传播中与声速相同;②超声波波长很短,与发射器、接收器的尺寸相当,由发射器发射出来的超声波不向四面八方发散,而成为方向性很强的波束,波长俞短方向性越强,因此超声用于探伤、水下探测,有很高的分辨能力,能分辨出非常微小的缺陷或物体;③能够产生窄脉冲,为了提高探测精度和分辨率。要求探测信号的脉冲极窄,但是一般脉冲宽度时波长的几倍,超声波波长短,因此可以作为窄脉冲信号发生器;④功率大,超声波能够产生并传递强大的能量。声波作用于物体时,物体的分子也要随着运动,其振频率和作用的声波频率一样,频率越高,分子运动速度越快,物体获得的能量正比于分子运动速度的平方。超声频率高,固可给出大频率。正是由于声波在空气中传播,随频率上升衰减加大&&20K以下不是超声波&&频率越高方向性越强,所以取个折中40kHz。
>_<: (超声波测距原理)声波在其介质中被定义为纵波。当声波收到尺寸大于其波长的目标物体阻挡会发生反射,反射波成为回声。假设声波在介质中传播的速度是已知的,而且声波从声源到达目标而后返回声源的时间可以测量得到,那就可以计算从声波到目标的距离,这便是超声波测距的原理。
ps:还记得初中做过的那个傻瓜级的问题嘛
ps:而现在高大上的东西也不过如此
>_<: (一体探头和非一体探头)优点是:单探头计算距离公式比较简单,而双探头要一些修正;探头多了,经济成本和故障概率变大;单探头一般是防水的。缺点是:灵敏度很差,当测量距离很远时,为了增大发射功率,需采用特殊形式的大功率发射换能器,但这样的换能器接收灵敏度一般很低;需要收发隔离控制,否则会烧毁电路;盲区较多。
三、测距电路方案对比
采用回波的原理进行距离的测量的电路方案相差不大,本质都是由40kHz方波生成电路、矩形波放大电路、回波检测电路,主要区别在于各个厂家采用不同方法进行实现(当然,如上一篇中所说还有温度检测模块)。
>_<: (方案一:行家说这是打擦边球,不过亲测效果不错)该方案一般采用普通单片机如51系列产生40kHz方波,采用反相器进行矩形波放大,回波检测采用CX20106A芯片。之所以说是擦边球是因为:超声波接收电路主要由CX20106A和超声波换能器构成,CX20106A是一款红外的专用芯片,考虑到红外遥控常用的载波频率38KHz与测距的超声波频率40KHz较为接近,故利用它制作超声波检测接收电路。而行家们一般自己设计标准的40kHz的检测电路(想比这个擦边球,自己设计就麻烦许多啦)!
在我的自制超声波平面定位仪的章节里(第一篇、第二篇、第三篇、第四篇)已经详细介绍:
(发送部分)这里采用51单片机产生40kHz的方波,通过CD4011BE进行反向,如果仅仅如此大家会发现测距的量程很短,于是采用MAX232对信号进行放大(这样效果就非常好了!!!)
(接收部分)这里采用CX20106A芯片,也就是不正规的方案,不过效果不错~
>_<: (方案二:采用专用芯片GM3101)
(概述)GM3101是专用于倒车雷达的超声波测距芯片,该芯片提供4路超声波探头的驱动,并根据超声波特性和倒车雷达的使用环境进行了一系列智能化处理,在保证超声波测距精确性基础上,更加强了报警功能的准确性和实用性。测试结果编码后采用双线差分方式输出,提高了信号传输的抗干扰性。GM3101可为倒车雷达系统提供最简单的单芯片控制方案,替代现有的单片机控制方案。 该芯片的优势在于尽可能地为倒车雷达系统提高集成度,减少外围元件。同时该芯片的功能满足高端和通用性的要求,用户利用该组芯片既可以生产高性能的整机产品,还可以灵活设置其产品的报警方式。 全硬件方式实现系统功能, 既降低了用户的开发难度, 更对系统性能有了显著的提高。[QFP44 封装]
(芯片特性)① 电源电压:5V,工作环境温度:-40℃ ~+85℃;② 四路超声波探头接口,探头发送驱动信号5V@2mA;③ 报警信号编码输出,报警信号包括:各探头检测到的障碍物距离危险等级信号、最近障碍物方位信号、最近障碍物距离信号及附加消息,信号电平5V;④ 检测结果输出周期80ms;⑤ 具有防声波衍射误报处理,提高报警信号的准确性;⑥ 具有环境适应功能,提高报警功能的实用性;⑦ 具有智能识别功能,可以忽略小物体,防止误报警;⑧ 报警信号输出采用双线差分方式,提高抗干扰性;⑨ 带防扒车报警功能
(结构性能)GM3101提供4路超声波探头接口,芯片通过探头发送和接收超声波信号,根据发送和接收的时间差计算障碍物的距离,输出相应报警信号。报警信号编码后采用双线差分方式输出,输出信号的内容包括:各探头检测到的障碍物距离的危险等级、最近障碍物的方位、最近障碍物的距离值和附加消息。最大输出距离为 3.15 米,输出精度为 0.05 米。
(工作周期)芯片接通电源后,探头驱动引脚向超声波探头发送驱动信号,驱动超声波探头发送超声波信号,驱动信号发送完毕后芯片等待信号返回;探头接收到超声波信号后,将信号送入芯片,进行信号放大处理,记录信号发送和接收的时间差,根据此时间差计算障碍物距离,控制报警信号输出。超声波探头驱动采用分时顺序的驱动方式,即依次对 4 个探头轮流进行驱动,一个探头的工作周期内要包括发送和接收两种操作。4 个探头检测完成构成一个检测周期。若前一探头在本工作周期内没有接收到返回的超声波信号,则芯片也转入控制下一个探头的工作。
(工作模式选择)本芯片具有倒车模式和扒车模式,可以通过MODE引脚设置:低电平或悬空为倒车模式;高电平为扒车模式
(倒车模式数据格式)报警信号以数据包形式输出,每个数据包3个字节:
- 第一字节高四位为起始标志,用于说明此报警数据是倒车模式下的数据还是扒车模式下的数据,倒车模式是“0101”,扒车模式是“1010”。第一字节的低两位用于输出附加消息,输出数据指示1或4探头是否进入环境适应模式,S1表示探头1是否进入环境适应模式,“1”表示进入环境适应模式,“0”表示正常倒车模式;S4 表示探头4是否进入环境适应模式,“1”表示进入环境适应模式,“0”表示正常倒车模式。第四位 SX1 和第三位SX0 表示最近障碍物的方位,00 表示是探头1方向,01表示是探头2方向,10表示是探头3方向,11 表示是探头4 方向。
- 第二个字节:如图SXA和SXB表示X号探头检测到的障碍物的危险等级,危险等级分为安全、警告、危险、停车4 级,分别用 00、01、10、11 表示。例如第二字节数据为“10010000”,表示第一个探头检测到危险状态,第二个探头检测到警告状态,第三和第四个探头为安全状态。
- 第三字节:输出最近障碍物的距离值,数据格式如图所示,DA1和DA0表示最近障碍物距离的第一位数据,按BCD编码,最大值为3;DB0~DB3表示最近障碍物距离的第二位数据,按BCD编码,最大值为9;DC0 表示第三位数据,0表示0,1表示5。其中最高位默认为 1。
(扒车模式数据格式)在防扒车功能状态下,芯片只检测 0.6 米距离内有无物体。防扒车模式下数据输出周期也是 80ms。
- 再造轮子之网易彩票-第二季(IOS 篇 by sixleaves) - sixleaves 阅读原文»
02-彩票项目第二季
2.封装SWPTabBar方式一
接着我们思考如何进行封装。前面已经将过了为什么要封装, 和封装达到的效果。这里我们主要有两种封装方式,分别是站在不同的角度上看待问题。虽然角度不同但是内部最核心的思想还是一样的,就是屏蔽控件内部是如何构造的细节, 为外界提供简单容易理解的接口。方式一: SWPTabBar屏蔽所有内在细节,只需提供要创建的控件个数。就可以创建出SWPTabBar的大体框架。接着有几个方法可以用来设置TabBar内部按钮的数据。简单来说只需提供1. 设置按钮个数。2. 设置按钮数据的接口。
注意: 那么我们如何保证接口的健壮性, 要知道如果有用户反复或多次设置按钮个数,那么我们要做到的效果是重新构造TabBar新的内部,而不能造成按钮的重复累加。这也是我们设计接口时候要考虑的一点,该接口是否方便使用, 时候多次使用会出错等等许多细节。并不是所设计接口就是随便的提供个方法的调用这么简单,这需要经验的积累。
首先看下面代码和注释:
-(void)viewDidLoad {
[super viewDidLoad];
// 创建自定义的tabBar控件
SWPTabBar * tabBar = [[SWPTabBar alloc] init];
tabBar.frame = self.tabBar.frame;
// 设置控件内部的按钮个数
tabBar.numberOfBarButton = 5;
// 控件代理对象, 用于监听对象
tabBar.delegate = self;
// 盖在原来tabBar之上
[self.view addSubview: tabBar];
}
看如上代码, 我们的意图很明显,就是用我们自定义的SWPTabBar盖在UITabBar上,造成一种以假乱真的效果。当然你也可以选择把UITabBar删了,在覆盖上去,减少不必要的渲染。接着我们就要关注内部细节的实现。这才是我们要掌握的重点。先看如下主线
分析:
1.首先我们可以选择在initWithFrame中添加控件。为什么不在init呢,因为用户可能会直接调用initWithFrame方法,这样就不正确了。但是init方法其实还会调用initWithFrame方法,所以我们重写initWithFrame方法是最保险的方式。
2.必须在确定了SWPTabBar的尺寸之后再计算其子控件的位置和尺寸, 所以我们重写layoutSubViews方法。
分析中的1和2代码分别如下:
-(instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame: frame]) {
[self setup]
}
return self;
}
-(void)setup {
for (int i = 0; i < self.numberOfBarButton; i++) {
SWPButton * button = [[SWPButton alloc] init];
// 设置按钮不同状态的图片
forState: UIControlStateNormal];
forState: UIControlStateSelected];
// 为每个按钮绑定tag, 方便后面切换为不同的UINavigationController
button.tag = i;
// 添加监听
;
// 设置初始选中按钮
if ( 0 == i) {
[self buttonClicked: button];
}
[self addSubview: button];
}
// 设置断言, 控件个数为按钮个数
assert(self.subviews.count == self.numberOfBarButton);
}
快速的浏览一下这段代码,抓住起重点功能理解即可。为什么我们要抽出setup把添加构造控件内部的代码都放在这, 其一、是为了提高代码的复用。因为现在我们只是在initWithFrame中构造,当我们可用可能会提供xib的方式进行构造,在xib方式中,我们一般在awakeFromNib中进行控件的构造,你可以把它理解为相当于xib的初始化方法。其二、是为了在出错的时候方便调试,一般一个函数或者方法的功能超过了12行以上不利于后面项目的维护,当然这也不能定死的看。像有的方法,刚好是20行已经是一个功能,而且也不能在抽取,这时候不要死脑筋非得把该方法进行拆分。拆分的是为了复用和调试。代码不难理解,有疑问的可以给我留言。我们主要掌握的是整个的思路。具体细节需要你们自己进行代码的编写,遇到问题在解决,那才有所意义,不要在意细节。
以下代码为重写layoutSubViews实现内部子控件的位置尺寸的确定。
- (void)layoutSubviews {
[super layoutSubviews];
// 按钮的尺寸, Y坐标值
CGFloat buttonW = self.frame.size.width / self.numberOfBarButton;
CGFloat buttonH = self.frame.size.height;
CGFloat buttonY = 0.0;
// 遍历按钮,设置尺寸、位置
for (int i = 0; i < self.numberOfBarButton; i++) {
CGFloat buttonX = i * buttonW;
SWPButton * button = self.subviews;
button.frame = CGRectMake(buttonX, buttonY, buttonW, buttonH);
button.backgroundColor = ;
}
}
分析:好了运行程序,自定义的SWPTabBar是不是不正常?为什么对应的按钮个数,无法显示出来。仔细思考下,不难发现。我们是在创建完SWPTabBar之后,才来设置按钮的个数。也就是说此时SWPTabBar已经构建后了,我们此时设置的个数根本就没有用!。那么怎么解决,我是通过重写numberOfButtons的setter方法,将setup方法放在setter中。这样,我们就可以再设置按钮个数的时候,再来构造该控件的内部。此时发现了setup的好处没。入果你把这些代码直接方法initWithFrame中, 那么现在就必须先复制,将initWithFrame删除,在粘贴到setter方法中。如果有多个自定义的控件,一个个这么做,我想你会疯了的。问题:我们需要关注一个细节,什么不是所不要在意细节!该在意的要在意,不该在意的要忽略,要看关注点!就是如果用户多次调用这个setter方法, 会造成按钮的重复添加。这不是我们要的效果。所以我们需要在这之前移除SWPTabBar内部所有的控件。再进行添加。所以我实现了 clear方法,具体删除细节看代码,思路很简单:逐个遍历,删除。
修改后的代码如下
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame: frame]) {
}
return self;
}
- (void)setNumberOfBarButton:(NSUInteger)numberOfBarButton {
_numberOfBarButton = numberOfBarButton;
// 先删除存在的按钮
[self clear];
// 先添加按钮
[self setup];
}
- (void)clear {
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
}
效果图如下:
点击跳转按钮的效果图
3.改进我们知道, UITabBarController中的几个子控制器是UINavagationController,当点击下面SWPTabBar控件中的按钮时候,进行子控制器的切换, 当点击UINavigationController上面的按钮进行跳转时候,实际上是UINavigationController对其子控制器进行压栈操作。现在我们想实现的效果是, 当UINavigationController切换其子控制器的时候, 新的控制器不显示出UITabBar, 我们可以通过SB中操作来勾选新控制器的Hide Bar Buttom …来对UITabBar进行隐藏, 然而我们自定义的SWPTabBar并不是UITabBar的子控件而是其兄弟控件, 所以设置的效果是作用在UITabBar上,又因为SWPTabBar比较后面添加, 盖在了上面, 所以是看不到效果的。此时我们只需要换种思路, 把SWPTabBar直接加进UITabBar控件中, 并完全覆盖它即可实现这样的效果。
修改的代码如下:(关注第6行和第12行,相信不难理解)
- (void)viewDidLoad {
[super viewDidLoad];
SWPTabBar * tabBar = [[SWPTabBar alloc] init];
tabBar.frame = self.tabBar.bounds;
tabBar.numberOfBarButton = 5;
tabBar.delegate = self;
[self.tabBar addSubview: tabBar];
}
思考:手动 VS 代码
我们的确可以通过勾选Hide Bar Buttom …来隐藏UITabBar,但是当控制器达到一定数量时候, 这种做法的做事效率太低, 这就好比快速排序和冒泡排序,这两种排序在排序个数很少的时候, 冒泡排序取得的时间效率比快速排序还低, 但是当操作某个值后,快速排序有十分好的时间效率。依次类比, 我们也要知道代码的方式, 这样遇到熟练一多的情况, 可以帮助我们提高项目的开发速度。说了太多废话,仁者见仁,智者见智。
实现具体细节如下:
1.通过自定义的SWPNavigationController重写管理其子控制器栈结构的入栈方法。来拦截push操作
,在push之前, 设置隐藏底部的UITabBar。
2.调用父类的入栈操作,完成父类自己的事情(将UIViewController入栈)。
代码如下:
3.到sb中分别更改UINavigationController的关联类
// SWPNavigationController
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
viewController.hidesBottomBarWhenPushed = YES;
[super pushViewController:viewController animated:animated];
}
改进后点击跳转按钮的效果图
可能你会发现我一直都没讲怎么实现SWPTabBar中如何切换到不同的UINavigationController,这个我们放在下一节介绍,现在我们只需要关注以上的重点,并理解之即可。
1.自定义类中,继承 + 重写拦截了要调用的方法。
2.UINavigationController与UITabBar管理子控制器的方式
3.如何自定义控件(setup、layoutSubViews)
4.如何更加全面的考虑设计的接口
本文链接:再造轮子之网易彩票-第二季(IOS 篇 by sixleaves),转载请注明。
没有评论:
发表评论