一. 摘要
这篇文章详细介绍了一个“多路信号采集系统”的开发过程。“多路信号采集系统”是一个可伸缩的信号采集系统,通道可以选择从0~100路不同的信号源。单个采集板都能够采集10路数据,用户可以根据自己的需求方便地扩展或者收缩信号通道数。本系统可以用于常见的民用或者工业现场监控、仪器仪表等数据采集场合。该系统基于Arm Context M3内核处理器实现,有基板和采集板两大部分组成,基板主要负责整个采集时序的控制,而采集板则完成真是的数据采集并将采集到的数据发送到数据总线,进而传输到主机端。数据传输采用了串口通信的方式(RS485),并采用Modbus协议实现,从而方便地实现了采集板地址的检索、数据量控制、以及CRC校验值确定等功能。软件系统则采用了固件库编程的方式,全程开发均使用C语言完成,从而为以后升级做好准备。开发使用了今日标企业工作平台以及Github代码托管平台相结合完成开发的方式,使用今日标企业工作平台管理项目开发流程,而使用Github则方便地实现了不同地区开发者协作开发的目的。而系统调试则选择了传统的调试方式,先进行单个功能模块测试,再测试系统功能,进而Burning实验。
二. 本文提纲
1. 摘要2. 本文提纲3. 项目起始4. 开发方式选择5. 系统构架6. 硬件设计7. 软件设计8. 系统调试9. 总结
三. 项目起始
该系统是应兰州交通大学自动化研究所(一下简称为研究所)的项目需求进行开发。项目开始的时候谈的最多的问题主要有两个,一个是“钱”的问题,还有一个就是项目需求了。至于钱最后的商定结果为,天佑电子有限公司(以下简称天佑)只负责产品研发,开发过程中所有的元器件、材料、以及测试系统的所有成本均由研究所承担,而天佑则只承担人力成本。最后研究所应该支付天佑XXX研发费用。由于以前有过很多次的合作经历了,所以谈判过程也比较顺利,也没有书面的合同约束双方。不过这也仅仅建立在双方已经有过很多次的合作经历,彼此都是很熟的人,项目本身技术上没有太大的复杂度,而且最终产品的应用场合也不会造成大的损失的基础之上。不过一般的项目合同是很重要的一个方面,合同不仅仅为双方的行为建立起了约束,而且如果项目失败等造成损失,双方也能够明确自己的责任。至于项目需求,则只有以下几点需求:
1. 信号通道数必须在50~100路之间(可伸缩)2. 单路信号误差限定在0.03V之内3. 数据传输使用485协议4. 数据协议遵循Modbus协议5. 传送数据中必须包含数据量以及CRC校验值6. 上电或者数据传送过程都需要有相应的指示灯指示其状态
项目本身是个很简单的项目,几乎没有技术上的难点。产品需求也一目了然,看了项目需求之后解决方案就已经呼之欲出了。经初步分析,整个设计过程中唯一可能需要仔细斟酌的也就是多个板子之间通信时的时序问题,事实证明,这部分到后面的确测试了挺长时间才实现功能,而且测试了好多边界条件才最终确定下来最后的最优化参数。
四. 开发方式选择
项目的开发方式选择了“今日标企业工作平台”和Github结合进行开发的方式。
在本次开发中使用金目标的目的在于管控项目开发流程,实现整个开发过程简单的文档化。虽然该开发过程与现在所倡导的敏捷开发模式相违背,但是考虑到目前我们的开发条件,最后还是决定使用传统的文档化开发模式。在本次开发中由于参与开发的主要人员在不同的地方(软件开发在江苏,而硬件开发在广东),造成了沟通上的诸多不便,而文档化则会改善一些沟通上的困难。将所有的开发过程都存放在一个公共平台上,大家可以随时查阅。这样也最大化地降低了沟通歧义所带来的项目风险。
众所周知,Github是一个不仅强大而且简单易用的分布式代码托管平台。对于不同地域开发人员之间的协作开发来说,拥有一个强大的代码共享平台至关重要。而Github刚好满足这些需求:
1. 多方开发人员同时参与一个工程的开发
2. 平台必须是分布式的,开发人员同时必须能编辑同一个文件
2. 整个调试过程中各个开发人员的代码必须要保持一致
有关这两个平台的使用方法,这里就不做介绍了,在各自的官网上都有很详细的教程。在这里只提供两个平台分别的链接。金目标的链接为:http://www.jingoal.com ,Github的链接为:https://github.com 。
五. 系统架构
系统架构
系统设计时考虑到可伸缩性,以及采集通道要求太多,故而将数据采集任务分给多个采集板去完成。每个采集板各自完成自己的采集任务,并将采集到的数据传送到数据总线。而具体的何时采集数据,以及何时传送数据到数据总线并交给PC段,则有Base Board控制。在这里设计的时候并没有将采集到的数据由Base Board传送到PC上,而是放到数据总线上直接由PC提取。这种设计是基于以下考虑,如果所有采集到的数据全都交由Base Board向上传送,则相当于多了一级路由,这样会降低整个系统的数据采集速率。也加重了Base Board的工作任务,使其控制逻辑变得复杂,严重违背“简单即为美”的编程原则。
六. 硬件设计
有关硬件设计的部分,由于我本人对于硬件不是很擅长,所以可能总结不好。故而请了我们团队中的另一位硬件工程师撰写。在这里我就只上两张设计的电路图。
采集板电路图
基板电路图
七. 软件设计
在本系统中软件设计分为两个部分,采集板软件设计和基板软件设计。下面分别就这两方面介绍整个软件开发的过程。
1. 采集板软件设计:
在确定了采集板功能的情况下再开始软件设计。本次设计中采用了“从两端到中间”的软件开发模式。所谓“从两端到中间的开发模式”是指在软件开发过程中,首先按照软件的架构层次习惯,将软件分为前端界面、中间逻辑控制层、以及底层的模型层(这是PC软件的开发框架)。而对于我们嵌入式软件来说,也可以分为类似的层。我在这次开发过程中,则是将整个软件分为:业务逻辑、中间件、以及底层驱动模型。而从两端到中间的开发则是指先开发底层驱动,然后再确定 业务逻辑 ,然后通过中间件将两个部分衔接起来,并进行测试的方法。
首先根据需求确定软件中需要使用到的底层硬件驱动分别有:AD、USART、DMA、GPIO、外部硬件中断。而在嵌入式系统中,一般还需要一些延时之类的功能,故而还需要提供一些延时函数的驱动。下图显示的是本次设计中用到的所有驱动,虽然开发过程选择了使用STM32官方提供的固件库编程方式,但是如果完全使用固件库中的函数,而没有经过封装。则主函数将会显得很臃肿,而且整个软件系统的业务逻辑和底层驱动也严重耦合在一起,不便于以后系统维护、升级,也会大大降低系统的可移植性。
这里的驱动设计,其实从本质上来讲的话只是对固件库中的一些函数或者是宏定义进行进一步的封装。例如ADC部分的驱动,在~/ADC/adc.c文件中只需要系统一个Adc_Init函数,在该函数中完成对CPU ADC资源的初始化。而对应的USART设备。不仅要提供初始化函数,还需要提供一些发送数据以及接收数据等的函数。而且为了调试方便期间,一般按照我个人的习惯的话还会添加一部分代码,用以支持printf函数,利用该函数通过串口向PC端,打印出一些程序执行的信息,这样会大大方便嵌入式软件调试。对于STM32来说,要支持printf函数只需要加入以下代码:
//加入以下代码,支持printf函数,而不需要选择use MicroLIB #if 1 #pragma import(__use_no_semihosting) //标准库需要的支持函数 struct __FILE { int handle; }; FILE __stdout; //定义_sys_exit()以避免使用半主机模式 _sys_exit(int x) { x = x; } //重定义fputc函数 int fputc(int ch, FILE *f) { while((USART1->SR&0X40)==0);//循环发送,直到发送完毕 USART1->DR = (u8) ch; return ch; } #endif
驱动文件列表
完成了驱动程序的设计之后,则设计业务逻辑。 业务逻辑 是指从用户需求的角度完成用户需要的功能的一部分代码。这部分代码一般都是指Main函数中的逻辑。在此次设计中主要的执行流程可以使用以下的图表示。
业务逻辑
最后设计的是中间件,中间件的设计不仅要考虑到业务逻辑的需求,更要兼顾底层驱动模型的实现方式。只有将这两者比较好地结合起来,最终才能得到比较理想的产品。
从底层往上层看,中间件是对底层驱动更高一级的封装,底层驱动只是实现独立的单个功能点。而通过中间件不仅实现了这些功能点,而且融入了一部分系统功能的成分在里面。从上层往底层看,中间件则可以理解成对于系统业务逻辑的细化。业务逻辑类似于将用户需求文本化或者“代码化”,从本质上来讲,它还是在用户需求一级。而通过中间件的作用,则将抽象的业务逻辑实现了具体化,成为对于处理器来说能够识别并执行的真实代码。
以上就是采集板的整个软件开发思路以及开发过程,这里的程序从其流程来看,的确是几乎没有复杂度。但是在这里提出了一些软件开发过程中的框架,一种分层机制。框架的出现将系统级的软件分成各个不同的模块,或者分为不同的层。从而实现了软件良好的可维护性,也大大方便了后期系统升级。对于软件框架的理解,我也只是一个初学者,在这里提出权当是抛砖引玉了吧。
1. 基板软件设计:
从软件的角度来讲,基板是整个系统时序的控制者。就仿佛人的大脑一般,它可以发出各种指令让系统的各个部分能够各司其职,而不会导致系统紊乱。本次设计中,基板主要的工作流程如下:
基板的主要工作流程
设计过程中基板中有一个唯一值得注意的地方就是板子复位后在线板的检测,在这个系统中挂在基板上的采集板的个数在0~10个之间是随意的,而控制板发送采集信号命令的时候应该只对当前系统中已经挂载的采集板发送。故而在设计之时,软件中设计了一个链表,大家知道链表是一个大小可以动态分配的数据结构,所以我们可以讲在线板的信息存储在这个链表当中,而每次需要发送控制命令的时候只要遍历整个链表,并发送控制命令即可。
八. 系统调试
在完成了系统硬件设计以及软件设计之后,接下来的主要任务就是单个模组测试以及系统测试。测试过程主要地可以分为以下几个部分:
1. 单个驱动功能点测试2. 采集板功能测试3. BaseBoard功能测试4. 系统功能测试5. 系统稳定性测试
下面分别介绍此次开发过程中针对前面5个测试点的具体实现过程。
1. 单个驱动功能点测试:
单个驱动功能点测试是指在测试单个功能块,比如在系统中用到的串口通信模块、GPIO模块、中断模块等。包括硬件电路测试和软件驱动测试。这部分的测试对于整个系统测试来说是一个基础测试,但也是至关重要的部分。只有保证了所有单个功能点能够正确地工作才使得后续系统逻辑无误成为可能。根据前面的介绍,在本次设计过程中,用到的所有驱动全都封装在相应的驱动文件中,并且单个功能点都非常独立。前面所有的驱动都能够独立出来形成一个完整的测试用例。而我的做法则是先创建一个通用的测试模板(一个没有实现任何功能只包含main函数的工程)。在测试过程中只需要分别将需要测试的驱动移植到该测试工程中,并且在main函数中调用驱动中提供的函数进行测试即可。
2. 采集板功能测试:
在本系统中,采集板的主要任务是根据基板的控制命令,完成采集数据的动作,并将采集到的数据按照之前的协定进行整理,然后传送到基板数据总线。所以对于基板的测试,则需要再使用一个模拟基板控制流程的电路板,或信号发生器之类的控制器。而我的做法是使用了一个51板,该板只完成产生一个定时跳变的功能。
采集板功能测试
3. BaseBoard功能测试
对于基板而言,测试的关键有两个方面:
1> 检测在线板,并将在线板信息保存在队列中
2> 按照队列中的信息,将控制信息发送到采集板中
其测试过程反而比较简单,只需要将测试的信息全部通过串口发送到PC段,根据PC端的显示信息即可判断是否实现了需要的功能。但是在测试的过程中发现了一个问题:串口要使用printf函数向上位机发送信息,则需要实现前面所示代码,但是这部分代码与工程中使用的malloc函数所在库stdlib冲突,故而产生了编译不过去的问题。也就是说在一个工程中这两个功能只能使用其中一个。而malloc函数是实现双向链表的关键,不能却去掉。所以我选择了自己实现一个向上位机发送字符串的方法。具体实现的代码如下:
void Send_Data(USART_TypeDef* usartx, uint16_t data) { USART_SendData(usartx, data); } void Send_String(USART_TypeDef* usartx, char *str) { while(*str != '