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 综合案例:一个振动数据采集固件
把上面所有知识点串起来。假设我们要做一个振动传感器节点:
- GPIO配置中断引脚,检测传感器就绪
- 定时器每5ms触发一次中断
- DMA自动从SPI读取数据到内存
- 数据采集完成后,CPU处理并进入低功耗
这个架构,我称之为「中断驱动+数据流」模式。CPU大部分时间在睡觉,只有数据来了才干活。
嗯,这一章内容不少。但都是嵌入式开发的「基本功」。你把这些吃透了,后面写联网协议栈、云平台对接,都会轻松很多。
记住:好的固件,不是功能多,而是稳定、省电、好维护。