拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32 流水灯三种写法:寄存器、标准外设库、HAL 库,以及一个让我意外的延时结论

这学期做 STM32 实验同一个流水灯4 只 LED 依次亮 1 秒我用三种方式各写了一遍直接读写寄存器、ST 标准外设库、STM32Cube HAL 库 按键中断。写完之后有两个收获挺意外的记一下。一、先说三种写法差在哪寄存器标准外设库HAL 库最终代码体积856 B836 B1444 BFlash 占用3.7 KB3.0 KB4.9 KB写起来要翻手册一般舒服第三种是 HAL 库配引脚就三行GPIO_InitTypeDef gpio{0};gpio.PinGPIO_PIN_0;gpio.ModeGPIO_MODE_OUTPUT_PP;gpio.PullGPIO_NOPULL;gpio.SpeedGPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA,gpio);而寄存器方式要自己算位段比如 PA0 是 CRL 的 bit[3:0]配成通用推挽输出 50MHz就是 0x3RCC-APB2ENR|(12)|(13)|(14);/* 先开时钟! */GPIOA-CRL(GPIOA-CRL~0xFUL)|0x3UL;/* 清 4 位再写 */GPIOA-BSRR(1UL0);/* 输出高 */GPIOA-BSRR(1UL(016));/* 输出低 */意外的地方库函数版本的最终体积反而最小836 B 856 B。原因是链接器会把没用到的库函数全部丢掉——所以用库不等于代码一定变大关键是按需裁剪。二、板载 PC13 那个坑最小系统板上出厂就焊了一个 LED 在 PC13我一开始照面包板的方式写输出高电平点亮结果它在流水灯里该亮的时候灭、该灭的时候亮跟其它三个正好反着。翻原理图才明白板载 LED 是共阳接法阳极经电阻接 3.3V阴极接 PC13所以PC13 输出低电平才亮。程序里我加了个标志位把点亮和输出电平分开typedefstruct{GPIO_TypeDef*port;uint16_tpin;uint8_tactive_low;}LedType;staticvoidLed_Write(uint8_ti,uint8_ton){GPIO_PinState level(on!0U)?GPIO_PIN_SET:GPIO_PIN_RESET;if(s_leds[i].active_low){level(levelGPIO_PIN_SET)?GPIO_PIN_RESET:GPIO_PIN_SET;}HAL_GPIO_WritePin(s_leds[i].port,s_leds[i].pin,level);}顺带记住两条PC13/PC14/PC15 由备份域供电最大输出电流只有 3mABlue Pill只引出 PC13/PC14/PC15三根 PC 口引脚PC0~PC12 没有排针。三、软件循环延时到底是多少秒标准库那一版老师要求用软件空循环做延时然后分析它的真实周期。我一开始写voidDelay(__IOuint32_tnCount){for(;nCount!0;nCount--){}}为什么参数要加__IO(volatile)因为不加的话开了优化编译器会觉得这空循环没意义直接删掉延时直接消失。分析周期时我把编译出来的 .axf 反汇编内层循环是这样 4 条指令SUBS r0,r0,#1 ; 1 周期 STR r0,[sp,#0] ; 2 周期 CMP r0,#0 ; 1 周期 BNE loop ; 3 周期(跳转)每次循环 7 个时钟周期。取nCount 10 000 000主频 72MHz10 000 000 × 7 ÷ 72 000 000 ≈ 0.972 秒看起来是1 秒但它不是精确的 1 秒把等待周期和流水线的开销算进去实测大概在1.0 ~ 1.2 秒之间如果把优化等级改成 -O2指令条数变了时间又不一样。所以在没有示波器的情况下我按老师说的用Keil 的软件仿真 逻辑分析仪看波形Options for Target → Debug选Use SimulatorCtrlF5启动调试View → Analysis Windows → Logic Analyzer→Setup…→New依次加PORTA.0、PORTB.0、PORTC.14、PORTC.13Display Range 设 4s按F5跑一会儿就能看到四条依次出现的方波看波形能确认两件事四路高低电平依次出现、互不重叠时序逻辑正确PC13 的波形和另外三路是反的因为它低电平点亮同时也能直观看到每一步的时间并不是标准的 1 秒。结论软件空循环延时不可靠跟优化等级、主频、Flash 等待周期都有关实际项目应该用定时器或 SysTick 中断做时间基准。四、用按键中断让流水灯暂停/恢复最后一个任务要求用HAL 库 按键中断控制流水灯暂停和继续。我在面包板上把 PA1 接了一个按键到 GNDPA1 ──┬── 按键 ── GND └── (内部上拉)平时上拉是高电平按下瞬间变低 →下降沿→ 触发EXTI1 外部中断GPIO_InitTypeDef gpio{0};gpio.PinGPIO_PIN_1;gpio.ModeGPIO_MODE_IT_FALLING;/* 下降沿触发中断 */gpio.PullGPIO_PULLUP;/* 内部上拉 */HAL_GPIO_Init(GPIOA,gpio);HAL_NVIC_SetPriority(EXTI1_IRQn,2U,0U);HAL_NVIC_EnableIRQ(EXTI1_IRQn);中断服务函数只改一个标志位具体动作交给主循环做voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){if(GPIO_PinKEY_PIN){uint32_tnowHAL_GetTick();if((now-s_lastPressTick)200U)/* 200ms 消抖 */{s_lastPressTicknow;g_pause!g_pause;}}}机械按键按下/松开时有几毫秒抖动会产生好几次中断所以必须消抖——我用的是HAL_GetTick() 判断距上次按下是否超过 200ms这种最简单的办法。还有个细节如果延时还是整块的HAL_Delay(1000)按下按键后要等最多 1 秒才响应手感很差。所以我把 1 秒切成 100 份每 10ms 检查一次暂停标志staticvoidDelay_1s_WithPause(void){for(uint32_ti0;i100U;i){while(g_pause!0U){}/* 暂停: 停在这里, 当前 LED 保持点亮 */HAL_Delay(10U);}}这样按下按键最多 10ms 就响应暂停时当前灯保持点亮再按一下就从这个位置继续往下走正好是暂停/恢复继续的效果。五、小结GPIO 的套路是固定的开时钟 → 配模式CRL/CRH 每引脚 4 位→ 读写数据BSRR三种写法本质完全一样区别只是谁去写这些寄存器输出用 BSRR一条指令、原子操作、中断里也安全原理图比代码重要PC13 反相、3mA 限制、PC0~PC12 没引出这些在代码里看不出来软件循环延时不可靠能用逻辑分析仪把误差看出来实际项目用定时器/SysTick中断服务函数要短中断里只改标志位配合分段延时才能响应及时。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门