NRF24L01模块通信实验
NRF24L01简要介绍
这里主要介绍模块的最重要的参数,废话就不多介绍了。
该模块是一款无线通信模块,一个模块即可同时具备发射和接收数据的功能,但是要想实现通信必须使用两个模块之间才能进行通信。NRF24L01模块使用的总线控制方式为SPI总线。SPI的通信方式本文就不介绍了。
预备知识
首先需要知道NRF24L10的通信过程是怎样的,这里主要是指具体的通信方式,假设一个模块A设置为发送方,另一个模块B设置为接收方,那么A和B之间通信需要几个基本设置,只有配置好这些参数后,双方才能进行通信。
通道
通道是发送方与接收方的通信具体的一条路,可以这么理解,比如小时候我们玩的两个人用纸杯子连一条线来进行传话,那么这里这条线就可以理解为通道,因为双方的声音是通过这条线进行传输的。对于NRF24L01模块,其最多有6个通道,也就是说,一个设备,最多可以与6个设备同时进行通信。
发送地址与接收地址
模块通信中有TX_ADDR和RX_ADDR两个地址。
所谓发送地址就是对于当前模块,你希望将数据在数据通道中发往哪个具体的地址,这个地址就是发送地址。对于发送模式下,需要对该地址进行配置。接收地址其实有些同学可能会越看越觉得难以理解,其实我们换个说法就很容易理解了,直接把接收地址理解为本地地址,也就是你用这个地址来接收别人发送的数据的地址,只有当别人发送过来的数据的发送地址和你的地址相同时,你才将这个数据进行接收。
对于接收地址,接收模式下需要进行配置,这是理所当然的,但是注意!接收地址在发送模式中也要进行配置(当工作在自动应答模式下),为什么呢?这是因为作为发送方,我们发送完数据后,还需要通过接收应答信号来确定接收方是否接收到了我们发送的数据,那么这个接收方的应答信号来了的话,发送方就需要去接收,因此这也是我们配置接收地址的原因了。
在NRF24L01模块中,发送地址TX_ADDR只有一个,而接收地址最多可以配置6个。
NRF24L01引脚介绍
数据手册上有很多引脚的介绍,但是我们实际使用的话主要用到其中6个引脚,如下图。
其中SPI的四个引脚就不过多介绍了。主要介绍另外两个引脚CE和IRQ。在下一节可以看到,CE的主要功能是用来配置NRF24L01的工作模式的。而另外一个引脚IRQ其实是中断引脚,这里具体解释一下这个引脚的作用是什么。
当下面的情况发生时,IRQ引脚会被硬件自动拉低用于提醒我们中断的产生:
1、接收到数据时,IRQ引脚会被硬件拉低;
2、发送数据成功时,IRQ引脚会被硬件拉低;
3、达到最大重发次数时,IRQ引脚会被硬件拉低;
当上述三种情况发生时,通过IRQ引脚的检测,我们可以知道事件的发生,比如在发射模式下,我们可以通过检测IRQ引脚是否被拉低来得知发送是否完成或者是发送次数达到最大重发次数。当IRQ引脚发起中断后,必须进行手动清除中断,否则IRQ引脚将一直保持低电平状态,在后面讲到的STATUS寄存器中,我们可以通过对STATUS寄存器的RX_DR、TX_DS、MAX_RT三个位置1分别清除三种情况产生的中断。
NRF24L01工作模式
在数据手册中,模块有6种工作模式,如果只是进行简单的通信实验我们不需要了解那么多,只需要知道三种模式:掉电模式、发射模式、接收模式。掉电模式顾名思义,就是让模块处于关机状态为了省电,此时PWR_UP位为0。发射模式和接收模式的引脚具体配置如下图所示,这里需要额外说明的有两点第一个是,在配置为发射模式时,需要将CE引脚拉低至少10us;第二个是不管是配置为发射模式还是接收模式,首先需要让模块处于掉电模式(不过在实际操作过程中发现好像没有进行这一步操作也没什么问题,但最好还是按手册规定来)。
NRF24L01相关操作码
这里主要介绍需要用到的操作码。一般不会用到的操作码就不介绍了,具体如下图所示。
R_REGISTER指令用于对寄存器进行读操作,0x000A AAAA,这里的A表示任意的数据,也就是你寄存器的地址,读操作时,前面三位必须是0;同理,写寄存器操作时,前面三位必须是001。
R_RX_PAYLOAD是读取接收数据寄存器中数据的操作码,最多读32字节。
W_RX_PAYLOAD是写数据到发送数据寄存器的操作,同样最多32字节。其实这里我不太理解为什么名称不是W_TX_PAYLOAD,在实际代码中我还是比较喜欢用W_TX_PAYLOAD来表示这个操作码的。
FLUSH_RX和FLUSH_TX就是用于清空接收数据寄存器和发送数据寄存器的操作码,因为有些情况寄存器中发送或者接收的数据会满了,如果没有及时清空里面的内容,会导致下一次的数据读取或接收产生错误。
寄存器地址
这里我们同样只介绍一般情况下需要用到的寄存器。
CONFIG寄存器
CONFIG寄存器主要用于对工作模式进行设置;
CONFIG寄存器地址:0x00
发射模式下设置参数:0x0E
接收模式设置参数:0x0F
EN_AA寄存器
EN_AA寄存器主要用于使能通道自动应答;
EN_AA寄存器地址:0x01
用到第几个通道就将对应的位置1,例如用到通道0则EN_AA:0x01;
EN_RXADDR寄存器
EN_RXADDR寄存器主要用于使能数据通道接收允许;
EN_RXADDR地址:0x02
用到第几个通道就将对应的位置1;
SETUP_AW寄存器
SETUP_AW寄存器地址:0x03
SETUP_AW寄存器主要用于对地址长度进行配置(第0位和第1位);
具体配置规则见下表。注意这里的地址长度是发射地址和接收地址,而不是单独某一个,也就是说发射地址和接收地址的长度必须一样。
SETUP_RETR寄存器
SETUP_RETR寄存器地址:0x04
SETUP_RETR寄存器主要用于重发相关的参数配置,主要包括重发时间间隔和重发次数;
RF_CH寄存器
RF_CH寄存器地址:0x05;
RF_CH寄存器主要用于设置通道的工作频率(第0位到第6位);
RF_SETUP寄存器
RF_SETUP寄存器地址:0x06;
RF_SETUP寄存器主要用于配置传输速率(第3位)和发射功率(第2位和第1位);
STATUS寄存器
STATUS寄存器地址:0x07:
STATUS寄存器主要用来获取当前模块状态的,通过该寄存器可以得知模块是否发送数据成功(发射模式下)、接收到数据(接收模式下)、是否达到最大重发次数(发射模式下)及其他相关信息。通过检查STATUS寄存器的RX_DR位可以判断在接收状态下是否收到数据,并对该位写1清除中断,实现对该位的清零操作。对于TX_DS和MAX_RT位同理。
RX_ADDR_P0寄存器
RX_ADDR_P0寄存器地址:0x0A;
RX_ADDR_P0寄存器主要用于设置接收通道0的接收地址,最大长度为5;
还有通道12345也是相同的,就不一一列出了;
TX_ADDR寄存器
TX_ADDR寄存器地址:0x10;
TX_ADDR寄存器主要用于配置发射地址,一个设备只有一个发射地址,最大长度5;
RX_PW_P0寄存器
RX_PW_P0寄存器地址:0x11;
RX_PW_P0寄存器主要用于设置接收通道0的接收地址宽度,范围在1-32,不能设置为0;通道12345也是相同的,就不一一列出了;
至此,所有需要用到的寄存器就全部介绍完了,接下来我们进入代码部分。
各功能代码
相关定义
#ifndef __NRF_H__
#define __NRF_H__
#include <STC8.H>
#include"UART.h"sbit CE=P6^0;
sbit CS=P6^1;
sbit CLK=P6^2;
sbit MOSI=P6^3;
sbit MISO=P6^4;
sbit IRQ=P6^5;#define TX_ADDR_WIDTH 5
#define RX_ADDR_WIDTH 5
#define TX_PLOAD_WIDTH 1
#define RX_PLOAD_WIDTH 1#define READ_REG 0x00
#define WRITE_REG 0x20
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2#define CONFIG 0x00
#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 RX_ADDR_P0 0x0A
#define TX_ADDR 0x10
#define RX_PW_P0 0x11#define RX_FLAG 0x40
#define TX_FLAG 0x20
#define MAX_FLAG 0x10void NRF_Init();
void NRF_Tx_Mode();
uchar NRF_Tx_Data(uchar*buffer);
void NRF_Rx_Mode();
uchar NRF_Rx_Data(uchar*buffer);
#endif
注意,引脚对应需要按照你自己的接线方式来修改。
NRF24L01初始化
void NRF_Init()
{CE=1;CS=1;CLK=0;
}
SPI读写
uchar SPI_RW(uchar Byte)
{uchar i;for(i=0;i<8;i++){if(Byte&0x80)MOSI=1;elseMOSI=0;CLK=1;Byte<<=1;if(MISO)Byte|=0x01;CLK=0;}return Byte;
}
NRF24L01写寄存器一个字节
uchar NRF_Write(uchar addr,Byte)
{uchar status;CS=0;status=SPI_RW(addr);SPI_RW(Byte);CS=1;return status;
}
NRF24L01读寄存器一个字节
uchar NRF_Read(uchar addr)
{uchar Byte;CS=0;SPI_RW(addr);Byte=SPI_RW(0xFF);CS=1;return Byte;
}
NRF24L01写多个字节
uchar NRF_Write_Buffer(uchar addr,uchar*buffer,uchar len)
{uchar status;CS=0;status=SPI_RW(addr);while(len--){SPI_RW(*buffer);buffer++;}CS=1;return status;
}
NRF24L01读多个字节
uchar NRF_Read_Buffer(uchar addr,uchar*buffer,uchar len)
{uchar status;CS=0;status=SPI_RW(addr);while(len--){*buffer=SPI_RW(0xFF);buffer++;}CS=1;return status;
}
NRF24L01配置为发射模式
void NRF_Tx_Mode()
{CE=0;NRF_Write_Buffer(WRITE_REG+TX_ADDR,TX_ADDRESS,TX_ADDR_WIDTH);NRF_Write_Buffer(WRITE_REG+RX_ADDR_P0,RX_ADDRESS,RX_ADDR_WIDTH);NRF_Write(WRITE_REG+EN_AA,0x01);NRF_Write(WRITE_REG+EN_RXADDR,0x01);NRF_Write(WRITE_REG+SETUP_AW,0x03);NRF_Write(WRITE_REG+SETUP_RETR,0x0F);NRF_Write(WRITE_REG+RF_CH,27);NRF_Write(WRITE_REG+RF_SETUP,0x0F);NRF_Write(WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);NRF_Write(WRITE_REG+CONFIG,0x0E);CE=1;
}
NRF24L01配置为接收模式
void NRF_Rx_Mode()
{CE=0;NRF_Write_Buffer(WRITE_REG+RX_ADDR_P0,RX_ADDRESS,RX_ADDR_WIDTH);NRF_Write(WRITE_REG+EN_AA,0x01);NRF_Write(WRITE_REG+EN_RXADDR,0x01);NRF_Write(WRITE_REG+SETUP_AW,0x03);NRF_Write(WRITE_REG+SETUP_RETR,0x0F);NRF_Write(WRITE_REG+RF_CH,27);NRF_Write(WRITE_REG+RF_SETUP,0x0F);NRF_Write(WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);NRF_Write(WRITE_REG+CONFIG,0x0F);CE=1;
}
NRF24L01发送数据
// 这里的返回值只是我自己调试代码的过程中自己设置的 你可以不要返回值或者按照你的需要进行修改返回值
uchar NRF_Tx_Data(uchar*buffer)
{uchar state;CE=0;NRF_Write_Buffer(W_TX_PAYLOAD,buffer,TX_PLOAD_WIDTH);CE=1;while(IRQ);state=NRF_Read(STATUS);NRF_Write(WRITE_REG+STATUS,state);if(state&TX_FLAG)return 0x01;else if(state&MAX_FLAG){NRF_Write(FLUSH_TX,0xFF);return 0xFF;}return 0x00;
}
NRF24L01接收数据
// 这里的返回值只是我自己调试代码的过程中自己设置的 你可以不要返回值或者按照你的需要进行修改返回值
uchar NRF_Rx_Data(uchar*buffer)
{uchar state;state=NRF_Read(STATUS);NRF_Write(WRITE_REG+STATUS,state);if(state&RX_FLAG){NRF_Read_Buffer(R_RX_PAYLOAD,buffer,RX_PLOAD_WIDTH);NRF_Write(FLUSH_RX,0xFF);return 0x01;}return 0x00;
}
总体代码
NRF.h
#ifndef __NRF_H__
#define __NRF_H__
#include <STC8.H>
#include"UART.h"sbit CE=P6^0;
sbit CS=P6^1;
sbit CLK=P6^2;
sbit MOSI=P6^3;
sbit MISO=P6^4;
sbit IRQ=P6^5;#define TX_ADDR_WIDTH 5
#define RX_ADDR_WIDTH 5
#define TX_PLOAD_WIDTH 1
#define RX_PLOAD_WIDTH 1#define READ_REG 0x00
#define WRITE_REG 0x20
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2#define CONFIG 0x00
#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 RX_ADDR_P0 0x0A
#define TX_ADDR 0x10
#define RX_PW_P0 0x11#define RX_FLAG 0x40
#define TX_FLAG 0x20
#define MAX_FLAG 0x10void NRF_Init();
void NRF_Tx_Mode();
uchar NRF_Tx_Data(uchar*buffer);
void NRF_Rx_Mode();
uchar NRF_Rx_Data(uchar*buffer);
#endif
NRF.h
#include"NRF.h"uchar TX_ADDRESS[]={0x00,0x11,0x22,0x33,0x44};
uchar RX_ADDRESS[]={0x00,0x11,0x22,0x33,0x44};void NRF_Init()
{CE=1;CS=1;CLK=0;
}
uchar SPI_RW(uchar Byte)
{uchar i;for(i=0;i<8;i++){if(Byte&0x80)MOSI=1;elseMOSI=0;CLK=1;Byte<<=1;if(MISO)Byte|=0x01;CLK=0;}return Byte;
}uchar NRF_Write(uchar addr,Byte)
{uchar status;CS=0;status=SPI_RW(addr);SPI_RW(Byte);CS=1;return status;
}
uchar NRF_Read(uchar addr)
{uchar Byte;CS=0;SPI_RW(addr);Byte=SPI_RW(0xFF);CS=1;return Byte;
}
uchar NRF_Write_Buffer(uchar addr,uchar*buffer,uchar len)
{uchar status;CS=0;status=SPI_RW(addr);while(len--){SPI_RW(*buffer);buffer++;}CS=1;return status;
}
uchar NRF_Read_Buffer(uchar addr,uchar*buffer,uchar len)
{uchar status;CS=0;status=SPI_RW(addr);while(len--){*buffer=SPI_RW(0xFF);buffer++;}CS=1;return status;
}
void NRF_Tx_Mode()
{CE=0;NRF_Write_Buffer(WRITE_REG+TX_ADDR,TX_ADDRESS,TX_ADDR_WIDTH);NRF_Write_Buffer(WRITE_REG+RX_ADDR_P0,RX_ADDRESS,RX_ADDR_WIDTH);NRF_Write(WRITE_REG+EN_AA,0x01);NRF_Write(WRITE_REG+EN_RXADDR,0x01);NRF_Write(WRITE_REG+SETUP_AW,0x03);NRF_Write(WRITE_REG+SETUP_RETR,0x0F);NRF_Write(WRITE_REG+RF_CH,27);NRF_Write(WRITE_REG+RF_SETUP,0x0F);NRF_Write(WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);NRF_Write(WRITE_REG+CONFIG,0x0E);CE=1;
}
uchar NRF_Tx_Data(uchar*buffer)
{uchar state;CE=0;NRF_Write_Buffer(W_TX_PAYLOAD,buffer,TX_PLOAD_WIDTH);CE=1;while(IRQ);state=NRF_Read(STATUS);NRF_Write(WRITE_REG+STATUS,state);if(state&TX_FLAG)return 0x01;else if(state&MAX_FLAG){NRF_Write(FLUSH_TX,0xFF);return 0xFF;}return 0x00;
}
void NRF_Rx_Mode()
{CE=0;NRF_Write_Buffer(WRITE_REG+RX_ADDR_P0,RX_ADDRESS,RX_ADDR_WIDTH);NRF_Write(WRITE_REG+EN_AA,0x01);NRF_Write(WRITE_REG+EN_RXADDR,0x01);NRF_Write(WRITE_REG+SETUP_AW,0x03);NRF_Write(WRITE_REG+SETUP_RETR,0x0F);NRF_Write(WRITE_REG+RF_CH,27);NRF_Write(WRITE_REG+RF_SETUP,0x0F);NRF_Write(WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);NRF_Write(WRITE_REG+CONFIG,0x0F);CE=1;
}
uchar NRF_Rx_Data(uchar*buffer)
{uchar state;state=NRF_Read(STATUS);NRF_Write(WRITE_REG+STATUS,state);if(state&RX_FLAG){NRF_Read_Buffer(R_RX_PAYLOAD,buffer,RX_PLOAD_WIDTH);NRF_Write(FLUSH_RX,0xFF);return 0x01;}return 0x00;
}
UART.h
#ifndef __UART_H__
#define __UART_H__#include <STC8.H>#define uchar unsigned char
void Uart1_Init();
void UartSendByte(uchar Byte);#endif
UART.c
#include"UART.h"void Uart1_Init(void) //9600bps@11.0592MHz
{SCON = 0x50; //8位数据,可变波特率AUXR |= 0x40; //定时器时钟1T模式AUXR &= 0xFE; //串口1选择定时器1为波特率发生器TMOD &= 0x0F; //设置定时器模式TL1 = 0xE0; //设置定时初始值TH1 = 0xFE; //设置定时初始值ET1 = 0; //禁止定时器中断TR1 = 1; //定时器1开始计时
}void UartSendByte(uchar Byte)
{SBUF=Byte;while(TI==0);TI=0;
}
main.c
#include <STC8.H>
#include"UART.h"
#include"NRF.h"
// 0:接收 1:发射
#define MODE 0
void Delay500ms(void) //@11.0592MHz
{unsigned char data i, j, k;i = 29;j = 14;k = 54;do{do{while (--k);} while (--j);} while (--i);
}
uchar tx_buffer[1];
uchar rx_buffer[1];
/*
程序说明
该程序实现了独立接线的NRF24L01模块的通信实验
成功实现发送与接收 可通过MODE的值设置为发射还是接收模式
发射和接收地址:{0x00,0x11,0x22,0x33,0x44} 通道27
*/
void main()
{uchar state;Uart1_Init();NRF_Init();if(MODE)NRF_Tx_Mode();elseNRF_Rx_Mode();while(1){if(MODE){tx_buffer[0]=(tx_buffer[0]+1)%10;state=NRF_Tx_Data(tx_buffer);UartSendByte(state);}else{state=NRF_Rx_Data(rx_buffer);if(state==0x01)UartSendByte(rx_buffer[0]);}Delay500ms();}
}
再次提醒!!!模块必须使用两个才能进行通信!!!需要用两块板子两个模块搭配才能进行通信!!!如果看完这篇文章对你有帮助的话不妨点个赞吧~~~