NRF24L01 2.4G模块与STM32硬件SPI的集成指南

模块介绍

引脚定义

程序编写

        我使用两个STM32F103C8T6核心板、两个2.4G模块和两个OLED屏,一个发送一个接收。实现发送端每秒计数加一,接收端接收并显示。下面是引脚分配。

NRF24L01引脚 主控GPIO
VCC 3.3V
GND GND
CSN PB12
CE PA11
MOSI PB15
SCK PB13
IRQ PA8
MISO PB14

        开始编写代码,先初始化引脚和SPI。

void NRF24L01_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	SPI_InitTypeDef  SPI_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);		 	

	//CSN B12 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;		
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 	// 推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	
 	GPIO_Init(GPIOB, &GPIO_InitStructure);						
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);
	
	//CE A11
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;		
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 	// 推挽输出
 	GPIO_Init(GPIOA, &GPIO_InitStructure);						
	GPIO_ResetBits(GPIOA,GPIO_Pin_11);
	
	//IRQ A8
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_8;   
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; 				// 下拉输入  
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//SPI2
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  			// 复用推挽输出 
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;						// SPI主机
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;					// 发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;							// 时钟悬空低
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;						// 数据捕获于第1个时钟沿
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;							// NSS信号由软件控制
	
	//APB1的PCLK为36MHZ APB2的PCLK为72MHZ
	//NRF24L01 SPI最高速率10Mbps SPI=36MHZ/4=9Mhz
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;	// 定义波特率预分频的值:波特率预分频值为4
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;					// 数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;							// CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);
 
	SPI_Cmd(SPI2, ENABLE); 		// 使能SPI外设
	
	NRF24L01_CE(0);		//待机模式
	NRF24L01_CSN(1);	//取消SPI片选
	
}

        编写SPI读写函数

// SPI读写一个字节
// TxData:要写入的字节
// 返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{		
	u8 TxWait = 0;
	u8 RxWait = 0;
	
	// 等待发送缓存为空
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
	{
		TxWait++;
		if(TxWait>250)	// 等待时间过长则放弃本次读写
		return 0;
	}	

	SPI_I2S_SendData(SPI2, TxData); // SPI2写一个字节
	
	// 等待接收缓存为空
	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)
	{
		RxWait++;
		if(RxWait>250)	// 等待时间过长则放弃本次读写
		return 0;
	}	  	
	
	return SPI_I2S_ReceiveData(SPI2); // 将读到的字节返回					    
}

        在.h文件中导入NRF24L01的操作命令和寄存器地址

//SI24R1寄存器操作命令
#define 	R_REGISTER    		0x00  	// 读寄存器,低5位为寄存器地址
#define 	W_REGISTER   		0x20  	// 写寄存器,低5位为寄存器地址
#define 	R_RX_PAYLOAD     	0x61  	// 读RX有效数据,1~32字节
#define 	W_TX_PAYLOAD     	0xA0  	// 写TX有效数据,1~32字节
#define 	FLUSH_TX        	0xE1  	// 清除TX FIFO寄存器.发射模式下用
#define 	FLUSH_RX        	0xE2  	// 清除RX FIFO寄存器.接收模式下用
#define 	REUSE_TX_PL     	0xE3  	// 重新使用上一包数据,CE为高,数据包被不断发送.
#define 	NOP             	0xFF  	// 空操作,可以用来读状态寄存器	 

//SPI(SI24R1)寄存器地址
#define 	CONFIG          	0x00  	// 配置寄存器地址;bit0:1接收模式,0发射模式;bit1:电选择;bit2:CRC模式;bit3:CRC使能;
										// bit4:中断MAX_RT(达到最大重发次数中断)使能;bit5:中断TX_DS使能;bit6:中断RX_DR使能
#define 	EN_AA           	0x01  	// 使能自动应答功能  bit0~5,对应通道0~5
#define 	EN_RXADDR       	0x02  	// 接收地址允许,bit0~5,对应通道0~5
#define 	SETUP_AW        	0x03  	// 设置地址宽度(所有数据通道):bit1,0:00,3字节;01,4字节;02,5字节;
#define 	SETUP_RETR      	0x04  	// 建立自动重发;bit3:0,自动重发计数器;bit7:4,自动重发延时 250*x+86us
#define 	RF_CH           	0x05  	// RF通道,bit6:0,工作通道频率;
#define 	RF_SETUP        	0x06  	// RF寄存器;bit3:传输速率(0:1Mbps,1:2Mbps);bit2:1,发射功率;bit0:低噪声放大器增益
#define 	STATUS          	0x07  	// 状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最大:6);bit4,达到最多次重发
										// bit5:数据发送完成中断;bit6:接收数据中断;
										
#define 	OBSERVE_TX      	0x08  	// 发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器
#define 	RSSI              	0x09  	// 载波检测寄存器,bit0,载波检测;
#define 	RX_ADDR_P0      	0x0A  	// 数据通道0接收地址,最大长度5个字节,低字节在前
#define 	RX_ADDR_P1      	0x0B  	// 数据通道1接收地址,最大长度5个字节,低字节在前
#define 	RX_ADDR_P2      	0x0C  	// 数据通道2接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define 	RX_ADDR_P3      	0x0D  	// 数据通道3接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define 	RX_ADDR_P4      	0x0E  	// 数据通道4接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define 	RX_ADDR_P5      	0x0F  	// 数据通道5接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define 	TX_ADDR         	0x10  	// 发送地址(低字节在前),ShockBurstTM模式下,RX_ADDR_P0与此地址相等
#define 	RX_PW_P0        	0x11  	// 接收数据通道0有效数据宽度(1~32字节),设置为0则非法
#define 	RX_PW_P1        	0x12  	// 接收数据通道1有效数据宽度(1~32字节),设置为0则非法
#define 	RX_PW_P2        	0x13  	// 接收数据通道2有效数据宽度(1~32字节),设置为0则非法
#define 	RX_PW_P3        	0x14  	// 接收数据通道3有效数据宽度(1~32字节),设置为0则非法
#define 	RX_PW_P4        	0x15  	// 接收数据通道4有效数据宽度(1~32字节),设置为0则非法
#define 	RX_PW_P5        	0x16  	// 接收数据通道5有效数据宽度(1~32字节),设置为0则非法
#define 	FIFO_STATUS 		0x17  	// FIFO状态寄存器;bit0,RX FIFO寄存器空标志;bit1,RX FIFO满标志;bit2,3,保留
										// bit4,TX FIFO空标志;bit5,TX FIFO满标志;bit6,1,循环发送上一数据包.0,不循环;

        下面编写读写NRF24L01寄存器的函数,根据原理图每次读写时都要下拉CSN引脚。

// SPI对SI24R1寄存器的写操作
// Reg 			= 寄存器
// Write_Value 	= 写入的值
// return		= 状态值
u8 RF2G4_Write_Reg(u8 Reg,u8 Write_Value)
{
	u8 V_Reg;	
	
   	NRF24L01_CSN(0);                  		// 使能SPI传输
	
  	V_Reg =SPI2_ReadWriteByte(Reg);		// 发送寄存器号 
	
  	SPI2_ReadWriteByte(Write_Value);	// 写入寄存器的值
	
  	NRF24L01_CSN(1);                  		// 禁止SPI传输	 
	
  	return(V_Reg);       				// 返回状态值
}

// SPI对SI24R1寄存器的读操作
// Reg 		= 寄存器
// return 	= 写入的值
u8 RF2G4_Read_Reg(u8 Reg)
{
	u8 V_Reg;	    
	
 	NRF24L01_CSN(0);           			// 使能SPI传输		
	
  	SPI2_ReadWriteByte(Reg);   			// 发送寄存器号
	
  	V_Reg=SPI2_ReadWriteByte(0xFF);		// 读取寄存器内容
	
  	NRF24L01_CSN(1);           			// 禁止SPI传输		    
	
  	return(V_Reg);           			// 返回状态值
}

// 向寄存器中写入指定长度的数据
// Reg		= 寄存器
// P_Data 	= 数据指针
// N_Data 	= 数据个数
// return 	= 此次读到的状态寄存器值
u8 RF2G4_Write_Cont(u8 Reg, u8* P_Data, u8 N_Data)
{
	u8 V_Reg = 0;
	
 	NRF24L01_CSN(0);          			// 使能SPI传输
	
  	V_Reg = SPI2_ReadWriteByte(Reg);	// 选定寄存器,并读取状态值
	
  	for(u8 i=0; i<N_Data; i++)
	{
		SPI2_ReadWriteByte(*P_Data++); 	// 写入数据
	}
		 
  	NRF24L01_CSN(1);       				// 关闭SPI传输
	
  	return V_Reg;          				// 返回读到的状态值
}

	

// 从寄存器中读出指定长度的数据
// Reg = 寄存器(位置)
// P_Data = 数据指针
// N_Data = 数据个数
// return = 此次读到的状态寄存器值
u8 RF2G4_Read_Cont(u8 Reg, u8* P_Data, u8 N_Data)
{
	u8 V_Reg;
	
  	NRF24L01_CSN(0);           			// 使能SPI传输
	
  	V_Reg=SPI2_ReadWriteByte(Reg);		// 发送寄存器值(位置),并读取状态值
	
 	for(u8 i=0; i<N_Data;i++)
		P_Data[i] = SPI2_ReadWriteByte(0xFF);	// 读出数据

  	NRF24L01_CSN(1);       				// 关闭SPI传输
	
  	return V_Reg;        				// 返回读到的状态值
}

        下面我们编写一个模块检测函数,往寄存器里面写入数据并读取,查看读写的数据是否一致,以此来判断模块是否接入。

//检测设备是否接入
//return 0接入 1未接入
u8 RF2G4_Check(void)
{
	u8 Array_Test[5]={0X66,0X66,0X66,0X66,0X66};
	
	RF2G4_Write_Cont(W_REGISTER+TX_ADDR,Array_Test,5);	// 写入5个字节的地址.	
	
	RF2G4_Read_Cont(TX_ADDR,Array_Test,5); 				// 读出写入的地址  
	
	for(u8 i=0; i<5; i++)
	if(Array_Test[i]!=0X66)
		return 1;
	
	return 0;		 			
	
}

        接着按照SPI指令配置模块的收发模式和各项参数。

// 初始化为接收模式
void RF2G4_RX_Mode(void)
{
	NRF24L01_CE(0);	  
  	RF2G4_Write_Cont(W_REGISTER+RX_ADDR_P0, (u8*)RF2G4_ADDR_RX, RX_ADR_WIDTH);	// 设置RX节点地址
	  
  	RF2G4_Write_Reg(W_REGISTER+EN_AA,0x01);    		// 使能通道0的自动应答
  	RF2G4_Write_Reg(W_REGISTER+EN_RXADDR,0x01);		// 使能通道0的接收地址
  	RF2G4_Write_Reg(W_REGISTER+RF_CH,40);	     	// 设置RF通信频率
  	RF2G4_Write_Reg(W_REGISTER+RX_PW_P0,14);		// 设置通道0的有效数据宽度(14位)
  	RF2G4_Write_Reg(W_REGISTER+RF_SETUP,0x27);		// 设置:发射功率7dBm、射频数据率250kbps
  	RF2G4_Write_Reg(W_REGISTER+CONFIG, 0x0F);		// 配置参数;接收模式、开机模式、CRC=2Byte、开启CRC、。。。
  	NRF24L01_CE(1); 	// CE为高,进入接收模式 
}	

// 初始化为发射模式
void RF2G4_TX_Mode(void)
{														 
	NRF24L01_CE(0);	    
  	RF2G4_Write_Cont(W_REGISTER+TX_ADDR,(u8*)RF2G4_ADDR_TX,TX_ADR_WIDTH);		// 设置TX节点地址
  	RF2G4_Write_Cont(W_REGISTER+RX_ADDR_P0,(u8*)RF2G4_ADDR_RX,RX_ADR_WIDTH); 	// 设置RX节点地址(ACK)	  
	
  	RF2G4_Write_Reg(W_REGISTER+EN_AA,0x01);     	// 使能通道0的自动应答    
  	RF2G4_Write_Reg(W_REGISTER+EN_RXADDR,0x01); 	// 使能通道0的接收地址  
  	RF2G4_Write_Reg(W_REGISTER+SETUP_RETR,0x1A);	// 设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次
  	RF2G4_Write_Reg(W_REGISTER+RF_CH,40);       	// 设置RF通信频率
  	RF2G4_Write_Reg(W_REGISTER+RF_SETUP,0x27);  	// 设置:发射功率7dBm、射频数据率250kbps  
  	RF2G4_Write_Reg(W_REGISTER+CONFIG,0x0E);    	// 配置参数;接收模式、开机模式、CRC=2Byte、开启CRC、。。。
	NRF24L01_CE(1);		//CE为高,10us后启动发送
}

        最后编写模块发送和接收数据函数

// SI24R1发送一帧数据
// P_Data	= 待发送数据首地址
// N_Data	= 待发送数据的个数
// return	= 发送完成状况
u8 RF2G4_Tx_Packet(u8* P_Data,u8 N_Data)
{
	u8 V_Reg;  
	
	NRF24L01_CE(0);
	
  	RF2G4_Write_Cont(W_TX_PAYLOAD,P_Data,N_Data);	// 将要发送的数据写入发送缓存器
	
 	NRF24L01_CE(1);			// 启动发送	   
	
	while(R_IRQ() != 0);	// 等待发送完成
	
	V_Reg = RF2G4_Read_Reg(STATUS);  // 读取状态寄存器的值	   
	
	RF2G4_Write_Reg(W_REGISTER+STATUS,V_Reg); 	// 清除TX_DS/MAX_RT中断标志
	
	
	if(V_Reg & TX_MAX)	// 判断是否达到最大重发次数
	{
		RF2G4_Write_Reg(FLUSH_TX,0xFF);	// 清除TX_FIFO 
		
		return TX_FAIL_MAX; 	// 返回发送失败(达到最大重发次数)
	}
	
	if(V_Reg & TX_OK)	
	{
		return TX_SUCCESS;		// 返回发送成功
	}

	return TX_FAIL;				// 返回发送失败
}

// SI24R1接收一帧数据
// P_Data	= 缓存接收数据的首地址
// N_Data	= 待缓存数据的个数
// return	= 数据接收情况
u8 RF2G4_Rx_Packet(u8* P_Data,u8 N_Data)
{
	u8 V_Reg;		    							   
	
	V_Reg=RF2G4_Read_Reg(STATUS);  				// 读取状态寄存器的值    	 
	
	RF2G4_Write_Reg(W_REGISTER+STATUS,V_Reg); 	// 清除TX_DS/MAX_RT中断标志
	
	if(V_Reg & RX_OK)		// 判断是否接收到数据
	{
		RF2G4_Read_Cont(R_RX_PAYLOAD,P_Data,N_Data);	// 读取数据
		
		RF2G4_Write_Reg(FLUSH_RX,0xFF);					// 清除RX_FIFO 
		
		return RX_SUCCESS; 	// 返回接收成功
	}	  
	
	return RX_FAIL;			// 返回接收失败
}

实验现象

代码参考:https://oshwhub.com/jixin/2_4G-56cf3a971e094f78885dc230ecbe10d2

源码:https://pan.baidu.com/s/1pI64JNFKxsIBKnjRpMgXAw?pwd=sus2

作者:Jesse Ning

物联沃分享整理
物联沃-IOTWORD物联网 » NRF24L01 2.4G模块与STM32硬件SPI的集成指南

发表回复