一、实验目的熟悉 STM32F103C8T6 GPIO 端口的基本结构、寄存器和时钟使能方法。掌握使用 C 语言直接操作 GPIO 寄存器控制 LED 的方法。掌握 STM32 标准外设库的工程建立、文件添加、GPIO 初始化和 LED 控制方法。学会利用 Keil 软件仿真观察 GPIO 输出波形并通过波形判断程序运行状态。掌握 HAL 库 GPIO 配置和 EXTI 外部中断的基本使用方法。实现流水灯运行、按键暂停和恢复等功能加深对 STM32 嵌入式程序结构的理解。二、实验任务一基于寄存器方式控制流水灯2.1 实验要求使用 STM32F103C8T6 最小系统核心板和 3 只红、绿、蓝 LED分别连接 GPIOA、GPIOB、GPIOC 端口。采用 GPIO 寄存器方式编程使 3 只 LED 轮流点亮每次间隔约 1 秒。本实验不调用 STM32 GPIO 标准外设库直接通过寄存器地址完成 GPIO 时钟开启、GPIO 工作模式配置以及 GPIO 输出控制。2.2 硬件连接本实验采用如下连接LEDSTM32 引脚连接方式有效电平红色 LEDPA0PA0 → 限流电阻 → LED → GND高电平亮绿色 LEDPB0PB0 → 限流电阻 → LED → GND高电平亮蓝色 LEDPC0PC0 → 限流电阻 → LED → GND高电平亮每个 LED 建议串联约 220Ω1kΩ 限流电阻防止 GPIO 输出电流过大。2.3 GPIO 寄存器地址与参数STM32F103 的 GPIO 外设挂接在 APB2 总线上。实验使用 GPIOA、GPIOB、GPIOC。2.3.1 GPIO 基地址外设基地址GPIOA0x40010800GPIOB0x40010C00GPIOC0x400110002.3.2 GPIO 主要寄存器以 GPIOA 为例寄存器偏移地址GPIOA 地址功能CRL0x000x40010800配置 PA0PA7CRH0x040x40010804配置 PA8PA15IDR0x080x40010808输入数据寄存器ODR0x0C0x4001080C输出数据寄存器BSRR0x100x40010810置位/复位BRR0x140x40010814复位LCKR0x180x40010818配置锁定GPIOB、GPIOC 的寄存器结构相同只是基地址不同。2.3.3 RCC_APB2ENRRCC 的 APB2 外设时钟使能寄存器地址为RCC_APB2ENR 0x40021018其中bit2IOPAENGPIOA 时钟使能bit3IOPBENGPIOB 时钟使能bit4IOPCENGPIOC 时钟使能。因此需要设置RCC_APB2ENR bit2 1 RCC_APB2ENR bit3 1 RCC_APB2ENR bit4 12.4 GPIO 配置参数分析STM32F1 GPIO 的 CRL/CRH 中每一个 GPIO 引脚占 4 bitMODE[1:0]选择输出速度CNF[1:0]选择输入/输出类型。本实验需要普通推挽输出因此设置MODE 10输出速度 2 MHz CNF 00通用推挽输出所以每个 LED 引脚对应的 4 位配置值为0010对于 PA0、PB0、PC0都只需要修改 CRL 的最低 4 位。2.5 程序设计思路程序执行过程如下系统复位 ↓ 开启 GPIOA、GPIOB、GPIOC 时钟 ↓ 配置 PA0、PB0、PC0 为推挽输出 ↓ 关闭全部 LED ↓ PA0 输出高电平红灯亮 ↓ 延时约 1 s ↓ 关闭红灯打开绿灯 ↓ 延时约 1 s ↓ 关闭绿灯打开蓝灯 ↓ 延时约 1 s ↓ 重复上述过程程序中使用ODR控制 GPIO 输出电平。为了让每次只有一个 LED 点亮在切换 LED 时先将三个引脚全部清零再设置当前 LED 对应的位。2.6 实验一完整程序代码本实验直接操作 STM32F103C8T6 的 GPIO 和 RCC 寄存器完整main.c如下#include./SYSTEM/sys/sys.h#include./SYSTEM/delay/delay.h#defineLED_RED_PIN(10)#defineLED_GREEN_PIN(10)#defineLED_BLUE_PIN(114)#defineLED_RED_ON()(GPIOA-BSRRLED_RED_PIN)#defineLED_RED_OFF()(GPIOA-BRRLED_RED_PIN)#defineLED_GREEN_ON()(GPIOB-BSRRLED_GREEN_PIN)#defineLED_GREEN_OFF()(GPIOB-BRRLED_GREEN_PIN)#defineLED_BLUE_ON()(GPIOC-BSRRLED_BLUE_PIN)#defineLED_BLUE_OFF()(GPIOC-BRRLED_BLUE_PIN)voidled_gpio_init(void){RCC-APB2ENR|(12)|(13)|(14);GPIOA-CRL~(0xF0);GPIOA-CRL|(0x30);GPIOB-CRL~(0xF0);GPIOB-CRL|(0x30);GPIOC-CRH~(0xF24);GPIOC-CRH|(0x324);GPIOB-BRRLED_GREEN_PIN;GPIOC-BRRLED_BLUE_PIN;GPIOA-BRRLED_RED_PIN;}intmain(void){sys_stm32_clock_init(9);delay_init(72);led_gpio_init();while(1){LED_RED_ON();LED_GREEN_OFF();LED_BLUE_OFF();delay_ms(1000);LED_RED_OFF();LED_GREEN_ON();LED_BLUE_OFF();delay_ms(1000);LED_RED_OFF();LED_GREEN_OFF();LED_BLUE_ON();delay_ms(1000);}}2.7 实验结果程序下载到 STM32F103C8T6 后3 只 LED 应按照红 → 绿 → 蓝 → 红 → 绿 → 蓝 → ...的顺序循环点亮每个状态保持约 1 秒。如果观察到 LED 亮灭顺序正确说明 GPIO 时钟、GPIO 模式、ODR 输出和延时程序均正常。三、实验任务二使用 STM32 标准外设库实现流水灯3.1 实验要求在实验一基础上改用 STM32 标准外设库方式控制 GPIO使 3 只 LED 轮流闪烁每个状态间隔约 1 秒。同时要求创建标准外设库工程添加.c和.h文件使用 Keil 的逻辑分析仪观察 GPIO 输出波形分析输出波形的周期、频率和高低电平时间。3.2 标准外设库工程创建过程按照下面的结构建立工程添加步骤新建 Keil ARM 工程。选择器件STM32F103C8。建立USER、SYSTEM、CMSIS等分组。添加标准外设库的 GPIO、RCC 等源文件。添加对应头文件路径到 Keil 的 Include Paths。添加启动文件startup_stm32f10x_md.s。编写main.c。编译工程确保没有 Error。下载程序并观察 LED。3.3 标准外设库初始化方法标准外设库方式不需要直接写 GPIO 寄存器地址而是通过库函数完成初始化。典型流程RCC_APB2PeriphClockCmd(...,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin...;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_2MHz;GPIO_Init(...);LED 输出状态可以使用GPIO_SetBits(...);GPIO_ResetBits(...);这种方式比直接操作寄存器更加直观也方便后续修改。3.4 LED 闪烁周期分析本实验采用“3 个 LED 中同时只有一个 LED 点亮”的方式红灯亮 1 s 绿灯亮 1 s 蓝灯亮 1 s 红灯亮 1 s ……因此对于单独观察某一个 GPIO例如 PA0高电平约 1 s 低电平约 2 s一个完整周期约为T ≈ 1 s 1 s 1 s 3 s因此单个 LED GPIO 的理论频率约为f 1 / T ≈ 1 / 3 Hz ≈ 0.333 Hz高电平时间约 1 s占空比约D ≈ 1 / 3 × 100% ≈ 33.3%三个 GPIO 合起来观察时会发现每隔约 1 秒就有一个 GPIO 从低变高同时上一个 GPIO 从高变低。3.5 Keil 逻辑分析仪观察方法在 Keil Debug 模式中打开逻辑分析相关功能把以下信号加入观察GPIOA-ODR GPIOB-ODR GPIOC-ODR如果需要观察单个 GPIO 位可以重点关注PA0 PB0 PC03.6 实验二代码编写main.c/* * 实验2标准外设库方式控制四灯流水灯 * PA0 红灯高电平点亮 * PB0 绿灯高电平点亮 * PC14 蓝灯高电平点亮 * PC13 板载灯低电平点亮 * 每盏亮 1 秒轮流闪烁 **/#includestm32f10x.h#includedelay.h/* 外接灯高电平点亮 */#defineLED_RED_ON()GPIO_SetBits(GPIOA,GPIO_Pin_0)#defineLED_RED_OFF()GPIO_ResetBits(GPIOA,GPIO_Pin_0)#defineLED_GREEN_ON()GPIO_SetBits(GPIOB,GPIO_Pin_0)#defineLED_GREEN_OFF()GPIO_ResetBits(GPIOB,GPIO_Pin_0)#defineLED_BLUE_ON()GPIO_SetBits(GPIOC,GPIO_Pin_14)#defineLED_BLUE_OFF()GPIO_ResetBits(GPIOC,GPIO_Pin_14)/* 板载灯低电平点亮 */#defineLED_BOARD_ON()GPIO_ResetBits(GPIOC,GPIO_Pin_13)#defineLED_BOARD_OFF()GPIO_SetBits(GPIOC,GPIO_Pin_13)voidLED_GPIO_Init(void){GPIO_InitTypeDef GPIO_InitStructure;/* 使能 GPIOA / GPIOB / GPIOC 时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);/* 推挽输出50MHz */GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;/* PA0 */GPIO_InitStructure.GPIO_PinGPIO_Pin_0;GPIO_Init(GPIOA,GPIO_InitStructure);/* PB0 */GPIO_Init(GPIOB,GPIO_InitStructure);/* PC13、PC14 */GPIO_InitStructure.GPIO_PinGPIO_Pin_13|GPIO_Pin_14;GPIO_Init(GPIOC,GPIO_InitStructure);/* 初始全部熄灭 */LED_RED_OFF();LED_GREEN_OFF();LED_BLUE_OFF();LED_BOARD_OFF();}intmain(void){/* 启动文件已调用 SystemInit() 配置 72MHz无需再配置时钟 */delay_init(72);LED_GPIO_Init();while(1){/* 第1拍红灯亮 */LED_RED_ON();LED_GREEN_OFF();LED_BLUE_OFF();LED_BOARD_OFF();delay_ms(1000);/* 第2拍绿灯亮 */LED_RED_OFF();LED_GREEN_ON();LED_BLUE_OFF();LED_BOARD_OFF();delay_ms(1000);/* 第3拍蓝灯亮 */LED_RED_OFF();LED_GREEN_OFF();LED_BLUE_ON();LED_BOARD_OFF();delay_ms(1000);/* 第4拍板载PC13灯亮 */LED_RED_OFF();LED_GREEN_OFF();LED_BLUE_OFF();LED_BOARD_ON();delay_ms(1000);}}delay.h#ifndef__DELAY_H#define__DELAY_HvoidDelay_ms(unsignedintms);#endifdelay.c#includestm32f10x.h#includedelay.hvoidDelay_ms(unsignedintms){unsignedinti;unsignedintj;for(i0;ims;i){for(j0;j8000;j){__NOP();}}}3.7 实验结果下载程序后LED 能够按照红、绿、蓝的顺序循环运行。使用 Keil 逻辑分析仪观察时可以看到三个 GPIO 的输出波形按照固定时间间隔依次变化。实际周期会受到系统时钟、延时函数实现方式以及仿真执行速度的影响因此允许存在一定误差。如果出现波形完全不变化应重点检查GPIO 时钟是否开启GPIO 引脚是否配置为输出引脚号是否与实际硬件连接一致LED 是否为低电平有效延时函数是否正常工程中是否正确加入标准外设库源文件。四、实验任务三HAL 库 按键外部中断控制流水灯暂停/恢复4.1 实验要求在实验二基础上改用 STM32 HAL 库并增加一个按键。实现流水灯正常运行 ↓ 按下一次按键 ↓ 流水灯暂停 ↓ 再次按下按键 ↓ 流水灯继续运行4.2 硬件连接LED 仍采用LED引脚红色 LEDPA0绿色 LEDPB0蓝色 LEDPC0按键使用器件引脚配置KEYPB12上拉输入下降沿触发 EXTI按键按下时 PB12 被拉低因此采用GPIO_MODE_IT_FALLING触发外部中断。4.3 HAL 初始化流程程序主要流程HAL_Init() ↓ 系统时钟配置 ↓ GPIO 初始化 ↓ 配置 PB12 为下降沿 EXTI ↓ 配置 EXTI15_10_IRQn ↓ 启动流水灯 ↓ 按键产生中断 ↓ HAL_GPIO_EXTI_Callback() ↓ 修改暂停/运行状态4.4 按键中断处理为了避免机械按键产生的抖动导致一次按键触发多次中断程序中加入简单的软件消抖。基本思想if(HAL_GetTick()-last_tick150){paused!paused;last_tickHAL_GetTick();}即两次有效按键之间至少间隔约 150 ms。暂停变量volatileuint8_tpaused0;其中paused 0流水灯正常运行paused 1流水灯暂停。4.5 实验结果正常运行时红 → 绿 → 蓝 → 红 → 绿 → 蓝 → ...按下按键后流水灯保持当前状态不再继续切换。再次按下按键后流水灯从当前状态继续运行。实验表明EXTI 外部中断能够及时响应按键操作HAL 库能够完成 GPIO 和中断相关配置。4.5 实验三代码编写main.c/* * 实验3HAL库 按键外部中断 控制流水灯暂停/恢复 * PA0 红灯高电平点亮 * PB0 绿灯高电平点亮 * PC14 蓝灯高电平点亮 * PC13 板载灯低电平点亮 * PA1 按键一端接PA1一端接GND按下触发中断 * 按一次按键 → 暂停流水灯再按一次 → 恢复继续 **/#include./SYSTEM/sys/sys.h#include./SYSTEM/delay/delay.h#include./BSP/LED/led.h#include./BSP/EXTI/exti.hexternvolatileuint8_tg_run_flag;/* 运行标志在 exti.c 中定义 *//* 根据当前步数点亮对应的LED */voidled_show(uint8_tstep){LED_RED_OFF();LED_GREEN_OFF();LED_BLUE_OFF();LED_BOARD_OFF();switch(step){case0:LED_RED_ON();break;/* 红灯亮 */case1:LED_GREEN_ON();break;/* 绿灯亮 */case2:LED_BLUE_ON();break;/* 蓝灯亮 */case3:LED_BOARD_ON();break;/* 板载灯亮 */}}intmain(void){uint8_tled_step0;/* 当前亮灯步数 0~3 */uint32_tlast_tick0;/* 上一次切换的时间戳 */HAL_Init();/* HAL库初始化 */sys_stm32_clock_init(RCC_PLL_MUL9);/* 系统时钟 72MHz */delay_init(72);/* 延时初始化 */led_init();/* LED初始化 */extix_init();/* 按键外部中断初始化 */led_show(led_step);/* 初始点亮第一盏灯 */last_tickHAL_GetTick();/* 记录起始时间 */while(1){if(g_run_flag)/* 运行状态每隔1秒切换到下一盏灯 */{if(HAL_GetTick()-last_tick1000){last_tickHAL_GetTick();led_step(led_step1)%4;led_show(led_step);}}/* 暂停状态什么都不做保持当前LED状态等待按键中断恢复 */}}stm32f1xx_it.c#includestm32f1xx_hal.h#includestm32f1xx_it.h/* * EXTI10~15 共用中断入口。 * PB12 对应 EXTI12因此按键触发时进入这里。 */voidEXTI15_10_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);}4.6 实验结果正常运行时红 → 绿 → 蓝 → 红 → 绿 → 蓝 → ...按下按键后流水灯保持当前状态不再继续切换。再次按下按键后流水灯从当前状态继续运行。实验表明EXTI 外部中断能够及时响应按键操作HAL 库能够完成 GPIO 和中断相关配置。五、三个实验程序结构对比项目实验一实验二实验三GPIO 控制方式直接寄存器标准外设库HAL 库GPIOAPA0PA0PA0GPIOBPB0PB0PB0GPIOCPC0PC0PC0按键无无PB12中断无无EXTILED 功能轮流点亮轮流闪烁可暂停/恢复延时软件延时软件延时HAL_GetTick调试方式下载观察Keil 逻辑分析仪下载观察 按键测试六、实验总结与心得通过本次实验我对 STM32 GPIO 的控制过程有了更加直观的认识。实验一直接使用寄存器完成 GPIO 配置虽然代码相对底层但能够清楚看到 GPIO 时钟、模式寄存器以及输出数据寄存器之间的关系。特别是 STM32F1 的 GPIO 配置需要通过 CRL/CRH 设置 MODE 和 CNF这让我对单片机 GPIO 初始化的底层过程有了更具体的理解。实验二改用 STM32 标准外设库以后GPIO 初始化的代码明显更加容易阅读。通过GPIO_Init()、GPIO_SetBits()和GPIO_ResetBits()等函数可以减少直接计算寄存器地址和位域的工作量。同时使用 Keil 逻辑分析仪观察 GPIO 波形后可以把程序中的“延时 1 秒”转换成实际的高低电平时间、周期和频率进行分析这对于理解程序运行过程比较有帮助。实验三进一步使用 HAL 库和 EXTI 外部中断实现按键控制。与前两个实验相比这个实验不仅需要控制 LED还需要处理外部事件。按键按下后程序通过中断回调改变流水灯的运行状态不需要一直在主循环中查询按键状态。实际调试过程中还可以发现机械按键存在抖动所以加入简单的软件消抖能够明显改善使用效果。三个实验从寄存器、标准外设库到 HAL 库实际上对应了不同层次的 STM32 编程方法。寄存器方式更接近硬件本身标准外设库和 HAL 库则逐渐提高了程序的抽象程度。通过这次实验我不仅实现了 LED 流水灯也进一步熟悉了 Keil 工程建立、GPIO 配置、延时、逻辑分析以及外部中断等基本操作。后续如果继续进行 STM32 项目开发还可以在这个实验基础上增加多个按键、不同流水灯模式、定时器中断以及 PWM 呼吸灯等功能。