一.前言

        这段时间的一个主线任务是做一辆平衡车,准备到一半发现自己没有遥控器,这怎么行呢,所以这个东西就这么出来了。因为玩F1的单片机比较熟悉,手上的资源也比较多,而且一个普通遥控对单片机的性能要求不是很高,所以我做的这块手柄的MCU就选用了F103C8T6。然后另一个比较主要的模块就是这个2.4G的遥控模块,NRF24L01,淘宝上比较多,大多也都是买来做遥控的。下面我就简单记录一下过程中遇到的一些问题。

二.硬件介绍

1.总述

图一 系统总览图

        如上图,左边是接收装置,右边是手柄的电路板。左边的OLED显示的是手柄摇杆的电压信号,此时两边的摇杆都居中,所以显示的数值就大概是2048(32的ADC是12位的,满电压量程是4096)。然后这个NRF24L01模块,本来我用的是贴片款的,但是在调试的时候拆下来了,换了一个直插的方便调试。中间的TFT因为屏幕底板板子打错了,FPC焊接的位置有问题,所以就先不焊上去,等后面有时间再搞吧,这个先将就着用。此外其他的不细说,简单提一下:震动马达驱动,IP5306充电管理电路,蜂鸣器电路,TYPE-C接口&&CH340电平转换芯片,拨轮开关,电源指示灯,通信指示灯。原理图和PCB图见下图。

图二 手柄原理图

 

图三 手柄PCB图

        PCB画的不是很好,存在一些不必要的过孔,走线有点绕。但是因为没有过大电流,也没有高速信号线,所以也还能用。板子其实可以不用这么大,但是为了手持裸板的手感,把PCB画成异形板,打了沉金,忏悔一下,主要就是没咋见过,想打一块回来看看,以后非必要不沉金,普通板的交货周期还快一些。

2.IP5306充电管理电路

        当初选型的时候我在TP4056和IP5306之间犹豫过,但是看这颗芯片有电量指示灯,且外围电路也比较简单,就选了这款芯片。后面画原理图的时候也上网搜了一下使用过这颗芯片出现的问题和建议。我现在也根据我的使用经历记录一下这颗芯片。

1.电源指示灯按照电路图画没问题,基本能够正常显示。

2.按键比较推荐画,如果没有需求,可以用软件模拟。

3.我的按键实际使用效果是没办法控制关断芯片的升压输出,短按两下没有任何作用。拿示波器打过,5号引脚输出的是矩形波。

4.在电量指示灯熄灭的时候短按一下按键可重新点亮,长按可以控制照明灯的开和关。

所以建议参考者不要希望靠S1按键去控制电源开关,而是应该在电池和BAT之间加一个开关控制输出。其他的自行尝试验证吧。

3.蜂鸣器电路

        在实际使用过程中,蜂鸣器区域发热比较严重,建议远离ADC采样电路,避免影响到采样。

4.TFT屏幕

        失误了,原理图有错,当然不是上面这个。不过如果自己买TFT屏幕来放上去的话,提醒一下记得修改PCB上的母座朝向,具体根据买的屏幕排针布局修改。

5.通信指示灯

        个人觉得这很有必要,这颗灯其实发挥的作用挺大的,不仅可以指示通信状态,而且还可以在你调试的时候给你比较大的帮助,尤其是在你没有屏幕显示打印数据的时候...当然可以TYPE-C连电脑用串口打印调试,不过个人比较喜欢用屏幕。

硬件部分大概就这么多。

三.软件介绍

        软件方面主要就是配置SPI通信,打开ADC通道,然后定时器的打开与关断,然后TYPE-C这里配一下串口初始化问题就解决了。比较难的是各个部分的协调,要理清各硬件的运行先后顺序,尽量减小互相的影响。单片机是顺序执行,再没有上操作系统的情况下,运行逻辑就比较重要,不然通信就不稳定,会频繁出现断连的情况。这点我也是花了比较多的时间去改进,到现在也没办法保证百分百稳定,只能说后面在硬件方面给NRF24L01模块加一个程控的复位开关,在断连的时候程控重启,然后重新连接。下面不多说了,仅展示部分代码。

-发送端

1.main.c

/********************************************************
发送端模块连接引脚对应:

VCC->3V3		CSN->PA4		MOSI->PA7		IRQ->PB12

GND->GND		CE->PB0			SCK->PA5		MISO->PA6

*********************************************************/
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "Buzzer.h"
#include "Motor.h"
#include "OLED.h"
#include "Serial.h"
#include "AD.h"
#include "Filter.h"
#include "MySPI.h"
#include "MyTimer.h"
#include "NRF24L01.h"

extern volatile uint8_t send_status;
extern volatile uint8_t timeout_flag;
extern volatile uint8_t Sendtimeout_flag;
uint8_t send_data[8];

uint16_t X1_AD,X2_AD,Y1_AD,Y2_AD;	//定义4通道的AD值变量

void CheckConnect(void);

int main(void)
{
	/*模块初始化*/
	AD_Init();				//AD初始化
	
	MySPI_Init();			//初始化SPI
	NVIC_Configuration();	//配置优先级
	EXTI_Configuration();	//配置外部中断
	Delay_ms(50);
    NRF24L01_Init();		//初始化NRF24L01

	Serial_Init();
	TIM1_Init();			//开启超时计时
	
	init_weight_sum();		//初始化权重总和
	LED_Init();
	Key_Init();
	
	beep_interval(2,5,5,1000);

	uint8_t SendCount=0;
	uint8_t KeyValue=0;
	
	while (1)
    {
		KeyValue=Key_GetNum();
		switch(KeyValue)
		{
			case 1:beep_interval(1, 5 , 5, 1000);break;
			case 2:beep_interval(1, 10, 5, 1000);break;
			case 3:beep_interval(1, 5 , 5, 1000);break;
		}
		
        X1_AD = AD_GetValue(0);					// 获取X1_AD转换的值
		Y1_AD = AD_GetValue(1);					// 获取Y1_AD转换的值
		Y2_AD = AD_GetValue(2);					// 获取Y2_AD转换的值
		X2_AD = AD_GetValue(3);					// 获取X2_AD转换的值
		
		Serial_Printf("AD1:%d\n",X1_AD);
		
		send_data[0] = X1_AD/100;
		send_data[1] = X1_AD%100;
		
		send_data[2] = Y1_AD/100;
		send_data[3] = Y1_AD%100;
		
		send_data[4] = X2_AD/100;
		send_data[5] = X2_AD%100;

		send_data[6] = Y2_AD/100;
		send_data[7] = Y2_AD%100;
		
		if(send_status==1)		//通信正常反馈
		{
			SendCount++;
			if(SendCount>10)
			{
				SendCount=0;
				LED1_Turn();
			}
		}
		if(send_status==2)		//通信异常反馈
		{
			LED1_OFF();
			send_status=0;
		}

		NRF24L01_Send_Data(send_data, 8);
    }
}

        这里解释一下,初始化权重总和这个是我原本打算对采集到的电压信号进行加权平均滤波,让信号毛刺少一点,但是后面发现不滤结果影响也不大,因为还没有实测,等实测了遥控小车再决定是否进行滤波操作。

2.NRF24L01.c

#include "stm32f10x.h"                  // Device header
#include "NRF24L010_def.h"
#include "MySPI.h"
#include "OLED.h"
#include "Delay.h"
#include "LED.h"
#include "Buzzer.h"
#include "Serial.h"

// 函数声明
void NRF24L01_Init(void);
void NRF24L01_Write_Reg(uint8_t reg, uint8_t value);
uint8_t NRF24L01_Read_Reg(uint8_t reg);
void NRF24L01_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len);
void NRF24L01_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len);
void NRF24L01_Send_Data(uint8_t *data, uint8_t len);

// 全局变量,用于标记发送状态
volatile uint8_t send_status = 0;
extern volatile uint8_t Sendtimeout_flag;
extern uint8_t send_data[8];

// NRF24L01 初始化
void NRF24L01_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // 修改时钟使能为 GPIOB
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = NRF24L01_CE_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    // 修改 GPIO 端口为 GPIOB
    GPIO_Init(NRF24L01_CE_PORT, &GPIO_InitStructure); 
    GPIO_ResetBits(NRF24L01_CE_PORT, NRF24L01_CE_PIN);

    NRF24L01_Write_Reg(SPI_WRITE_REG + CONFIG, 0x0E);      // 使能 CRC,2 字节 CRC,上电,发射模式
    NRF24L01_Write_Reg(SPI_WRITE_REG + EN_AA, 0x01);        // 使能通道 0 自动应答
    NRF24L01_Write_Reg(SPI_WRITE_REG + EN_RXADDR, 0x01);    // 使能通道 0 接收地址
    NRF24L01_Write_Reg(SPI_WRITE_REG + SETUP_AW, 0x03);     // 地址宽度为 5 字节
    NRF24L01_Write_Reg(SPI_WRITE_REG + SETUP_RETR, 0x1A);   // 自动重发延迟 750us,自动重发次数 10 次
    NRF24L01_Write_Reg(SPI_WRITE_REG + RF_CH, 0x40);        // 射频通道为 64
    NRF24L01_Write_Reg(SPI_WRITE_REG + RF_SETUP, 0x07);     // 发射功率 0dBm,数据速率 1Mbps
    NRF24L01_Write_Reg(SPI_WRITE_REG + RX_PW_P0, 8);        // 通道 0 接收数据长度为 8 字节

    uint8_t tx_address[5] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7};
    NRF24L01_Write_Buf(SPI_WRITE_REG + TX_ADDR, tx_address, 5);
    NRF24L01_Write_Buf(SPI_WRITE_REG + RX_ADDR_P0, tx_address, 5);

    GPIO_SetBits(NRF24L01_CE_PORT, NRF24L01_CE_PIN);

    // 配置 IRQ 引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Pin = NRF24L01_IRQ_PIN;
    GPIO_Init(NRF24L01_IRQ_PORT, &GPIO_InitStructure);
}

// 向 NRF24L01 的寄存器写入一个字节数据
void NRF24L01_Write_Reg(uint8_t reg, uint8_t value)
{
    MySPI_Start();
    MySPI_SwapByte(reg);
    MySPI_SwapByte(value);
    MySPI_Stop();
}

// 从 NRF24L01 的寄存器读取一个字节数据
uint8_t NRF24L01_Read_Reg(uint8_t reg)
{
    uint8_t value;
    MySPI_Start();
    MySPI_SwapByte(reg);
    value = MySPI_SwapByte(0xFF);
    MySPI_Stop();
    return value;
}

// 向 NRF24L01 的寄存器写入多个字节数据
void NRF24L01_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len)
{
    uint8_t i;
    MySPI_Start();
    MySPI_SwapByte(reg);
    for (i = 0; i < len; i++)
    {
        MySPI_SwapByte(pBuf[i]);
    }
    MySPI_Stop();
}

// 从 NRF24L01 的寄存器读取多个字节数据
void NRF24L01_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len)
{
    uint8_t i;
    MySPI_Start();
    MySPI_SwapByte(reg);
    for (i = 0; i < len; i++)
    {
        pBuf[i] = MySPI_SwapByte(0xFF);
    }
    MySPI_Stop();
}

// 清除最大重发标志位
void NRF24L01_ClearMaxRTFlag(void) 
{
    uint8_t status = NRF24L01_Read_Reg(STATUS);
    status |= MAX_RT; // 将MAX_RT位置1
    NRF24L01_Write_Reg(SPI_WRITE_REG + STATUS, status); // 写入状态寄存器以清除标志位
}

// 清空TX FIFO缓冲区
void NRF24L01_FlushTXFIFO(void) 
{
    NRF24L01_Write_Reg(FLUSH_TX, 0);
}

// 发送数据到 NRF24L01
void NRF24L01_Send_Data(uint8_t *data, uint8_t len)
{
    send_status = 0;
    GPIO_ResetBits(NRF24L01_CE_PORT, NRF24L01_CE_PIN);
    NRF24L01_Write_Buf(WR_TX_PLOAD, data, len);
    GPIO_SetBits(NRF24L01_CE_PORT, NRF24L01_CE_PIN);

    // 等待发送完成中断
	TIM_Cmd(TIM1, ENABLE);		//使能计时器
    while(send_status == 0 && Sendtimeout_flag < 5)
	{
		Serial_Printf("time:%d status:%d",Sendtimeout_flag,send_status);
	}
	Sendtimeout_flag=0;			//即使清零
	TIM_Cmd(TIM1, DISABLE);		//失能计时器
}

// 外部中断配置
void EXTI_Configuration(void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    // 将 PB12 连接到 EXTI 线 12
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource12);

    EXTI_InitStructure.EXTI_Line = EXTI_Line12;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}

// NVIC 配置
void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

// 外部中断服务函数
void EXTI15_10_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line12) != RESET)
    {
        uint8_t status = NRF24L01_Read_Reg(STATUS);
        if (status & TX_OK)
        {
            send_status = 1; 			 // 标记发送成功
        }
        if (status & MAX_TX)
        {
            send_status = 2; 			// 标记达到最大重发次数
			NRF24L01_ClearMaxRTFlag();  // 清除MAX_RT标志位
			NRF24L01_FlushTXFIFO();     // 清空TX FIFO缓冲区
			NRF24L01_Init();            // 重新初始化模块
        }
        NRF24L01_Write_Reg(SPI_WRITE_REG + STATUS, status); // 清除中断标志
        EXTI_ClearITPendingBit(EXTI_Line12);
    }
}

3.NRF24L01_def.h

#ifndef __NRF24L01_DEF_H
#define __NRF24L01_DEF_H

// 定义 NRF24L01 的 CE 引脚
#define NRF24L01_CE_PORT GPIOB
#define NRF24L01_CE_PIN GPIO_Pin_0
// 定义 NRF24L01 的 IRQ 引脚
#define NRF24L01_IRQ_PORT GPIOB
#define NRF24L01_IRQ_PIN GPIO_Pin_12

//无线收发地址宽度(字节数)
#define TX_ADDR_WIDTH 5			//40位来表示地址信息
#define RX_ADDR_WIDTH 5			//40位来表示地址信息
 
//无线收发数据长度(字节数)
#define TX_PLOAD_WIDTH 1
#define RX_PLOAD_WIDTH 1
 
//无线收发中断标志
#define MAX_TX  	    0x10    //达到最大发送次数中断
#define TX_OK       	0x20    //TX发送完成中断
#define RX_OK   	    0x40    //接收到数据中断
 
/****************************************************************************************************/
//NRF24L01寄存器操作命令(共11个)
#define SPI_READ_REG    0x00  //读配置寄存器,低5位为寄存器地址
#define SPI_WRITE_REG   0x20  //写配置寄存器,低5位为寄存器地址
#define RD_RX_PLOAD     0x61  //读RX有效数据(低字节先出),1~32字节
#define WR_TX_PLOAD     0xA0  //写TX有效数据,1~32字节
#define FLUSH_TX        0xE1  //清除TX FIFO寄存器.发射模式下用
#define FLUSH_RX        0xE2  //清除RX FIFO寄存器.接收模式下用
#define REUSE_TX_PL     0xE3  //重新使用上一包数据,CE为高,数据包被不断发送
#define RD_RX_PL_WID    0x60  //读RX有效数据(高字节先出),1~32字节
#define W_ACK_PLOAD     0xA0  //发送寄存器,写入数据可发送出去
#define W_TX_PLOAD_NACK 0xB0  //发送寄存器,写入数据可发送出去,但不应答
#define RF_NOP          0xFF  //空操作,可以用来读状态寄存器	 

//NRF24L01寄存器地址(共24个)
#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寄存器;bit5,bit3:传输速率(00:1Mbps,01:2Mbps,10:250Kbps);bit2:1,发射功率;bit0:低噪声放大器增益
#define STATUS          0x07  //状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最大:6);bit4,达到最多次重发
                              //bit5:数据发送完成中断;bit6:接收数据中断;
#define MAX_RT 			0x10  //STATUS寄存器中的多次重发寄存器地址
#define OBSERVE_TX      0x08  //发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器
#define CD              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,不循环;
/****************************************************************************************************/															
 
 #endif

-接收端

下面是接收端的代码

1.main.c

/****************************************************
发送端模块连接引脚对应:

VCC->3V3		CSN->PA4		MOSI->PA7		IRQ->PA8

GND->GND		CE->PA3			SCK->PA5		MISO->PA6

****************************************************/
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "AD.h"
#include "Filter.h"
#include "MySPI.h"
#include "NRF24L01.h"

extern volatile uint8_t receive_status;
extern uint8_t received_data[RXLen];

/*测试代码*/
int main(void)
{
	OLED_Init();
	MySPI_Init();
	EXTI_Configuration();
    NVIC_Configuration();
    
    NRF24L01_Init();
   
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Received:");
	OLED_ShowString(2, 1, "X1:");
	OLED_ShowString(2, 9, "Y1:");
	OLED_ShowString(3, 1, "X2:");
	OLED_ShowString(3, 9, "Y2:");
	
    while (1)
    {
        if (receive_status == 1)
        {
			// 处理接收到的数据
			OLED_ShowNum(2,4,received_data[0]*100+received_data[1],4);
			OLED_ShowNum(2,12,received_data[2]*100+received_data[3],4);
			OLED_ShowNum(3,4,received_data[4]*100+received_data[5],4);
			OLED_ShowNum(3,12,received_data[6]*100+received_data[7],4);
            receive_status = 0; // 处理完数据后,重置接收状态
        }
    }
}

2.NRF24L01.c

#include "stm32f10x.h"                  // Device header
#include "NRF24L010_def.h"
#include "NRF24L01.h"
#include "MySPI.h"
#include "OLED.h"
#include "Delay.h"

void NRF24L01_Init(void);
void NRF24L01_Write_Reg(uint8_t reg, uint8_t value);
uint8_t NRF24L01_Read_Reg(uint8_t reg);
void NRF24L01_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len);
void NRF24L01_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len);
uint8_t NRF24L01_CheckDataReceived(void);
void NRF24L01_ReadData(uint8_t *data, uint8_t len);
void EXTI_Configuration(void);
void NVIC_Configuration(void);

// 全局变量,用于标记接收状态
volatile uint8_t receive_status = 0;
// 存储接收到的数据
uint8_t received_data[RXLen];

// NRF24L01 初始化
void NRF24L01_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = NRF24L01_CE_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(NRF24L01_CE_PORT, &GPIO_InitStructure);
    GPIO_ResetBits(NRF24L01_CE_PORT, NRF24L01_CE_PIN);

    NRF24L01_Write_Reg(SPI_WRITE_REG + CONFIG, 0x0F);      // 使能 CRC,2 字节 CRC,上电,接收模式
    NRF24L01_Write_Reg(SPI_WRITE_REG + EN_AA, 0x01);        // 使能通道 0 自动应答
    NRF24L01_Write_Reg(SPI_WRITE_REG + EN_RXADDR, 0x01);    // 使能通道 0 接收地址
    NRF24L01_Write_Reg(SPI_WRITE_REG + SETUP_AW, 0x03);     // 地址宽度为 5 字节
    NRF24L01_Write_Reg(SPI_WRITE_REG + SETUP_RETR, 0x1A);   // 自动重发延迟 750us,自动重发次数 10 次
    NRF24L01_Write_Reg(SPI_WRITE_REG + RF_CH, 0x40);        // 射频通道为 64
    NRF24L01_Write_Reg(SPI_WRITE_REG + RF_SETUP, 0x07);     // 发射功率 0dBm,数据速率 1Mbps
    NRF24L01_Write_Reg(SPI_WRITE_REG + RX_PW_P0, RXLen);        // 通道 0 接收数据长度为 8 字节

    uint8_t rx_address[5] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7};
    NRF24L01_Write_Buf(SPI_WRITE_REG + RX_ADDR_P0, rx_address, 5);

    GPIO_SetBits(NRF24L01_CE_PORT, NRF24L01_CE_PIN);

    // 配置 IRQ 引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Pin = NRF24L01_IRQ_PIN;
    GPIO_Init(NRF24L01_IRQ_PORT, &GPIO_InitStructure);
	
//	OLED_ShowString(1,1,"NRF Inited");						//
//	Delay_s(1);
//	OLED_Clear();
}

// 向 NRF24L01 的寄存器写入一个字节数据
void NRF24L01_Write_Reg(uint8_t reg, uint8_t value)
{
    MySPI_Start();
    MySPI_SwapByte(reg);
    MySPI_SwapByte(value);
    MySPI_Stop();
}

// 从 NRF24L01 的寄存器读取一个字节数据
uint8_t NRF24L01_Read_Reg(uint8_t reg)
{
    uint8_t value;
    MySPI_Start();
    MySPI_SwapByte(reg);
    value = MySPI_SwapByte(0xFF);
    MySPI_Stop();
    return value;
}

// 向 NRF24L01 的寄存器写入多个字节数据
void NRF24L01_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len)
{
    uint8_t i;
    MySPI_Start();
    MySPI_SwapByte(reg);
    for (i = 0; i < len; i++)
    {
        MySPI_SwapByte(pBuf[i]);
    }
    MySPI_Stop();
}

// 从 NRF24L01 的寄存器读取多个字节数据
void NRF24L01_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len)
{
    uint8_t i;
    MySPI_Start();
    MySPI_SwapByte(reg);
    for (i = 0; i < len; i++)
    {
        pBuf[i] = MySPI_SwapByte(0xFF);
    }
    MySPI_Stop();
}

// 从 NRF24L01 读取接收到的数据
void NRF24L01_ReadData(uint8_t *data, uint8_t len)
{
    NRF24L01_Read_Buf(RD_RX_PLOAD, data, len);
    NRF24L01_Write_Reg(FLUSH_RX, 0xFF); // 清空接收 FIFO
}

// 外部中断配置
void EXTI_Configuration(void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    // 将 PA8 连接到 EXTI 线 8
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource8);

    EXTI_InitStructure.EXTI_Line = EXTI_Line8;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
	
//	OLED_ShowString(1,1,"EXTI Inited");						//
//	Delay_s(1);
//	OLED_Clear();
}

// NVIC 配置
void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

    NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
	
//	OLED_ShowString(1,1,"NVIC Inited");						//
//	Delay_s(1);
//	OLED_Clear();
}

// 外部中断服务函数
void EXTI9_5_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line8) != RESET)
    {
        uint8_t status = NRF24L01_Read_Reg(STATUS);
        if (status & RX_OK)
        {
            receive_status = 1; // 标记接收到数据
            NRF24L01_ReadData(received_data, RXLen);
        }
        NRF24L01_Write_Reg(SPI_WRITE_REG + STATUS, status); // 清除中断标志
        EXTI_ClearITPendingBit(EXTI_Line8);
    }
}

3.NRF24L01_def.h

#ifndef __NRF24L01_DEF_H
#define __NRF24L01_DEF_H

// 定义 NRF24L01 的 CE 引脚
#define NRF24L01_CE_PORT GPIOA
#define NRF24L01_CE_PIN GPIO_Pin_3
// 定义 NRF24L01 的 IRQ 引脚
#define NRF24L01_IRQ_PORT GPIOA
#define NRF24L01_IRQ_PIN GPIO_Pin_8

//无线收发地址宽度(字节数)
#define TX_ADDR_WIDTH 5			//40位来表示地址信息
#define RX_ADDR_WIDTH 5			//40位来表示地址信息
 
//无线收发数据长度(字节数)
#define TX_PLOAD_WIDTH 1
#define RX_PLOAD_WIDTH 1
 
//无线收发中断标志
#define MAX_TX  	    0x10    //达到最大发送次数中断
#define TX_OK       	0x20    //TX发送完成中断
#define RX_OK   	    0x40    //接收到数据中断
 
/****************************************************************************************************/
//NRF24L01寄存器操作命令(共11个)
#define SPI_READ_REG    0x00  //读配置寄存器,低5位为寄存器地址
#define SPI_WRITE_REG   0x20  //写配置寄存器,低5位为寄存器地址
#define RD_RX_PLOAD     0x61  //读RX有效数据(低字节先出),1~32字节
#define WR_TX_PLOAD     0xA0  //写TX有效数据,1~32字节
#define FLUSH_TX        0xE1  //清除TX FIFO寄存器.发射模式下用
#define FLUSH_RX        0xE2  //清除RX FIFO寄存器.接收模式下用
#define REUSE_TX_PL     0xE3  //重新使用上一包数据,CE为高,数据包被不断发送
#define RD_RX_PL_WID    0x60  //读RX有效数据(高字节先出),1~32字节
#define W_ACK_PLOAD     0xA0  //发送寄存器,写入数据可发送出去
#define W_TX_PLOAD_NACK 0xB0  //发送寄存器,写入数据可发送出去,但不应答
#define RF_NOP          0xFF  //空操作,可以用来读状态寄存器	 

//NRF24L01寄存器地址(共24个)
#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寄存器;bit5,bit3:传输速率(00:1Mbps,01:2Mbps,10:250Kbps);bit2:1,发射功率;bit0:低噪声放大器增益
#define STATUS          0x07  //状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最大:6);bit4,达到最多次重发
                              //bit5:数据发送完成中断;bit6:接收数据中断;
#define OBSERVE_TX      0x08  //发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器
#define CD              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,不循环;
/****************************************************************************************************/															
 
 #endif

        修改发送通道字节大小的时候要注意,一次性能发送的字节数从1-36都可以,但是发送端和接收端一定要一致,否则没办法正常通信。修改的时候在NRF24L01_Init()这个函数里,以及在外部中断函数里,都要修改大小。建议写的时候用宏定义,不然修改的时候很容易遗漏。我的代码里,接收端使用了宏定义,在我的H文件里,没有贴出来,发送端没有用宏定义,要注意!

其他的就不贴咯,比较简单,问问AI都能直接出来。

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐