CAN总线在嵌入式Linux下驱动程序的实现
1引言
基于嵌入式系统设计的工业控制装置,在工业控制现场受到各种干扰,如电磁、粉尘、天气等对系统的正常运行造成很大的影响。在工业控制现场各个设备之间要经常交换、传输数据,需要一种抗干扰性强、稳定、传输速率快的现场总线进行通信。文章采用CAN总线,基于嵌入式系统32位的S3C44B0X微处理器,通过其SPI接口,MCP2510 CAN控制器扩展CAN总线;将嵌入式操作系统嵌入到S3C44B0X微处理器中,能实现多任务、友好图形用户界面;针对S3C44B0X微处理器没有内存管理单元MMU,采用uClinux嵌入式操作系统。这样在嵌入式系统中扩展CAN设备关键技术就是CAN设备在嵌入式操作系统下驱动程序的实现。文章重点解决了CAN总线在嵌入式操作系统下驱动程序实现的问题。对于用户来说,CAN设备在嵌入式操作系统驱动的实现为用户屏蔽了硬件的细节,用户不用关心硬件就可以编出自己的用户程序。实验结果表明驱动程序的正确性,能提高整个系统的抗干扰能力,稳定性好,最大传输速率达到1Mb/s;硬件的错误检定特性也增强了CAN的抗电磁干扰能力。
2系统硬件设计
系统采用S3C44B0X微处理器,需要扩展CAN控制器。常用的CAN控制器有SJA1000和mcp2510,这两种芯片都支持CAN2.0B标准。SJA1000采用的总线是地址线和数据线复用的方式,但是嵌入式处理器外部总线大多是地址线和数据线分开的结构,这样每次对SJA1000操作时需要先后写入地址和数据2次数据,而且SJA1000使用5V逻辑电平。所以应用MCP2510控制器进行扩展,收发器采用82C250。MCP2510控制器特点:1.支持标准格式和扩展格式的CAN数据帧结构(CAN2.0B);2.0~8字节的有效数据长度,支持远程帧;3.最大1Mb/s的可编程波特率;4.2个支持过滤器的接受缓冲区,3个发送缓冲区;5.SPI高速串行总线,最大5MHz;6.3~5.5V宽电压范围供电。MCP2510工作电压为3.3V,能够直接与S3C44B0X微处理器I/O口相连。为了进一步提高系统抗干扰性,可在CAN控制器和收发器之间加一个光隔6N137。其结构原理框图如图1:
图1.S3C44B0X扩展CAN结构框图
图2.字符设备注册表
3CAN设备驱动程序的设计
Linux把设备看成特殊的文件进行管理,添加一种设备,首先要注册该设备,增加它的驱动。设备驱动程序是操作系统内核与设备硬件之间的接口,并为应用程序屏蔽了硬件细节。在linux中用户进程不能直接对物理设备进行操作,必须通过系统调用向内核提出请求,由内核调用相应的设备驱动。因此首先建立Linux设备管理、设备驱动、设备注册、Linux中断这几个概念。
3.1 Linux的设备管理
Linux支持各种各样的外围设备,对这些设备的管理通称为设备管理。设备管理分为两部分:一部分是驱动程序的上层,与设备无关的,这部分根据输入输出请求,通过特定的设备驱动程序接口与设备进行通信;另一部分是下层,与设备有关的,通常称为设备驱动程序,它直接与硬件打交道,并且向上层提供一组访问接口。Linux设备管理为了对设备进行读、写等操作,把物理设备逻辑化,把它看成特殊的文件,称为设备文件,采用文件系统接口和系统调用来管理和控制设备。Linux把设备分为三类,块设备、字符设备和网络设备。每类设备都有不同管理控制方式和不同的驱动程序,这样方便于对系统进行裁减。Linux内核对设备的识别是根据设备类型和设备号。在字符设备中使用同一个驱动程序的每种设备都有唯一的主设备号。CAN设备通过在/vendor/Samsung/44b0/Makefile文件下设置设备类型和设备号分别为can、125。
Linux对设备操作的具体实现是由设备驱动程序完成。设备驱动程序加载到系统中通过设备注册实现。Linux驱动程序对文件的操作通过file_operations结构体来完成。file_operations结构体是文件操作函数指针的集合。在设备管理中该结构体各个成员项指向的操作函数就是设备驱动程序的各个操作例程,编写驱动程序实质上就是编写该结构体中的各个函数。对不同的设备可以配备其中全部或部分的操作函数,不使用的函数指针置为NULL。下面是CAN设备file_operations结构体:
Static struct file_operations {
write: s3c44b0_mcp2510_write,//写操作
read: s3c44b0_mcp2510_read,//读操作
ioctl: s3c44b0_mcp2510_ioctl,//读写之外的操作
open: s3c44b0_mcp2510_open,//打开设备
release: s3c44b0_mcp2510_release};//关闭设备
这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用,来调用自己的驱动接口,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。
3.3 设备注册
在linux中,当一种设备安装到系统时必须向系统进行注册,设备注册的主要任务是把设备驱动程序加载到系统中。Linux对不同的设备(如字符设备和块设备)分开进行注册管理。每个设备描述符包括两个指针:name指向设备名字符串,fops指向文件操作函数结构file_operations,该结构体中包含着指向驱动程序各个操作例程的指针。图2给出了linux字符设备注册表的示意图。CAN字符设备的注册函数是内核函数:register_chrdev(MAJOR_NR,device_NAME,&s3c44b0_mcp2510_fops);
其中参数DEVICE_NAME表示设备名,s3c44b0_mcp2510_fops表示指向file_operations结构体的指针,即指向设备的驱动程序。
3.4 Linux中断的处理
在linux系统里,对中断的处理是属于系统核心部分,因而如果设备与系统之间以中断方式进行数据交换,就必须把该设备的驱动程序作为系统核心的一部分。设备驱动程序通过用request_irq函数来申请中断,通过free_irq来释放中断。由于本实验未用到中断,因此在此不作详细介绍。
3.5 CAN驱动程序的实现
3.5.1 编写驱动程序操作例程
CAN设备属于字符设备,对于CAN总线设备,除了发送(使用write方法)、接受(使用read方法)以外,还需要控制CAN总线通信的波特率、设置工作模式、设置ID等,所以使用ioctl是最合适的方法。
CAN驱动程序的入口函数:
int __init s3c44b0_mcp2510_init(void){ARMTargetInit();//初始化ARM
init_MCP2510(BandRat125kbps);//初始化CAN控制器 ret=register_chrdev(MAJOR_NR,DEVICE_NAME,&s3c44b0_mcp2510_fops);}//注册CAN设备
CAN驱动程序的退出函数:void __exit s3c44b0_mcp2510_exit(void){
unregister_chrdev(MAJOR_NR,DEVICE_NAME);printk("MCP2510 Eixt!n");}
编写CAN设备驱动程序各个操作例程:
1.ioctl函数:
Static int s3c44b0_mcp2510_ioctl (struct inode * inode,struct file *file, unsined cmd ,unsigned long arg){switch(cmd){case SETBAND://设置波特率
MCP2510_SetBandRate(BandRate,TRUE);break;case SETLPBK://设置工作方式
MCP2510_Write(CLKCTRL, MODE_LOOPBACK| CLK| CLK1);break;case SETID://设置标识符
MCP2510_Write_Can_ID(RXF0SIDH,U8 ID,0);break;case SETFILTER: //设置屏蔽码
MCP2510_Write_Can_ID(RXM0SIDH,0x1ff,0);break;}}
2.open函数(打开设备):
static int s3c44b0_mcp2510_open(struct inode *inode,struct file *file)
{printk("device openn");return 0;}
3.write函数(发送数据):
static ssize_t s3c44b0_mcp2510_write(struct file *file,const char *buffer,size_t count,loff_t *ppos){copy_from_user(&temp,buffer,sizeof(mcpcan_data)); canWrite(temp.id,temp.data,temp.DataLen,temp.IdType,temp.BufNo);}//发送数据函数
4.read函数(接收数据):
static ssize_t s3c44b0_mcp2510_read(struct file *file,char *buffer,size_t count,loff_t *ppos){Revdata(0x66,datas,0x08);//接收数据函数
copy_to_user(buffer,Receivedata.data,0x08);return count;}
3.5.2交叉编译CAN驱动程序
交叉编译驱动程序需要一台装了Red Hat Linux的宿主机。安装交叉编译工具的方法请参考相关文档(交叉编译工具:arm-elf-tools-20030314.sh)。驱动程序的使用可以按照两种方式进行编译,一种是静态编译进内核,一种是编译成模块以供动态加载。由于uclinux不支持模块动态加载,所以这里只介绍将驱动程序静态编译进内核的方法。为了让编译器编译所添加的驱动程序,需要修改相关文件。
1.修改/linux-2.4.x/driver/char/Makefile文件,增加:
Ifeq((tab键)$(CONFIG_MCP2510),Y) (换行)Obj-y+=akaeled.o
Endif//这几句话的意思是如果配置了mcp2510,则把mcp2510.o加进内核。
2.修改linux-2.4.x/driver/char/mem.c,在文件中增加如下代码:
#ifdef CONFIG_MCP2510 (换行)extern void mcp2510_init();
#endif//通过该文件告诉内核调用相应的CAN驱动程序
#ifdef CONFIG_MCP2510 (换行)mcp2510_init(); (换行)#endif
3.修改linux-2.4.x/driver/char/Config.in文件,在字符字段内添加如下代码:
Bool ‘mcp2510 support’ CONFIG_MCP2510
这样在make menuconfig时将出现mcp2510的配置选项。
4.修改/uClinux/vendor/Samsung/44b0/Makefile
在DEVICES部分添加内容:can, c, 125, 0。这句话的意思是在device中注册一个字符设备can,该设备主设备号为125,次设备号为0。在make menuconfig时进入Character devices,选中里面的support mcp2510。在root权限下执行下列命令编译内核:
1、#make dep;2、#make lib_only;3、#make romfs;4、#make image;5、#make
4 CAN驱动程序的测试
4.1 编写应用程序
为了验证所添加的驱动程序的正确性,编写一个应用程序CAN2510.C进行测试,在应用程序中使用下面函数创建一个线程用来发送数据:
pthread_creat(&id,NULL,(void *)cansend,&sendata);
在cansend()函数中用write()函数调用驱动程序s3c44b0_mcp2510_write()实现数据的发送,用read()函数调用驱动程序s3c44b0_mcp2510_read()接收节点发送过来的数据,用printf()输出节点发送过来的数据,验证接收到的数据是否正确。
4.2 编译CAN应用程序
编译应用程序有两种方法:一是放到内核中编译,这种方法需要写一个Makefile文件,还需要修改相应文件,比较麻烦;另外一种办法是单独编译,把编译产生的可执行文件添加到uclinux文件系统romfs中的bin文件夹下,重新编译内核。本实验采用了后者。执行:#arm-elf-gcc –elf2flt can2510.c –o can2510 –lpthread
其中arm-elf-gcc是编译器,增加参数–elf2flt是由于uclinux只支持flat格式的可执行文件,-0是对编译进行优化,can2510是编译产生的可执行文件名称。把can2510复制到/home/cai/uclinux/romfs/bin目录下,重新编译内核,把产生的映像文件image.rom或image.ram下载到目标板,运行can2510进行CAN驱动测试。
5 结论
本文的创新点:在分析Linux设备驱动程序工作原理和结构的基础上,独立添加了CAN总线设备驱动程序到嵌入式操作系统Linux中。经实验表明嵌入式系统下扩展CAN总线传输数据可靠、抗干扰强,在工业控制场合有很大的使用价值;同时,CAN设备在嵌入式操作系统linux下驱动程序的成功实现,为在嵌入式系统中扩展其他硬件设备驱动程序提供了很好的参考价值。