一款传感器的介绍 nRF24L01

  我很喜欢linus和stallman, 这样说也许你一下就明白了, 做为GNU的支持者, 本文的内容是free的, 可以自由的使用/复制/发布/修改, 但是它是CopyLeft, 任何基于本文的衍生物同样应该是自由的, 并且本文是有版权的, 所有人和原作者都是NCWU的小杜

  本文介绍nRF24L01 它是一款高度集成化的2.4G无线传输芯片, 归为传感器很牵强, 但我实在想不到有什么具有代表性的传感器可写, 经典的传感器在课堂上都直接或间接地介绍到了. 在最近的一次项目中我用到了角度传感器, 但那是一个输出模拟量的传感器, 用AD去读, 并没有什么特殊性. 我大致想了一下, 传感器按接口也许可以分为如下几类:

  输出模拟量 湿度传感器HIH等 用AD就好
  输出逻辑电平 红外传感器等 任何一款CPU都可以(必要时,通过电平转换)区分出0, 1吧
  1-wire DS18B20 DALLAS公司的,它实在太经典了
  SPI serial peripheral interface 如字面一样
  同步时序的 老实说我不知道它叫什么 就是至少2根线,一个DATA 一个CLK那种

  我想想还是介绍一款SPI的好了, 因为除SPI之外其它的传感器, 只要接口相同, 在使用上并没有十分大的区别, 全世界的Analog-to-Digital都是用同样的方法相似的代码, 这样说也许太绝对, 但是8位的AD和12位的AD并没有本质的区别. 而对于不同传感器, SPI读写虽然完全一致, 但是在SPI层之上的协议层或应用层, 完全不同, 因为命令集千差万别, 如果这么想的话, 介绍一个基于SPI的传感器是完全可行的, 因为它不是妇孺皆知的废话, 那就nRF24L01吧

  

  首先是读写, 对于SPI, 它的引脚再熟悉不过了 CSN MOSI MISO SCLK, 有不懂百度之

 ;SPI - Serial Peripheral Interface, Motorola
;MOSI - spi bus Master Output / Slave Input
;MISO - spi bus Master Input / Slave Output
;SCLK - SPI CLK

汇编版本

 1 ;Assemble Version
2 CLR CS
3 CLR SCK
4
5 RLC A
6 MOV R0, #8
7
8 loop:
9 MOV MOSI, C
10 SETB SCK
11 MOV MISO, C
12 CLR SCK
13 RLC A
14
15 DJNZ R0, loop
16
17 SETB CS

C版本

 1 ;C Language Version
2 register BYTE i = 8;
3 while (i--) {
4 MOSI = (val & 0x80);
5 byte <<= 1;
6 SCK = 1;
7 val |= MISO;
8 SCK = 0;
9 }
10 return val;

如果是使用Keil_C 我想还是要把代码写完整, 因为大部分人都不懂得如何在C语言的工程中非内嵌式的加入汇编

 1 $NOMOD51
2
3 NAME nRF24L01
4
5 ?PR?read_write_spi?nRF24L01 SEGMENT CODE
6
7 PUBLIC read_write_spi
8
9 ; public unsigned char read_write_spi( unsigned char )
10 RSEG ?PR?read_write_spi?nRF24L01
11 read_write_spi:
12 USING 0
13 MOV A, AR7
14
15 ; op begin
16 CLR CS
17 CLR SCK
18 RLC A
19 MOV R0, #8
20
21 loop:
22 MOV MOSI, C
23 SETB SCK
24 MOV MISO, C
25 CLR SCK
26 RLC A
27
28 DJNZ R0, loop
29 SETB CS
30 ; op end
31
32 MOV R7, A
33 RET
34 END


对于为什么我会操作A或R7, 我并不想冗述, 你可以通过Keil的帮助查到, 大概是叫passing in register 和 function return values. C语言或汇编传递参数, 除非指定NOREGPARMS宏, 都是通过特定的寄存器. 你会发现C与汇编的算法相当不同, 这是理所当然的, 因为汇编中我们用到了RLC指令, 有一个进位寄存器C帮助我们保存了要发送和接收的位, 因此在汇编时实际有9位参与了发送, 而C语言中只有8位, 因此我们不得不一直进行与/或操作. 如果你有兴趣看看SRC文件, 你会发现由C生成的汇编相当低效率, 而这种低效率还不是最低, 我特意用到了

while(i--)

这样的语句, 帮助编译器生成

DJNZ @r, @lable

实际上按照大多数人的习惯, 更容易见到

for(i=0; i<8; i++)

它将会生成

    MOV  R6, ?C?_004
loop:
; do something
INC R6
CJNE R6, #8, loop

尽管一些smart编译器在不断改进, 以试图猜测程序员的用意, 生成DJNZ式的代码, 但我仍然推荐有经验的程序员用汇编去写直接操作硬件的代码, 因为它们不仅代来了高效率, 并为以后的移植创造了可能, 传感器相比于一些高档的CPU, 速度实在慢的可怜, 以至于CPU不得不加入延时来让传感器完成操作, 汇编的延时则是可以精确计算出.

  这样还没结束, 在2011年夏天一次项目中, 团队遇到了一个小问题, 测量温度不能正常使用, 通过进一步测试后发现它与显示部分有冲突, 两者可以独立工作, 但整合到一起时, 便不能测温了, 后来找到引起BUG的原因是, 显示模块偷偷(未经声明)使用了一个中断源, 大概每50毫秒进入一次ISR, 而这个ISR写的实在太长了, 以至于温度测量每50毫秒就被一个打断极长的时间. 协商后, 我们在温度测量代码的开头和结尾加入了中断保护

    EA = 0;
; test temperature
EA = 1;

显示模块居然用中断, 中断服务例程又那么长... 呃... 吕ga的代码弱爆了, 这是废话, 重点是, 如何防止被打断

  没有接触过内核编程的程序员也许不了解其重要性, 但是如果你对Windows或linux略有接触的话, 就会明白, 各种打断所引发的后果, 比如蓝屏, 比如冒烟...

  OS编程中, 一个重要的概念叫作, 原子操作, 于是我们常常在linux内核中看到ATOM之类的东东. 原子一度被认为是最小的不能再分的物质, 后来虽然发现了比原子更小的体系结构, 但这一观念深入人心, 我们可以理解为最初一代程序员不怎么看物理学周刊或者干脆不去上物理课, 认为原子最小, 于是提出原子操作这一名称, 它就是指, 这一系列操作不可分割, 当我运行的时候, 不允许任何人打断我, 直到运行结束, 就像我们的DS18B20这类, 它要主机在240ms到480ms之间拉低总线, 如果这时候CPU响应中断跑去干别的事情, 那么DS18B20就不干了, 产生各种异常.

  接下来也许我应该介绍自旋锁互斥体, 但是显而易见的是, 这篇文章开始变长了, 所以就简单的使用EA寄存器来保护原子操作吧, MSC51单片机常常被设计为单线程的, IRQL也不如PC那样被分为32个级别, 那就是, 我们既不用理解KeGetCurrentIrql, 也不用考虑KeRaiseIrql,KeLowerIrql

对PC的内核感兴趣的读者去百度下
自旋: KeAcquireSpinLockAtDpcLevel KeReleaseSpinLockFromDpcLevel
等待: WaitForSingleObject WaitForMultipleObjects
线程: _beginthreadex
事件: CreateEvent
信号: CreateSemaphore
互斥: CreateMutex

  感觉还剩下好多东西没讲啊, 没办法呃, 高级东西就是涉及的东西多嘛. 那就以后再说好了. 最后, 我发现我写的有点小跑题, 呃, 介绍下, 传感器nRF24L01, 封装了传输层协议, 实现应答与自动重发, 并且这些对开发者完全透明, 支持DMA(支持么?), 支持多通道传输(RX有6个), 更省电更环保, 更多的去百度文库搜specification, 嗯姆, 不会1问百度, 2看spec, 3上论坛

木有啦...







posted @ 2011-09-30 08:29  airzack  阅读(708)  评论(0)    收藏  举报