1.简介
功能简介:
实现由两块STM32F103C8T6为主控驱动NRF24L01向另一块NRF24L01进行单工的无线通讯并由两块0.96寸的OLED显示屏显示,实现效果如图。
图1为发送端显示的发送成功,图二则是接收端显示的len是发送过来数字的长度和前6位内容。
图1 图2
1.1 芯片简介
NRF24L01 是 NORDIC 公司最近生产的一款无线通信通信芯片,采用 FSK 调制,内部 集成 NORDIC 自己的 Enhanced Short Burst 协议。可以实现点对点或是 1 对 6 的无线通信。 无线通信速度可以达到 2M(bps)。NORDIC 公司提供通信模块的 GERBER 文件,可以直 接加工生产。嵌入式工程师或是单片机爱好者只需要为单片机系统预留 5 个 GPIO,1 个中断输入引脚,就可以很容易实现无线通信的功能,非常适合用来为 MCU 系统构建无线通信 功能。大家想要具体了解这个模块的所有功能和函数的解释可以在文章的最后下载本模块的手册去理解。
1.2 功能框图
NRF24L01 的框图如 图 所示,从单片机控制的角度来看,我们只需要关注右面 的六个控制和数据信号,分别为 CSN、SCK、MISO、MOSI、IRQ、CE。
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 的状态机)。
1.3 模块状态简介
NRF24L01主要有5个状态:
(1)Power Down Mode:掉电模式
(2)Tx Mode:发射模式
(3)Rx Mode:接收模式
(4)Standby-1Mode:待机 1 模式
(5)Standby-2 Mode:待机 2 模式
在本次1对1通讯的任务中我们只需要用到其中的(2)和(3),也就是发射模式和接收模式,所以就不过多介绍其他模式。
1.4 硬件原理图
/********** NRF24L01引脚定义 ***********/
#define IRQ_Port GPIOB
#define CE_Port GPIOB
#define CSN_Port GPIOA
#define SCK_Port GPIOA
#define MOSI_Port GPIOA
#define MISO_Port GPIOA
#define IRQ_Pin GPIO_Pin_1
#define CE_Pin GPIO_Pin_0
#define CSN_Pin GPIO_Pin_4
#define SCK_Pin GPIO_Pin_5
#define MOSI_Pin GPIO_Pin_7
#define MISO_Pin GPIO_Pin_6
这是NRF24L01与STM32F103C8T6的接线注释
大家如果想自己修改的话可以到下面源码中的NRF24L01_ins.h的文件中自行修改。
VCC脚接电压范围为1.9V~3.6V之间,不能在这个区间之外,超 过3.6V将会烧毁模块。推荐电压3.3V左右。(这里我选用的是STM32F103C8T6单片机,正好是3.3v,所以可以放心使用)
(2) 除电源VCC和接地端,其余脚都可以直接和普通的5V单片机IO口 直接相连,无需电平转换。当然对3V左右的单片机更加适用了。
(3) 硬件上面没有SPI的单片机也可以控制本模块,用普通单片机IO 口模拟SPI不需要单片机真正的串口介入,只需要普通的单片机IO口 就可以了,当然用串口也可以了
a:与51系列单片机P0口连接时候, 需要加10K的上 拉电阻,与其余口连接不需要。 与其余口连接不需要。
b:其他系列的单片机,如果是5V的,请参考该系列单片机IO口输出电流大小,如果超过IO口输出电流大小,如果超过单片机IO口输出电流大小,如果超过10mA,需要串联 电阻分压否则容易烧毁模块! 如果是3.3V的,可以直接和RF24l01模块的IO口线连接。比如AVR系列的单片机。
1.5 所需元器件清单
0.96寸的OLED屏幕如果只买一个也可以,因为这次的任务只是一对一通讯,只看到接收到的值即可,当然有两个屏幕最好,也方便之后调试互相收发。
2.程序源码
2.1 TX和RX的状态配置
void NRF24L01_RT_Init(void)
{
W_CE(0);
NRF24L01_Write_Reg(nRF_WRITE_REG+RX_PW_P0, RX_PLOAD_WIDTH);
NRF24L01_Write_Reg(FLUSH_RX, NOP);
NRF24L01_Write_Buf(nRF_WRITE_REG + TX_ADDR, (uint8_t*)TX_ADDRESS, TX_ADR_WIDTH);
NRF24L01_Write_Buf(nRF_WRITE_REG + RX_ADDR_P0, (uint8_t*)RX_ADDRESS, RX_ADR_WIDTH);
NRF24L01_Write_Reg(nRF_WRITE_REG + EN_AA, 0x01);
NRF24L01_Write_Reg(nRF_WRITE_REG + EN_RXADDR, 0x01);
NRF24L01_Write_Reg(nRF_WRITE_REG + SETUP_RETR, 0x1A);
NRF24L01_Write_Reg(nRF_WRITE_REG + RF_CH, 0);
NRF24L01_Write_Reg(nRF_WRITE_REG + RF_SETUP, 0x0F);
NRF24L01_Write_Reg(nRF_WRITE_REG + CONFIG, 0x0F);
W_CE(1);
}
在配置TX,RX模式方面我是一直初始化为接收模式,当要发送值时在发送的函数中改变为发送模式,这在后面源码中的void NRF24L01_SendBuf(uint8_t *Buf)函数有体现。
2.2 NRF24L01.C
# include "NRF24L01.h"
#include "NRF24L01_Ins.h"
#include "stm32f10x.h" // Device header
#include "Delay.h"
/***************************************************************************
主要函数使用说明:
NRF24L01_Pin_Init() 引脚初始化, 无参数,无返回值,一般不调用,初始化直接调用NRF24L01_Init()即可
*---------------------------------------------------------------------------*
NRF24L01_Write_Reg(uint8_t Reg, uint8_t Value) 写NRF24L01寄存器
参数:Reg, 寄存器地址
Value:要写入的数据
返回值:状态值
*---------------------------------------------------------------------------*
NRF24L01_Read_Reg(uint8_t Reg) 读NRF24L01寄存器
参数:Reg:寄存器地址
返回值:状态值
*---------------------------------------------------------------------------*
NRF24L01_Read_Buf(uint8_t Reg, uint8_t *Buf, uint8_t Len) 一次读NRF24L01寄存器多个字节
参数:Reg:寄存器地址;*Buf:寄存器字节读出后存储的数组;Len:要读出的字节个数
返回值:状态值
*---------------------------------------------------------------------------*
NRF24L01_Write_Buf(uint8_t Reg, uint8_t *Buf, uint8_t Len) 一次写NRF24L01寄存器多个字节
参数:Reg:寄存器地址;*Buf:寄存器写入字节的存放数组;Len:要读出的字节个数
返回值:状态值
*---------------------------------------------------------------------------*
NRF24L01_GetRxBuf(uint8_t *Buf) 读出接收到的数据,为多个字节
参数:*Buf多个字节存放的数组
返回值:状态值
*---------------------------------------------------------------------------*
NRF24L01_SendTxBuf(uint8_t *Buf) 发送多个字节
参数:*Buf要发送的字节存放的数组
返回值:状态值
*---------------------------------------------------------------------------*
NRF24L01_Check(void) 检验NRF24L01是否存在
返回值:1:不存在,0:存在
*---------------------------------------------------------------------------*
NRF24L01_Init() NRF24L01初始化,包含检验NRF24L01是否存在、收发配置初始化,初始化直接调用该函数即可
*---------------------------------------------------------------------------*
NRF24L01_RT_Init(void) NRF24L01收发配置初始化
*---------------------------------------------------------------------------*
NRF24L01_SendBuf(uint8_t *Buf) NRF24L01发送多个字节数据
参数:要发送字节存放的数组
*---------------------------------------------------------------------------*
uint8_t NRF24L01_Get_Value_Flag() 获取中断标志,一旦接收到数据,返回0
返回值:0:接收到数据;1:未接收到数据
************************** 结束 **********************************/
/*************** 结束 *******************************************/
#define TX_ADR_WIDTH 5 //5字节地址宽度
#define RX_ADR_WIDTH 5 //5字节地址宽度
#define TX_PLOAD_WIDTH 32 //32字节有效数据宽度
#define RX_PLOAD_WIDTH 32 //32字节有效数据宽度
const uint8_t TX_ADDRESS[TX_ADR_WIDTH]={0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
const uint8_t RX_ADDRESS[RX_ADR_WIDTH]={0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
//GPIO的简便输入方法
void W_SS(uint8_t BitValue)
{
GPIO_WriteBit(CSN_Port, CSN_Pin, (BitAction)BitValue);
}
void W_CE(uint8_t BitValue)
{
GPIO_WriteBit(CE_Port, CE_Pin, (BitAction)BitValue);
}
void W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(SCK_Port, SCK_Pin, (BitAction)BitValue);
}
void W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(MOSI_Port, MOSI_Pin,(BitAction)BitValue);
}
uint8_t R_MISO(void)
{
return GPIO_ReadInputDataBit(MISO_Port, MISO_Pin);
}
uint8_t R_IRQ(void)
{
return GPIO_ReadInputDataBit(IRQ_Port, IRQ_Pin);
}
/*****************************************************************************
函数:void NRF24L01_Pin_Init(void)
作用:模块GPIO口的初始化
*****************************************************************************/
void NRF24L01_Pin_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = CSN_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(CSN_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = SCK_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SCK_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = MOSI_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(MOSI_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = CE_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(CE_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = MISO_Pin;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(MISO_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = IRQ_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IRQ_Port, &GPIO_InitStructure);
}
uint8_t SPI_SwapByte(uint8_t Byte)
{
uint8_t i;
for(i = 0; i < 8; i ++)
{
if((uint8_t)(Byte & 0x80) == 0x80)
{
W_MOSI(1);
}
else
{
W_MOSI(0);
}
Byte = (Byte << 1);
W_SCK(1);
Byte |= R_MISO();
W_SCK(0);
}
return Byte;
}
uint8_t NRF24L01_Write_Reg(uint8_t Reg, uint8_t Value)
{
uint8_t Status;
W_SS(0);
Status = SPI_SwapByte(Reg);
SPI_SwapByte(Value);
W_SS(1);
return Status;
}
uint8_t NRF24L01_Read_Reg(uint8_t Reg)
{
uint8_t Value;
W_SS(0);
SPI_SwapByte(Reg);
Value = SPI_SwapByte(NOP);
W_SS(1);
return Value;
}
uint8_t NRF24L01_Read_Buf(uint8_t Reg, uint8_t *Buf, uint8_t Len)
{
uint8_t Status, i;
W_SS(0);
Status =SPI_SwapByte(Reg);
for(i = 0; i < Len; i ++)
{
Buf[i] = SPI_SwapByte(NOP);
}
W_SS(1);
return Status;
}
uint8_t NRF24L01_Write_Buf(uint8_t Reg, uint8_t *Buf, uint8_t Len)
{
uint8_t Status, i;
W_SS(0);
Status = SPI_SwapByte(Reg);
for(i = 0; i < Len; i ++)
{
SPI_SwapByte(*Buf ++);
}
W_SS(1);
return Status;
}
uint8_t NRF24L01_GetRxBuf(uint8_t *Buf)
{
uint8_t State;
State = NRF24L01_Read_Reg(STATUS);
NRF24L01_Write_Reg(nRF_WRITE_REG + STATUS, State);
if(State & RX_OK)
{
W_CE(1);
NRF24L01_Read_Buf(RD_RX_PLOAD, Buf, RX_PLOAD_WIDTH);
NRF24L01_Write_Reg(FLUSH_RX, NOP);
W_CE(1);
Delay_us(150);
return 0;
}
return 1;
}
uint8_t NRF24L01_SendTxBuf(uint8_t *Buf)
{
uint8_t State;
W_CE(0);
NRF24L01_Write_Buf(WR_TX_PLOAD, Buf, TX_PLOAD_WIDTH);
W_CE(1);
while(R_IRQ() == 1);
State = NRF24L01_Read_Reg(STATUS);
NRF24L01_Write_Reg(nRF_WRITE_REG + STATUS, State);
if(State&MAX_TX)
{
NRF24L01_Write_Reg(FLUSH_TX, NOP);
return MAX_TX;
}
if(State & TX_OK)
{
return TX_OK;
}
return NOP;
}
/*****************************************************************************
函数:uint8_t NRF24L01_Check(void)
作用:数据接收检测,先发送一个数组,再接收比较,是否传值正确,正确返回0,不正确返回1.
*****************************************************************************/
uint8_t NRF24L01_Check(void)
{
uint8_t check_in_buf[5] = {0x11 ,0x22, 0x33, 0x44, 0x55};
uint8_t check_out_buf[5] = {0x00};
W_SCK(0);
W_SS(1);
W_CE(0);
NRF24L01_Write_Buf(nRF_WRITE_REG + TX_ADDR, check_in_buf, 5);
NRF24L01_Read_Buf(nRF_READ_REG + TX_ADDR, check_out_buf, 5);
if((check_out_buf[0] == 0x11) && (check_out_buf[1] == 0x22) && (check_out_buf[2] == 0x33) && (check_out_buf[3] == 0x44) && (check_out_buf[4] == 0x55))
{
return 0;
}
else
{
return 1;
}
}
void NRF24L01_RT_Init(void)
{
W_CE(0);
NRF24L01_Write_Reg(nRF_WRITE_REG+RX_PW_P0, RX_PLOAD_WIDTH);
NRF24L01_Write_Reg(FLUSH_RX, NOP);
NRF24L01_Write_Buf(nRF_WRITE_REG + TX_ADDR, (uint8_t*)TX_ADDRESS, TX_ADR_WIDTH);
NRF24L01_Write_Buf(nRF_WRITE_REG + RX_ADDR_P0, (uint8_t*)RX_ADDRESS, RX_ADR_WIDTH);
NRF24L01_Write_Reg(nRF_WRITE_REG + EN_AA, 0x01);
NRF24L01_Write_Reg(nRF_WRITE_REG + EN_RXADDR, 0x01);
NRF24L01_Write_Reg(nRF_WRITE_REG + SETUP_RETR, 0x1A);
NRF24L01_Write_Reg(nRF_WRITE_REG + RF_CH, 0);
NRF24L01_Write_Reg(nRF_WRITE_REG + RF_SETUP, 0x0F);
NRF24L01_Write_Reg(nRF_WRITE_REG + CONFIG, 0x0F);
W_CE(1);
}
void NRF24L01_Init()
{
NRF24L01_Pin_Init();
while(NRF24L01_Check());
NRF24L01_RT_Init();
}
void NRF24L01_SendBuf(uint8_t *Buf)
{
W_CE(0);
NRF24L01_Write_Reg(nRF_WRITE_REG + CONFIG, 0x0E); //切换发送模式的配置
W_CE(1);
Delay_us(15);//延时15us
NRF24L01_SendTxBuf(Buf); //发送数组的值
W_CE(0);
NRF24L01_Write_Reg(nRF_WRITE_REG + CONFIG, 0x0F);//切换回接收模式
W_CE(1);
}
uint8_t NRF24L01_Get_Value_Flag()//接收标志位(判断是否有接收到数值)
{
return R_IRQ();
}
2.3 NRF24L01.H
#ifndef __nRF24L01_API_H
#define __nRF24L01_API_H
#include "stm32f10x.h" // Device header
uint8_t SPI_SwapByte(uint8_t byte);
uint8_t NRF24L01_Write_Reg(uint8_t reg,uint8_t value);
uint8_t NRF24L01_Read_Reg(uint8_t reg);
uint8_t NRF24L01_Read_Buf(uint8_t reg,uint8_t *pBuf, uint8_t len);
uint8_t NRF24L01_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len);
uint8_t NRF24L01_GetRxBuf(uint8_t *rxbuf);
uint8_t NRF24L01_SendTxBuf(uint8_t *txbuf);
uint8_t NRF24L01_Check(void);
void NRF24L01_RT_Init(void);
void NRF24L01_Init(void);
void NRF24L01_SendBuf(uint8_t *Buf);
void NRF24L01_Pin_Init(void);
uint8_t NRF24L01_Get_Value_Flag();
#endif
2.4 NRF24L01_INS.H
#ifndef __nRF24L01_H
#define __nRF24L01_H
/********** NRF24L01引脚定义 ***********/
#define IRQ_Port GPIOB
#define CE_Port GPIOB
#define CSN_Port GPIOA
#define SCK_Port GPIOA
#define MOSI_Port GPIOA
#define MISO_Port GPIOA
#define IRQ_Pin GPIO_Pin_1
#define CE_Pin GPIO_Pin_0
#define CSN_Pin GPIO_Pin_4
#define SCK_Pin GPIO_Pin_5
#define MOSI_Pin GPIO_Pin_7
#define MISO_Pin GPIO_Pin_6
/********** NRF24L01寄存器操作命令 ***********/
#define nRF_READ_REG 0x00
#define nRF_WRITE_REG 0x20
#define RD_RX_PLOAD 0x61
#define WR_TX_PLOAD 0xA0
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3
#define NOP 0xFF
/********** NRF24L01寄存器地址 *************/
#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 OBSERVE_TX 0x08
#define CD 0x09
#define RX_ADDR_P0 0x0A
#define RX_ADDR_P1 0x0B
#define RX_ADDR_P2 0x0C
#define RX_ADDR_P3 0x0D
#define RX_ADDR_P4 0x0E
#define RX_ADDR_P5 0x0F
#define TX_ADDR 0x10
#define RX_PW_P0 0x11
#define RX_PW_P1 0x12
#define RX_PW_P2 0x13
#define RX_PW_P3 0x14
#define RX_PW_P4 0x15
#define RX_PW_P5 0x16
#define FIFO_STATUS 0x17
/****** STATUS寄存器bit位定义 *******/
#define MAX_TX 0x10 //达到最大发送次数中断
#define TX_OK 0x20 //TX发送完成中断
#define RX_OK 0x40 //接收到数据中断
#endif
2.5 接收与发送主程序
这是发送的stm32单片机主程序
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "NRF24L01.h"
/***************************************************************
以下为NRF24L01接发送数据的模板
发送的数据格式如下
[第一位,第二位,第三位,第四位.....,第三十三位]
其中,第一位也就是Buf[0]位接收到的数据长度
第二位到第三十三位一个32个字节的数据位接受到的数据;
例子如下,5代表后面跟着5个数据
{5, 0x11, 0x22, 0x33, 0x44, 0x55};
*****************************************************************/
int main(void)
{
uint8_t Buf[32] = {6, 0x12, 0x22, 0x33, 0x44, 0x55,0x99};
OLED_Init();
NRF24L01_Init();
NRF24L01_SendBuf(Buf);
OLED_ShowString(1, 1, "job done");
while (1)
{
}
}
下面则是另一块接收的stm32的主程序
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "NRF24L01.h"
/***************************************************************
以下为NRF24L01接收数据的模板
NRF24L01_Get_Value_Flag()返回0时,代表NRF24L01接收到了数据,此时调用
NRF24L01_GetRxBuf()将得到的值储存在Buf这个数组中
接收到的数据格式如下
[第一位,第二位,第三位,第四位.....,第三十三位]
其中,第一位也就是Buf[0]位接收到的数据长度
第二位到第三十三位一个32个字节的数据位接受到的数据;
****************************************************************/
int main(void)
{
uint8_t Buf[32] = {0};
OLED_Init();
NRF24L01_Init();
OLED_ShowString(1, 1, "Len:");
while (1)
{
if (NRF24L01_Get_Value_Flag() == 0)
{
NRF24L01_GetRxBuf(Buf);
}
OLED_ShowHexNum(1, 5, Buf[0], 2);
OLED_ShowHexNum(2, 1, Buf[1], 2);
OLED_ShowHexNum(2, 4, Buf[2], 2);
OLED_ShowHexNum(3, 1, Buf[3], 2);
OLED_ShowHexNum(3, 4, Buf[4], 2);
OLED_ShowHexNum(4, 1, Buf[5], 2);
OLED_ShowHexNum(4, 4, Buf[6], 2);
}
}
2.6 注释
(1) 发送
在下载完程序之后,需要在都接通电源的情况下按一下发送的32的Reset键,数据会发送给NRF24L01,屏幕上只会显示最前面的六个值,如果大家需要更多值可以自行更改代码。
(2) 调试
因为NRF24L01设计到双向通讯,所以不推荐面包板接线,我更加推荐使用自己画好的板或者开发板,这样可以让大家减少硬件的错误排查时间,因为这不像普通的模块,可能本身就只有三四根线,这个不仅要连接的线比较多,而且有两个模块,错了也不知道到底是哪个模块出错了或者可能元件本身就是坏的。我就是一开始使用的面包板因为面包板的连接不稳定从而没有接收到传值,接线也是正确的,一度以为是程序的错误,一直在找文档去修改程序。后面用上了之前自己画的板一连上程序立马就成功了,所以大家在不知道程序的对错时应该尽量去减少硬件的错误。
(3) 源文件
大家在引用的时候因为里面有OLED和DELAY函数所以可能会报错,这是因为我没有给大家贴出源码,所以如果大家不知道怎么去写这些函数可以直接下载b站上江协科技的示例工程去套用。
如果大家觉得麻烦我这里给大家在网盘上附上完整的源码
链接:https://pan.baidu.com/s/1MqUXo3cq0TS_fzb6Q3GC8w?pwd=ADBC
提取码:ADBC
其中还有关于NRF24L01的文档,大家也可以通过这个文档来理解这个程序。
By:凌浩