厚德载物

爱喝茶的家伙

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

   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 #include <SoftwareSerial.h>
  2 #include <Servo.h>
  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 }

 

posted on 2018-05-24 11:22  剑胆琴心2015  阅读(1296)  评论(0编辑  收藏  举报