第一次接触STM32开发的朋友可能会被各种开发工具和配置搞得头晕。我刚开始用Keil MDK时,光是安装驱动就折腾了半天。这里分享一个快速上手的配置方案:
硬件准备:你需要一块STM32开发板(推荐F103C8T6核心板)、USB转串口工具、杜邦线若干。我用的这块板子自带CH340芯片,省去了额外买调试器的钱。
软件安装:
安装时有个小坑要注意:Keil的芯片包需要单独下载。有次我编译时一直报错,后来发现是没装F1系列的DFP包。建议在Keil的Pack Installer里直接搜索安装。
c复制#include "stm32f1xx_hal.h"
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1) {
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
HAL_Delay(500);
}
}
这个例子用HAL库实现了PB8引脚LED的闪烁。新手常遇到的问题是忘记在CubeMX里配置时钟树,导致程序跑不起来。我第一次就栽在这,后来发现时钟配置就像给芯片搭"骨架",没它其他功能都动不了。
LoRa模块的接线看似简单,但实际调试时我踩过不少坑。以常见的SX1278模块为例:
硬件连接表:
| STM32引脚 | LoRa模块引脚 | 注意事项 |
|---|---|---|
| PA4 | NSS | SPI片选 |
| PA5 | SCK | SPI时钟 |
| PA6 | MISO | 主入从出 |
| PA7 | MOSI | 主出从入 |
| PB0 | RST | 复位引脚 |
| PB1 | DIO0 | 中断引脚 |
有次调试发现数据死活发不出去,后来用逻辑分析仪抓波形才发现SPI时钟极性配反了。这里分享一个快速验证SPI是否正常的方法:
c复制// 发送测试数据
uint8_t test_cmd[] = {0x42, 0x00};
HAL_SPI_Transmit(&hspi1, test_cmd, 2, 100);
// 读取版本号
uint8_t version = 0;
HAL_SPI_TransmitReceive(&hspi1, &test_cmd[0], &version, 1, 100);
if(version != 0x12) { // SX1276的版本号
printf("SPI通信异常!\n");
}
这个通用库最让我惊喜的是它封装了复杂的射频配置,像发送数据只需要两行代码:
c复制uint8_t data[] = "Hello LoRa";
Radio.Send(data, sizeof(data));
但想要用好这些API,需要理解几个关键参数:
c复制NS_RadioInit(433000000, 17, 1000, 1000);
c复制uint8_t rxBuffer[64];
uint16_t len = ReadRadioRxBuffer(rxBuffer);
if(len > 0) {
printf("收到%d字节: %s\n", len, rxBuffer);
}
实测发现,在城区环境下使用SF=12的扩频因子时,传输距离能达到2公里以上,但代价是传输速率降到300bps左右。这就像用大喇叭喊话——传得远但说得慢。
环境监测项目最核心的就是传感器数据采集。我用的这款开发板集成了多种传感器接口:
光照传感器(BH1750)读取示例:
c复制#include "bh1750.h"
void read_light_sensor(void) {
float lux = BH1750_ReadLight();
printf("光照强度: %.2f lx\n", lux);
// 低光照时自动开启LED
if(lux < 50) {
GpioWrite(&Led1, 0);
}
}
温湿度传感器(SHT30)注意事项:
有次项目验收时,温湿度数据突然跳变,后来发现是I2C总线被静电干扰了。解决方法是在SDA、SCL线上加100pF的滤波电容。
做物联网终端最头疼的就是功耗问题。经过多次测试,我总结出几个有效的方法:
c复制void enter_stop_mode(void) {
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后需要重新配置时钟
SystemClock_Config();
}
c复制// 降低发射功率
NS_RadioInit(433000000, 10, 1000, 1000);
// 缩短发射时间
Radio.SetTxConfig(MODEM_LORA, 17, 0, 0, 7, 1, 8, false, true, false, 0, false, 3000);
实测下来,采用间歇工作模式(每10分钟唤醒一次)可使设备续航从3天提升到2个月以上。这就像让设备"打瞌睡",需要时才醒来工作。
在实际项目中,直接发送原始数据既浪费带宽又不可靠。我设计了一套简单的传输协议:
数据包结构:
code复制| 包头(0xAA) | 设备ID(2B) | 数据类型(1B) | 数据长度(1B) | 数据(NB) | 校验和(1B) |
打包示例代码:
c复制typedef struct {
uint8_t header;
uint16_t dev_id;
uint8_t data_type;
uint8_t length;
uint8_t payload[32];
uint8_t checksum;
} lora_packet_t;
void send_sensor_data(void) {
lora_packet_t pkt;
pkt.header = 0xAA;
pkt.dev_id = 0x1234;
pkt.data_type = 0x01; // 温度数据
float temperature = read_temperature();
memcpy(pkt.payload, &temperature, sizeof(float));
pkt.length = sizeof(float);
pkt.checksum = calculate_checksum(&pkt);
Radio.Send((uint8_t*)&pkt, sizeof(pkt));
}
这种结构既节省流量(一个温湿度数据包只要10字节),又能通过校验和发现传输错误。我在项目里用这套协议实现了98%以上的数据接收率。
OLED屏虽然小,但用好了一样能做出直观的界面。分享几个实用技巧:
c复制typedef struct {
const char* title;
void (*action)(void);
} menu_item_t;
menu_item_t main_menu[] = {
{"温湿度显示", show_temp_humi},
{"光照数据", show_light},
{"设备信息", show_device_info}
};
void draw_menu(uint8_t selected) {
OLED_Clear();
for(int i=0; i<3; i++) {
if(i == selected) {
OLED_ShowString(0, i*2, ">");
}
OLED_ShowString(10, i*2, main_menu[i].title);
}
}
[==== ] 60%↑2.5℃有次客户要求显示二维码,我研究出用取模软件转换的方法:
OLED_DrawBMP()函数显示结合前面所有知识点,我们来实现一个完整的环境监测节点:
硬件清单:
软件架构:
c复制void main() {
hardware_init();
lora_init();
sensors_init();
oled_init();
while(1) {
if(should_sample()) { // 每5分钟采样一次
sample_sensors();
send_lora_data();
update_display();
}
handle_button();
low_power_delay();
}
}
避坑指南:
这个项目我在多个农业大棚部署过,最远的一个节点距离网关1.8公里,靠一节18650电池工作了整整半年。期间遇到最大的挑战是金属大棚对无线信号的屏蔽,后来通过调整天线位置和增加中继节点解决了。