引言
嵌入式系统体积小、成本低。用嵌入式系统实现各种通信方式之间的数据交互是监控系统发展的方向;但是,嵌入式系统相对资源有限,由其实现高效、实时的数据交互,存在一定的难度。本设计利用嵌入式系统实现复杂交互通信控制器,硬件采用深圳盛博公司提供的核心模块板SAM3410,自行设计和扩展外围接口电路;软件采用嵌入式Linux操作系统和多线程应用编程,实现多种通信方式的有效交互。通过测试,证明其可靠性高、数据流量大、容错率高,完全符合工业监控系统的要求。
1 硬件扩展
图1 通信控制器硬件电路框图
通信控制器硬件电路设计原理框图如图1所示。核心模块板SAM3410采用基于ARM920T内核的32位高性能微控制器AT91RM9200。AT91RM9200片内集成功能丰富,包括USART、CAN、Ethernet MAC、USB、SPI等控制器[1]。采用CPLD芯片CY37032实现扩展电路外围芯片编址并提供控制信号。选用芯片SC16C7541B80,扩充4路串行接口。通过芯片ICL3232IV,对外提供标准的RS232信号通路;芯片SJA1000及其附加电路,对外提供标准的CAN总线信号。网络隔离变压器选用H1102模块。
2 驱动开发
Linux操作系统为设备驱动提供了标准的SPI接口,并有严格的规范要求[2]。设备驱动程序属于系统核心层程序,运行于系统核心态;应用开发程序属于应用层程序,运行于系统用户态。设备驱动程序的主要工作包括设备初始化、读写设备和与操作系统的数据交换,设计微控制器和外围芯片之间的数据交换采用中断方式[3]。
3 应用编程
3.1 多线程程序的编制
多线程指一个进程中存在多个控制流。多线程编程有加快应用程序的响应速度、改善程序的结构、使程序占用更少的系统资源和改善系统性能的优点。Linux操作系统内核(V2.6)支持多线程编程,并提供了丰富的功能函数。设计中采用的函数原型有[4]:
int pthread_create(pthread_t *tid, const pthread_attr_t *tattr, void * (*start_routine)(void *), void *arg);
//创建受控线程
int pthread_join(thread_t tid, void **status);
//线程链接、回收和处理
int pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *mattr);//动态方式初始化互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//锁定mutex指向的互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//解除mutex指向的互斥锁
int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);
//条件变量初始化,也有动态和静态两种初始化方式
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
//基于条件变量的阻塞
int pthread_cond_signal(pthread_cond_t *cv);
//解除基于条件变量的阻塞
int pthread_cond_timewait(pthread_cond_t *cv, pthread_mutex_t *mutex, const struct timespec *abstime);
//在指定的时间之前和条件下阻塞
线程属于轻量级进程(Light Weight Process,LWP)。在与LWP绑定或非绑定运行的情况下,均拥有独立的线程表项和堆栈空间,并共享进程中的其他运行环境,如存储区、文件系统属性、信号处理等。与进程相比,线程的最大优点是数据共享,但线程调度与进程调度不同。线程调度保存并恢复线程堆栈和寄存器数据,但并不包括其他内存数据;因此,线程编程需处理好全局变量、可重入函数中的局部静态变量、函数的返回值等内容,涉及线程堆栈的使用、各变量在内存和寄存器中的分配方式[5]。
多线程编程还会引起对物理I/O设备接口的顺序读写问题。设计中编写了各接口读写线程,包括串口读、写(处理)函数,CAN总线接口读、写函数,网络接口读、写函数等。为保护全局变量,特别是各物理I/O设备接口缓冲区数据段,读、写线程间使用同步和互斥(PV操作)技术,保证了多线程并行安全性。对物理I/O设备接口的操作也采用互斥访问,以解决多线程对物理设备的假并行可能引发的问题。各接口初始化均采用阻塞方式,以提高接口空闲时微控制器利用率,也是多线程调度的基本要求。
3.2 功能函数的实现
以串口收发为例。应用层读函数通过操作系统内核调用方式读取系统读缓冲区数据,并将数据放入相应串口处理缓冲区中,串口写函数分析并处理缓冲区中的数据。当通信控制器需要转发数据时,写函数通过操作系统内核调用方式将数据写入系统相应的写缓冲区中,然后由内核底层驱动完成数据外发工作。函数之间调用采用指针方式传送数据。其他功能函数有CAN总线接口读写函数、网络收发函数等。关键读、写函数均被线程化。
3.3 可重入函数的构建
可重入函数是可以被中断和同时被多个线程(任务)类函数并发调用,并在相同的输入条件下获得相同输出结果的函数体。原则上,可重入函数体内不应使用全局变量、静态数据结构和静态变量,不调用malloc()或free()等内存分配和释放函数,以及其他不可重入函数,包括标准I/O函数等。
编写可重入函数可以减少代码总量,优化程序结构,提高代码效率。设计中大量采用可重入函数编程,包括读和写数据缓冲区操作、数据处理过程等。设计构建的可重入函数,多不采用静态局部变量和内存动态分配方法;对公有数据缓冲区,采用代码锁和数据锁的同步和互斥技术;对物理I/O设备接口的操作,采用全局互斥技术;指定编译器链接可重入库函数,保证了多线程调用可重入函数的正确性。关键可重入函数体int dispose(struct prodcons *b)完成对数据缓冲区中接收到的有效数据处理工作,其扇入、扇出分别为8和4,有较高的代码复用率。
3.4 同步和互斥技术
为各对应的物理I/O设备接口建立有效的数据缓冲区,并以读、写线程方式处理缓冲区中的数据,可以提高微控制器的利用率。数据处理缓冲区建成环形读写缓冲区,并采用生产者—消费者进程同步和互斥原理[6],通过互斥锁和信号量方式处理缓冲区的读、写线程同步问题。另外还采用带有时间等待的条件互斥,实现了对接收数据的连续时间控制,解决了实际通信中的无效延迟数据,如电台无效数据、干扰和乱码等。设计关键代码段如下:
int dispose(struct prodcons *b) {
//写线程调用的数据缓冲区处理函数
pthread_mutex_lock(&mutex);
//通过mutex锁定全局数据缓冲区
while (条件判断1) {
pthread_cond_wait(&cv1, &mutex);
//锁定在条件cv1上,同时解除mutex锁定
}
while (条件判断2) {
pthread_cond_timedwait(&cv1, &mutex, &to);
//锁定在条件cv1和时间to上,同时解除mutex锁定
}
pthread_cond_signal(&cv2);//解除条件cv2上的锁定
pthread_mutex_unlock(&b->lock);//解除mutex锁定
}
图2 环形缓冲区示例
图2为环形缓冲区示意图。为提高缓冲区读写效率,避免发生类似网络传输中的糊涂窗口现象,并降低互斥锁切换的开销,设计采用了粗粒度锁定下的合理开窗机制[4]。图中m和n字节大小的设定分别控制读、写线程的休眠期。编程中考虑应用层缓冲区空间w(4 096字节)应大于底层系统驱动缓冲区空间v(1 024字节,原则上w≥2v),两者均远大于单包有效传送数据的最大值a(256字节),因此,设计简化m为v(原则上m≥v);但n值不应大于单包有效数据的最小值b(8字节,原则上n<b)。设计中,n取临界值(8字节)作为程序中的判断条件,即读线程在向缓冲区中追加数据时,采用一次整批放入缓冲区中。当余留空间小于v(1 024字节)时,停止追加数据,并以信号量方式通知启动写线程,之后释放互斥锁。写线程则在n小于一定值(8字节)时,停止数据处理工作,同样以信号量方式通知启动读线程,之后释放互斥锁。
3.5 程序优化
程序源代码级优化采用了结构优化、算法优化和语句优化等多种方法,如程序中有许多测试、判断和跳转语句,均按照实际发生概率的大小排列次序,以减少判断耗时;控制扇入、扇出量;采用内联函数等。另外,读线程采用批量数据传送方式,通过合理设计缓冲区大小,取消单字节传送。写线程需要分析和处理缓冲区中的数据,相对读线程需要更多的微控制器机时。采用while条件判断语句,优化写线程中各函数结构,实现连续处理缓冲区中有效数据的方法,充分利用每一次线程调度的时间片,提高数据处理效率。
采用提高写线程优先级的方法,需先作线程与LWP的绑定。绑定会造成线程同步和互斥锁切换缓慢,不利于设计中大量采用的同步和互斥技术。因此,设计中各线程均采用非绑定运行方式,具有相同的默认优先级别,平等分时调度。
目标程序的执行速度和映像文件的大小是相互矛盾的。核心微控制器主频不高,而系统数据处理量较大(≤35 kbps),因此,编译优化更注重程序执行速度的优化。编译采用了最高优化级别O3和静态编译参数static,以提高程序的运行速度。选用了剥离符号表和重定位信息参数s,以减小生成的二进制映像文件的大小。
程序源代码文件49 KB,编译器编译、链接生成的二进制文件仅为22 KB。
4 系统测试
测试贯穿于整个设计过程,覆盖了产品的功能、性能、可靠性、容错性、安全性和易用性等多方面[7]。
4.1 单元测试
采用两台PC分别作为通信控制器的上、下位机,数据通过通信控制器的串口在上、下位机间双向传输。串口速率115 200 bps,通信控制器流量1 700 bps,测试收发数据无误;流量提高到3 400 bps,短时间内收发数据无误。一段时间后,接收到的数据包不完整,丢失前部数字节。分析发现,当通信控制器多接口频繁快速收发数据时,各线程分时较短。当应用层写函数分得的时间片已无法保证数据处理速度时,应用层数据缓冲区将出现数据堆积现象,严重时将波及系统底层,造成系统底层驱动缓冲区出现数据堆积直至接收停滞现象。这时,新到数据在外围芯片缓存中覆盖未接收处理的旧数据,造成系统缓冲区内容错误。
通过扩大应用层和系统驱动缓冲区,优化程序,问题得以解决。
4.2 集成测试
通信控制器连接电台后,在快速收发数据时出现死机。问题由电台CTS信号快速变化引起,更改系统驱动中相应部分,解决死机问题。
结语
用嵌入式系统设计并实现的复杂交互通信控制器,经系统测试后交付使用,现正进行用户级测试工作。设计的嵌入式系统多线程编程框架,特别是深度同步和互斥模型,同样适用于其他Linux系统多线程编程。