【STM32HAL-----NRF24L01】

1. NRF24L01介绍

Cn:NRF24L01命令

Sn:STATUS寄存器

Dn:数据位

模块支持SPI工作模式0即CPOL=0(SCK空闲状态为低电平),CPHA=0(数据在时钟第一个时钟边沿采样)。标准的SPI接口,最大的数据传输率10Mbps。

1.1. NRF24L01写时序

1.2. NRF24L01读时序

1.3. NRF24L01操作指令

2. NRF24L01工作模式

3. NRF24L01数据包处理

3.1. 发送流程

3.1.1. 发送模式初始化
3.1.2. 启动发送

3.2. 接收流程

3.2.1. 接收模式初始化
3.2.2. 启动接收

4. NRF24L01寄存器介绍

4.1. 配置寄存器(CONFIG,0x00)

4.2. 自动应答使能寄存器(EN_AA,0x01)

4.3. RX地址使能寄存器(EN_RXADDR,0x02)

4.4. 地址宽度设置寄存器(SETUP_AW,0x03)

4.5. 自动重发设置寄存器(SETUP_RETR,0x04)

4.6. 射频频率设置寄存器(RF_CH,0x05)

4.7. 射频配置寄存器(RF_SETUP,0x06)

4.8. 状态寄存器(STATUS,0x07)

4.9. 数据通道0接收地址寄存器(RX_ADDR_P0,0x0A)

4.10. 发送地址设置寄存器(TX_ADDR,0x10)

4.11. 接收通道0有效数据宽度设置寄存器(RX_PW_P0,0x11)

5. 编程实战

使用两块32单片机各自搭载NRF24L01无线收发模块,一个为接收端,一个为发送端,发送端发送遥控器的数据到接收端。

5.1. 发送端

5.1.1. STM32CubeMX软件配置
5.1.2. 代码编写
cpp 复制代码
#ifndef __BSP_SPI_H
#define __BSP_SPI_H


#include "spi.h"


#define SPI1_GPIO_PORT			GPIOA
#define SPI1_GPIO_PIN_SCK		GPIO_PIN_5
#define SPI1_GPIO_PIN_MISO		GPIO_PIN_6
#define SPI1_GPIO_PIN_MOSI		GPIO_PIN_7

#define NRF_CE_GPIO_PORT		GPIOA           // SPI模式控制
#define NRF_CE_GPIO_PIN			GPIO_PIN_15
#define NRF_CE(x)               HAL_GPIO_WritePin(NRF_CE_GPIO_PORT, NRF_CE_GPIO_PIN, (GPIO_PinState)(x))

#define NRF_CSN_GPIO_PORT		GPIOA           // SPI片选
#define NRF_CSN_GPIO_PIN		GPIO_PIN_4  
#define NRF_CSN(x)              HAL_GPIO_WritePin(NRF_CSN_GPIO_PORT, NRF_CSN_GPIO_PIN, (GPIO_PinState)(x))

#define NRF_IRQ_GPIO_PORT		GPIOB           // 中断引脚
#define NRF_IRQ_GPIO_PIN		GPIO_PIN_1
#define NRF_IRQ()               HAL_GPIO_ReadPin(NRF_IRQ_GPIO_PORT, NRF_IRQ_GPIO_PIN)    


void Bsp_Spi_Init(void);            // bsp_Spi初始化
void Spi_Csn(uint8_t csn_state);            // SPI片选
uint8_t spi2_read_write_byte(uint8_t txdata);
void spi2_set_speed(uint8_t speed);

#endif
cpp 复制代码
#include "bsp_spi.h"



/// @brief BSP_SPI初始化
/// @param  无
void Bsp_Spi_Init(void) 
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 1. 启用时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();      // 启用GPIOA时钟
    __HAL_RCC_SPI1_CLK_ENABLE();       // 启用SPI1时钟
	__HAL_RCC_GPIOB_CLK_ENABLE();	   // 启用GPIOB时钟

    // 2. 配置CE和CSN和IRQ引脚(推挽输出)
    GPIO_InitStruct.Pin = NRF_CE_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;             /* 推挽输出 */
    GPIO_InitStruct.Pull = GPIO_PULLUP;                     /* 上拉 */
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;           /* 高速 */
    HAL_GPIO_Init(NRF_CE_GPIO_PORT, &GPIO_InitStruct); /* 初始化CE引脚 */

    GPIO_InitStruct.Pin = NRF_CSN_GPIO_PIN;
    HAL_GPIO_Init(NRF_CSN_GPIO_PORT, &GPIO_InitStruct);/* 初始化CSN引脚 */

    GPIO_InitStruct.Pin = NRF_IRQ_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;                 /* 输入 */
    GPIO_InitStruct.Pull = GPIO_PULLUP;                     /* 上拉 */
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;           /* 高速 */
    HAL_GPIO_Init(NRF_IRQ_GPIO_PORT, &GPIO_InitStruct);/* 初始化CE引脚 */

    // 初始状态:CE低,CSN高
    HAL_GPIO_WritePin(NRF_CE_GPIO_PORT, NRF_CE_GPIO_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(NRF_CSN_GPIO_PORT, NRF_CSN_GPIO_PIN, GPIO_PIN_SET);

    // 3. 配置SPI引脚(复用功能)
    __HAL_RCC_SPI1_CLK_ENABLE(); /* SPI2时钟使能 */

    hspi1.Instance = SPI1;                                /* SPI1 */
    hspi1.Init.Mode = SPI_MODE_MASTER;                        /* 设置SPI工作模式,设置为主模式 */
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;              /* 设置SPI单向或者双向的数据模式:SPI设置为双线模式 */
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;                  /* 设置SPI的数据大小:SPI发送接收8位帧结构 */
    hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;               /* 串行同步时钟的空闲状态为高电平 */
    hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;                    /* 串行同步时钟的第二个跳变沿(上升或下降)数据被采样 */
    hspi1.Init.NSS = SPI_NSS_SOFT;                            /* NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制 */
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; /* 定义波特率预分频的值:波特率预分频值为256 */
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;                   /* 指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 */
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;                   /* 关闭TI模式 */
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;   /* 关闭硬件CRC校验 */
    hspi1.Init.CRCPolynomial = 7;                             /* CRC值计算的多项式 */
    HAL_SPI_Init(&hspi1);                                     /* 初始化 */

    __HAL_SPI_ENABLE(&hspi1); /* 使能SPI2 */

    spi2_read_write_byte(0Xff); /* 启动传输, 实际上就是产生8个时钟脉冲, 达到清空DR的作用, 非必需 */
}


/// @brief SPI片选引引脚高电平
/// @param  
void Spi_Csn(uint8_t csn_state)
{
    if(csn_state == 1)
        HAL_GPIO_WritePin(NRF_CSN_GPIO_PORT, NRF_CSN_GPIO_PIN, GPIO_PIN_SET);
    else if(csn_state == 0)
        HAL_GPIO_WritePin(NRF_CSN_GPIO_PORT, NRF_CSN_GPIO_PIN, GPIO_PIN_RESET);
}

/**
 * @brief       SPI2速度设置函数
 *   @note      SPI2时钟选择来自APB1, 即PCLK1, 为36Mhz
 *              SPI速度 = PCLK1 / 2^(speed + 1)
 * @param       speed   : SPI2时钟分频系数
                        取值为SPI_BAUDRATEPRESCALER_2~SPI_BAUDRATEPRESCALER_2 256
 * @retval      无
 */
void spi2_set_speed(uint8_t speed)
{
    assert_param(IS_SPI_BAUDRATE_PRESCALER(speed)); /* 判断有效性 */
    __HAL_SPI_DISABLE(&hspi1);             /* 关闭SPI */
    hspi1.Instance->CR1 &= 0XFFC7;         /* 位3-5清零,用来设置波特率 */
    hspi1.Instance->CR1 |= speed << 3;     /* 设置SPI速度 */
    __HAL_SPI_ENABLE(&hspi1);              /* 使能SPI */
}

/**
 * @brief       SPI2读写一个字节数据
 * @param       txdata  : 要发送的数据(1字节)
 * @retval      接收到的数据(1字节)
 */
uint8_t spi2_read_write_byte(uint8_t txdata)
{
    uint8_t rxdata;
    HAL_SPI_TransmitReceive(&hspi1, &txdata, &rxdata, 1, 1000);
    return rxdata; /* 返回收到的数据 */
}
cpp 复制代码
#ifndef __NRF24L01_H
#define __NRF24L01_H


#include "bsp_spi.h"

/******************************************************************************************/
/* NRF24L01 操作引脚 定义(不包含SPI_SCK/MISO/MISO等三根线) */

#define NRF24L01_CE_GPIO_PORT              GPIOA
#define NRF24L01_CE_GPIO_PIN               GPIO_PIN_15
#define NRF24L01_CE_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define NRF24L01_CSN_GPIO_PORT             GPIOA
#define NRF24L01_CSN_GPIO_PIN              GPIO_PIN_4
#define NRF24L01_CSN_GPIO_CLK_ENABLE()     do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */

#define NRF24L01_IRQ_GPIO_PORT             GPIOB
#define NRF24L01_IRQ_GPIO_PIN              GPIO_PIN_1
#define NRF24L01_IRQ_GPIO_CLK_ENABLE()     do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PG口时钟使能 */

/******************************************************************************************/

/* 24L01操作线 */
#define NRF24L01_CE(x)    do{ x ? \
                              HAL_GPIO_WritePin(NRF24L01_CE_GPIO_PORT, NRF24L01_CE_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(NRF24L01_CE_GPIO_PORT, NRF24L01_CE_GPIO_PIN, GPIO_PIN_RESET); \
                          }while(0)       /* 24L01模式选择信号 */

#define NRF24L01_CSN(x)   do{ x ? \
                              HAL_GPIO_WritePin(NRF24L01_CSN_GPIO_PORT, NRF24L01_CSN_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(NRF24L01_CSN_GPIO_PORT, NRF24L01_CSN_GPIO_PIN, GPIO_PIN_RESET); \
                          }while(0)       /* 24L01片选信号 */

#define NRF24L01_IRQ      HAL_GPIO_ReadPin(NRF24L01_IRQ_GPIO_PORT, NRF24L01_IRQ_GPIO_PIN) /* IRQ主机数据输入 */


/* 24L01发送接收数据宽度定义 
 * 用户必须根据实际情况设置正确的数据宽度和数据长度
 * 发送端&接收端必须保持一致, 否则将导致通信失败!!!!
 */
#define TX_ADR_WIDTH    5       /* 5字节的地址宽度 */
#define RX_ADR_WIDTH    5       /* 5字节的地址宽度 */
#define TX_PLOAD_WIDTH  32      /* 32字节的用户数据宽度 */
#define RX_PLOAD_WIDTH  32      /* 32字节的用户数据宽度 */

/* SPI总线速度设置 */
#define SPI_SPEED_2         0
#define SPI_SPEED_4         1
#define SPI_SPEED_8         2
#define SPI_SPEED_16        3
#define SPI_SPEED_32        4
#define SPI_SPEED_64        5
#define SPI_SPEED_128       6
#define SPI_SPEED_256       7

/******************************************************************************************/
/* NRF24L01寄存器操作命令 */
#define NRF_READ_REG    0x00    /* 读配置寄存器,低5位为寄存器地址 */
#define NRF_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 NOP             0xFF    /* 空操作,可以用来读状态寄存器 */

/* SPI(NRF24L01)寄存器地址 */
#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 MAX_TX          0x10    /* 达到最大发送次数中断 */
#define TX_OK           0x20    /* TX发送完成中断 */
#define RX_OK           0x40    /* 接收到数据中断 */

#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 NRF_FIFO_STATUS 0x17    /* FIFO状态寄存器;bit0,RX FIFO寄存器空标志;bit1,RX FIFO满标志;bit2,3,保留 */
                                /* bit4,TX FIFO空标志;bit5,TX FIFO满标志;bit6,1,循环发送上一数据包.0,不循环; */
/******************************************************************************************/



#define NRF_TICK_TIME   100


typedef struct nrf24l01_param_t
{
    uint8_t tx_buff[33];    // 发送缓存
    uint8_t rx_buff[33];    // 接收缓存
} nrf24l01_param_t;

typedef struct nrf24l01_struct_t
{
    uint8_t state;      // 模块状态

    nrf24l01_param_t param;
    
    uint8_t en;         // 模块使能
    uint8_t tick;       // 模块运行时间
} nrf24l01_t;


extern nrf24l01_t nrf24l01;


/* 函数申明 */
static uint8_t nrf24l01_write_buf(uint8_t reg, uint8_t *pbuf, uint8_t uint8_ts);   /* 写数据区 */
static uint8_t nrf24l01_read_buf(uint8_t reg, uint8_t *pbuf, uint8_t uint8_ts);    /* 读数据区 */

void nrf24l01_spi_init(void);   /* 针对NRF24L01修改SPI2驱动 */
void nrf24l01_init(void);       /* 初始化 */
void nrf24l01_rx_mode(void);    /* 配置为接收模式 */
void nrf24l01_tx_mode(void);    /* 配置为发送模式 */
uint8_t nrf24l01_check(void);   /* 检查24L01是否存在 */
uint8_t nrf24l01_tx_packet(uint8_t *ptxbuf);     /* 发送一个包的数据 */
uint8_t nrf24l01_rx_packet(uint8_t *prxbuf);     /* 接收一个包的数据 */
void Nrf_Timing_Callback(void);             // NRF模块定时任务
void Nrf_Task(void);                        // NRF模块任务
void Nrf_Init(void);                // NRF模块初始化

#endif
cpp 复制代码
#include "nrf24l01.h"
#include "joystick.h"


nrf24l01_t nrf24l01 = {0};

const uint8_t TX_ADDRESS[TX_ADR_WIDTH] = {0x34, 0x43, 0x10, 0x10, 0x01};    /* 发送地址 */
const uint8_t RX_ADDRESS[RX_ADR_WIDTH] = {0x34, 0x43, 0x10, 0x10, 0x01};    /* 接收地址 */

 
/**
 * @brief       针对NRF24L01修改SPI2驱动
 * @param       无
 * @retval      无
 */
void nrf24l01_spi_init(void)
{
    __HAL_SPI_DISABLE(&hspi1);                 /* 先关闭SPI2 */
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; /* 串行同步时钟的空闲状态为低电平 */
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;     /* 串行同步时钟的第1个跳变沿(上升或下降)数据被采样 */
    HAL_SPI_Init(&hspi1);
    __HAL_SPI_ENABLE(&hspi1);                  /* 使能SPI2 */
}

/**
 * @brief       初始化24L01的IO口
 *   @note      将SPI2模式改成SCK空闲低电平,及SPI 模式0
 * @param       无
 * @retval      无
 */
void nrf24l01_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;

    NRF24L01_CE_GPIO_CLK_ENABLE();  /* CE脚时钟使能 */
    NRF24L01_CSN_GPIO_CLK_ENABLE(); /* CSN脚时钟使能 */
    NRF24L01_IRQ_GPIO_CLK_ENABLE(); /* IRQ脚时钟使能 */

    gpio_init_struct.Pin = NRF24L01_CE_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;             /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                     /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;           /* 高速 */
    HAL_GPIO_Init(NRF24L01_CE_GPIO_PORT, &gpio_init_struct); /* 初始化CE引脚 */

    gpio_init_struct.Pin = NRF24L01_CSN_GPIO_PIN;
    HAL_GPIO_Init(NRF24L01_CSN_GPIO_PORT, &gpio_init_struct);/* 初始化CSN引脚 */

    gpio_init_struct.Pin = NRF24L01_IRQ_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_INPUT;                 /* 输入 */
    gpio_init_struct.Pull = GPIO_PULLUP;                     /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;           /* 高速 */
    HAL_GPIO_Init(NRF24L01_IRQ_GPIO_PORT, &gpio_init_struct);/* 初始化CE引脚 */

    Bsp_Spi_Init();                /* 初始化SPI2 */
    nrf24l01_spi_init();        /* 针对NRF的特点修改SPI的设置 */
    NRF24L01_CE(0);             /* 使能24L01 */
    NRF24L01_CSN(1);            /* SPI片选取消 */
}

/**
 * @brief       检测24L01是否存在
 * @param       无
 * @retval      0, 成功; 1, 失败;
 */
uint8_t nrf24l01_check(void)
{
    uint8_t buf[5] = {0XA5, 0XA5, 0XA5, 0XA5, 0XA5};
    uint8_t i;
    spi2_set_speed(SPI_SPEED_32);                         /* spi速度为7.5Mhz(24L01的最大SPI时钟为10Mhz) */
    nrf24l01_write_buf(NRF_WRITE_REG + TX_ADDR, buf, 5);  /* 写入5个字节的地址. */
    nrf24l01_read_buf(TX_ADDR, buf, 5);                   /* 读出写入的地址 */

    for (i = 0; i < 5; i++)
    {
        if (buf[i] != 0XA5) break;
    }
    
    if (i != 5) return 1;   /* 检测24L01错误 */

    return 0;               /* 检测到24L01 */
}

/**
 * @brief       NRF24L01写寄存器
 * @param       reg   : 寄存器地址
 * @param       value : 写入寄存器的值
 * @retval      状态寄存器值
 */
static uint8_t nrf24l01_write_reg(uint8_t reg, uint8_t value)
{
    uint8_t status;
    NRF24L01_CSN(0);                    /* 使能SPI传输 */
    status = spi2_read_write_byte(reg); /* 发送寄存器号 */
    spi2_read_write_byte(value);        /* 写入寄存器的值 */
    NRF24L01_CSN(1);                    /* 禁止SPI传输 */
    return status;                      /* 返回状态值 */
}

/**
 * @brief       NRF24L01读寄存器
 * @param       reg   : 寄存器地址
 * @retval      读取到的寄存器值;
 */
static uint8_t nrf24l01_read_reg(uint8_t reg)
{
    uint8_t reg_val;
    NRF24L01_CSN(0);            /* 使能SPI传输 */
    spi2_read_write_byte(reg);  /* 发送寄存器号 */
    reg_val = spi2_read_write_byte(0XFF);   /* 读取寄存器内容 */
    NRF24L01_CSN(1);            /* 禁止SPI传输 */
    return reg_val;             /* 返回状态值 */
}

/**
 * @brief       在指定位置读出指定长度的数据
 * @param       reg   : 寄存器地址
 * @param       pbuf  : 数据指针
 * @param       len   : 数据长度
 * @retval      状态寄存器值
 */
static uint8_t nrf24l01_read_buf(uint8_t reg, uint8_t *pbuf, uint8_t len)
{
    uint8_t status, i;
    NRF24L01_CSN(0);    /* 使能SPI传输 */
    status = spi2_read_write_byte(reg);         /* 发送寄存器值(位置),并读取状态值 */

    for (i = 0; i < len; i++)
    {
        pbuf[i] = spi2_read_write_byte(0XFF);   /* 读出数据 */
    }
    
    NRF24L01_CSN(1);    /* 关闭SPI传输 */
    return status;      /* 返回读到的状态值 */
}

/**
 * @brief       在指定位置写指定长度的数据
 * @param       reg   : 寄存器地址
 * @param       pbuf  : 数据指针
 * @param       len   : 数据长度
 * @retval      状态寄存器值
 */
static uint8_t nrf24l01_write_buf(uint8_t reg, uint8_t *pbuf, uint8_t len)
{
    uint8_t status, i;
    NRF24L01_CSN(0);    /* 使能SPI传输 */
    status = spi2_read_write_byte(reg);/* 发送寄存器值(位置),并读取状态值 */

    for (i = 0; i < len; i++)
    {
        spi2_read_write_byte(*pbuf++); /* 写入数据 */
    }
    
    NRF24L01_CSN(1);    /* 关闭SPI传输 */
    return status;      /* 返回读到的状态值 */
}

/**
 * @brief       启动NRF24L01发送一次数据(数据长度 = TX_PLOAD_WIDTH)
 * @param       ptxbuf : 待发送数据首地址
 * @retval      发送完成状态
 *   @arg       0    : 发送成功
 *   @arg       1    : 达到最大发送次数,失败
 *   @arg       0XFF : 其他错误
 */
uint8_t nrf24l01_tx_packet(uint8_t *ptxbuf)
{
    uint8_t sta;
    uint8_t rval = 0XFF;
    
    NRF24L01_CE(0);
    nrf24l01_write_buf(WR_TX_PLOAD, ptxbuf, TX_PLOAD_WIDTH);    /* 写数据到TX BUF  TX_PLOAD_WIDTH个字节 */
    NRF24L01_CE(1); /* 启动发送 */

    // while (NRF24L01_IRQ != 0);          /* 等待发送完成 */

    sta = nrf24l01_read_reg(STATUS);    /* 读取状态寄存器的值 */
    nrf24l01_write_reg(NRF_WRITE_REG + STATUS, sta);    /* 清除TX_DS或MAX_RT中断标志 */

    if (sta & MAX_TX)   /* 达到最大重发次数 */
    {
        nrf24l01_write_reg(FLUSH_TX, 0xff); /* 清除TX FIFO寄存器 */
        rval = 1;
    }

    if (sta & TX_OK)/* 发送完成 */
    {
        rval = 0;   /* 标记发送成功 */
    }

    return rval;    /* 返回结果 */
}

/**
 * @brief       启动NRF24L01接收一次数据(数据长度 = RX_PLOAD_WIDTH)
 * @param       prxbuf : 接收数据缓冲区首地址
 * @retval      接收完成状态
 *   @arg       0 : 接收成功
 *   @arg       1 : 失败
 */
uint8_t nrf24l01_rx_packet(uint8_t *prxbuf)
{
    uint8_t sta;
    uint8_t rval = 1;
    
    sta = nrf24l01_read_reg(STATUS); /* 读取状态寄存器的值 */
    nrf24l01_write_reg(NRF_WRITE_REG + STATUS, sta); /* 清除RX_OK中断标志 */

    if (sta & RX_OK)    /* 接收到数据 */
    {
        nrf24l01_read_buf(RD_RX_PLOAD, prxbuf, RX_PLOAD_WIDTH); /* 读取数据 */
        nrf24l01_write_reg(FLUSH_RX, 0xff); /* 清除RX FIFO寄存器 */
        rval = 0;       /* 标记接收完成 */
    }

    return rval;    /* 返回结果 */
}

/**
 * @brief       NRF24L01进入接收模式
 *   @note      设置RX地址,写RX数据宽度,选择RF频道,波特率
 *              当CE变高后,即进入RX模式,并可以接收数据了
 * @param       无
 * @retval      无
 */
void nrf24l01_rx_mode(void)
{
    NRF24L01_CE(0);
    nrf24l01_write_buf(NRF_WRITE_REG + RX_ADDR_P0, (uint8_t *)RX_ADDRESS, RX_ADR_WIDTH);    /* 写RX节点地址 */

    nrf24l01_write_reg(NRF_WRITE_REG + EN_AA, 0x01);        /* 使能通道0的自动应答 */
    nrf24l01_write_reg(NRF_WRITE_REG + EN_RXADDR, 0x01);    /* 使能通道0的接收地址 */
    nrf24l01_write_reg(NRF_WRITE_REG + RF_CH, 40);          /* 设置RF通信频率 */
    nrf24l01_write_reg(NRF_WRITE_REG + RX_PW_P0, RX_PLOAD_WIDTH);   /* 选择通道0的有效数据宽度 */
    nrf24l01_write_reg(NRF_WRITE_REG + RF_SETUP, 0x0f);     /* 设置TX发射参数,0db增益,2Mbps */
    nrf24l01_write_reg(NRF_WRITE_REG + CONFIG, 0x0f);       /* 配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式 */
    NRF24L01_CE(1); /* CE为高,进入接收模式 */
}

/**
 * @brief       NRF24L01进入发送模式
 *   @note      设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,选择RF频道,波特率和
 *              PWR_UP,CRC使能
 *              当CE变高后,即进入TX模式,并可以发送数据了, CE为高大于10us,则启动发送.
 * @param       无
 * @retval      无
 */
void nrf24l01_tx_mode(void)
{
    NRF24L01_CE(0);
    nrf24l01_write_buf(NRF_WRITE_REG + TX_ADDR, (uint8_t *)TX_ADDRESS, TX_ADR_WIDTH);       /* 写TX节点地址 */
    nrf24l01_write_buf(NRF_WRITE_REG + RX_ADDR_P0, (uint8_t *)RX_ADDRESS, RX_ADR_WIDTH);    /* 设置RX节点地址,主要为了使能ACK */

    nrf24l01_write_reg(NRF_WRITE_REG + EN_AA, 0x01);        /* 使能通道0的自动应答 */
    nrf24l01_write_reg(NRF_WRITE_REG + EN_RXADDR, 0x01);    /* 使能通道0的接收地址 */
    nrf24l01_write_reg(NRF_WRITE_REG + SETUP_RETR, 0x1a);   /* 设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次 */
    nrf24l01_write_reg(NRF_WRITE_REG + RF_CH, 40);          /* 设置RF通道为40 */
    nrf24l01_write_reg(NRF_WRITE_REG + RF_SETUP, 0x0f);     /* 设置TX发射参数,0db增益,2Mbps */
    nrf24l01_write_reg(NRF_WRITE_REG + CONFIG, 0x0e);       /* 配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断 */
    NRF24L01_CE(1); /* CE为高,10us后启动发送 */
}



/// @brief NRF模块初始化
/// @param  无
void Nrf_Init(void)
{
    nrf24l01_init();
    nrf24l01.state = nrf24l01_check();

	if(nrf24l01.state == 0)     //NRF24L01模块如果连接正常,则将NRF初始化为主发送模式
	{
		nrf24l01_tx_mode();
		nrf24l01.en = 1;
	}
}


/// @brief NRF模块定时回调函数
/// @param  无
void Nrf_Timing_Callback(void)
{
	if(nrf24l01.tick <= NRF_TICK_TIME)
	{
		nrf24l01.tick++;
	}
}


/// @brief NRF模块任务
/// @param  无
void Nrf_Task(void)
{
	if(nrf24l01.tick >= NRF_TICK_TIME)
	{
		nrf24l01.param.tx_buff[0] = Joystick.THR >> 8;
		nrf24l01.param.tx_buff[1] = Joystick.THR;
		nrf24l01.param.tx_buff[2] = Joystick.YAW >> 8;
		nrf24l01.param.tx_buff[3] = Joystick.YAW;
		

		nrf24l01_tx_packet(nrf24l01.param.tx_buff);

		nrf24l01.tick = 0;
	}
}

5.2. 接收端

5.2.1. STM32CubeMX软件配置
5.2.2. 代码编写
cpp 复制代码
#ifndef __BSP_SPI_H
#define __BSP_SPI_H


#include "spi.h"


#define SPI2_GPIO_PORT			GPIOB
#define SPI2_GPIO_PIN_SCK		GPIO_PIN_13
#define SPI2_GPIO_PIN_MISO		GPIO_PIN_14
#define SPI2_GPIO_PIN_MOSI		GPIO_PIN_15

#define NRF_CE_GPIO_PORT		GPIOA           // SPI模式控制
#define NRF_CE_GPIO_PIN			GPIO_PIN_11
#define NRF_CE(x)               HAL_GPIO_WritePin(NRF_CE_GPIO_PORT, NRF_CE_GPIO_PIN, (GPIO_PinState)(x))

#define NRF_CSN_GPIO_PORT		GPIOB           // SPI片选
#define NRF_CSN_GPIO_PIN		GPIO_PIN_12 
#define NRF_CSN(x)              HAL_GPIO_WritePin(NRF_CSN_GPIO_PORT, NRF_CSN_GPIO_PIN, (GPIO_PinState)(x))

#define NRF_IRQ_GPIO_PORT		GPIOA           // 中断引脚
#define NRF_IRQ_GPIO_PIN		GPIO_PIN_12
#define NRF_IRQ()               HAL_GPIO_ReadPin(NRF_IRQ_GPIO_PORT, NRF_IRQ_GPIO_PIN)    


void Bsp_Spi_Init(void);                // bsp_Spi初始化
void Spi_Csn(uint8_t csn_state);        // SPI片选
uint8_t spi2_read_write_byte(uint8_t txdata);
void spi2_set_speed(uint8_t speed);


#endif
cpp 复制代码
#include "bsp_spi.h"



/// @brief BSP_SPI初始化
/// @param  无
void Bsp_Spi_Init(void) 
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 1. 启用时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();      // 启用GPIOA时钟
    __HAL_RCC_SPI1_CLK_ENABLE();       // 启用SPI1时钟
	__HAL_RCC_GPIOB_CLK_ENABLE();	   // 启用GPIOB时钟

    // 2. 配置CE和CSN和IRQ引脚(推挽输出)
    GPIO_InitStruct.Pin = NRF_CE_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;             /* 推挽输出 */
    GPIO_InitStruct.Pull = GPIO_PULLUP;                     /* 上拉 */
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;           /* 高速 */
    HAL_GPIO_Init(NRF_CE_GPIO_PORT, &GPIO_InitStruct); /* 初始化CE引脚 */

    GPIO_InitStruct.Pin = NRF_CSN_GPIO_PIN;
    HAL_GPIO_Init(NRF_CSN_GPIO_PORT, &GPIO_InitStruct);/* 初始化CSN引脚 */

    GPIO_InitStruct.Pin = NRF_IRQ_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;                 /* 输入 */
    GPIO_InitStruct.Pull = GPIO_PULLUP;                     /* 上拉 */
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;           /* 高速 */
    HAL_GPIO_Init(NRF_IRQ_GPIO_PORT, &GPIO_InitStruct);/* 初始化CE引脚 */

    // 初始状态:CE低,CSN高
    HAL_GPIO_WritePin(NRF_CE_GPIO_PORT, NRF_CE_GPIO_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(NRF_CSN_GPIO_PORT, NRF_CSN_GPIO_PIN, GPIO_PIN_SET);

    // 3. 配置SPI引脚(复用功能)
    __HAL_RCC_SPI2_CLK_ENABLE(); /* SPI2时钟使能 */

    hspi2.Instance = SPI2;                                /* SPI2 */
    hspi2.Init.Mode = SPI_MODE_MASTER;                        /* 设置SPI工作模式,设置为主模式 */
    hspi2.Init.Direction = SPI_DIRECTION_2LINES;              /* 设置SPI单向或者双向的数据模式:SPI设置为双线模式 */
    hspi2.Init.DataSize = SPI_DATASIZE_8BIT;                  /* 设置SPI的数据大小:SPI发送接收8位帧结构 */
    hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;               /* 串行同步时钟的空闲状态为高电平 */
    hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;                    /* 串行同步时钟的第二个跳变沿(上升或下降)数据被采样 */
    hspi2.Init.NSS = SPI_NSS_SOFT;                            /* NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制 */
    hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; /* 定义波特率预分频的值:波特率预分频值为256 */
    hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;                   /* 指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 */
    hspi2.Init.TIMode = SPI_TIMODE_DISABLE;                   /* 关闭TI模式 */
    hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;   /* 关闭硬件CRC校验 */
    hspi2.Init.CRCPolynomial = 7;                             /* CRC值计算的多项式 */
    HAL_SPI_Init(&hspi2);                                     /* 初始化 */

    __HAL_SPI_ENABLE(&hspi2); /* 使能SPI2 */

    spi2_read_write_byte(0Xff); /* 启动传输, 实际上就是产生8个时钟脉冲, 达到清空DR的作用, 非必需 */
}


/// @brief SPI片选引引脚高电平
/// @param  
void Spi_Csn(uint8_t csn_state)
{
    if(csn_state == 1)
        HAL_GPIO_WritePin(NRF_CSN_GPIO_PORT, NRF_CSN_GPIO_PIN, GPIO_PIN_SET);
    else if(csn_state == 0)
        HAL_GPIO_WritePin(NRF_CSN_GPIO_PORT, NRF_CSN_GPIO_PIN, GPIO_PIN_RESET);
}

/**
 * @brief       SPI2速度设置函数
 *   @note      SPI2时钟选择来自APB1, 即PCLK1, 为36Mhz
 *              SPI速度 = PCLK1 / 2^(speed + 1)
 * @param       speed   : SPI2时钟分频系数
                        取值为SPI_BAUDRATEPRESCALER_2~SPI_BAUDRATEPRESCALER_2 256
 * @retval      无
 */
void spi2_set_speed(uint8_t speed)
{
    assert_param(IS_SPI_BAUDRATE_PRESCALER(speed)); /* 判断有效性 */
    __HAL_SPI_DISABLE(&hspi2);             /* 关闭SPI */
    hspi2.Instance->CR1 &= 0XFFC7;         /* 位3-5清零,用来设置波特率 */
    hspi2.Instance->CR1 |= speed << 3;     /* 设置SPI速度 */
    __HAL_SPI_ENABLE(&hspi2);              /* 使能SPI */
}

/**
 * @brief       SPI2读写一个字节数据
 * @param       txdata  : 要发送的数据(1字节)
 * @retval      接收到的数据(1字节)
 */
uint8_t spi2_read_write_byte(uint8_t txdata)
{
    uint8_t rxdata;
    HAL_SPI_TransmitReceive(&hspi2, &txdata, &rxdata, 1, 100);
    return rxdata; /* 返回收到的数据 */
}
cpp 复制代码
#ifndef __NRF24L01_H
#define __NRF24L01_H


#include "bsp_spi.h"



/******************************************************************************************/
/* NRF24L01 操作引脚 定义(不包含SPI_SCK/MISO/MISO等三根线) */

#define NRF24L01_CE_GPIO_PORT              GPIOA
#define NRF24L01_CE_GPIO_PIN               GPIO_PIN_11
#define NRF24L01_CE_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define NRF24L01_CSN_GPIO_PORT             GPIOB
#define NRF24L01_CSN_GPIO_PIN              GPIO_PIN_12
#define NRF24L01_CSN_GPIO_CLK_ENABLE()     do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */

#define NRF24L01_IRQ_GPIO_PORT             GPIOA
#define NRF24L01_IRQ_GPIO_PIN              GPIO_PIN_12
#define NRF24L01_IRQ_GPIO_CLK_ENABLE()     do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PG口时钟使能 */

/******************************************************************************************/

/* 24L01操作线 */
#define NRF24L01_CE(x)    do{ x ? \
                              HAL_GPIO_WritePin(NRF24L01_CE_GPIO_PORT, NRF24L01_CE_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(NRF24L01_CE_GPIO_PORT, NRF24L01_CE_GPIO_PIN, GPIO_PIN_RESET); \
                          }while(0)       /* 24L01模式选择信号 */

#define NRF24L01_CSN(x)   do{ x ? \
                              HAL_GPIO_WritePin(NRF24L01_CSN_GPIO_PORT, NRF24L01_CSN_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(NRF24L01_CSN_GPIO_PORT, NRF24L01_CSN_GPIO_PIN, GPIO_PIN_RESET); \
                          }while(0)       /* 24L01片选信号 */

#define NRF24L01_IRQ      HAL_GPIO_ReadPin(NRF24L01_IRQ_GPIO_PORT, NRF24L01_IRQ_GPIO_PIN) /* IRQ主机数据输入 */


/* 24L01发送接收数据宽度定义 
 * 用户必须根据实际情况设置正确的数据宽度和数据长度
 * 发送端&接收端必须保持一致, 否则将导致通信失败!!!!
 */
#define TX_ADR_WIDTH    5       /* 5字节的地址宽度 */
#define RX_ADR_WIDTH    5       /* 5字节的地址宽度 */
#define TX_PLOAD_WIDTH  32      /* 32字节的用户数据宽度 */
#define RX_PLOAD_WIDTH  32      /* 32字节的用户数据宽度 */

/* SPI总线速度设置 */
#define SPI_SPEED_2         0
#define SPI_SPEED_4         1
#define SPI_SPEED_8         2
#define SPI_SPEED_16        3
#define SPI_SPEED_32        4
#define SPI_SPEED_64        5
#define SPI_SPEED_128       6
#define SPI_SPEED_256       7

/******************************************************************************************/
/* NRF24L01寄存器操作命令 */
#define NRF_READ_REG    0x00    /* 读配置寄存器,低5位为寄存器地址 */
#define NRF_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 NOP             0xFF    /* 空操作,可以用来读状态寄存器 */

/* SPI(NRF24L01)寄存器地址 */
#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 MAX_TX          0x10    /* 达到最大发送次数中断 */
#define TX_OK           0x20    /* TX发送完成中断 */
#define RX_OK           0x40    /* 接收到数据中断 */

#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 NRF_FIFO_STATUS 0x17    /* FIFO状态寄存器;bit0,RX FIFO寄存器空标志;bit1,RX FIFO满标志;bit2,3,保留 */
                                /* bit4,TX FIFO空标志;bit5,TX FIFO满标志;bit6,1,循环发送上一数据包.0,不循环; */
/******************************************************************************************/



#define NRF_TICK_TIME   10


typedef struct nrf24l01_param_t
{
    uint16_t thr;       // 油门
    uint16_t yaw;       // 偏航

    uint8_t tx_buff[33];    // 发送缓存
    uint8_t rx_buff[33];    // 接收缓存
} nrf24l01_param_t;

typedef struct nrf24l01_struct_t
{
    uint8_t state;      // 模块状态

    nrf24l01_param_t param;
    
    uint8_t en;         // 模块使能
    uint8_t tick;       // 模块运行时间
} nrf24l01_t;


extern nrf24l01_t nrf24l01;


/* 函数申明 */
static uint8_t nrf24l01_write_buf(uint8_t reg, uint8_t *pbuf, uint8_t uint8_ts);   /* 写数据区 */
static uint8_t nrf24l01_read_buf(uint8_t reg, uint8_t *pbuf, uint8_t uint8_ts);    /* 读数据区 */

void nrf24l01_spi_init(void);   /* 针对NRF24L01修改SPI2驱动 */
void nrf24l01_init(void);       /* 初始化 */
void nrf24l01_rx_mode(void);    /* 配置为接收模式 */
void nrf24l01_tx_mode(void);    /* 配置为发送模式 */
uint8_t nrf24l01_check(void);   /* 检查24L01是否存在 */
uint8_t nrf24l01_tx_packet(uint8_t *ptxbuf);     /* 发送一个包的数据 */
uint8_t nrf24l01_rx_packet(uint8_t *prxbuf);     /* 接收一个包的数据 */
void Nrf_Timing_Callback(void);             // NRF模块定时任务
void Nrf_Task(void);                        // NRF模块任务
void Nrf_Init(void);            // NRF模块初始化

uint16_t map_throttle_to_pwm_with_deadzone(uint16_t thr_val);

#endif
cpp 复制代码
#include "nrf24l01.h"
#include "led.h"
#include "motor.h"

#include <math.h>
#include <stdlib.h>


nrf24l01_t nrf24l01 = {0};

const uint8_t TX_ADDRESS[TX_ADR_WIDTH] = {0x34, 0x43, 0x10, 0x10, 0x01};    /* 发送地址 */
const uint8_t RX_ADDRESS[RX_ADR_WIDTH] = {0x34, 0x43, 0x10, 0x10, 0x01};    /* 接收地址 */

 
/**
 * @brief       针对NRF24L01修改SPI2驱动
 * @param       无
 * @retval      无
 */
void nrf24l01_spi_init(void)
{
    __HAL_SPI_DISABLE(&hspi2);                 /* 先关闭SPI2 */
    hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; /* 串行同步时钟的空闲状态为低电平 */
    hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;     /* 串行同步时钟的第1个跳变沿(上升或下降)数据被采样 */
    HAL_SPI_Init(&hspi2);
    __HAL_SPI_ENABLE(&hspi2);                  /* 使能SPI2 */
}

/**
 * @brief       初始化24L01的IO口
 *   @note      将SPI2模式改成SCK空闲低电平,及SPI 模式0
 * @param       无
 * @retval      无
 */
void nrf24l01_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;

    NRF24L01_CE_GPIO_CLK_ENABLE();  /* CE脚时钟使能 */
    NRF24L01_CSN_GPIO_CLK_ENABLE(); /* CSN脚时钟使能 */
    NRF24L01_IRQ_GPIO_CLK_ENABLE(); /* IRQ脚时钟使能 */

    gpio_init_struct.Pin = NRF24L01_CE_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;             /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                     /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;           /* 高速 */
    HAL_GPIO_Init(NRF24L01_CE_GPIO_PORT, &gpio_init_struct); /* 初始化CE引脚 */

    gpio_init_struct.Pin = NRF24L01_CSN_GPIO_PIN;
    HAL_GPIO_Init(NRF24L01_CSN_GPIO_PORT, &gpio_init_struct);/* 初始化CSN引脚 */

    gpio_init_struct.Pin = NRF24L01_IRQ_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_INPUT;                 /* 输入 */
    gpio_init_struct.Pull = GPIO_PULLUP;                     /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;           /* 高速 */
    HAL_GPIO_Init(NRF24L01_IRQ_GPIO_PORT, &gpio_init_struct);/* 初始化CE引脚 */

    Bsp_Spi_Init();                /* 初始化SPI2 */
    nrf24l01_spi_init();        /* 针对NRF的特点修改SPI的设置 */
    NRF24L01_CE(0);             /* 使能24L01 */
    NRF24L01_CSN(1);            /* SPI片选取消 */
}

/**
 * @brief       检测24L01是否存在
 * @param       无
 * @retval      0, 成功; 1, 失败;
 */
uint8_t nrf24l01_check(void)
{
    uint8_t buf[5] = {0XA5, 0XA5, 0XA5, 0XA5, 0XA5};
    uint8_t i;
    nrf24l01_write_buf(NRF_WRITE_REG + TX_ADDR, buf, 5);  /* 写入5个字节的地址. */
    nrf24l01_read_buf(TX_ADDR, buf, 5);                   /* 读出写入的地址 */

    for (i = 0; i < 5; i++)
    {
        if (buf[i] != 0XA5) break;
    }
    
    if (i != 5) return 1;   /* 检测24L01错误 */

    return 0;               /* 检测到24L01 */
}

/**
 * @brief       NRF24L01写寄存器
 * @param       reg   : 寄存器地址
 * @param       value : 写入寄存器的值
 * @retval      状态寄存器值
 */
static uint8_t nrf24l01_write_reg(uint8_t reg, uint8_t value)
{
    uint8_t status;
    NRF24L01_CSN(0);                    /* 使能SPI传输 */
    status = spi2_read_write_byte(reg); /* 发送寄存器号 */
    spi2_read_write_byte(value);        /* 写入寄存器的值 */
    NRF24L01_CSN(1);                    /* 禁止SPI传输 */
    return status;                      /* 返回状态值 */
}

/**
 * @brief       NRF24L01读寄存器
 * @param       reg   : 寄存器地址
 * @retval      读取到的寄存器值;
 */
static uint8_t nrf24l01_read_reg(uint8_t reg)
{
    uint8_t reg_val;
    NRF24L01_CSN(0);            /* 使能SPI传输 */
    spi2_read_write_byte(reg);  /* 发送寄存器号 */
    reg_val = spi2_read_write_byte(0XFF);   /* 读取寄存器内容 */
    NRF24L01_CSN(1);            /* 禁止SPI传输 */
    return reg_val;             /* 返回状态值 */
}

/**
 * @brief       在指定位置读出指定长度的数据
 * @param       reg   : 寄存器地址
 * @param       pbuf  : 数据指针
 * @param       len   : 数据长度
 * @retval      状态寄存器值
 */
static uint8_t nrf24l01_read_buf(uint8_t reg, uint8_t *pbuf, uint8_t len)
{
    uint8_t status, i;
    NRF24L01_CSN(0);    /* 使能SPI传输 */
    status = spi2_read_write_byte(reg);         /* 发送寄存器值(位置),并读取状态值 */

    for (i = 0; i < len; i++)
    {
        pbuf[i] = spi2_read_write_byte(0XFF);   /* 读出数据 */
    }
    
    NRF24L01_CSN(1);    /* 关闭SPI传输 */
    return status;      /* 返回读到的状态值 */
}

/**
 * @brief       在指定位置写指定长度的数据
 * @param       reg   : 寄存器地址
 * @param       pbuf  : 数据指针
 * @param       len   : 数据长度
 * @retval      状态寄存器值
 */
static uint8_t nrf24l01_write_buf(uint8_t reg, uint8_t *pbuf, uint8_t len)
{
    uint8_t status, i;
    NRF24L01_CSN(0);    /* 使能SPI传输 */
    status = spi2_read_write_byte(reg);/* 发送寄存器值(位置),并读取状态值 */

    for (i = 0; i < len; i++)
    {
        spi2_read_write_byte(*pbuf++); /* 写入数据 */
    }
    
    NRF24L01_CSN(1);    /* 关闭SPI传输 */
    return status;      /* 返回读到的状态值 */
}

/**
 * @brief       启动NRF24L01发送一次数据(数据长度 = TX_PLOAD_WIDTH)
 * @param       ptxbuf : 待发送数据首地址
 * @retval      发送完成状态
 *   @arg       0    : 发送成功
 *   @arg       1    : 达到最大发送次数,失败
 *   @arg       0XFF : 其他错误
 */
uint8_t nrf24l01_tx_packet(uint8_t *ptxbuf)
{
    uint8_t sta;
    uint8_t rval = 0XFF;
    
    NRF24L01_CE(0);
    nrf24l01_write_buf(WR_TX_PLOAD, ptxbuf, TX_PLOAD_WIDTH);    /* 写数据到TX BUF  TX_PLOAD_WIDTH个字节 */
    NRF24L01_CE(1); /* 启动发送 */

    while (NRF24L01_IRQ != 0);          /* 等待发送完成 */

    sta = nrf24l01_read_reg(STATUS);    /* 读取状态寄存器的值 */
    nrf24l01_write_reg(NRF_WRITE_REG + STATUS, sta);    /* 清除TX_DS或MAX_RT中断标志 */

    if (sta & MAX_TX)   /* 达到最大重发次数 */
    {
        nrf24l01_write_reg(FLUSH_TX, 0xff); /* 清除TX FIFO寄存器 */
        rval = 1;
    }

    if (sta & TX_OK)/* 发送完成 */
    {
        rval = 0;   /* 标记发送成功 */
    }

    return rval;    /* 返回结果 */
}

/**
 * @brief       启动NRF24L01接收一次数据(数据长度 = RX_PLOAD_WIDTH)
 * @param       prxbuf : 接收数据缓冲区首地址
 * @retval      接收完成状态
 *   @arg       0 : 接收成功
 *   @arg       1 : 失败
 */
uint8_t nrf24l01_rx_packet(uint8_t *prxbuf)
{
    uint8_t sta;
    uint8_t rval = 1;
    
    sta = nrf24l01_read_reg(STATUS); /* 读取状态寄存器的值 */
    nrf24l01_write_reg(NRF_WRITE_REG + STATUS, sta); /* 清除RX_OK中断标志 */

    if (sta & RX_OK)    /* 接收到数据 */
    {
        nrf24l01_read_buf(RD_RX_PLOAD, prxbuf, RX_PLOAD_WIDTH); /* 读取数据 */
        nrf24l01_write_reg(FLUSH_RX, 0xff); /* 清除RX FIFO寄存器 */
        rval = 0;       /* 标记接收完成 */
    }

    return rval;    /* 返回结果 */
}

/**
 * @brief       NRF24L01进入接收模式
 *   @note      设置RX地址,写RX数据宽度,选择RF频道,波特率
 *              当CE变高后,即进入RX模式,并可以接收数据了
 * @param       无
 * @retval      无
 */
void nrf24l01_rx_mode(void)
{
    NRF24L01_CE(0);
    nrf24l01_write_buf(NRF_WRITE_REG + RX_ADDR_P0, (uint8_t *)RX_ADDRESS, RX_ADR_WIDTH);    /* 写RX节点地址 */

    nrf24l01_write_reg(NRF_WRITE_REG + EN_AA, 0x01);        /* 使能通道0的自动应答 */
    nrf24l01_write_reg(NRF_WRITE_REG + EN_RXADDR, 0x01);    /* 使能通道0的接收地址 */
    nrf24l01_write_reg(NRF_WRITE_REG + RF_CH, 40);          /* 设置RF通信频率 */
    nrf24l01_write_reg(NRF_WRITE_REG + RX_PW_P0, RX_PLOAD_WIDTH);   /* 选择通道0的有效数据宽度 */
    nrf24l01_write_reg(NRF_WRITE_REG + RF_SETUP, 0x0f);     /* 设置TX发射参数,0db增益,2Mbps */
    nrf24l01_write_reg(NRF_WRITE_REG + CONFIG, 0x0f);       /* 配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式 */
    NRF24L01_CE(1); /* CE为高,进入接收模式 */
}

/**
 * @brief       NRF24L01进入发送模式
 *   @note      设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,选择RF频道,波特率和
 *              PWR_UP,CRC使能
 *              当CE变高后,即进入TX模式,并可以发送数据了, CE为高大于10us,则启动发送.
 * @param       无
 * @retval      无
 */
void nrf24l01_tx_mode(void)
{
    NRF24L01_CE(0);
    nrf24l01_write_buf(NRF_WRITE_REG + TX_ADDR, (uint8_t *)TX_ADDRESS, TX_ADR_WIDTH);       /* 写TX节点地址 */
    nrf24l01_write_buf(NRF_WRITE_REG + RX_ADDR_P0, (uint8_t *)RX_ADDRESS, RX_ADR_WIDTH);    /* 设置RX节点地址,主要为了使能ACK */

    nrf24l01_write_reg(NRF_WRITE_REG + EN_AA, 0x01);        /* 使能通道0的自动应答 */
    nrf24l01_write_reg(NRF_WRITE_REG + EN_RXADDR, 0x01);    /* 使能通道0的接收地址 */
    nrf24l01_write_reg(NRF_WRITE_REG + SETUP_RETR, 0x1a);   /* 设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次 */
    nrf24l01_write_reg(NRF_WRITE_REG + RF_CH, 40);          /* 设置RF通道为40 */
    nrf24l01_write_reg(NRF_WRITE_REG + RF_SETUP, 0x0f);     /* 设置TX发射参数,0db增益,2Mbps */
    nrf24l01_write_reg(NRF_WRITE_REG + CONFIG, 0x0e);       /* 配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断 */
    NRF24L01_CE(1); /* CE为高,10us后启动发送 */
}


/// @brief NRF模块初始化
/// @param  无
void Nrf_Init(void)
{
    nrf24l01_init();
    nrf24l01.state = nrf24l01_check();

	if(nrf24l01.state == 0)     //NRF24L01模块如果连接正常,则将NRF初始化为主发送模式
	{
		nrf24l01_rx_mode();
		nrf24l01.en = 1;
        LED2(0);
	}
    else
    {
        nrf24l01.en = 0;
        LED2(1);
    }
}


/// @brief NRF模块定时任务
/// @param  无
void Nrf_Timing_Callback(void)
{
    if(nrf24l01.tick <= NRF_TICK_TIME)
    {
        nrf24l01.tick++;
    }
}


// PWM反向函数(保持中位75不变)
uint16_t invert_motor_pwm(uint16_t pwm) {
    if(pwm == 75) return 75;  // 中位保持不变
    
    if(pwm > 75) { 
        // 正转区域(76-100) → 转换为反转区域(74-50)
        return 74 - (pwm - 76) * (74 - 50) / (100 - 76);
    } else { 
        // 反转区域(50-74) → 转换为正转区域(76-100)
        return 76 + (74 - pwm) * (100 - 76) / (74 - 50);
    }
}


/// @brief NRF模块任务
/// @param  无
void Nrf_Task(void)
{
    if(nrf24l01.tick >= NRF_TICK_TIME)
    {
        nrf24l01_rx_packet(nrf24l01.param.rx_buff);

        nrf24l01.param.thr = nrf24l01.param.rx_buff[0] << 8 | nrf24l01.param.rx_buff[1];
		nrf24l01.param.yaw = nrf24l01.param.rx_buff[2] << 8 | nrf24l01.param.rx_buff[3];
		
		
		// 转换为电调PWM值
		uint16_t pwm_value = map_throttle_to_pwm_with_deadzone(nrf24l01.param.thr);
		
		// 控制电调
		__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_value);
		__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, invert_motor_pwm(pwm_value));
		__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, invert_motor_pwm(pwm_value));
		__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, pwm_value);
		
		
        nrf24l01.tick = 0;
    }    
}




#define DEADZONE 0 // 死区范围±20

/**
 * @brief 将油门信号(1000-2000)转换为电调PWM信号(50-100)
 * @param thr_val: 油门值(1000-2000)
 * @return 电调PWM值(50-100)
 */
uint16_t map_throttle_to_pwm_with_deadzone(uint16_t thr_val) 
{
    // 死区处理 - 接近中位时强制停转
    if(abs(thr_val - 1500) <= DEADZONE) {
        return 75;
    }
    
    // 限制油门范围
    if(thr_val < 1000) thr_val = 1000;
    if(thr_val > 2000) thr_val = 2000;
    
    if(thr_val < 1500) {
        // 反转区域(1000-1480 -> 50-74)
        return 50 + ((thr_val - 1000) * (74 - 50) / (1480 - 1000));
    }
    else {
        // 正转区域(1520-2000 -> 76-100)
        return 76 + (((thr_val - 1520) * (100 - 76) / (2000 - 1520)) * 0.3);
    }
}
相关推荐
geovindu几秒前
java: Backtracking Algorithm
java·开发语言·windows·后端·算法·回溯算法
Scott9999HH8 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社9 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海10 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖10 小时前
JDK 26 新特性详解
java·开发语言
马优晨11 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区13 小时前
怎么把C语言学到精通?
c语言·开发语言
sramdram13 小时前
MCU微控制器OA打印机方案
单片机·嵌入式硬件·mcu·mcu微控制器
心平气和量大福大13 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai14 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang