-
写在前面
最近基本搞定了 CC2500 在 linux 下面的驱动,在这个过程中遇到了好多问题,特此总结出来和大家分享。但是需要注意的事情是:
第一,本文不负责程序的具体讲解,诸如每一行程序都讲什么:这个在程序的里面有注释。本篇文章更侧重于从整体结构上让大家对于 linux 下的 CC2500DE 驱动有一个整体的了解,如果到时候需要深入研究的时候,再来看具体的代码;
第二,很多师弟师妹没有习惯在 linux 下编程,或曰,在操作系统下面写程序;并且,对于本专业的东西理解并不扎实。但是,文章中不可能就每一个涉及到的概念都大加讲解。因此,这篇文章还是需要有一些基础才能来阅读的,如果遇到什么不明白的地方,请及时来问我,或者跟帖,或者自己查资料。毕竟,这个驱动花费了我半个多月的时间,其中遇到的问题,不可能在一篇文章中就讲得清清楚楚。
-
背景知识
如果大家看过实验室的 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
| |||||
具体内容可以参见老师的《无线传感器网络技术与工程应用》这本书,这里不再详细的讲,大概内容就是,会有一系列的帧头,然后是帧数据,最后有校验。而我们最关心的就是帧的内容。但是,帧头也比较麻烦,因此我们需要首先对帧头进行处理,把数据给“剥离”出来,这也是基本上所有的协议栈解析协议的方法。
-
硬件结构
硬件上,我们采用的是 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
-
Linux下驱动编写
为什么要用驱动呢?简单的说,就是为了在 linux 下完成用户空间和内核空间的交互。本质上,驱动就是完成了两件事情:第一,初始化 SPI 接口,并且通过 SPI 和 CC2500 完成通信。第二,完成读写函数, read 和 write ,通过读写函数的接口来对 CC2500 进行操作。
附件里面有几个文件,这里简单讲解一下:
Common.h 主要是包含了很多 linux 的头文件,并且对一些数据类型做了定义
CC2500.h 里面主要是对 CC2500 的寄存器做了一些定义,没啥东西
Driver.h 主要是定义了很多和 CC2500 进行通信,并且利用 CC2500 进行发送和接收的内容
Phy.h 主要是完成了一些诸如信道设定、通信速率等功能,并且把 CC2500 里的数据读到环形缓冲区里面
CC2500.c 这个就是完成设备注册、读写函数、 iocontrol 等功能
这样有了一个整体的把握之后,不用看很多代码,学会使用就好。
-
驱动接口使用
那么,这个驱动怎样使用呢?主要是通过 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 } 中的数组下标
-
应用层程序编写
这里的应用层主要是将 mac 层实现了。其实, mac 层的作用也很简单,主要是对要发送的包进行封装,接收回来的包进行包的解析。这一部分的内容还不全面,仅仅是将原来的程序移植过来,还有很多需要改动的地方,有待补充。因此,这里只是提供源代码,具体的说明请参见程序内部
-
如何配置内核文件
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
2 修改 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.youkuaiyun.com/tommy_wxie/article/details/7282463
http://hi.baidu.com/wjq_qust/blog/item/97ddbdfdfb2e541309244d30.html
-
调试过程中的一些问题
CC2500驱动调试的过程中,遇到的一些问题
第一,在调试的过程中,发现板子和2500没有办法进行通信。一开始怀疑是2500芯片的问题,后来确认2500芯片没有问题之后,用示波器看了一下波形,发现SPI引脚上没有输出。进而发现,对SPI寄存器进行读写的时候,无论写入是什么,读出的都是0。后来发现,原来是PCLK(也就是SPI模块的时钟)没有使能,囧。设置了寄存器之后,问题就解决了。
第二,CC2500驱动没有办法自动在/dev/目录下面创建节点。这个问题可以参考下面几篇文章:
http://blog.youkuaiyun.com/cjok376240497/article/details/6848536
整体来说,创建节点的工作是这样的:
我们通过udev来创建节点。但是,udev是应用层的东东,不要试图在内核的配置选项里找到它;加入对udev的支持很简单,只要在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用class_device_create创建对应的设备。
换句话说,首先要为每一个设备创建一个类。然后用这个类去申请一个设备节点。
第三,在调试的过程中,发现只要一启动CC2500,那么液晶屏就熄灭了。调试了半天也没有找到原因。
最后发现,在配置S3C2440中GPGCON寄存器的时候,对寄存器直接进行了覆盖性的修改;而该寄存器还管理着LCD的电源...于是,对寄存器进行覆盖性修改的时候,悲剧了...
总结一下吧,在linux下2500驱动编写的过程中,如果涉及到寄存器级别的操作,和单片机一样,必须十分注意各个模块之间的关系,以及相关模块的寄存器配置,不要认为linux会全部给你配好!
====
http://blog.youkuaiyun.com/zxzxy1988/article/details/6929065
本文详细介绍了CC2500在Linux下的驱动编写过程,涵盖了硬件结构、驱动接口使用及调试过程中遇到的问题等内容。
12万+

被折叠的 条评论
为什么被折叠?



