基于寄存器&标准外设库的LED流水灯

前言

1. 确定实现LED流水灯代码的逻辑

需要的功能有:
① 点亮与熄灭流水灯–实验的首要功能
② 计时器–用于以时间控制led效果

2. 准备工作

① LED灯珠
② STM32电路板
本次选择STM32F103C8T6
③ 标准外设库
④ 选择控制的引脚:本次选择A10,B5以及C13引脚

3. 算法思路

① 设计延时函数

延时函数,用于以秒为单位进行延时

② 启用GPIO的时钟:

在STM32微控制器中,每个GPIO端口都有对应的时钟使能位,需要启用相应的GPIO端口时钟才能进行配置和控制。GPIOA、GPIOB、和GPIOC分别对应不同的GPIO端口。

这是因为时钟管理是微控制器内部各个模块的重要组成部分,它可以用来控制不同模块的运行时钟。通过启用相应的GPIO端口时钟,你允许这些GPIO端口正常工作,并可以进行配置和控制。

具体来说,通过启用GPIO端口的时钟,你可以:

配置GPIO引脚的工作模式(比如输入、输出、复用功能等)。
控制GPIO引脚的电平状态(点亮LED、控制外部设备)。
使用GPIO引脚来连接外部电路或器件,如传感器、开关等。
监控GPIO引脚的输入状态,以获取外部信息。
因此,启用GPIO端口的时钟是确保你可以正确配置和使用微控制器的GPIO引脚的重要步骤。启用GPIOA、GPIOB和GPIOC端口的时钟,使它们可用于配置和控制对应的A10,B5,C13引脚

③配置引脚为通用推挽输出

GPIO支持4种输入模式(浮空输入、上拉输入、下拉输入、模拟输入)和4种输出模式(开漏输出、开漏复用输出、推挽输出、推挽复用输出)。同时,GPIO还支持三种最大翻转速度(2MHz、10MHz、50MHz)。

每个I/O口可以自由编程,但I/O口寄存器必须按32位字被访问。

1.GPIO_Mode_AIN 模拟输入

模拟输入模式下,I/O端口的模拟信号(电压信号,而非电平信号)直接模拟输入到片上外设模块,比如ADC模块等等

2.GPIO_Mode_IN_FLOATING 浮空输入

浮空输入模式下,I/O端口的电平信号直接进入输入数据寄存器。也就是说,I/O的电平状态是不确定的,完全由外部输入决定;如果在该引脚悬空(在无信号输入)的情况下,读取该端口的电平是不确定的

3.GPIO_Mode_IPD 下拉输入

下拉输入模式下,I/O端口的电平信号直接进入输入数据寄存器。但是在I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在低电平;并且在I/O端口输入为高电平的时候,输入端的电平也还是高电平

4.GPIO_Mode_IPU 上拉输入

上拉输入模式下,I/O端口的电平信号直接进入输入数据寄存器。但是在I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在高电平;并且在I/O端口输入为低电平的时候,输入端的电平也还是低电平

5.GPIO_Mode_Out_OD 开漏输出

开漏输出模式下,通过设置位设置/清除寄存器或者输出数据寄存器的值,途经N-MOS管,最终输出到I/O端口。这里要注意N-MOS管,当设置输出的值为高电平的时候,N-MOS管处于关闭状态,此时I/O端口的电平就不会由输出的高低电平决定,而是由I/O端口外部的上拉或者下拉决定;当设置输出的值为低电平的时候,N-MOS管处于开启状态,此时I/O端口的电平就是低电平。同时,I/O端口的电平也可以通过输入电路进行读取;注意,I/O端口的电平不一定是输出的电平

6.GPIO_Mode_Out_PP 推挽输出

推挽输出模式下,通过设置位设置/清除寄存器或者输出数据寄存器的值,途经P-MOS管和N-MOS管,最终输出到I/O端口。这里要注意P-MOS管和N-MOS管,当设置输出的值为高电平的时候,P-MOS管处于开启状态,N-MOS管处于关闭状态,此时I/O端口的电平就由P-MOS管决定:高电平;当设置输出的值为低电平的时候,P-MOS管处于关闭状态,N-MOS管处于开启状态,此时I/O端口的电平就由N-MOS管决定:低电平。同时,I/O端口的电平也可以通过输入电路进行读取;注意,此时I/O端口的电平一定是输出的电平

7.GPIO_Mode_AF_OD 复用开漏输出

开漏复用输出模式,与开漏输出模式很是类似。只是输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的

8.GPIO_Mode_AF_PP 复用推挽输出

推挽复用输出模式,与推挽输出模式很是类似。只是输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的

Q:为何要设置为推挽输出模式?
点亮LED或控制其他输出设备时,使用通用推挽输出模式是常见的选择,因为这种输出模式提供了许多优点:

  1. 高驱动能力:通用推挽输出模式能够提供较高的输出电流,可以驱动各种负载,包括LED、继电器、电机和其他外部设备。这意味着它能够轻松地控制不同类型的负载。

  2. 双向控制:通用推挽输出模式允许引脚的状态既能设为高电平又能设为低电平。这使得你可以轻松地在需要时点亮或熄灭LED,也可以控制其他双向负载。

  3. 抗干扰能力:通用推挽输出模式对于外部干扰(例如噪声)具有一定的抗干扰能力,因为它在高电平和低电平状态都有一个相对稳定的输出电压。

  4. 低功耗:与开漏输出模式相比,通用推挽输出模式通常在输出高电平时具有更低的功耗。这有助于在需要时节省电能。

  5. 多功能性:通用推挽输出模式可以用于多种用途,因此适用于多种应用场景,包括输入捕获、输出比较等。

④ 通过输出高低电平控制LED

电平(Voltage Level)是指电压的状态,通常用于描述数字电路中的信号状态。在数字电路中,通常有两个电平,即高电平(High Level)和低电平(Low Level)。

高电平:通常表示为逻辑1或者是高电压状态,它对应于正电压,通常表示为“1”或“真”。
低电平:通常表示为逻辑0或者是低电压状态,它对应于负电压,通常表示为“0”或“假”。
在数字电路中,通过电压的高低电平来表示和传递信息。例如,通常情况下,LED(Light Emitting Diode,发光二极管)的亮灭状态与控制它的引脚的电平有关。LED通常需要连接到电源(高电平)和地(低电平)来工作,因此控制引脚的高低电平就能控制LED的亮灭。

具体来说,LED是一种半导体器件,当它的两端之间施加电压时,会发光。LED通常是双向极性器件,其中一个引脚是阳极(Anode),另一个引脚是阴极(Cathode)。通过将阳极连接到高电平(正电压)而将阴极连接到低电平(地或负电压),电流会在LED中流动,激活LED并导致其发光。

因此,通过控制连接LED的引脚的高低电平,你可以实现LED的亮灭控制。当引脚处于高电平时,LED亮;当引脚处于低电平时,LED灭。这是数字电路中基本的亮灭控制原理。当然,使灯亮是高电平还是低电平,需要看具体的线路,下文会提到。

一、 基于寄存器点亮LED流水灯

1.创建工程

新建工程在这里插入图片描述
在这里插入图片描述
选择对应型号
在这里插入图片描述
勾选
在这里插入图片描述
新建main.c
‘
在这里插入图片描述
添加到Source Group1
在这里插入图片描述

2. 编写代码

Ⅰ 代码

先上代码再解释:

#include "stm32f10x.h"
void SysTick_Handler(void) {
    // 什么也不做,只是为了满足SysTick中断的要求
}

void delay_s(uint32_t s) {
    // 配置SysTick定时器为1毫秒滴答
    SysTick_Config(SystemCoreClock / 1000);

    // 计算所需的滴答数
    uint32_t ticks = s * 1000;

    while (ticks) {
        if (SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) {
            // 如果SysTick倒计时到0
            ticks--;
        }
    }

    // 关闭SysTick定时器
    SysTick->CTRL = 0;
}
int main(void) {
    // 启用GPIOA、GPIOB和GPIOC时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN;

    // 配置引脚为通用推挽输出
    GPIOA->CRH |= GPIO_CRH_MODE10 ;
    GPIOA->CRH &= ~GPIO_CRH_CNF10;

    GPIOB->CRL |= GPIO_CRL_MODE5;
    GPIOB->CRL &= ~GPIO_CRL_CNF5;

    GPIOC->CRH |= GPIO_CRH_MODE13;
    GPIOC->CRH &= ~GPIO_CRH_CNF13;
	

    while (1) {
        // 点亮A10引脚,同时熄灭 B5, PC13引脚
			  GPIOA->BSRR = GPIO_BSRR_BS10;
        GPIOB->BSRR = GPIO_BSRR_BR5;
			  GPIOC->BSRR = GPIO_BSRR_BS13;
        delay_s(1);  // 延时一秒

        // 点亮B5引脚,同时熄灭A10, PC13引脚
			  GPIOB->BSRR = GPIO_BSRR_BS5;
        GPIOA->BSRR =  GPIO_BSRR_BR10;
			  GPIOC->BSRR =  GPIO_BSRR_BS13;
        
        delay_s(1);  // 延时一秒

        // 点亮PC13引脚,同时熄灭A10, B5引脚
			  GPIOC->BSRR = GPIO_BSRR_BR13;
        GPIOB->BSRR =  GPIO_BSRR_BR5;
			  GPIOA->BSRR =  GPIO_BSRR_BR10;
        
        delay_s(1);  // 延时一秒
    }

    return 0;
}

Ⅱ 算法思路解释

①延时函数:

这段代码是用于实现毫秒级别的延迟功能,基于STM32中的SysTick定时器。以下是对这段代码的详细解释:

void SysTick_Handler(void): 这是SysTick定时器的中断处理函数。在这个特定的实现中,这个函数实际上是一个空函数,什么也不做。它的存在是为了满足SysTick中断的要求,因为SysTick定时器在每个滴答(tick)结束时都会引发一个中断,而中断处理函数必须存在,即使它不执行任何操作。

void delay_s(uint32_t s): 这个函数用于实现以秒为单位的延迟。它的功能如下:

首先,它配置SysTick定时器为1毫秒滴答。通过 SysTick_Config 函数,它将SysTick定时器的时钟频率设置为系统核心时钟频率(SystemCoreClock)除以1000,以便每毫秒引发一次SysTick中断。
接着,函数计算所需的滴答数(ticks),通过将所需的秒数(s)乘以1000来得到。这是因为每秒有1000毫秒,所以将秒数转换为毫秒级别。
进入一个while循环,检查SysTick定时器是否到达0。SysTick定时器的倒计时从一个大整数开始,每经过一个滴答(tick),倒计时减小1。
如果SysTick->CTRL 寄存器中的COUNTFLAG位被置位(指示定时器已到达0),则减小 ticks 变量的值。
循环会一直运行,直到 ticks 变为0,即所需的延迟时间已经经过。
最后,函数关闭SysTick定时器,通过将SysTick->CTRL 寄存器设置为0,以停止SysTick定时器的计数。
这个delay_s 函数允许你在程序中添加毫秒级别的延迟,它使用SysTick定时器来实现,因此延迟时间是相对精确的,不受其他代码的影响。需要注意的是,具体的延迟时间可能会受到系统核心时钟频率的影响

② 设置推挽输出

GPIOA->CRH |= GPIO_CRH_MODE10;: 这一行代码设置GPIOA端口的控制寄存器高字(Control Register High)中与引脚A10对应的位,以配置A10引脚的输出模式。具体来说,它执行了按位或运算,将 GPIO_CRH_MODE10 定义的位设置为1。

GPIO_CRH_MODE10 是一个宏,它表示要配置的引脚的输出模式。通常,这里的 MODE 位是2位字段,用于指定引脚的输出模式。在这种情况下,MODE10 代表A10引脚。通过将其设置为1,你指示该引脚应该被配置为通用推挽输出模式。
GPIOA->CRH &= ~GPIO_CRH_CNF10;: 这一行代码用于清除GPIOA端口的控制寄存器高字中与引脚A10对应的CNF位。具体来说,它执行了按位与非运算,将 GPIO_CRH_CNF10 定义的位设置为0。

GPIO_CRH_CNF10 是一个宏,它表示要配置的引脚的控制寄存器配置模式。通常,这里的 CNF 位是2位字段,用于指定引脚的配置模式。通过将其设置为0,你指示该引脚应该被配置为通用推挽输出模式的配置模式

③ 控制引脚高低电平

当使用STM32微控制器的GPIO引脚控制LED或其他外部设备时,你可以通过直接操作寄存器来控制引脚的电平状态。具体来说:

GPIO[A\B\C]->BSRR = GPIO_BSRR_BS[引脚序号] 用于将引脚置为高电平。
GPIO[A\B\C]->BSRR = GPIO_BSRR_BR[引脚序号] 用于将引脚置为低电平。

根据LED与引脚连接方式不同,决定了控制LED亮起的高低电平选择不同:
①若LED正极连接引脚,则高电平为亮
②若LED负极连接引脚,则低电平为亮,此时LED正极需要连接电源
③PC13引脚需要输出低电平才能使内置灯亮起

3. 测试

将代码编译烧录进stm32中,运行效果如图:
在这里插入图片描述

二、通过标准库函数控制LED流水灯

1. 准备环境

Ⅰ 下载并配置模板

① 下载

点击链接进入官网下载:
https://www.st.com/zh/embedded-software/stm32-standard-peripheral-libraries.html
选择型号:
这里我选择F1
在这里插入图片描述
选择下载版本,这里我选择get latest,最新版本
在这里插入图片描述
点击接受:
在这里插入图片描述
初次下载会填写个人信息,邮箱填真实的就行,验证后通过发送到邮箱的链接即可下载

②配置

解压下载好的压缩包:
在这里插入图片描述
新建一个文件夹,然后在其中新建以下四个文件夹:
在这里插入图片描述
然后根据表格复制粘贴文件到对应文件夹里:

文件路径文件名目标路径
.\STM32F10x_StdPeriph_Lib_V3.6.0\Libraries\CMSIS\CM3\CoreSupportcore_cm3.c、core_cm3.hCore
.\STM32F10x_StdPeriph_Lib_V3.6.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm根据型号选择,本次选择’startup_stm32f10x_md.s’Core
.\STM32F10x_StdPeriph_Lib_V3.6.0\Libraries\STM32F10x_StdPeriph_Driverinc和src两个文件夹Lib
.\STM32F10x_StdPeriph_Lib_V3.6.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10xstm32f10x.h、system_stm32f10x.c、system_stm32f10x.c.hUser
.\STM32F10x_StdPeriph_Lib_V3.6.0\Project\STM32F10x_StdPeriph_Templatemain.c(也可以自己创建)、stm32f10x_conf.h、stm32f10x_it.c、stm32f10x_it.hUser

至此一个项目模板配置好了

Ⅱ. 新建项目

① 创建一个项目的文件夹,将配置好的模板的所有文件夹粘贴到该文件夹下

在这里插入图片描述

② 创建项目

在KEIL中新建项目,把项目路径选择到User文件夹内
T

在这里插入图片描述
选择F103C8T6
在这里插入图片描述
不勾选任何一项,直接点ok
在这里插入图片描述

④ 配置项目环境

点击"品"字按钮
在这里插入图片描述
把Groups栏的内容修改为模板的文件夹名称

在这里插入图片描述

然后每个group按照下图添加文件:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
点击魔法棒,在C/C++栏中更改如下:
在这里插入图片描述

STM32F10X_MD,USE_STDPERIPH_DRIVER
注意STM32F10X_MD的MD是根据选择的启动文件设置的,本次选择的是startup_stm32f10x_md.s,所以后缀是MD

然后在main函数里简单输入一点代码编译测试一下

#include "stm32f10x.h"
int main(void)
{

}

运行后发现报四个错

在这里插入图片描述
本人并没有分析错误的根本原因,而是根据错误定位到发生错误的文件,并把文件中报错词条删除掉,再编译便不会再次报错
在这里插入图片描述
在这里插入图片描述

2. 编写代码

Ⅰ 上代码

#include "stm32f10x.h"

// 定义LED连接的引脚
#define LED_A10 GPIO_Pin_10
#define LED_B5  GPIO_Pin_5
#define LED_C13 GPIO_Pin_13
void Delay(uint32_t nCount) {
    for (uint32_t i = 0; i < nCount; i++) {
        __NOP();
    }
}
void GPIO_Init_LEDs(void) {
    // 使能相应的GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);

    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

    // 初始化A10引脚
    GPIO_InitStruct.GPIO_Pin = LED_A10;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 初始化B5引脚
    GPIO_InitStruct.GPIO_Pin = LED_B5;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // 初始化C13引脚
    GPIO_InitStruct.GPIO_Pin = LED_C13;
    GPIO_Init(GPIOC, &GPIO_InitStruct);
}

int main(void) {
    // 初始化系统时钟等设置

    GPIO_Init_LEDs();

    while (1) {
        // 依次点亮LED灯
//亮A10
			  GPIO_SetBits(GPIOA, LED_A10);
        GPIO_ResetBits(GPIOB,LED_B5);
			
				GPIO_SetBits(GPIOC,LED_C13);
			
			Delay(2000000);
			
//亮B5
			GPIO_SetBits(GPIOB,LED_B5);
			GPIO_ResetBits(GPIOA, LED_A10);
			
			GPIO_SetBits(GPIOC,LED_C13);
			Delay(2000000);
			
//亮C13
			GPIO_ResetBits(GPIOB,LED_B5);
			GPIO_ResetBits(GPIOA, LED_A10);
			
			
			GPIO_ResetBits(GPIOC,LED_C13);
			Delay(2000000);
    }
}

Ⅱ 解释

①延时函数

与上一段代码原理相似,都是重复运行无功能的空代码循环,以做到延时效果

② 使能GPIO时钟

定义了一个名为 GPIO_Init_LEDs 的函数,它的目的是初始化控制LED的GPIO引脚。以下是代码的逐行解释:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);

这一行代码用于启用与GPIOA、GPIOB和GPIOC端口相关的时钟。在STM32微控制器中,时钟管理是非常重要的,要想使用特定的GPIO端口,首先必须启用相应的时钟。这行代码通过将三个时钟位的逻辑或操作(|)来同时启用这三个GPIO端口的时钟。

GPIO_InitTypeDef GPIO_InitStruct;

这一行代码定义了一个名为 GPIO_InitStruct 的结构体变量,用于配置GPIO引脚的初始化参数。这个结构体包含了一些配置选项,如GPIO引脚的模式、速度等。

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;

:这一行代码将 GPIO_Mode 字段设置为 GPIO_Mode_Out_PP,表示要配置这些引脚为通用推挽输出模式。通用推挽输出模式适用于控制LED等需要点亮和熄灭的外部设备。

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

这一行代码将 GPIO_Speed 字段设置为 GPIO_Speed_50MHz,表示引脚的输出速度为50MHz。这通常是一个合适的设置,可以提供足够的输出速度来控制LED等外部设备。

  1. 接下来的三段代码用于分别初始化A10、B5和C13引脚。每段代码都将 GPIO_InitStruct 结构体的 GPIO_Pin 字段设置为相应的LED引脚(LED_A10、LED_B5、LED_C13),然后通过调用 GPIO_Init 函数来将这些配置应用到相应的GPIO端口(GPIOA、GPIOB、GPIOC)。这样就完成了对这些引脚的初始化。
③ 控制引脚高低电平
GPIO_SetBits(GPIO[A/B/C], LED_[引脚序号,如A10]);//高电平
GPIO_ResetBits(GPIO[A/B/C], LED_[引脚序号,如A10]);//低电平

3. 测试

在这里插入图片描述

4. 分析

LED的亮/灭周期主要受到以下因素的影响:

程序中的延时函数:代码使用了自定义的延时函数 Delay 和 delay_s 来实现LED的亮/灭控制。这些延时函数的准确性决定了LED的闪烁周期。

系统时钟频率:程序中使用的 SystemCoreClock 决定了延时函数中的延时时间单位。系统时钟频率的设置在程序启动时由芯片的时钟配置决定。更高的时钟频率可以提供更精确的延时。

其他中断和处理:在实时系统中,可能会有其他中断和处理程序运行,它们可能会影响LED的闪烁周期。这些中断的优先级和执行时间可能会导致延时的变化

使用软件仿真逻辑分析仪分析

① 点击魔法棒,按照图示进行修改

在这里插入图片描述

② 点击调试按钮

在这里插入图片描述

③ 选择逻辑分析仪

在这里插入图片描述

熟悉窗口:
在这里插入图片描述

④ 选择实时显示波形

在这里插入图片描述

⑤ 点击setup,输入一个引脚进行监听

在这里插入图片描述

⑥ 修改参数

在这里插入图片描述

⑦ 运行

记得鼠标移动至表格内,然后滑动滚轮进行缩放调整,调整到合适grid的大小再进行观测
在这里插入图片描述
在这里插入图片描述

可以看到A10引脚的周期大致为1.5秒,即高电平0.5秒,而后低电平1秒。所以得知程序中的一个灯的点亮时间实际上是0.5秒,修改代码中Delay函数的参数后再进行观测:
在这里插入图片描述
可以看到现在A10的周期为3秒(高电平1秒,低电平2秒)

我们进一步观察其余的B5,C13引脚:

在这里插入图片描述
在这里插入图片描述

参考链接:

  1. 【STM32】GPIO工作原理(八种工作方式超详细分析,附电路图)
  2. STM32标准外设库(标准库)官网下载方法,附带2021最新标准固件库下载链接
  3. 【keil 5】进阶玩法:逻辑分析仪的使用(软件仿真)
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值