在mini2440上面搞定CC2500物理层驱动

mini2440上面搞定CC2500驱动



  1. 写在前面


最近基本搞定了 CC2500 linux 下面的驱动,在这个过程中遇到了好多问题,特此总结出来和大家分享。但是需要注意的事情是:
第一,本文不负责程序的具体讲解,诸如每一行程序都讲什么:这个在程序的里面有注释。本篇文章更侧重于从整体结构上让大家对于 linux 下的 CC2500DE 驱动有一个整体的了解,如果到时候需要深入研究的时候,再来看具体的代码;
第二,很多师弟师妹没有习惯在 linux 下编程,或曰,在操作系统下面写程序;并且,对于本专业的东西理解并不扎实。但是,文章中不可能就每一个涉及到的概念都大加讲解。因此,这篇文章还是需要有一些基础才能来阅读的,如果遇到什么不明白的地方,请及时来问我,或者跟帖,或者自己查资料。毕竟,这个驱动花费了我半个多月的时间,其中遇到的问题,不可能在一篇文章中就讲得清清楚楚。


  1. 背景知识


如果大家看过实验室的 WSN 方面的程序,可能会被其中的架构搞得天昏地暗。这里,简单的帮助大家把实验室的 WSN 的软件、硬件方面捋一捋。
说到软件,就不能不说硬件。我们的节点由两部分组成,一个是单片机,一个是 CC2500 的射频模块。射频模块通过 SPI 接口和单片机进行通信。除了 SPI 模块用到的 4 条线,实际上 CC2500 还需要告诉单片机“我接收到一个数据”,这个地方 CC2500 可以通过配置 GDO 来实现。我们这里采用的 CC2500 GDO0 作为单片机的一个外部中断,当接收到数据的时候, GDO0 就会变为低电平,然后触发 CC2500 的中断,从而接收数据。
那么, CC2500 究竟是如何来进行数据发送、接收的呢?其实很简单。如果大家稍微用过一点可以控制的芯片就会知道,很多芯片内部都有寄存器, CC2500 之所以能够完成发送、接收的功能,就是通过配置寄存器来完成的。比较重要的配置有信道、波特率、信号强度等等,另外, CC2500 还有 64bytes RXFIFO 64bytes TXFIFO ,他们的作用就是用来发送和接收。当配置 CC2500 结束之后,如果用来发送,那么就把数据放在 TXFIFO 里面,然后把 CC2500 的状态变为发射状态,数据就自动发送出去了。同样的道理,当 RXFIFO 里面接收到了数据之后, CC2500 就会如前所说,发出一个中断,通知单片机来读取数据。
单片机通过 SPI 接口读出了 CC2500 中的数据,就需要进行简单的处理了。我们的节点是遵循 802.15.4 协议的,如果有兴趣,大家可以去看一看这个协议的内容。对于我们来说,比较重要的就是帧的结构。下面这个是物理层帧的结构:
字节: 4
1
1
可变
先导码
帧定界符

帧长度(7位)

保留位(1位)
负载内容

同步头

PHY

PHY负载

简单的讲,因为是无线通信,没有一个时钟的存在,因此本质上所有的无线通信都是一个异步的通信。那么,就需要发送和接收的双方首先对数据进行同步——这个就是同步头的作用。随后的是 PHY 的头,很简单,就是一个长度。换句话说,每个帧不能够超过 2 8 次方减一个数据( 127 个)。随后就是负载的内容了,简单吧?另外,由于 CC2500 是一个 802.15.4 兼容的芯片,这一部分内容都已经固化在芯片里面了,像同步头这种东西,都是芯片帮我们完成(当然也可以自行配置)。我们只需要把写入的数据大小和数据内容写入即可。
虽然简单,但是还有一个问题:每次从 CC2500 中读取了数据,存放到哪里呢?有的同学可能想的比较简单,比如建立一个大数组,收到多少个数,放在里面就行了。但是,这样就会遇到问题:如果收到了两个包,上层来不及处理怎么办?建立两个大数组?更多的包呢?当然,我们可以通过建立好多个数组来解决这个问题。但是,从效率上讲,这个是非常笨的一个办法。因此,我们建立了一个环形缓冲区 PHY_RX_BUFFER ,来解决这个问题。环形缓冲区的具体原理不过多涉及,可以自行百度。
另外一个问题就是,如果我们从 CC2500 中接收到了数据,那么应该怎样才能告诉上层来进行处理呢?这里就要用到操作系统的消息机制了。如果确定接收到了一个数据,那么就给上层发送一个消息,来通知上层。上层接收到这个消息,就会知道接收到了数据。
当从 CC2500 读取到了数据,因为物理层没有什么东西,我们就直接通知 MAC 层来处理数据。 MAC 层的结构如下:
字节: 2
1
0/2
0/2/8
0/2
0/2/8
可变
2
帧控制域
序列号
目的 PAN 标示符
目的地址
PAN 标示符
源地址
帧负载
FCS
地址域


MAC 帧头
MAC 负载
MFR

具体内容可以参见老师的《无线传感器网络技术与工程应用》这本书,这里不再详细的讲,大概内容就是,会有一系列的帧头,然后是帧数据,最后有校验。而我们最关心的就是帧的内容。但是,帧头也比较麻烦,因此我们需要首先对帧头进行处理,把数据给“剥离”出来,这也是基本上所有的协议栈解析协议的方法。


  1. 硬件结构


硬件上,我们采用的是 mini2440 开发板,和实验室的 CC2500 节点(把单片机焊掉,然后把 mini2440 的相应管脚接到上面)。


Mini2440 方面,我们用的是 CON8 这个引脚,具体连线如下:
EINT8 对应 S3C2440 GPG0 引脚,连接到了 CC2500 GDO2
EINT11 对应 S3C2440 GPG3 引脚,连接到了 CC2500 CSN
EINT13 对应 S3C2440 GPG5 引脚,连接到了 CC2500 SO
EINT14 对应 S3C2440 GPG6 引脚,连接到了 CC2500 SI
EINT15 对应 S3C2440 GPG7 引脚,连接到了 CC2500 CLK
EINT19 对应 S3C2440 GPG11 引脚,连接到了 CC2500 GDO0
除此之外,还有 3.3V 电源和 GND


  1. Linux下驱动编写


为什么要用驱动呢?简单的说,就是为了在 linux 下完成用户空间和内核空间的交互。本质上,驱动就是完成了两件事情:第一,初始化 SPI 接口,并且通过 SPI CC2500 完成通信。第二,完成读写函数, read write ,通过读写函数的接口来对 CC2500 进行操作。
附件里面有几个文件,这里简单讲解一下:
Common.h 主要是包含了很多 linux 的头文件,并且对一些数据类型做了定义
CC2500.h 里面主要是对 CC2500 的寄存器做了一些定义,没啥东西
Driver.h 主要是定义了很多和 CC2500 进行通信,并且利用 CC2500 进行发送和接收的内容
Phy.h 主要是完成了一些诸如信道设定、通信速率等功能,并且把 CC2500 里的数据读到环形缓冲区里面
CC2500.c 这个就是完成设备注册、读写函数、 iocontrol 等功能
这样有了一个整体的把握之后,不用看很多代码,学会使用就好。


  1. 驱动接口使用


那么,这个驱动怎样使用呢?主要是通过 read/write/ioctl 三个接口来实现的。
在应用程序中 read/dev/CC2500 这个文件,如果有数据,会返回相应的数据。其中,返回的数据中第一个字节为数据的长度(不包括该字节本身),后面为相应的数据。
如果是写文件的话,只需要向 /dev/CC2500 中写入相应的数据即可。写入的时候需要注意,第一个字节同样为数据长度(不包括该字节本身),后面跟相应的数据。当写入的字节合适的时候,驱动会自动执行发送函数。如果写入的字节数量和写入的数据第一个字节不匹配的时候,会返回错误。
ioctl 主要有这样几种命令:


CC2500_IOC_PHY_DETECT_STATUS
表示将 CC2500 的状态变为 RX
CC2500_IOC_PHY_GET_BAUDRATE
读取 CC2500 的波特率,返回的是 1,2,3,4 ,分别是 2.4k,10k,250k,500k 的波特率
CC2500_IOC_PHY_SET_BAUDRATE
设定 CC2500 的波特率,后面的参数是 1,2,3,4 ,分别是 2.4k,10k,250k,500k 的波特率
CC2500_IOC_PHY_GET_TXPOWER
获取发送功率,返回的是在发送功率表 {0x00, 0x50, 0x44, 0xC0, 0x84, 0x81, 0x46, 0x93, 0x55, 0x8D, 0xC6,0x97, 0x6E, 0x7F, 0xA9, 0xBB, 0xFE, 0xFF } 中的数组下标
CC2500_IOC_PHY_SET_TXPOWER
设定发送功率,参数的是在发送功率表 {0x00, 0x50, 0x44, 0xC0, 0x84, 0x81, 0x46, 0x93, 0x55, 0x8D, 0xC6,0x97, 0x6E, 0x7F, 0xA9, 0xBB, 0xFE, 0xFF } 中的数组下标
CC2500_IOC_PHY_GET_CHANNEL
获取频段,参数是频段表 {0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0,0xB0, 0xC0, 0xD0, 0xE0, 0xF0 } 中的数组下标
CC2500_IOC_PHY_SET_CHANNEL
设定频段,参数是频段表 {0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0,0xB0, 0xC0, 0xD0, 0xE0, 0xF0 } 中的数组下标


  1. 应用层程序编写


这里的应用层主要是将 mac 层实现了。其实, mac 层的作用也很简单,主要是对要发送的包进行封装,接收回来的包进行包的解析。这一部分的内容还不全面,仅仅是将原来的程序移植过来,还有很多需要改动的地方,有待补充。因此,这里只是提供源代码,具体的说明请参见程序内部


  1. 如何配置内核文件


1 首先修改 /opt/FriendlyARM/mini2440/linux-2.6.32.2/drivers/char/Kconfig
加入如下几句话:
config CC2500_DRIVER


        tristate "CC2500 driverfor FriendlyARM Mini2440 development boards"


        depends on MACH_MINI2440


        default m if MACH_MINI2440


        help


        this is CC2500 driver forFriendlyARM Mini2440 development boards




修改 Makefile ,让 CC2500 的驱动可以编译。
加入如下几句话:


obj-$(CONFIG_CC2500_DRIVER)     +=CC2500inuse.o


CC2500inuse-objs :=./CC2500.in_use/cc2500.o ./CC2500.in_use/phy.o./CC2500.in_use/driver.o


其中, CONFIG_CC2500_DRIVER  需要和 Kconfig 中的 configCC2500_DRIVER
保持一致。
CC2500inuse-objs 这句话,意思是 CC2500inuse.ko 是由哪几个文件组成的。这样就实现了模块化的处理。
需要注意的是, CC2500inuse-objs 和冒号之间需要有个空格,不然会出错误。这个地方我搞了好久。
具体的 Makefile 的格式也可以参考这两篇文章:
http://blog.csdn.net/tommy_wxie/article/details/7282463
http://hi.baidu.com/wjq_qust/blog/item/97ddbdfdfb2e541309244d30.html


  1. 调试过程中的一些问题


CC2500 驱动调试的过程中,遇到的一些问题
第一,在调试的过程中,发现板子和 2500 没有办法进行通信。一开始怀疑是 2500 芯片的问题,后来确认 2500 芯片没有问题之后,用示波器看了一下波形,发现 SPI 引脚上没有输出。进而发现,对 SPI 寄存器进行读写的时候,无论写入是什么,读出的都是 0 。后来发现,原来是 PCLK (也就是 SPI 模块的时钟)没有使能,囧。设置了寄存器之后,问题就解决了。
第二, CC2500 驱动没有办法自动在 /dev/ 目录下面创建节点。这个问题可以参考下面几篇文章:
http://blog.csdn.net/cjok376240497/article/details/6848536
整体来说,创建节点的工作是这样的:
我们通过 udev 来创建节点。但是, udev 是应用层的东东,不要试图在内核的配置选项里找到它 ; 加入对 udev 的支持很简单,只要在驱动初始化的代码里调用 class_create 为该设备创建一个 class ,再为每个设备调用 class_device_create 创建对应的设备。
换句话说,首先要为每一个设备创建一个类。然后用这个类去申请一个设备节点。
第三,在调试的过程中,发现只要一启动 CC2500 ,那么液晶屏就熄灭了。调试了半天也没有找到原因。
最后发现,在配置 S3C2440 GPGCON 寄存器的时候,对寄存器直接进行了覆盖性的修改;而该寄存器还管理着 LCD 的电源 ... 于是,对寄存器进行覆盖性修改的时候,悲剧了 ...
总结一下吧,在 linux 2500 驱动编写的过程中,如果涉及到寄存器级别的操作,和单片机一样,必须十分注意各个模块之间的关系,以及相关模块的寄存器配置,不要认为 linux 会全部给你配好!

你可能感兴趣的:(在mini2440上面搞定CC2500物理层驱动)