相关资料
背景-无人机遥控
手柄板
飞控板
上电后SPI写寄存器失败问题
同样的 Int_Si24R1.h
驱动代码,放在手柄板上可以正常初始化 Si24R1
,但放在飞控板上却总是会有第一次SPI写入失败的情况。
起初没有怀疑到芯片硬件问题上,总是在排查SPI
外设是否初始化正确、初始化程序是否不符合 Si24R1
规定的流程、Si24R1
时序要求是否需要延时等。
后来发现,虽然是同样的芯片,但是手柄板上的芯片每次上电第一次SPI写寄存器都能成功,而飞控板上的芯片每次上电第一次SPI写寄存器都失败。(通过两次写寄存器,再读寄存器才验证了确实是芯片的缺陷)
SPI写Si24R1寄存器自检V1.0
如下,最初期望通过写一次,然后轮询读来测试写入是否成功。但这先入为主地认为SPI写入是成功的、读出来的不对可能是因为存在写入周期。这个逻辑对于芯片上电后最初的几次写入可能都失败的情况则无法应对(即使增加上电延时以确保芯片工作稳定也存在该问题)。
验证芯片问题
发现硬件问题:后来怀疑飞控板上的芯片存在上电后的最初几次写入可能失败的情况,通过多次写入后再读取,验证了飞控板上的芯片存在上电后SPI写入不稳定的情况。
SPI写Si24R1寄存器自检V2.0
应该重复写入并读取验证的逻辑,确保某一次写入正常后(芯片的SPI工作稳定)才终止轮询自检逻辑。
复盘
- 不能总是习惯性地认为芯片是可靠的,我们应该完善自检逻辑以确保芯片工作正常
- 芯片上电后,对于需要使用通讯协议向芯片内寄存器写入的场景,需要考虑上电后芯片可能不稳定的情况;在正式写寄存器初始化芯片之前,需要增加写入并读取验证的轮询自检机制,确保芯片写入正常之后才开始正式的芯片初始化程序。
初始化时清除标志位和FIFO的问题
官方例程没有考虑FIFO已满的情况
如上,初始化为RX模式时,将所有中断标志位清除,但没有清除FIFO。因此,如果FIFO缓存了之前接收到的数据并没有被处理的话(例如飞控板重新烧录期间,手柄板仍在发送数据,那么飞控板的FIFO就会累积数据),这时会出现 RxPacket
函数无法接收数据的现象(例如手柄板一直在发送,飞控板烧录重启后突然无法接收了),这是因为飞控板的FIFO满了,新来的数据无法进入FIFO,RX_DR
也无法被置位。
官方例程初始化流程优化
初始化时将中断标志位和FIFO都清除,适用于不考虑初始化前FIFO累积的数据的情况。
拓展-接收时判断FIFO状态
或者在 RxPacket
中增加对 FIFO_STATUS
的判断逻辑,而不仅仅只判断 STATUS
中断标志位
Si24R1驱动示例代码
Int_Si24R1.h
#ifndef __INT_SI24R1_H__
#define __INT_SI24R1_H__
#include "App_Remote.h"
#include "spi.h"
#define SI24R1_CSN_HIGH HAL_GPIO_WritePin(SI24R1_CSN_GPIO_Port, SI24R1_CSN_Pin, GPIO_PIN_SET)
#define SI24R1_CSN_LOW HAL_GPIO_WritePin(SI24R1_CSN_GPIO_Port, SI24R1_CSN_Pin, GPIO_PIN_RESET)
#define SI24R1_CE_HIGH HAL_GPIO_WritePin(SI24R1_CE_GPIO_Port, SI24R1_CE_Pin, GPIO_PIN_SET)
#define SI24R1_CE_LOW HAL_GPIO_WritePin(SI24R1_CE_GPIO_Port, SI24R1_CE_Pin, GPIO_PIN_RESET)
#define READ_SI24R1_IRQ HAL_GPIO_ReadPin(SI24R1_IRQ_GPIO_Port, SI24R1_IRQ_Pin) // IRQ主机数据输入
/*****************************************发送接收数据宽度定义***********************************************/
#define TX_ADR_WIDTH 5 // 5字节的地址宽度
#define RX_ADR_WIDTH 5 // 5字节的地址宽度
#define TX_PLOAD_WIDTH sizeof(RemoteControlPacket_t) // 32字节的用户数据宽度
#define RX_PLOAD_WIDTH sizeof(RemoteControlPacket_t) // 32字节的用户数据宽度
/*******************************************寄存器操作指令**************************************************/
#define SPI_READ_REG 0x00 // 读配置寄存器,低5位为寄存器地址
#define SPI_WRITE_REG 0x20 // 写配置寄存器,低5位为寄存器地址
#define R_RX_PL_WID 0x60 // 读取收到的数据字节数
#define RD_RX_PLOAD 0x61 // 读RX有效数据,1~32字节
#define WR_TX_PLOAD 0xA0 // 写TX有效数据,1~32字节
#define FLUSH_TX 0xE1 // 清除TX FIFO寄存器.发射模式下用
#define FLUSH_RX 0xE2 // 清除RX FIFO寄存器.接收模式下用
#define REUSE_TX_PL 0xE3 // 重新使用上一包数据,CE为高,数据包被不断发送.
#define W_ACK_PAYLOAD 0xA8 // 接收方,将数据通过ACK的形式发出去,最多允许三帧数据存于FIFO中。
#define NOP 0xFF // 空操作,可以用来读状态寄存器
/*******************************************寄存器地址****************************************************/
#define CONFIG \
0x00 // 配置寄存器地址;bit0:1接收模式,0发射模式;bit1:电选择;bit2:CRC模式;bit3:CRC使能;
// bit4:中断MAX_RT(达到最大重发次数中断)使能;bit5:中断TX_DS使能;bit6:中断RX_DR使能
#define EN_AA 0x01 // 使能自动应答功能 bit0~5,对应通道0~5
#define EN_RXADDR 0x02 // 接收地址允许,bit0~5,对应通道0~5
#define SETUP_AW 0x03 // 设置地址宽度(所有数据通道):bit1,0:00,3字节;01,4字节;02,5字节;
#define SETUP_RETR 0x04 // 建立自动重发;bit3:0,自动重发计数器;bit7:4,自动重发延时 250*x+86us
#define RF_CH 0x05 // RF通道,bit6:0,工作通道频率;
#define RF_SETUP \
0x06 // RF寄存器;bit3:传输速率(0:1Mbps,1:2Mbps);bit2:1,发射功率;bit0:低噪声放大器增益
#define STATUS 0x07 // 状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最大:6);
#define MAX_TX 0x10 // 状态寄存器;bit4:达到最大发送次数中断
#define TX_OK 0x20 // 状态寄存器;bit5:TX发送完成中断
#define RX_OK 0x40 // 状态寄存器;bit6:接收到数据中断
#define OBSERVE_TX 0x08 // 发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器
#define CD 0x09 // 载波检测寄存器,bit0,载波检测;
#define RX_ADDR_P0 0x0A // 数据通道0接收地址,最大长度5个字节,低字节在前
#define RX_ADDR_P1 0x0B // 数据通道1接收地址,最大长度5个字节,低字节在前
#define RX_ADDR_P2 0x0C // 数据通道2接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P3 0x0D // 数据通道3接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P4 0x0E // 数据通道4接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P5 0x0F // 数据通道5接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define TX_ADDR 0x10 // 发送地址(低字节在前),ShockBurstTM模式下,RX_ADDR_P0与此地址相等
#define RX_PW_P0 0x11 // 接收数据通道0有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P1 0x12 // 接收数据通道1有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P2 0x13 // 接收数据通道2有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P3 0x14 // 接收数据通道3有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P4 0x15 // 接收数据通道4有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P5 0x16 // 接收数据通道5有效数据宽度(1~32字节),设置为0则非法
#define FIFO_STATUS \
0x17 // FIFO状态寄存器;bit0:RX FIFO寄存器空标志;bit1:RX FIFO满标志;bit2,3:保留
// FIFO状态寄存器;bit4:TX FIFO空标志;bit5:TX FIFO满标志;
// FIFO状态寄存器;bit6:1,循环发送上一数据包/0,不循环;
#define DYNPD 0x1C // 动态负载长度寄存器;bit0——bit5对应通道0-5的动态负载长度使能
#define FEATURE 0x1D // 特征寄存器;bit1:使能ACK负载(带负载数据的ACK包);bit2:使能动态负载长度;
/**********************************************************************************************************/
#define MODEL_RX 1 // 普通接收
#define MODEL_TX 2 // 普通发送
#define MODEL_RX2 3 // 接收模式2,用于双向传输
#define MODEL_TX2 4 // 发送模式2,用于双向传输
#define STATUS_RX_DR 0x40 /**/
#define STATUS_TX_DS 0x20
#define STATUS_MAX_RT 0x10
void Int_SI24R1_InitTxMode(void);
void Int_SI24R1_InitRxMode(void);
void Int_SI24R1_WriteByte(uint8_t reg, uint8_t value);
void Int_SI24R1_WriteBytes(uint8_t reg, uint8_t *buf, uint8_t size);
uint8_t Int_SI24R1_ReadByte(uint8_t reg);
void Int_SI24R1_ReadBytes(uint8_t reg, uint8_t *buf, uint8_t size);
uint8_t Int_SI24R1_RxPacket(uint8_t *rxbuf);
uint8_t Int_SI24R1_TxPacket(uint8_t *txbuf);
void Int_SI24R1_Check(void);
#endif /* __INT_SI24R1_H__ */
Int_Si24R1.c
#include "Int_Si24R1.h"
#include "Com_Logger.h"
// uint8_t TX_ADDRESS[TX_ADR_WIDTH] = {0xB1, 0xB2, 0xB3, 0xB4, 0xB5}; // 定义一个静态发送地址
// uint8_t RX_ADDRESS[RX_ADR_WIDTH] = {0xB1, 0xB2, 0xB3, 0xB4, 0xB5}; // 定义一个静态发送地址
uint8_t TX_ADDRESS[TX_ADR_WIDTH] = {0x0A, 0x01, 0x07, 0x0E, 0x01}; // 定义一个静态发送地址
uint8_t RX_ADDRESS[RX_ADR_WIDTH] = {0x0A, 0x01, 0x07, 0x0E, 0x01}; // 定义一个静态发送地址
void Int_SI24R1_InitTxMode(void) {
SI24R1_CE_LOW;
Int_SI24R1_WriteBytes(SPI_WRITE_REG | TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH); // 写入发送地址
Int_SI24R1_WriteBytes(SPI_WRITE_REG | RX_ADDR_P0, RX_ADDRESS,
RX_ADR_WIDTH); // 为了应答接收设备,接收通道0地址和发送地址相同
Int_SI24R1_WriteByte(SPI_WRITE_REG | EN_AA, 0x01); // 使能接收通道0自动应答
Int_SI24R1_WriteByte(SPI_WRITE_REG | EN_RXADDR, 0x01); // 使能接收通道0
Int_SI24R1_WriteByte(SPI_WRITE_REG | SETUP_RETR,
0x0a); // 自动重发延时等待250us+86us,自动重发10次
Int_SI24R1_WriteByte(SPI_WRITE_REG | RF_CH, 40); // 选择射频通道40
Int_SI24R1_WriteByte(SPI_WRITE_REG | RF_SETUP, 0x0f); // 数据传输率2Mbps,发射功率7dBm
Int_SI24R1_WriteByte(SPI_WRITE_REG | CONFIG, 0x0e); // CRC使能,16位CRC校验,上电,发送模式
Int_SI24R1_WriteByte(SPI_WRITE_REG | STATUS, 0xff);
Int_SI24R1_WriteByte(FLUSH_TX, 0xff);
LOG_DEBUG("Int_SI24R1_InitTxMode done")
uint8_t buf[5]={0};
Int_SI24R1_ReadBytes(SPI_READ_REG | TX_ADDR, buf, TX_ADR_WIDTH);
LOG_DEBUG("TX_ADDRESS: %02X %02X %02X %02X %02X", buf[0], buf[1], buf[2], buf[3], buf[4])
}
void Int_SI24R1_InitRxMode(void) {
SI24R1_CE_LOW;
Int_SI24R1_WriteBytes(SPI_WRITE_REG | RX_ADDR_P0, RX_ADDRESS,
RX_ADR_WIDTH); // 接收设备接收通道0使用和发送设备相同的发送地址
Int_SI24R1_WriteByte(SPI_WRITE_REG | EN_AA, 0x01); // 使能接收通道0自动应答
Int_SI24R1_WriteByte(SPI_WRITE_REG | EN_RXADDR, 0x01); // 使能接收通道0
Int_SI24R1_WriteByte(SPI_WRITE_REG | RF_CH, 40); // 选择射频通道40
Int_SI24R1_WriteByte(SPI_WRITE_REG | RX_PW_P0,
TX_PLOAD_WIDTH); // 接收通道0选择和发送通道相同有效数据宽度
Int_SI24R1_WriteByte(SPI_WRITE_REG | RF_SETUP, 0x0f); // 数据传输率2Mbps,发射功率7dBm
Int_SI24R1_WriteByte(SPI_WRITE_REG | CONFIG, 0x0f); // CRC使能,16位CRC校验,上电,接收模式
Int_SI24R1_WriteByte(SPI_WRITE_REG | STATUS, 0xff); // 清除所有的中断标志位
Int_SI24R1_WriteByte(FLUSH_RX, 0xff); // 清除RX FIFO
SI24R1_CE_HIGH;
LOG_DEBUG("Int_SI24R1_InitRxMode done")
uint8_t buf[5]={0};
Int_SI24R1_ReadBytes(SPI_READ_REG | RX_ADDR_P0, buf, RX_ADR_WIDTH);
LOG_DEBUG("RX_ADDRESS: %02X %02X %02X %02X %02X", buf[0], buf[1], buf[2], buf[3], buf[4])
}
void Int_SI24R1_WriteByte(uint8_t reg, uint8_t value) {
SI24R1_CSN_LOW;
Dri_SPI1_SwapByte(reg);
Dri_SPI1_SwapByte(value);
SI24R1_CSN_HIGH;
}
void Int_SI24R1_WriteBytes(uint8_t reg, uint8_t *buf, uint8_t size) {
SI24R1_CSN_LOW;
Dri_SPI1_SwapByte(reg);
for (uint8_t i = 0; i < size; i++) {
Dri_SPI1_SwapByte(buf[i]);
}
SI24R1_CSN_HIGH;
}
uint8_t Int_SI24R1_ReadByte(uint8_t reg) {
uint8_t value;
SI24R1_CSN_LOW;
Dri_SPI1_SwapByte(reg);
value = Dri_SPI1_SwapByte(0);
SI24R1_CSN_HIGH;
return value;
}
void Int_SI24R1_ReadBytes(uint8_t reg, uint8_t *buf, uint8_t size) {
SI24R1_CSN_LOW;
Dri_SPI1_SwapByte(reg);
for (uint8_t i = 0; i < size; i++) {
buf[i] = Dri_SPI1_SwapByte(0);
}
SI24R1_CSN_HIGH;
}
/********************************************************
函数功能:读取接收数据
入口参数:rxbuf:接收数据存放首地址
返回 值:0:接收到数据
1:没有接收到数据
*********************************************************/
uint8_t Int_SI24R1_RxPacket(uint8_t *rxbuf) {
uint8_t state;
state = Int_SI24R1_ReadByte(SPI_READ_REG + STATUS); // 读取状态寄存器的值
Int_SI24R1_WriteByte(SPI_WRITE_REG + STATUS, state); // 清除RX_DR中断标志
if (state & STATUS_RX_DR) { // 接收到数据
Int_SI24R1_ReadBytes(RD_RX_PLOAD, rxbuf, RX_PLOAD_WIDTH); // 读取数据
Int_SI24R1_WriteByte(FLUSH_RX, 0xff); // 清除RX FIFO寄存器
return 0;
}
return 1; // 没收到任何数据
}
/********************************************************
函数功能:发送一个数据包
入口参数:txbuf:要发送的数据
返回 值:0x10:达到最大重发次数,发送失败
0x20:发送成功
0xff:发送失败
*********************************************************/
uint8_t Int_SI24R1_TxPacket(uint8_t *txbuf) {
uint8_t state;
SI24R1_CE_LOW; // CE拉低,使能SI24R1配置
Int_SI24R1_WriteBytes(WR_TX_PLOAD, txbuf, TX_PLOAD_WIDTH); // 写数据到TX FIFO,32个字节
SI24R1_CE_HIGH; // CE置高,使能发送
while (READ_SI24R1_IRQ == GPIO_PIN_SET)
; // 等待发送完成
state = Int_SI24R1_ReadByte(SPI_READ_REG + STATUS); // 读取状态寄存器的值
Int_SI24R1_WriteByte(SPI_WRITE_REG + STATUS, state); // 清除TX_DS或MAX_RT中断标志
if (state & STATUS_MAX_RT) { // 达到最大重发次数
Int_SI24R1_WriteByte(FLUSH_TX, 0xff); // 清除TX FIFO寄存器
return STATUS_MAX_RT;
}
if (state & STATUS_TX_DS) { // 发送完成
return STATUS_TX_DS;
}
return 0XFF; // 发送失败
}
void Int_SI24R1_Check(void) {
uint8_t buf[5] = {0};
while (1) {
Int_SI24R1_WriteBytes(SPI_WRITE_REG | TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH);
Int_SI24R1_ReadBytes(SPI_READ_REG | TX_ADDR, buf, TX_ADR_WIDTH);
for (uint8_t i = 0; i < TX_ADR_WIDTH; i++) {
if (buf[i] != TX_ADDRESS[i]) {
LOG_DUMP("Int_SI24R1_Test Failed, TX_ADDRESS not match", buf, TX_ADR_WIDTH);
break;
} else if(i == TX_ADR_WIDTH - 1) {
LOG_DEBUG("Int_SI24R1_Test passed")
return;
}
}
}
}
接收方
MX_SPI1_Init();
Int_SI24R1_Check();
Int_SI24R1_InitRxMode();
while(1) {
if (Int_SI24R1_RxPacket((uint8_t *)&packet) == 0) {
LOG_DEBUG("THR: %d, YAW: %d, PIT: %d, ROL: %d", packet.data.THR, packet.data.YAW, packet.data.PIT, packet.data.ROL);
}
HAL_Delay(100);
}
发送方
MX_SPI1_Init();
Int_SI24R1_Check();
Int_SI24R1_InitTxMode();
RemoteControlPacket_t packet;
while (1) {
App_Remote_BuildPacket(&rcdata, &packet);
Int_SI24R1_TxPacket((uint8_t *)&packet);
HAL_Delay(100);
}