拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

流水灯控制实验

流水灯控制实验

实验1 流水灯控制实验

一、实验目的

  1. 掌握STM32 GPIO寄存器底层原理,理解GPIO端口寄存器地址、配置参数。
  2. 掌握寄存器方式编写GPIO输出驱动,实现LED流水灯。
  3. 学会STM32标准外设库工程建立、库文件添加方法,标准库方式驱动LED。
  4. 掌握HAL库+外部中断,实现按键控制流水灯暂停/继续。
  5. 学会Keil软件仿真逻辑分析仪,观测GPIO输出波形,测量闪烁周期。

二、实验环境

  • 硬件:STM32F103C8T6最小系统板、3只红绿蓝LED、杜邦线、面包板
  • 软件:Keil MDK5,STM32标准外设库V3.5,STM32CubeMX,HAL库,ST-Link下载器

三、实验原理

STM32F103的GPIO端口可配置为通用推挽输出模式,输出高低电平控制LED亮灭。
LED一端串联电阻接GPIO引脚,另一端接GND:GPIO输出低电平,LED点亮;输出高电平,LED熄灭。
最小系统自带PC13LED,原理一致,PC13为推挽输出控制板载LED。

GPIO关键寄存器(以GPIOC为例):

  1. GPIOC_CRL:端口低8位配置寄存器(引脚0~7)
  2. GPIOC_CRH:端口高8位配置寄存器(引脚8~15,PC13在此寄存器)
  3. GPIOC_ODR:端口输出数据寄存器,置1输出高电平,置0输出低电平
  4. RCC_APB2ENR:APB2外设时钟使能寄存器,开启GPIO端口时钟

任务1:寄存器方式实现流水灯(包含PC13板载LED)

1.1 程序设计思路

  1. 开启GPIOA、GPIOB、GPIOC的APB2外设时钟。
  2. 配置GPIOA、GPIOB、GPIOC对应LED引脚为推挽输出模式,最大速度50MHz。
  3. 循环依次点亮单个LED,其余熄灭,软件延时1s,不断轮换,实现流水灯。
  4. 将板载PC13 LED加入流水灯序列,与外接3个LED轮流闪烁。

寄存器地址:

  • RCC基地址:0x40021000,RCC_APB2ENR偏移:0x18
  • GPIOA基地址:0x40010800
  • GPIOB基地址:0x40010C00
  • GPIOC基地址:0x40011000
    寄存器偏移:CRL=0x00,CRH=0x04,ODR=0x0C

1.2 寄存器版C代码(带详细注释)

#include"stm32f10x.h"// 简单软件延时函数,粗略延时1svoidDelay_s(uint32_ts){uint32_ti,j;for(i=0;i<s;i++)for(j=0;j<7200000;j++);}voidLED_GPIO_Init(void){// 1.开启GPIOA/GPIOB/GPIOC时钟 RCC_APB2ENR*(volatileuint32_t*)(0x40021000+0x18)|=(1<<2)|(1<<3)|(1<<4);// bit2:GPIOA, bit3:GPIOB, bit4:GPIOC// PA0推挽输出 50MHz,CRL寄存器*(volatileuint32_t*)(0x40010800+0x00)&=~(0x0F<<0);//清空PA0*(volatileuint32_t*)(0x40010800+0x00)|=(0x03<<0);//0011 推挽输出50M// PB0推挽输出 50MHz*(volatileuint32_t*)(0x40010C00+0x00)&=~(0x0F<<0);*(volatileuint32_t*)(0x40010C00+0x00)|=(0x03<<0);// PC13推挽输出 50MHz,CRH寄存器(PC13对应bit20~23)*(volatileuint32_t*)(0x40011000+0x04)&=~(0x0F<<20);*(volatileuint32_t*)(0x40011000+0x04)|=(0x03<<20);}intmain(void){LED_GPIO_Init();while(1){// PA0亮,其余灭*(volatileuint32_t*)(0x40010800+0x0C)&=~(1<<0);*(volatileuint32_t*)(0x40010C00+0x0C)|=(1<<0);*(volatileuint32_t*)(0x40011000+0x0C)|=(1<<13);Delay_s(1);// PB0亮,其余灭*(volatileuint32_t*)(0x40010800+0x0C)|=(1<<0);*(volatileuint32_t*)(0x40010C00+0x0C)&=~(1<<0);*(volatileuint32_t*)(0x40011000+0x0C)|=(1<<13);Delay_s(1);// PC13板载LED亮,其余灭*(volatileuint32_t*)(0x40010800+0x0C)|=(1<<0);*(volatileuint32_t*)(0x40010C00+0x0C)|=(1<<0);*(volatileuint32_t*)(0x40011000+0x0C)&=~(1<<13);Delay_s(1);}}任务2:标准外设库版本流水灯2.1工程项目创建、添加标准库详细步骤1.打开 Keil MDK5,新建Project,选择芯片STM32F103C8T6,创建工程文件夹,命名LED_STDLIB。2.从 STM32F10x_StdPeriph_Lib 库中复制:inc头文件文件夹、src源文件文件夹到工程目录。3.Keil 工程中,点击Manage Project Items,新建StdLib分组,添加需要的库.c文件:stm32f10x_gpio.c、stm32f10x_rcc.c。4.打开OptionsforTarget → C/C++,添加头文件路径..\inc;添加宏定义STM32F10X_MD,USE_STDPERIPH_DRIVER。5.新建main.c,添加用户代码,编译工程。2.2标准库代码#include"stm32f10x.h"voidDelay_s(uint32_ts){uint32_ti,j;for(i=0;i<s;i++)for(j=0;j<7200000;j++);}voidLED_Init(void){GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);//PA0GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//PB0GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;GPIO_Init(GPIOB,&GPIO_InitStruct);//PC13GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13;GPIO_Init(GPIOC,&GPIO_InitStruct);}intmain(void){LED_Init();while(1){GPIO_ResetBits(GPIOA,GPIO_Pin_0);GPIO_SetBits(GPIOB,GPIO_Pin_0);GPIO_SetBits(GPIOC,GPIO_Pin_13);Delay_s(1);GPIO_SetBits(GPIOA,GPIO_Pin_0);GPIO_ResetBits(GPIOB,GPIO_Pin_0);GPIO_SetBits(GPIOC,GPIO_Pin_13);Delay_s(1);GPIO_SetBits(GPIOA,GPIO_Pin_0);GPIO_SetBits(GPIOB,GPIO_Pin_0);GPIO_ResetBits(GPIOC,GPIO_Pin_13);Delay_s(1);}}2.3软件延时周期&Keil 逻辑分析仪仿真1.软件循环延时特点:软件空循环延时不精确,会受编译器优化、CPU 时钟影响,只是粗略1秒。2.Keil 仿真步骤:-开启 Debug:Use Simulator,进入 Debug 模式。-打开Logic Analyzer,添加 GPIOA->ODR、GPIOB->ODR、GPIOC->ODR 信号。-运行仿真,观测波形高低电平翻转时刻。3.时序分析: 逻辑分析仪可以捕获 GPIO 引脚电平跳变,测量高电平持续时间、低电平持续时间。 软件延时的实际周期不等于1s,存在误差;可根据波形读出真实闪烁周期。 任务3:HAL 库+外部中断,按键控制流水灯暂停/继续#include"stm32f1xx_hal.h"//全局标志uint8_trun_flag=1;//1运行,0暂停GPIO_HandleTypeDef hgpio_led;GPIO_HandleTypeDef hgpio_key;voidSystemClock_Config(void);staticvoidMX_GPIO_Init(void);voidEXTI0_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);}//外部中断回调函数voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){if(GPIO_Pin==GPIO_PIN_0){run_flag=!run_flag;//翻转启停状态}}voidDelay_ms(uint32_tms){HAL_Delay(ms);}intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();while(1){if(run_flag==1){HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);Delay_ms(1000);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);Delay_ms(1000);HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);Delay_ms(1000);}}}staticvoidMX_GPIO_Init(void){GPIO_InitTypeDef GPIO_InitStruct={0};__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();//LED输出GPIO_InitStruct.Pin=GPIO_PIN_0;GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Speed=GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);HAL_GPIO_Init(GPIOB,&GPIO_InitStruct);GPIO_InitStruct.Pin=GPIO_PIN_13;HAL_GPIO_Init(GPIOC,&GPIO_InitStruct);//按键PA0 外部中断下降沿GPIO_InitStruct.Pin=GPIO_PIN_0;GPIO_InitStruct.Mode=GPIO_MODE_IT_FALLING;GPIO_InitStruct.Pull=GPIO_PULLUP;HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);HAL_NVIC_EnableIRQ(EXTI0_IRQn);HAL_NVIC_SetPriority(EXTI0_IRQn,0,0);}voidSystemClock_Config(void){RCC_OscInitTypeDef RCC_OscInitStruct={0};RCC_ClkInitTypeDef RCC_ClkInitStruct={0};RCC_OscInitStruct.OscillatorType=RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState=RCC_HSE_ON;RCC_OscInitStruct.HSEPredivValue=RCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.HSIState=RCC_HSI_ON;RCC_OscInitStruct.PLL.PLLState=RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource=RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMUL=RCC_PLL_MUL9;if(HAL_RCC_OscConfig(&RCC_OscInitStruct)!=HAL_OK){Error_Handler();}RCC_ClkInitStruct.ClockType=RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider=RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider=RCC_HCLK_DIV2;RCC_ClkInitStruct.APB2CLKDivider=RCC_HCLK_DIV1;if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct,FLASH_LATENCY_2)!=HAL_OK){Error_Handler();}}voidError_Handler(void){__disable_irq();while(1){}}#ifdefUSE_FULL_ASSERTvoidassert_failed(uint8_t*file,uint32_tline){}#endif/* USE_FULL_ASSERT */四、实验结果与分析1.![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7a02300e52404533994dcad8a9890e63.png)(https://i-blog.csdnimg.cn/direct/37bf46c6098f4511b3471e99dd40caca.png)(https://i-blog.csdnimg.cn/direct/735b791fd29e4e8eb518df89869671cc.png)寄存器版本:程序下载后,PA0、PB0、PC13 板载 LED 轮流点亮,间隔约 1s,流水灯正常运行,验证寄存器配置正确。2.标准库版本:调用库函数初始化 GPIO,实现相同流水效果;Keil 逻辑分析仪观测 GPIO 波形,高低电平翻转对应 LED 亮灭。软件空循环延时存在误差,实际周期和理论1s 有偏差。3.HAL 库外部中断版本:按下按键,流水灯暂停;再次按下,恢复流水,实现启停控制。![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/98269a85014d47be8d803effcdb42527.png)(https://i-blog.csdnimg.cn/direct/564a56fccc6f40e8a25afa308a438ca2.png)(https://i-blog.csdnimg.cn/direct/c852b53fa86140dbb688ca8f973d3657.png)(https://i-blog.csdnimg.cn/direct/ebfb093e04cc4b8891f878bffdef447d.png)五、实验总结5.寄存器方式直接操作内存地址,底层可控,适合理解 STM32 硬件原理,代码简洁但可读性差。6.标准外设库封装底层寄存器,开发更快,工程搭建需要手动添加库文件。7.HAL 库更抽象,结合外部中断可以实现按键事件驱动,中断不会阻塞主循环。8.软件延时精度低,精确定时推荐使用定时器中断;Keil 逻辑分析仪可以无硬件示波器情况下观测 GPIO 输出波形,调试 GPIO 时序。
返回列表