STM32-SPI协议
前言:
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 模块引脚 | 功能描述 |
|---|---|---|
|
| SPI时钟线,用于同步数据传输。 |
|
| 主设备输入,从设备输出,STM32通过此线从SI24R1读取数据。 |
|
| 主设备输出,从设备输入,STM32通过此线向SI24R1发送数据。 |
|
| 片选信号,低电平有效。此引脚为低时,SI24R1才会响应SPI通信 。 |
|
| 芯片使能,用于控制SI24R1的工作模式切换(如待机、发送、接收)。 |
|
| 中断请求,当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为发送方地址 |
自动应答 | 使能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 | 核心模式控制: |
EN_AA (使能自动应答) | 0x01 | 控制自动应答(ACK): |
EN_RXADDR (使能接收地址) | 0x02 | 启用接收管道: |
SETUP_AW (地址宽度设置) | 0x03 | 设置地址长度: |
SETUP_RETR (自动重发设置) | 0x04 | 可靠性核心: |
RF_CH (射频频道) | 0x05 | 设置工作频率: |
RF_SETUP (射频设置) | 0x06 | 设置射频参数: |
STATUS (状态寄存器) | 0x07 | 读取通信状态: |
TX_ADDR (发送地址) | 0x10 | 写入目标地址: |
RX_ADDR_P0 (接收管道0地址) | 0x0A | 关键地址配置: |
RX_PW_P0 (接收管道0负载宽度) | 0x11 | 设置数据包大小: |
FIFO_STATUS (FIFO状态) | 0x17 | 查看数据缓冲区: |
其中需要注意:
基本通信条件:要确保两个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 |
下面是控制遥控车的代码,首先是遥控器代码如下:
#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 FIFOreturn 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)
#endifPUTCHAR_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);}
}遥控车代码如下:
#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等,与您提供的负载数据打包成一个完整的射频信号发送出去。接收端则会持续侦听空中的信号,只有当地址字段与自身某个已使能的接收管道地址完全匹配时,才会接收后面的数据
