你有没有遇到过这种情况?
串口单独测试正常,按键单独测试也正常。
可一放到项目里,按键一按,串口就乱码;串口一打印,按键状态又开始乱跳。
很多人第一反应是查代码:
是不是消抖没写好?
是不是波特率不对?
是不是中断优先级冲突?
是不是 HAL 库配置有问题?
但很多时候,问题不在代码。
而是原理图上,你让两个功能抢了同一个 IO。

01
复用,不等于同时使用
STM32 的引脚很灵活。
一个 IO 可以配置成普通 GPIO,也可以复用成 USART、I2C、SPI、PWM、ADC。
于是很多初学者容易误解:
既然这个引脚能做 GPIO,也能做串口,那是不是可以一边接按键,一边跑串口?
答案是:不行。
一个 IO 在同一时刻只能有一个身份。
它要么是普通输入,要么是串口 RX/TX,要么是其他外设功能。
能复用,不代表能同时用。
02
为什么按键会把串口搞乱?
按键输入很简单。
比如上拉输入:
c
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_10) == GPIO_PIN_RESET)
{
// 按键按下
}
没按下是高电平,按下变低电平。
但串口 RX 不一样。
串口空闲时通常是高电平,一旦有数据进来,电平会按照波特率快速翻转。
也就是说,串口线上本来就在高速变化。
如果你把这个引脚又拿去判断按键,程序读到的就不再是稳定的"按下/松开",而是一堆串口波形。
你以为是按键抖动,其实是串口数据在跳。
反过来也一样。
如果按键接在串口 RX 上,按下一瞬间把 RX 拉低,串口外设可能误以为来了一个起始位。
结果就是:
乱码、丢包、接收中断乱进,协议解析失败。
03
最常见的坑:PA9、PA10
很多 STM32 工程里,PA9 是 USART1_TX,PA10 是 USART1_RX。
如果你已经把 PA10 配成串口接收,又在硬件上把它接了按键,再写这样的代码:
c
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_10) == GPIO_PIN_RESET)
{
HAL_UART_Transmit(&huart1, (uint8_t *)"KEY\r\n", 5, 100);
}
看起来没问题,实际上已经埋雷了。
因为 PA10 此时已经不是普通 GPIO,它正在承担 USART1_RX 的功能。
外部按键会影响串口接收。
串口数据也会影响按键判断。
两个功能互相干扰,程序当然表现异常。
04
能不能分时复用?
理论上可以。
比如平时当按键,需要通信时再切成串口。
但在真实项目里,不建议初学者这么做。
因为你要保证切换时对面设备不发数据,按键电路不影响电平,串口中断能正确关闭和恢复,外设状态不能残留。
少考虑一步,问题就会变成现场玄学。
尤其串口常常不只是普通通信口。
它可能负责调试打印、蓝牙模块、4G 模块、屏幕通信,甚至 Bootloader 升级。
你把按键挂在串口线上,就等于给项目埋了一个定时炸弹。
05
正确做法:画板前先规划 IO
真正靠谱的做法,不是在代码里硬救,而是在画原理图前把引脚规划清楚。
串口、I2C、SPI、PWM、ADC、SWD 下载口、晶振、复位、Boot 脚,这些资源优先安排。
按键、LED、蜂鸣器、普通检测输入,再放到普通 GPIO 上。
尤其是调试串口,尽量保持干净。
项目出问题时,串口就是你的眼睛。
为了省一个按键,把调试串口搞废,真的不划算。
如果 IO 不够,也别硬抢串口脚。
可以考虑矩阵按键、ADC 分压按键、IO 扩展芯片,或者重新调整功能优先级。
最后说一句
同一个引脚不能同时做按键和串口,不是 STM32 不够强。
而是因为:
引脚模式冲突。
外设复用冲突。
外部电路电平冲突。
以后再遇到按键乱跳、串口乱码、I2C 卡死、SPI 不通信,别只盯着代码。
先回头看看原理图:
是不是有两个功能,在抢同一个引脚?
很多项目里的"玄学问题",答案可能就在这里。
如果你正在学 STM32,建议收藏这篇文章。下次选引脚前看一眼,可能就能少踩一个大坑。