4、固件开发基础:嵌入式C/Python(MicroPython)编程、GPIO操作、定时器中断、DMA传输、低功耗设计

好,咱们进入第四章。这一章是真正动手写代码的开始。

很多朋友觉得嵌入式开发就是「点灯」,其实没那么简单。但也没那么玄乎。说白了,就是让芯片按照你的想法干活。

我个人习惯,先把硬件外设摸清楚,再动手写固件。你想想看,如果连GPIO的电气特性都不清楚,代码写得再漂亮也没用。

4.1 嵌入式C与MicroPython:选哪个?

这是个老生常谈的问题。我直接说结论:

  • 嵌入式C:性能极致,资源占用小。适合量产产品、实时性要求高的场景。比如振动传感器的原始数据采集,我建议用C。
  • MicroPython:开发效率高,调试方便。适合原型验证、快速迭代。我在做实验室Demo时经常用。

但注意,MicroPython的定时器精度和中断响应速度,跟C比还是有差距的。嗯,这里要注意,如果你要做高频振动采样(比如10kHz以上),老老实实用C。

我的经验:在STM32F4上,用C写一个SPI读取加速度计的程序,代码量大概200行。用MicroPython,50行搞定。但C版本的采样抖动小于1us,MicroPython大概在5-10us。看你的需求取舍。

4.2 GPIO操作:别小看这个

GPIO是嵌入式世界的「手」和「脚」。但很多人栽在配置上。

举个例子,振动传感器的中断引脚,通常需要配置为上升沿触发。我曾经遇到过,配置成双边沿触发,结果MCU频繁进入中断,系统直接卡死。

看一段C代码示例:

// 配置GPIO为输入,上拉,中断触发
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;  // 上升沿触发
GPIO_InitStruct.Pull = GPIO_PULLUP;          // 内部上拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

MicroPython版本更简洁:

from machine import Pin
int_pin = Pin(0, Pin.IN, Pin.PULL_UP)
int_pin.irq(trigger=Pin.IRQ_RISING, handler=my_handler)

避坑指南:我曾经在ESP32上用MicroPython配置GPIO中断,发现中断响应偶尔丢失。后来查手册才知道,ESP32的GPIO中断有「去抖」要求。加了个10ms的软件延时,问题解决。

4.3 定时器中断:精准的节拍器

振动数据采集,核心就是定时器。你要在固定的时间间隔读取传感器数据。

为什么不用delay?因为delay会阻塞CPU。你想想看,如果采集频率是1kHz,用delay意味着CPU有99%的时间在空转。太浪费了。

定时器中断的配置要点:

  • 预分频器:决定计数时钟频率
  • 自动重装载值:决定中断频率
  • 中断优先级:别让其他中断抢了它的时间

看一个实际案例。我做过一个项目,需要以200Hz采集三轴加速度数据。定时器配置如下:

// 假设系统时钟72MHz,定时器时钟72MHz
// 预分频 7200-1,计数频率 10kHz
// 自动重装载 50-1,中断频率 200Hz
TIM_HandleTypeDef htim2;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 7200 - 1;
htim2.Init.Period = 50 - 1;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_Base_Init(&htim2);
HAL_TIM_Base_Start_IT(&htim2);

注意:中断服务函数里不要做耗时操作。比如打印日志、复杂计算。我一般只在中断里设置一个标志位,主循环里处理数据。否则会导致中断嵌套,系统崩溃。

4.4 DMA传输:解放CPU

DMA,全称直接存储器访问。说白了,就是让数据自己「跑」到内存里,CPU不用管。

振动传感器通常通过SPI或I2C接口输出数据。如果用CPU轮询读取,每次都要等待传输完成。用DMA的话,你只需要配置一次,数据会自动搬运。

我做过一个对比实验:

传输方式 CPU占用率 采样速率
轮询读取 85% 1kHz
中断读取 30% 5kHz
DMA传输 5% 10kHz

看到差距了吧?DMA几乎是零开销。

MicroPython也支持DMA,但封装得比较底层。我建议初学者先用C掌握DMA原理,再回MicroPython用现成库。

4.5 低功耗设计:让设备活得更久

振动传感器通常用在无线节点上,电池供电。低功耗设计是必修课。

核心思路就一句话:没事就睡,有事再醒

具体做法:

  • 睡眠模式:关闭CPU时钟,保留RAM。唤醒时间几微秒。
  • 停止模式:关闭大部分外设时钟。唤醒时间几十微秒。
  • 待机模式:几乎全部关闭,只有RTC在工作。唤醒时间毫秒级。

我建议,对于振动监测场景,用停止模式最合适。因为你需要定时唤醒采集数据,采集完继续睡。

看一个低功耗设计的伪代码:

void main() {
    while(1) {
        // 进入停止模式
        HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
        
        // 被RTC中断唤醒
        // 采集振动数据
        read_vibration_sensor();
        
        // 处理数据(可选)
        process_data();
        
        // 继续睡眠
    }
}

我的经验:曾经有个项目,电池容量只有500mAh,要求工作3个月。我通过优化采样策略——平时1分钟采一次,检测到振动后改为100ms采一次——最终续航达到了4个半月。低功耗不只是硬件的事,软件策略更重要。

4.6 综合案例:一个振动数据采集固件

把上面所有知识点串起来。假设我们要做一个振动传感器节点:

  1. GPIO配置中断引脚,检测传感器就绪
  2. 定时器每5ms触发一次中断
  3. DMA自动从SPI读取数据到内存
  4. 数据采集完成后,CPU处理并进入低功耗

这个架构,我称之为「中断驱动+数据流」模式。CPU大部分时间在睡觉,只有数据来了才干活。

嗯,这一章内容不少。但都是嵌入式开发的「基本功」。你把这些吃透了,后面写联网协议栈、云平台对接,都会轻松很多。

记住:好的固件,不是功能多,而是稳定、省电、好维护。