1. 项目背景与目标
本项目旨在使用 STM32F103C8T6 最小系统板,结合 超声波模块(HC-SR04)、舵机、按键和 OLED 显示屏,设计一个智能垃圾桶系统。该系统能够实现自动感知距离,并根据超声波测距值控制舵机开关,同时支持按键触发操作。系统还可以在 OLED 屏幕上显示实时的测距信息。
本项目的设计重点是:
- 综合训练 定时器中断、输入捕获和 PWM控制。
- 训练配置 外部中断(按键触发),并设置中断优先级。
- 使用 OLED 显示屏输出调试信息,提供距离反馈。
2. 硬件选型与连接
2.1 硬件选型
- 主控板:STM32F103C8T6 最小系统板
- 显示模块:0.96 寸 OLED 显示屏
- 超声波模块:HC-SR04,用于测量距离
- 舵机:用于控制垃圾桶的开关
- 按键:用于手动触发操作
- 电路搭建:使用面包板进行电路搭建
2.2 硬件连接
OLED 屏幕 (0.96寸)
- SCL(时钟) →
PB8
- SDA(数据) →
PB9
- GND(地) → 直接连接到电源地(
PB6
暂空) - VCC(供电) → 直接连接到电源正极(
PB7
暂空)
超声波测距模块 (HC-SR04)
- VCC(供电) →
5V
(从 ST-LINK 单独引出) - GND(地) →
GND
- Trig(触发) →
PA11
(推挽输出,同时用于 TIM1通道4 间接下降沿输入捕获) - Echo(回声) →
PA10
(设置为 TIM1通道3 上升沿输入捕获)
舵机 (SG90)
- VCC(供电) →
5V
(与超声波模块共用 ST-LINK 5V 供电) - GND(地) →
GND
- PWM信号 → 未指定,请补充连接引脚信息
按键
- 信号输入 →
PB1
(上拉输入) - GND(地) →
GND
3. 软件设计
3.1 使用 STM32CubeMX 配置
在 STM32CubeMX 中进行以下配置:
- 定时器 1:
- 通道3 (PA10, Echo) → 上升沿输入捕获,启用 上升沿捕获中断
- 通道4 (PA11, Trig) → 间接下降沿输入捕获
- 时钟源 →
Internal Clock
- 预分频系数 →
72 - 1
(即 1MHz 计数频率,每 1us 计数+1) - 自动重装载值 →
65535
(最大计数时间 65.535ms,足够测 11m 以内的距离)
- 定时器 2:
- 时钟源 →
Internal Clock
- 通道2 (PA1) → PWM 生成预分频系数 →
720 - 1
(即72MHz / 720 = 100kHz
计数频率,每 10µs 计数+1) - 向上计数模式自动重装载值(ARR) →
2000 - 1
(周期 20000 µs,即 20ms,符合舵机信号标准)
- 时钟源 →
- 外部中断:
- 配置按键连接的 PB1 引脚为外部中断源,设置中断优先级。
3.2 主要功能实现
3.2.1 超声波测距与舵机控制
- 使用定时器 1 的输入捕获功能来测量超声波回波的时延,从而计算物体的距离。
- 当物体距离小于 5 cm 时,舵机会旋转至 40° 位置,同时 LED 灯亮起。
- 在 2 秒后,舵机恢复至 0° 位置,LED 灯熄灭。
- 用户按键触发时,舵机也会旋转至 40°,并点亮 LED。
3.2.2 中断管理与优先级
- 超声波测距的输入捕获中断:设置定时器 1 的输入捕获中断,以便及时捕获回声信号并计算距离。
- 按键中断:使用外部中断管理按键触发操作,按键触发时会优先处理,舵机旋转并点亮 LED。
3.3 代码解析
3.3.1 定时器 1 输入捕获中断
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim1 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_4) {
RisingEdgeTime = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_3);
FallingEdgeTime = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_4);
Distance = (FallingEdgeTime - RisingEdgeTime) * 0.034 / 2;
}
}
该函数用于捕获上升沿和下降沿的时间,功能是通过定时器输入捕获中断计算超声波测距。代码解释如下:
- 判断是否为定时器 1 的捕获通道 4(即超声波模块的 Trig 触发口)触发的中断。
- 读取上升沿和下降沿的计数值(通过定时器输入捕获功能获取)。
- 计算距离:
距离公式为:$\text{Distance} = \frac{(\text{FallingEdgeTime} – \text{RisingEdgeTime}) \times 0.034}{2}$ - 这里
0.034
是声音在空气中的传播速度(单位:厘米/微秒),除以2
是因为超声波的传播是来回的。
3.3.2 外部中断处理(按键)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == Key1_Pin) {
key1_flag = 1;
}
}
判断是否为 PB1 引脚的中断(即按键触发的中断)。如果是,设置 key1_flag
为 1,表示按键已被触发,主程序可以根据此标志位进行进一步的操作。
3.3.3 PWM 控制舵机
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 94);
通过定时器 2 设置 PWM 波形的占空比来控制舵机的旋转角度。
3.4 中断优先级设置
HAL_NVIC_SetPriority(TIM1_CC_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);
HAL_NVIC_SetPriority(EXTI1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(EXTI1_IRQn);
为定时器 1 捕获中断和按键中断设置了不同的优先级,确保按键事件能够高优先级响应。
4. 主函数逻辑
4.1 主函数代码
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
MX_TIM2_Init();
OLED_Init();
HAL_TIM_Base_Start(&htim1); // 开启定时器1
HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_3); // 开启通道3输入捕获 上升沿
HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_4); // 开启通道4输入捕获中断,下降沿
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); // 开启定时器2的PWM输出
OLED_ShowString(2, 1, "Distance:");
OLED_ShowString(2, 15, "cm");
while (1)
{
HAL_GPIO_WritePin(Trig_GPIO_Port, Trig_Pin, GPIO_PIN_SET); // 拉高触发引脚
HAL_Delay(1);
HAL_GPIO_WritePin(Trig_GPIO_Port, Trig_Pin, GPIO_PIN_RESET); // 拉低触发引脚
__HAL_TIM_SET_COUNTER(&htim1, 0); // 清零定时器1计数器
HAL_Delay(20);
OLED_ShowNum(2, 11, (uint32_t)Distance, 3); // 显示距离,最大3位
OLED_ShowString(2, 15, "cm");
/* 1. 按键触发 (最高优先级) */
if (key1_flag)
{
key1_flag = 0; // 清除标志位
/* 舵机旋转到 40° */
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 94);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // 点亮 LED
HAL_Delay(2000);
/* 舵机旋转回 0° */
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 50);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // 熄灭 LED
HAL_Delay(2000);
}
/* 2. 超声波触发 (低优先级) */
else if (Distance < 5 && servo_state == 0)
{
servo_state = 1;
servo_timer = HAL_GetTick(); // 记录触发时间
/* 舵机旋转到 40° */
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 94);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // 点亮 LED
}
else if (servo_state == 1 && (HAL_GetTick() - servo_timer >= 2000))
{
servo_state = 0;
/* 舵机旋转回 0° */
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 50);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // 熄灭 LED
}
HAL_Delay(500);
}
主函数中的初始化与启动
HAL_Delay(20)
: 延时 20ms,确保硬件初始化完成。OLED_Init()
: 初始化 OLED 显示屏。HAL_TIM_Base_Start(&htim1)
: 启动定时器 1,用于超声波测距的输入捕获。HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_3)
: 启动定时器 1 的 通道 3,用于捕获超声波回声的上升沿。HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_4)
: 启动定时器 1 的通道 4,并使能输入捕获中断,用于捕获超声波回声的下降沿。HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2)
: 启动定时器 2 的通道 2,输出 PWM 信号控制舵机。
主循环功能
- OLED 显示:
- 显示“Distance: ”和“cm”提示信息。
- 显示当前计算的距离值,单位为厘米。
- 超声波触发:
- Trig 引脚拉高 1ms,然后拉低,触发超声波模块发射脉冲。
- 清除定时器计数器,确保每次测距从零开始。
- 按键触发(优先级最高):
- 如果
key1_flag
为 1,表示按键被触发,舵机旋转到 40°,同时点亮 LED。 - 舵机旋转后等待 2 秒,然后回到初始位置(0°)并熄灭 LED。
- 如果
- 超声波触发条件(低优先级):
- 如果测得的距离小于 5cm 且舵机状态为默认(
servo_state == 0
),舵机旋转到 40°,并点亮 LED。 - 如果舵机已旋转并且触发时间超过 2 秒,舵机回到 0° 并熄灭 LED。
- 如果测得的距离小于 5cm 且舵机状态为默认(
- 延时:
- 每次循环结束后延时 500ms,避免过于频繁的测量和操作。
总结
主循环负责以下任务:
- 控制 OLED 显示距离。
- 触发超声波测距。
- 响应按键操作和距离变化,控制舵机旋转,并控制 LED 状态。