这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【分享评测,赢取加热台】+AT89C2051控制NRF24L01收发数据

共3条 1/1 1 跳转至

【分享评测,赢取加热台】+AT89C2051控制NRF24L01收发数据

专家
2024-05-10 23:30:39   被打赏 50 分(兑奖)     打赏

早以前,Wifi还不是很普及的时候,我为了组建一个小型网络,使用了NRF24L01无线收发模块,同时为了减小整个电路板的体积,使用了89C2051单片机。当时还不知道有MDK这种开发工具。整个程序都是用51汇编语言写的。当时为了测试收发处理,是在面包板上使用相同的电路搭建的。

AT89C2051单片机芯片引脚图:

图片1.png

NRF24L01模块:

图片2.png

NRF24L01的接线:

图片3.png

NRF24L01使用的标准SPI通讯接口,因此处理起来比较简单。

AT89C2051单片机本身是支持低电压的,所以二者在一起使用,不用在工作电压上做过多处理,统一使用3.3V就好。二者的连接电路图如下:

图片4.png电路图很简单,就是为了测试能否控制NRF24L01相互间通讯。一侧按下按键的时候,启动发送。另一组接收到数据后,点亮对应的发光管。AT89C2051的P1.0和P1.1作为IO口使用的时候,因为是开漏的,需要上拉电阻。在本电路中直接作为驱动LED的,因为由限流电阻和LED构成上拉电路。

在接收处理中,没有使用AT89C2051本身的外部中断处理NRF24L01产生的中断,而是使用轮巡P1.7上的电平变化来判断是否发生中断。

程序用的是汇编语言,现在估计没多少人还在用51单片机以及汇编语言了。但作为初学者,学习汇编,在理解单片机的工作过程上还是会有很大帮助的。同时由于AT89C2051的内存和Flash空间比较小,使用汇编的话,在有效利用空间上,还是有好处的。以下是程序,因为有注释,应该会容易理解的:

;为了防止在从机应答没有完成过程中,又再次发射数据,导致接收端异常,需要加必要的处理
;比如时间之类定时中断,防止IRQ管脚处于高电平,导致程序进入死循环
;主要是判断IRQ==0的处理段
;用于设备控制的时候,通讯最好采用两个字节,防止但字节时通讯错误,导致设备误动作
;比如c0~c9,ca o0~o9,oa之类的双字节ASCII码 c:close, o:open, a:all,0-9:设备编号
LED0    BIT        P1.0
LED1    BIT        P1.1
CE      BIT        P1.2
CSN     BIT        P1.3
SCK     BIT        P1.4
MOSI    BIT        P1.5
MISO    BIT        P1.6
IRQ     BIT        P1.7
KEY34   BIT        P3.4
KEY35   BIT        P3.5
;发射地址长度:5字节
TX_ADDR_WIDTH   EQU    5
;按钮是否按下的标志.按下:1;
UNIT_LOCK_KEY    EQU   03H   ;R3
;发射地址内存保持区
UNIT_TXADDR     EQU    08H  ;TX_ADDR, 5Byte(08H~0DH)
;发射缓冲区/发射缓冲区宽度,即发送接收数据的长度
TR_DATA_WIDTH  EQU    1
;发射缓冲区,1字节(控制16个设备):形如10~1F:打开0号~15号设备,00~0F:关闭0号~15号设备
UNIT_TX_BUF     EQU    0EH  ;
;接收缓冲区,1字节(控制16个设备):形如O0~OF:打开0号~15号设备,C0~CF:关闭0号~15号设备
UNIT_RX_BUF     EQU    0FH  ;
;临时变量
UNIT_20H        EQU    20H  ;st : st1-8
UNIT_21H        EQU    21H  ;st1: st11-18
UNIT_22H        EQU    22H  ;sta: (22H.4: MAX_RT, 22H.5: TX_DS, 22H.6:RX_DR)
                   ORG   0000H
Q0000:             LJMP  StartUp
;======================================0003
                    ORG     0003H
StartUp:            MOV     SP,     #40H        ;设置堆栈指针
                    MOV     P1,     #255
                    MOV     P3,     #255
                    ;发射地址调入内存?直接用DPTR读是否一样?
                    MOV     R7,     #TX_ADDR_WIDTH
                    MOV     R0,     #UNIT_TXADDR
                    MOV     DPTR,   #DAT_TX_ADDR
suSetAddr:          CLR     A
                    MOVC    A,      @A+DPTR
                    MOV     @R0,    A
                    INC     R0
                    INC     DPTR
                    DJNZ    R7,     suSetAddr
                   
MAIN:               CLR     A
                    MOV     20H,A          ;st
                    MOV     21H,A          ;st1
                    MOV     22H,A          ;sta
                    MOV     UNIT_LOCK_KEY,     #0   ;//lock_key
                    LCALL   Delay100ms
                    LCALL   InitNfr2410
                    LCALL   Delay100ms
                    LCALL   Delay100ms
                    LCALL   SetRxMode
                    ;检查是否有按键按下(P3.4 or P3.5)
CheckKeyPress:      LCALL   EmptyOpt
                    SETB    KEY34
                    SETB    KEY35
CheckKeyPress1:     JNB     KEY34,KeyPressed
                    JB      KEY35,Q00A8
KeyPressed:         SETB    IRQ
                    ;写状态寄存器
                    MOV     R5,     #0FFH       ;写数据255
                    MOV     R7,     #27H        ;写状态寄存器
                    LCALL   SpiRwReg            ;执行写操作
                    ;判断是哪个按键按下
                    JB      KEY34,  KeyPressed1
                    ;P3.4按键被按下的时候
                    MOV     R0,     #UNIT_TX_BUF
                    MOV     @R0,    #0AAH
                    LJMP    KeyPressed2
KeyPressed1:        JB      KEY35,  KeyPressed2
                    ;P3.5按键被按下的时候
                    MOV     R0,     #UNIT_TX_BUF
                    MOV     @R0,    #55H
                    ;判断按键按下处理完毕
KeyPressed2:        LCALL   EmptyOpt
                    ;设置NRF24L01进入发射模式
                    LCALL   SetTxMode
                   
WaitIRQClr1:        JNB     IRQ,    CheckIRQType
                    LCALL   EmptyOpt
                    LJMP    WaitIRQClr1
                    
                    ;读取NRF24L01状态值,判断中断的类型
CheckIRQType:       MOV     R7,     #07H
                    LCALL   SpiRead
                    ;状态值暂时保存在22H中
                    MOV     22H,    R7
                    ;清除NRF24L01的所有中断指示
                    MOV     R5,     #0FFH
                    MOV     R7,     #27H
                    LCALL   SpiRwReg
                    ;取出状态值,判断是什么中断
                    MOV     A,      22H
                    ;是没有收到正常发射完成的中断,转L_SendNg
                    JNB     ACC.5,  L_SendNg
                     ;建立指示灯交替亮灭,表示发射正常
                     CPL     LED0
                     ;判断是哪个按钮被按下的,此处代码没有意义,仅用于显示
                     JB      KEY34,  Q0087
                      ;是P3.4对应的按钮
                      LCALL   Delay100ms
                      LCALL   Delay100ms
                      LJMP    Q0090
                     
                      ;是P3.5对应的按钮
Q0087:                LCALL   Delay100ms
                      LCALL   Delay100ms
Q0090:                LCALL   EmptyOpt
                      LJMP    Q00A0
                     ;清除NRF24L01收发缓冲区
L_SendNg:           LCALL   Nrf2401ClearAll
                    LCALL   EmptyOpt
Q00A0:              ;建立按钮被按下的标志
                    MOV     UNIT_LOCK_KEY, #01H
                    LJMP    CheckKeyPress1
                    
                    ;检查是否有按钮被按下标志
Q00A8:              MOV     A,  UNIT_LOCK_KEY
                    ;没有按钮按下,转Q00C7
                    JZ      Q00C7
                     ;有按钮被按下过
                     ;清除标志
                     MOV     UNIT_LOCK_KEY, #0
                     ;使NRF24L01处于读状态
                     LCALL   SetRxMode
                     ;拉高中断管脚电平
                     SETB    IRQ
                     ;等待在此产生中断(收到从机的应答中断)
Q00B8:               JB      IRQ,   Q00C0
                      LCALL   EmptyOpt
                      LJMP    Q00B8
                     ;没有收到从机的应答,延时
Q00C0:               MOV     R7, #90H
                     MOV     R6, #01H
                     LCALL   DelayUs
                     
;此处是用来响应从机主动发射数据场合
Q00C7:              SETB    IRQ
                    ;若发生中断,转L_RecvOK,处理接收
                    JNB     IRQ, L_RecvOK
                     ;否则继续转监视按键处理
                     LJMP    CheckKeyPress
L_RecvOK:           LCALL   EmptyOpt
                    ;取得NRF24L01状态
                    MOV     R7,     #07H
                    LCALL   SpiRead
                    ;缓存到22H中
                    MOV     22H,    R7
                    ;清除中断状态指示
                    MOV     R5,     #0FFH
                    MOV     R7,     #27H
                    LCALL   SpiRwReg
                    ;判断中断类型
                    MOV     A,      22H
                    ;如果没有接收到从机数据的中断,转L_RecvNg
                    JNB     ACC.6,  L_RecvNg
;接收到从机数据的中断
                     LCALL   EmptyOpt
                     
                     ;交替亮灭指示灯
                     CPL     LED1
                     
                     ;取得主机发送过来的数据,保存到内存中的缓冲区中
                     MOV     R1,     #UNIT_RX_BUF
                     MOV     R2,     #TR_DATA_WIDTH
                     MOV     R7,     #61H
                     LCALL   SpiReadBuf
;确认得到的标志数据是不是合法的值范围
                     ;从缓冲区取出数据,并判断值
                     MOV     R0,     #UNIT_RX_BUF
                     MOV     A,      @R0
                     ;判断是哪个按钮按下对应的发送值
                     CJNE    A,      #0AAH,  Q0112
                       ;等于AAH时
                       LCALL   Delay100ms
                       LCALL   Delay100ms
                       LJMP    L_ClearRecvBuffer
                      
                      ;不是AAH, 是55H吗? 
Q0112:                MOV     R0,     #UNIT_RX_BUF
                      MOV     A,      @R0
                      ;是55H吗? 不是,则转L_ClearRecvBuffer
                      CJNE    A,      #55H,   L_ClearRecvBuffer
                        LCALL   Delay100ms
                        LCALL   Delay100ms
;接收到的,不是按钮按下的标志数据(AAH, 55H)
L_ClearRecvBuffer:    CLR     A
                      MOV     R0,     #UNIT_RX_BUF
                      MOV     @R0,    A
                      LCALL   EmptyOpt
                      ;继续转监视按键处理
                      LJMP    CheckKeyPress
;没有收到接收中断
L_RecvNg:           NOP
                    LCALL   EmptyOpt
                    ;清除NRF24L01缓冲数据
                    LCALL   Nrf2401ClearAll
                    ;设置NF24L01进入接收模式
                    LCALL   SetRxMode
                    SETB    IRQ
Q013E:              JB      IRQ,    Q0146
                    LCALL   EmptyOpt
                    LJMP    Q013E
Q0146:              NOP
                    LJMP    CheckKeyPress
                   
;========================================01D7
SpiRw:             ;从R7取得读写数据
                   MOV   20H,R7
                   ;输出字节的B7位
                   MOV   C,20H.7     ;07H
                   MOV   MOSI,C      ;输出到管脚上
                   SETB  SCK         ;发出脉冲高电平
                   MOV   C,MISO      ;从MISO管脚取得输入
                   MOV   21H.7,C     ;保存到21H中(为了不使用21H,此处用B,P2代替如何)
                   MOV   B.7,  C
                   CLR   SCK         ;发出脉冲低电平,完成一位的输出操作
                   ;输出字节的B6位
                   MOV   C,20H.6
                   MOV   MOSI,C
                   SETB  SCK
                   MOV   C,MISO
                   MOV   21H.6,C
                  CLR   SCK
                   ;输出字节的B5位
                   MOV   C,20H.5
                   MOV   MOSI,C
                   SETB  SCK
                   MOV   C,MISO
                   MOV   21H.5,C
                   CLR   SCK
                   ;输出字节的B4位
                   MOV   C,20H.4
                   MOV   MOSI,C
                   SETB  SCK
                   MOV   C,MISO
                   MOV   21H.4,C
                   CLR   SCK
                   ;输出字节的B3位
                   MOV   C,20H.3
                   MOV   MOSI,C
                   SETB  SCK
                   MOV   C,MISO
                   MOV   21H.3,C
                   CLR   SCK
                   ;输出字节的B2位
                   MOV   C,20H.2
                   MOV   MOSI,C
                   SETB  SCK
                   MOV   C,MISO
                   MOV   21H.2,C
                   CLR   SCK
                   ;输出字节的B1位
                   MOV   C,20H.1
                   MOV   MOSI,C
                   SETB  SCK
                   MOV   C,MISO
                   MOV   21H.1,C
                   CLR   SCK
                   ;输出字节的B0位
                   MOV   C,20H.0
                   MOV   MOSI,C
                   SETB  SCK
                   MOV   C,MISO
                   MOV   21H.0,C
                   CLR   SCK
                   ;取得的返回值回存到R7中
                   MOV   R7,21H
                   
;                   PUSH ACC                   
;                   MOV  A,      R7
;                   MOV  R6,     #8
;rwloop:            MOV  MOSI,   ACC.7
;                   RL   A
;                   ANL  A,      #0FEH
;                   ORL  A,      MISO
;                   MOV  R7,     A
;                   DJNZ R6,     rwloop
;                   POP  ACC
                   RET
;=====================================023C
SetTxMode:          ;进入待机模式          
                    LCALL   PowerOff
                    CLR     CE
                    
                    ;写发送地址
                    MOV   R1,   #UNIT_TXADDR        ;发射端物理地址的首地址
                    MOV   R2,  #TX_ADDR_WIDTH      ;地址宽度
                    MOV   R7,   #30H                ;写发送地址寄存器操作:20H+10H(发送地址寄存器)
                    LCALL SpiWriteBuf
                    
                    ;写数据通道0地址
                    MOV   R1,   #UNIT_TXADDR        ;发射端物理地址的首地址
                    MOV   R2,  #TX_ADDR_WIDTH      ;地址宽度
                    MOV   R7,   #2AH                ;写数据通道0地址操作 20H+0AH(数据通道0地址寄存器)
                    LCALL SpiWriteBuf
                   
                   ;把要发射的数据写入NRF24L01的发送缓冲寄存器中
                   MOV   R1,    #UNIT_TX_BUF        ;发射数据缓冲区首地址
                   MOV   R2,   #TR_DATA_WIDTH      ;发射的数据长度
                   MOV   R7,    #0A0H               ;写入NRF24L01的发送缓冲寄存器操作:A0
                   LCALL SpiWriteBuf
                   
                   ;允许自动应答(只允许数据通道0)
                   MOV   R5,    #01H                ;只允许数据通道0
                   MOV   R7,    #21H                ;写自动应答寄存器
                   LCALL SpiRwReg
                   
                   ;设置允许接收的数据通道(只允许数据通道0)
                   MOV   R5,    #01H                ;只允许数据通道0
                   MOV   R7,    #22H                ;写接收地址允许寄存器
                   LCALL SpiRwReg
                   
                   ;设置自动重发参数
                   MOV   R5,    #1AH                ;重发延时500+86us, 重发10次
                   MOV   R7,    #24H                ;写自动重发寄存器操作
                   LCALL SpiRwReg
                   
                   ;设置射频通道的工作频率
                   MOV   R5,    #28H                ;
                   MOV   R7,    #25H                ;写射频通道设置寄存器操作
                   LCALL SpiRwReg
                   
                   ;设置射频参数
                   MOV   R5,    #26H                ;0db, 1Mbps,低噪声增益放大
                   MOV   R7,    #26H                ;写射频参数寄存器操作(6号)
                   LCALL SpiRwReg
                   
                   ;启动发射
                   MOV   R5,    #0EH                ;允许中断时IRQ脚电平拉低.允许CRC,16位CRC,发射模块上电,发射
                   MOV   R7,    #20H                ;写配置寄存器操作(0号)
                   LCALL SpiRwReg
                   
                   SETB  CE
                   RET
;============================================
;SetRxMode-0298
SetRxMode:         LCALL PowerOff
                   CLR   CE
                   
                    ;写数据通道0地址
                    MOV   R1,   #UNIT_TXADDR        ;发射端物理地址的首地址
                    MOV   R2,  #TX_ADDR_WIDTH      ;地址宽度
                    MOV   R7,   #2AH                ;写数据通道0地址操作 20H+0AH(数据通道0地址寄存器)
                    LCALL SpiWriteBuf
                   ;允许自动应答(只允许数据通道0)
                   MOV   R5,    #01H                ;只允许数据通道0
                   MOV   R7,    #21H                ;写自动应答寄存器
                   LCALL SpiRwReg
                   
                   ;设置允许接收的数据通道(只允许数据通道0)
                   MOV   R5,    #01H                ;只允许数据通道0
                   MOV   R7,    #22H                ;写接收地址允许寄存器
                   LCALL SpiRwReg
                   ;设置射频通道的工作频率
                   MOV   R5,    #28H                ;
                   MOV   R7,    #25H                ;写射频通道设置寄存器操作(5)
                   LCALL SpiRwReg
                    ;设置接收通道0的数据宽度
                   MOV   R5,    #TR_DATA_WIDTH      ;1个字节
                   MOV   R7,    #31H                ;写数据通道0的数据宽度寄存器操作(11H)
                   LCALL SpiRwReg
                   ;设置射频参数
                   MOV   R5,    #26H                ;0db, 1Mbps,低噪声增益放大
                   MOV   R7,    #26H                ;写射频参数寄存器操作(6号)
                   LCALL SpiRwReg
                   
                   ;启动接收
                   MOV   R5,    #0FH                ;允许中断时IRQ脚电平拉低.允许CRC,16位CRC,发射模块上电,接收
                   MOV   R7,    #20H                ;写配置寄存器操作(0号)
                   LCALL SpiRwReg
                   
                   SETB  CE
                   RET
;==========================================
;R2:读写NRF24L01数据缓冲区的字节数,用于参数传递
;R1:保持发送数据的内存地址
;R7:操作指令(含目标寄存器地址)
SpiWriteBuf:       ;允许SPI操作
                   CLR   CSN
                   ;写入R7指令和寄存器地址
                   LCALL SpiRw
                   ;保存返回值
                   MOV   06H,   R7
                   ;循环处理完指定的字节数,字节数保存在R2中
                   ;取出缓冲区的一个字节数据
swbloop:
                    MOV     A,      @R1
                    ;存入R7
                    MOV     R7,     A
                    ;发给NRF24L01
                    LCALL   SpiRw
                    ;指向下一个缓冲区单元
                    INC     R1
                    ;处理完所有字节了吗?
                    DJNZ    R2,    swbloop
                    SETB    CSN
                    MOV     R7,     06H
                    RET               
;=======================================
;R2:读写NRF24L01数据缓冲区的字节数,用于参数传递
;R1:保持接收数据的内存地址
;R7:操作指令(含目标寄存器地址)
SpiReadBuf:         ;允许SPI操作
                    CLR     CSN
                    ;写入R7指令和寄存器地址
                    LCALL   SpiRw
                    ;保存返回值
                    MOV     06H,     R7
                    ;循环处理完指定的字节数,字节数保存在R2中
                    ;取出NRF24L01接收缓冲区的一个字节数据
srbloop:            
                    MOV     R7,     #0
                    LCALL   SpiRw
                    MOV     A,      R7
                    MOV     @R1,    A
                    ;指向下一个缓冲区单元
                    INC     R1
                    ;处理完所有字节了吗?
                    DJNZ    R2,    srbloop
                    SETB    CSN
                    MOV     R7,     06H
                    RET    
                               
;=======================================
;R7中的值用来区分清空对象:发送FIFO 还是接收FIFO
SpiClrReg:         CLR   CSN
                   CJNE  R7,#01H,scr1
                   ;清空发送FIFO缓冲区
                   MOV   R7,#0E1H
                   LCALL SpiRw
                   LJMP  scr2
                   ;清空接收FIFO缓冲区
scr1:              MOV   R7,#0E2H
                   LCALL SpiRw
scr2:              SETB  CSN
                   RET
;=======================================
Nrf2401ClearAll:   CLR   A
                   MOV   R7,A
                   LCALL SpiClrReg
                   MOV   R7,#01H
                   LCALL SpiClrReg
                   MOV   R5,#0FFH
                   MOV   R7,#27H
                   LCALL SpiRwReg
                   SETB  IRQ
                   RET
;=======================================
DelayUs:           CLR   A
                   MOV   R5,A
dus1:              MOV   A,R7
                   ORL   A,R6
                   JZ    dus4
                   CLR   A
                   MOV   R5,A
dus2:              INC   R5
                   CJNE  R5,#01H,dus2
                   MOV   A,R7
                   DEC   R7
                   JNZ   dus3
                   DEC   R6
dus3:              LJMP  dus1
dus4:              RET
;=======================================
SpiRwReg:          MOV   06H,    05H
                   CLR   CSN
                   LCALL SpiRw
                   MOV   05H,    R7
                   MOV   R7,    06H
                   LCALL SpiRw
                   SETB  CSN
                   MOV   R7,    05H
                   RET
;=======================================
PowerOff:          CLR   CE
                   MOV   R5,#0DH
                   MOV   R7,#20H
                   LCALL SpiRwReg
                   SETB  CE
                   MOV   R7,#14H
                   MOV   R6,#00H
                   LCALL DelayUs
                   RET
;=======================================
Delay100ms:        NOP
                   NOP
                   MOV   R7,#09H
                   MOV   R6,#68H
                   MOV   R5,#8BH
dms1:              DJNZ  R5,$
                   DJNZ  R6,dms1
                   DJNZ  R7,dms1
                   RET
;========================================
SpiRead:           CLR   CSN
                   LCALL SpiRw
                   CLR   A
                   MOV   R7,A
                   LCALL SpiRw
                   SETB  CSN
                   RET
;========================================
InitNfr2410:       CLR   CE
                   SETB  CSN
                   CLR   SCK
                   RET
;========================================
;可以删除,没有实际处理
EmptyOpt:          NOP
                   NOP
                   RET
;========================================
;物理地址
DAT_TX_ADDR:       DB 34H, 43H, 10H, 10H, 01H, 01H
;字形笔段对应区
DAT_SM_MAP:        DB 0C0H, 0F9H,0A4H,0B0H, 99H, 92H, 82H,0F8H, 80H, 90H,0BFH,0FFH
END

NRF24L01在收到有效数据后,就会发出中断,因此在实际应用中,特别在多对多环境时,有必要建立自己的通讯协议,在通讯数据上加上地址编号之类的数据,在接收时检查地址与自己所在的编号是否一致,来确定数据是要发给谁。我这里只是测试两个模块之间的收发,因此没有加通讯协议。





关键词: AT89C2051     NRF24L01    

专家
2024-05-11 06:46:45     打赏
2楼

来看看


工程师
2024-05-15 15:35:07     打赏
3楼

666


共3条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]