随着数字信息技术的不断发展,人们对伺服控制系统的实时性、稳定性和复杂性的要求越来越高,单靠顺序结构的软件设计已经不容易满足上述要求。目前很多伺服控制系统的控制器采用PC/104结构或依赖上位计算机,根据实际的控制系统需要扩展相应的控制电路,使得系统体积大、成本高、可靠性不易保证,且用户交互性不好。嵌入式Linux操作系统由于具有代码开源、可移植性、软硬件可裁剪性、资源丰富及支持多种硬件平台和接口等特点,并且从2.6版本以后的Linux实时性有了很大的提高,正被越来越多地应用于伺服控制系统中。通过嵌入式Linux操作系统对控制系统的软硬件资源进行分配、调度、控制和协调,能够充分发挥控制系统的性能。ARM处理器以其体积小、低功耗、低成本、高性能、文档丰富及嵌入式软件多等优点而得到广泛的应用。因此,本文以ARM9和CPLD为硬件平台,在嵌入式Linux操作系统下设计了直流伺服控制系统。
1 硬件平台
系统原理框图[1]如图1所示。系统以ARM作为主控芯片,主要负责运行操作系统并实现控制算法、人机交互和多机通信等。CPLD EPM570T144主要负责从ARM接收数据,产生相应的PWM波;接收编码器输出信号,并对其进行处理,得到编码器的值,将其送给ARM,从而实现电机的闭环控制。CPLD和ARM之间通过地址总线(13根)、数据总线(16根)、控制总线(片选、读写使能信号等)与GPIO口(作为外部中断使用)连接,即CPLD类似于ARM的一个外部存储器(CPLD挂接在ARM的bank1存储空间上,地址空间为0x08000000~0x10000000),ARM和CPLD的数据交换类似于对存储器的读写操作。这种总线方式扩展,使得系统数据交换快速、操作简单。控制板通过JTAG、UART、USB和网口与上位机连接,在目标板和上位机之间建立交叉开发环境,可在控制板和上位机之间实现程序下载调试、文件传输和通信等,便于系统软件开发和调试。
2 CPLD程序设计
CPLD程序分为电机辨向、四倍频、编码器脉冲计数、PWM波生成和总线数据读写5个模块,如图2所示。采用VHDL语言,依据自底向上设计的方法,以便于程序开发和移植。
采用增量式编码器,需对编码器输出的ABZ码进行处理[2],经过辨向、倍频、计数后得到编码器值。ARM与CPLD之间通过双向总线交换数据,CPLD读取ARM写入数据总线的数据,产生对应的PWM波。当CPLD中的编码器值可读后,CPLD采用中断方式通知ARM,然后将编码器值写到数据总线上供ARM读取。由于CPLD与ARM的其他外设共用数据总线,所以在CPLD对总线进行操作时要特别注意,除了CPLD往总线上写数据外,其他时刻都应该将总线置为高阻态,以让出总线的使用权,否则其他外设(如网口、ADC接口等)会因CPLD一直占用总线而不能正常工作。
CPLD应用计数法产生PWM波[3],CPLD时钟频率为100 MHz,设置PWM总计数值为8 000。CPLD根据ARM给定的0~8 000的计数值对时钟计数,产生两路反相的PWM波。为防止功率放大器的H桥同一侧上下同时导通,一般设置有3~5 μs的死区,本设计中设置为5 μs的死区。
3 设备驱动设计
3.1 设备驱动简介
设备驱动是连接应用程序与硬件设备的桥梁,驱动程序为应用程序提供了接口函数,用户在应用程序中调用相应的接口函数便可实现对硬件设备的操作,因此,驱动程序的开发是嵌入式系统开发的关键环节。Linux设备驱动分为字符设备驱动、块设备驱动和网络设备驱动[4]。本文中控制板上移植了Linux2.6操作系统,该操作系统下需设计ARM读写CPLD的数据及对CPLD产生的中断信号响应的驱动,这一要求采用字符设备驱动来实现。应用程序通过系统调用对设备文件进行诸如read、write等操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取设备驱动程序中初始化的file_operations结构体,获取相应操作(read/write等)对应的函数指针,接着把控制权交给该函数。因此,编写设备驱动程序的主要工作就是编写这些文件操作的接口函数,并填充file_operations的各个域。
3.2 设备驱动程序设计
为便于开发和调试,设备驱动使用模块的方式动态加载到内核中去。加载模块的方式与以往的应用程序开发有很大的不同。以往在开发应用程序时都有一个main()函数作为程序的入口点,而在驱动开发时却没有main()函数,模块在调用insmod命令时被加载,此时的入口点为module_init()函数,在该函数中完成设备的注册、设备文件的创建和相关内存及寄存器的地址映射。同样,模块在调用rmmod命令时被卸载,此时的入口点为module_exit()函数,在该函数中将不用的资源返还给操作系统,把注册的设备、创建的设备文件及IO内存映射等注销掉。在设备完成注册和加载之后,用户的应用程序就可以对该设备进行一定的操作,如read、write等。而驱动就是用于实现这些操作,在用户应用程序调用相应入口函数时执行相关的操作,module_init()入口点函数则不需要完成其他如read、wirte之类的功能。驱动程序需要定义和实现open、read、write等函数,并填充到file_operations结构中,file_operations结构把应用程序中的系统调用与驱动中对应的函数联系在一起。file_operations结构体如下所示:
static struct file_operations cpld_drv_fops = {
.owner = THIS_MODULE,
.write = cpld_drv_write,
.read = cpld_drv_read,
.open = cpld_drv_open,
.release = cpld_drv_close,
.fasync = cpld_drv_fasync,
};
其中,write()函数实现向CPLD中写入数据,read()函数实现ARM从CPLD读取数据。设备驱动运行在内核空间,而应用程序运行在用户空间,设备驱动程序不能直接访问用户空间的地址,在read()和write()函数中分别调用内核函数copy_to_user()和copy_from_user()实现数据的转移。read函数实现读取CPLD中的编码器值,write函数实现将产生PWM波的计数值写入CPLD中,这两个函数实现了内核空间与用户空间的数据交换。从驱动程序结构看,驱动程序由三部分组成:结构体struct file_operations及其成员函数的实现、设备初始化module_init()和设备注销module_exit()。
读写CPLD需要对内存进行读写操作[5]。CPLD产生的读中断信号连接到ARM的GPF1口,CPLD的使能信号由ARM的GPF0产生,因此需要配置相应的寄存器。驱动程序中需要对内存和寄存器进行操作,本操作系统下不能直接对内存和寄存器的物理地址进行操作,需先将相应的内存和寄存器的物理地址映射到内核的虚拟地址空间,通过对映射后的虚拟地址进行操作实现对寄存器和内存的操作。
ARM对CPLD的读操作采用异步通知和内核中断方式[5]实现,这样可减少系统开支。首先在驱动的open()函数中调用request_irq()函数注册内核中断,并在内核中实现中断处理函数,在内核中断处理函数中调用kill_fasyn()函数给指定的应用程序发送信号,通知应用程序CPLD中的编码器值可读。当CPLD无可读中断产生时,将read()函数放入等待队列,主程序一直处于睡眠状态,而不是应用程序主动去调用read()函数来等待中断的产生,即采用异步通知方式,调用内核中的fasync_helper()函数来实现。当CPLD有可读中断产生时,在中断处理函数中通过kill_fasync()函数,向进程发送信号SIGIO,触发应用程序中signal声明的异步触发函数,使用POLL_IN表明有数值可以读取。另外,要注意,在进入中断服务程序后,首先通过中断自旋锁spin_lock_irq()关闭所有中断,以防止其他中断源中断kill_fasync的工作,在中断服务程序结束时,再通过spin_unlock_irq()打开中断。中断处理函数部分代码如下:
spinlock_t lock;
static irqreturn_t eint1_irq(int irq, void *dev_id)
//中断服务程序
{
spin_lock_irq(&lock);//关闭中断
kill_fasync (&eint1_async, SIGIO, POLL_IN);
//产生中断后,驱动向应用程序发送数据可读信号
spin_unlock_irq(&lock);//开中断
return IRQ_RETVAL(IRQ_HANDLED);
}
4 应用程序设计
在应用程序[6]中,通过函数signal()注册信号处理函数,以接收内核发来的数据可读信号。为了打开设备文件的异步触发机制,用户程序需指定当前进程为内核发送信号的接收进程,可以通过fcntl系统调用的F_SETOWN命令来设置该值。用户程序还必须通过另一个fcntl命令设置设备的FASYNC标志,打开异步触发机制。只要内核中有CPLD可读中断产生,输入文件就会产生一个SIGIO信号,信号发送给应用程序,应用程序调用信号处理函数。在信号处理函数中读取编码器的值,通过控制算法得到控制数据(即PWM波计数值),然后将PWM波的计数值写入到CPLD中。主程序流程图如图3所示,部分代码如下:
int main(int argc, char **argv)
{
int Oflags;
signal(SIGIO, signal_fun);//注册信号处理函数
Init(argc,argv); //打开设备,控制参数初始化
…
fcntl(fd_cpld, F_SETOWN, getpid());
//指定当前进程为接收信号进程
Oflags=fcntl(fd_cpld, F_GETFL);//返回当前的信号标志
fcntl(fd_cpld, F_SETFL, Oflags | FASYNC);
//打开异步触发机制
while(1)
{
sleep(1000);//进程睡眠,等待内核发送中断信号
}
……
return 0;
}
由于Linux是多任务系统,各个进程间采用一定的调度算法调度,进程间会不时地切换,因此编写程序时要特别考虑系统进程调度的问题。控制程序对实时性有一定的要求,因此,要将控制程序进程设置为实时进程且要具有较高的进程调度优先级,同时控制程序中要尽量少地使用系统调用,以保证控制系统的实时性。
5 实验结果
控制板通过串口、网口与计算机(装有Linux系统)建立交叉编译环境,程序在计算机上编译调试。利用网线通过nfs(网络文件系统)服务,在计算机和控制板之间实现网络文件共享,可直接在控制板上访问计算机上的共享文件、执行计算机上编译好的程序,无需将计算机上编译好的程序下载到板子上。这种交叉开发的方式将程序和实验数据直接存储在计算机上,可节省控制板上的存储空间,便于程序开发和进行实验数据分析。
实验过程中,首先要对电机参数进行辨识。电机参数辨识的方法[7-8]很多,本实验采用阶跃响应法测得电机的模型参数,近似为一阶惯性环节。编码器为增量式编码器,电机转一圈产生20 000个码,即一个码值对应0.018°,编码器值采样周期为2 ms。将电机一个采样周期内转过的角度值除以采样周期作为速度反馈值,将电机转过的角度进行累加得到位置反馈值,电机最高转速为900°/s,最低转速为9°/s,采用PI控制[9-10]。图4为系统速度阶跃响应曲线,速度响应误差为每1个采样周期1个码值。图5为系统位置阶跃曲线,设定速度环最大速度为180°/s。位置环采用抗积分饱和算法,以消除因积分饱和引起的过大的超调,位置阶跃稳态误差为0。图6是系统的速度正弦跟踪曲线,正弦引导函数为:v=180°sin(0.8πt),跟踪误差在正反转速度换向处跟踪误差较大。
实验结果表明,基于ARM9和CPLD硬件平台,在嵌入式Linux操作系统下,系统能够实现等速跟踪、位置定点和正弦跟踪等功能,满足控制实时性要求,可实现伺服控制。系统体积小、成本低、功耗小、接口丰富、便于开发,且Linux系统具有很好的文件管理功能,有助于实验数据的存储和导出,便于实验结果分析。系统进一步完善后可将控制程序设计成图形界面,利用触摸屏输入和显示伺服控制结果,则可实现控制结果实时显示,可视化效果好,整个系统可脱离计算机工作,具有广泛的应用价值。
参考文献
[1] 李金洪,杨小军.基于DSP和FPGA的经纬仪控制系统设计[J].电子技术应用,2010,36(7):48-51.
[2] 钞靖,王小椿,姜虹.基于FPGA的光电编码器四倍频电路设计[J].仪表技术,2007(6):17-21.
[3] 耿伟松,于海东.基于CPLD的PWM发生器设计[J].制造业自动化,2010,32(6):151-153.
[4] 韦东山.嵌入式Linux应用开发完全手册[M].北京:人民邮电出版社,2008.
[5] 宋宝华.Linux设备驱动详解[M].北京:人民邮电出版社,2008.
[6] 孙程建.基于Linux的嵌入式数控系统底层软件设计[D].武汉:武汉科技大学,2007.
[7] 王帅,陈涛,李洪文,等.光电跟踪伺服系统的频率特性测试与模型辨识[J].光学精密工程,2009,17(1):78-83.
[8] 王伟国,陈涛,沈湘衡.直流伺服系统机械时间常熟测试方法的研究[J].仪器仪表学报,2005,26(8):66-70.
[9] 李洪文.基于内模PID控制的大型望远镜伺服系统[J].光学精密工程,2009(2):327-332.
[10] 刘金锟.先进PID控制及其MATLAB仿真[M].北京:电子工业出版社,2003.