第1章 GPIO与基础外设驱动
各位同学,咱们今天聊聊窗帘电机里最基础、也最绕不开的部分——GPIO与基础外设驱动。说白了,就是让单片机跟外面的世界打交道。你想想看,电机转不转、按键按没按、灯亮不亮,这些都得靠GPIO来管。
我个人习惯把GPIO比作单片机的「手脚」。手脚不灵活,整个系统就瘫了。我在项目中遇到过不少次,明明逻辑写对了,硬件就是不干活,最后查出来是GPIO配置出了问题。嗯,这里要注意,配置错了,神仙也救不了。
1.1 GPIO输入输出模式配置
GPIO的模式,说白了就两种:输入和输出。但实际用起来,花样还挺多。
| 模式 | 典型用途 | 注意事项 |
|---|---|---|
| 推挽输出 | 驱动LED、蜂鸣器 | 电流驱动能力有限,别直接带大负载 |
| 开漏输出 | I2C总线、电平转换 | 需要外部上拉电阻 |
| 浮空输入 | 按键检测(外部有上拉) | 容易受干扰,不推荐单独使用 |
| 上拉/下拉输入 | 按键检测(省外部电阻) | 内部上拉约40kΩ,弱上拉 |
| 模拟输入 | ADC采样 | 关闭数字功能,省电 |
配置代码其实很简单,但容易踩坑。我贴一段常用的初始化代码:
void GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能时钟——这一步忘了,后面全白搭
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置PB0为推挽输出,驱动LED
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);
// 配置PB1为上拉输入,接按键
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
1.2 按键消抖
按键消抖,这话题老生常谈了。但每次做项目,总有人在这上面翻车。为什么会这样?因为机械按键按下和松开的时候,触点会弹跳,产生一连串的毛刺信号。单片机跑得飞快,一个毛刺就能触发一次中断。
消抖有两种路子:硬件消抖和软件消抖。
硬件消抖
最简单的方法,就是加个RC低通滤波器。电阻选10kΩ,电容选0.1μF,时间常数约1ms。这个组合我用了好多年,效果稳定。不过要注意,电容别太大,否则按键响应会变慢。
软件消抖
我个人更推荐软件消抖,省成本、调参数也方便。核心思路就一句话:检测到电平变化后,等一段时间再确认。
uint8_t Key_Scan(void)
{
static uint8_t last_state = 1; // 假设上拉,默认高电平
static uint16_t count = 0;
uint8_t current_state;
current_state = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);
if (current_state != last_state)
{
count++;
if (count >= 5) // 连续采样5次一致,才算有效
{
last_state = current_state;
count = 0;
if (current_state == 0) // 按下(低电平有效)
{
return 1; // 返回按键按下事件
}
}
}
else
{
count = 0;
}
return 0;
}
1.3 LED指示灯驱动
LED驱动,看起来简单,但里面也有门道。直接拿GPIO推挽输出,串个限流电阻就完事了。电阻怎么选?我一般按这个公式算:
R = (Vcc - Vf) / If
Vcc是3.3V,Vf是LED正向压降(红色约2V,蓝色约3V),If取5~10mA。算下来,红色LED用220Ω,蓝色用100Ω。嗯,差不多就这样。
实际项目中,我习惯把LED做成呼吸灯效果,让窗帘电机的工作状态更直观。用PWM控制亮度,代码也不复杂:
void LED_Breath(void)
{
static uint16_t pwm_val = 0;
static int8_t direction = 1;
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_val);
pwm_val += direction * 10;
if (pwm_val >= 1000) direction = -1;
if (pwm_val == 0) direction = 1;
}
1.4 蜂鸣器驱动
蜂鸣器分两种:有源和无源。有源的给电就响,无源的得给PWM波才能发声。窗帘电机里,我一般用有源的,简单粗暴,用来提示操作成功或故障报警。
驱动方式跟LED差不多,但要注意:蜂鸣器是感性负载,关断时会产生反向电动势。直接接GPIO,容易把引脚烧了。我建议加个续流二极管,或者用三极管驱动。
void Buzzer_On(void)
{
HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET);
}
void Buzzer_Off(void)
{
HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET);
}
// 短促鸣叫,用于按键反馈
void Buzzer_Beep(uint16_t duration_ms)
{
Buzzer_On();
HAL_Delay(duration_ms);
Buzzer_Off();
}
1.5 外部中断(EXTI)应用
外部中断,说白了就是让单片机「被动」响应外部事件。按键按下、电机堵转、限位开关触发,这些都可以用EXTI来处理。
配置EXTI有几个关键点:
- 选择触发边沿:上升沿、下降沿、或者双边沿
- 配置中断优先级:别让不重要的事件抢了CPU
- 中断服务函数里别干重活:只设标志位,具体处理放主循环
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == KEY_Pin)
{
// 设置按键标志,不要在中断里做消抖
key_press_flag = 1;
}
else if (GPIO_Pin == LIMIT_SWITCH_Pin)
{
// 限位开关触发,立即停止电机
motor_stop_flag = 1;
}
}
好了,这一章的内容就这些。GPIO和基础外设驱动,看似简单,但它是整个嵌入式系统的根基。你想想看,如果连灯都点不亮、按键都读不准,后面的电机控制、通信协议还怎么玩?
下一章,咱们聊聊定时器和PWM,窗帘电机调速的核心就靠它了。
公众号:蓝海资料掘金营,微信deep3321