ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM8裸机驱动LT8920无线模块:SPI时序、寄存器配置与避坑指南

STM8裸机驱动LT8920无线模块:SPI时序、寄存器配置与避坑指南

简介:这份资源是面向嵌入式开发者的LT8920 2.4G无线模块测试程序,基于STM8微控制器实现,适合正在学习短距离无线通信、需要快速验证LT8920收发功能的初学者与工程师。压缩包共62个文件,约825KB,以c与h源码文件为主,涵盖LT8920驱动、SPI通信、时钟配置、中断与延时等模块,另含o、cout等编译中间文件及pbi、ewp、ewd等IAR工程配置,hex可直接烧录验证。资源围绕STM8控制LT8920完成初始化、参数配置与数据收发,读者可借此理解GFSK调制下的通信流程,并在此基础上修改数据帧格式、调整发射功率与接收灵敏度。目前已有1056人学习下载,工程结构完整,便于在IDE中直接打开调试,是掌握2.4GHz无线通信与STM8编程的实用参考。

1. 从一块 LT8920 说起:为什么 STM8 是它最省事的搭档

手上拿到一块 LT8920 无线模块,第一反应往往是找例程。但市面上大多数示例要么跑在 STM32 上,要么用 Arduino 封装好的库,真正用 STM8 裸机驱动 LT8920 的完整测试程序反而不多。LT8920 是一颗 Sub-1GHz 频段的射频收发芯片,常见工作在 315/433/868/915MHz,SPI 接口配置寄存器,配合 STM8S 系列这种便宜、够用、外设刚好对得上的 MCU,是低成本无线数传方案里非常经典的一套组合。

这篇笔记要解决的就是:怎么用 STM8 把 LT8920 跑通,从 SPI 时序、寄存器配置、收发切换,到实际测试时怎么判断模块到底有没有工作。适合正在做无线遥控、无线传感器节点、低功耗数传的嵌入式工程师,也适合刚接触 Sub-1GHz 射频、想找一个能复现的最小系统的朋友。整套测试程序的核心不在代码量,而在几个关键寄存器和时序细节上,踩过一遍坑之后,后面换频点、改速率都是改参数的事。

2. LT8920 与 STM8 的硬件连接和 SPI 底层驱动

2.1 先搞清楚 LT8920 的引脚和 STM8 的对应关系

LT8920 模块通常引出 8 个左右的引脚,核心是 SPI 四线加两个控制脚。SPI 的 SCS、SCK、SDIO 三根线负责寄存器读写,另外还有两个关键控制引脚:一个是 CSN(片选,低有效),一个是 PKT(中断/状态指示,收发完成或收到数据时拉低或拉高,具体看配置)。有些模块把 CSN 和 SCS 合并,有些分开,接线前一定要看模块丝印或规格书。

STM8S103F3 这类芯片的 SPI 引脚是固定的:PC5 是 SCK,PC6 是 MOSI,PC7 是 MISO。但 LT8920 的 SDIO 是双向单线,不是标准四线 SPI 的 MOSI/MISO 分离结构。这一点是第一个容易翻车的地方——LT8920 用的是三线 SPI 模式,SDIO 在写寄存器时是输出,读寄存器时是输入,需要 MCU 在读写之间切换引脚方向。

我一般这样接:

LT8920 引脚STM8 引脚说明
VCC3.3V供电,注意模块是否耐 5V
GNDGND共地
SCSPC4片选,软件控制
SCKPC5SPI 时钟
SDIOPC6双向数据
PKTPC3中断输入,接外部中断
GIO悬空或接 LED可选

这里没有用 STM8 硬件 SPI 的 MISO,因为 LT8920 的 SDIO 是单线双向,硬件 SPI 的 MOSI 和 MISO 分开反而不好处理。常见做法是用 GPIO 模拟三线 SPI,时序完全自己控制,反而更稳。

2.2 用 GPIO 模拟三线 SPI 的读写函数

LT8920 的 SPI 时序不复杂,但有几个细节:数据在 SCK 上升沿采样,命令字节先发,然后是地址或数据。读寄存器时,SDIO 要在命令字节发完后切换为输入模式。下面是我常用的读写函数:

// LT8920 三线 SPI 读写,基于 STM8 GPIO // PC4 = SCS, PC5 = SCK, PC6 = SDIO #define SCS_L() (PC_ODR &= ~(1<<4)) #define SCS_H() (PC_ODR |= (1<<4)) #define SCK_L() (PC_ODR &= ~(1<<5)) #define SCK_H() (PC_ODR |= (1<<5)) #define SDIO_IN() (PC_DDR &= ~(1<<6), PC_CR1 &= ~(1<<6)) #define SDIO_OUT() (PC_DDR |= (1<<6), PC_CR1 |= (1<<6)) #define SDIO_H() (PC_ODR |= (1<<6)) #define SDIO_L() (PC_ODR &= ~(1<<6)) #define SDIO_READ() ((PC_IDR & (1<<6)) ? 1 : 0) // 写一个字节,MSB 先出 void LT8920_WriteByte(uint8_t data) { uint8_t i; SDIO_OUT(); for(i = 0; i < 8; i++) { SCK_L(); if(data & 0x80) SDIO_H(); else SDIO_L(); data <<= 1; SCK_H(); // 上升沿写入 } SCK_L(); } // 读一个字节,MSB 先入 uint8_t LT8920_ReadByte(void) { uint8_t i, data = 0; SDIO_IN(); for(i = 0; i < 8; i++) { data <<= 1; SCK_H(); if(SDIO_READ()) data |= 0x01; SCK_L(); } return data; }

逻辑说明:写字节时先把 SDIO 设为输出,每个 bit 在 SCK 低电平期间准备好,上升沿被 LT8920 采样。读字节时先把 SDIO 设为输入,在 SCK 高电平期间读取电平。参数上唯一需要调的是延时——如果 STM8 主频较高(比如 16MHz),GPIO 翻转太快可能导致 LT8920 采样不稳,可以在 SCK 翻转之间加几个nop()。我一般先在 8MHz 下跑通,再往上调。

2.3 寄存器读写命令格式

LT8920 的寄存器操作有固定格式:写寄存器是 0x00 开头加 7 位地址,读寄存器是 0x80 开头加 7 位地址。地址是 7 位,所以命令字节的高位区分读写。下面是对外封装的两个函数:

// 写寄存器:命令字节 = 地址(bit7=0),然后写数据 void LT8920_WriteReg(uint8_t addr, uint8_t value) { SCS_L(); LT8920_WriteByte(addr & 0x7F); // 写命令,bit7=0 LT8920_WriteByte(value); SCS_H(); } // 读寄存器:命令字节 = 地址 | 0x80,然后读数据 uint8_t LT8920_ReadReg(uint8_t addr) { uint8_t val; SCS_L(); LT8920_WriteByte(addr | 0x80); // 读命令,bit7=1 val = LT8920_ReadByte(); SCS_H(); return val; }

这里有个容易忽略的点:SCS 拉低后不要立刻发命令,最好等几个微秒让模块准备好。另外,读操作时命令字节发完后,SDIO 要立刻切为输入,中间不能有额外的 SCK 脉冲,否则会多读一个 bit。我一般在LT8920_WriteByte返回后直接调SDIO_IN(),中间不加延时。

3. 寄存器配置:让 LT8920 真正进入收发状态

3.1 上电后的初始化顺序

LT8920 上电后不能直接发数据,必须先配置几个关键寄存器。顺序错了,模块可能完全不响应。我一般按这个流程走:

  1. 拉低 SCS,发软复位命令(如果有)
  2. 配置频点寄存器
  3. 配置数据速率和调制方式
  4. 配置发射功率
  5. 配置包格式和同步字
  6. 切换到接收或发射模式

频点配置是最核心的。LT8920 的频点由一个 16 位寄存器决定,常见公式是:Freq = (RF_CH << 8) | RF_CH_L,实际频率和晶振、分频比有关。以 433MHz 为例,常见配置值是 0x0A5C 左右,但不同模块的晶振可能不同,最好用频谱仪或已知模块对测。

// LT8920 初始化示例,433MHz 频点 void LT8920_Init(void) { // 配置 PC4/PC5/PC6 为输出,PC3 为输入 PC_DDR |= (1<<4) | (1<<5) | (1<<6); PC_CR1 |= (1<<4) | (1<<5) | (1<<6); PC_DDR &= ~(1<<3); // PKT 输入 PC_CR1 &= ~(1<<3); SCS_H(); SCK_L(); // 软复位(如果模块支持) LT8920_WriteReg(0x00, 0x80); delay_ms(10); // 频点设置,433MHz 附近 LT8920_WriteReg(0x06, 0x0A); // 频点高字节 LT8920_WriteReg(0x07, 0x5C); // 频点低字节 // 数据速率,比如 10kbps LT8920_WriteReg(0x08, 0x02); // 发射功率,最大 LT8920_WriteReg(0x09, 0x1F); // 包格式:同步字 + 数据 LT8920_WriteReg(0x0A, 0x40); // 切换到接收模式 LT8920_WriteReg(0x01, 0x01); }

参数说明:频点寄存器 0x06 和 0x07 的具体值需要根据模块晶振计算,上面给的 0x0A5C 是常见 433MHz 模块的经验值,不一定通用。数据速率寄存器 0x08 的值和速率对应关系要看规格书,一般值越小速率越高。发射功率寄存器 0x09 不是所有模块都支持可调,有些固定最大功率。

3.2 收发切换和中断处理

LT8920 的收发切换靠模式寄存器。发数据前切到发射模式,发完切回接收。PKT 引脚在收到数据或发送完成时会翻转,可以接 STM8 的外部中断。我一般用 PKT 下降沿触发中断,在中断里读状态寄存器判断是收还是发完成。

// 发送一个包 void LT8920_SendPacket(uint8_t *buf, uint8_t len) { uint8_t i; // 切到发射模式 LT8920_WriteReg(0x01, 0x00); delay_us(200); // 写数据到 FIFO SCS_L(); LT8920_WriteByte(0x20); // FIFO 写命令 for(i = 0; i < len; i++) LT8920_WriteByte(buf[i]); SCS_H(); // 触发发送 LT8920_WriteReg(0x02, 0x01); delay_ms(5); // 等发送完成 // 切回接收 LT8920_WriteReg(0x01, 0x01); } // PKT 中断处理,PC3 下降沿 #pragma vector = EXTI3_IRQHandler __interrupt void EXTI3_IRQHandler(void) { uint8_t status = LT8920_ReadReg(0x03); if(status & 0x01) { // 收到数据,读 FIFO uint8_t len = LT8920_ReadReg(0x04); // ... 读数据 } // 清中断标志 LT8920_WriteReg(0x03, 0x00); }

逻辑说明:发送时先切模式,再写 FIFO,最后触发。FIFO 写命令 0x20 是常见值,具体看规格书。中断里一定要读状态寄存器并清标志,否则会反复进中断。PKT 引脚的电平极性可以通过寄存器配置,如果中断一直触发,先检查极性设置。

3.3 用寄存器回读验证配置是否生效

配置完不要急着发数据,先回读几个关键寄存器,确认写入的值能读回来。如果读回来全是 0 或全是 0xFF,说明 SPI 时序有问题。我一般写一个简单的自检函数:

// 自检:写一个测试值到测试寄存器,再读回 uint8_t LT8920_SelfTest(void) { LT8920_WriteReg(0x1F, 0xA5); // 假设 0x1F 是可读写测试寄存器 if(LT8920_ReadReg(0x1F) != 0xA5) return 0; // SPI 通信失败 return 1; }

注意:不是所有寄存器都可读写,有些是只读状态寄存器。选一个规格书里标注可读写的寄存器做测试。如果读回值不对,先查 SCK 和 SDIO 的接线,再查时序延时。

4. 避坑记录:LT8920 调试中最容易翻车的五个点

4.1 现象:SPI 读回全是 0xFF,写寄存器没反应

原因:SDIO 方向切换时机不对。LT8920 在读命令字节发完后,SDIO 必须立刻从输出切为输入,如果切换晚了,模块已经把数据驱动到 SDIO 上,MCU 还在输出,会打架。另外,SCK 空闲电平如果是高,也可能导致模块误采样。

解决:在LT8920_WriteByte返回后立刻调SDIO_IN(),中间不加任何延时。SCK 空闲时保持低电平。如果还不行,把 SCK 频率降到 100kHz 以下试。

4.2 现象:模块能读回寄存器,但发数据对方收不到

原因:频点配置不对。LT8920 的频点寄存器值和实际频率的关系受晶振影响,不同批次的模块晶振可能有偏差。另外,数据速率和频偏设置不匹配也会导致收不到。

解决:用两个模块对测,一个发一个收,先用默认频点,确认能通后再改。如果没有频谱仪,可以用一个已知能工作的模块做参考,逐步调整频点寄存器值。数据速率先设低一点,比如 2kbps,容错更高。

4.3 现象:PKT 中断一直触发,进不去主循环

原因:中断标志没清,或者 PKT 极性配置反了。LT8920 的 PKT 引脚在收发完成时会翻转,如果配置成高电平触发但实际是低电平有效,就会一直进中断。

解决:在中断里先读状态寄存器,确认是什么事件,然后写寄存器清标志。PKT 极性通过配置寄存器设置,一般有专门的位控制。如果还不行,先把 PKT 中断关掉,用轮询方式读状态寄存器,确认模块工作正常后再开中断。

4.4 现象:发射时电流很大,但通信距离很短

原因:发射功率寄存器没配对,或者天线匹配没做好。LT8920 的发射功率不是所有模块都可调,有些模块固定最大功率,但天线端的匹配网络如果没调好,大部分功率反射回来,实际辐射很小。

解决:先确认发射功率寄存器是否写进去了,回读确认。天线端如果是 PCB 天线,检查匹配电容电感是否按模块规格书推荐值。如果是外接天线,确认阻抗是 50 欧姆。测试时把两个模块靠近,先确认能通,再拉距离。

4.5 现象:低功耗模式下电流降不下来

原因:LT8920 的休眠模式配置不对,或者 STM8 的 GPIO 没设成低功耗状态。模块在休眠时如果 SCS、SCK 还有电平变化,会额外耗电。

解决:休眠前把 SCS 拉高,SCK 拉低,SDIO 设成输入。STM8 这边把不用的 GPIO 设成输出低或输入上拉,避免浮空。LT8920 的休眠命令写完后等一段时间再测电流,有些模块需要几十毫秒才能进入低功耗状态。

5. 进阶技巧:用 RSSI 和误包率快速判断链路质量

跑通收发只是第一步,实际部署时更关心链路稳不稳。LT8920 有一个 RSSI 寄存器,可以读出当前接收信号的强度,虽然精度不高,但用来判断“有没有信号”和“信号大概多强”足够了。我一般会在接收端加一个统计:每收 100 个包,记录 RSSI 平均值和丢包数,这样不用频谱仪也能大致判断距离和干扰情况。

// 读取 RSSI 并统计误包率 uint8_t rssi_val; uint16_t rx_count = 0, lost_count = 0; void LT8920_OnPacketReceived(void) { rssi_val = LT8920_ReadReg(0x05); // 假设 0x05 是 RSSI 寄存器 rx_count++; // 如果包序号不连续,说明丢包 // ... } // 每 100 个包打印一次 void LT8920_PrintStats(void) { if(rx_count >= 100) { printf("RSSI: %d, Lost: %d\r\n", rssi_val, lost_count); rx_count = 0; lost_count = 0; } }

RSSI 寄存器的地址和换算关系要看规格书,不同模块可能不一样。我一般先近距离测一个基准值,然后拉远看 RSSI 下降趋势,结合丢包率判断临界距离。如果 RSSI 还很高但丢包严重,多半是干扰或频点冲突;如果 RSSI 很低且丢包多,就是距离或天线问题。

另一个实用技巧是动态改速率。近距离时用高速率,远距离时切到低速率,容错更高。LT8920 的速率寄存器可以在运行时改,但改完要重新初始化收发模式。我一般设两档:10kbps 和 2kbps,根据 RSSI 和丢包率自动切。这个逻辑不复杂,但能明显提升实际可用距离。

最后说一个我自己的习惯:每次改完寄存器配置,一定先回读确认,再发数据。很多“模块坏了”的情况,其实只是某个寄存器没写进去。调试射频模块,示波器看 SPI 波形、万用表测供电、两个模块对测,这三样比任何高级工具都管用。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进