2. 嵌入式系统基础:STM32微控制器介绍、GPIO、定时器、中断系统

各位同学,欢迎来到第二章。这一章我们聊聊嵌入式系统的核心——STM32微控制器。说实话,我当年刚接触STM32时,也被它那几百页的参考手册吓到过。但别担心,咱们今天只讲最实用的三个模块:GPIO、定时器和中断系统。掌握了这些,你就能让电梯的按钮亮起来、让电机转起来、让系统响应各种突发状况。

2.1 STM32微控制器概览

STM32是意法半导体推出的32位ARM Cortex-M系列微控制器。为什么电梯系统选它?因为它的性价比高、外设丰富、生态成熟。我在项目中用过F1、F4、G0系列,各有千秋。

系列 内核 主频 适合场景
STM32F1 Cortex-M3 72MHz 通用控制,成本敏感
STM32F4 Cortex-M4 168MHz 需要DSP或浮点运算
STM32G0 Cortex-M0+ 64MHz 低功耗、小封装

我个人习惯在电梯控制板上用STM32F103系列。为什么?因为它的定时器资源丰富,而且市面上能找到大量参考设计。你想想看,电梯系统需要同时处理多个楼层呼叫、门控信号、安全回路,外设不够用可不行。

2.2 GPIO:让芯片与外界对话

GPIO(通用输入输出引脚)是芯片最基础的接口。说白了,它就是芯片的“手脚”——用来读取按钮状态、控制指示灯、驱动继电器。

2.2.1 GPIO的工作模式

STM32的GPIO有8种模式,但咱们做电梯系统,常用的就这几种:

  • 推挽输出:驱动LED、继电器。输出高电平时拉高,低电平时拉低。
  • 浮空输入:读取按钮状态。注意,这种模式下引脚电平不确定,需要外部上拉或下拉。
  • 上拉/下拉输入:内部集成电阻,省去外部元件。我建议在电梯门锁检测上用内部上拉,省一个电阻就是省一分钱。
  • 复用功能:比如把引脚配置为定时器输出或串口通信。

重点:配置GPIO时,别忘了开启对应的时钟!我见过太多新手调了半天GPIO没反应,结果发现是RCC时钟没开。嗯,这个坑我踩过。

2.2.2 代码示例:点亮电梯楼层指示灯

// 初始化PB0为推挽输出,用于控制1楼指示灯
void LED_Init(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  // 开启时钟
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;       // 推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

// 点亮1楼指示灯
void Floor1_LED_On(void) {
    GPIO_SetBits(GPIOB, GPIO_Pin_0);
}

// 熄灭1楼指示灯
void Floor1_LED_Off(void) {
    GPIO_ResetBits(GPIOB, GPIO_Pin_0);
}

小技巧:我习惯用位带操作来快速翻转GPIO。比如 PBout(0) = 1; 比调用函数快得多。在电梯高速运行时,这点时间差可能决定系统能否及时响应。

2.3 定时器:精确控制时间

电梯系统里,定时器无处不在。电机加速/减速需要PWM、门控延时需要计时、故障检测需要超时判断。STM32的定时器分为三类:基本定时器、通用定时器、高级定时器。

2.3.1 定时器的工作原理

定时器的核心是一个不断递增(或递减)的计数器。当计数值达到预设的自动重装载值时,就会产生更新事件或中断。你可以把它想象成一个秒表——到了设定时间就“叮”一声。

举个例子:系统时钟72MHz,预分频器设为7200-1,那么定时器时钟就是72MHz/7200 = 10kHz。如果自动重装载值设为10000-1,那么定时器每1秒溢出一次。

注意:预分频器和自动重装载值都要减1!因为计数器是从0开始计数的。我曾经在调试电梯门控延时程序时,忘了减1,结果门关了2秒就自动打开,差点夹到人。

2.3.2 代码示例:定时器中断实现1秒闪烁

// 定时器2初始化,产生1秒中断
void TIM2_Init(void) {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseStructure.TIM_Prescaler = 7200 - 1;      // 72MHz/7200 = 10kHz
    TIM_TimeBaseStructure.TIM_Period = 10000 - 1;        // 10kHz/10000 = 1Hz
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);           // 使能更新中断
    TIM_Cmd(TIM2, ENABLE);                               // 启动定时器
}

// 定时器2中断服务函数
void TIM2_IRQHandler(void) {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        // 在这里翻转LED,实现1秒闪烁
        PBout(0) = !PBout(0);
    }
}

2.4 中断系统:让CPU及时响应紧急事件

中断是嵌入式系统的灵魂。没有中断,CPU就得轮询所有外设,效率极低。在电梯系统里,中断用来处理最紧急的事情——比如安全回路断开、急停按钮按下、平层传感器触发。

2.4.1 NVIC与中断优先级

STM32使用嵌套向量中断控制器(NVIC)来管理中断。每个中断源都可以设置抢占优先级和子优先级。抢占优先级高的中断可以打断优先级低的中断——这就是“嵌套”的含义。

我个人建议:在电梯系统中,安全相关的中断(如门锁检测、超速保护)设为最高优先级。为什么?因为人命关天。我曾经在一个项目中,把串口中断优先级设得比安全回路中断还高,结果串口数据量一大,安全回路响应延迟了十几毫秒。嗯,这个教训让我记住了:优先级分配要按紧急程度,而不是按方便程度。

2.4.2 外部中断配置步骤

  1. 配置GPIO为输入模式(上拉或下拉)
  2. 将GPIO连接到EXTI(外部中断/事件控制器)
  3. 配置EXTI的触发方式(上升沿、下降沿或双边沿)
  4. 配置NVIC,使能中断并设置优先级
  5. 编写中断服务函数

2.4.3 代码示例:急停按钮中断

// 配置PA0为外部中断,下降沿触发(按钮按下时)
void EXTI0_Init(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;        // 内部上拉输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
    
    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  // 下降沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  // 最高优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

// 急停中断服务函数
void EXTI0_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
        // 急停按钮被按下!立即停止电梯
        Motor_Stop();           // 停止电机
        Brake_Engage();         // 抱闸
        Door_Unlock();          // 开门
        SetAlarm();             // 发出警报
        
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

避坑指南:中断服务函数里不要做复杂操作!我曾经在中断里调用printf打印调试信息,结果串口还没发完,下一个中断又来了,系统直接卡死。记住:中断里只做最紧急的事,比如置标志位、清中断标志。具体的处理逻辑放到主循环里做。

2.5 本章小结

这一章我们聊了STM32的三大基础模块:GPIO是芯片的“手脚”,定时器是“时钟”,中断是“警报系统”。在电梯故障诊断与自检系统中,这三者缺一不可。下一章,我们将把这些知识应用到实际的电梯控制逻辑中。

最后送大家一句话:嵌入式开发没有捷径,多动手、多踩坑、多总结。我在这个行业干了十几年,最深的体会就是——纸上得来终觉浅,绝知此事要躬行。