0 引言
随着经济技术的不断发展,以及自动化程度的提高,越来越多的场合需要用到远程控制。在承接的国家大学生创新性实验计划项目——基于无线跳传网络的智能抄表系统中,中继(SINK)模块,即采用S3C2410在WinCE下驱动射频芯片CC1101作手持终端控制器。其中,在WinCE下驱动CC1101成为该项目的一个难点。CC1101使用SPI通信,而SPI驱动属于WinCE串口驱动的一种,是流驱动。本文将介绍WinCE 5.0下,C11 01的SPI驱动程序设计。
1 CC1101的SPI接口特性
CC1101基于TI公司的0.18 μm CMOS晶体SmartRF04技术,是一种低成本、真正单片的UHF收发器,为低功耗无线应用而设计。电路主要设定在315 MHz,433 MHz,868 MHz和915 MHz的ISM(工业,科学和医学)和SRD(短距离设备)频率波段,CC1101的主要操作参数和64位发送/接收FIFO可通过SPI接口控制,具有14个命令寄存器,47个普通配置寄存器和12个状态寄存器,通过4线SPI兼容接口(SI,SO,SCLK和CSn)配置。其中,SPI接口是一种同步串行通信接口,CSn是芯片选择管脚,当该管脚为低电平时,SPI接口可以通信;SI和SO为数字传输管脚,SI用于数据输入,SO用于数据输出;SCLK为同步时钟,在时钟的上升沿数据被写入或读出。CC1101中SPI接口的读/写操作方式如图1所示。
CC1101的配置、命令发布和发射接收缓存的数据读取都通过SPI完成,SPI的操作都由主机控制,对CC1101来说,主机的控制操作即是发送的headerbyte。下面介绍两种主要状态下的主机操作。
(1)读寄存器、读状态
①写入头字节(R,0/1,address);
②dummy write为从设备提供一个CLK,从SPI接收数据即读出address的数据。如果是突发访问n个寄存器,则重复n次。
(2)写寄存器、写命令
①写入头字节(W,0/1,address);
②写入数据字节(data)。如果是突发访问n个寄存器,则重复n次。
由于ARM的SPI硬件操作屏蔽了对CLK的直接控制,读的时候必须要dummy write为从设备提供一个CLK,可以写0xFF。在每次写前要确保SPI空闲,并且没有发生溢出,写后要确保发送完毕,再进行其他SPI操作。
2 WinCE下SPI驱动程序开发
Win CE下的SPI驱动属于流驱动。流驱动是能够导出流接口函数的驱动程序。在设计此类驱动时,把设备驱动程序当成一种特殊的文件,接口函数与一般的文件APl函数一样,比如CreatFile(),WriteFile(),ReadFile()和CloseHandle()等,因此在应用程序设计时可以通过使用文件系统API来调用驱动程序,以达到访问设备的目的。SPI驱动是一个动态链接库(DLL),可以被加载到内核空间,成为内核模式驱动。
SPI驱动程序是操作系统与硬件之间的接口,是对硬件设备的抽象。操作系统可以通过驱动程序来对设备进行操作和管理。当应用程序需要读取底层的物理器件输出时,就必须通过操作系统内核来加载特定的设备驱动程序,通过驱动程序来与底层的硬件进行通信,然后将读取信息传入应用程序中。当为WinCE 5.0添加外围设备时,必须以流接口驱动方式提供给操作系统内核,再由操作系统对其进行加载,加载正确后,才可以在应用程序中通过标准的I/O函数调用底层的驱动。
WinCE 5.0设备驱动程序开发中最重要的是设备相关寄存器的配置。寄存器的配置包括将寄存器地址映射到内核进程的虚拟地址,在串口操作的不同阶段配置好各种寄存器。这里,给出了在无线通信领域中,基于S3C2410和WinCE 5.0的具体应用方案,并在该应用测试方案上设计基于WinCE 5.0的设备接口驱动。现在开始建立WCE Dynamic-Link Library工程SPI_Driver,然后编写驱动程序接口函数。基于Win CE设备流驱动程序的开发,不管是什么设备,它们的实现框架都是相同的,只要把相关流接口实现即可。下面介绍几个常用的函数和测试操作。
2.1 DllEntry()函数
该函数是动态链接库的入口,每个动态链接库都需要输出这个函数,但它只在动态库被加载和卸载时才被调用,它是每个动态链接库最早被调用的函数,一般用它做一些全局变量的初始化。
2.2 SPI_Init()函数
该函数是驱动程序动态库被成功装载后第一个被调用的函数。它的调用时间仅次于DllEntry()函数,驱动程序应当在这个函数中初始化硬件,如果初始化成功,就分配一个自己的内存空间,将自己的状态保存起来,并且将该内存块的地址作为一个DWORD值返回给上层。设备管理器就会在调用SPI_Open()时将该句柄传回。如果初始化失败,则返回零以通知这个驱动程序没有加载成功,先前所分配的系统资源应该全部释放,此程序的生命即告终止。VirtualAlloc()和VirtualCopy()函数用来实现虚拟内存空间的分配,并且映射到硬件的物理地址,在Ini-tAddrlO()和InkAddrSPIreg()中被调用。初始化软件流程如图2所示。
2. 3 SPI_Open()函数和SPI_Close()函数
当用户程序调用CreateFile()打开这个设备时,设备管理器就会讽用此驱动程序的SPI_Open()函数。
当用户程序调用CloseHandle()关闭这个设备时,SPI_Close()函数就会被设备管理器调用。参数hOpenContext是SPI_Open()返回给上层的那个值。 SPI_Close()函数应该做与SPI_Open()相反的事情,具体包括释放SPI_Open()分配的内存,将驱动程序被打开的计数减少等。
2.4 SPI_IOControl()
几乎一个驱动程序的所有功能都可以在这个函数中实现。对于一类CE自身已经支持的设备,它们已经被定义了一套I/O操作,只需按照各类设备已经定义的内容去实现所有的I/O操作。当要实现一个自定义的设备时,就可以随心所欲定义自已的I/O操作。下面是一个读取寄存器值的操作函数。
驱动程序SPI_IOControl()里调用了读寄存器函数读取CC1101的FSCTRL1寄存器的值,所以只要应用程序里调用DevicelOControl(),就可以让串口输出读取FSCTRL1的值。应用程序里具体调用如下3个函数:
读出的数据就保存在cBuffer_in[]数组中了,用串口就可以将其中内容正确输出。
2.5 设备驱动程序的内核加戴和注册表设置
流驱动是由设备管理器来管理的。当系统启动时,设备管理器被加载到内核中,由它全程监控驱动程序的执行过程。设备管理器通过调用ActivateDeviceEx()函数来加载指定的驱动,而该函数的第一个参数是一个注册表路径,这就要求驱动程序被加载的一个必要条件是把自己的信息记录在注册表中。因此需在Platform.reg中添加如下内容:
另外,还要修改SPI_Driver.def文件,在里面列出所有SPI驱动接口函数,并在platform.bib中填加一行内容:
修改platform/BSP/drvers目录下的dirs文件,加上一行SPI_Driver。
以上步骤完成了WinCE 5.0下设备驱动程序的设计,通过Platform Builder环境进行编译,生成特定的dll文件,然后将其重新打包,并编译进WinCE内核中重新生成NK.bin,这样就可以在应用程序中通过标准的文件I/O函数来调用这个驱动函数,从而完成应用层与物理设备的通信。
2.6 测试驱动程序
在此使用eMbedded Visual C++4.0编写测试应用程序,用WinCE驱动调试助手加串口输出信息进行调试。推荐使用博客园的WinCE驱动调试助手,这个工具允许在系统里动态地加载和卸载驱动程序,避免每次都要打包生成NK,再下载到板子上。以下是采用串口输出变量的方法:
以下为测试第2.4节读寄存器得到的结果:
因为InitCC1101()里给FSCTRL1配置的值是0x0A,由此可以看出已经正确读出寄存器值。同时也验证了其他寄存器所得到的数据完全正确。这足以说明SPI驱动程序的通信是成功的。
3. 结语
本文完成了WinCE 5.0下对SPI驱动程序的开发,提出了CC1101与S3C2410之伺的SPI通信方案。经过测试,该方案已在实际系统中得到了实现。系统充分利用SPI总线接口功能完善、时序简单等特点,提高了系统的可靠性。同时,CC1101与S3C2410处理器结合可广泛应用于嵌入式远程控制和数据无线传输。