1. 基础无线LED控制
任务说明
使用一对NRF24L01无线模块和两块STM32F103C8T6最小系统板,实现按键无线控制远端LED亮灭。发送端读取按键状态,通过NRF24L01以2.4GHz射频发送;接收端收到数据后驱动LED。通过本案例掌握NRF24L01的SPI初始化、收发寄存器配置及基本数据交互流程。
硬件连接
| STM32引脚 | NRF24L01引脚 |
|---|---|
| PA5 (SPI1_SCK) | SCK |
| PA6 (SPI1_MISO) | MISO |
| PA7 (SPI1_MOSI) | MOSI |
| PA4 (GPIO输出) | CSN |
| PA3 (GPIO输出) | CE |
| PA2 (GPIO输入) | IRQ(本案例不使用中断) |
发送端按键接PB0(内部上拉,按下为低电平);接收端LED正极经330Ω电阻接PB1,负极接地。两模块需共地(若分别供电)。
发送端:STM32_PA5→NRF_SCK,PA6→MISO,PA7→MOSI,PA4→CSN,PA3→CE,PB0→按键(另一端接GND)
接收端:STM32_PA5→NRF_SCK,PA6→MISO,PA7→MOSI,PA4→CSN,PA3→CE,PB1→LED(另一端经电阻接GND)
代码实现
以下代码基于STM32标准外设库,发送端与接收端共用同一SPI驱动和NRF24L01寄存器操作函数,仅主函数逻辑不同。两工程分别编译后烧录到对应板卡。
// ============ nrf24l01.h ============
#ifndef __NRF24L01_H
#define __NRF24L01_H
#include "stm32f10x.h"
#define TX_ADDR_WIDTH 5
#define RX_ADDR_WIDTH 5
// 寄存器地址
#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 FIFO_STATUS 0x17
// 指令
#define R_REGISTER 0x00
#define W_REGISTER 0x20
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
// 引脚定义
#define NRF_CSN_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4)
#define NRF_CSN_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4)
#define NRF_CE_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_3)
#define NRF_CE_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_3)
void NRF_GPIO_Init(void);
void NRF_SPI_Init(void);
uint8_t NRF_SPI_RW(uint8_t byte);
uint8_t NRF_ReadReg(uint8_t reg);
void NRF_WriteReg(uint8_t reg, uint8_t value);
void NRF_WriteBuf(uint8_t reg, uint8_t *buf, uint8_t len);
void NRF_ReadBuf(uint8_t reg, uint8_t *buf, uint8_t len);
void NRF_TX_Mode(uint8_t *tx_addr);
void NRF_RX_Mode(uint8_t *rx_addr);
uint8_t NRF_SendPacket(uint8_t *data, uint8_t len);
uint8_t NRF_ReceivePacket(uint8_t *data, uint8_t *len);
void NRF_Init(void);
#endif
// ============ nrf24l01.c ============
#include "nrf24l01.h"
#include <string.h>
void NRF_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// CSN(PA4), CE(PA3) 推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
NRF_CE_LOW();
NRF_CSN_HIGH();
}
void NRF_SPI_Init(void)
{
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
// SCK=PA5, MOSI=PA7 推挽复用
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// MISO=PA6 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
uint8_t NRF_SPI_RW(uint8_t byte)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, byte);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
uint8_t NRF_ReadReg(uint8_t reg)
{
uint8_t val;
NRF_CSN_LOW();
NRF_SPI_RW(R_REGISTER | reg);
val = NRF_SPI_RW(0x00);
NRF_CSN_HIGH();
return val;
}
void NRF_WriteReg(uint8_t reg, uint8_t value)
{
NRF_CSN_LOW();
NRF_SPI_RW(W_REGISTER | reg);
NRF_SPI_RW(value);
NRF_CSN_HIGH();
}
void NRF_WriteBuf(uint8_t reg, uint8_t *buf, uint8_t len)
{
uint8_t i;
NRF_CSN_LOW();
NRF_SPI_RW(W_REGISTER | reg);
for (i = 0; i < len; i++)
NRF_SPI_RW(buf[i]);
NRF_CSN_HIGH();
}
void NRF_ReadBuf(uint8_t reg, uint8_t *buf, uint8_t len)
{
uint8_t i;
NRF_CSN_LOW();
NRF_SPI_RW(R_REGISTER | reg);
for (i = 0; i < len; i++)
buf[i] = NRF_SPI_RW(0x00);
NRF_CSN_HIGH();
}
void NRF_Init(void)
{
NRF_CE_LOW();
NRF_WriteReg(CONFIG, 0x0E); // 使能CRC, 16位CRC, 上电
NRF_WriteReg(EN_AA, 0x01); // 使能自动应答通道0
NRF_WriteReg(EN_RXADDR, 0x01); // 使能接收通道0
NRF_WriteReg(SETUP_AW, 0x03); // 5字节地址
NRF_WriteReg(SETUP_RETR, 0x1A); // 自动重发500us, 10次
NRF_WriteReg(RF_CH, 0x40); // 信道 2.440GHz
NRF_WriteReg(RF_SETUP, 0x07); // 1Mbps, 0dBm
NRF_WriteReg(RX_PW_P0, 0x01); // 通道0接收1字节
NRF_CE_HIGH();
}
void NRF_TX_Mode(uint8_t *tx_addr)
{
NRF_CE_LOW();
NRF_WriteReg(CONFIG, 0x0E); // 上电, 发送模式(PRIM_RX=0)
NRF_WriteBuf(TX_ADDR, tx_addr, TX_ADDR_WIDTH);
NRF_WriteBuf(RX_ADDR_P0, tx_addr, RX_ADDR_WIDTH); // 自动应答需要
NRF_CE_HIGH();
}
void NRF_RX_Mode(uint8_t *rx_addr)
{
NRF_CE_LOW();
NRF_WriteBuf(RX_ADDR_P0, rx_addr, RX_ADDR_WIDTH);
NRF_WriteReg(CONFIG, 0x0F); // 上电, 接收模式(PRIM_RX=1)
NRF_CE_HIGH();
}
uint8_t NRF_SendPacket(uint8_t *data, uint8_t len)
{
uint8_t status;
NRF_CE_LOW();
NRF_CSN_LOW();
NRF_SPI_RW(FLUSH_TX);
NRF_CSN_HIGH();
NRF_CSN_LOW();
NRF_SPI_RW(W_TX_PAYLOAD);
for (uint8_t i = 0; i < len; i++)
NRF_SPI_RW(data[i]);
NRF_CSN_HIGH();
NRF_CE_HIGH(); // 启动发送
// 等待发送完成或超时(约10ms)
uint32_t timeout = 10000;
while (timeout--) {
status = NRF_ReadReg(STATUS);
if (status & 0x20) { // TX_DS 发送成功
NRF_WriteReg(STATUS, 0x20); // 清除标志
return 1;
}
if (status & 0x10) { // MAX_RT 重发超限
NRF_WriteReg(STATUS, 0x10);
NRF_CSN_LOW();
NRF_SPI_RW(FLUSH_TX);
NRF_CSN_HIGH();
return 0;
}
}
return 0;
}
uint8_t NRF_ReceivePacket(uint8_t *data, uint8_t *len)
{
uint8_t status = NRF_ReadReg(STATUS);
if (status & 0x40) { // RX_DR 收到数据
NRF_CSN_LOW();
NRF_SPI_RW(R_RX_PAYLOAD);
*len = NRF_SPI_RW(0x00);
data[0] = NRF_SPI_RW(0x00);
NRF_CSN_HIGH();
NRF_WriteReg(STATUS, 0x40); // 清除RX_DR
return 1;
}
return 0;
}
// ============ main.c (发送端) ============
#include "stm32f10x.h"
#include "nrf24l01.h"
uint8_t tx_addr[5] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7};
void Delay_ms(uint32_t ms)
{
for (uint32_t i = 0; i < ms * 4000; i++)
__NOP();
}
int main(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// PB0 按键输入,内部上拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure);
NRF_GPIO_Init();
NRF_SPI_Init();
NRF_Init();
NRF_TX_Mode(tx_addr);
uint8_t key_state = 1; // 上一次按键状态(1=未按下)
uint8_t led_cmd = 0;
while (1) {
uint8_t current = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0);
if (key_state == 1 && current == 0) {
// 检测到按下沿
led_cmd = !led_cmd; // 翻转命令
NRF_SendPacket(&led_cmd, 1);
Delay_ms(50); // 消抖
}
key_state = current;
Delay_ms(5);
}
}
// ============ main.c (接收端) ============
#include "stm32f10x.h"
#include "nrf24l01.h"
uint8_t rx_addr[5] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7};
void Delay_ms(uint32_t ms)
{
for (uint32_t i = 0; i < ms * 4000; i++)
__NOP();
}
int main(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// PB1 LED输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
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_1); // 初始熄灭
NRF_GPIO_Init();
NRF_SPI_Init();
NRF_Init();
NRF_RX_Mode(rx_addr);
uint8_t rx_data = 0;
uint8_t rx_len = 0;
while (1) {
if (NRF_ReceivePacket(&rx_data, &rx_len)) {
if (rx_len == 1) {
if (rx_data == 0)
GPIO_ResetBits(GPIOB, GPIO_Pin_1); // 熄灭
else
GPIO_SetBits(GPIOB, GPIO_Pin_1); // 点亮
}
}
Delay_ms(1);
}
}
编译与烧录
使用Keil MDK分别创建两个工程(发送端和接收端),均添加nrf24l01.c和对应main.c。芯片选择STM32F103C8,勾选USE_STDPERIPH_DRIVER宏。编译无误后,用ST-Link将发送端程序烧录到按键侧板卡,接收端程序烧录到LED侧板卡。
验证方法
两板均以3.3V供电(注意NRF24L01不能接5V)。将两模块天线端朝向同一方向,距离1~5米内。按下发送端按键,观察接收端LED是否翻转状态。每按一次,LED应在亮/灭之间切换一次。
判定标准:连续按10次按键,LED状态跟随翻转10次且无丢失,说明收发链路正常。若LED无反应,依次检查:①两模块信道和地址是否一致(本代码已固定为0x40信道和0xE7地址);②SPI接线是否与代码一致;③接收端PB1是否输出正确电平(可用万用表测量);④供电是否稳定在3.3V。
预期输出
发送端每检测到一次按键按下沿,通过NRF24L01发送1字节数据(0x00或0x01交替)。接收端收到后,PB1引脚输出对应电平,LED亮灭状态翻转。由于代码中发送端仅在按键按下沿发送一次,长按不会重复触发。