在ARM9(S3C2440)上实现ZigBee协议--基于CC2420芯片
ZigBee是一种基于IEEE802.15.4规范的无线技术。它在短距离的低速率的数据通信有很大的优势,它的控制范围大概是200m-500m,传输速率也是250Kb/s左右。ZigBee依据802.15.4标准,在数千个微小的传感器之间相互协调实现通信,这些传感器只需要很少的能量,以接力的方式通过无线电波将数据从一个传感器传到另一个传感器,所以它们的通信效率非常高。采用ZigBee有几个好处:一是采用该技术的设备可以工作在无需获得许可的频带上;第二是它对的功耗达到了最低的限度,基于ZigBee的设备能用单个电池运行长达5年的时间;最后就是基于ZigBee协议组建的网络具有极大的可伸缩性,采用星型拓扑结构的网络的主设备可以支持4万多个节点。因此ZigBeed在短距离的无线控制及传感器网络中向到广泛的应用,本系统设计的时候采用了基于ZigBee协议的CC2420射频收发器。
外围接口电路
CC2420的应用电路
CC2420模块引脚
S3C2440与CC2420连接电路
2. 驱动程序设计
CC2420与S3C2440的接口主要是SPI的四线接口,如上图,其实用到就是三个
在编写CC2420的驱动程序前,先写一个驱动程序的框架,接着就可以在后面的开发过程中添加有关CC2420操作的代码了。下面是我最先写的一个框架:
struct file_operations这个数据结构提供文件系统的入口点函数,也就是访问设备驱动的函数,包括设备的读写操作,初始化操作等等。file_operations在定义。
static struct file_operations spi_fops =
{
.owner = THIS_MODULE,
.open = spi_open,
.read = spi_read,
.write = spi_write,
};
下面是对设备的打开操作,设备文件被打开后,应用程序就会得到一个对应于些设备的句柄。
static int spi_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "spi open");
return 0;
}
下面的两个函数是应用程序对设备进行读写操作时会调用的。由于这里涉及到数据在内核空间与数据空间的数据交换,因此在这里不能直接使用从用户空间传递进来的数据,也不能直接将内核空间的数据缓冲区直接给用户空间引用。Linux内核提供了两个函数可以实现用户空间与内核空间之间的数据拷贝,它们分别为copy_from_user ,copy_to_user。
static ssize_t spi_write(struct file *filp,const char *buf,size_t count,loff_t *f_ops)
{
char pTXBuffer[30];
copy_from_user(pTxBuffer, buf, count);
}
static ssize_t spi_read(struct file *filp, const char *buff, size_t count, loff_t *offp)
{
copy_to_user(buff, pRxBuffer, count);
return count;
}
Spi_init函数是在驱动模块加载时调用的,它是最先被执行的一个函数。在这里一般会进行一些设备的初始化工作,例如注册设备,端口映射。
Static int spi_init(void)
{
printk(KERN_ALERT "mscl_spi_bus initialization\n");
if(SPI_MAJOR)
{
SPI_DEV = MKDEV(SPI_MAJOR, SPI_MINOR);
reval = register_chrdev_region(SPI_DEV, 0, SPI_NAME);
}
else
{
reval = alloc_chrdev_region( &SPI_DEV, 0, 1, SPI_NAME);
SPI_MAJOR = MAJOR(SPI_DEV);
}
if (reval == -1)
{
printk(KERN_ERR "mscl_register device failed\n");
return -1;
}
SPI_CDEV = cdev_alloc();
if (SPI_CDEV != NULL)
{
cdev_init(SPI_CDEV, &spi_fops);
SPI_CDEV->ops = &spi_fops;
SPI_CDEV->owner = THIS_MODULE;
if (cdev_add(SPI_CDEV, SPI_DEV, 1))
{
printk(KERN_ALERT "Something wrong accors when register device\n");
}
else
{
printk(KERN_ALERT "mscl_Successfully adding the device\n");
}
}
}
spi_exit在驱动模块被卸载时会被调用,例如我们用rmmod命令时。在这里一般会进行一些资源的释放操作,例如释放之前注册的中断,端口内存等等。
static void __exit spi_exit(void)
{
}
module_init(spi_init);
module_exit(spi_exit);
module_init是要告诉内核spi_init是驱动的初始化函数,这样在加载驱动模块时该函数就会被执行去完成设备的初始化工作。
module_exit是要告诉内核spi_exit是驱动模块的清除函数,在移除驱动模块时这个函数就会被执行。
module_init,module_exit都只是一个宏,其实就是告诉编译器将它们放到代码段的init节中。
在具备了驱动程序的基本框架后,下面就可以逐渐完善它,增强它的功能。
l I/O端口映射
I/O端口空间非常有限,所有的总线设备都以内在映射方式来映射它的I/O端口和外设内存。但是驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内,然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。Linux内核提供了ioremap()函数将I/O内存资源的物理地址映射到核心虚地址空间中。
下面的几个函数提供了映射后SPI0的相关寄存器,外部中断控制寄存器。
rSPCON0 = ioremap(S3C2410_SPCON0, 1);
rSPSTA0 = ioremap(S3C2410_SPSTA0, 1);
rSPPIN0 = ioremap(S3C2410_SPPIN0, 1);
rSPPRE0 = ioremap(S3C2410_SPPRE0, 1);
rSPTDAT0 = ioremap(S3C2410_SPTDAT0, 1);
rSPRDAT0 = ioremap(S3C2410_SPRDAT0, 1);
rEXINT0 = ioremap(EXINT0, 4);
rEXINT1 = ioremap(EXINT1, 4);
rEXINT2 = ioremap(EXINT2, 4);
这里利用ioremap函数进行端口的映射后就会得到这些寄存器的虚地址,在后面对这些寄存器进行读写操作时,都是利用这些返回的虚地址,不能利用它们的物理地址去进行寄存器的读写。
l SPI端口设置
为了能够使S3C2440能够与CC2420进行数据传输,S3C2440的SPI必须正确地配置其传输格式。
(1) 将GPE11-13脚设置成SPI0的I/O口。
s3c2410_gpio_cfgpin(S3C2410_GPE11, S3C2440_PIN_MISO0);
s3c2410_gpio_cfgpin(S3C2410_GPE12, S3C2440_PIN_MOSI0);
s3c2410_gpio_cfgpin(S3C2410_GPE13, S3C2440_PIN_SCLK0);
这里将GPIO的E端口的三个引脚映射成SPI的通道0的三个功能脚(MISO,MOSI,SCLK),注意程序中其它的地方要将它们映射成其它其它功能的I/O口时必须之后将其它还原,以免影响后面对CC2420的操作。
(2) 设置SPI的数据传输格式
Spivalue=S3C2410_SPCON_SMOD_POLL|S3C2410_SPCON_ENSCK|S3C2410_SPCON_MSTR|S3C2410_SPCON_CPOL_LOW|S3C2410_SPCON_CPHA_FMTA|0<<0;
iowrite8(spivalue, rSPCON0);
iowrite8是Linux内核提供的一个读写外部IO口的函数,一般用它来设置外部设备的一个寄存器。上面的操作是设置SPI的控制寄存器的相关的控制位:上升沿收发数据,主模式,正常模式。
(3) I/O口设置
根据硬件接口电路,S3C2440与CC2420的其它控制口也必须正确地配置,因些必须将相关的I/O口映射到接口电路设计时的功能口。下面的配置请参照硬件接口设计一章关于CC2420接口设置一节。
s3c2410_gpio_cfgpin(S3C2410_GPG2, S3C2440_PIN_REST);
s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2440_PIN_CS);
s3c2410_gpio_cfgpin(S3C2410_GPG5, S3C2440_PIN_VREG);
s3c2410_gpio_cfgpin(S3C2410_GPG6, S3C2440_PIN_FIFOP);
s3c2410_gpio_cfgpin(S3C2410_GPG7, S3C2440_PIN_FIFO);
s3c2410_gpio_cfgpin(S3C2410_GPG9, S3C2440_PIN_SFD);
s3c2410_gpio_cfgpin(S3C2410_GPG10, S3C2440_PIN_CCA);
s3c2410_gpio_cfgpin这个函数是将CC2440的某一个I/O口设置成参数2指定的功能,其中参数2是一个宏,例如每一条语句就将GPIO的G2引脚设置成REST脚的功能,即是输入脚。基本上每一个宏都对应着CC2420上的某一个引脚上的一个功能,如输入/输出/中断,具体的请参见程序及原理图。
l 中断设置
为了方便驱动的开发,CC2420的一些引脚接到S3C2440的中断脚上,如SFD,FIFO,FIFOP,它们为驱动的调试提供了相关的信息。中断的设置包括了外部中断的中断模式,中断触发方式等的设置。软件的配置如下,硬件的接口详细细节请参考硬件接口一章。
(1) 外部中断模式为IRQ
gpiovalue = (~(EINT8_23)) & ioread32(S3C2410_INTMOD);
iowrite32(gpiovalue, S3C2410_INTMOD);
(2) 允许外部中断14,15,17
gpiovalue = ioread32(S3C2410_INTMSK);
gpiovalue &= (~(EINT8_23));
iowrite32(gpiovalue, S3C2410_INTMSK);
// 允许FIFOP,FIFO,SFD的中断
gpiovalue = ioread32(S3C2410_EINTMASK);
gpiovalue &= (~((1<<14)|(1<<15)|(1<<17)));
iowrite32(gpiovalue, S3C2410_EINTMASK);
其中S3C2440的中断模块是这样的,其中有6个外部中断,其中的第4个中断又对应有4个子中断(EINT4-7),第5个外部中断源又对应有16个子中断(EINT8-23),这些中断在S3C2440的处理器的外部引脚上都有唯一的一个引脚与之想对应的,因此S3C2440具有非常丰富的外部中断源,与外设的交互性是很强大的,在接口设计时也是非常的方便,我们可以用中断的方式同步再者之间的工作,提高处理器的CPU利用率。
(3) 外部中断的触发方式选择
gpiovalue = ioread32(rEXINT1)&(~(0xFF00F000));
gpiovalue |= INTTRI_FIFOP | INTTRI_FIFO;
iowrite32(gpiovalue,rEXINT1);
gpiovalue = ioread32(rEXINT2)&(~(0xF0));
gpiovalue |= INTTRI_SFD;
iowrite32(gpiovalue,rEXINT2);
l 中断注册
前面设置好了外部中断,现在就要向内核注册了,将相关的中断服务程序关联起来,这样当发生了外部中断就会进入到
先前定义的中断服务程序了。
request_irq(IRQ_EINT14, basicRfRecvPacket, SA_INTERRUPT, SPI_NAME, NULL);
request_irq(IRQ_EINT17, SFD_Interrupt, SA_INTERRUPT, "SFD", NULL);
request_irq(IRQ_EINT15, FIFO_Interrupt, SA_INTERRUPT, "FIFO", NULL);
basicRfRecvPacket,SFD_Interrupt,FIFO_Interrupt这三个中断服务程序分别是CC2420的FIFOP,SFD,FIFO的中断服务程序。
l CC2420的操作
在前面的几个步骤中,一个CC2420的驱动程序已经基本完成了,但是还没有涉及到对CC2420的操作。其实对它的读写操作也就是针对SPI的读写操作,只是在收发数据时还要结合在前面介绍的关于CC2420的读写时序,数据传输格式等。
下面简要地介绍一下CC2420的读写操作。首先要先实现对CC2420的读写操作必须要先实现对SPI数据的正确读写。下面的两个宏是两个对SPI数据寄存器的读写的最基本操作:
#define FASTSPI_TX(x) \
do { \
FASTSPI_WAIT(); \
iowrite8(x, rSPTDAT0);\
FASTSPI_WAIT(); \
} while (0)
#define FASTSPI_RX(x) \
do { \
FASTSPI_RX_GARBAGE();\
x = ioread8(rSPRDAT0); \
} while (0)
有了这两个基本的操作宏,则对 CC2420的操作就可以完成了,例如我们要读取CC2420的状态字节,就可以先用FASTSPI_TX向CC2420发送一个命令脉冲,等待发送完毕后就可以再用FASTSPI_RX读取到CC2420的状态字了。其它的例如RAM,寄存器等的读写操作也是相似的,只要在设计的时候参照CC2420的读写时序及数据传输格式就可以了,读写时序图可以参照CC2420的技术手册