前言:
SPI(Serial Peripheral Interface)是一种高速、全双工、同步的串行通信协议,由摩托罗拉公司提出,广泛应用于微控制器与Flash存储器、传感器、ADC/DAC等外设之间的短距离通信
整体核心信息如下:
| 特性 | 描述 |
|---|---|
| 通信模式 | 同步、全双工(可配置为半双工或单工) |
| 拓扑结构 | 主从模式(单主多从) |
| 信号线 | SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、SS/CS(片选) |
| 关键配置 | 时钟极性(CPOL)和时钟相位(CPHA),组合成4种工作模式 |
| 数据帧 | 无固定格式,长度通常为8位或16位,支持MSB或LSB先行 |
| 起始/停止信号 | 通过片选信号(SS/CS)的拉低和拉高来界定 |
| 主要优点 | 高速、协议简单、硬件连接简单 |
| 主要缺点 | 无流控制、无应答机制、占用I/O口较多(多从机时) |
SPI协议基础:
物理接口:
其物理连接如下:

SPI的物理连接基于以下四根基础信号线:
-
SCLK(Serial Clock):由主设备产生的时钟信号,用于同步数据传输。所有从设备都接收同一个SCLK信号。
-
MOSI(Master Output Slave Input):主设备数据输出、从设备数据输入的通道。
-
MISO(Master Input Slave Output):从设备数据输出、主设备数据输入的通道。
-
SS/CS(Slave Select / Chip Select):从设备片选信号,由主设备控制,低电平有效。每个从设备都需要一个独立的片选线。
连接方式:
主机和从机存在两种连接方式,独立片选和菊花链模式,因为如果没改设置独立一个片选线会占用大量的I/O口
上图的连接方式就是独立片选模式:每个从设备使用独立的片选线。优点是通信简单直接,速率高;缺点是当从设备数量多时,会占用主设备大量I/O口
另外一种菊花链模式:所有从设备共享一条片选线,数据从一个设备传到下一个设备。优点是可以节省I/O口;缺点是通信效率较低,且需要从设备支持这种模式。

通信时序:
当主设备需要与某个从设备通信时,会先将该从设备的SS线拉至低电平,表示选中它,然后产生SCLK时钟。数据在时钟的同步下,通过MOSI线由主设备发送给从设备,同时通过MISO线由从设备返回给主设备,实现全双工通信

工作模式:
SPI协议的精髓在于其时钟的灵活性,由时钟极性(CPOL) 和时钟相位(CPHA) 两个参数定义了四种工作模式。
-
CPOL(Clock Polarity):决定SCLK时钟线在空闲状态(无数据传输时)的电平。
-
CPOL=0:SCLK空闲时为低电平 -
CPOL=1:SCLK空闲时为高电平
-

-
CPHA(Clock Phase):决定数据在时钟的哪个边沿被采样(捕获)。
-
CPHA=0:数据在时钟的第一个边沿(即SCLK从空闲状态跳变到有效状态后的第一个边沿)被采样。 -
CPHA=1:数据在时钟的第二个边沿被采样。
-

四种模式的具体时序特性如下表所示:
| 模式 | CPOL | CPHA | SCLK空闲状态 | 数据采样边沿 | 数据切换边沿 |
|---|---|---|---|---|---|
| 模式0 | 0 | 0 | 低电平 | 奇数边沿(上升沿) | 下降沿 |
| 模式1 | 0 | 1 | 低电平 | 偶数边沿(下降沿) | 上升沿 |
| 模式2 | 1 | 0 | 高电平 | 奇数边沿(下降沿) | 上升沿 |
| 模式3 | 1 | 1 | 高电平 | 偶数边沿(上升沿) | 下降沿 |
主设备与从设备必须工作在相同的模式下才能正常通信。在实际应用中,模式0和模式3最为常见。
通信完整流程:
一次典型的SPI通信包含以下步骤:
-
起始通信 :主设备将目标从设备的SS信号拉低。这是通信的起始信号。
-
生成时钟 :主设备开始产生SCLK时钟信号。
-
数据传输:
-
在SCLK的每个时钟周期内,主设备通过MOSI线发送1位数据,同时通过MISO线接收1位数据。
-
数据通常高位(MSB)在前,低位(LSB)在后,但顺序可配置。
-
数据的发送和采样时刻由所选的CPHA决定。例如,在模式0(CPHA=0)下,数据在SCLK的上升沿被采样,在下降沿进行切换(准备下一位数据)。
-
-
结束通信 :数据传输完毕后,主设备将SS信号拉高。这是通信的结束信号。
由于SPI协议本身没有定义数据帧的格式,因此数据包的长度和含义通常由具体的外设数据手册规定,常见的是一次传输8位或16位数据。
SI24R1例程:
官方芯片说明:
SI24R1官方手册中各引脚含义:

SI24R1和STM32F10C8T6引脚连接关系:
| STM32F103C8T6 引脚 | SI24R1 模块引脚 | 功能描述 |
|---|---|---|
PA5(SPI1_SCK) |
SCK |
SPI时钟线,用于同步数据传输。 |
PA6(SPI1_MISO) |
MISO |
主设备输入,从设备输出,STM32通过此线从SI24R1读取数据。 |
PA7(SPI1_MOSI) |
MOSI |
主设备输出,从设备输入,STM32通过此线向SI24R1发送数据。 |
PA4(自定义GPIO) |
CSN(或CS) |
片选信号,低电平有效。此引脚为低时,SI24R1才会响应SPI通信 。 |
PA8(自定义GPIO) |
CE |
芯片使能,用于控制SI24R1的工作模式切换(如待机、发送、接收)。 |
PB2(自定义GPIO) |
IRQ |
中断请求,当SI24R1发送完成、接收数据或达到最大重发次数时,会通过此引脚触发STM32中断 |
对应的典型电路如下

工作模式如下:

SI24R1芯片内部有一个状态机,管理着五种主要的工作模式,上图的流程正展示了它们之间的转换关系。
-
关断模式:这是最省电的模式,功耗低于0.7μA,所有射频功能关闭,但寄存器值和FIFO数据保持不变。通过设置CONFIG寄存器的PWR_UP位为0进入此模式。从上图可见,这是整个状态机的起点和终点。
-
待机模式:当PWR_UP位设置为1后,芯片在时钟稳定后(约1.5-2ms)进入待机模式。此模式下仅晶体振荡器工作,功耗小于15μA,可以快速切换到其他工作模式。如图所示,待机模式是切换到发送或接收活动的中心枢纽。
-
发射空闲模式与发送模式:当芯片处于待机模式且CE引脚置为高电平、CONFIG寄存器的PRIM_RX位设置为0时,进入发射空闲模式。一旦TX FIFO中有数据,芯片便会自动切换到发送模式,将数据打包发送。发送完成后,会根据CE引脚电平及TX FIFO状态决定返回发射空闲模式还是待机模式。这个从Idle-TX到TX的转换路径在上图中清晰可见。
-
接收模式:当芯片处于待机模式且CE引脚置为高电平、PRIM_RX位设置为1时,进入接收模式。芯片会持续侦听空中信号,一旦接收到地址匹配且CRC校验正确的数据包,就会将数据存入RX FIFO,并通过IRQ引脚产生中断通知MCU。这个从Standby到RX的路径同样可以在图中找到。
数据包通讯与ARQ协议:
SI24R1基于数据包进行通信,并内置了自动重传请求机制,使得通信非常可靠

ARQ协议是一个框架,它包含几种不同的工作模式,而ACK机制是这些模式得以运转的"血液"
-
停等式ARQ (Stop-and-Wait ARQ)
这是最简单的一种模式。发送方每发送一个数据包后,就停止发送并等待接收方的ACK确认。收到ACK后,再发送下一个包。如果超时未收到ACK,则重发原数据包。这种方式非常可靠,但效率较低,因为大部分时间都在等待。
-
回退N帧ARQ (Go-Back-N ARQ)
为了提升效率,这种模式允许发送方在未收到确认时连续发送多个数据包 (一个窗口内的包)。如果发送方收到对第N个包的ACK,则表明第N个包及之前的所有包都已被正确接收。如果某个包出错或丢失,发送方需要回退并从该包开始重传所有后续的包。效率比停等式高,但重传时可能会浪费一些带宽。
-
选择性重传ARQ (Selective Repeat ARQ)
这是最高效但实现也最复杂的模式。当一系列数据包中某个包丢失时,接收方可以缓存后续正确接收但失序的包。发送方只重传真正丢失或出错的那个特定数据包,接收方最后将所有包按顺序重组。这最大限度地节省了带宽,但要求接收方有更大的缓存空间和更复杂的管理逻辑。
| 特性 | ARQ协议 (Automatic Repeat reQuest) | ACK模式 (Acknowledgement) |
|---|---|---|
| 角色定位 | 可靠传输的总体框架 | 框架内的关键执行机制 |
| 核心目标 | 在不可靠的信道上实现无差错的数据传输 | 为发送方提供明确的接收状态反馈 |
| 工作机制 | 通过超时重传、序号机制、滑动窗口等综合手段确保数据正确送达 | 接收方向发送方发送确认信号(ACK/NACK) |
| 关系比喻 | 交通控制系统(规定红灯停、绿灯行的规则和事故处理流程) | 交通信号灯(直接给出红灯或绿灯的指令) |
ACK自动应答流程如下:
-
发送端使用
W_TX_PAYLOAD命令将数据写入TX FIFO。 -
数据包被发送出去,其包控制字中的NO_ACK标志位为0,表示需要接收端应答。
-
接收端成功接收数据后,会自动回复一个ACK信号。
-
发送端收到ACK,则产生TX_DS中断,表示发送成功,并自动清除TX FIFO中的数据。
-
若发送端在设定的重发延迟时间内未收到ACK,会自动重发数据。超过最大重发次数后,会产生MAX_RT中断。
为了实现ACK通信,发送端除了配置接收端的地址外,还必须将自身的接收通道0的地址设置为与接收端地址相同,以便能够接收到ACK信号。
要使SI24R1正常工作,需要进行一系列寄存器配置。以下是发送模式和接收模式的关键配置步骤摘要:
| 配置项 | 发送方 | 接收方 |
|---|---|---|
| 地址宽度 | 设置SETUP_AW寄存器(通常为3-5字节) | 同左 |
| 收发地址 | 设置TX_ADDR为接收方地址; 同时设置RX_ADDR_P0为相同地址 | 设置RX_ADDR_P0为发送方地址 |
| 自动应答 | 使能EN_AA寄存器的相应通道 | 同左 |
| 接收通道 | 使能EN_RXADDR寄存器的通道0 | 使能目标接收通道 |
| 射频频道 | 设置RF_CH寄存器,双方需一致 | 同左 |
| 数据速率与功率 | 设置RF_SETUP寄存器(如0x0F对应2Mbps, 0dBm) | 同左 |
| 自动重发 | 设置SETUP_RETR寄存器(重发延迟和次数) | 不适用 |
| 负载宽度 | 设置RX_PW_P0寄存器(接收ACK的负载长度) | 设置接收通道的负载宽度 |
| 工作模式 | 设置CONFIG寄存器:PWR_UP=1, PRIM_RX=0 | 设置CONFIG寄存器:PWR_UP=1, PRIM_RX=1 |
SI24R1 在通信过程中最核心的寄存器及其作用:
SI24R1 核心寄存器功能速查表
| 寄存器名称 | 地址 (Hex) | 主要功能与配置要点 |
|---|---|---|
| CONFIG (配置寄存器) | 0x00 | 核心模式控制 : • PRIM_RX位 :1=接收模式(RX),0=发送模式(TX)。 • PWR_UP位 :1=上电,0=关断模式(功耗最低)。 • CRC0位 :设置CRC校验长度(0=8位,1=16位)。 • CRCO位:使能CRC校验。 |
| EN_AA (使能自动应答) | 0x01 | 控制自动应答(ACK) : • 使能特定数据管道(Pipe 0-5)的自动应答功能。这是实现可靠通信的基础。 |
| EN_RXADDR (使能接收地址) | 0x02 | 启用接收管道 : • 使能6个接收数据管道(Pipe 0-5)中的哪一个或哪几个。 |
| SETUP_AW (地址宽度设置) | 0x03 | 设置地址长度 : • 配置收发双方地址的宽度,通常为3、4或5个字节。双方必须一致。 |
| SETUP_RETR (自动重发设置) | 0x04 | 可靠性核心 : • 高4位:设置自动重发延迟时间(250-4000μs)。 • 低4位:设置最大自动重发次数(1-15次)。 |
| RF_CH (射频频道) | 0x05 | 设置工作频率 : • 设置射频信道(0-125),对应频率为 2400 + RF_CH MHz。通信双方必须在同一频道。 |
| RF_SETUP (射频设置) | 0x06 | 设置射频参数 : • 设置数据传输速率(250Kbps, 1Mbps, 2Mbps)。 • 设置发射功率等级(-12dBm 到 +7dBm)。 |
| STATUS (状态寄存器) | 0x07 | 读取通信状态 : • RX_DR位 :数据接收就绪中断。 • TX_DS位 :数据发送成功中断。 • MAX_RT位 :达到最大重发次数中断。 • 写入该寄存器可清除中断标志。 |
| TX_ADDR (发送地址) | 0x10 | 写入目标地址 : • 设置数据包要发送到的接收端地址。通常为5字节。 |
| RX_ADDR_P0 (接收管道0地址) | 0x0A | 关键地址配置 : • 设置接收管道0的地址。为实现ACK功能,发送端的RX_ADDR_P0必须与接收端的RX_ADDR_P0地址相同,以便接收应答信号。 |
| RX_PW_P0 (接收管道0负载宽度) | 0x11 | 设置数据包大小 : • 设置接收管道0的负载数据长度(1-32字节)。发送和接收方的设置需匹配。 |
| FIFO_STATUS (FIFO状态) | 0x17 | 查看数据缓冲区 : • 检查发送(TX)或接收(RX)FIFO(先入先出缓冲区)是满还是空。 |
其中需要注意:
-
基本通信条件 :要确保两个SI24R1模块能正常通信,必须满足四个条件:相同的射频频道(RF_CH)、相同的地址(TX_ADDR与RX_ADDR_P0一致)、相同的数据负载宽度(RX_PW_P0)以及相同的空中数据传输速率(RF_SETUP)。
-
ACK通信的关键 :要实现带自动应答的可靠通信,除了配置
EN_AA寄存器,一个极易出错的点是发送端必须将其接收通道0的地址(RX_ADDR_P0)设置为与接收端的地址相同。这样发送端才能正确接收到接收端回复的ACK信号。 -
模式切换顺序:芯片只能在关断(Shutdown)、待机(Standby)或发射空闲(Idle-TX)模式下配置寄存器。配置为发射或接收模式前,通常需要先将CE引脚拉低,配置完相关寄存器后再将CE拉高以启动发射或接收。
-
中断处理 :当数据发送成功、接收就绪或达到最大重发次数时,状态寄存器(
STATUS)的相应位会被置起,同时IRQ引脚会产生信号。主控制器(MCU)应在中断服务程序中读取状态寄存器以判断事件类型,并在处理完成后通过向状态寄存器写入相应值来清除中断标志。
封装模块DX-NR01:
或者可以使用其他嵌入了SI24R1芯片的模块,这样就不需要自行添加天线,我这里使用的是DX-NR01,对应的引脚:

外部电路:

对应的一些规格参数:

测试代码:
硬件连接配置:
SI24R1与STM32F103C8T6连接
| STM32引脚 | SI24R1引脚 | 功能 |
|---|---|---|
| PA5 | SCK | SPI时钟线 |
| PA6 | MISO | 主输入从输出 |
| PA7 | MOSI | 主输出从输入 |
| PA4 | CSN | 片选信号(低有效) |
| PA8 | CE | 芯片使能 |
| PB2 | IRQ | 中断请求 |
小车端电机驱动连接(TB6612FNG)
| STM32引脚 | TB6612引脚 | 电机控制 |
|---|---|---|
| PB6 | PWMA | 电机A PWM |
| PB7 | AIN1 | 电机A方向1 |
| PB8 | AIN2 | 电机A方向2 |
| PB9 | PWMB | 电机B PWM |
| PB10 | BIN1 | 电机B方向1 |
| PB11 | BIN2 | 电机B方向2 |
下面是控制遥控车的代码,首先是遥控器代码如下:
cpp
#ifndef __REMOTE_CONTROL_H
#define __REMOTE_CONTROL_H
#include "stm32f1xx_hal.h"
// SI24R1引脚定义
#define SI24R1_CSN_PIN GPIO_PIN_4
#define SI24R1_CSN_PORT GPIOA
#define SI24R1_CE_PIN GPIO_PIN_8
#define SI24R1_CE_PORT GPIOA
#define SI24R1_IRQ_PIN GPIO_PIN_2
#define SI24R1_IRQ_PORT GPIOB
// SI24R1寄存器地址定义 [2,4](@ref)
#define NRF_READ_REG 0x00
#define NRF_WRITE_REG 0x20
#define CONFIG_REG 0x00
#define EN_AA_REG 0x01
#define EN_RXADDR_REG 0x02
#define SETUP_RETR_REG 0x04
#define RF_CH_REG 0x05
#define RF_SETUP_REG 0x06
#define STATUS_REG 0x07
#define RX_ADDR_P0_REG 0x0A
#define TX_ADDR_REG 0x10
#define RX_PW_P0_REG 0x11
#define FIFO_STATUS_REG 0x17
#define FLUSH_TX_CMD 0xE1
#define FLUSH_RX_CMD 0xE2
#define W_TX_PAYLOAD_CMD 0xA0
#define R_RX_PAYLOAD_CMD 0x61
// 状态寄存器位定义 [1,5](@ref)
#define SI24R1_STATUS_TX_DS (1 << 5) // 数据发送完成
#define SI24R1_STATUS_MAX_RT (1 << 4) // 达到最大重发次数
#define SI24R1_STATUS_RX_DR (1 << 6) // 数据接收完成
// 通信参数
#define TX_ADDRESS {0x34, 0x43, 0x10, 0x10, 0x01} // 发送地址 [4](@ref)
#define RX_PAYLOAD_WIDTH 5 // 数据包长度
#define RF_CHANNEL 40 // 射频频道 [2](@ref)
// 控制数据结构
typedef struct {
uint8_t lx; // 左摇杆X轴 (0-255)
uint8_t ly; // 左摇杆Y轴 (0-255)
uint8_t rx; // 右摇杆X轴 (0-255)
uint8_t ry; // 右摇杆Y轴 (0-255)
uint8_t buttons;// 按键状态 (按位表示)
} ControlData_t;
// 函数声明
void SI24R1_Init(void);
uint8_t SI24R1_Check(void);
void SI24R1_TX_Mode(void);
uint8_t SI24R1_Tx_Packet(uint8_t *tx_buf, uint8_t length);
void SI24R1_Write_Reg(uint8_t reg, uint8_t value);
uint8_t SI24R1_Read_Reg(uint8_t reg);
void SI24R1_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len);
void SI24R1_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len);
void Remote_Control_Init(void);
void Read_Controller_Input(ControlData_t *ctrl);
uint8_t Send_Control_Data(ControlData_t *ctrl);
#endif
#include "remote_control.h"
#include "stdio.h" // 用于调试输出
extern SPI_HandleTypeDef hspi1;
// 引脚控制宏
#define SI24R1_CSN_LOW() HAL_GPIO_WritePin(SI24R1_CSN_PORT, SI24R1_CSN_PIN, GPIO_PIN_RESET)
#define SI24R1_CSN_HIGH() HAL_GPIO_WritePin(SI24R1_CSN_PORT, SI24R1_CSN_PIN, GPIO_PIN_SET)
#define SI24R1_CE_LOW() HAL_GPIO_WritePin(SI24R1_CE_PORT, SI24R1_CE_PIN, GPIO_PIN_RESET)
#define SI24R1_CE_HIGH() HAL_GPIO_WritePin(SI24R1_CE_PORT, SI24R1_CE_PIN, GPIO_PIN_SET)
// SPI读写函数 [5](@ref)
uint8_t SI24R1_ReadWriteByte(uint8_t tx_data) {
uint8_t rx_data;
HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, 1000);
return rx_data;
}
// 写寄存器
void SI24R1_Write_Reg(uint8_t reg, uint8_t value) {
SI24R1_CSN_LOW();
SI24R1_ReadWriteByte(NRF_WRITE_REG | reg);
SI24R1_ReadWriteByte(value);
SI24R1_CSN_HIGH();
}
// 读寄存器
uint8_t SI24R1_Read_Reg(uint8_t reg) {
uint8_t value;
SI24R1_CSN_LOW();
SI24R1_ReadWriteByte(NRF_READ_REG | reg);
value = SI24R1_ReadWriteByte(0xFF);
SI24R1_CSN_HIGH();
return value;
}
// 写数据缓冲区
void SI24R1_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len) {
SI24R1_CSN_LOW();
SI24R1_ReadWriteByte(NRF_WRITE_REG | reg);
for(uint8_t i = 0; i < len; i++) {
SI24R1_ReadWriteByte(pBuf[i]);
}
SI24R1_CSN_HIGH();
}
// 读数据缓冲区 (补充缺失的函数) [2](@ref)
void SI24R1_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len) {
SI24R1_CSN_LOW();
SI24R1_ReadWriteByte(NRF_READ_REG | reg);
for(uint8_t i = 0; i < len; i++) {
pBuf[i] = SI24R1_ReadWriteByte(0xFF);
}
SI24R1_CSN_HIGH();
}
// SI24R1硬件初始化
void SI24R1_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// CSN和CE引脚配置为输出 [1](@ref)
GPIO_InitStruct.Pin = SI24R1_CSN_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SI24R1_CSN_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = SI24R1_CE_PIN;
HAL_GPIO_Init(SI24R1_CE_PORT, &GPIO_InitStruct);
// IRQ引脚配置为输入 [3](@ref)
GPIO_InitStruct.Pin = SI24R1_IRQ_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(SI24R1_IRQ_PORT, &GPIO_InitStruct);
// 初始状态
SI24R1_CSN_HIGH();
SI24R1_CE_LOW();
HAL_Delay(5); // 等待芯片稳定
}
// 检查SI24R1是否存在 [2,5](@ref)
uint8_t SI24R1_Check(void) {
uint8_t check_in[5] = {0xA5, 0xA5, 0xA5, 0xA5, 0xA5};
uint8_t check_out[5] = {0};
// 写入测试地址再读回比较
SI24R1_Write_Buf(TX_ADDR_REG, check_in, 5);
SI24R1_Read_Buf(TX_ADDR_REG, check_out, 5);
for(uint8_t i = 0; i < 5; i++) {
if(check_in[i] != check_out[i]) {
return 1; // 检查失败
}
}
return 0; // 检查成功
}
// 设置为发送模式 (增强版) [4,5](@ref)
void SI24R1_TX_Mode(void) {
uint8_t tx_address[5] = TX_ADDRESS;
SI24R1_CE_LOW();
// 1. 配置基本参数 [5](@ref)
SI24R1_Write_Reg(SETUP_RETR_REG, 0x1A); // 自动重发: 500us+86us延迟, 10次重试
SI24R1_Write_Reg(RF_CH_REG, RF_CHANNEL); // 设置射频频道
SI24R1_Write_Reg(RF_SETUP_REG, 0x0F); // 2Mbps, 0dBm发射功率
// 2. 配置地址 [4](@ref)
SI24R1_Write_Buf(TX_ADDR_REG, tx_address, 5); // 设置接收端地址
SI24R1_Write_Buf(RX_ADDR_P0_REG, tx_address, 5); // 设置发送端通道0地址(用于接收ACK)
// 3. 使能自动应答和接收通道 [4](@ref)
SI24R1_Write_Reg(EN_AA_REG, 0x01); // 使能通道0自动应答
SI24R1_Write_Reg(EN_RXADDR_REG, 0x01); // 使能通道0接收地址
SI24R1_Write_Reg(RX_PW_P0_REG, RX_PAYLOAD_WIDTH); // 设置通道0数据宽度
// 4. 配置为发送模式 [5](@ref)
SI24R1_Write_Reg(CONFIG_REG, 0x0E); // 使能CRC, PWR_UP, 发送模式
// 清除中断标志
SI24R1_Write_Reg(STATUS_REG, 0x70);
SI24R1_CE_HIGH();
HAL_Delay(1);
}
// 发送数据包 (改进版) [1,5](@ref)
uint8_t SI24R1_Tx_Packet(uint8_t *tx_buf, uint8_t length) {
uint8_t status;
uint16_t timeout = 0;
SI24R1_CE_LOW();
// 清空TX FIFO [1](@ref)
SI24R1_Write_Reg(FLUSH_TX_CMD, 0xFF);
// 写入数据到TX FIFO [5](@ref)
SI24R1_Write_Buf(W_TX_PAYLOAD_CMD, tx_buf, length);
// 启动发送
SI24R1_CE_HIGH();
HAL_Delay(1); // CE至少保持10us高电平 [4](@ref)
// 等待发送完成或超时 (使用IRQ引脚检测) [3,5](@ref)
while (HAL_GPIO_ReadPin(SI24R1_IRQ_PORT, SI24R1_IRQ_PIN) != GPIO_PIN_RESET) {
timeout++;
if (timeout > 1000) { // 超时处理
SI24R1_CE_LOW();
return 2; // 超时错误
}
HAL_Delay(1);
}
// 读取状态寄存器
status = SI24R1_Read_Reg(STATUS_REG);
// 清除中断标志
SI24R1_Write_Reg(STATUS_REG, status);
SI24R1_CE_LOW();
// 判断发送结果 [5](@ref)
if (status & SI24R1_STATUS_MAX_RT) {
SI24R1_Write_Reg(FLUSH_TX_CMD, 0xFF); // 清除TX FIFO
return 3; // 达到最大重发次数
}
if (status & SI24R1_STATUS_TX_DS) {
return 0; // 发送成功
}
return 1; // 其他错误
}
// 遥控器初始化
void Remote_Control_Init(void) {
printf("Initializing Remote Control...\r\n");
SI24R1_Init();
if(SI24R1_Check() == 0) {
printf("SI24R1 Check OK\r\n");
SI24R1_TX_Mode();
printf("SI24R1 TX Mode Set\r\n");
} else {
printf("SI24R1 Check Failed!\r\n");
// 这里可以添加错误处理,如LED闪烁报警
}
}
// 读取控制器输入 (需要根据实际硬件调整)
void Read_Controller_Input(ControlData_t *ctrl) {
// 模拟摇杆数据 - 实际应用中应替换为ADC读取
static uint8_t counter = 0;
ctrl->lx = 128 + (counter % 50); // 模拟变化的摇杆值
ctrl->ly = 128;
ctrl->rx = 128;
ctrl->ry = 128;
ctrl->buttons = (counter % 20 == 0) ? 0x01 : 0x00; // 模拟按键
counter++;
}
// 发送控制数据 (带返回值)
uint8_t Send_Control_Data(ControlData_t *ctrl) {
uint8_t tx_data[5];
uint8_t result;
// 打包数据
tx_data[0] = ctrl->lx;
tx_data[1] = ctrl->ly;
tx_data[2] = ctrl->rx;
tx_data[3] = ctrl->ry;
tx_data[4] = ctrl->buttons;
// 发送数据
result = SI24R1_Tx_Packet(tx_data, 5);
// 可选的调试输出
// printf("Sending: LX=%d, LY=%d, RX=%d, RY=%d, BTN=0x%02X, Result=%d\r\n",
// ctrl->lx, ctrl->ly, ctrl->rx, ctrl->ry, ctrl->buttons, result);
return result;
}
#include "main.h"
#include "remote_control.h"
#include "stdio.h"
ControlData_t ctrl_data;
// 重定向printf用于调试
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE {
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI1_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
printf("Remote Control System Starting...\r\n");
Remote_Control_Init();
uint32_t last_send_time = 0;
uint8_t send_count = 0;
uint8_t error_count = 0;
printf("Entering Main Loop\r\n");
while(1) {
uint32_t current_time = HAL_GetTick();
// 50Hz发送频率控制
if(current_time - last_send_time >= 20) {
last_send_time = current_time;
Read_Controller_Input(&ctrl_data);
uint8_t result = Send_Control_Data(&ctrl_data);
send_count++;
if(result != 0) {
error_count++;
}
// 每100次发送打印一次统计信息
if(send_count % 100 == 0) {
printf("Send: %d, Errors: %d, Success Rate: %.1f%%\r\n",
send_count, error_count,
(1.0 - (float)error_count/send_count) * 100);
}
}
// 其他任务可以放在这里
HAL_Delay(1);
}
}
遥控车代码如下:
cpp
#ifndef __CAR_RECEIVER_H
#define __CAR_RECEIVER_H
#include "stm32f1xx_hal.h"
// SI24R1寄存器地址定义
#define NRF_READ_REG 0x00
#define NRF_WRITE_REG 0x20
#define CONFIG_REG 0x00
#define EN_AA_REG 0x01
#define EN_RXADDR_REG 0x02
#define SETUP_RETR_REG 0x04
#define RF_CH_REG 0x05
#define RF_SETUP_REG 0x06
#define STATUS_REG 0x07
#define RX_ADDR_P0_REG 0x0A
#define TX_ADDR_REG 0x10
#define RX_PW_P0_REG 0x11
#define FIFO_STATUS_REG 0x17
#define FLUSH_TX_CMD 0xE1
#define FLUSH_RX_CMD 0xE2
#define W_TX_PAYLOAD_CMD 0xA0
#define R_RX_PAYLOAD_CMD 0x61
// 状态寄存器位定义
#define STATUS_RX_DR (1 << 6)
#define STATUS_TX_DS (1 << 5)
#define STATUS_MAX_RT (1 << 4)
// 通信参数
#define RF_CHANNEL 2
#define RF_DATA_RATE 0x0F
#define RX_PAYLOAD_WIDTH 5
#define RX_ADDRESS {0x34, 0x43, 0x10, 0x10, 0x01}
// 中断标志位全局变量声明
extern volatile uint8_t si24r1_irq_triggered;
extern volatile uint8_t si24r1_data_ready;
extern uint8_t rx_data[5];
// 电机控制结构
typedef struct {
int16_t left_speed;
int16_t right_speed;
} MotorControl_t;
// 函数声明
void Car_Receiver_Init(void);
void Motor_Control_Init(void);
void Set_Motor_Speed(uint8_t motor, int16_t speed);
void Motor_Control(uint8_t lx, uint8_t ly, uint8_t buttons);
void SI24R1_RX_Mode(void);
uint8_t SI24R1_Rx_Packet(uint8_t *rx_buf);
void Process_SI24R1_Interrupt(void);
// SI24R1底层函数
void SI24R1_Init(void);
uint8_t SI24R1_Check(void);
void SI24R1_Write_Reg(uint8_t reg, uint8_t value);
uint8_t SI24R1_Read_Reg(uint8_t reg);
void SI24R1_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len);
void SI24R1_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len);
#endif
#include "car_receiver.h"
#include "stdio.h"
extern SPI_HandleTypeDef hspi1;
extern TIM_HandleTypeDef htim3;
extern TIM_HandleTypeDef htim4;
// 全局变量定义
volatile uint8_t si24r1_irq_triggered = 0;
volatile uint8_t si24r1_data_ready = 0;
uint8_t rx_data[5] = {0};
// 引脚控制宏
#define SI24R1_CSN_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)
#define SI24R1_CSN_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)
#define SI24R1_CE_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET)
#define SI24R1_CE_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET)
// SPI读写函数
uint8_t SI24R1_ReadWriteByte(uint8_t tx_data) {
uint8_t rx_data;
HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, 1000);
return rx_data;
}
// 写寄存器
void SI24R1_Write_Reg(uint8_t reg, uint8_t value) {
SI24R1_CSN_LOW();
SI24R1_ReadWriteByte(NRF_WRITE_REG | reg);
SI24R1_ReadWriteByte(value);
SI24R1_CSN_HIGH();
}
// 读寄存器
uint8_t SI24R1_Read_Reg(uint8_t reg) {
uint8_t value;
SI24R1_CSN_LOW();
SI24R1_ReadWriteByte(NRF_READ_REG | reg);
value = SI24R1_ReadWriteByte(0xFF);
SI24R1_CSN_HIGH();
return value;
}
// 写数据缓冲区
void SI24R1_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len) {
SI24R1_CSN_LOW();
SI24R1_ReadWriteByte(NRF_WRITE_REG | reg);
for(uint8_t i = 0; i < len; i++) {
SI24R1_ReadWriteByte(pBuf[i]);
}
SI24R1_CSN_HIGH();
}
// 读数据缓冲区
void SI24R1_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len) {
SI24R1_CSN_LOW();
SI24R1_ReadWriteByte(NRF_READ_REG | reg);
for(uint8_t i = 0; i < len; i++) {
pBuf[i] = SI24R1_ReadWriteByte(0xFF);
}
SI24R1_CSN_HIGH();
}
// SI24R1硬件初始化
void SI24R1_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// CSN和CE引脚配置为输出
GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// IRQ引脚配置为输入
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
SI24R1_CSN_HIGH();
SI24R1_CE_LOW();
HAL_Delay(5);
}
// 检查SI24R1是否存在
uint8_t SI24R1_Check(void) {
uint8_t check_in[5] = {0xA5, 0xA5, 0xA5, 0xA5, 0xA5};
uint8_t check_out[5] = {0};
SI24R1_Write_Buf(TX_ADDR_REG, check_in, 5);
SI24R1_Read_Buf(TX_ADDR_REG, check_out, 5);
for(uint8_t i = 0; i < 5; i++) {
if(check_in[i] != check_out[i]) return 1;
}
return 0;
}
// 设置为接收模式
void SI24R1_RX_Mode(void) {
SI24R1_CE_LOW();
uint8_t rx_addr[5] = RX_ADDRESS;
SI24R1_Write_Buf(RX_ADDR_P0_REG, rx_addr, 5);
SI24R1_Write_Reg(EN_AA_REG, 0x01);
SI24R1_Write_Reg(EN_RXADDR_REG, 0x01);
SI24R1_Write_Reg(CONFIG_REG, 0x0F);
SI24R1_Write_Reg(RF_CH_REG, RF_CHANNEL);
SI24R1_Write_Reg(RF_SETUP_REG, RF_DATA_RATE);
SI24R1_Write_Reg(RX_PW_P0_REG, RX_PAYLOAD_WIDTH);
uint8_t status = SI24R1_Read_Reg(STATUS_REG);
SI24R1_Write_Reg(STATUS_REG, status);
SI24R1_Write_Reg(FLUSH_RX_CMD, 0xFF);
SI24R1_CE_HIGH();
HAL_Delay(1);
}
// 接收数据包
uint8_t SI24R1_Rx_Packet(uint8_t *rx_buf) {
uint8_t status = SI24R1_Read_Reg(STATUS_REG);
if(status & STATUS_RX_DR) {
SI24R1_CSN_LOW();
SI24R1_ReadWriteByte(R_RX_PAYLOAD_CMD);
for(uint8_t i = 0; i < 5; i++) {
rx_buf[i] = SI24R1_ReadWriteByte(0xFF);
}
SI24R1_CSN_HIGH();
SI24R1_Write_Reg(STATUS_REG, STATUS_RX_DR);
return 0;
}
return 1;
}
// 电机控制初始化
void Motor_Control_Init(void) {
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);
Set_Motor_Speed(0, 0);
Set_Motor_Speed(1, 0);
}
// 设置电机速度
void Set_Motor_Speed(uint8_t motor, int16_t speed) {
if(speed > 1000) speed = 1000;
if(speed < -1000) speed = -1000;
if(motor == 0) {
if(speed >= 0) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, speed);
} else {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, -speed);
}
} else {
if(speed >= 0) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, speed);
} else {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET);
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, -speed);
}
}
}
// 电机控制函数
void Motor_Control(uint8_t lx, uint8_t ly, uint8_t buttons) {
int16_t base_speed = ((int16_t)ly - 128) * 8;
int16_t turn = ((int16_t)lx - 128) * 4;
int16_t speed_left = base_speed + turn;
int16_t speed_right = base_speed - turn;
speed_left = (speed_left > 1000) ? 1000 : ((speed_left < -1000) ? -1000 : speed_left);
speed_right = (speed_right > 1000) ? 1000 : ((speed_right < -1000) ? -1000 : speed_right);
if(buttons & 0x01) {
speed_left = 0;
speed_right = 0;
}
Set_Motor_Speed(0, speed_left);
Set_Motor_Speed(1, speed_right);
}
// 中断处理函数
void EXTI2_IRQHandler(void) {
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if(GPIO_Pin == GPIO_PIN_2) {
si24r1_irq_triggered = 1;
}
}
// 处理SI24R1中断(在主循环中调用)
void Process_SI24R1_Interrupt(void) {
if(si24r1_irq_triggered) {
si24r1_irq_triggered = 0;
if(SI24R1_Rx_Packet(rx_data) == 0) {
si24r1_data_ready = 1;
}
}
}
// 小车接收器初始化
void Car_Receiver_Init(void) {
SI24R1_Init();
if(SI24R1_Check() == 0) {
SI24R1_RX_Mode();
Motor_Control_Init();
}
}
#include "main.h"
#include "car_receiver.h"
#include "stdio.h"
uint32_t last_receive_time = 0;
uint8_t connection_ok = 0;
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI1_Init();
MX_TIM3_Init();
MX_TIM4_Init();
MX_USART1_UART_Init();
printf("Car Receiver Starting...\r\n");
Car_Receiver_Init();
// 配置外部中断
HAL_NVIC_SetPriority(EXTI2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI2_IRQn);
printf("Entering Main Loop\r\n");
while(1) {
// 处理中断标志位
Process_SI24R1_Interrupt();
// 检查数据就绪标志
if(si24r1_data_ready) {
si24r1_data_ready = 0;
last_receive_time = HAL_GetTick();
connection_ok = 1;
Motor_Control(rx_data[0], rx_data[1], rx_data[4]);
printf("RX: LX=%d, LY=%d, BTN=0x%02X\r\n",
rx_data[0], rx_data[1], rx_data[4]);
}
// 通信超时保护(3秒无数据自动停止)
if(HAL_GetTick() - last_receive_time > 3000) {
if(connection_ok) {
printf("Connection Lost! Stopping Motors.\r\n");
connection_ok = 0;
}
Set_Motor_Speed(0, 0);
Set_Motor_Speed(1, 0);
}
HAL_Delay(5);
}
}
其中需要注意:
在 SI24R1_TX_Mode中建立连接(配置地址)后,每次通过 SI24R1_Tx_Packet发送数据时,无需在数据包里附带接收地址。
一个完整的数据包在射频层面由以下几个部分构成:
-
前导码:用于接收方进行信号同步。
-
地址字段:即目标接收机的地址。
-
包控制字:包含数据长度等信息。
-
负载数据 :即您要发送的实际应用数据(如
ControlData_t结构体)。 -
CRC校验码:用于检查数据完整性。
当调用 SI24R1_Tx_Packet时,芯片会自动将从 TX_ADDR寄存器中读取的地址、以及根据配置生成的前导码、CRC等,与您提供的负载数据打包成一个完整的射频信号发送出去。接收端则会持续侦听空中的信号,只有当地址字段与自身某个已使能的接收管道地址完全匹配时,才会接收后面的数据