各位看官老爷们,点击关注不迷路哟。你的点赞、收藏,一键三连,是我持续更新的动力哟!!!
目录
普中STM32F103ZET6开发攻略
1. GPIO端口实验——点亮LED灯
1.1 实验目的
1.2 实验原理
1.3 实验环境和器材:
1.4 实验思路
1.5 实验代码
1.5.1 LED相关代码
1.5.2 delay相关代码
1.6 实验思考和拓展
1.6.1 如何调整LED灯的闪烁频率?
1.6.2 如何实现呼吸灯效果?
1.6.2.1 非 PWM 实现呼吸灯的原理
1.6.2.2 具体实现方法(以 STM32 为例)
1. 亮度等级划分
2. 控制亮灭时间比例
1.6.2.3 代码实例:
1.6.3 除了使用简单延时外,STM32还有哪些更加精准的延时方式?
1.7 注意事项
了解STM32F10x微控制器的GPIO口结构和基本操作
掌握STM32标准库函数对GPIO的配置和使用方法
学会使用GPIO控制LED的亮灭,实现LED基本显示效果
掌握简单延时函数的编写方法
GPIO原理
STM32的GPIO(通用输入/输出端口)用于外设信号的输入输出控制。STM32F10x系列GPIO具有以下特性:
(1) 每个I/O端口有16个可独立配置的I/O位
(2) 支持8种不同的工作模式
模式分类 | 具体模式 | 核心特点 | 典型应用 |
---|---|---|---|
输入模式 | 浮空输入 | 无内部上下拉,电平由外部决定 | 外部信号采集(需外部上下拉) |
上拉输入 | 内部上拉,默认高电平 | 按键输入(低电平有效) | |
下拉输入 | 内部下拉,默认低电平 | 按键输入(高电平有效) | |
模拟输入 | 连接 ADC,禁用数字输入功能 | ADC 模数转换 | |
输出模式 | 开漏输出 | 需外部上拉,支持线与逻辑 | I2C 总线、电平转换 |
推挽输出 | 直接输出高低电平,驱动能力强 | LED 控制、普通数字信号 | |
开漏复用功能 | 外设驱动,需外部上拉 | SPI/I2C 外设输出 | |
推挽复用功能 | 外设驱动,直接输出高低电平 | USART/CAN 外设输出 |
通过配置 GPIO 的模式寄存器(CRL/CRH)和输出类型寄存器,可灵活选择上述工作模式,满足不同外设的控制需求。
(3) 每个I/O口可以产生外部中断
(4) 位设置/复位寄存器,支持原子位操作
LED控制原理
LED (发光二极管) 是一种单向导电器件,只有在正向偏置时才会发光。根据开发板硬件设计,LED灯通常采用如下接法:
(1) 共阳极接法:LED阳极接VCC,阴极接单片机GPIO,GPIO输出低电平时LED点亮;
(2) 共阴极接法:LED阴极接地,阳极接单片机GPIO,GPIO输出高电平时LED点亮;
本实验中,LED灯采用的是共阳极接法。
电脑:Keil5+Vstudio
硬件资源:普中玄武F103开发板(主控芯片:STM32F103ZET6)
硬件接线图如下所示:
由上图可知:
DS0即LED0、DS1即LED1,分别连接着:PB5和PE5
LED0连接到GPIOB的Pin5引脚,LED1连接到GPIOE的Pin5引脚
为了完成实验目的:
我们需要再项目工程中新建两个“库函数”:LED.c+delay.c
头文件:
//led.h #ifndef _led_H #define _led_H #include "stm32f10x.h" #include "stm32f10x_gpio.h" // 包含GPIO相关的函数和宏定义 #include "stm32f10x_rcc.h" // 包含RCC(时钟控制)相关的函数和宏定义 void LED_init(void); void LED0_On(void); void LED0_Off(void); void LED1_On(void); void LED1_Off(void); void LED0_Toggle(void); void LED1_Toggle(void); #endif
源文件:
//led.c #include "led.h" void LED_init(void) { // 使能 GPIOB 和 GPIOE 的时钟(几乎所有外设(包括 GPIO)在使用前都需要先使能对应的时钟) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; // 配置 PB5 为推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // 配置 PE5 为推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOE, &GPIO_InitStructure); // 初始状态 GPIO_SetBits(GPIOB, GPIO_Pin_5); GPIO_SetBits(GPIOE, GPIO_Pin_5); } void LED0_On(void) { GPIO_ResetBits(GPIOB, GPIO_Pin_5); } void LED0_Off(void){ GPIO_SetBits(GPIOB, GPIO_Pin_5); } void LED1_On(void) { GPIO_ResetBits(GPIOE, GPIO_Pin_5); } void LED1_Off(void) { GPIO_SetBits(GPIOE, GPIO_Pin_5); } void LED0_Toggle(void) { GPIOB->ODR ^= GPIO_Pin_5; } void LED1_Toggle(void) { GPIOE->ODR ^= GPIO_Pin_5; }
因为我们这里是直接输出高低电平:所以采用的是推挽输出的方式,由上文可知:推完输出直接输出高低电平,驱动能力强。
初始状态需要设置成为高电平,由上文中的硬件接线图可知,LED(发光二极管)是在正向偏置时才会发光,所以应该是“低电平有效”
头文件:
#ifndef __DELAY_H #define __DELAY_H #include "stm32f10x.h" void Delay_Init(void); void Delay_ms(u32 nms); #endif
源文件:
#include "delay.h" static u32 fac_ms = 0; void Delay_Init(void) { SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); // HCLK/8 fac_ms = SystemCoreClock / 8000; } void Delay_ms(u32 nms) { u32 temp; SysTick->LOAD = nms * fac_ms; SysTick->VAL = 0x00; SysTick->CTRL = 0x01; do { temp = SysTick->CTRL; } while ((temp & 0x01) && !(temp & (1 << 16))); SysTick->CTRL = 0x00; SysTick->VAL = 0X00; }
LED灯闪烁的频率,归根结底还是由灯光的亮和灭的时间间隔决定的,亮和灭的时间间隔越小,那么频率就越高。
实现方法:
使用定时器实现精确延时
若使用自定义简单延时函数(如void Delay_ms(uint32_t ms)
),直接调整传入的延时参数。 例:原闪烁周期为 0.6 秒(亮 0.3 秒 + 灭 0.3 秒),若改为亮 0.1 秒 + 灭 0.1 秒,则周期变为 0.2 秒,频率提高 3 倍。
// 原代码(周期0.6秒) LED0_ON(); // 亮 Delay_ms(300); LED0_OFF(); // 灭 Delay_ms(300); // 调整后(周期0.2秒) LED0_ON(); Delay_ms(100); Delay_ms(100);
使用定时器实现精确延时:
简单延时函数依赖系统时钟和循环次数,可通过计算循环次数与时钟周期的关系提高精度。 例:若系统时钟为 72MHz,一个循环约耗时 1 微秒,则延时 1 毫秒需循环 1000 次。
使用定时器实现精确延时:
利用 STM32 的定时器(如 TIM2、TIM3)产生精确中断,通过中断计数实现延时,避免 CPU 空转,提升系统效率。 步骤: ① 初始化定时器,配置为定时中断模式(如定时 1 毫秒); ② 在中断服务函数中维护一个全局计数器; ③ 主函数中通过判断计数器值实现延时。
方法:软件模拟渐变效果
呼吸灯的本质是让人眼感知到 LED 亮度的线性渐变(从暗到亮或从亮到暗)。人眼对亮度的感知具有惰性,当 LED 在极短时间内(如几毫秒)频繁亮灭时,会将亮灭时间的平均值视为 “亮度”。因此,即使不使用硬件 PWM,也可以通过软件控制 LED 的亮灭占空比(即导通时间与周期的比例),逐步改变占空比来模拟亮度变化。
LED 为共阳极接法(低电平点亮),通过以下步骤实现亮度渐变:
将亮度从 0%(全灭)到 100%(最亮)划分为若干等级(如 256 级),用一个变量(如brightness
)表示当前等级(范围:0~255)。
brightness=0
:LED 全灭(高电平)。
brightness=255
:LED 全亮(低电平)。
每个亮度等级对应一个周期总时间(如 10ms),其中:
点亮时间 = (brightness / 255) × 周期总时间
熄灭时间 = 周期总时间 - 点亮时间
通过循环改变brightness
的值(如从 0 逐渐增加到 255,再逐渐减小到 0),并在每个等级中控制 LED 的亮灭时间,即可实现渐变效果。
// 定义LED引脚(共阳极,低电平点亮) #define LED_GPIO_PORT GPIOB #define LED_PIN GPIO_Pin_5 // 亮度等级(0~255) uint8_t brightness = 0; uint8_t direction = 1; // 1表示亮度增加,-1表示亮度减少 void breathe_led(void) { // 亮度渐变方向控制(到达边界时反转方向) if (brightness == 255) direction = -1; if (brightness == 0) direction = 1; brightness += direction; // 计算亮灭时间(周期总时间设为10ms,可调整) uint16_t on_time = (brightness * 10) / 255; // 点亮时间(ms) uint16_t off_time = 10 - on_time; // 熄灭时间(ms) // 控制LED点亮 GPIO_ResetBits(LED_GPIO_PORT, LED_PIN); // 低电平点亮 delay_ms(on_time); // 点亮持续时间 // 控制LED熄灭 GPIO_SetBits(LED_GPIO_PORT, LED_PIN); // 高电平熄灭 delay_ms(off_time); // 熄灭持续时间 } // 主循环中调用 while (1) { breathe_led(); }
与 PWM 方案的对比
特性 | 非 PWM 软件模拟 | 硬件 PWM |
---|---|---|
实现复杂度 | 需编写循环逻辑和延时函数,代码较繁琐 | 直接配置定时器 PWM 模式,代码简洁 |
CPU 占用率 | 高(需实时控制亮灭时间,阻塞延时) | 低(定时器硬件自动更新占空比) |
亮度平滑度 | 受延时精度限制,可能有闪烁感 | 平滑(硬件驱动,频率稳定) |
适用场景 | 简单实验、对实时性要求低的场合 | 高要求场景(如电机调速、精密调光) |
定时器延时(精确且不阻塞 CPU)
原理:利用定时器的计数功能,通过配置自动重装载值(TIMx_ARR)和预分频器(TIMx_PSC)设定定时时间,结合中断或查询方式实现延时。
优点:精度高(可达微秒级),延时期间 CPU 可执行其他任务。
SysTick 定时器延时(系统级延时)
原理:利用 STM32 内部的 SysTick 定时器(系统滴答定时器),基于系统时钟(如 72MHz)产生精确中断。
优点:无需额外外设,由 Cortex-M 内核直接支持,适合系统级延时。
实时操作系统(RTOS)任务调度
原理:在 RTOS(如 FreeRTOS、uCOS)中,通过任务调度函数(如vTaskDelay()
)实现延时,本质是释放 CPU 资源给其他任务。
优点:多任务并行处理,延时期间可执行其他任务,适合复杂系统。
总结与拓展
简单场景:优先使用 SysTick 或定时器查询方式,兼顾精度与代码复杂度。
复杂场景:引入 RTOS 或定时器中断,实现多任务协同和精确时序控制。
进阶实践:结合 DMA(直接内存访问)与定时器,实现无 CPU 参与的自动 PWM 输出,进一步提升效率。
(1) 使用标准库函数时,需要注意头文件的包含和依赖关系。
(2) 共阳极LED的控制逻辑:低电平点亮,高电平熄灭。
(3) 简单延时函数的精确度受系统时钟和编译优化的影响。
(4) GPIO操作前必须先使能对应的外设时钟。
文章有写的不当的地方,欢迎在评论区中指正修改。如果感觉文章实用对你有帮助,欢迎点赞收藏和关注,你的点赞关注就是我动力,大家一起学习进步。
有不懂的可以在评论区里提出来哟,博主看见后会及时回答的。