2016年,曾经尝试用51单片机制作一台循迹避障小车,已经准备好了大部分零件,也组装好了车身,试机时发现两个轮子的转速不一样,而且转速太快导致小车在瓷砖地面上打滑,当时一筹莫展,只好就此搁置. 这反映了我在硬件选型上的失误(当然自身水平也很有限哦). 前几天看了一篇安卓控制Arduino小车的文章 ,这篇文章简要介绍了使用Ardiuno制作小车并用APP控制小车的基本流程,可以看出使用Arduino制作控制小车要容易很多,因为它的很多功能硬件和软件都已经模块化, 每个模块对应一块PCB和一个标准化的软件库/接口,这样编写程序就清晰很多,有问题也比较容易调试。
下面就介绍一下嵌入式系统的硬件选型, 嵌入式系统是指以应用为中心、以计算机技术为基础,软件硬件可裁剪,适应应用系统对功能、可靠性、成本、体积和功耗严格要求的专用计算机系统,我们就从功能、可靠性、成本、体积和功耗来分析智能小车的硬件选型:
功能:
能够实现循迹、避障,手机通过蓝牙可以操控小车,而Arduino UNO的基本指标如下
Arduino Uno
- 处理器: ATmega328 (8位 CPU, 16MHz 时钟频率, 2KB SRAM, 32KB 闪存)
- 特性: 14 个数字 I/O 口, 6 个模拟输入口, 6个PWM口,可更换处理器设计
- 尺寸: 75 x 55mm
- 价格: $30
这是我目前程序使用的Flash和内存空间的情况
项目使用了 7168 字节,占用了 (22%) 程序存储空间。最大为 32256 字节。
全局变量使用了388字节,(18%)的动态内存,余留1660字节局部变量。最大为2048字节。显然它能够配合相关模块和程序实现功能
可靠性:Arduino Uno采用标准化的设计和制造,可靠性比较高
成本:Arduino Uno成本显然是比较低的,一般人都可以负担得起
体积:75 x 55mm的面积对智能小车来说显然是能够承受的
功耗:智能小车的功耗主要是电机驱动和运转产生的功耗,Arduino Uno功耗所占比重是很低的,而且现在可以使用18650高容量电池
这里转载一篇有关的文章
嵌入式系统硬件设计流程
1)详细理解设计需求,从需求中整理出电路功能模块和性能指标要求。
2)根据功能和性能需求制定总体设计方案,对CPU进行选型,CPU选型有以下几点要求: a)性价比高; b)容易开发:体现在硬件调试工具种类多,参考设计多,软件资源丰富,成功案例多; c)可扩展性好。
3)针对已经选定的CPU芯片,选择一个与我们需求比较接近的成功参考设计,一般CPU生产商或他们的合作方都会对每款CPU芯片做若干款开发板进行验证,比如AMCC的PPC440EP就有yosemite和bamboo两款开发版。厂家最后公开给用户的参考设计图虽说不是产品级的东西,也应该是经过严格验证的,否则将会影响到他们的芯片推广应用。纵然参考设计的外围电路有可推敲的地方,CPU本身的管脚连接使用方法也绝对是值得我们信赖的,当然如果万一出现多个参考设计某些管脚连接方式不同的情况,我们可以细读CPU芯片手册和勘误表,或者找厂商确认。另外在设计之前,最好能外借或者购买一块选定的参考板进行软件验证,如果软件验证没有问题,那么硬件参考设计也是可以信赖的。但要注意一点,现在很多CPU都有若干种启动模式,我们要自己选一种最适合的启动模式,或者做成兼容设计。
4)根据需求对外设功能模块进行元器件选型,元器件选型应该遵守以下原则: a)普遍性原则:所选的元器件要是被广泛使用验证过的,尽量少使用冷门、偏门芯片,减少开发风险。 b)高性价比原则:在功能、性能、使用率都相近的情况下,尽量选择价格比较好的元器件,降低成本。 c)采购方便原则:尽量选择容易买到、供货周期短的元器件。 d)持续发展原则:尽量选择在可预见的时间内不会停产的元器件。 e)可替代原则:尽量选择pin to pin兼容芯片品牌比较多的元器件。 f)向上兼容原则:尽量选择以前老产品用过的元器件。 g)资源节约原则:尽量用上元器件的全部功能和管脚。
5)对选定的CPU参考设计原理图外围电路进行修改。修改时对于每个功能模块都要找至少3个相同外围芯片的成功参考设计,如果找到的参考设计连接方法都是完全一样的,那么基本可以放心参照设计,但即使只有一个参考设计与其他的不一样,也不能简单地按少数服从多数的原则,而是要细读芯片数据手册,深入理解那些管脚含义,多方讨论,联系芯片厂技术支持,最终确定科学、正确的连接方式,如果仍有疑义,可以做兼容设计。这是整个原理图设计过程中最关键的部分,我们必须做到以下几点: a)对于每个功能模块要尽量找到更多的成功参考设计,越难的应该越多,成功的参考设计是“前人”的经验和财富,我们理当借鉴吸收,站在“前人”的肩膀上,也就提高了自己的起点。 b)要多向权威请教、学习,但不能迷信权威,因为人人都有认知误差,很难保证对哪怕是自己最了解的事物总能做出最科学的理解和判断,开发人员一定要在广泛调查、学习和讨论的基础上做出最科学正确的决定。 c)如果是参考已有的老产品设计,设计中要留意老产品有哪些遗留问题,这些遗留问题与硬件哪些功能模块相关,在设计这些相关模块时要更加注意推敲,不能机械照抄原来设计,比如我们老产品中的IDE经常出问题,经过仔细斟酌,广泛讨论和参考其他成功设计,发现我们的IDE接口有两个管脚连线方式确实不规范。还有,针对FGPI通道丢失视频同步信号的问题,可以在硬件设计中引出硬件同步信号管脚,以便进一步验证,更好发现问题的本质。
6)硬件原理图设计还应该遵守一些基本原则,这些基本原则要贯彻到整个设计过程,虽然成功的参考设计中也体现了这些原则,但因为是“拼”出来的原理图,所以我们还要随时根据这些原则来设计审查原理图,这些原则包括: a)数字电源和模拟电源分割。 b)数字地和模拟地分割,单点接地,数字地可以直接接机壳地(大地),机壳必须接地,以保护用护人身安全。 c)保证系统各模块资源不能冲突,例如:同一I2C总线上的设备地址不能相同等。 d)阅读系统中所有芯片的手册(一般是设计参考手册),看它们未用的输入管脚是否需要做外部处理,是要上拉、下拉,还是悬空,如果需要上拉或下拉,则一定要做相应处理,否则可能引起芯片内部振荡,导致芯片不能正常工作。 e)在不增加硬件设计难度的情况下尽量保证软件开发方便,或者以较小的硬件设计难度来换取更多方便、可靠、高效的软件设计,这点需要硬件设计人员懂得底层软件开发调试,要求较高。 f)功耗问题,设计时尽量降低功耗。 g)产品散热问题,可以在功耗和发热较大的芯片增加散热片或风扇,产品机箱也要考虑这个问题,不能把机箱做成保温盒,电路板对“温室”是感冒的。还要考虑产品的安放位置,最好是放在空间比较大,空气流动畅通的位置,有利于热量散发出去。
7)硬件原理图设计完成之后,设计人员应该按照以上步骤和要求首先进行自审,自审后要有95%以上的把握和信心,然后再提交给他人审核,其他审核人员同样按照以上要求对原理图进行严格审查,如发现问题要及时进行讨论分析,分析解决过程同样遵循以上原则和步骤。
8)只要开发和审核人员都能够严格按照以上要求进行电路设计和审查,我们就有理由相信,所有硬件开发人员设计出的电路板第一版成功率都会很高的,所以我提出以下几点: a)设计人员自身应该保证原理图的正确性和可靠性,要做到设计即是审核,严格自审,不要把希望寄托在审核人员身上,设计出现的任何问题应由设计人员自己承担,其他审核人员不负连带责任。 b)其他审核人员虽然不承担连带责任,也应该按照以上要求进行严格审查,一旦设计出现问题,同样反映了审核人员的水平、作风和态度。 c)普通原理图设计,包括老产品升级修改,原则上要求原理图一版成功,最多两版封板,超过两版将进行绩效处罚。 d)对于功能复杂,疑点较多的全新设计,原则上要求原理图两版内成功,最多三版封板,超过三版要进行绩效处罚。 e)原理图封板标准为:电路板没有任何原理性飞线和其他处理点。
9)制定上述规范的目的和出发点是为了培养硬件开发人员严谨、务实的工作作风和严肃、认真的工作态度,增强他们的责任感和使命感,提高工作效率和开发成功率,保证产品质量。希望年轻的硬件开发人员能在磨练中迅速成长起来。对于复杂的PCB板设计,如高频多层板一般都请专人布线,因为复杂的PCB板涉及电磁兼容和电磁干扰方面的问题,这是一门高深的学问,除了一些基本的注意事项外还有一些特殊的防护措施,比如设计时使用一些专门的电磁兼容芯片等,感兴趣的读者可以看一些电磁理论方面的书籍。
这是我初步完成的小车外观(暂时使用DC12V电源):
附录:小车源代码, 源文件、安卓蓝牙遥控程序和相关资料参见https://pan.baidu.com/s/1WyoaKsiQ_LMk6e_UdRCs6w
1 #include2 #include 3 4 #define IN1 10 5 #define IN2 11 6 #define IN3 12 7 #define IN4 13 //电机驱动脚 8 9 #define ENA 5 10 #define ENB 6 //PWM调速脚 11 12 #define TrigPin 2 13 #define EchoPin 4 //超声波测距脚 14 15 #define sensorPin1 0 16 #define sensorPin2 1 17 #define sensorPin3 2 18 #define sensorPin4 3 // 红外循迹探头,使用A0~A3模拟输入引脚 19 20 Servo myservo; // 创建舵机对象来控制舵机 21 SoftwareSerial BT(8, 9); //软串口,蓝牙接口 22 char Direction; // 用来存储蓝牙发送过来的指令 23 int pos = 90; // 用来存储舵机位置的变量 24 float dist,dist1,dist2; 25 bool mode = 0; // mode等于零时为手动模式,等于一时为循迹或避障模式 26 27 void Forward() // 前进 28 { 29 MotorA(001, 150); 30 MotorB(001, 156); 31 } 32 void Backward() // 后退 33 { 34 MotorA(002, 150); 35 MotorB(002, 156); 36 } 37 void TurnLeft() // 向左转 38 { 39 MotorA(004, 150); 40 MotorB(004, 150); 41 } 42 void TurnRight() // 向右转 43 { 44 MotorA(005, 150); 45 MotorB(005, 150); 46 } 47 void Stop() // 停止 48 { 49 MotorA(003,0); 50 MotorB(003,0); 51 delay(100); 52 } 53 float Distance() //超声波测距 54 { 55 float distance; // 用来存储小车距周边障碍物距离 56 digitalWrite(TrigPin, LOW); 57 delayMicroseconds(2); 58 digitalWrite(TrigPin, HIGH); //高低电平发一个短时间脉冲去TrigPin 59 delayMicroseconds(10); 60 digitalWrite(TrigPin, LOW); 61 distance = pulseIn(EchoPin, HIGH) / 58.0; //采样高电平宽度并折算成距离 62 return distance; 63 } 64 void MotorA(char Action, int speed1) { // 电机A在不同运动模式下的设定 65 if (Action == 001) { 66 digitalWrite(IN1, HIGH); 67 digitalWrite(IN2, LOW); 68 analogWrite(ENA, speed1); 69 } 70 if (Action == 002) { 71 digitalWrite(IN1, LOW); 72 digitalWrite(IN2, HIGH); 73 analogWrite(ENA, speed1); 74 } 75 if (Action == 003) { 76 digitalWrite(IN1, LOW); 77 digitalWrite(IN2, LOW); 78 } //001正转,002反转,003停止,下同 79 if (Action == 004) { 80 digitalWrite(IN1, HIGH); 81 digitalWrite(IN2, LOW); 82 analogWrite(ENA, speed1); 83 } 84 if (Action == 005) { 85 digitalWrite(IN1, LOW); 86 digitalWrite(IN2, HIGH); 87 analogWrite(ENA, speed1); 88 } //004前进左转,005前进右转,下同 89 if (Action == 006) { 90 digitalWrite(IN1, LOW); 91 digitalWrite(IN2, LOW); 92 } 93 if (Action == 007) { 94 digitalWrite(IN1, LOW); 95 digitalWrite(IN2, HIGH); 96 analogWrite(ENA, speed1); 97 } //006后退左转,007后退右转,下同 98 } 99 100 void MotorB(char Action, int speed2) { // 电机B在不同运动模式下的设定101 if (Action == 001) {102 digitalWrite(IN3, HIGH);103 digitalWrite(IN4, LOW);104 analogWrite(ENB, speed2);105 }106 if (Action == 002) {107 digitalWrite(IN3, LOW);108 digitalWrite(IN4, HIGH);109 analogWrite(ENB, speed2);110 }111 if (Action == 003) {112 digitalWrite(IN3, LOW);113 digitalWrite(IN4, LOW);114 } //001正转,002反转,003停止,下同115 if (Action == 004) {116 digitalWrite(IN3, LOW);117 digitalWrite(IN4, HIGH);118 analogWrite(ENB, speed2);119 }120 if (Action == 005) {121 digitalWrite(IN3, HIGH);122 digitalWrite(IN4, LOW);123 analogWrite(ENB, speed2);124 } //004前进左转,005前进右转,下同125 if (Action == 006) {126 digitalWrite(IN3, LOW);127 digitalWrite(IN4, HIGH);128 analogWrite(ENB, speed2);129 }130 if (Action == 007) {131 digitalWrite(IN3, LOW);132 digitalWrite(IN4, LOW);133 } //006后退左转,007后退右转,下同134 }135 void setup() {136 Serial.begin(9600);137 BT.begin(9600);138 pinMode(TrigPin, OUTPUT);139 pinMode(EchoPin, INPUT);140 for (int i = 10; i <= 13; i++){141 pinMode(i, OUTPUT);142 }143 myservo.attach(3); // 把连接在引脚3上的舵机赋予舵机对象144 145 myservo.write(85); //设置舵机初始位置146 delay(1000);147 }148 149 void loop() {150 if (mode == 0) // 小车默认为手动模式并读取蓝牙输入,在循迹或避障模式设定后不再读取蓝牙输入151 Direction = BT.read();152 153 switch (Direction) {154 case 'A': // 高速前进155 MotorA(001, 180);156 MotorB(001, 188);157 break;158 159 case 'C': // 前进160 MotorA(001, 150);161 MotorB(001, 156);162 break;163 164 case 'E': // 高速后退165 MotorA(002, 180);166 MotorB(002, 188);167 break;168 169 case 'G': // 后退170 MotorA(002, 150);171 MotorB(002, 156);172 break;173 174 case 'Z': // 停止175 MotorA(003,0);176 MotorB(003,0);177 break;178 179 case 'L': // 向前左转180 MotorA(004, 150);181 MotorB(004, 150);182 break;183 184 case 'R': // 向前右转185 MotorA(005, 150);186 MotorB(005, 150);187 break;188 189 case 'M': // 向后左转190 MotorA(006, 0);191 MotorB(006, 150);192 break;193 194 case 'N': // 向后右转195 MotorA(007, 150);196 MotorB(007, 0);197 break;198 199 case 'P': // 避障模式200 mode = 1;201 Direction = 'P';202 dist = Distance(); // 读取前方距离 205 if (dist < 15 )206 {207 Backward();208 delay(100);209 Stop(); //距离障碍物小于15cm时立即后退并刹车,然后测量左右距离 210 myservo.write(0);211 delay(300);212 dist1 = Distance(); 215 myservo.write(180);216 delay(600);217 dist2 = Distance(); 220 myservo.write(85); // 测量完后舵机回归正前方位置221 delay(300);222 if (dist1 > dist2) { // 向远离障碍物的方向转弯后继续前进223 TurnRight();224 delay(150);225 Forward();226 }227 else {228 TurnLeft();229 delay(150);230 Forward();231 }232 } 233 else if (dist > 300) // 若测量数据过大为异常情况,此时尝试调整车身方位,此处程序为实验性质,可能不是最好的方案234 {235 TurnLeft();236 delay(200);237 Forward();238 }239 else 240 {241 Forward(); // 正常情况向前242 }243 break;244 245 case 'Q': // 循迹模式246 mode = 1;247 Direction = 'Q';248 int num1 = analogRead(sensorPin1); // 收集四个红外感应头的输出电压 251 int num2 = analogRead(sensorPin2); 254 int num3 = analogRead(sensorPin3); 257 int num4 = analogRead(sensorPin4); 260 if (num1 > 500) { // 最左侧的感应头在跑道上方,车身应向左转261 TurnLeft();262 delay(100);263 Stop();264 }265 else if (num4 > 500) { // 最右侧的感应头在跑道上方,车身应向右转266 TurnRight();267 delay(100);268 Stop();269 }270 else if (num2 > 500 || num3 > 500) // 中间两个感应头至少有一个在跑道上方则继续前进271 Forward();272 else {273 Stop(); //注意发生此种情况往往是因为小车冲出跑道,274 Backward(); //因此往后退100毫秒,这样一般都能重新275 delay(100); //找回跑道.276 Stop();277 }278 }279 }