
你以为你在写代码?不,你在和示波器对话,和功耗表谈判。云端可以重启,嵌入式设备只能靠看门狗自救。
GPIO(通用输入输出)是嵌入式最基础的"肌肉"。点灯、读按键、驱动电机,背后都是GPIO的电平翻转。下面以STM32 + HAL库为例(目前工业界最主流):
// STM32 GPIO控制:点灯和读按键(仅核心逻辑)
#include "stm32f1xx_hal.h"
void gpio_init(void) {
__HAL_RCC_GPIOC_CLK_ENABLE(); // 使能GPIOC时钟
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_13; // PC13接LED
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_0; // PA0接按键
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 内部上拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
// 主循环中:按键按下翻转LED
void loop(void) {
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) {
HAL_Delay(50); // 消抖(简单粗暴)
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 翻转LED
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
}
}工程要点拆解:
GPIO的"电压逻辑":
GPIO_PIN_SET = 3.3V(高电平)GPIO_PIN_RESET = 0V(低电平)轮询(Polling)是"每隔1ms问一次",中断是"有事叫我,没事别烦我"。对于物联网设备,中断是省电和实时响应的核心。
外部中断配置(按键唤醒 + 定时器中断):
// STM32外部中断配置(按键触发)
void EXTI0_IRQHandler(void) {
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0); // 清中断标志
// 中断服务函数(尽量简短)
static uint32_t last_time = 0;
uint32_t now = HAL_GetTick();
if (now - last_time > 200) { // 200ms防抖(不阻塞CPU)
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
last_time = now;
}
}
}
// 定时器中断:每1秒采集一次温湿度(不占用CPU轮询)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
// 触发DHT11采集(I2C或单总线)
uint8_t temp = read_dht11_temperature();
uint8_t humi = read_dht11_humidity();
// 存入缓冲区,等待主循环发送到云端
sensor_data.temperature = temp;
sensor_data.humidity = humi;
sensor_data.ready = 1; // 标志位通知主循环
}
}中断的三个铁律:
中断优先级(NVIC配置):
物联网设备95%的时间在睡觉,5%的时间在工作。功耗是硬指标,不是优化项。
STM32的三种睡眠模式:
模式 | 电流消耗 | 唤醒方式 | 适用场景 |
|---|---|---|---|
睡眠模式 | ~1mA | 任何中断 | 等待时间不长(1秒内) |
停止模式 | ~50μA | 外部中断 | 等待外部触发(按键、传感器) |
待机模式 | ~2μA | 特定引脚/WakeUp | 超低功耗(RTC定时唤醒) |
进入停止模式并在RTC中断唤醒(最常用的物联网休眠方案):
// 配置RTC每30秒唤醒一次
void enter_stop_mode(void) {
// 1. 关闭不需要的外设时钟(省电)
__HAL_RCC_USART1_CLK_DISABLE();
__HAL_RCC_I2C1_CLK_DISABLE();
__HAL_RCC_GPIOB_CLK_DISABLE();
// 2. 配置唤醒引脚(PA0作为WakeUp)
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
// 3. 设置RTC闹钟(30秒后唤醒)
RTC_AlarmTypeDef sAlarm = {0};
sAlarm.AlarmTime = get_current_time() + 30; // 30秒后
sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
// 4. 进入停止模式(核心)
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);
// 5. 醒来后重新初始化时钟(停止模式会关闭HSE/PLL)
SystemClock_Config(); // 重新配置时钟为72MHz
HAL_Delay(100); // 等待时钟稳定
// 6. 重新使能外设时钟
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_I2C1_CLK_ENABLE();
}
// 主循环中的状态机(用标志位控制工作/休眠)
void main_loop(void) {
while(1) {
if (sensor_data.ready) {
// 有数据,发送到云端
send_to_cloud(sensor_data.temperature, sensor_data.humidity);
sensor_data.ready = 0;
// 发送完立即休眠
enter_stop_mode();
} else {
// 无事发生,休眠
enter_stop_mode();
}
}
}功耗计算(电池寿命预估):
假设电池容量 1000mAh
工作电流:50mA,持续时间 2秒(采集+发送)
休眠电流:50μA,持续时间 28秒(30秒周期)
平均电流 = (50mA × 2s + 0.05mA × 28s) / 30s = 3.38mA
电池寿命 = 1000mAh / 3.38mA ≈ 296小时 ≈ 12天如果改成休眠模式(2μA),平均电流降到 3.3mA,电池寿命可达 12.5天。
嵌入式通信协议不是"哪个好",是"哪个合适":
协议 | 功耗 | 带宽 | 距离 | 适用场景 |
|---|---|---|---|---|
UART | 低 | 115200bps | 1米 | 调试、GPS模块 |
I2C | 极低 | 400Kbps | 0.5米 | 传感器(温湿度、陀螺仪) |
SPI | 中 | 几十Mbps | 0.3米 | 显示屏、SD卡 |
LoRa | 极低 | 几十Kbps | 几公里 | 农业、抄表、广域网 |
NB-IoT | 低 | 100Kbps | 几公里 | 智能水表、烟感 |
BLE | 极低 | 1Mbps | 50米 | 穿戴设备、智能家居 |
一个典型的物联网设备代码结构:
// 嵌入式主程序框架(状态机 + 低功耗)
typedef enum {
STATE_INIT,
STATE_SENSOR_READ,
STATE_DATA_PROCESS,
STATE_CLOUD_SEND,
STATE_SLEEP
} SystemState;
SystemState current_state = STATE_INIT;
void main(void) {
hardware_init();
cloud_connect();
while(1) {
switch(current_state) {
case STATE_INIT:
// 初始化传感器、校准
current_state = STATE_SENSOR_READ;
break;
case STATE_SENSOR_READ:
read_all_sensors();
current_state = STATE_DATA_PROCESS;
break;
case STATE_DATA_PROCESS:
// 滤波、校准、异常值剔除
filter_sensor_data();
current_state = STATE_CLOUD_SEND;
break;
case STATE_CLOUD_SEND:
if (cloud_is_connected()) {
mqtt_publish(sensor_data);
} else {
// 存到Flash,下次重试
save_to_flash(sensor_data);
}
current_state = STATE_SLEEP;
break;
case STATE_SLEEP:
// 进入低功耗,RTC 30秒后唤醒
enter_stop_mode();
current_state = STATE_SENSOR_READ;
break;
}
}
}很多人以为嵌入式就是"C语言+单片机",但真正的嵌入式工程师需要同时懂三样:
最后一段真理代码(不是C,是工程思维):
# 嵌入式开发的三个不等式
嵌入式 ≠ 编程 # 硬件不懂软件,软件不懂硬件,产品就废了
低功耗 ≠ 关外设 # 要学会"睡多久、醒多快"的平衡
物联网 ≠ 联网 # 80%的问题出在传感器本身,不是网络送给嵌入式新手的一句话:
云端可以重启、可以扩容、可以回滚。但嵌入式设备只有一次机会——出厂后你就再也碰不到它了。所以,写代码时想象那颗芯片就在南极科考站,零下40度,太阳能供电,10年不关机。
附加:看门狗(最后的安全绳):
// 独立看门狗(IWDG):防止程序跑飞,倒计时结束就复位
void watchdog_init(void) {
IWDG_HandleTypeDef hiwdg;
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_64; // 时钟分频
hiwdg.Init.Reload = 4095; // 倒计时最大值
HAL_IWDG_Init(&hiwdg);
}
void main_loop(void) {
while(1) {
// 喂狗:在关键循环中重置倒计时
HAL_IWDG_Refresh(&hiwdg);
// 你的业务逻辑
do_something();
}
}看门狗是嵌入式工程师的最后尊严——即使代码完全崩溃,设备也能自动重启,继续工作。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。