首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《嵌入式物联网:在字节和毫安之间寻找平衡》

《嵌入式物联网:在字节和毫安之间寻找平衡》

原创
作者头像
资源shanxueit.com
发布2026-08-19 11:57:24
发布2026-08-19 11:57:24
1500
举报

你以为你在写代码?不,你在和示波器对话,和功耗表谈判。云端可以重启,嵌入式设备只能靠看门狗自救。


一、GPIO控制:嵌入式世界的"握手礼"

GPIO(通用输入输出)是嵌入式最基础的"肌肉"。点灯、读按键、驱动电机,背后都是GPIO的电平翻转。下面以STM32 + HAL库为例(目前工业界最主流):

代码语言:javascript
复制
// 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);
        }
    }
}

工程要点拆解

  • 时钟使能:STM32每个外设都有独立时钟,用前必须先打开(省电设计)
  • 推挽输出:可直接驱动LED(灌电流20mA),但驱动电机必须加MOS管
  • 上拉电阻:内部上拉省去外接电阻,但抗干扰能力弱于外接10K电阻
  • 消抖:机械按键抖动持续5-20ms,软件延时是最简单方案(工业上用硬件消抖更可靠)

GPIO的"电压逻辑"

  • GPIO_PIN_SET = 3.3V(高电平)
  • GPIO_PIN_RESET = 0V(低电平)
  • 传感器输出的是电压,你读的是"0"和"1"

二、中断:嵌入式系统的"紧急通道"

轮询(Polling)是"每隔1ms问一次",中断是"有事叫我,没事别烦我"。对于物联网设备,中断是省电和实时响应的核心

外部中断配置(按键唤醒 + 定时器中断)

代码语言:javascript
复制
// 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;  // 标志位通知主循环
    }
}

中断的三个铁律

  1. ISR(中断服务函数)要极短:不能超过100μs,否则会影响其他中断
  2. 不要在中断里做浮点运算:STM32F1没有FPU,浮点运算耗时几百微秒
  3. 标志位通信:中断只负责"存数据+置标志",主循环负责"处理数据+发云端"

中断优先级(NVIC配置)

  • 高优先级中断可以打断低优先级(比如按键唤醒比定时器采集优先级高)
  • 系统时钟中断(SysTick)优先级最高,因为HAL_Delay依赖它

三、低功耗:物联网的"生存底线"

物联网设备95%的时间在睡觉,5%的时间在工作。功耗是硬指标,不是优化项。

STM32的三种睡眠模式

模式

电流消耗

唤醒方式

适用场景

睡眠模式

~1mA

任何中断

等待时间不长(1秒内)

停止模式

~50μA

外部中断

等待外部触发(按键、传感器)

待机模式

~2μA

特定引脚/WakeUp

超低功耗(RTC定时唤醒)

进入停止模式并在RTC中断唤醒(最常用的物联网休眠方案)

代码语言:javascript
复制
// 配置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();
        }
    }
}

功耗计算(电池寿命预估)

代码语言:javascript
复制
假设电池容量 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米

穿戴设备、智能家居

一个典型的物联网设备代码结构

代码语言:javascript
复制
// 嵌入式主程序框架(状态机 + 低功耗)
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语言+单片机",但真正的嵌入式工程师需要同时懂三样:

  1. 看数据手册:知道寄存器地址、时序图、电气特性
  2. 看示波器:I2C波形对不对、电源纹波大不大
  3. 看功耗表:休眠电流是不是微安级,唤醒时间够不够快

最后一段真理代码(不是C,是工程思维)

代码语言:javascript
复制
# 嵌入式开发的三个不等式
嵌入式 ≠ 编程           # 硬件不懂软件,软件不懂硬件,产品就废了
低功耗 ≠ 关外设         # 要学会"睡多久、醒多快"的平衡
物联网 ≠ 联网           # 80%的问题出在传感器本身,不是网络

送给嵌入式新手的一句话

云端可以重启、可以扩容、可以回滚。但嵌入式设备只有一次机会——出厂后你就再也碰不到它了。所以,写代码时想象那颗芯片就在南极科考站,零下40度,太阳能供电,10年不关机。


附加:看门狗(最后的安全绳)

代码语言:javascript
复制
// 独立看门狗(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 删除。

目录
  • 一、GPIO控制:嵌入式世界的"握手礼"
  • 二、中断:嵌入式系统的"紧急通道"
  • 三、低功耗:物联网的"生存底线"
  • 四、通信协议:物联网的"语言"(选型比代码重要)
  • 五、嵌入式物联网的"道":硬件是根,软件是魂
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档