段月骁 郭 斌 胡晓峰 罗 哉 陆 艺
(中国计量学院计量测试工程学院,杭州 310018)
在自动化测试系统中,处理器往往需要通过模数转换器获取传感器采集到的原始数据。S3C2440A微处理器自带八通道10位的AD转换器,在测量精度要求不高时可以直接使用。在实际应用中,为满足高精度测量要求,需要外扩模数转换器。笔者选用24位分辨率带SPI接口的模数转换器ADS1256作为S3C2440A的外部AD转换器。SPI是一种高速、全双工、同步通信总线。因其传输稳定、高效、占用引脚数量少,在模数转换器、Flash及MCU等串行设备中应用广泛。Linux是全球知名的开源自由多任务操作系统,任何人都可以在遵循GPL(General Public License)协议的基础上修改其源代码以适应实际需求。正是Linux操作系统的免费、开放源代码及内核可裁剪等诸多优点,使其广泛应用于智能仪表及工业控制等嵌入式系统中。
笔者采用S3C2440A微处理器和嵌入式Linux操作系统作为开发平台,结合实际工程气密性检测系统,设计S3C2440A与ADS1256的SPI接口,详细分析了Linux2.6.32.2下ADS1256驱动程序的设计、编译、加载和测试过程。
笔者选用S3C2440A微处理器,该处理器采用ARM920T的核心,0.13μm的CMOS标准宏单元和存储单元,主频400MHz,最高工作频率可达533MHz。S3C2440A提供了两个SPI接口,每个接口分别包含两个8位发送移位寄存器和接收移位寄存器,通过串行时钟线(SCK)、主机输出从机输入(MOSI)、主机输入从机输出(MISO)和片选控制线(nSS)与外围设备进行通信。处理器带有增强型ARM架构内存管理单元(MMU),支持Linux及Windows CE等多任务操作系统,可扩展能力强。
ADS1256是24位八通道Sigma-Delta(Σ-Δ)高分辨率低噪声模数转换芯片。ADS1256通过串行总线SPI与MCU进行数据通信,简化了接口电路设计。芯片支持对PGA(可编程增益放大器)设置所造成的偏移与增益误差进行自校准和系统校准。芯片带有通用数字I/O口和可编程时钟输出。
LM285D-2.5是低动态阻抗、低温度系数和低噪声的电压基准芯片。设计中LM285D-2.5的电源电压为5.0V,输出电压2.5V,为ADS1256模数转换提供稳定的参考电压。
实际工程中需采集气密性检测系统中的压差信号和压力信号。差压传感器和压力传感器输出的模拟信号由ADS1256转换成数字信号后通过SPI接口传入S3C2440A。ADS1256属于高分辨率的模数转换器,其外围电路设计需要尤其注意以保证数据转换精度。ADS1256外围电路和S3C2440A的接口电路如图1所示,ADS1256除了包含标准SPI总线的信号线SCLK、DIN、DOUT、CS外,还带有指示数据是否已转换好的标志信号线DRDY(低有效)与串口配合使用。MCU可以把DRDY作为外部中断源信号,或者通过查询方式访问ADS1256状态寄存器中的DRDY位来判断是否有数据可读。此处ADS1256的信号线SCLK、DIN、DOUT、CS、DRDY分别接S3C2440A的信号线SPICLK0、SPIMOSI0、SPIMISO、nSS0、EINT0。LM285D-2.5与ADS1256的连接电路如图2所示。LM285D-2.5的输出VREFN和VREFP为ADS1256数模转换提供稳定的2.5V工作电压。
图1 ADS1256外围电路和S3C2440A的SPI接口电路
图2 LM285D-2.5与ADS1256的连接电路
设备驱动程序是连接应用程序和实际硬件的纽带,它使得应用软件只需要调用操作系统的应用编程接口(API)就可让硬件完成要求的工作。Linux的外设分为3类:字符设备、块设备和网络接口[1]。模数转换器ADS1256属于字符型设备,字符型设备是使用最广泛的一类外设。笔者选用Linux2.6.32.2为嵌入式操作系统,arm-linux-gcc 4.4.3交叉编译器用于编译生成目标驱动程序。Linux给用户提供了两种开发设备驱动的方式:设计成可加载模块和直接编译到Linux内核中。前者适合驱动处于开发调试的阶段,驱动开发采用模块化思想,使得不需要修改内核代码,单独编译所开发的驱动模块即可,可以使用命令insmod和rmmod动态加载和卸载驱动。后者适合驱动功能已经调试成功的阶段,直接编译进内核后,该驱动就能随机启动,应用程序可以直接调用相应的接口以操作硬件。为方便调试,笔者使用前者。
在加载驱动程序时,系统将首先调用驱动的入口函数即初始化函数,完成存储空间分配、SPI0配置和字符设备注册工作。初始化流程如图3所示。
图3 初始化流程
部分程序如下:
static int __init ads1256_spi0_init(void)
{
int ret;
spi_gpacon=ioremap(0x56000000, 4); //以下采用ioremap函数实现重定向功能,使定义的指针指向相应的寄存器
…
s3c2410_gpio_cfgpin(S3C2410_GPE(11), S3C2410_GPE11_SPIMISO0); //设置SPI0端口
…
*spi_sppin0 = (0<<2) | (1<<1) | (0<<0) ; //配置SPI0的相关寄存器
…
*spi_clkcon |= (1<<18); //使能SPI时钟
ret = register_chrdev(ADS1256_MAJOR, DEVICE_NAME, &ads1256_spi0_fops); //调用函数register_chrdev进行设备注册
…
}
在Linux下,无论是用户程序还是内核程序,都不能直接访问物理内存,必须先通过函数ioremap()把物理地址映射成相应的虚拟地址,再通过虚拟地址来对内存进行访问[2]。所以在ads1256_spi0_init中,首先完成SPI0配置寄存器的地址映射。其次完成SPI0端口和工作模式寄存器的设置。最后调用函数register_chrdev完成字符设备的注册。退出函数static void _exit ads1256_spi0_exit(void)主要负责完成与初始化函数相反的操作,包括使用函数unregister_chrdev注销设备和使用函数iounmap取消地址映射。
结构体file_operations中的成员全部是函数指针。这些函数指针所指向的函数就是驱动程序与Linux内核的接口,是用户空间对Linux进行系统调用最终的落实者[3]。编写字符设备驱动程序的主要任务就是实现file_operations结构体中函数指针所指向的函数[4]。Linux内核提供了功能丰富的接口函数,如open、write及ioctl等,设计驱动程序时只要实现实际需要的接口函数,其他未实现的部分不对驱动程序造成影响。
本驱动程序定义的file_operations为:
static struct file_operations ads1256_spi0_fops = {
.owner=THIS_MODULE,
.open=ads1256_spi0_open,
.write=ads1256_spi0_write,
.read=ads1256_spi0_read,
.release=ads1256_spi0_close,
}
函数ads1256_spi0_open和ads1256_spi0_close分别用于打开和关闭ADS1256设备,将模块的使用计数加1和减1;ads1256_spi0_write用于向ADS1256寄存器组发送设置参数以控制设备的数据转换模式,如PGA、数据输出速率及是否自校准等;ads1256_spi0_read用于读取指定采集通道的转换数据。
读写设备时需要在内核地址空间和用户地址空间之间传输数据。在Linux中,跨空间复制是通过定义在
在本驱动程序中,用户程序通过函数static ssize_t ads1256_spi0_write(struct file *filp, const char *buf, size_t count, loff_t *f_ops)实现对ADS1256的配置。在该函数中通过copy_from_user(dataTx, buf, count)把用户空间的设置参数buf复制到内核空间的dataTx中,然后在SPI0状态寄存器的数据发送/接收准备位为1时,将dataTx写入SPI0的发送数据寄存器中以实现对ADS1256工作模式的设置。
函数的关键代码如下:
if(copy_from_user(dataTx, buf, count))
{
return-EFAULT;
}
*spi_gpgdat &= ~(1<<2); //使能ADS1256
udelay(500); //等待ADS1256稳定
for(x = 0; x { while(!(*spi_spsta0&0x01)); //SPI0是否准备好发送/接收数据? *spi_sptdat0 = dataTx[x]; udelay(8); } *spi_gpgdat |= (1<<2); //停用 ADS1256 本驱动中的函数static ssize_t ads1256_spi0_read(struct file *filp,char *buf,size_t count,loff_t *f_ops)实现接收ADS1256的转换结果。因为S3C2440A的SPI接收寄存器为8位移位寄存器,要接收24位的ADS1256转换数据,需要分3次顺序读取SPI0的接收数据寄存器。函数copy_to_user(buf, dataRx, count)负责把内核空间的数据dataRx复制到用户空间的buf中。 函数的关键代码如下: *spi_gpgdat &= ~(1<<2); //使能 ADS1256 udelay(500); //等待ADS1256稳定 for(x = 0; x { while(!(*spi_spsta0&0x01)); // SPI0是否准备好发送/接收数据? dataRx[x] = *spi_sprdat0; *spi_sptdat0 = 0xFF; } *spi_gpgdat |= (1<<2); //停用ADS1256 copy_to_user(buf, dataRx, count); 完成驱动源程序后需要编写Makefile文件用于编译驱动模块,Makefile的作用是制定Linux内核编译设备驱动程序的规则,如指定Linux的内核版本、驱动源文件所在的目录及目标驱动的名称等。本项目使用的Makefile文件格式如下: KERN_DIR = /opt/FriendlyARM/mini2440/linux-2.6.32.2 //编译驱动所需的内核版本 all: make -C S|(KERN_DIR) M=′pwd′ modules //使用KERN_DIR中的Makefile编译当前目录下的驱动源文件 obj-m+= ads1256_drv.o //编译生成目标驱动模块ads1256_drv 编译完成驱动程序后,在控制台输入insmod ads1256_drv.ko加载ADS1256驱动模块。Linux将实际的外围硬件在内核中映射为相应的设备节点,使用户程序控制实际硬件如同操作普通文件,输入命令mknod /dev/ads1256_spi0 c 153 0创建ADS1256的字符设备节点,这里c代表此驱动程序是字符型设备驱动,153是主设备号,0是次设备号。应用程序通过open("/dev/ads1256_spi0",O_RDWR | O_NOCTTY)打开设备,调用驱动程序提供的接口函数即可对ADS1256进行相应的读写操作。 图4为ADS1256驱动模块加载后控制台的输出结果。 图4 ADS1256驱动模块加载测试结果 由图4可知,在加载模块后,Linux依次正确地完成了寄存器映射、SPI0配置和驱动模块注册,输入命令lsmod可以查询到该驱动。 将驱动应用在气密性检测系统中,ADS1256接压力传感器和差压传感器进行工程测试。以压力传感器信号采集为例说明驱动测试情况,压力传感器采用Huba 511型陶瓷压力传感器,24V直流供电,采集的气压量程为0~1MPa。压力传感器的4~20mA电流信号,通过250Ω精密电阻转换成对应的1~5V电压信号。ADS1256输入电压量程为0~5V,24位分辨率对应的输出值为0~16 777 215。在控制台输入命令./ads1256_test,开始运行工程测试程序。笔者设置采样周期为1s,采样10次,采样结果如图5所示,计算得到采样平均值为11 817 308,对应的测试气压为630.458kPa,与数字压力表测量的630.500kPa相符,ADS1256驱动成功。 图5 采样通道的AD转换结果 设计了微处理器S3C2440A和模数转换器ADS1256之间的硬件接口电路,详细介绍了在嵌入式操作系统Linux 2.6.32.2下基于SPI总线的ADS1256驱动程序的实现过程。将ADS1256应用在气密性检测系统中,测试得到正确可靠的数据转换结果,表明硬件设计正确,驱动工作正常,为高精度多通道数据采集的嵌入式系统提供了一种解决方案。 [1] 宋宝华.Linux设备驱动开发详解[M].北京:人民邮电出版社,2011:118~138. [2] 尉军军.基于嵌入式系统的电流互感器准确度测试仪的设计与实现[D].镇江:江苏大学,2010:56~60. [3] 韦东山.嵌入式Linux应用开发完全手册[M].北京:人民邮电出版社,2008:384~389. [4] 朱园.嵌入式Linux设备驱动的研究与开发[D].北京:北京邮电大学,2008:29~32.2.4 读函数
2.5 驱动模块编译和加载
3 驱动在气密性检测系统中的应用
4 结束语