ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

嵌入式基础第三课——标准外设库 / HAL 库控制 GPIO 流水灯

嵌入式基础第三课——标准外设库 / HAL 库控制 GPIO 流水灯 嵌入式基础第三课——标准外设库 / HAL 库控制 GPIO 流水灯本课是上一课《寄存器方式控制 GPIO 流水灯》之后的两个进阶实验。上一课已经用直接操作寄存器的方式点亮了三盏 LED、搞清了 GPIO 的底层原理本课在此基础上分别改用 ST 官方的**标准外设库StdPeriph**和HAL 库重新实现同一个流水灯并加入两个新内容软件延时标定Keil 逻辑分析仪与按键中断暂停/恢复。三盏灯的接线、闪烁顺序绿灯 PB0 → 蓝灯 PC13 → 红灯 PA0同一时刻只有一盏灯亮与上一课完全相同这里不再赘述电路。一、先搞清楚三种写法是什么关系写 STM32 程序一共有三层「由低到高」的写法这三课正好各对应一层写法怎么配 GPIO特点寄存器方式第二课直接往RCC_APB2ENR、GPIOx_CRL/CRH这些地址写数最短最快但每个地址每个位都要查手册标准外设库本课实验二调GPIO_Init()、RCC_APB2PeriphClockCmd()这类函数封装了一层可读性好但要自己搭工程HAL 库本课实验三CubeMX 图形界面点几下代码自动生成最省事代码量最大、层次最多一句话总结越往上越方便越往下越透明。本课做的就是从中间两层各实现一遍。二、实验二标准外设库方式软件延时 逻辑分析仪1. 实验目的掌握用 STM32F10x 标准外设库StdPeriph配置 GPIO 的方法理解它和寄存器方式的异同掌握软件空循环延时的原理学会估算它的周期学会用Keil 软件仿真的逻辑分析仪观察 GPIO 引脚波形测量并分析实际时序。2. 硬件连接与上一课完全一样LED引脚接法点亮方式 绿灯PB0PB0 → 330Ω → LED → GND高电平点亮 蓝灯PC133.3V → 1kΩ → LED → PC13与板载蓝灯同亮低电平点亮 红灯PA0PA0 → 330Ω → LED → GND高电平点亮3. 标准外设库工程怎么建标准外设库工程要手动搭容易漏的几点新建工程选器件STM32F103C8加启动文件startup_stm32f10x_md.smd 中容量C8T6 用这个、system_stm32f10x.c以及外设驱动stm32f10x_gpio.c、stm32f10x_rcc.c、misc.c魔术棒 → C/C →Define填两个宏漏了必报错STM32F10X_MD告诉库是中容量芯片USE_STDPERIPH_DRIVER打开标准外设库头文件不填的话stm32f10x_conf.h里那些#include全被注释掉GPIO 函数全是「未定义」Include Paths把库的inc、CMSIS、Device\Include目录加进去先用Use Simulator做逻辑分析仪仿真要烧录时再切成 ST-Link。4. 标准外设库 C 代码#includestm32f10x.h/* 标准外设库总头文件 *//* 软件延时粗调系数72MHz、-O0 下空循环每轮约 4 个周期 1ms 72000 周期所以 1ms ≈ 72000/4 18000 次估算值需实测微调 */#defineDELAY_LOOP_PER_MS18000U/* 供 Keil 逻辑分析仪观察的软信号 * Keil 软件仿真器不模拟 STM32 的 GPIO 外设PORTB.0 / GPIOB_ODR 这类 * 内存映射寄存器在仿真器里既不是变量、也不是内核虚拟寄存器逻辑分析仪 * 认不出报 Unknown Signal。所以用三个普通全局变量代替引脚电平 * 逻辑分析仪里直接看这三个变量。统一按 1亮、0灭 记录。 */volatileunsignedintg_led_green0;/* 绿灯 PB0 */volatileunsignedintg_led_blue0;/* 蓝灯 PC13 */volatileunsignedintg_led_red0;/* 红灯 PA0 *//* 软件空循环延时不依赖 SysTick周期不精确 */voidDelay_ms(uint32_tnms){uint32_ti,j;for(i0;inms;i)for(j0;jDELAY_LOOP_PER_MS;j);}voidLED_GPIO_Config(void){GPIO_InitTypeDef GPIO_InitStructure;/* 使能 GPIOA/B/C 时钟标准库函数底层还是写 RCC_APB2ENR */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);/* PB0绿灯50MHz 通用推挽输出 */GPIO_InitStructure.GPIO_PinGPIO_Pin_0;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOB,GPIO_InitStructure);/* PA0红灯参数相同复用结构体 */GPIO_Init(GPIOA,GPIO_InitStructure);/* PC13蓝灯备份域引脚速度只能 2MHz */GPIO_InitStructure.GPIO_PinGPIO_Pin_13;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_2MHz;GPIO_Init(GPIOC,GPIO_InitStructure);/* 上电全灭PA0/PB0 输出 0灭PC13 输出 1低电平点亮置 1灭 */GPIO_ResetBits(GPIOB,GPIO_Pin_0);GPIO_ResetBits(GPIOA,GPIO_Pin_0);GPIO_SetBits(GPIOC,GPIO_Pin_13);}intmain(void){LED_GPIO_Config();while(1){/* 第1拍绿灯亮 */GPIO_SetBits(GPIOB,GPIO_Pin_0);GPIO_ResetBits(GPIOA,GPIO_Pin_0);GPIO_SetBits(GPIOC,GPIO_Pin_13);g_led_green1;g_led_blue0;g_led_red0;Delay_ms(1000);/* 第2拍蓝灯亮 */GPIO_ResetBits(GPIOC,GPIO_Pin_13);GPIO_ResetBits(GPIOB,GPIO_Pin_0);GPIO_ResetBits(GPIOA,GPIO_Pin_0);g_led_green0;g_led_blue1;g_led_red0;Delay_ms(1000);/* 第3拍红灯亮 */GPIO_SetBits(GPIOA,GPIO_Pin_0);GPIO_ResetBits(GPIOB,GPIO_Pin_0);GPIO_SetBits(GPIOC,GPIO_Pin_13);g_led_green0;g_led_blue0;g_led_red1;Delay_ms(1000);}}5.【重点】用 Keil 逻辑分析仪测时序软件仿真不需要硬件用 Keil 自带的逻辑分析仪就能看 GPIO 波形、测周期。操作步骤编译通过后魔术棒 → Debug左上角选Use Simulator点Debug放大镜 / CtrlF5进入仿真菜单View → Analysis Windows → Logic Analyzer点窗口左上角Setup添加三个信号g_led_green、g_led_blue、g_led_red每个的Display Type 选 Bit点RunF5跑几秒再Stop用光标卡两个跳变沿读时间差。⚠️ 这里有两个大坑本课花了很久才解决坑 1直接添加PORTB.0/GPIOB_ODR会报 Unknown Signal。原因是 Keil 软件仿真器不模拟 STM32 的 GPIO 外设这些引脚是内存映射寄存器在仿真器里既不是变量、也不是它认识的内核虚拟寄存器。解决办法就是上面代码里定义的三个全局变量g_led_green / g_led_blue / g_led_red——它们是真实符号逻辑分析仪 100% 能看。坑 2一运行就报access violation at 0x40021000RCC 寄存器。原因是仿真器默认没把外设地址段映射进内存。在Command 窗口输入MAP 0x40000000, 0x400FFFFF READ WRITE即可推荐写成debug.ini在魔术棒 Debug → Initialization File 指定每次进仿真自动执行。6. 软件延时的实际周期是多少软件空循环延时本质上不精确取决于「空循环每轮执行几个时钟周期」而这又由主频和编译器优化等级决定。粗略估算主频 72MHz1ms 72000 个时钟周期for(j0;jN;j);空循环在 -O0 下每轮约 4 个周期所以 1ms 需要 N ≈ 72000/4 18000Delay_ms(1000)约 1 秒。用逻辑分析仪实测把结果填入下表数值以实测为准测量项理论值实测值结论单拍LED 点亮时长1000 ms约 1000 ms略有偏差接近 1s但不完全相等完整流水灯循环周期3000 ms约 3 × 单拍与三拍逻辑一致时序是否错开任何时刻只有一路亮三路方波互不重叠时序正确结论只要三路波形错开、任何时刻只有一路处于「点亮电平」时序就正确单拍实测值和 1000ms 的偏差就是软件延时的误差偏大就调小DELAY_LOOP_PER_MS、偏小就调大。这正是软件延时的特点只能靠实测标定不像 SysTick 天生精确。7. 实验现象烧到真板后绿灯 1 秒 → 蓝灯 1 秒 → 红灯 1 秒循环任何时刻只有一盏亮。三、实验三HAL 库 按键中断暂停/恢复1. 实验目的与思路掌握用HAL 库 STM32CubeMX快速配置工程的方法理解**外部中断EXTI**原理掌握按键中断的配置实现用按键中断控制流水灯的暂停与恢复。思路三盏灯与实验二相同轮流闪 1 秒HAL 里用HAL_Delay(1000)内部是 SysTick周期天生精确加一个按键接PB1另一端接 GND按下为低电平用芯片的外部中断EXTI1检测用一个全局标志g_paused初始 0运行每按一下按键在中断回调里把它取反——按一下暂停、再按一下恢复主循环里用「可暂停延时」代替HAL_Delay每毫秒检查一次g_paused按下就立刻冻住当前灯。2. 硬件连接器件引脚接法 绿灯PB0PB0 → 330Ω → LED → GND 蓝灯PC133.3V → 1kΩ → LED → PC13与板载蓝灯同亮 红灯PA0PA0 → 330Ω → LED → GND 按键PB1PB1 → 按键 → GND按下为低电平3. STM32CubeMX 是什么、怎么生成工程STM32CubeMX是 ST 官方的免费图形化配置工具以前要手写的 GPIO/时钟/中断初始化代码现在在界面上点一点它自动生成完整的 HAL 工程。从官网st.com下载需要注册 myST 账号还要先装 Java。在 CubeMX 里的操作新建工程器件选STM32F103C8引脚图上把PB0、PA0、PC13设为GPIO_Output把PB1设为GPIO_EXTI1System Core → GPIO里把 PB1 的 Mode 设为External Interrupt Mode with Falling edge trigger detection下降沿、Pull 设为Pull-upSystem Core → NVIC勾选EXTI line1 interruptSystem Core → SYS的 Debug 选Serial Wire不设的话 SWD 下载口可能被关掉之后烧不进去时钟因为板子外部晶振起振不了见实验二踩坑不启用 HSE直接用内部 HSI 时钟Project Manager填工程名、路径别带中文和空格Toolchain 选MDK-ARM点GENERATE CODE。 CubeMX 生成完会弹窗问是否打开点Open Project就用 Keil 打开了。工程里文件一大堆我们只需要改main.c和中断相关的stm32f1xx_it.c是自动生成的不用动。4. HAL 库代码生成的main.c里只需在USER CODE区域补几段放在这些区域里重新生成时不会被覆盖① 全局标志USER CODE BEGIN PVvolatileuint8_tg_paused0;/* 0运行 1暂停 */volatileuint32_tg_last_press0;/* 上次按键时间用于消抖 */② 可暂停延时USER CODE BEGIN 0/* 带暂停功能的延时等待 nms 毫秒期间若按下暂停则原地等待直到恢复 */staticvoidPauseable_Delay(uint32_tnms){uint32_tstartHAL_GetTick();while(HAL_GetTick()-startnms){while(g_paused)/* 暂停冻结在当前灯直到按键恢复 */{}}}③ PB0 初始化MX_GPIO_Init里注意必须是输出/*Configure GPIO pin : PB0 */GPIO_InitStruct.PinGPIO_PIN_0;GPIO_InitStruct.ModeGPIO_MODE_OUTPUT_PP;/* 千万别是 GPIO_MODE_IT_RISING */GPIO_InitStruct.PullGPIO_NOPULL;GPIO_InitStruct.SpeedGPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOB,GPIO_InitStruct);④ 主循环USER CODE BEGIN 3while(1){/* 第1拍绿灯亮 */HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);Pauseable_Delay(1000);/* 第2拍蓝灯亮 */HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);Pauseable_Delay(1000);/* 第3拍红灯亮 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);Pauseable_Delay(1000);}⑤ 按键中断回调USER CODE BEGIN 4带 50ms 消抖voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){uint32_tnow;if(GPIO_PinGPIO_PIN_1)/* 按键 PB1 */{nowHAL_GetTick();if(now-g_last_press50)/* 50ms 内只认一次滤掉抖动 */{g_last_pressnow;g_paused!g_paused;/* 按一下暂停/恢复切换 */}}}EXTI1_IRQHandler是 CubeMX 自动生成在stm32f1xx_it.c里的它会调用HAL_GPIO_EXTI_IRQHandler()最终转到上面的回调函数无需手写。5. 实验现象上电后流水灯正常跑绿灯 → 蓝灯 → 红灯每 1 秒切换按一下按键流水灯立即停在当前那盏灯上暂停再按一下从停的位置继续往后闪恢复。四、这两次实验踩过的坑现象原因解决逻辑分析仪加信号报 Unknown SignalKeil 仿真器不模拟 GPIO 外设PORTB.0是内存映射寄存器用普通全局变量当「软信号」观察仿真报 access violation at 0x40021000仿真器默认没映射外设地址段MAP 0x40000000, 0x400FFFFF READ WRITE烧录报 Connection refused due to device mismatch板子芯片是国产仿片 CS32F103C8T6ID 0x2BA01477Debug→Settings→Pack 取消 Enable 校验真板延时比 1 秒长 9 倍外部 8MHz 晶振没起振退回内部 8MHz HSIDELAY_LOOP_PER_MS改小或改用 HSI 时钟下载后要按复位才运行没勾 Reset and RunDebug→Settings→Flash Download 勾选 Reset and Run编译报 Failed to check out a licensecompiler5ARM Compiler 5 的 license 失效换成 ARM Compiler 6V6烧录后只有蓝灯亮流水灯代码没加进主循环停在初始状态把代码加进USER CODE 3PB0 不亮CubeMX 里 PB0 误配成中断输入改成 GPIO_Output /GPIO_MODE_OUTPUT_PP按键不灵敏、只能停红灯① 按键抖动② 暂停判断只在整轮循环开头检查一次① 50ms 软件消抖② 用可暂停延时替代HAL_Delay五、实验心得这两次实验把同一个流水灯用标准外设库和 HAL 库各实现了一遍加上第二课的寄存器方式三种写法都走了一遍收获很大。最大的体会还是**「越往上越方便越往下越透明」**。寄存器方式每个地址每个位都要自己查手册写起来最累但也让我最清楚芯片到底在做什么标准外设库把寄存器包成GPIO_Init这类函数读起来舒服多了到了 HAL 库CubeMX 点几下就生成一整个工程省事是省事但出问题时那一层层函数点进去看反而更晕。所以这门课安排三个实验从底层往上走是有道理的。第二个深刻的点是软件延时是真的不可靠。它的周期取决于主频和编译器只能靠逻辑分析仪实测标定而 HAL 的HAL_Delay基于 SysTick天生精确。这一课我还亲身撞上了一个更隐蔽的坑——主频不对延时差 9 倍板子外部晶振没起振芯片就静默退回 8MHz 内部时钟灯一下慢了 9 倍。这让我真正意识到硬件和软件是分不开的不看板子实际状态、不测时钟光改代码是没用的。第三个点是中断和消抖。按键中断让程序不用一直轮询响应快了很多但机械按键会抖动不消抖就「时灵时不灵」。而且我还发现暂停判断的位置很关键放在整轮循环开头按下后要等一轮走完才生效结果「只能停在红灯」改成在延时里每毫秒检查一次才真正做到「停在当前灯」。这个 bug 是我自己观察现象一步步定位出来的印象特别深。最后还有一个「意外收获」这一路踩的坑里有好几个其实不是代码的错——仿片报 device mismatch、ARM Compiler 5 的 license 失效都会挡住编译下载。这让我学会了一个排查习惯出问题时先分清是代码错、还是工具/硬件错能少走很多弯路。
返回列表