news 2026/10/28 7:29:40

STM32F103硬件SPI驱动SX1278 LoRa通信实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103硬件SPI驱动SX1278 LoRa通信实战

简介:这份资源面向嵌入式开发初学者与物联网无线通信方向的工程师,提供基于STM32F103单片机通过SPI接口驱动SX1278 LoRa模块的完整软件设计源代码,帮助读者快速搭建远距离低功耗无线通信原型,解决从硬件初始化到数据收发全流程的实现难题。压缩包共442个文件,约14.89MB,以C语言源文件与头文件为核心,辅以编译生成的o、d、crf等中间文件,以及uvprojx、uvoptx工程配置、hex烧录文件、map映射与lst列表等,覆盖STM32标准外设库中tim、flash、rcc等模块,目录结构清晰,便于按功能检索。目前已有106人学习下载。代码涵盖SPI接口初始化、SX1278频率带宽扩频因子等参数配置、数据包收发、中断响应与错误恢复处理,并预留调试信息与状态机、电源管理等扩展空间,可作为可复用框架直接移植到远程监测、智能监控等项目中,有效缩短研发周期。

1. 从一块 SX1278 说起:STM32F103 的 SPI 驱动到底难在哪

很多人第一次把 SX1278 焊到板子上,满心期待能收到几公里外的数据,结果串口打印出来的全是 0x00 或者 0xFF。翻遍手册、换了几块模块,最后发现不是芯片坏了,而是 SPI 的时序和寄存器访问方式没搞对。SX1278 这颗 LoRa 收发器,本质上是一个挂在 SPI 总线上的寄存器堆,STM32F103 通过 SPI 读写它的配置寄存器、FIFO 和状态机,任何一步时序错位都会让通信直接趴窝。这个标题讲的就是这套软件设计:用 STM32F103 的硬件 SPI 外设,驱动 SX1278 完成初始化、模式切换、数据收发和中断处理。适合正在做 LoRa 点对点通信、想从零搭一套稳定驱动、或者手里有源代码但跑不通的嵌入式工程师。接下来我会把寄存器访问、SPI 配置、状态机切换和实际收发流程拆开讲,让你能照着复现,也能看懂那些“玄学”问题出在哪。

2. SX1278 的寄存器访问机制:为什么不能直接连续读

2.1 寄存器地址与读写位的关系

SX1278 的 SPI 接口不是标准的“地址+数据”两段式,它把读写方向嵌在地址字节的最高位。写操作时,地址字节的 bit7 为 0,后面跟一个数据字节;读操作时,地址字节的 bit7 为 1,后面跟一个空字节用来移出数据。这个细节如果搞错,写进去的值会跑到错误的寄存器,读出来的全是无效数据。常见做法是定义两个宏,把读写地址分开处理:

// SX1278 寄存器读写地址宏 #define SX1278_REG_WRITE(addr) ((addr) & 0x7F) // bit7=0 表示写 #define SX1278_REG_READ(addr) ((addr) | 0x80) // bit7=1 表示读 // 单寄存器写 void SX1278_WriteReg(uint8_t addr, uint8_t data) { SX1278_CS_LOW(); // 片选拉低 SPI_TransferByte(SX1278_REG_WRITE(addr)); // 发送写地址 SPI_TransferByte(data); // 发送数据 SX1278_CS_HIGH(); // 片选拉高 } // 单寄存器读 uint8_t SX1278_ReadReg(uint8_t addr) { uint8_t val; SX1278_CS_LOW(); SPI_TransferByte(SX1278_REG_READ(addr)); // 发送读地址 val = SPI_TransferByte(0x00); // 发送空字节换回数据 SX1278_CS_HIGH(); return val; }

逻辑说明:SPI_TransferByte 是一个阻塞式收发函数,内部等待 TXE 和 RXNE 标志。片选信号必须全程拉低,中间不能有抖动,否则 SX1278 会认为一次传输结束。参数说明:addr 是 0x00 到 0x7F 的寄存器偏移,data 是要写入的 8 位值。读操作时第二个字节发什么无所谓,因为 SX1278 已经在第一个字节的时钟沿把数据推到了 MISO 线上。

2.2 突发读写与 FIFO 访问

SX1278 的 FIFO 是 64 字节,读写 FIFO 时地址不会自动递增,需要手动指定每次访问的地址。但有些寄存器组支持连续读写,比如配置寄存器从 0x00 开始连续写多个值。常见做法是写一个突发写函数,循环调用单寄存器写,或者直接在一个片选周期内连续发送地址和数据。注意 FIFO 的读写指针由 RegFifoAddrPtr 控制,每次读写前要设置好指针位置,否则数据会覆盖或者读空。

// 突发写多个寄存器 void SX1278_BurstWrite(uint8_t addr, uint8_t *buf, uint8_t len) { SX1278_CS_LOW(); SPI_TransferByte(SX1278_REG_WRITE(addr)); for (uint8_t i = 0; i < len; i++) { SPI_TransferByte(buf[i]); } SX1278_CS_HIGH(); } // 读 FIFO 数据 void SX1278_ReadFifo(uint8_t *buf, uint8_t len) { SX1278_WriteReg(0x0D, 0x00); // RegFifoAddrPtr = 0 SX1278_CS_LOW(); SPI_TransferByte(SX1278_REG_READ(0x00)); // RegFifo 地址为 0x00 for (uint8_t i = 0; i < len; i++) { buf[i] = SPI_TransferByte(0x00); } SX1278_CS_HIGH(); }

逻辑说明:突发写时片选保持低电平,地址只发一次,后续字节依次写入递增的寄存器地址。读 FIFO 前必须把 RegFifoAddrPtr 设为 0,否则会从上次的位置继续读。参数说明:len 不能超过 FIFO 剩余数据量,否则会读到无效字节。实际项目中,我一般会在读 FIFO 前先读 RegRxNbBytes 获取有效载荷长度,再决定读多少。

3. STM32F103 的 SPI 外设配置:从 GPIO 到时钟极性

3.1 硬件 SPI 的引脚与模式设置

STM32F103 的 SPI1 挂在 APB2 总线上,最高时钟 72MHz,SPI2 挂在 APB1 上最高 36MHz。SX1278 的 SPI 最高支持 10MHz,所以分频系数选 8 或 16 比较稳妥。常见做法是用 SPI1,引脚选 PA5(SCK)、PA6(MISO)、PA7(MOSI),片选单独用一个 GPIO 比如 PA4 软件控制。SPI 模式要设成 CPOL=0、CPHA=0,也就是空闲低电平、第一个时钟沿采样,这和 SX1278 的时序要求一致。

// SPI1 初始化配置 void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; SPI_InitTypeDef SPI_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // SCK/MOSI 复用推挽输出 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // MISO 浮空输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStruct); // 片选 PA4 推挽输出 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_SetBits(GPIOA, GPIO_Pin_4); // 默认拉高 SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStruct.SPI_Mode = SPI_Mode_Master; SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; // 72/8=9MHz SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &SPI_InitStruct); SPI_Cmd(SPI1, ENABLE); }

逻辑说明:SPI_NSS 设为软件模式,因为片选由 GPIO 手动控制,这样可以在多从机场景下灵活切换。SPI_BaudRatePrescaler_8 对应 9MHz,在 SX1278 的 10MHz 上限内。参数说明:CPOL_Low 和 CPHA_1Edge 组合成模式 0,是 SX1278 唯一支持的模式。如果设成模式 3,读出来的数据会整体偏移一位。

3.2 收发函数与超时处理

SPI 收发函数不能死等,否则总线故障时程序会卡死。常见做法是加一个超时计数器,超过一定循环次数就返回错误。发送时先等 TXE 置位,写入数据,再等 RXNE 置位读回数据。接收时发一个空字节产生时钟,然后读 DR。

// SPI 单字节收发,带超时 uint8_t SPI_TransferByte(uint8_t txData) { uint32_t timeout = 0xFFFF; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) { if (--timeout == 0) return 0xFF; // 超时返回 } SPI_I2S_SendData(SPI1, txData); timeout = 0xFFFF; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) { if (--timeout == 0) return 0xFF; } return SPI_I2S_ReceiveData(SPI1); }

逻辑说明:TXE 表示发送缓冲区空,可以写入下一个字节;RXNE 表示接收缓冲区非空,可以读取。超时值 0xFFFF 在 72MHz 下大约几毫秒,足够完成一次字节传输。参数说明:txData 是要发送的字节,返回值是同时接收到的字节。如果只发不收,返回值忽略即可,但必须读 DR 否则 RXNE 会一直置位导致下一次传输出错。

3.3 片选信号的时序控制

片选信号 SX1278_CS_LOW 和 SX1278_CS_HIGH 之间要保证足够的建立和保持时间。SX1278 要求片选下降沿到第一个时钟沿至少 50ns,片选上升沿在最后一个时钟沿之后至少 50ns。STM32 的 GPIO 翻转速度在 50MHz 模式下约 20ns,直接操作寄存器就能满足。但如果在片选之间插入了其他中断或者长循环,可能导致时序拉长,SX1278 会复位内部状态机。我一般会在片选操作前后加 __NOP() 或者用寄存器级操作确保速度。

#define SX1278_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define SX1278_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4)

逻辑说明:这两个宏直接操作 BSRR 寄存器,翻转时间在纳秒级。参数说明:GPIOA 和 GPIO_Pin_4 根据实际硬件连接修改。如果片选接在其他端口,改这两个参数即可。

4. SX1278 初始化与模式切换:从睡眠到接收的完整流程

4.1 上电后的寄存器配置顺序

SX1278 上电后默认处于睡眠模式,所有寄存器都是默认值。初始化要按顺序来:先切到睡眠模式确保配置可写,然后设置频率、带宽、扩频因子、编码率,再切到待机模式,最后切到接收或发送模式。顺序错了会导致配置不生效,比如在待机模式下写频率寄存器会被忽略。

// SX1278 初始化 void SX1278_Init(void) { SX1278_WriteReg(0x01, 0x80); // 进入睡眠模式 Delay_ms(10); // 设置频率 433MHz,RegFrf = 433000000 / 61.035 = 0x6C8000 SX1278_WriteReg(0x06, 0x6C); // RegFrfMsb SX1278_WriteReg(0x07, 0x80); // RegFrfMid SX1278_WriteReg(0x08, 0x00); // RegFrfLsb // 设置带宽 125kHz,扩频因子 7,编码率 4/5 SX1278_WriteReg(0x1D, 0x72); // RegModemConfig1 SX1278_WriteReg(0x1E, 0x74); // RegModemConfig2 // 设置前导码长度 8 SX1278_WriteReg(0x20, 0x08); // RegPreambleMsb SX1278_WriteReg(0x21, 0x08); // RegPreambleLsb // 设置同步字 0x12 SX1278_WriteReg(0x39, 0x12); // RegSyncWord // 设置 FIFO 基地址 SX1278_WriteReg(0x0E, 0x00); // RegFifoTxBaseAddr SX1278_WriteReg(0x0F, 0x00); // RegFifoRxBaseAddr // 映射 DIO0 为 RxDone SX1278_WriteReg(0x40, 0x00); // RegDioMapping1 SX1278_WriteReg(0x01, 0x81); // 进入待机模式 Delay_ms(5); }

逻辑说明:RegFrf 的计算公式是 Frf = Freq * 2^19 / 32e6,433MHz 对应 0x6C8000。RegModemConfig1 的 0x72 表示带宽 125kHz、编码率 4/5、显式报头。RegModemConfig2 的 0x74 表示扩频因子 7、CRC 开启。参数说明:频率、带宽、扩频因子要根据实际法规和距离需求调整,扩频因子越大距离越远但速率越低。

4.2 模式切换与状态查询

SX1278 的模式由 RegOpMode 的 bit7-5 控制,000 是睡眠,001 是待机,011 是发送,101 是接收。切换模式后要等 RegIrqFlags 的 ModeReady 位置位,或者直接延时几毫秒。常见做法是写一个模式切换函数,带状态查询。

// 切换 SX1278 模式 void SX1278_SetMode(uint8_t mode) { uint8_t opMode = SX1278_ReadReg(0x01); opMode = (opMode & 0x1F) | (mode << 5); SX1278_WriteReg(0x01, opMode); while ((SX1278_ReadReg(0x01) & 0x80) == 0); // 等待模式就绪 } // 进入接收模式 void SX1278_RxMode(void) { SX1278_SetMode(0x05); // 接收模式 SX1278_WriteReg(0x40, 0x00); // DIO0 映射 RxDone }

逻辑说明:RegOpMode 的 bit7 是 LongRangeMode,LoRa 模式下必须为 1。SetMode 函数先读回当前值,保留低 5 位,再写入新的模式位。参数说明:mode 取值 0x00 睡眠、0x01 待机、0x03 发送、0x05 接收。等待 ModeReady 的循环在极端情况下可能死循环,实际项目中我会加超时计数。

4.3 中断与 DIO 引脚处理

SX1278 的 DIO0 到 DIO5 可以映射不同的中断源,比如 RxDone、TxDone、CadDone。常见做法是把 DIO0 映射为 RxDone,接到 STM32 的外部中断引脚,在中断服务函数里读 RegIrqFlags 判断中断类型,然后清标志。注意 RegIrqFlags 是读清除的,读一次就自动清零。

// DIO0 外部中断服务函数 void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { uint8_t irqFlags = SX1278_ReadReg(0x12); // RegIrqFlags if (irqFlags & 0x40) { // RxDone uint8_t len = SX1278_ReadReg(0x13); // RegRxNbBytes SX1278_ReadFifo(rxBuf, len); rxFlag = 1; // 通知主循环 } if (irqFlags & 0x08) { // TxDone txFlag = 1; } EXTI_ClearITPendingBit(EXTI_Line0); } }

逻辑说明:RegIrqFlags 的 bit6 是 RxDone,bit3 是 TxDone。读 RegRxNbBytes 获取有效载荷长度,然后从 FIFO 读出数据。参数说明:rxBuf 是接收缓冲区,长度要大于最大可能载荷。中断里不要做耗时操作,读 FIFO 要快,否则下一包数据会覆盖。

5. 避坑与排查:SX1278 驱动中最容易翻车的 5 个点

5.1 现象:读寄存器全返回 0x00 或 0xFF

原因:SPI 模式设置错误,或者 MISO 引脚没有正确配置为浮空输入。SX1278 在片选无效时 MISO 是高阻态,如果 STM32 的 MISO 配成了推挽输出,会拉低或拉高总线。解决:检查 GPIO_Mode_IN_FLOATING,用示波器看 MISO 在片选有效时是否有数据翻转。

5.2 现象:初始化后无法进入接收模式

原因:RegOpMode 的 LongRangeMode 位没有置 1,或者频率寄存器写入顺序不对。SX1278 在 FSK 模式下频率计算方式不同,LoRa 模式必须先设 LongRangeMode。解决:在写频率之前先写 RegOpMode 为 0x80,确保进入 LoRa 睡眠模式。

5.3 现象:发送正常但接收不到数据

原因:DIO0 映射错误,或者中断没有使能。RegDioMapping1 的 bit7-6 控制 DIO0,00 是 RxDone,01 是 TxDone。如果设成了 TxDone,接收完成不会触发中断。解决:接收前写 RegDioMapping1 为 0x00,发送前写 0x40。

5.4 现象:通信距离短,丢包严重

原因:扩频因子和带宽配置不匹配,或者天线匹配网络没调好。扩频因子 7 配带宽 125kHz 时灵敏度约 -123dBm,如果换成扩频因子 12 能到 -137dBm。解决:优先调大扩频因子,降低带宽,同时检查天线的驻波比。

5.5 现象:SPI 通信偶尔出错,重启后恢复

原因:片选信号在传输过程中被中断打断,或者 SPI 时钟太快导致建立时间不足。STM32F103 的 SPI 在 9MHz 时对布线敏感,杜邦线连接容易受干扰。解决:降低分频系数到 16 或 32,缩短杜邦线,片选操作前后加 __NOP() 确保时序。

6. 进阶技巧:用 DMA 和状态机把 LoRa 驱动做成非阻塞

6.1 SPI DMA 收发配置

轮询方式收发数据会占用大量 CPU 时间,尤其在扩频因子 12、带宽 125kHz 时,一包数据空中时间超过 1 秒。用 DMA 可以把 SPI 收发交给硬件,CPU 去处理其他任务。STM32F103 的 SPI1 有 TX 和 RX 两个 DMA 通道,配置成单次传输模式,每次收发前重新设置长度。

// SPI1 TX DMA 配置 void SPI1_TxDMA_Init(void) { DMA_InitTypeDef DMA_InitStruct; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR; DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)txBuf; DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStruct.DMA_BufferSize = 0; // 每次传输前设置 DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; DMA_InitStruct.DMA_Priority = DMA_Priority_High; DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel3, &DMA_InitStruct); }

逻辑说明:DMA_DIR_PeripheralDST 表示从内存搬到外设,适合发送。接收时改成 DMA_DIR_PeripheralSRC,内存地址换成 rxBuf。参数说明:DMA_BufferSize 在每次传输前用 DMA_SetCurrDataCounter 设置,传输完成后 DMA 通道自动关闭,需要重新使能。

6.2 状态机设计:空闲、发送、接收、错误

把 LoRa 驱动做成状态机,主循环轮询状态,中断只负责置标志。状态包括 IDLE、TX、RX、ERROR。发送时切到 TX 状态,启动 DMA,等 TxDone 中断置位后切回 IDLE。接收时切到 RX 状态,等 RxDone 中断读 FIFO。错误状态处理超时和 CRC 错误。

typedef enum { STATE_IDLE, STATE_TX, STATE_RX, STATE_ERROR } LoRaState_t; LoRaState_t loraState = STATE_IDLE; void LoRa_Process(void) { switch (loraState) { case STATE_IDLE: if (txRequest) { SX1278_WriteReg(0x0D, 0x00); // FIFO 指针归零 SX1278_BurstWrite(0x00, txBuf, txLen); // 写 FIFO SX1278_WriteReg(0x22, txLen); // RegPayloadLength SX1278_SetMode(0x03); // 发送模式 loraState = STATE_TX; } else if (rxRequest) { SX1278_RxMode(); loraState = STATE_RX; } break; case STATE_TX: if (txFlag) { txFlag = 0; loraState = STATE_IDLE; } break; case STATE_RX: if (rxFlag) { rxFlag = 0; loraState = STATE_IDLE; } break; case STATE_ERROR: SX1278_Init(); // 重新初始化 loraState = STATE_IDLE; break; } }

逻辑说明:状态机把阻塞操作拆成非阻塞步骤,主循环可以同时处理按键、串口等其他任务。参数说明:txRequest 和 rxRequest 是外部置位的请求标志,txFlag 和 rxFlag 由中断置位。超时处理可以在 STATE_TX 和 STATE_RX 里加计数器,超过一定时间没收到中断就切到 STATE_ERROR。

6.3 用 RSSI 和 SNR 做链路质量评估

SX1278 在接收完成后会把 RSSI 和 SNR 存在 RegPktRssiValue 和 RegPktSnrValue 里。RSSI 的换算公式是 RSSI = -157 + RegPktRssiValue(高频口)或 -164 + RegPktRssiValue(低频口)。SNR 是带符号的 8 位值,除以 4 得到 dB。实际部署时,我习惯在每包数据后面打印这两个值,用来判断链路余量和干扰情况。

寄存器地址含义换算
RegPktRssiValue0x1A数据包 RSSI-157 + 值
RegRssiValue0x1B当前信道 RSSI-157 + 值
RegPktSnrValue0x19数据包 SNR值 / 4

逻辑说明:RSSI 反映信号强度,SNR 反映信号质量。SNR 低于 -5dB 时误包率会明显上升。参数说明:高频口指 868/915MHz,低频口指 433MHz。实际项目中,我会把 RSSI 和 SNR 通过串口输出,用上位机画曲线观察。

6.4 低功耗场景下的轮询接收

如果设备用电池供电,不能一直开着接收。常见做法是让 SX1278 在睡眠和接收之间周期切换,用 Cad(信道活动检测)模式快速判断信道是否有信号。Cad 模式比接收模式省电,检测到前导码后再切到接收模式。STM32F103 本身也可以进 STOP 模式,用 RTC 定时唤醒。

// 周期唤醒接收 void LoRa_LowPowerRx(void) { SX1278_SetMode(0x00); // 睡眠 RTC_SetAlarm(1000); // 1 秒后唤醒 __WFI(); // 进 STOP 模式 SX1278_SetMode(0x05); // 接收模式 Delay_ms(50); // 接收窗口 if (rxFlag) { rxFlag = 0; // 处理数据 } SX1278_SetMode(0x00); // 回睡眠 }

逻辑说明:RTC 闹钟唤醒后打开接收窗口,窗口长度要大于前导码时间。参数说明:接收窗口 50ms 在扩频因子 7、带宽 125kHz 时能覆盖 8 个前导码符号。如果扩频因子更大,窗口要相应加长。

这套驱动我前后调了三个月,最深的教训是:SX1278 的寄存器读写看着简单,但时序和模式切换的坑一个接一个。示波器比打印日志管用,片选和时钟的波形一看就知道问题在哪。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 7:29:26

JavaScript本地代理:核心概念与OpenCode实战

1. 本地代理&#xff08;JavaScript脚本&#xff09;的核心概念与应用场景在当今前端开发领域&#xff0c;本地代理已成为开发者日常工作中不可或缺的工具。简单来说&#xff0c;本地代理就是运行在你本机上的一个中间层程序&#xff0c;它能够拦截、修改或转发网络请求。不同于…

作者头像 李华
网站建设 2026/10/28 7:29:25

基于STM32F103 SPI驱动SX1278 LoRa模块软件设计源代码详解

简介&#xff1a;这份资源面向嵌入式开发初学者与物联网无线通信方向的工程师&#xff0c;提供基于STM32F103单片机通过SPI接口驱动SX1278 LoRa模块的完整软件设计源代码&#xff0c;帮助读者快速搭建远距离、低功耗的无线数据传输方案&#xff0c;适用于智能监控、远程数据采集…

作者头像 李华
网站建设 2026/10/28 7:28:41

Matlab LBM多孔介质流动仿真:D2Q9模型与边界条件实现

简介&#xff1a;这份资源提供了一套基于 Matlab 的 LBM 流体仿真代码&#xff0c;用于模拟流体流经多孔介质的流动过程&#xff0c;面向计算机、电子信息工程、数学等专业的大学生及科研人员&#xff0c;可服务于课程设计、期末大作业与毕业设计等场景。资源包共 11 个文件&am…

作者头像 李华
网站建设 2026/10/28 7:27:36

OpenCV+QT人脸识别考勤系统C++实战指南

简介&#xff1a;这是一套面向计算机专业本科生及初学者的C毕业设计级项目资源&#xff0c;基于OpenCV与Qt双框架实现端到端的人脸识别考勤系统&#xff0c;解决课程设计、毕业设计及实训项目中图像处理GUI集成实际业务逻辑落地的典型需求。压缩包共134个文件&#xff0c;涵盖4…

作者头像 李华
网站建设 2026/10/28 7:27:06

AI搜索流量红利,江西中小商家如何以小博大?

“江西中小商家现在抢AI搜索流量&#xff0c;真的太晚了吗&#xff1f;”这个问题我最近被问了不下十次。我的回答一直很直接&#xff1a;晚的不是入场时间&#xff0c;而是大多数人还没看懂AI搜索到底怎么分配流量。本地生活服务、电商、工厂转型&#xff0c;这三类商家在AI搜…

作者头像 李华
网站建设 2026/10/28 7:27:05

AI进前端课堂三天实录:从教代码到教决策

上周我在自己的Web前端课上做了一个实验&#xff1a;连续三天&#xff0c;把学生练习和作业环节里所有“代码不会写”的问题&#xff0c;全部交给AI来解答&#xff0c;我退到一边只做规则制定、结果验收和问题点评。三天后课堂面貌的变化速度远超我的预期——学生开始给AI“下需…

作者头像 李华