4. 无线PWM调光

任务定义

本案例实现一个无线PWM调光系统:发送端通过按键调整PWM占空比(0%~100%,步进10%),将占空比数值以单字节形式通过NRF24L01无线发送;接收端解析数据后,利用定时器硬件PWM输出驱动LED,实现亮度调节。系统上电默认占空比为50%,每按一次按键占空比增加10%,超过100%后回绕至0%。

硬件连接

发送端 STC8H P3.2 ← 按键(按下接地) P1.0 → NRF24L01 CE P1.1 → CSN P1.2 → SCK, P1.3 → MOSI NRF24L01 (TX) SPI 从机 NRF24L01 (RX) SPI 从机 接收端 STC8H P1.0 ← NRF24L01 CE P1.1 ← CSN P1.2 ← SCK, P1.3 ← MISO PWM输出: P2.0 → LED(串330Ω) 2.4GHz 无线 数据: 占空比(0~100) K LED GND 发送端按键调节占空比 → 无线发送 → 接收端PWM输出驱动LED

代码实现

以下代码基于STC8H系列单片机(24MHz主频),NRF24L01驱动采用SPI接口。发送端与接收端共用同一份NRF24L01底层驱动,通过宏定义区分收发角色。PWM使用定时器0产生20kHz频率,占空比由捕获比较寄存器控制。

// ============ 公共头文件 nrf24l01.h ============
#ifndef NRF24L01_H
#define NRF24L01_H
#include <STC8H.H>

// SPI引脚定义
#define CE  P1_0
#define CSN P1_1
#define SCK P1_2
#define MOSI P1_3
#define MISO P1_4

// NRF24L01寄存器命令
#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 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 TX_ADDR  0x10
#define RX_ADDR_P0 0x0A

// 状态位
#define TX_DS   (1<<5)
#define RX_DR   (1<<6)

void SPI_Init(void);
void NRF24_Init(void);
void NRF24_WriteReg(unsigned char reg, unsigned char val);
unsigned char NRF24_ReadReg(unsigned char reg);
void NRF24_SendByte(unsigned char data);
unsigned char NRF24_ReceiveByte(void);
void NRF24_TX_Mode(void);
void NRF24_RX_Mode(void);
unsigned char NRF24_Send(unsigned char data);
unsigned char NRF24_Receive(unsigned char *data);
#endif
// ============ NRF24L01底层驱动 nrf24l01.c ============
#include "nrf24l01.h"

void SPI_Init(void) {
    P1M1 = 0x10;  // MISO设为输入
    P1M0 = 0x0F;  // CE,CSN,SCK,MOSI设为推挽输出
    SPI_InitTypeDef spi = {0};
    spi.SPI_Mode = SPI_Mode_Master;
    spi.SPI_Speed = SPI_Speed_4;  // 6MHz
    spi.SPI_CPOL = SPI_CPOL_Low;
    spi.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_Init(&spi);
    SPI_Cmd(ENABLE);
}

void NRF24_WriteReg(unsigned char reg, unsigned char val) {
    CSN = 0;
    SPI_WriteByte(W_REGISTER | reg);
    SPI_WriteByte(val);
    CSN = 1;
}

unsigned char NRF24_ReadReg(unsigned char reg) {
    unsigned char val;
    CSN = 0;
    SPI_WriteByte(R_REGISTER | reg);
    val = SPI_ReadByte();
    CSN = 1;
    return val;
}

void NRF24_TX_Mode(void) {
    CE = 0;
    NRF24_WriteReg(CONFIG, 0x0E);  // 上电, TX模式
    NRF24_WriteReg(EN_AA, 0x01);   // 使能自动ACK
    NRF24_WriteReg(EN_RXADDR, 0x01);
    NRF24_WriteReg(SETUP_AW, 0x03); // 5字节地址
    NRF24_WriteReg(SETUP_RETR, 0x1A); // 自动重发
    NRF24_WriteReg(RF_CH, 40);
    NRF24_WriteReg(RF_SETUP, 0x26); // 2Mbps, 0dBm
    // 设置TX地址和RX地址(用于ACK)
    unsigned char addr[5] = {0xE7,0xE7,0xE7,0xE7,0xE7};
    CSN = 0;
    SPI_WriteByte(W_REGISTER | TX_ADDR);
    for(int i=0;i<5;i++) SPI_WriteByte(addr[i]);
    CSN = 1;
    CSN = 0;
    SPI_WriteByte(W_REGISTER | RX_ADDR_P0);
    for(int i=0;i<5;i++) SPI_WriteByte(addr[i]);
    CSN = 1;
    CE = 1;
}

void NRF24_RX_Mode(void) {
    CE = 0;
    NRF24_WriteReg(CONFIG, 0x0F);  // 上电, RX模式
    NRF24_WriteReg(EN_AA, 0x01);
    NRF24_WriteReg(EN_RXADDR, 0x01);
    NRF24_WriteReg(SETUP_AW, 0x03);
    NRF24_WriteReg(RF_CH, 40);
    NRF24_WriteReg(RF_SETUP, 0x26);
    unsigned char addr[5] = {0xE7,0xE7,0xE7,0xE7,0xE7};
    CSN = 0;
    SPI_WriteByte(W_REGISTER | RX_ADDR_P0);
    for(int i=0;i<5;i++) SPI_WriteByte(addr[i]);
    CSN = 1;
    CE = 1;
}

unsigned char NRF24_Send(unsigned char data) {
    CE = 0;
    CSN = 0;
    SPI_WriteByte(FLUSH_TX);
    CSN = 1;
    CSN = 0;
    SPI_WriteByte(W_TX_PAYLOAD);
    SPI_WriteByte(data);
    CSN = 1;
    CE = 1;  // 启动发送
    // 等待发送完成或超时(10ms)
    unsigned int timeout = 10000;
    while(timeout--) {
        unsigned char status = NRF24_ReadReg(STATUS);
        if(status & TX_DS) {
            NRF24_WriteReg(STATUS, TX_DS);  // 清除中断
            return 1;  // 发送成功
        }
    }
    return 0;  // 超时失败
}

unsigned char NRF24_Receive(unsigned char *data) {
    unsigned char status = NRF24_ReadReg(STATUS);
    if(status & RX_DR) {
        CSN = 0;
        SPI_WriteByte(R_RX_PAYLOAD);
        *data = SPI_ReadByte();
        CSN = 1;
        NRF24_WriteReg(STATUS, RX_DR);
        return 1;
    }
    return 0;
}
// ============ 发送端 main_tx.c ============
#include "nrf24l01.h"

// 按键引脚 P3.2
sbit KEY = P3^2;

// PWM输出(调试用, 可接LED观察本地状态)
sbit PWM_OUT = P2^0;

void Timer0_Init(void) {
    // 20kHz PWM, 定时器0模式0(16位)
    AUXR |= 0x80;  // 1T模式
    TMOD &= 0xF0;
    TMOD |= 0x01;
    TH0 = 0xFF;
    TL0 = 0x06;  // 20kHz重载值
    ET0 = 1;
    TR0 = 1;
}

unsigned char duty = 50;  // 默认50%
unsigned int pwm_count = 0;

void Timer0_ISR(void) interrupt 1 {
    TH0 = 0xFF;
    TL0 = 0x06;
    pwm_count++;
    if(pwm_count >= 100) pwm_count = 0;
    PWM_OUT = (pwm_count < duty) ? 1 : 0;
}

void main(void) {
    SPI_Init();
    NRF24_TX_Mode();
    Timer0_Init();
    EA = 1;
    
    while(1) {
        if(KEY == 0) {  // 按键按下
            unsigned int delay = 5000;
            while(delay--);  // 消抖
            if(KEY == 0) {
                duty += 10;
                if(duty > 100) duty = 0;
                // 等待按键释放
                while(KEY == 0);
                // 发送占空比
                NRF24_Send(duty);
            }
        }
    }
}
// ============ 接收端 main_rx.c ============
#include "nrf24l01.h"

// PWM输出引脚 P2.0
sbit PWM_OUT = P2^0;

void Timer0_Init(void) {
    AUXR |= 0x80;
    TMOD &= 0xF0;
    TMOD |= 0x01;
    TH0 = 0xFF;
    TL0 = 0x06;  // 20kHz
    ET0 = 1;
    TR0 = 1;
}

unsigned char duty = 50;  // 默认50%
unsigned int pwm_count = 0;

void Timer0_ISR(void) interrupt 1 {
    TH0 = 0xFF;
    TL0 = 0x06;
    pwm_count++;
    if(pwm_count >= 100) pwm_count = 0;
    PWM_OUT = (pwm_count < duty) ? 1 : 0;
}

void main(void) {
    SPI_Init();
    NRF24_RX_Mode();
    Timer0_Init();
    EA = 1;
    
    unsigned char rx_data;
    while(1) {
        if(NRF24_Receive(&rx_data)) {
            if(rx_data <= 100) {  // 合法性检查
                duty = rx_data;   // 更新占空比
            }
        }
    }
}

编译与烧录

使用Keil C251编译器,创建两个工程分别编译发送端和接收端代码。发送端工程包含main_tx.c和nrf24l01.c,接收端工程包含main_rx.c和nrf24l01.c。编译生成HEX文件后,通过STC-ISP软件分别烧录到两块STC8H开发板。

验证方法

按以下步骤验证系统功能:

  1. 发送端和接收端各接一个LED(发送端LED接P2.0用于本地观察,接收端LED接P2.0作为调光目标);
  2. 两块板均上电,观察接收端LED应保持50%亮度(默认占空比);
  3. 按下发送端按键一次,发送端本地LED亮度应变为60%亮度(占空比增加10%),同时接收端LED亮度同步变为60%;
  4. 连续按按键10次,占空比从50%→60%→…→100%→0%→10%,观察两端LED亮度同步变化;
  5. 用示波器测量接收端P2.0引脚波形,确认频率约为20kHz,占空比与按键次数对应。

判定标准:每次按键后,接收端LED亮度与发送端本地LED亮度一致,且示波器测得的占空比数值与预期值(50%、60%、…、100%、0%、10%…)完全吻合,即判定为通过。