基于嵌入式Linux的步进电机驱动程序设计
扫描二维码
随时随地手机看文章
1.引言
随着嵌入式技术的飞速发展,基于嵌入式系统的新一代工业控制器也日益增多。同以往的控制器不同,新的仪器大多以32位嵌入式处理器为核心,并且安装有嵌入式操作系统,从而大幅度提高了处理能力,方便了设计开发。在各种嵌入式操作系统中,嵌入式Linux是免费的自由软件,其构建的系统成本较低,而且Linux是单内核的操作系统,并可按要求进行任意剪裁,因此越来越多的研究人员开始在用Linux平台来开发自己的产品[1]。
嵌入式开发过程中,经常需要为特定设备开发驱动程序。这些驱动程序的编写和编译与PC上的Linux驱动开发相比存在明显的差异,需要考虑的因素更多,实现过程更为复杂。本文以SAMSUNG公司S3C2410X CPU为例,探讨如何为使用嵌入式Linux的工业控制器开发字符设备驱动程序来驱动步进电动机。
2.Linux驱动程序概述
在Linux中,几乎所有的内容都是文件,对设备驱动的访问也是以文件操作的方式实现的。Linux系统支持3种类型的硬件设备:字符设备、块设备和网络设备,这些设备的驱动程序是系统内核的重要组成部分。对用户程序而言,操作系统隐藏了设备的具体细节,把设备映射为一个设备文件,用户程序可以对设备文件进行open、CLOSE、read、write等操作。这些操作和驱动程序是通过struct file_operations这一数据结构关联起来的,编写设备驱动程序的主要工作就是编写子函数填充file_operations的各个字段[2]。
3.嵌入式Linux步进电机驱动程序开发
3.1 嵌入式Linux设备驱动程序的结构
嵌入式Linux下的设备总体上可以分为两部分:
其一,驱动与内核接口层,它实现驱动模块在Linux内核的注册加载与卸除工作。主要任务就是在模块加载时向内核注册驱动,以及实现虚拟文件系统的设备操作接口。对于采用中断的设备,此部分还包括中断处理函数的注册与注销。
其二,硬件设备接口层,这部分主要描述驱动程序与设备的交互。它主要包括硬件探测和初始化以及设备的读写访问和设备控制操作。硬件探测主要是在驱动注册加载时监测设备是否存在,设备初始化主要是检测到设备后对它进行初始化操作。设备的读写操作主要完成从设备接受数据和将数据发送给设备的操作。硬件设备接口层还需要包括一些设备的控制操作,设定设备的工作参数。
对于驱动程序与内核接口层,Linux提供了标准的入口点函数init_module();在通过模块化的设计方法设计驱动程序时,使用insmod加载核心模块时会调用本函数,通知内核对驱动程序进行注册。模块的卸除工作与加载工作类似,通过rmmod卸载模块时,调用cleanup_module()取消驱动程序的注册。
3.2 步进电机驱动程序需求分析
步进电机是将电脉冲信号转变为角位移或线位移的开环控制元件。在非超负载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响。所以在驱动程序中间只需要考虑这两个方面的影响。
本系统的步进电机的四相由硬件地址0x28000006的bit0~bit3控制,bit0对应MOTOR_A,bit1对应MOTOR_B,bit2对应MOTOR_C,bit3对应MOTOR_D。本文所描述的驱动是针对整步模式下的步进电机,整步模式下的步距角18°。在整步模式下的脉冲分配信号如表所示。
所以在程序中需要通过编制脉冲分配表控制步进电机,并且通过修改脉冲分配表可以实现步进电机方向的控制。
系统的步进电机仅仅是一个输出的通道,只能顺序的进行控制的操作,因此作为一个字符设备来进行驱动。对于字符设备的操作而言驱动程序需要提供相关的几个操作分别为open,read,write,ioctl等相关的函数入口点。在驱动程序的实现过程中需要定义这些文件相关的操作,填充进入file_operations结构中。
与普通文件相比,设备文件的操作要复杂得多,不可能简单的通过read、write等操作来实现。并且由于对于步进电机驱动程序没有相关的输入与输出,更关注的是对硬件的控制,因此在驱动程序对于write操作和read操作仅需返回0,而对于硬件的控制只需要在驱动程序中实现ioctl函数,并在其中添加相应的case即可。通过cmd区分操作,通过arg传递参数和结果[3]。[!--empirenews.page--]
3.3 步进电机驱动程序设计
因为步进电机用到了I/O端口,而在ARM9中操作端口要用虚拟地址而非实际的物理地址,所以要修改内核代码。
修改文件内核源代码中间的smdk.c,在结构体
static struct map_desc smdk_io_desc] __initdata = {
{ vCS8900_BASE, pCS8900_BASE, 0x00100000, DOMAIN_IO, 0, 1, 0, 0 },
{ vCF_MEM_BASE, pCF_MEM_BASE, 0x01000000, DOMAIN_IO, 0, 1, 0, 0 },
{ vCF_IO_BASE, pCF_IO_BASE, 0x01000000, DOMAIN_IO, 0, 1, 0, 0 },
LAST_DESC
};
中添加一行数组元素{ 0xd3000000, 0x28000000, 0x01000000, DOMAIN_IO, 0, 1, 0, 0 },则步进电机的物理地址0x28000006对应的虚拟地址为0xd3000006,在驱动程序中应对这个地址进行操作。
定义全局变量num和status用来控制步进电机的速度和方向:
static int num=1;
static enum{off,clockwise,anticlockwise} status=off;
定义步进电机的整步模式正转脉冲表:
unsigned char pulse_table[] =
{
0x05, 0x09, 0x0a, 0x06,
};
定义时钟节拍函数time_tick()
static void time_tick(unsigned long data)
{
static int i=0;
switch(status)
{
case off: break;
case clockwise:
if(++i==num){
i=0;
if( row == 4 ) row = 0;
(*(char *)0xd3000006)=pulse_table[row++];
}
ttimer.expires=jiffies+1;
add_timer(&ttimer);
break;
case anticlockwise:
if(++i==num){
i=0;
if( row == -1 ) row = 3;
(*(char *)0xd3000006)=pulse_table[row--];
}
ttimer.expires=jiffies+1;
add_timer(&ttimer);
break;
case default: break;
}
}[!--empirenews.page--]
在time_tick()函数中判断步进电机的状态,是停止、正转还是反转。若是正转,则按正向顺序发送脉冲,并添加定时器ttimer;若是反转,则按反向顺序发送脉冲,并添加定时器ttimer;若是停止则不再发送脉冲,也不再添加定时器。
在stepper_module_init()函数中申请I/O端口,并初始化定时器ttimer:
if(check_region(0x28000006, 1)) //看该I/O端口是否已经被占用
{
printk("The stepper port is used by another module.n");
return -1;
}
request_region(0x28000006, 1, DEVICE_NAME); //申请该I/O端口
init_timer(&ttimer); //初始化定时器ttimer
ttimer.function=time_tick; //填写定时器处理函数为time_tick()
编写ioctl函数用来接收应用程序对于步进电机的控制。
int device_ioctl( struct inode *inode, struct file *file, unsigned int ioctl_num,
unsigned long ioctl_param)
{
struct stepper * s;
/* 根据实际程序中的不同需求更改ioctl函数的调用*/
switch (ioctl_num)
{
case IOCTL_SET_MSG:
s = (struct stepper*) ioctl_param;
switch (s->CmdID)
{
case 0: /*开始*/
status=clockwise;
ttimer.expires=jiffies+1; //开启定时器
add_timer(&ttimer);
break;
case 1: status=off; break; /*停止*/
case 2: /*反转*/
if(status==clockwise){ status=anticlockwise; }
if(status==anticlockwise){ status=clockwise; }
break;
case 3: if(num!=1)num--; break; /*加速*/
case 4: num++; break; /*减速*/
}
}
return 0;
};
通过s指针得到stepper结构中的表示命令类型的参数,根据该参数判断命令类型,0是start起动,1是stop停止,2是reverse反向,3是up电机加速,4是down电机减速,通过改变全局变量num和status来控制电机。电机的起动是通过在start分支中起动一个定时器ttimer,然后在定时器处理函数time_tick中发送步进电机脉冲,并重新添加定时器,从而实现步进电机的转动。
4.结语
本文归纳了嵌入式Linux驱动程序开发的特点并且结合嵌入式Linux下步进电机的驱动说明了驱动程序的编写。本文论述的驱动程序比较简单,一个功能齐全的驱动程序除了本文提到的几种功能外,还应该包括中断处理。这些工作有待日后完成。
本文作者创新点:步进电机在嵌入式的应用中传统的方式都是在没有操作系统中完成,或者在没有支持MMU的操作系统中实现,本文在操作系统支持MMU的情况下完成了对于步进电机的控制。