当前位置: 首页 > news >正文

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通信包含以下步骤:

  1. 起始通信​:主设备将目标从设备的SS信号拉低。这是通信的起始信号。

  2. 生成时钟​:主设备开始产生SCLK时钟信号。

  3. 数据传输​:

    • 在SCLK的每个时钟周期内,主设备通过MOSI线发送1位数据,同时通过MISO线接收1位数据。

    • 数据通常高位(MSB)在前,低位(LSB)在后,但顺序可配置。

    • 数据的发送和采样时刻由所选的CPHA决定。例如,在模式0(CPHA=0)下,数据在SCLK的上升沿被采样,在下降沿进行切换(准备下一位数据)。

  4. 结束通信​:数据传输完毕后,主设备将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机制是这些模式得以运转的“血液”

  1. 停等式ARQ (Stop-and-Wait ARQ)​

    这是最简单的一种模式。发送方每发送一个数据包后,就停止发送并等待接收方的ACK确认。收到ACK后,再发送下一个包。如果超时未收到ACK,则重发原数据包。这种方式非常可靠,但效率较低,因为大部分时间都在等待。

  2. 回退N帧ARQ (Go-Back-N ARQ)​

    为了提升效率,这种模式允许发送方在未收到确认时连续发送多个数据包​(一个窗口内的包)。如果发送方收到对第N个包的ACK,则表明第N个包及之前的所有包都已被正确接收。如果某个包出错或丢失,发送方需要回退并从该包开始重传所有后续的包​。效率比停等式高,但重传时可能会浪费一些带宽。

  3. 选择性重传ARQ (Selective Repeat ARQ)​

    这是最高效但实现也最复杂的模式。当一系列数据包中某个包丢失时,接收方可以缓存后续正确接收但失序的包。发送方只重传真正丢失或出错的那个特定数据包,接收方最后将所有包按顺序重组。这最大限度地节省了带宽,但要求接收方有更大的缓存空间和更复杂的管理逻辑。

特性

ARQ协议 (Automatic Repeat reQuest)

ACK模式 (Acknowledgement)

角色定位

可靠传输的总体框架

框架内的关键执行机制

核心目标

在不可靠的信道上实现无差错的数据传输

为发送方提供明确的接收状态反馈

工作机制

通过超时重传、序号机制、滑动窗口等综合手段确保数据正确送达

接收方向发送方发送确认信号(ACK/NACK)

关系比喻

交通控制系统​(规定红灯停、绿灯行的规则和事故处理流程)

交通信号灯​(直接给出红灯或绿灯的指令)

ACK自动应答流程如下:

  1. 发送端使用W_TX_PAYLOAD命令将数据写入TX FIFO。

  2. 数据包被发送出去,其包控制字中的NO_ACK标志位为0,表示需要接收端应答。

  3. 接收端成功接收数据后,会自动回复一个ACK信号。

  4. 发送端收到ACK,则产生TX_DS中断,表示发送成功,并自动清除TX FIFO中的数据。

  5. 若发送端在设定的重发延迟时间内未收到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(先入先出缓冲区)是满还是空。

其中需要注意:

  1. 基本通信条件​:要确保两个SI24R1模块能正常通信,必须满足四个条件:​相同的射频频道(RF_CH)、相同的地址(TX_ADDR与RX_ADDR_P0一致)、相同的数据负载宽度(RX_PW_P0)以及相同的空中数据传输速率(RF_SETUP)​​。

  2. ACK通信的关键​:要实现带自动应答的可靠通信,除了配置EN_AA寄存器,一个极易出错的点是发送端必须将其接收通道0的地址(RX_ADDR_P0)设置为与接收端的地址相同。这样发送端才能正确接收到接收端回复的ACK信号。

  3. 模式切换顺序​:芯片只能在关断(Shutdown)、待机(Standby)或发射空闲(Idle-TX)模式下配置寄存器。配置为发射或接收模式前,通常需要先将CE引脚拉低,配置完相关寄存器后再将CE拉高以启动发射或接收。

  4. 中断处理​:当数据发送成功、接收就绪或达到最大重发次数时,状态寄存器(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等,与您提供的负载数据打包成一个完整的射频信号发送出去。接收端则会持续侦听空中的信号,只有当地址字段与自身某个已使能的接收管道地址完全匹配时,才会接收后面的数据


http://www.dtcms.com/a/537070.html

相关文章:

  • 西安网站开发php网站插件
  • LinearRAG—重新定义GraphRAG:无需关系抽取的线性图构建新范式 -香港理工
  • 第4章-程序计数器
  • HashMap 与 HashSet
  • 怎么在虚拟主机上建网站wordpress rest图片
  • 小米手机之间数据转移的6种方法
  • 前端开发中的表格标签
  • PaddleOCR-VL本地部署流程
  • 2.2 复合类型
  • 做网站图片自动切换宁波软件开发
  • quat:高性能四元数运算库
  • [MySQL]表——分组查询
  • 济南做网站的好公司有哪些极简资讯网站开发
  • 网站后台页面设计互联网+可以做什么项目
  • 项目八 使用postman实现简易防火墙功能
  • 使用postman 测试restful接口
  • 2008 iis 添加 网站 权限设置网站策划案4500
  • 以自主创新推动能源装备智能化升级,为能源安全构筑“确定性”底座
  • 构建AI智能体:七十六、深入浅出LoRA:低成本高效微调大模型的原理与实践
  • 中国各大网站排名网站源码爬取
  • FFmpeg 安装与配置教程(Windows 系统)
  • 【数字逻辑】 60进制数字秒表设计实战:用74HC161搭计数器+共阴极数码管显示(附完整接线图)
  • 新网网站空间花都网站建设价格
  • 前端底层原理与复杂问题映射表
  • Digital Micrograph下载安装教程
  • 怎么做网站的301建设设计院网站
  • 自己的服务器 linux centos8部署django项目
  • 做网站注册会员加入实名认证功能广西建设工程质量监督网站
  • 遵义哪里有做网站的外国网站上做Task
  • 动态修改浏览器地址而不刷新页面