【嵌入式实战分享】模块篇 NRF24L01

目录

  • 项目背景
  • 模块介绍
    • 1、引脚说明
    • 2、使用介绍
    • 3、电路结构
    • 4、驱动代码
    • 5、应用编程
  • 使用总结
    • 1.问题描述
    • 2.原因分析

项目背景

通过2.4G发送和接收信息,要求一主多从


模块介绍

1、引脚说明

本模块引脚较多,如果要接都邦线的话可能会比较繁琐,最好有自己的测试板。
【嵌入式实战分享】模块篇 NRF24L01_第1张图片
在这里呢,由于时间因素,我没有去进行PCB的设计和打板,而实随手焊接了一个洞洞板,足矣!【嵌入式实战分享】模块篇 NRF24L01_第2张图片
他的引脚虽多,但是主要功能主要分为电源类通讯协议类射频信号
【嵌入式实战分享】模块篇 NRF24L01_第3张图片
抛开电源和通讯协议的相关引脚,这些引脚其实就剩下了两个:一个用于使能模块,一个是模块的中断信号,如果大家已经掌握了SPI协议的话,使用这个模块是很好上手的。

2、使用介绍

关于这个模块,它再物联网场合应用甚广,先说我们使用者关心的:
【嵌入式实战分享】模块篇 NRF24L01_第4张图片
在这里呢,一定要注意它的工作电压和平均工作电流!!!对电流“敏感”,切勿热插拔(尖峰电流),以免造成损坏。

3、电路结构

整个模块最核心的主要是他的2.4G芯片,大致结构如下
【嵌入式实战分享】模块篇 NRF24L01_第5张图片
关于他的内部原理图呢,我们只需要简单了解,不需要去理解。但是可以根据原理图推断出引脚的主要作用:

CSN:芯片的片选线,CSN 为低电平芯片工作。
SCK:芯片控制的时钟线(SPI 时钟)
MISO:芯片控制数据线(Master input slave output)
MOSI:芯片控制数据线(Master output slave input)
IRQ:中断信号。无线通信过程中 MCU 主要是通过 IRQ 与 NRF24L01 进行通信。
CE: 芯片的模式控制线。 在 CSN 为低的情况下,CE 协同 NRF24L01 的 CONFIG 寄
存器共同决定 NRF24L01 的状态(参照 NRF24L01 的状态机)。

那么什么是NRF24L01状态机呢?

对于 NRF24L01 的固件编程工作主要是参照NRF24L01 的状态机。主要有以下几个状态
Power Down Mode:掉电模式
Tx Mode:发射模式
Rx Mode:接收模式
Standby-1Mode:待机 1 模式
Standby-2 Mode:待机 2 模式

如图【嵌入式实战分享】模块篇 NRF24L01_第6张图片
下面是对这些模式的介绍

Shutdown 工作模式
在 Shutdown 工作模式下,Si24R1 所有收发功能模块关闭,芯片停止工作,
消耗电流最小,但所有内部寄存器值FIFO 值保持不变,仍可通过 SPI 实现对
寄存器的读写
。设置 CONFIG 寄存器的 PWR_UP 位的值为 0,芯片立即返回到
Shutdown 工作模式。

Standby 工作模式。
在 Standby 工作模式,只有晶体振荡器电路工作,保证了芯片在消耗较少电
流的同时能够快速启动。设置 CONFIG 寄存器下的 PWR_UP 位的值为 1,芯片
待时钟稳定后进入 Standby 模式。芯片的时钟稳定时间一般为 1.5~2ms,与晶振
的性能有关。当引脚 CE=1 时,芯片将由 Standby 模式进入到 Idle-TX 或 RX 模
,当 CE=0 时,芯片将由 Idle-TX、TX 或 RX 模式返回到 Standby 模式

Idle-TX 工作模式
在 Idle-TX 工作模式下,晶体振荡器电路及时钟电路工作。相比于 Standby
模式,芯片消耗更多的电流。当发送端 TX FIFO 寄存器为空,并且引脚 CE=1
时,芯片进入到 Idle-TX 模式
。在该模式下,如果有新的数据包被送到 TX FIFO
中,芯片内部的电路将立即启动,切换到 TX 模式将数据包发送。
在 Standby 和 Idle-TX 工作模式下,所有内部寄存器值和 FIFO 值保持不变,
仍可通过 SPI 实现对寄存器的读写。

TX 工作模式
当需要发送数据时,需要切换到 TX 工作模式。芯片进入到 TX 工作模式的
条件为:TX FIFO 中有数据, CONFIG 寄存器的 PWR_UP 位的值为 1,PRIM_RX
位的值为 0,同时要求引脚 CE 上有一个至少持续 10us 的高脉冲。芯片不会直接
由 Standby 模式直接切换到 TX 模式,而是先立即切换到 Idle-TX 模式,再由
Idle-TX 模式自动切换到 TX 模式。Idle-TX 模式切换到 TX 模式的时间为
120us~130us 之间,但不会超过 130us。单包数据发送完成后,如果 CE=1, 则由
TX FIFO 的状态来决定芯片所处的工作模式,当 TX FIFO 还有数据,芯片继续
保持在TX工作模式,并发送下一包数据;当TX FIFO没有数据,芯片返回Idle-TX
模式;如果 CE=0,立即返回 Standby 模式。数据发射完成后,芯片产生数据发
射完成中断。

RX 工作模式
当需要接收数据时,需要切换到 RX 工作模式。芯片进入到 RX 工作模式的
条件为:设置寄存器 CONFIG 的 PWR_UP 位的值为 1PRIM_RX 位的值为 1
并且引脚 CE=1。芯片由 Standby 模式切换到 RX 模式的时间为 120~130us。当接
收到数据包的地址与芯片的地址相同,并且 CRC 检查正确时,数据会自动存入
RX FIFO
,并产生数据接收中断。芯片最多可以同时存三个有效数据包,当 FIFO
已满,接收到的数据包被自动丢掉。

在接收模式下,可以通过 RSSI 寄存器检测接收信号功率。当接收到的信号
强度大于-60dBm 时,RSSI 寄存器的 RSSI 位的值将被设置为 1。否则,RSSI=0。。
RSSI 寄存器的更新方法有两种:当接收到有效的数据包后,RSSI 会自动更新,
此外,将芯片从 RX 模式换到 Standby 模式时 RSSI 也会自动更新。RSSI 的值会
随温度的变化而变化,范围在±5dBm 以内

4、驱动代码

SPI 命令
SPI 命令参见表 6-1。CSN 从高电平翻转为低电平,SPI 接口开始工作。每 一次 SPI 操作,MISO 输出的第一字节为状态寄存器的值,之后通过命令来确定 是否输出值(不输出为高阻态)。命令格式中命令字按从 MSBit 到 LSBit 的顺序输
入,数据格式中按从 LSByte 到 MSByte 的顺序,每字节中按从 MSBit 到 LSBit 的顺序输入。

【嵌入式实战分享】模块篇 NRF24L01_第7张图片

SPI 命令

【嵌入式实战分享】模块篇 NRF24L01_第8张图片

根据上述的状态机图我们可以写出我们的驱动代码,如下

NRF24L01.H代码如下(示例):(在这里,我们就不讲解关于SPI协议的代码,对于SPI协议不太了解的话,可以看我的其他博客)

#ifndef __NRF24L01_H__
#define __NRF24L01_H__

#include 
#include 

#define uchar unsigned char
#define uint  unsigned int

#define MODE 1  //MODE=1时 为发送代码   MODE=0时  为接收代码
//****************************************IO端口定义***************************************
sbit  MISO = P3 ^ 2;
sbit  MOSI = P3 ^ 4;
sbit  SCK  = P3 ^ 1;
sbit  CE   = P3 ^ 0;
sbit  CSN  = P3 ^ 3;
sbit  IRQ  = P3 ^ 5;

//******************************************************************************************
uchar bdata sta;   //状态标志
sbit RX_DR  = sta ^ 6;
sbit TX_DS  = sta ^ 5;
sbit MAX_RT = sta ^ 4;
//*********************************************NRF24L01*************************************
#define TX_ADR_WIDTH    5    // 5 uints TX address width
#define RX_ADR_WIDTH    5    // 5 uints RX address width
#define TX_PLOAD_WIDTH  5    // 5 uints TX payload
#define RX_PLOAD_WIDTH  5    // 5 uints TX payload
uchar const TX_ADDRESS[TX_ADR_WIDTH] = {0x34, 0x43, 0x10, 0x10, 0x01}; //本地地址
uchar const RX_ADDRESS[RX_ADR_WIDTH] = {0x34, 0x43, 0x10, 0x10, 0x01}; //接收地址
uchar data Tx_Buf[TX_PLOAD_WIDTH] = {0xff, 0xee, 0x11, 0x22, 0x33};//发送数据
uchar Rx_Buf[RX_PLOAD_WIDTH];//接收数据
//***************************************NRF24L01寄存器指令*******************************************************
#define READ_REG        0x00   // 读寄存器指令
#define WRITE_REG       0x20  // 写寄存器指令
#define RD_RX_PLOAD     0x61   // 读取接收数据指令
#define WR_TX_PLOAD     0xA0   // 写待发数据指令
#define FLUSH_TX        0xE1  // 冲洗发送 FIFO指令
#define FLUSH_RX        0xE2   // 冲洗接收 FIFO指令
#define REUSE_TX_PL     0xE3   // 定义重复装载数据指令
#define NOP             0xFF   // 保留
//*************************************SPI(nRF24L01)寄存器地址****************************************************
#define CONFIG          0x00  // 配置收发状态,CRC校验模式以及收发状态响应方式
#define EN_AA           0x01  // 自动应答功能设置
#define EN_RXADDR       0x02  // 可用信道设置
#define SETUP_AW        0x03  // 收发地址宽度设置
#define SETUP_RETR      0x04  // 自动重发功能设置
#define RF_CH           0x05  // 工作频率设置
#define RF_SETUP        0x06  // 发射速率、功耗功能设置
#define STATUS          0x07  // 状态寄存器
#define OBSERVE_TX      0x08  // 发送监测功能
#define CD              0x09  // 地址检测           
#define RX_ADDR_P0      0x0A  // 频道0接收数据地址
#define RX_ADDR_P1      0x0B  // 频道1接收数据地址
#define RX_ADDR_P2      0x0C  // 频道2接收数据地址
#define RX_ADDR_P3      0x0D  // 频道3接收数据地址
#define RX_ADDR_P4      0x0E  // 频道4接收数据地址
#define RX_ADDR_P5      0x0F  // 频道5接收数据地址
#define TX_ADDR         0x10  // 发送地址寄存器
#define RX_PW_P0        0x11  // 接收频道0接收数据长度
#define RX_PW_P1        0x12  // 接收频道0接收数据长度
#define RX_PW_P2        0x13  // 接收频道0接收数据长度
#define RX_PW_P3        0x14  // 接收频道0接收数据长度
#define RX_PW_P4        0x15  // 接收频道0接收数据长度
#define RX_PW_P5        0x16  // 接收频道0接收数据长度
#define FIFO_STATUS     0x17  // FIFO栈入栈出状态寄存器设置
/******************************************延时函数********************************************************/
//长延时
void Delay(unsigned int s)
{
    unsigned int i, j;
    for(i = 0; i < 1000; i++)for(j = 0; j < s; j++);
}
//短延时
void delay_ms(unsigned int x)
{
    unsigned int i, j;
    i = 0;
    for(i = 0; i < x; i++)
    {
        j = 108;;
        while(j--);
    }
}
/************************************IO 口模拟SPI总线 代码************************************************/
uchar SPI_RW(uchar byte)
{
    uchar bit_ctr;
    for(bit_ctr = 0; bit_ctr < 8; bit_ctr++)
    {
        MOSI = (byte & 0x80);

        byte = (byte << 1);
        SCK = 1;
        byte |= MISO;
        //led=MISO;Delay(150);
        SCK = 0;
    }
    return(byte);
}
uchar SPI_RW_Reg  (uchar  reg, uchar value) // 向寄存器REG写一个字节,同时返回状态字节
{
    uchar status;
    CSN = 0;
    status = SPI_RW(reg);
    SPI_RW(value);
    CSN = 1;
    return(status);
}
uchar SPI_Read (uchar  reg )
{
    uchar reg_val;
    CSN = 0;
    SPI_RW(reg);
    reg_val = SPI_RW(0);
    CSN = 1;
    return(reg_val);
}
uchar SPI_Write_Buf(uchar reg, uchar *pBuf, uchar bytes)
{
    uchar status, byte_ctr;
    CSN = 0;                   // Set CSN low, init SPI tranaction
    status = SPI_RW(reg);    // Select register to write to and read status byte
    for(byte_ctr = 0; byte_ctr < bytes; byte_ctr++) // then write all byte in buffer(*pBuf)
        SPI_RW(*pBuf++);
    CSN = 1;                 // Set CSN high again
    return(status);          // return nRF24L01 status byte
}
#if MODE
/*******************************发*****送*****模*****式*****代*****码*************************************/
void TX_Mode(void)
{
    CE = 0;

    SPI_RW_Reg(FLUSH_TX, 0x00);
    SPI_Write_Buf(WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH);    // Writes TX_Address to nRF24L01
    SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // RX_Addr0 same as TX_Adr for Auto.Ack
    SPI_RW_Reg(WRITE_REG + EN_AA, 0x01);      // Enable Auto.Ack:Pipe0
    SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01);  // Enable Pipe0
    SPI_RW_Reg(WRITE_REG + SETUP_RETR, 0x1a); // 500us + 86us, 10 retrans...1a
    SPI_RW_Reg(WRITE_REG + RF_CH, 40);        // Select RF channel 40
    SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x07);   // TX_PWR:0dBm, Datarate:1Mbps, LNA:HCURR
    SPI_RW_Reg(WRITE_REG + RX_PW_P0, RX_PLOAD_WIDTH); //设置接收数据长度,本次设置为2字节
    SPI_RW_Reg(WRITE_REG + CONFIG, 0x0e);
    CE = 1;
    delay_ms(100);
}
void Transmit(unsigned char *tx_buf)
{
    CE = 0; //StandBy I模式
    SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // 装载接收端地址
    SPI_RW_Reg(FLUSH_TX, 0x00);
    SPI_Write_Buf(WR_TX_PLOAD, tx_buf, TX_PLOAD_WIDTH);     // 装载数据
    SPI_RW_Reg(WRITE_REG + CONFIG, 0x0e);      // IRQ收发完成中断响应,16位CRC,主发送
    CE = 1; //置高CE,激发数据发送
    delay_ms(150);
}

#else
/*******************************接*****收*****模*****式*****代*****码*************************************/
uchar SPI_Read_Buf(uchar reg, uchar *pBuf, uchar uchars)
{
    uchar status, uchar_ctr;

    CSN = 0;                      // Set CSN low, init SPI tranaction
    status = SPI_RW(reg);         // Select register to write to and read status uchar

    for(uchar_ctr = 0; uchar_ctr < uchars; uchar_ctr++)
        pBuf[uchar_ctr] = SPI_RW(0);    //

    CSN = 1;
    return(status);                    // return nRF24L01 status uchar
}
/******************************************************************************************************/
/*函数:unsigned char nRF24L01_RxPacket(unsigned char* rx_buf)
/*功能:数据读取后放如rx_buf接收缓冲区中
/******************************************************************************************************/
unsigned char nRF24L01_RxPacket(unsigned char *rx_buf)
{
    unsigned char revale = 0;
    sta = SPI_Read(STATUS); // 读取状态寄存其来判断数据接收状况
    if(RX_DR)    // 判断是否接收到数据
    {
        //CE = 0;    //SPI使能
        SPI_Read_Buf(RD_RX_PLOAD, rx_buf, RX_PLOAD_WIDTH); // read receive payload from RX_FIFO buffer
        revale = 1;  //读取数据完成标志
        //Delay(100);
    }
    SPI_RW_Reg(WRITE_REG + STATUS, sta); //接收到数据后RX_DR,TX_DS,MAX_PT都置高为1,通过写1来清楚中断标志
    return revale;
}
/****************************************************************************************************/
/*函数:void RX_Mode(void)
/*功能:数据接收配置
/****************************************************************************************************/
void RX_Mode(void)
{
    CE = 0;

    SPI_RW_Reg(FLUSH_RX, 0x00);
    SPI_Write_Buf(WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH);    // Writes TX_Address to nRF24L01
    SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // RX_Addr0 same as TX_Adr for Auto.Ack

    SPI_RW_Reg(WRITE_REG + EN_AA, 0x01);      // Enable Auto.Ack:Pipe0
    SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01);  // Enable Pipe0
    SPI_RW_Reg(WRITE_REG + SETUP_RETR, 0x1a); // 500us + 86us, 10 retrans...1a
    SPI_RW_Reg(WRITE_REG + RF_CH, 40);        // Select RF channel 40
    SPI_RW_Reg(WRITE_REG + RX_PW_P0, RX_PLOAD_WIDTH); //设置接收数据长度,本次设置为2字节
    SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x07);   // TX_PWR:0dBm, Datarate:1Mbps, LNA:HCURR
    SPI_RW_Reg(WRITE_REG + CONFIG, 0x0F);
    CE = 1;
    delay_ms(130);
}
//************************************串口初始化*********************************************************
void StartUART( void )
{
    //波特率9600
    SCON = 0x50;
    TMOD = 0x20;
    TH1 = 0xFD;
    TL1 = 0xFD;
    PCON = 0x00;
    TR1 = 1;
}
//************************************通过串口将接收到数据发送给PC端**************************************
void R_S_Byte(uchar R_Byte)
{
    SBUF = R_Byte;
    while( TI == 0 );    //查询法
    TI = 0;
}
#endif
//=========================================================================================//
#endif

5、应用编程

那么,在本次项目中呢我们是要用到一对多发送,

多管道通信
收发器可同时进行 6 个发送端,1 个接收端之的双向或单向通信。此时,接收端要在EN_RXADDR
寄存器中使能各个管道,并设置每一个接收管道地址与对应的发送端发送地址相同.其中接收管道 0 有单独的 5 字节地址,管道 1-5 共用高4 字节有效地址。 接收端如果需要接收 ACK 信号,还需要设置接收管道 0 地址与自身发送地 址相同.

【嵌入式实战分享】模块篇 NRF24L01_第9张图片
在本次开发中呢,由于传输的数据较少,数据没有占满整个通道,我们采用的方法是用通道中的某一位作为标志位,如下

主机判断接受的标志位

/*USER*/
#define ID_TX_1 0XAA
#define ID_TX_2 0XBB
#define ID_TX_3 0XCC
..............//其余部分省略
..............//在接收之后进行判断
    if(RxBuf[5]==ID_TX_1)OLED_ShowNum(48,5,1,1,8);//是从机1发送过来的消息
    else if(RxBuf[5]==ID_TX_2)OLED_ShowNum(48,5,2,1,8);//是从机2发送过来的消息
    else if(RxBuf[5]==ID_TX_3)OLED_ShowNum(48,5,3,1,8);//是从机3发送过来的消息

从机发送标志位

/*USER*/
#define ID_TX_1 0XAA
..............//其余部分省略
..............//在发送前进行赋值
    TxBuf[5]=ID_TX_1;

使用总结

1.问题描述

在本次开发的过程中,遇到了一个比较玄学的问题——本来设计好了一对主从通讯,再过了几天之后,有一个电路板上的2.4g突然不能够正常工作,我检查了一点电路,没有断点,
【嵌入式实战分享】模块篇 NRF24L01_第10张图片
但是我把它拔下来,在我的测试板上重新测试的时候发现它是正常的【嵌入式实战分享】模块篇 NRF24L01_第11张图片
当时比较着急,然后就直接在这几个一主多从电路板之间,把他们的2.4g相互替换了一下,居然又可以正常工作了!!!每日玄学又有了新素材【嵌入式实战分享】模块篇 NRF24L01_第12张图片

2.原因分析

现在回想起来,可能是当时用电脑的外设太多,导致拓展坞提供的电源功率不够,不能够使得模块正常工作。

***提示:这里对文章进行最后总结:***

你可能感兴趣的:(模块介绍,学习,经验分享,嵌入式硬件,51单片机,物联网)