第三章 嵌入式系统基础:STM32/ESP32平台介绍、GPIO、定时器、中断、PWM基础
各位同学,欢迎来到第三章。这一章我们聊聊嵌入式平台的基础。
做智能药盒,说白了就是让一块小芯片去控制几个电机、读几个传感器、再驱动个屏幕。听起来简单,但里面的门道不少。我个人习惯把嵌入式开发比作「搭积木」——你得先认识每块积木长什么样,才能搭出漂亮的房子。
今天我们就来认识两块最常见的积木:STM32 和 ESP32。
3.1 两大主流平台:STM32 vs ESP32
先说说STM32。这是意法半导体的产品,ARM Cortex-M内核。我在项目中用过不下十款STM32芯片,从F103到H743都有。它的特点是稳定、功耗低、外设丰富。做药盒这种需要长期运行的设备,STM32是首选。
ESP32呢?乐鑫的产品,集成了Wi-Fi和蓝牙。如果你想让药盒联网、远程提醒、数据上传,ESP32就很合适。不过它的功耗比STM32高一些,待机时要注意。
| 特性 | STM32(以F103为例) | ESP32 |
|---|---|---|
| 内核 | ARM Cortex-M3 | Xtensa LX6 双核 |
| 主频 | 72 MHz | 240 MHz |
| 无线 | 无(需外接) | Wi-Fi + BLE |
| 功耗 | 低(uA级待机) | 较高(mA级待机) |
| 开发工具 | Keil / STM32CubeIDE | Arduino / ESP-IDF |
| 适合场景 | 控制类、低功耗设备 | 联网类、数据处理 |
3.2 GPIO:最基础的操作
GPIO,全称通用输入输出口。说白了就是芯片的「手脚」——可以输出高电平或低电平,也可以读取外部信号的高低。
在药盒里,GPIO用来做什么?
- 控制LED指示灯(吃药提醒灯)
- 读取按键状态(用户按了哪个按钮)
- 驱动蜂鸣器(闹钟响)
- 控制电机驱动芯片的使能脚
嗯,这里要注意:GPIO不能直接驱动大电流设备。比如一个直流电机,启动电流可能几百毫安,GPIO只能输出几毫安。强行驱动会烧坏芯片。我曾经有个实习生就这么干过,芯片直接冒烟了……
看个简单的代码示例,STM32上用HAL库点亮一个LED:
// 初始化GPIO
void LED_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
// 点亮LED
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
ESP32上用Arduino框架更简单:
void setup() {
pinMode(2, OUTPUT); // 板载LED通常接GPIO2
}
void loop() {
digitalWrite(2, HIGH);
delay(1000);
digitalWrite(2, LOW);
delay(1000);
}
3.3 定时器:精确的时间管理
定时器是嵌入式系统里最常用的外设之一。你想想看,药盒需要定时提醒吃药、定时检测药仓状态、定时上报数据……这些都离不开定时器。
STM32的定时器分几种:基本定时器、通用定时器、高级定时器。做药盒的话,通用定时器就够用了。
定时器的核心原理:内部有一个计数器,每个时钟周期加1。当计数值达到预设的「自动重装载值」时,就会触发中断或产生事件。
举个例子,STM32F103的定时器时钟是72MHz,我想产生1秒的中断:
- 预分频值设为7199(即7200分频,得到10kHz)
- 自动重装载值设为9999(即10000个计数)
- 定时时间 = 7200 × 10000 / 72,000,000 = 1秒
代码实现:
void TIM2_Init(void) {
TIM_HandleTypeDef htim2;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 7199; // 预分频
htim2.Init.Period = 9999; // 自动重装载值
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_Base_Init(&htim2);
HAL_TIM_Base_Start_IT(&htim2); // 启动定时器并开启中断
}
// 中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
// 每秒执行一次
Check_Medicine_Time(); // 检查吃药时间
}
}
3.4 中断:让系统及时响应
中断是什么?打个比方:你正在看书,突然电话响了。你放下书去接电话,接完回来继续看书。这个「电话响」就是中断信号,「接电话」就是中断服务函数。
在嵌入式系统里,中断让芯片能及时响应外部事件。比如:
- 按键按下 → 触发外部中断 → 执行吃药确认
- 定时器溢出 → 触发定时器中断 → 更新显示
- UART收到数据 → 触发串口中断 → 解析指令
STM32的中断优先级可以配置。我个人习惯把时间敏感的中断设高优先级(比如电机堵转检测),把不那么紧急的设低优先级(比如按键扫描)。
外部中断的配置示例:
void EXTI0_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0); // 设置优先级
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
// 中断服务函数
void EXTI0_IRQHandler(void) {
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
// 回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == GPIO_PIN_0) {
key_pressed = 1; // 设置标志位,主循环处理
}
}
3.5 PWM:模拟世界的数字控制
PWM,脉冲宽度调制。说白了就是通过快速开关信号,让负载感受到一个「平均电压」。
在药盒里,PWM的典型应用:
- 控制直流电机转速(出药机构)
- 调节LED亮度(夜间模式)
- 驱动蜂鸣器发出不同音调
PWM有两个关键参数:频率和占空比。频率决定了开关的快慢,占空比决定了平均电压的高低。
| 参数 | 说明 | 药盒中的典型值 |
|---|---|---|
| 频率 | 每秒开关次数 | 电机:1kHz~10kHz;LED:100Hz以上 |
| 占空比 | 高电平时间占整个周期的比例 | 0%~100%,控制电机速度或LED亮度 |
STM32用定时器产生PWM非常方便。以TIM3的通道1为例:
void PWM_Init(void) {
TIM_HandleTypeDef htim3;
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71; // 72MHz / 72 = 1MHz
htim3.Init.Period = 999; // PWM频率 = 1MHz / 1000 = 1kHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim3);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 初始占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
}
// 动态调整占空比
void Set_Motor_Speed(uint16_t speed) {
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, speed);
// speed范围0~1000,对应0%~100%占空比
}
ESP32的PWM实现更灵活,因为它有LEDC控制器:
// ESP32 Arduino PWM
const int freq = 5000; // 5kHz
const int ledChannel = 0;
const int resolution = 8; // 8位分辨率,0~255
void setup() {
ledcSetup(ledChannel, freq, resolution);
ledcAttachPin(2, ledChannel); // GPIO2输出PWM
}
void loop() {
for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {
ledcWrite(ledChannel, dutyCycle);
delay(10);
}
}
好了,这一章的内容就到这里。总结一下:我们认识了STM32和ESP32两个平台,掌握了GPIO的输入输出、定时器的精确计时、中断的及时响应、PWM的模拟控制。这些都是做智能药盒的「基本功」。
下一章,我们会把这些知识串起来,开始设计药盒的硬件电路。到时候见。