Chinaunix首页 | 论坛 | 博客
  • 博客访问: 495124
  • 博文数量: 63
  • 博客积分: 1187
  • 博客等级: 少尉
  • 技术积分: 706
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-05 16:53
个人简介

Must Be

文章分类

全部博文(63)

文章存档

2019年(1)

2017年(4)

2016年(6)

2015年(2)

2014年(1)

2013年(3)

2012年(10)

2011年(36)

我的朋友

分类: 嵌入式

2012-03-30 13:29:25

在ARM9(S3C2440)上实现ZigBee协议--基于CC2420芯片

ZigBee是一种基于IEEE802.15.4规范的无线技术。它在短距离的低速率的数据通信有很大的优势,它的控制范围大概是200m-500m,传输速率也是250Kb/s左右。ZigBee依据802.15.4标准,在数千个微小的传感器之间相互协调实现通信,这些传感器只需要很少的能量,以接力的方式通过无线电波将数据从一个传感器传到另一个传感器,所以它们的通信效率非常高。采用ZigBee有几个好处:一是采用该技术的设备可以工作在无需获得许可的频带上;第二是它对的功耗达到了最低的限度,基于ZigBee的设备能用单个电池运行长达5年的时间;最后就是基于ZigBee协议组建的网络具有极大的可伸缩性,采用星型拓扑结构的网络的主设备可以支持4万多个节点。因此ZigBeed在短距离的无线控制及传感器网络中向到广泛的应用,本系统设计的时候采用了基于ZigBee协议的CC2420射频收发器。

  1.  外围接口电路

在ARM9(S3C2440)上实现ZigBee协议--基于CC2420芯片 - zybzyb1 - 博客

CC2420的应用电路

在ARM9(S3C2440)上实现ZigBee协议--基于CC2420芯片 - zybzyb1 - 博客

CC2420模块引脚

在ARM9(S3C2440)上实现ZigBee协议--基于CC2420芯片 - zybzyb1 - 博客

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的技术手册

阅读(2793) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~