文章目录
- The-Little-Embedded-System
- 作者:Xian.Chen
前言
本书适合有一些嵌入式入门基础的学习者阅读。希望阅读完本小书,能够解答大家这些问题
- 硬件布线最基本的准则
- 不带RTOS的嵌入式系统如何更好地开发
- 嵌入式软件的分层设计怎么做
- 在设计控制器时,被控系统的简化动态模型怎么获得
- 传统的PID控制器设计在工程上到底怎么实施,如何仿真测试和写代码
- Matlab/Simulink怎么可以帮助提高嵌入式控制器的开发和测试效率
- 如何简单快速地做嵌入式控制器故障分析和错误排查,等等
智能车系统介绍
1.从智能车说起
本小书是以全国大学生智能车比赛作为案例来展开,这是我大学时候的最爱,也是目前自己带的学生创新实验室主要的比赛项目,所以站在这个角度给大家简单说一下如何构建一个基本的嵌入式系统,不求实现的多么完美,但求简洁而明白。
智能车比赛主要任务是做赛道寻线跟踪处理,用摄像头采集前方赛道图像,实现车速和转向的控制,达到跟踪赛道行驶的效果,如果类比人开车的话,摄像头就相当于司机的眼睛,图像处理和控制相当于老司机,转速控制相当于加减油门和刹车,转向控制相当于方向盘,这样对应理解了吧。
智能车整体结构的俯视图和侧视图如图1所示,后轮电机驱动,电池后放,摄像头中间放置,舵机改造为立式。

整个智能车系统里面涉及几部分:机械设计,硬件设计,嵌入式平台软件,控制算法软件与图像处理软件,本小书将会重点对后三部分详细展开介绍。这里要说一点,虽然本小书不探讨机械,但是整个智能车机械部分是非常重要的部分,或者可以这样说,机械调教决定了车的理论最高车速,而控制软件部分只决定实际车速,由于受限于自己自动化的专业背景,机械这方面不能过多展开。如果想深入学习机械,请大家阅读相关的专业书籍。
2.整体系统结构
智能车的整体结构如图2所示,上面是软件部分,主要包括图像处理,转速控制和转向控制,下面是硬件机械部分,主要包括电机,驱动,编码器,舵机,摄像头。整个系统的信号流和控制结构都可以从图中看出来,获取图像进行处理得到加减速指令和转向指令,下发给转速控制器和转向控制器,转向控制器负责控制舵机实现路径跟踪,转速控制器负责两个后轮的转速闭环控制。
-
机械与硬件设计(电源,编码器,电机驱动,摄像头)
-
嵌入式平台软件(系统结构,分层设计,模块设计)
-
控制算法软件(转速PI控制器,Matlab仿真,转向控制器设计)
-
图像处理软件(Matlab与C语言混合编程,图像处理与验证)
-
系统测试与分析(串口辅助调试,IO辅助测试,Matlab可视化数据分析)

3.经验之谈
一个嵌入式产品都会涉及到机械,硬件和软件,需要三部分的协作才能够完成,如果把做这个比赛看成一个项目的话,那就要考虑时间上的安排,这里要注意三者时间上的关系,机械如果有定制件的话,迭代周期会是1-2个月,硬件的迭代周期一般是1-2周,软件的迭代周期是1-2天,务必要清楚这一点,用到的机械上大部件(电机,舵机,摄像头等),必须提前采购好,做好充分的备料,这玩意如果出了问题,妥妥地托一个星期没商量,所以在满足性能和可用性的前提下,尽量简化机械与硬件的设计,做好保护措施。同时也要提前安排好时间,比如PCB发出去之前,最好所有用到的元器件要买齐,不然PCB板回来了,根本焊不出一个完整板子。
系统分层设计
1.分层与模块化
说起分层,让我想起了大学刚毕业去的那家小公司,当时自己维护空调控制板,代码是前辈做的,功能相当棒,但是当我看到代码的那一刻,差点吐血,因为整个代码就两个文件shuikongtiao.c和shuikongtiao.h,里面的变量和函数定义全部用汉语拼音,哈哈哈,一万只草泥马从心中飞过。我擦,终于见识到了国产一线小厂的实力水平。看到代码的那一刻,就在想,这玩意居然好使,写代码的人是爽了,维护者怎么搞呀,一个.c文件上万行。当然这些都是内心戏,说出来,前辈肯定把我废了。
我大学时学C语言,刚开始码代码就是一个main.c然后用一个main函数搞定所有的事,比如下面:
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char**argv)
{
//定义变量,balabala
int a,b;
int sum;
//输入数据或者读取文件
scanf("%d %d",&a,&b);
//处理逻辑
sum=a+b;
//打印输出
printf("%d+%d=%d",a,b,sum);
return 0;
}
这样的单文件单函数处理一些比如简单计算,文件读写,代码行不超过两个屏还不错,当代码超过2屏(大概150行),就要开始切分模块分割函数了,于是main.c变成了下面的样子。
#include<stdio.h>
#include<stdlib.h>
struct data{
int a;
int b;
}
int read_data(char*filename,struct data*d)
{
//读取数据
}
int process_data(struct data*d)
{
//处理数据和逻辑
}
int print_data(struct data*d)
{
//输出数据
}
int main(int argc,char**argv)
{
//定义变量,balabala
struct data d;
read_data("input.dat",&d)
process_data(&d);
print_data(&d);
return 0;
}
通过将部分功能模块化抽离出函数,原本上千行的代码被切割为几个50-200行的代码,既方便阅读,又方便处理,随着功能继续完善,我们会产生不同的数据处理方式,比如添加,删除,修改,查看,查找,排序等,这时候我们就需要把处理数据的部分单独拿出来成为一个独立的模块,于是我们产生了新的模块process_data.c和process_data.h,其中.c文件负责模块的代码实现,.h负责模块的对外接口声明,其他的模块也类似,于是我们的代码变成了下面几个文件main.h,process_data.c和process_data.h,read_data.c和read_data.h,print_data.c和print_data.h。
//process_data.h
#ifndef __PROCESS_DATA_DEF__
#def __PROCESS_DATA_DEF__
//数据元素
struct data{
int a;
int b;
}
typedef struct data* dat;
//数据链表
struct data_list{
struct data d;
dat next;
}
typedef struct data_list* dat_list;
extern int new_list (dat_list dl);
extern int add_data (dat_list dl,dat d);
extern int update_data(dat_list dl,int index,dat d);
extern int delete_data(dat_list dl,int index);
extern dat select_data(dat_list dl,char* cmd);
extern int sort_data (dat_list dl);
extern int search_data(dat_list dl,dat d);
#endif
//process_data.c
#include "process_data.h"
int new_list (dat_list dl)
{
}
int add_data (dat_list dl,dat d)
{
//添加数据
}
int update_data(dat_list dl,int index,dat d)
{
//更新数据
}
int delete_data(dat_list dl,int index)
{
//删除数据
}
dat select_data(dat_list dl,char* cmd)
{
//查找数据
}
int sort_data (dat_list dl)
{
//排序数据
}
int search_data(dat_list dl,dat d)
{
//查找数据
}
#endif
这时候的main.c就把process_data,read_data,print_data包含进来,即可以使用该模块,main.c的代码进一步缩减,框架和结构更清晰明了。
#include <stdio.h>
#include <stdlib.h>
#include "process_data.h"
#include "read_data.h"
#include "print_data.h"
int main(int argc,char**argv)
{
//定义变量,balabala
struct data d;
dat_list dl;
//输入数据
new_list(dl);
while(read_data("input.dat",&d) != 0)
add_data(&d);
//一系列的数据处理过程
select_data(dl,d);
//个性化显示数据
print_data(&d);
return 0;
}
随着系统功能进一步复杂,输入设备会有各种各样,输出设备与模式也会有各种各样的适配,为了控制系统的复杂度,会进一步进行分层,整体的进化流程就如图1所示。

其实说到底,最初其分成几个函数,到后面的模块化,再到最后的分层设计,都是在简化系统的复杂度,做到局部可控,这样才能hold住全场,让我们同一时刻只关注有限的信息量,毕竟都是人类,谁能一下子接受那么多code,更何况是凌乱的呢。善待code,善待自己,请从模块和分层开始。其实分层不是绝对的完美,所有的分层都会带来效率的降低,比如额外增加的函数调用时间损耗,但是为了可读性和可维护性,牺牲一点效率又能怎么样呢。不过千万不要过度分层,那是在装逼,不是在设计。
2.整个系统的总体设计
智能车系统的模块与分层划分,总体上分为三层,如图2所示。
- 控制与图像层(转速与转向控制器,图像处理)
- 嵌入式平台层(信号采集,器件驱动,任务调度)
- 硬件与机械层(舵机,电机,硬件驱动,编码器,电源,摄像头,巴拉巴拉)
这三层划分,在一般的项目中正好对应四类工程师,控制与图像层对应控制与算法工程师,嵌入式平台层对应嵌入式软件工程师,硬件与机械层,对应嵌入式硬件工程师和机械设计工程师。如果要想实现一个完备的嵌入式系统产品,需要凑齐这四类人才才能够有备无患。

控制与图像层
系统中图像处理模块图如图3所示,主要实现图像的处理,寻找中线,以及与Matlab2011a和VS2010配合实现对算法的快速仿真验证,大大提高开发效率,后文会重点介绍这里。
- imCom:图像处理的公用模块
- imProc:图像处理找中线和计算方向偏差的算法实现
- imType:自定义数据类型
- imCar:与Matlab的接口模块,用来快速批量验证算法

系统中控制算法部分的模块图如图4所示,主要负责实现转速和转向控制,其中转速控制会结合Matlab/Simulink 进行仿真,寻找合理的PI控制参数,后面会详细展开如何设计PI控制器。
- ControlVar:所有的共享全局变量
- ControlParam:所有的全局配置参数
- ControlGraphTask:图像和方向控制任务模块
- ControlSpeedTask:速度控制任务模块
Control子模块介绍:
- EIT_PID:PID控制器模块
- EIT_SpeedL:左轮速度控制器
- EIT_SpeedR:右轮速度控制器

嵌入式平台层
嵌入式平台层,负责整个嵌入式软件系统的初始化,信号采集以及驱动执行,模块结构如图5所示,其中本小书会详细介绍EITLIb库中的电机驱动与编码器和摄像头采集部分。
- CarDisplay:显示模块
- CarSystem:系统初始化模块
- CarTest:主循环模块
- IntHandler:中断处理模块
- Board:Vcan山外的K60核心板库
- EITLib:自定义的硬件驱动库
- Chip:Vcan山外实现的 K60的部件库
- CMSIS:CMSIS支持库

硬件与机械层
硬件采用山外的K60核心板,其他部分,控制核心板和驱动电源板都是自制。模块结构如图6所示,其中主要的是Power,Camera,Motor,Sensor,LCD和Key模块图。

机械部分在ch2再做介绍。
到现在为止,对整个智能车系统有了一个总体的了解,下面我们会分模块进行详细的介绍。
机械与硬件设计部分
1.硬件布线
当初大学画电路板的时候,啥玩意都不懂,记得当初直接用面包板焊接,虽然好使,但其丑无比。后来工作也画过一些PCB,但是始终不得要领,以为能把线连上连对就万事大吉了。后来阅读了一些电子电路和硬件的书籍,有了自己的一点点体会,虽然不多,但是应付一般场合足够了。很简单的物理知识就可以理解,至于高手搞通信高频布线,要考虑群延时,分布电感电容,信号完整性分析,我觉得大家以后真的玩高级硬件的话,可以再深入,这里就算是一个简单入门吧。
这里我就先画一个非常简单的电路图,做一个简单的计算,大家就会明白。
比如一个电源给两个器件供电,一个工作电流10mA,一个工作电流10A,我们看下面图1中两种接线方式有啥区别?????

很多同学会说,不都是两个器件并联吗,不都是电源电压吗???电路原理的课上说啦,并联电压相等,所以上面的两张图,完全一个样子嘛,能有啥区别???!!!!
这里我要提醒大家啦,注意连线,那都是铜线,不是超导体,拿起小本子要记住啦,铜线也有阻抗,PCB布线也有阻抗。考虑进来,之后的图就是下面这样子图2所示。

有的同学看到图2,会说,才20m欧,能有啥大事嘛。。。。大家要记住,我们这里还没说电源电压多少V呢,如果是1000V的话,那当然没事,但是如果电源是7V电压呢???你想想两种布局方式,1A器件与10A器件的静态工作电压有什么差别??
| 左侧布局 | 右侧布局 |
|---|---|
| 1A器件电压:7-(1+10)*(0.02*2)=6.56V | 1A器件电压:7-10*0.02*2-1*0.02*4=6.52V |
| 10A器件电压:7-10*0.02*4-1*0.02*2=6.16V | 10A器件电压:7-10*0.02*2-1*0.02*2=6.56V |
只通过一个简单的计算,我们就发现,右侧布局明显好于左侧布局,就因为10A电流在线路上产生了更大的线损,所以越靠近电源越好。这时候我们就得到这个结论,功率越大的器件,越靠近电源供电的话,那对整个系统工作的影响就越小。
于是我们的电路布局图就成为这个样子图3所示,大功率和小功率分布布局,大功率的地和电源尽可能从靠近电源的正负极直接引线,这样大功率器件对其他小功率器件的影响能够降到最小。

下面我的电路继续升级,加入了PWM数字开关器件,还有一些小的器件,大家有没有想过,这样玩,会不会有啥问题。。。

如果说第一条只用到了电路的欧姆定律,那这一条就要用到电磁感应原理,大家跟我的思路想哈,PWM要不停地开关,那0.5A电流就是交变的电流,交变的电流线圈会在整个线路环里产生交变的磁场,那是不是说所有包含在电源到0.5APWM器件的环路里的所有器件信号都会被这个交变磁场影响,因为交变的磁场会在其内部线路里又产生感性电动势和感应电流。这里只是0.5A,那如果10A电流也是开关器件呢,那简直瞬间干扰死那些线路里的小信号器件。于是我们就有了第二条定律,大功率器件的电源和地最好贴着走,不要包含小功率的器件。
于是图4就会进化到图5,虽然丑了点,是效果好呀!!!

所以最终结论就是如下:
- 尽可能大功率的器件靠近电源接线
- 尽可能开关功率器件尤其是大功率的开关器件,电源和地包络器件越少越好。
铭记上面两条法则,应该能应付一般的PCB布线了。
智能车电路为例,整体电路分为三部分,电机驱动,舵机,控制电路,总体布线如图6所示,三大部分彻底分开,尤其是电机驱动这块,流大电流。

最低0.47元/天 解锁文章
1073

被折叠的 条评论
为什么被折叠?



