资讯动态

基于STM32L与LoRa的环境监测系统设计:从传感器到无线传输

发布时间:2026/9/12 4:38:14 来源:尧图企业网站定制
简介一套面向物联网竞赛的LoRa环境监测系统Keil工程基于STM32系列单片机与LoRa远距离通信技术实时采集温湿度、光照数据并通过按键控制LED交互解决竞赛中环境数据远程采集与低功耗无线传输问题适合备战物联网赛项或希望系统学习LoRa与STM32开发的读者。资源总计650个文件以C源码和头文件为主同时包含LoRaMac协议栈、STM32 HAL驱动、Keil工程配置文件以及编译输出的hex/axf和链接映射文件压缩包约18MB目录划分清晰导入工程即可整体查看、修改与二次开发。目前已有1278人学习资源完整度较高尤其适合作为国赛、省赛前的实战参考。通过阅读源码可掌握LoRa通信参数配置、DHT11/DHT22温湿度读取、光敏传感器采集、OLED屏显示、GPIO按键检测、中断与定时器等知识编译生成的hex文件可烧录至目标板快速验证功能中间文件也有助于排查编译链接问题例如低于阈值点亮LED、按键控制LED闪烁等竞赛要求均可在工程中对照实现。整套工程提供了从底层驱动到无线应用层的完整技术链路是一份可运行、可复用的赛题级参考资源。1. 为什么物联网竞赛里的环境监测都喜欢选LoRa在物联网竞赛里环境监测项目经常被评委追问“节点能传多远、能跑多久”WiFi和蓝牙只能回答几十米、几小时而LoRa环境监测系统的答案通常是“几百米到几公里一颗电池跑几个月”。这套系统用STM32L系列单片机控制DHT11/DHT22温湿度传感器和光敏传感器数据一方面显示在OLED上一方面通过LoRa发送给网关。它覆盖嵌入式硬件、传感器应用、无线通信与人机交互四个竞赛高频考点适合作为物联网竞赛开发主项目也适合改造成毕业设计。如果你是第一次做LoRa项目建议先跑通STM32点灯再按下面的步骤挂模块。这样排查问题时能区分是MCU还是无线模块问题。2. STM32L0/L1低功耗选型与LoRaWAN协议栈集成2.1 为什么是STM32L系列而不是普通F系列做环境监测节点主控的静态功耗决定了电池生命周期。STM32F1系列在低功耗模式下电流仍有十几微安到几十微安而STM32L系列配合RTC待机可以做到3μA以下。LoRa模块在接收状态也有十几毫安发送峰值可能到120mA所以MCU自身的动态功耗不能高。工程文件里出现了stm32l0xx_hal_adc.c和stm32l1xx_hal_i2c.c说明这个工程采用HAL库并且兼容了L0和L1两个系列。STM32L0是Cortex-M0主频最高32MHz适合极低功耗场景L1是Cortex-M3外设更丰富带有硬件RTC和更多定时器适合需要OLED和多个传感器同时工作的场景。如果传感器数量多、通信频繁选L1如果做电池抛点选L0。二者在软件上可以通过条件编译切换HAL库的驱动文件名已经给出了这种物理隔离。具体差异如下表。参数STM32L0STM32L1内核Cortex-M0Cortex-M3最高主频32MHz32MHzFlash范围64-192KB64-512KB待机电流约0.29μA约1.1μA运行电流约3.4mA 32MHz约9mA 32MHz适用场景极简传感器节点OLED多外设节点选型还要注意工作电压。LoRa模块如SX1278推荐3.3V供电STM32L系列支持1.8-3.6V可以直接共用一组电源但要避免模块发送时把MCU电源拉低。2.2 LoRaMac.c与HAL库的分层关系工程里的LoRaMac.c是LoRaWAN协议栈的核心文件负责处理入网请求、维护连接、管理帧序号和重传。HAL库驱动只是底层外设封装。实际写代码时单节点上抛数据只需要SX1278的FIFO直接发送但竞赛中往往要求“组网”能力所以保留LoRaMac.c便于加入认证和加密。常见做法是在主循环里这样初始化LoRa// lora_app.c #include lora_driver.h LoRa_Status_t LoRa_Init(void) { LoRa_Status_t st; // RADIO_RESET_PIN 对应STM32的GPIOA4低电平复位 HAL_GPIO_WritePin(GPIOA, RADIO_RESET_PIN, GPIO_PIN_RESET); HAL_Delay(10); HAL_GPIO_WritePin(GPIOA, RADIO_RESET_PIN, GPIO_PIN_SET); st LoRa_SetFrequency(470000000); // 中国频段CN470竞赛常用 if (st ! LORA_OK) { return st; } LoRa_SetTxPower(14); // 14dBm兼顾功耗与距离 LoRa_SetSpreadingFactor(9); // SF9速率略低但抗干扰强 LoRa_SetBandwidth(125000); // 125kHz带宽 return LORA_OK; }这段代码先复位LoRa模块然后配置频率、发射功率、扩频因子和带宽。470MHz是中国LoRa常用的免授权频段14dBm约25mW对电池友好扩频因子越高灵敏度越好但空中传输时间越长。SF9在125kHz带宽下实际速率约2.7kbps足够上传感测数据。2.3 低功耗进入与唤醒配置节点采集间隔通常是分钟级因此MCU大部分时间应停在STOP模式。使用HAL库时进入STOP模式的代码是这样void Enter_StopMode(void) { __HAL_RCC_PWR_CLK_ENABLE(); if (HAL_PWREx_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI) ! HAL_OK) { Error_Handler(); } // 从STOP唤醒后会先执行SystemClock_Config() SystemClock_Config(); }唤醒源可以配置为RTC周期唤醒或按下按键时使用EXTI中断。注意LoRa模块的DIO0引脚也可以接到MCU的EXTI上当收到网关下行的ACK或命令时唤醒MCU。唤醒后必须重新配置时钟因为STOP模式下主时钟被关闭了。很多学生第一次做低功耗时唤醒后串口乱码就是漏了SystemClock_Config()。功耗不是只靠代码就能压下去的。LoRa模块在发送时如果电源退耦电容不够会瞬间掉压导致MCU重启。我一般会在模块电源脚就近放一个100μF电解电容并联0.1μF陶瓷电容并在LoRa发送期间关掉ADC和OLED电源让电流峰值尽量集中。3. 温湿度与光照采集DHT11/光敏电阻的驱动写法与参数校正3.1 温湿度数据时序DHT11单总线协议DHT11用一根数据线完成通信时序要求微妙级精确。其数据格式是40bit湿度整数、湿度小数、温度整数、温度小数、校验和。比如接收到0011 0101 0000 0000 0001 1000 0000 0001 0100 1111第一位湿度整数0x3553%RH温度整数0x1824°C校验和0x4F0x350x000x180x01。如果校验不对这一帧直接丢弃。HAL_Delay最小单位是1ms不能满足DHT11几微秒信号的读取。常见做法是用定时器提供微秒延时或者使用空循环。// dht11.c void DHT11_Delay_US(uint32_t us) { uint32_t ticks us * (SystemCoreClock / 1000000) / 4; while (ticks--) { __NOP(); } } uint8_t DHT11_ReadByte(void) { uint8_t byte 0; for (int i 0; i 8; i) { while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET); DHT11_Delay_US(36); if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { byte (byte 1) | 0x01; while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET); } else { byte byte 1; } } return byte; }这段代码里读取每一位时先等待数据线拉低再延时36μs如果此时数据线仍为高说明是高电平“1”否则为“0”。DHT11规定50μs高电平为126-28μs高电平为0延时36μs正处于区间中。SystemCoreClock在主频32MHz时空循环除以4是为了补偿函数调用与__NOP()的时钟偏差实际以逻辑分析仪校准为准。3.2 光照阈值从ADC原始值到80 lux判断光敏电阻通过分压给STM32的ADC采样输出电压随光照变化。如果光敏电阻放在上端、固定电阻放在下端光照增强时光敏电阻阻值下降ADC电压降低。项目要求光照低于80 lux时点亮LED1需要先建立一个照度-电压映射。常见做法是用公式lux 300 / (ADC_Volt / 3.3 * R_fixed) - offset实际竞赛中最简单的标定方法是拿手机照度计App对比几个点把ADC值存表查接近值。下面代码使用滑动滤波和阈值判断// light.c #define LIGHT_THRESHOLD_ADC 1200 // 对应80 lux需实测调整 static uint32_t adc_buf[8]; uint32_t Light_GetAverage(void) { uint32_t sum 0; for (int i 0; i 8; i) { adc_buf[i] HAL_ADC_GetValue(hadc); HAL_Delay(2); sum adc_buf[i]; } return sum / 8; } void Light_Process(void) { if (Light_GetAverage() LIGHT_THRESHOLD_ADC) { HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_RESET); // 低电平点亮 } else { HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_SET); } }阈值1200是假设ADC参考电压3.3V、12位分辨率、F/R1.5的粗略折算。实际中应根据环境光强和实际电阻网络标定建议先在完全遮光下记录ADC值再在正常日光下记录80 lux通常出现在二者之间的十几分之一处。8次采样的目的不是提高精度而是滤掉市电频闪和LoRa发射时的射频干扰。环境典型lux光敏电阻阻值参考ADC近似值(3.3V/12bit)昏暗房间50 lux10kΩ约90080 lux阈值80 lux8kΩ约1200办公室300 lux2kΩ约2400户外阴影1000 lux800Ω约32003.3 传感器自检与坏值处理采集到的温湿度如果连续3次校验失败就不该继续往外发否则网关会拿到-40°C这种假数据。这个系统在设计时可以增加一个异常计数连续失败5次就通过LoRa发送一个0x01错误码。注意不要在主循环里频繁读取DHT11建议5秒一次否则传感器自身发热会影响温度也会让LoRa的发送间隔无法拉长。4. 按键、LED与OLED交互GPIO中断、定时闪烁与人机界面4.1 Key2/Key3外部中断与延时消抖物理按键在按下瞬间会产生0.1-1ms的抖动如果不处理一次按压会被识别成多次触发。这里Key2用于打开LED2闪烁Key3用于关闭并熄灭。工程中采用GPIO上升沿检测中断回调里只做标志位记录具体逻辑放到主循环执行。因为定时器中断和外部中断共享优先级如果直接在EXTI回调里长时间延时会导致系统时钟调度异常。// gpio_callback.c volatile uint8_t key2_pressed 0; volatile uint8_t key3_pressed 0; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin KEY2_PIN) { HAL_Delay(10); if (HAL_GPIO_ReadPin(KEY2_PORT, KEY2_PIN) GPIO_PIN_SET) { key2_pressed 1; } } else if (GPIO_Pin KEY3_PIN) { HAL_Delay(10); if (HAL_GPIO_ReadPin(KEY3_PORT, KEY3_PIN) GPIO_PIN_SET) { key3_pressed 1; } } }按键行为可以这样定义按键功能实现方式Key2LED2开始闪烁置位blink_enable标志Key3关闭LED2并停止闪烁清除标志强制置高电平注意在EXTI回调里使用HAL_Delay并不推荐更好的做法是配置一个软件定时器计数在10ms后读取一次按键电平。但竞赛代码用延时消抖也能接受前提是LoRa发送不在中断里占用同样优先级。主循环里根据标志执行启动或停止if (key2_pressed) { key2_pressed 0; led2_blink_enable 1; } if (key3_pressed) { key3_pressed 0; led2_blink_enable 0; HAL_GPIO_WritePin(LED2_PORT, LED2_PIN, GPIO_PIN_SET); // 熄灭 }4.2 定时器中断让LED2闪烁而不阻塞主循环有人喜欢在主循环里用翻转后HAL_Delay(500)实现闪烁但这会导致采集、LoRa发送全都卡住。正确做法是设置一个100ms周期的TIM定时器中断每次翻转一次LED2达到500ms周期闪动。在主循环只维护一个blink_enable标志让主流程始终能处理传感器和无线通信。void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim htim3) { if (led2_blink_enable) { HAL_GPIO_TogglePin(LED2_PORT, LED2_PIN); } } }TIM3的预分频和周期可以用以下参数计算如果主频32MHz、产生100ms中断预分频32000-1计数器1000-1。这样每秒只触发10次CPU占用可以忽略。注意定时器初始化要在开启更新中断后调用HAL_TIM_Base_Start_IT(htim3)否则中断标志永远不触发。4.3 OLED数据布局与局部刷新OLED屏幕通过I2C或SPI与STM32连接SSD1306是应用最广的控制器。这个项目的显示界面建议固定刷三块区域左上角显示温度和湿度右下角显示光照lux底部一行显示LoRa发送状态。如果每秒刷新全屏0.96寸OLED要占几百毫秒时间消耗不少电流。更好的做法是把数值变化部分单独开窗刷新只更新变化的位置。// oled_display.c void OLED_Update_Environment(void) { char line[16]; snprintf(line, sizeof(line), T:%02d.%d C, temp_int, temp_dec); OLED_SetCursor(0, 0); OLED_ShowString(line); snprintf(line, sizeof(line), H:%02d.%d%%, humi_int, humi_dec); OLED_SetCursor(0, 2); OLED_ShowString(line); snprintf(line, sizeof(line), L:%4dlux, light_lux); OLED_SetCursor(0, 4); OLED_ShowString(line); }该函数通过OLED_SetCursor定位到行和列然后覆盖原位置字符串。因为OLED没有背景色如果新字符串比旧的短需要先往旧位置写空格再显示新值否则会留下残影。按键交互在这个环境监测系统中不只是灯效它模拟了真实产品里的“静默模式”和“巡检模式”。Key2开启闪烁代表设备进入主动报警状态Key3关闭则回到待机配合OLED上的状态提示这样的交互逻辑在物联网竞赛答辩时容易加分。5. LoRa数据上抛与验收调试从串口日志到距离测试当传感器数据采集无问题后难点在LoRa怎么稳定上抛。竞赛最常见的坑是节点和网关在不同频段。国内LoRa跑CN470频段但很多评测板默认是EU868必须先写死频率。发送前可以用串口打印当前配置printf([LoRa] Freq%uHz, SF%d, BW%d, Pwr%ddBm\r\n, lora_freq, lora_sf, lora_bw, lora_pwr);如果串口日志上打印的Freq和你网关配置不一致先检查是否有外部晶振频率校准。SX1278对晶振频率偏差敏感如果晶振偏差超过20ppm接收灵敏度会明显下降表现是近距离能收、远距离全丢包。验收测试建议从室内近距离开始再用手机打开地图定位以100米、300米、500米拉距。同时记录网关收到的RSSI和丢包率。一个可用的经验同一位置如果RSSI波动超过6dBm通常是有同频干扰或者天线接头松动。不用SMA外接天线的情况下板载天线附近不要走覆铜这会影响阻抗。进阶一点的做法是发送间隔自适应。平时5分钟一帧当光照连续3次低于阈值时切换为10秒一帧让网关在1分钟内收到掉进暗环境的报警。实现上用一个计数器和当前状态主循环里判断if (light_alert) { if ((tick_ms - last_send_ms) 10000) { LoRa_SendData(rxbuf, len); last_send_ms tick_ms; } } else if ((tick_ms - last_send_ms) 300000) { LoRa_SendData(rxbuf, len); last_send_ms tick_ms; }这个技巧能兼顾电池寿命和报警及时性。竞赛验收时用手遮住光敏传感器3秒内网关收到报警帧这是一个很有说服力的演示动作。最后别忘了日志里的时间戳。在串口发送前把RTC时间加进printf这样后面分析丢包和时延会非常直观。LoRa本身是异步传输没有统一时钟节点RTC只要保证相对时间准确即可。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价