- 保护二极管
- 弱上下拉电阻
- 施密特触发器简介
- P-MOS & N-MOS管简介
- 高阻态
- 1、输入浮空
- 2、输入上拉
- 3、输入下拉
- 4、模拟功能
- 5、开漏输出
- 6、开漏式复用功能
- 7、推挽输出
- 8、推挽式复用功能
- F4/F7/H7系列和F1系列的GPIO差异点
- F1系列的GPIO相关寄存器
- F4/F7/H7系列的GPIO相关寄存器
- ODR和BSRR寄存器控制输出有什么区别?
- 相关HAL库函数简介
- 关键结构体简介
- 1、编程实战:点亮一个LED灯
- 查找函数原型
- 代码实现
- 2、解读例程源码:跑马灯实验
- 1、编程实战:通过一个按键控制一个LED灯亮灭
- 2、解读例程源码:按键输入实验
一、什么是GPIO?
GPIO,全称为 General Purpose Input Output(通用输入输出),是一种通用的数字输入/输出端口。在嵌入式系统中,GPIO被设计为灵活的引脚,可以被配置为输入或输出,以满足不同的应用需求。
主要作用包括:
输入(采集):
- GPIO 可以配置为输入模式,用于采集外部器件的信息。例如,连接传感器、开关或按钮等外部设备,通过 GPIO 读取这些设备的状态。
输出(控制):
- GPIO 也可以配置为输出模式,用于控制外部器件的工作。例如,连接LED、继电器、驱动器等外部设备,通过 GPIO 输出电平信号来控制这些设备的工作状态。
在嵌入式系统中,GPIO 的灵活性使得它成为连接和控制外部设备的重要工具。通过软件配置,可以根据具体应用要求将 GPIO 引脚用于输入或输出。GPIO 的状态(高电平或低电平)可以通过读写相应的寄存器来控制,从而实现对外部设备的控制和监测。
需要注意的是,虽然 GPIO 具有通用性,但在具体的嵌入式平台和微控制器上,不同的 GPIO 引脚可能会有不同的特性和限制。在使用 GPIO 时,需要查阅相应的芯片手册或技术文档,了解每个 GPIO 引脚的功能、电气特性以及可用性。
二、STM32 GPIO简介
2.1、GPIO特点
GPIO(General Purpose Input Output)具有一些通用的特点,这些特点可能因芯片型号和制造商而有所差异,但通常包括以下几个方面:
IO口数量:
- 不同型号的微控制器,芯片,或者开发板上的 GPIO 数量可能不同。通过查阅相应的选型手册、数据手册或者开发板文档,可以了解每个型号上可用的 GPIO 引脚数量和功能。
快速翻转:
- GPIO 可以快速地翻转其输出状态。每次翻转的速度可能取决于具体的芯片型号和工作频率。一些微控制器甚至可以在极短的时间内完成翻转,例如在几个时钟周期内。
中断支持:
- 每个 GPIO 引脚都可以配置为中断触发源。当引脚的电平状态变化(上升沿、下降沿、或者边沿触发)时,可以触发相应的中断服务程序。这使得 GPIO 可以用于实时事件处理。
工作模式:
- GPIO 支持多种工作模式,具体的工作模式可能因芯片型号而异。常见的工作模式包括输入模式、输出模式、复用功能模式(例如,配置 GPIO 为串口、SPI、I2C 等功能)、模拟输入模式等。
需要注意的是,这些特点可能会在不同的芯片系列和型号之间有所不同。因此,在使用 GPIO 时,始终建议参考相关的芯片手册、数据手册或者开发板文档,以确保对 GPIO 的理解和使用是正确的。
2.2、GPIO电气特性
在STM32系列微控制器中,GPIO的电气特性包括工作电压范围、识别电压范围和输出电流等,具体如下:
工作电压范围:
- STM32系列的工作电压范围为 2V ≤ VDD ≤ 3.6V。这表示微控制器的工作电源电压应在这个范围内,以保证正常的工作和性能。
GPIO识别电压范围:
- GPIO 的电平识别电压范围(VIL 和 VIH)取决于使用的电平标准,例如COMS端口和FT(TTL)。
- 对于COMS端口:-0.3V ≤ VIL ≤ 1.164V,1.833V ≤ VIH ≤ 3.6V。
- 对于FT(TTL):具体的电平标准可能会有所不同,但通常符合TTL电平标准,其中VIL < 0.8V,VIH > 2.0V。
- GPIO 的电平识别电压范围(VIL 和 VIH)取决于使用的电平标准,例如COMS端口和FT(TTL)。
GPIO输出电流:
- 单个 GPIO 引脚的最大输出电流为 25mA。这表示当引脚配置为输出模式,并且输出高电平或低电平时,可以提供的最大电流为25mA。超过这个电流可能会损坏引脚或器件。
需要注意的是,这些电气特性可能会因不同的 STM32 系列、型号和具体的芯片而有所不同。因此,在使用 GPIO 时,始终建议查阅相应的芯片手册或数据手册,以确保对 GPIO 的电气特性有准确的理解。
2.3、GPIO引脚分布
STM32微控制器引脚主要分为不同的类型,包括电源引脚、晶振引脚、复位引脚、下载引脚、BOOT引脚和GPIO引脚。以下是对这些引脚类型的简要说明:
电源引脚:
- 电源引脚用于提供芯片工作所需的电源电压。常见的电源引脚包括 VDD(供应电压)、VSS(地)等。VDD 是供电电压,VSS 是地。
晶振引脚:
- 晶振引脚用于连接外部晶振,以提供微控制器的时钟信号。常见的晶振引脚包括 XTAL1(晶振输入)和XTAL2(晶振输出)。
复位引脚:
- 复位引脚用于将微控制器复位到初始状态。复位引脚通常标记为RESET或NRST,通过对该引脚施加低电平来触发复位操作。
下载引脚:
- 下载引脚通常用于通过编程器或调试器将程序下载到微控制器。下载引脚通常标记为SWDIO、SWCLK等,用于串行线调试(SWD)。
BOOT引脚:
- BOOT引脚用于选择启动模式。通过对BOOT引脚的配置,可以选择从不同的存储器区域(例如Flash或System Memory)启动。常见的BOOT引脚包括BOOT0和BOOT1。
GPIO引脚:
- GPIO引脚是通用输入输出引脚,可以根据需要配置为输入或输出。GPIO引脚用于连接外部设备、传感器或执行控制操作。
这些引脚类型在STM32系列微控制器中的具体引脚编号和功能可能会有所不同,因此在使用时需要参考具体的芯片手册或数据手册。不同的型号和系列的STM32微控制器可能会有不同的引脚分布和功能。
以下是提到的一些引脚的简要说明:
PC13 - TAMPER - RTC:
- PC13 是一个GPIO引脚,通常用于处理与RTC(实时时钟)模块中的TAMPER(防篡改)功能相关的输入。此引脚可能被用于检测与RTC有关的特定事件或状态。
PC14 - OSC 32_IN:
- PC14 是一个晶振输入引脚,用于连接外部的32kHz晶振。这样的晶振通常用于RTC模块,提供低功耗时钟。
PC15 - OSC 32_OUT:
- PC15 是晶振输出引脚,用于连接外部的32kHz晶振。这个引脚可能用于输出晶振的振荡信号。
PD0 - OSC_IN:
- PD0 是另一个晶振输入引脚,通常用于连接外部的晶振。这个引脚可能用于提供微控制器的主时钟。
PD1 - OSC_OUT:
- PD1 是晶振输出引脚,通常用于连接外部的晶振。这个引脚可能用于输出晶振的振荡信号。
这些引脚的具体功能和使用方式可能取决于你使用的STM32微控制器型号以及在你的应用中的具体配置。请参考相应的芯片手册或数据手册,以获取更详细的信息。
不同芯片引脚分布情况
三、IO端口基本结构介绍
由于我无法提供实际的图片或图表,但我可以为你提供关于STM32 F1系列和F4/F7/H7系列微控制器的GPIO结构的一般概述。请注意,以下描述是一般性的,实际上可能因具体型号和配置而有所不同。要获取确切的信息,建议查阅相关的芯片手册或数据手册。
STM32 F1 系列 GPIO 结构图:
IO结构:
- F1系列的GPIO引脚结构包含输入/输出缓冲器、上拉/下拉电阻和输入驱动器。
- 输入驱动器通过IO进入输入缓冲器。
- 输出驱动器直接与IO相连。
上拉/下拉电阻:
- F1系列的GPIO引脚具有可配置的上拉和下拉电阻。这意味着可以通过软件配置引脚为上拉、下拉或浮空状态。
STM32 F4/F7/H7 系列 GPIO 结构图:
IO结构:
- F4/F7/H7系列的GPIO引脚结构包含输入/输出缓冲器、上拉/下拉电阻、输入驱动器和输出驱动器。
- 输入驱动器和输出驱动器都经过上拉/下拉电阻。
上拉/下拉电阻:
- 与F1系列类似,F4/F7/H7系列的GPIO引脚也具有可配置的上拉和下拉电阻。
总体来说,这两个系列的GPIO结构有一些共同之处,但F4/F7/H7系列在IO结构上更为灵活,输入驱动器和输出驱动器都经过上拉/下拉电阻。这种灵活性使得在更多的应用场景下可以进行配置和优化。
在STM32 F1系列中,每个GPIO端口都包含了通用输入和通用输出功能。以下是F1系列IO端口基本结构和工作流程的详细介绍:
F1系列IO端口基本结构:
通用输入:
- 通用输入的外部引脚信号首先经过保护二极管,然后进入输入驱动器。
- 在输入驱动器内部,信号可以经过上拉电阻、下拉电阻、模拟输入(如果配置为模拟输入引脚)、TTL(肖特基触发器)等。
通用输出:
- 通用输出的数据可以通过输出控制器进行读写。
- 输出控制器包含输出数据寄存器(ODR),它存储了输出端口的当前状态。通过对ODR的写入,可以改变输出端口的状态。
执行机构:
- 执行机构包括P-MOS(P型金属氧化物半导体场效应晶体管)和N-MOS(N型金属氧化物半导体场效应晶体管)管。这些管通过输出控制器的控制机构来进行控制。
控制机构:
- 控制机构是一个逻辑电路,通过外部输入(例如输入数据寄存器的值)来控制执行机构中的P-MOS和N-MOS管的通断状态。
复用功能:
- GPIO引脚可以配置为复用功能输入或输出。在复用功能输入模式下,TTL(肖特基触发器)用于将引脚连接到片上外设,例如USART、SPI、I2C等。
工作流程:
通用输入流程:
- 外部引脚信号通过保护二极管进入输入驱动器。
- 输入驱动器内部可能包括上拉电阻、下拉电阻、模拟输入等功能。
- TTL肖特基触发器将输入信号传递到复用功能输入或者通过输入数据寄存器(IDR)读取。
通用输出流程:
- 数据可以通过输出数据寄存器(ODR)进行写入,改变输出端口的状态。
- 输出控制器的执行机构控制P-MOS和N-MOS管,决定引脚的输出状态。
- 复用功能输出可以通过片上外设进行配置。
这样的结构和流程使得F1系列的GPIO引脚可以适应多种应用场景,既可以用于普通的输入输出,也可以配置为连接各种片上外设,提供更多的灵活性和功能。
保护二极管
在STM32系列微控制器的GPIO端口中,通常会包含保护二极管(Protection Diodes),这些二极管用于保护输入引脚免受静电放电(ESD)等不良事件的影响。
保护二极管的作用:
防止静电放电:
- 当外部引脚暴露在可能发生静电放电的环境中时,保护二极管可以将静电放电的能量引导到地,从而防止对芯片的损害。
防止电源冲击:
- 在一些情况下,输入引脚可能受到电源冲击的影响。保护二极管可以将这些冲击引导到地,以减小对芯片的影响。
钳位(Clamping):
保护二极管通常工作在一定的电压范围内,当输入电压超过这个范围时,保护二极管开始导通,将输入电压钳在一个相对较低的水平上。这个过程被称为钳位(Clamping)。
- 上钳位: 保护二极管对过高的正电压进行钳位。
- 下钳位: 保护二极管对过低的负电压进行钳位。
通过保护二极管,输入引脚的电压被限制在一定范围内,避免了对芯片的损坏。
具体的保护二极管的特性和钳位水平可能因不同的STM32系列和型号而有所不同,因此,要获取确切的信息,建议查阅相关的芯片手册或数据手册。
弱上下拉电阻是STM32系列微控制器GPIO端口提供的一种电阻特性。这些电阻可以在配置为输入引脚时使用,以提供一个默认的电平,防止输入信号在悬空状态时漂移或产生噪音。
弱上下拉电阻
弱上拉电阻:
当使用弱上拉电阻时,当外部设备未连接到输入引脚时,引脚会被连接到电源电压(VDD)上,即被拉高。这样可以确保输入引脚在未连接外部信号时具有确定的电平。
弱下拉电阻:
相反,当使用弱下拉电阻时,当外部设备未连接到输入引脚时,引脚会被连接到地(GND),即被拉低。
作用:
- 防止悬空状态: 在实际应用中,当某个输入引脚未连接到外部设备时,它可能处于悬空状态,容易受到环境中的电磁噪音的干扰。通过启用弱上下拉电阻,可以确保在未连接外部信号时,输入引脚具有一个已知的电平,从而减小噪音的影响。
使用方法:
在STM32的HAL库或CubeMX等工具中,可以通过软件配置来启用弱上下拉电阻。通常,这是在初始化GPIO时进行的设置。
请注意,弱上下拉电阻的电阻值相对较大,因此在连接到外部设备时,外部设备的信号应该能够轻松地覆盖这个电阻。否则,外部信号可能无法有效地改变输入引脚的电平。
具体的配置方法和参数可能因使用的STM32系列和型号而有所不同,因此建议查阅相关的芯片手册或数据手册以获取详细的信息。
施密特触发器简介
施密特触发器(Schmitt Trigger)是一种特殊类型的比较器电路,其主要特点是具有正向和负向的阈值电压,使得输出在输入信号上升沿和下降沿之间产生突变。施密特触发器被广泛用于信号整形和去除噪声的电路中。
主要特点:
双阈值:
- 施密特触发器具有两个阈值电压,一个用于上升沿(正向阈值),一个用于下降沿(负向阈值)。
正反馈:
- 施密特触发器通常包含正反馈回路,这使得输出在达到某个阈值后突变,增强了电路的稳定性。
去抖动特性:
- 由于正反馈的存在,施密特触发器对于输入信号的小幅度抖动具有一定的抵抗能力,能够防止由于输入信号微小的噪声引起的频繁输出变化。
工作原理:
上升沿:
- 当输入信号上升到正向阈值以上时,输出由低变高。
下降沿:
- 当输入信号下降到负向阈值以下时,输出由高变低。
滞回特性:
- 施密特触发器具有滞回特性,即输出状态改变后,输入必须越过相反方向的阈值才能再次触发状态变化。
作用:
信号整形:
- 用于将不稳定的输入信号整形为稳定的方波信号。
去噪声:
- 对于具有噪声的输入信号,施密特触发器可以通过设置适当的阈值来消除小幅度的干扰,提高抗噪声性能。
数字电路应用:
- 在数字电路中,施密特触发器经常用于去抖动开关信号和数字信号的整形。
总体而言,施密特触发器在电子电路中有广泛的应用,特别是在需要稳定的数字信号和去除噪声的场景中。
P-MOS & N-MOS管简介
P-MOS(P型金属氧化物半导体场效应晶体管)和N-MOS(N型金属氧化物半导体场效应晶体管)是MOS(金属氧化物半导体)场效应晶体管的两种类型,它们是CMOS(复杂金属氧化物半导体)技术的基本组成部分。这两种MOS管在数字电路和集成电路中具有关键的作用。
P-MOS(P型MOS):
导电条件:
- 当栅源电压(Vgs)为负值时,P-MOS导通。
- P-MOS的栅极为正电压。
特性:
- 通常被用作逻辑门等电路的开关元件。
- 在CMOS技术中,P-MOS通常和N-MOS一起组成互补对(CMOS对)。
N-MOS(N型MOS):
导电条件:
- 当栅源电压(Vgs)为正值时,N-MOS导通。
- N-MOS的栅极为负电压。
特性:
- 通常被用作逻辑门等电路的开关元件。
- 在CMOS技术中,N-MOS通常和P-MOS一起组成互补对(CMOS对)。
MOS管的结构:
G(栅极):
- 控制MOS管的导电状态,通过改变栅源电压(Vgs)来控制导电或截止。
S(源极):
- 提供电流的出口。
D(漏极):
- 提供电流的入口。
MOS管的应用:
MOS管广泛应用于数字电路、模拟电路和混合信号电路中。在CMOS技术中,P-MOS和N-MOS的组合构成了大多数数字电路的基础,包括微处理器、存储器和逻辑门等。由于其低功耗、高集成度和高性能等特点,MOS技术在当代集成电路中得到了广泛的应用。
四、GPIO的八种模式分析
GPIO(通用输入输出端口)在STM32等微控制器中具有多种工作模式,八种常见的模式如下:
输入浮空(Floating Input):
- 特点: 输入用,完全浮空,状态不定。
- 应用: 适用于需要读取外部信号的场景,但外部信号状态不确定。
输入上拉(Input Pull-Up):
- 特点: 输入用,使用内部上拉电阻,初始状态是高电平。
- 应用: 适用于外部信号默认为高电平的情况,如按钮按下时会拉低信号。
输入下拉(Input Pull-Down):
- 特点: 输入用,使用内部下拉电阻,初始状态是低电平。
- 应用: 适用于外部信号默认为低电平的情况,如按钮按下时会拉高信号。
模拟功能(Analog Mode):
- 特点: 用于连接模拟外设,如ADC(模数转换器)、DAC(数模转换器)等。
- 应用: 适用于需要进行模拟信号处理的场景。
开漏输出(Open-Drain Output):
- 特点: 用于实现开漏输出,常用于构建总线,如I2C。
- 应用: 适用于需要多个输出端口共享同一信号线的场景,例如I2C的SDA、SCL线。
推挽输出(Push-Pull Output):
- 特点: 驱动能力强,支持通用输出,可提供较大电流。
- 应用: 适用于需要输出到外部设备,需要较大驱动能力的场景。
开漏式复用功能(Open-Drain Alternate Function):
- 特点: 用于实现开漏输出,并复用了片上外设功能。
- 应用: 适用于需要实现外设功能,同时共享信号线的场景,例如硬件I2C的SDA、SCL线。
推挽式复用功能(Push-Pull Alternate Function):
- 特点: 用于实现推挽输出,并复用了片上外设功能。
- 应用: 适用于需要实现外设功能,同时需要提供较大驱动能力的场景,例如SPI的SCK、MISO、MOSI线。
高阻态
高阻态(High-Z,High Impedance)是指电路中的某个部分呈现非常高的电阻,使得该部分对电流的负载非常小。在高阻态下,电路处于一种几乎不消耗电流的状态,类似于开路状态。
在微控制器的GPIO引脚中,高阻态通常指的是输入电路的状态,即输入电路的输入阻抗非常大。在高阻态下,输入电路对外部信号的影响几乎可以忽略不计。
对于输入上拉或输入浮空等模式,当引脚处于高阻态时,它对外部电路的负载很小,不会对外部电路造成明显的影响。这种状态下,GPIO引脚可以接收外部信号,而外部信号不会被引脚的状态明显地拉低或拉高。
在输出模式中,高阻态通常指的是开漏输出模式。在这种模式下,输出端可以是高电平、低电平,也可以是高阻态。高阻态输出常用于构建总线,例如I2C总线中的SDA线。在高阻态时,该总线上的其他设备可以将线拉低,而当前设备不会主动拉高或拉低总线。
总的来说,高阻态是一种电路状态,其中电路对外部电流的负载非常小,通常用于输入电路或开漏输出模式。
1、输入浮空
输入浮空模式是GPIO的一种工作模式,其特点是在空闲时,IO的状态不确定,由外部环境决定。以下是输入浮空模式的特点和相关设置:
特点:
上拉电阻关闭:
- 在输入浮空模式下,通常关闭了内部的上拉电阻。
下拉电阻关闭:
- 内部的下拉电阻也通常处于关闭状态。
施密特触发器打开:
- 输入浮空模式本身并没有打开或关闭施密特触发器的选项。施密特触发器通常用于去除输入信号的抖动,但在浮空模式下,信号的状态不受到控制,因此施密特触发器的作用受限。
双MOS管不导通:
- 在输入浮空模式下,双MOS管(P-MOS和N-MOS)不导通,输入电路处于高阻态。
设置:
在STM32等微控制器中,通过相应的寄存器设置可以将GPIO配置为输入浮空模式。例如,在STM32CubeMX或HAL库中,可以通过以下步骤进行设置:
- 选择GPIO引脚。
- 将引脚配置为输入。
- 关闭内部上拉电阻。
- 关闭内部下拉电阻。
这样配置后,GPIO引脚将处于输入浮空模式,其状态由外部环境决定,可能受到环境中的噪声和干扰。适用于需要读取外部信号,但在空闲时信号状态不确定的场景。
2、输入上拉
输入上拉模式是GPIO的一种工作模式,其特点是在空闲时,IO呈现高电平。以下是输入上拉模式的特点和相关设置:
特点:
上拉电阻打开:
- 在输入上拉模式下,内部的上拉电阻通常是打开状态。
下拉电阻关闭:
- 内部的下拉电阻通常处于关闭状态。
施密特触发器打开:
- 输入上拉模式并没有直接影响施密特触发器的打开或关闭。施密特触发器通常用于去除输入信号的抖动,但在上拉模式下,主要关注信号的稳定性。
双MOS管不导通:
- 在输入上拉模式下,双MOS管(P-MOS和N-MOS)不导通,输入电路处于高阻态。
设置:
在STM32等微控制器中,通过相应的寄存器设置可以将GPIO配置为输入上拉模式。例如,在STM32CubeMX或HAL库中,可以通过以下步骤进行设置:
- 选择GPIO引脚。
- 将引脚配置为输入。
- 打开内部上拉电阻。
- 关闭内部下拉电阻。
这样配置后,GPIO引脚在空闲时呈现高电平,适用于需要读取外部信号,且在空闲时信号默认为高电平的场景。在按钮等开关连接到引脚的情况下,按钮未按下时引脚保持高电平。
3、输入下拉
输入下拉模式是GPIO的一种工作模式,其特点是在空闲时,IO呈现低电平。以下是输入下拉模式的特点和相关设置:
特点:
上拉电阻关闭:
- 在输入下拉模式下,通常关闭了内部的上拉电阻。
下拉电阻打开:
- 内部的下拉电阻通常是打开状态。
施密特触发器打开:
- 输入下拉模式并没有直接影响施密特触发器的打开或关闭。施密特触发器通常用于去除输入信号的抖动,但在下拉模式下,主要关注信号的稳定性。
双MOS管不导通:
- 在输入下拉模式下,双MOS管(P-MOS和N-MOS)不导通,输入电路处于高阻态。
设置:
在STM32等微控制器中,通过相应的寄存器设置可以将GPIO配置为输入下拉模式。例如,在STM32CubeMX或HAL库中,可以通过以下步骤进行设置:
- 选择GPIO引脚。
- 将引脚配置为输入。
- 关闭内部上拉电阻。
- 打开内部下拉电阻。
这样配置后,GPIO引脚在空闲时呈现低电平,适用于需要读取外部信号,且在空闲时信号默认为低电平的场景。在按钮等开关连接到引脚的情况下,按钮未按下时引脚保持低电平。
4、模拟功能
模拟功能模式是GPIO的一种工作模式,其特点是专门用于模拟信号输入或输出,如连接模拟数字转换器(ADC)或数字模拟转换器(DAC)等。以下是模拟功能模式的特点和相关设置:
特点:
上拉电阻关闭:
- 在模拟功能模式下,通常关闭了内部的上拉电阻。
下拉电阻关闭:
- 内部的下拉电阻通常是关闭状态。
施密特触发器关闭:
- 模拟功能模式并没有直接关联施密特触发器,因为这种模式通常用于模拟信号,而施密特触发器主要用于去抖动数字信号。
双MOS管不导通:
- 在模拟功能模式下,双MOS管(P-MOS和N-MOS)不导通,输入电路处于高阻态,以避免对模拟信号的干扰。
设置:
在STM32等微控制器中,通过相应的寄存器设置可以将GPIO配置为模拟功能模式。例如,在STM32CubeMX或HAL库中,可以通过以下步骤进行设置:
- 选择GPIO引脚。
- 将引脚配置为模拟模式(例如,选择模拟输入或模拟输出)。
- 关闭内部上拉电阻。
- 关闭内部下拉电阻。
这样配置后,GPIO引脚可以用于连接模拟数字转换器(ADC)以获取模拟输入信号,或连接数字模拟转换器(DAC)以输出模拟信号。这对于需要进行模拟信号处理的应用非常重要。
5、开漏输出
开漏输出模式是GPIO的一种工作模式,其特点是不能输出高电平,必须有外部或内部上拉电阻才能实现输出高电平。以下是开漏输出模式的特点和相关设置:
特点:
上拉电阻关闭:
- 在开漏输出模式下,通常关闭了内部的上拉电阻。
下拉电阻关闭:
- 内部的下拉电阻通常处于关闭状态。
施密特触发器打开:
- 开漏输出模式并没有直接影响施密特触发器的打开或关闭。
P-MOS管始终不导通:
- 在开漏输出模式下,P-MOS管通常不导通。
N-MOS管控制输出:
- 通过写入ODR寄存器的相应位,可以控制N-MOS管的导通状态。写0则N-MOS导通,写1则N-MOS不导通。
设置:
在STM32等微控制器中,通过相应的寄存器设置可以将GPIO配置为开漏输出模式。例如,在STM32CubeMX或HAL库中,可以通过以下步骤进行设置:
- 选择GPIO引脚。
- 将引脚配置为开漏输出模式。
- 关闭内部上拉电阻。
- 关闭内部下拉电阻。
在使用开漏输出模式时,通常需要外部上拉电阻来确保在输出高电平时引脚处于高电平状态。这种模式常用于构建总线,例如I2C总线中的SDA线。由于该模式无法输出高电平,所以需要外部上拉电阻来提供高电平状态。
6、开漏式复用功能
开漏式复用功能是GPIO的一种工作模式,其特点是不能直接输出高电平,必须有外部(或内部)上拉电阻才能实现输出高电平。此外,该模式下的输出通常由其他外设控制。以下是开漏式复用功能模式的特点和相关设置:
特点:
上拉电阻关闭:
- 在开漏式复用功能模式下,通常关闭了内部的上拉电阻。
下拉电阻关闭:
- 内部的下拉电阻通常处于关闭状态。
施密特触发器打开:
- 开漏式复用功能模式并没有直接影响施密特触发器的打开或关闭。
P-MOS管始终不导通:
- 在开漏式复用功能模式下,P-MOS管通常不导通。
设置:
在STM32等微控制器中,通过相应的寄存器设置可以将GPIO配置为开漏式复用功能模式。例如,在STM32CubeMX或HAL库中,可以通过以下步骤进行设置:
- 选择GPIO引脚。
- 将引脚配置为开漏输出模式,并选择相应的复用功能。
- 关闭内部上拉电阻。
- 关闭内部下拉电阻。
在开漏式复用功能模式下,由于不能直接输出高电平,通常需要外部上拉电阻来提供高电平状态。此模式常用于一些通信总线,例如I2C总线中的SCL线,其中总线上的主设备控制输出。由于外设负责控制输出,GPIO的高低电平状态由外设来决定。
7、推挽输出
推挽输出模式是GPIO的一种工作模式,其特点是可以输出高电平和低电平,具有较强的驱动能力。以下是推挽输出模式的特点和相关设置:
特点:
上拉电阻关闭:
- 在推挽输出模式下,通常关闭了内部的上拉电阻。
下拉电阻关闭:
- 内部的下拉电阻通常处于关闭状态。
施密特触发器打开:
- 推挽输出模式并没有直接影响施密特触发器的打开或关闭。
输出控制由ODR位决定:
- 通过向ODR寄存器的相应位写入0,可以使N-MOS管导通,从而将引脚拉低;写1则P-MOS管导通,从而将引脚拉高。
设置:
在STM32等微控制器中,通过相应的寄存器设置可以将GPIO配置为推挽输出模式。例如,在STM32CubeMX或HAL库中,可以通过以下步骤进行设置:
- 选择GPIO引脚。
- 将引脚配置为推挽输出模式。
- 关闭内部上拉电阻。
- 关闭内部下拉电阻。
推挽输出模式常用于需要输出高、低电平信号的场景,且具有较强的驱动能力,适用于驱动外部设备或其他数字电路。
8、推挽式复用功能
推挽式复用功能模式是GPIO的一种工作模式,具有输出高低电平的特点,同时具备较强的驱动能力。以下是推挽式复用功能模式的特点和相关设置:
特点:
上拉电阻关闭:
- 在推挽式复用功能模式下,通常关闭了内部的上拉电阻。
下拉电阻关闭:
- 内部的下拉电阻通常处于关闭状态。
施密特触发器打开:
- 推挽式复用功能模式并没有直接影响施密特触发器的打开或关闭。
可输出高低电平:
- 由于是推挽输出模式,因此可以输出高电平和低电平。
驱动能力强:
- 推挽式输出模式通常具备较强的驱动能力,适合驱动外部负载或其他数字电路。
由其他外设控制输出:
- 输出的高低电平状态通常由其他外设来控制,例如外设产生的信号直接控制GPIO引脚的电平状态。
设置:
在STM32等微控制器中,通过相应的寄存器设置可以将GPIO配置为推挽式复用功能模式。例如,在STM32CubeMX或HAL库中,可以通过以下步骤进行设置:
- 选择GPIO引脚。
- 将引脚配置为推挽输出模式,并选择相应的复用功能。
- 关闭内部上拉电阻。
- 关闭内部下拉电阻。
推挽式复用功能模式常用于需要输出高低电平信号,并具备较强驱动能力的场景,适用于与其他外设协同工作的应用。
F4/F7/H7系列和F1系列的GPIO差异点
在STM32系列中,不同型号和系列之间的GPIO的特性和功能可能有所不同。以下是F4/F7/H7系列和F1系列之间的GPIO差异点:
内部上下拉设置:
- 在F1系列中,当GPIO处于输出模式时,禁止使用内部上下拉电阻。
- 在F4/F7/H7系列中,GPIO在输出模式下可以使用内部上下拉电阻。
IO翻转速度:
- 不同系列的GPIO翻转速度可能存在差异,具体速度取决于芯片型号和系列。
5V电平输出:
- STM32芯片通常采用3.3V工作电压,因此其GPIO输出的电平通常为3.3V。直接从STM32输出的电平不能达到5V。
总体而言,STM32系列芯片在GPIO方面有很多共通之处,但具体的差异点可能取决于具体的型号和系列。在使用时,建议参考具体型号的数据手册和参考手册,以确保准确的GPIO配置和使用。
回答最后一个问题:STM32通常工作在3.3V电平,因此直接输出的电平是3.3V,不能直接输出5V电平。如果需要与5V电平的设备连接,通常需要使用电平转换电路或逻辑电平转换器。
五、GPIO寄存器介绍
对于STM32中的GPIO通用寄存器,不同系列的芯片可能存在一些差异。F1系列和F4/F7/H7系列的GPIO寄存器,下面对这两个系列的GPIO通用寄存器进行简要说明:
F1系列(GPIO通用寄存器):
CRL (Control Register Low) 和 CRH (Control Register High):
- 用于配置工作模式和输出速度。
- CRL用于配置0到7号引脚,CRH用于配置8到15号引脚。
IDR (Input Data Register):
- 用于读取输入数据。
ODR (Output Data Register):
- 用于设置输出数据。
BSRR (Bit Set/Reset Register):
- 用于原子性地设置或重置引脚,而不影响其他引脚。
BRR (Bit Reset Register):
- 用于快速清零特定的引脚。
LCKR (Lock Register):
- 用于配置锁定,但在F1系列中用得相对较少。
F4/F7/H7系列(GPIO通用寄存器):
MODER (Mode Register):
- 用于设置模式,即输入、输出、复用功能等。
OTYPER (Output Type Register):
- 用于设置输出类型,即推挽输出或开漏输出。
OSPEEDR (Output Speed Register):
- 用于设置输出速度。
PUPDR (Pull-up/Pull-down Register):
- 用于设置上下拉电阻。
IDR (Input Data Register):
- 用于读取输入数据。
ODR (Output Data Register):
- 用于设置输出数据。
BSRR (Bit Set/Reset Register):
- 用于原子性地设置或重置引脚,而不影响其他引脚。
LCKR (Lock Register):
- 用于配置锁定,但在实际应用中用得相对较少。
这些寄存器的具体配置和使用方式可以参考相应的芯片参考手册或数据手册,以确保在编程时正确地配置和操作GPIO引脚。
F1系列的GPIO相关寄存器
F4/F7/H7系列的GPIO相关寄存器
ODR和BSRR寄存器控制输出有什么区别?
在STM32中,ODR(Output Data Register)和BSRR(Bit Set/Reset Register)寄存器都可以用于控制GPIO的输出状态,但它们在操作方式上有一些区别。
ODR(Output Data Register):
操作方式:
- ODR是一个32位寄存器,每一位对应一个引脚。
- 在对某一位进行修改时,需要先读取整个ODR寄存器,然后修改特定位的值,最后将修改后的值写回ODR。
示例代码:
GPIOB->ODR |= 1 << 3; /* 设置PB3引脚为高电平 */
BSRR(Bit Set/Reset Register):
操作方式:
- BSRR同样是一个32位寄存器,但它的每一位有特殊的含义。
- BSRR的前16位用于设置引脚为高电平,后16位用于设置引脚为低电平。写入1表示设置,写入0表示保持原状态。
示例代码:
GPIOB->BSRR = 0x00000008; /* 设置PB3引脚为高电平 */
区别和建议:
ODR修改方式:
- 读->改->写的方式,可能在读和写之间产生中断时发生风险。
BSRR修改方式:
- 直接写入需要设置或重置的位,无需读取先前状态,因此更为直接且无风险。
总的来说,建议使用BSRR寄存器来控制GPIO输出。BSRR的写操作更为直观且不会受到中断的干扰,减少了风险。
六、通用外设驱动模型(四步法)
通用外设驱动模型的四步法是一个常见的设计思路,它通常用于设计和实现针对各种外设的驱动程序。下面对每一步进行简要解释:
1. 初始化
在初始化步骤中,执行以下操作:
时钟设置: 配置外设所需的时钟源和时钟频率。
参数设置: 配置外设的相关参数,例如数据位宽、波特率等。
IO设置: 配置与外设通信的引脚,包括引脚的工作模式、上下拉电阻等。
中断设置: 配置外设相关的中断,包括开启中断、设置中断优先级,并在需要时配置NVIC(Nested Vectored Interrupt Controller)。
2. 读函数(可选)
如果外设支持从外设读取数据,设计读函数用于从外设获取数据。
3. 写函数(可选)
如果外设支持往外设写入数据,设计写函数用于向外设发送数据。
4. 中断服务函数(可选)
如果外设产生中断,设计中断服务函数用于处理中断。中断服务函数根据中断标志来执行相应的操作,例如处理接收到的数据、发送数据等。
这个四步法的模型具有灵活性,可以根据外设的特性和需求选择性地实现读函数、写函数和中断服务函数。这种模型的优势在于可以根据具体情况进行定制,使得驱动程序更加灵活和高效。
七、GPIO配置步骤
相关HAL库函数简介
GPIO的配置步骤通常包括以下几个关键步骤,以下以HAL库为例进行解释:
1. 使能时钟
在使用GPIO之前,需要先使能相应的GPIO时钟。时钟的使能位置和方式可能会根据不同系列的STM32微控制器有所不同。以下是使能GPIO时钟的一般步骤:
#define __HAL_RCC_GPIOA_CLK_ENABLE() do {
\
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\
/* Delay after an RCC peripheral clock enabling */\
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\
UNUSED(tmpreg); \
} while(0U)
2. 设置工作模式
配置GPIO的工作模式,包括输入、输出、模拟等。这一步通常使用HAL_GPIO_Init
函数来完成,该函数需要传入GPIO端口号和一个包含GPIO初始化信息的结构体。
// 例子:配置GPIOB的Pin 5为推挽输出,速度为中等,无上下拉
GPIO_InitTypeDef GPIO_InitStruct = {
0};
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_MEDIUM;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
3. 设置输出状态(可选)
如果配置的GPIO是输出引脚,可以使用HAL_GPIO_WritePin
或HAL_GPIO_TogglePin
函数来设置输出状态。
// 例子:设置GPIOB的Pin 5输出高电平
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
// 例子:翻转GPIOB的Pin 5状态
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
4. 读取输入状态(可选)
如果配置的GPIO是输入引脚,可以使用HAL_GPIO_ReadPin
函数来读取输入状态。
// 例子:读取GPIOB的Pin 5输入状态
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_SET) {
// 输入为高电平
} else {
// 输入为低电平
}
这些步骤可以根据具体的应用场景和需求进行适当的调整,使得GPIO的配置更加灵活和符合实际要求。
关键结构体简介
八、编程实战:点亮一个LED灯
要点亮一个LED灯,首先需要选择一个GPIO引脚连接LED。在设置GPIO的模式时,需要考虑LED的工作方式以及连接方式。
一般情况下,LED是通过一个电流限制电阻连接到STM32的GPIO引脚上。常见的工作方式是将GPIO引脚配置为推挽输出,通过向引脚写入高电平来点亮LED,写入低电平来熄灭LED。
以下是一般步骤:
使能时钟: 选择连接LED的GPIO引脚所在的端口,并使能该端口的时钟。例如,如果LED连接在GPIOB的Pin 5上:
__HAL_RCC_GPIOB_CLK_ENABLE();
设置工作模式: 配置GPIO引脚的工作模式为推挽输出。
GPIO_InitTypeDef GPIO_InitStruct = { 0}; GPIO_InitStruct.Pin = GPIO_PIN_5; // 选择连接LED的引脚 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式 GPIO_InitStruct.Speed = GPIO_SPEED_MEDIUM; // 中等速度 GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上下拉 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
点亮LED: 将引脚写入高电平。
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
熄灭LED: 将引脚写入低电平。
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
上述代码示例假设LED连接在GPIOB的Pin 5上,实际连接的引脚可能有所不同。选择引脚时,请确保了解实际连接情况并相应地进行配置。
1、编程实战:点亮一个LED灯
查找函数原型
__HAL_RCC_GPIOB_CLK_ENABLE();
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
代码实现
led.c
#include "./BSP/LED/led.h"
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
// 使能 GPIOB 时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置 GPIOB Pin 5 为推挽输出,速度为低速
gpio_init_struct.Pin = GPIO_PIN_5;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &gpio_init_struct);
// 将 GPIOB Pin 5 设置为高电平,点亮 LED
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
}
这段代码的主要步骤是:
使能时钟:
__HAL_RCC_GPIOB_CLK_ENABLE()
用于使能GPIOB的时钟。配置工作模式: 使用
GPIO_InitTypeDef
结构体配置GPIOB Pin 5为推挽输出,速度为低速。初始化GPIO:
HAL_GPIO_Init(GPIOB, &gpio_init_struct)
将配置应用到GPIO引脚。点亮LED:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET)
将GPIOB Pin 5设置为高电平,点亮LED。
总体来说,该函数的目的是初始化GPIOB的Pin 5引脚,将其配置为推挽输出模式,并通过HAL_GPIO_WritePin
函数将其设置为高电平,可能是为了点亮LED。
led.h
#ifndef __LED_H
#define __LED_H
#include "./SYSTEM/sys/sys.h"
void led_init(void);
#endif
这是一个LED模块的头文件,用于声明LED相关的函数和可能的数据结构。下面是对头文件的简要分析:
#ifndef __LED_H
,#define __LED_H
,#endif
是头文件的预处理器指令,用于防止头文件被重复包含。如果之前已经包含了该头文件,预处理器会跳过后续内容。#include "./SYSTEM/sys/sys.h"
:包含了一个名为sys.h
的头文件,该文件可能包含一些系统级的定义和声明,为LED模块提供必要的系统支持。void led_init(void);
:声明了一个名为led_init
的函数,该函数没有参数,返回类型为void
。这个函数可能用于初始化LED相关的硬件设置,具体的实现应该在对应的源文件中。#endif
:结束条件编译的指令。
总体来说,这个头文件主要用于声明LED模块的初始化函数,以及可能依赖的其他系统级的头文件。在实现文件中,你将找到对 led_init
函数的具体实现。如果需要更详细的分析或有其他问题,请随时提问。
main.c
PB5置一是灭,置零才亮
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
led_init(); /* LED初始化 */
while(1)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); /* PB5置1 */
delay_ms(200);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET); /* PB5置0 */
delay_ms(200);
// 用于翻转LED状态
// HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5); /* PB5翻转 */
// delay_ms(200);
}
}
这个主程序的主要功能是初始化STM32的HAL库、时钟、延时和LED,然后通过循环实现LED的闪烁。具体步骤如下:
HAL_Init();
:初始化HAL库,这是HAL库的必要步骤。sys_stm32_clock_init(RCC_PLL_MUL9);
:设置时钟为72MHz,具体的时钟初始化函数在sys.h
和sys.c
中实现。delay_init(72);
:初始化延时函数,参数是时钟频率,这里设置为72MHz。led_init();
:LED初始化函数,设置GPIOB Pin 5为推挽输出模式。while(1)
:主循环,程序会在这里一直循环执行。HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
:将GPIOB Pin 5置1,点亮LED。delay_ms(200);
:延时200毫秒。HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
:将GPIOB Pin 5置0,熄灭LED。delay_ms(200);
:延时200毫秒。如果需要使用翻转LED状态的方式,可以取消注释
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
和delay_ms(200);
这两行代码。
这个程序主要用于测试LED是否正常工作。如果有其他问题或需要更多解释,请随时提问。
2、解读例程源码:跑马灯实验
led.c
#include "./BSP/LED/led.h"
/**
* @brief 初始化LED相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
LED0_GPIO_CLK_ENABLE(); /* LED0时钟使能 */
LED1_GPIO_CLK_ENABLE(); /* LED1时钟使能 */
gpio_init_struct.Pin = LED0_GPIO_PIN; /* LED0引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct); /* 初始化LED0引脚 */
gpio_init_struct.Pin = LED1_GPIO_PIN; /* LED1引脚 */
HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct); /* 初始化LED1引脚 */
LED0(1); /* 关闭 LED0 */
LED1(1); /* 关闭 LED1 */
}
该函数的主要功能是初始化两个LED引脚,其中 LED0_GPIO_CLK_ENABLE()
和 LED1_GPIO_CLK_ENABLE()
用于使能相应的时钟。
接着,通过 GPIO_InitTypeDef
结构体配置每个LED引脚的参数:
gpio_init_struct.Pin
:引脚编号,分别是LED0_GPIO_PIN
和LED1_GPIO_PIN
。gpio_init_struct.Mode
:引脚工作模式,这里设置为推挽输出 (GPIO_MODE_OUTPUT_PP
)。gpio_init_struct.Pull
:引脚上拉或下拉,这里设置为上拉 (GPIO_PULLUP
)。gpio_init_struct.Speed
:引脚工作速度,这里设置为高速 (GPIO_SPEED_FREQ_HIGH
)。
最后,通过 HAL_GPIO_Init
函数将这些配置应用到相应的GPIO引脚上。
最后两行代码 LED0(1)
和 LED1(1)
用于关闭 LED0 和 LED1,这可能是因为某些硬件设计中,LED引脚处于低电平时为亮,高电平时为灭。如果不理解这个设计,可以查看具体的LED驱动电路和引脚接法。
led.h
#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define LED0_GPIO_PORT GPIOB
#define LED0_GPIO_PIN GPIO_PIN_5
#define LED0_GPIO_CLK_ENABLE() do{
__HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define LED1_GPIO_PORT GPIOE
#define LED1_GPIO_PIN GPIO_PIN_5
#define LED1_GPIO_CLK_ENABLE() do{
__HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
/******************************************************************************************/
/* LED端口定义 */
#define LED0(x) do{
x ? \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED0翻转 */
#define LED1(x) do{
x ? \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED1翻转 */
/* LED取反定义 */
#define LED0_TOGGLE() do{
HAL_GPIO_TogglePin(LED0_GPIO_PORT, LED0_GPIO_PIN); }while(0) /* 翻转LED0 */
#define LED1_TOGGLE() do{
HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0) /* 翻转LED1 */
/******************************************************************************************/
/* 外部接口函数*/
void led_init(void); /* 初始化 */
#endif
这是一个LED模块的头文件 led.h
,包含了LED的相关定义和函数声明,下面是对该头文件的简要分析:
引脚定义: 提供了LED引脚的定义,包括端口、引脚编号、时钟使能等信息。
LED端口定义: 提供了对LED进行控制的宏定义,包括LED状态设置、LED取反、LED初始化等。
外部接口函数: 声明了一个
led_init
函数,用于初始化LED。
此头文件使用了条件编译防御式声明 #ifndef _LED_H
,以防止头文件被重复包含。
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72M */
delay_init(72); /* 初始化延时函数 */
led_init(); /* 初始化LED */
while(1)
{
LED0(0); /* LED0 亮 */
LED1(1); /* LED1 灭 */
delay_ms(500);
LED0(1); /* LED0 灭 */
LED1(0); /* LED1 亮 */
delay_ms(500);
}
}
这是一个LED灯的简单测试程序,主要实现了LED0和LED1的交替闪烁。
main
函数的基本流程:- 初始化HAL库
- 设置系统时钟为72MHz
- 初始化延时函数
- 初始化LED
- 进入无限循环
循环中 LED 控制:
LED0(0);
:点亮 LED0LED1(1);
:熄灭 LED1delay_ms(500);
:延时500毫秒LED0(1);
:熄灭 LED0LED1(0);
:点亮 LED1delay_ms(500);
:延时500毫秒
通过这样的控制,实现了LED0和LED1的交替闪烁,每个LED亮500毫秒,灭500毫秒。
九、编程实战:通过一个按键控制一个LED灯亮灭
按键控制一个LED灯亮灭,需要配置相应的GPIO引脚作为输入模式。一般而言,按键连接到GPIO引脚,涉及到按键输入的GPIO引脚配置。
按键和对应GPIO引脚的连接:
- PA0(按键):输入下拉
/* 配置 PA0 为输入下拉 */
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能 PA 时钟 */
gpio_init_struct.Pin = GPIO_PIN_0; /* PA0引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入模式 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉模式 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速模式 */
HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 初始化 GPIOA PA0 引脚 */
- PE4/PE3/PE2(按键):输入上拉
/* 配置 PE4/PE3/PE2 为输入上拉 */
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOE_CLK_ENABLE(); /* 使能 PE 时钟 */
gpio_init_struct.Pin = GPIO_PIN_4 | GPIO_PIN_3 | GPIO_PIN_2; /* PE4/PE3/PE2引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入模式 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉模式 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速模式 */
HAL_GPIO_Init(GPIOE, &gpio_init_struct); /* 初始化 GPIOE PE4/PE3/PE2 引脚 */
以上代码示例中,通过HAL库函数设置了不同GPIO引脚的输入模式和上下拉电阻,以适应按键的连接和使用。在具体的应用中,需要根据硬件连接和按键的实际工作方式来调整配置。
1、编程实战:通过一个按键控制一个LED灯亮灭
key.c
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
/* 按键初始化函数 */
void key_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOE_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_2;
gpio_init_struct.Mode = GPIO_MODE_INPUT;
gpio_init_struct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &gpio_init_struct);
}
/* 按键扫描函数 */
uint8_t key_scan(void)
{
if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_2) == 0)
{
delay_ms(10); /* 消抖 */
if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_2) == 0)
{
while(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_2) == 0); /* 等待按键松开 */
return 1; /* 按键按下了 */
}
}
return 0; /* 按键没有按下 */
}
这段代码涉及按键的初始化和扫描功能,主要包括以下几个部分:
按键初始化:
- 使能时钟:
__HAL_RCC_GPIOE_CLK_ENABLE()
用于使能GPIOE的时钟。 - 配置工作模式:使用
GPIO_InitTypeDef
结构体配置GPIOE Pin 2为输入模式。 - 设置上拉电阻:
gpio_init_struct.Pull = GPIO_PULLUP;
配置上拉电阻,确保按键默认为高电平。
- 使能时钟:
按键扫描:
- 使用
HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_2)
读取GPIOE Pin 2的状态,如果为低电平表示按键被按下。 - 消抖处理:在检测到按键按下后,延时一段时间进行二次检测,确保按键稳定按下。
- 再次读取按键状态,如果依然为低电平,说明按键真正被按下,返回1。
- 如果在延时后按键已经松开,则返回0,表示按键未按下。
- 使用
整体来说,这段代码实现了按键的初始化和简单的按键扫描功能,可以用于检测按键是否被按下。
key.h
#ifndef __KEY_H
#define __KEY_H
#include "./SYSTEM/sys/sys.h"
/* 按键初始化函数 */
void key_init(void);
/* 按键扫描函数 */
uint8_t key_scan(void);
#endif
这是一个按键(Key)模块的头文件,其中包含了按键初始化函数 key_init
和按键扫描函数 key_scan
的声明。这样的头文件通常用于定义模块的接口,其他源文件通过包含这个头文件可以使用这两个函数的功能,而无需关心函数的具体实现细节。
key_init
函数用于按键的初始化。key_scan
函数用于扫描按键状态,返回按键是否被按下的信息。
通过这样的头文件设计,你可以在其他源文件中包含这个头文件,然后调用 key_init
和 key_scan
函数,实现对按键模块的使用。
led.c
#include "./BSP/LED/led.h"
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
// 使能 GPIOB 时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置 GPIOB Pin 5 为推挽输出,速度为低速
gpio_init_struct.Pin = GPIO_PIN_5;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &gpio_init_struct);
// 将 GPIOB Pin 5 设置为高电平,点亮 LED
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
}
这段代码的作用是初始化一个LED,主要包括以下几个步骤:
使能时钟:
__HAL_RCC_GPIOB_CLK_ENABLE()
用于使能GPIOB的时钟。配置工作模式: 使用
GPIO_InitTypeDef
结构体配置GPIOB Pin 5为推挽输出模式,速度为低速。初始化GPIO:
HAL_GPIO_Init(GPIOB, &gpio_init_struct)
将配置应用到GPIO引脚。点亮LED:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET)
将GPIOB Pin 5设置为高电平,点亮LED。
整体来说,这段代码初始化了一个LED,将其引脚配置为推挽输出模式,然后将引脚设置为高电平,点亮LED。
led.h
#ifndef __LED_H
#define __LED_H
#include "./SYSTEM/sys/sys.h"
void led_init(void);
#endif
这是一个LED模块的头文件,用于声明LED相关的函数和可能的数据结构。下面是对头文件的简要分析:
#ifndef __LED_H
,#define __LED_H
,#endif
是头文件的预处理器指令,用于防止头文件被重复包含。如果之前已经包含了该头文件,预处理器会跳过后续内容。#include "./SYSTEM/sys/sys.h"
:包含了一个名为sys.h
的头文件,该文件可能包含一些系统级的定义和声明,为LED模块提供必要的系统支持。void led_init(void);
:声明了一个名为led_init
的函数,该函数没有参数,返回类型为void
。这个函数可能用于初始化LED相关的硬件设置,具体的实现应该在对应的源文件中。#endif
:结束条件编译的指令。
总体来说,这个头文件主要用于声明LED模块的初始化函数,以及可能依赖的其他系统级的头文件。在实现文件中,你将找到对 led_init
函数的具体实现。如果需要更详细的分析或有其他问题,请随时提问。
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
led_init(); /* LED初始化 */
key_init(); /* KEY初始化 */
while(1)
{
if(key_scan())
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
}
else
{
delay_ms(10);
}
}
}
这是一个简单的主程序,使用了HAL库和你之前提到的LED和KEY模块。主要的流程如下:
初始化:
HAL_Init()
:初始化HAL库。sys_stm32_clock_init(RCC_PLL_MUL9)
:设置时钟为72MHz。delay_init(72)
:初始化延时函数。led_init()
:LED初始化。key_init()
:按键初始化。
主循环:
- 使用
while(1)
进入主循环。 - 在循环中,通过
key_scan()
函数扫描按键状态。 - 如果检测到按键按下,使用
HAL_GPIO_TogglePin()
函数翻转(Toggle)GPIOB Pin 5的状态,即改变LED的亮灭状态。 - 如果未检测到按键按下,使用
delay_ms(10)
进行短暂的延时,避免频繁扫描。
- 使用
这样的程序逻辑实现了按键控制LED的亮灭效果。按下按键时,LED状态翻转;释放按键时,延时一小段时间,以避免连续翻转。
2、解读例程源码:按键输入实验
key.c
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
/**
* @brief 按键初始化函数
* @param 无
* @retval 无
*/
void key_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
KEY0_GPIO_CLK_ENABLE(); /* KEY0时钟使能 */
KEY1_GPIO_CLK_ENABLE(); /* KEY1时钟使能 */
KEY2_GPIO_CLK_ENABLE(); /* KEY2时钟使能 */
WKUP_GPIO_CLK_ENABLE(); /* WKUP时钟使能 */
gpio_init_struct.Pin = KEY0_GPIO_PIN; /* KEY0引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY0_GPIO_PORT, &gpio_init_struct); /* KEY0引脚模式设置,上拉输入 */
gpio_init_struct.Pin = KEY1_GPIO_PIN; /* KEY1引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY1_GPIO_PORT, &gpio_init_struct); /* KEY1引脚模式设置,上拉输入 */
gpio_init_struct.Pin = KEY2_GPIO_PIN; /* KEY2引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY2_GPIO_PORT, &gpio_init_struct); /* KEY2引脚模式设置,上拉输入 */
gpio_init_struct.Pin = WKUP_GPIO_PIN; /* WKUP引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct); /* WKUP引脚模式设置,下拉输入 */
}
/**
* @brief 按键扫描函数
* @note 该函数有响应优先级(同时按下多个按键): WK_UP > KEY2 > KEY1 > KEY0!!
* @param mode:0 / 1, 具体含义如下:
* @arg 0, 不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
* 必须松开以后, 再次按下才会返回其他键值)
* @arg 1, 支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
* @retval 键值, 定义如下:
* KEY0_PRES, 1, KEY0按下
* KEY1_PRES, 2, KEY1按下
* KEY2_PRES, 3, KEY2按下
* WKUP_PRES, 4, WKUP按下
*/
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; /* 按键按松开标志 */
uint8_t keyval = 0;
if (mode) key_up = 1; /* 支持连按 */
if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 1)) /* 按键松开标志为1, 且有任意一个按键按下了 */
{
delay_ms(10); /* 去抖动 */
key_up = 0;
if (KEY0 == 0) keyval = KEY0_PRES;
if (KEY1 == 0) keyval = KEY1_PRES;
if (KEY2 == 0) keyval = KEY2_PRES;
if (WK_UP == 1) keyval = WKUP_PRES;
}
else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 0) /* 没有任何按键按下, 标记按键松开 */
{
key_up = 1;
}
return keyval; /* 返回键值 */
}
key.h
#ifndef __KEY_H
#define __KEY_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define KEY0_GPIO_PORT GPIOE
#define KEY0_GPIO_PIN GPIO_PIN_4
#define KEY0_GPIO_CLK_ENABLE() do{
__HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define KEY1_GPIO_PORT GPIOE
#define KEY1_GPIO_PIN GPIO_PIN_3
#define KEY1_GPIO_CLK_ENABLE() do{
__HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define KEY2_GPIO_PORT GPIOE
#define KEY2_GPIO_PIN GPIO_PIN_2
#define KEY2_GPIO_CLK_ENABLE() do{
__HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define WKUP_GPIO_PORT GPIOA
#define WKUP_GPIO_PIN GPIO_PIN_0
#define WKUP_GPIO_CLK_ENABLE() do{
__HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
/******************************************************************************************/
#define KEY0 HAL_GPIO_ReadPin(KEY0_GPIO_PORT, KEY0_GPIO_PIN) /* 读取KEY0引脚 */
#define KEY1 HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_GPIO_PIN) /* 读取KEY1引脚 */
#define KEY2 HAL_GPIO_ReadPin(KEY2_GPIO_PORT, KEY2_GPIO_PIN) /* 读取KEY2引脚 */
#define WK_UP HAL_GPIO_ReadPin(WKUP_GPIO_PORT, WKUP_GPIO_PIN) /* 读取WKUP引脚 */
#define KEY0_PRES 1 /* KEY0按下 */
#define KEY1_PRES 2 /* KEY1按下 */
#define KEY2_PRES 3 /* KEY2按下 */
#define WKUP_PRES 4 /* KEY_UP按下(即WK_UP) */
void key_init(void); /* 按键初始化函数 */
uint8_t key_scan(uint8_t mode); /* 按键扫描函数 */
#endif
key.c
#include "./BSP/LED/led.h"
/**
* @brief 初始化LED相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
LED0_GPIO_CLK_ENABLE(); /* LED0时钟使能 */
LED1_GPIO_CLK_ENABLE(); /* LED1时钟使能 */
gpio_init_struct.Pin = LED0_GPIO_PIN; /* LED0引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct); /* 初始化LED0引脚 */
gpio_init_struct.Pin = LED1_GPIO_PIN; /* LED1引脚 */
HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct); /* 初始化LED1引脚 */
LED0(1); /* 关闭 LED0 */
LED1(1); /* 关闭 LED1 */
}
key.h
#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define LED0_GPIO_PORT GPIOB
#define LED0_GPIO_PIN GPIO_PIN_5
#define LED0_GPIO_CLK_ENABLE() do{
__HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define LED1_GPIO_PORT GPIOE
#define LED1_GPIO_PIN GPIO_PIN_5
#define LED1_GPIO_CLK_ENABLE() do{
__HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
/******************************************************************************************/
/* LED端口定义 */
#define LED0(x) do{
x ? \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED0翻转 */
#define LED1(x) do{
x ? \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED1翻转 */
/* LED取反定义 */
#define LED0_TOGGLE() do{
HAL_GPIO_TogglePin(LED0_GPIO_PORT, LED0_GPIO_PIN); }while(0) /* 翻转LED0 */
#define LED1_TOGGLE() do{
HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0) /* 翻转LED1 */
/******************************************************************************************/
/* 外部接口函数*/
void led_init(void); /* 初始化 */
#endif
beep.c
#include "./BSP/BEEP/beep.h"
/**
* @brief 初始化BEEP相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void beep_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
BEEP_GPIO_CLK_ENABLE(); /* BEEP时钟使能 */
gpio_init_struct.Pin = BEEP_GPIO_PIN; /* 蜂鸣器引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(BEEP_GPIO_PORT, &gpio_init_struct); /* 初始化蜂鸣器引脚 */
BEEP(0); /* 关闭蜂鸣器 */
}
beep.h
#ifndef __BEEP_H
#define __BEEP_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define BEEP_GPIO_PORT GPIOB
#define BEEP_GPIO_PIN GPIO_PIN_8
#define BEEP_GPIO_CLK_ENABLE() do{
__HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
/******************************************************************************************/
/* 蜂鸣器控制 */
#define BEEP(x) do{
x ? \
HAL_GPIO_WritePin(BEEP_GPIO_PORT, BEEP_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(BEEP_GPIO_PORT, BEEP_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
/* BEEP状态翻转 */
#define BEEP_TOGGLE() do{
HAL_GPIO_TogglePin(BEEP_GPIO_PORT, BEEP_GPIO_PIN); }while(0) /* BEEP = !BEEP */
void beep_init(void); /* 初始化蜂鸣器 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/BEEP/beep.h"
#include "./BSP/KEY/key.h"
int main(void)
{
uint8_t key;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
beep_init(); /* 初始化蜂鸣器 */
LED0(0); /* 先点亮LED0 */
while(1)
{
key = key_scan(0); /* 得到键值 */
if (key)
{
switch (key)
{
case WKUP_PRES: /* 控制蜂鸣器 */
BEEP_TOGGLE(); /* BEEP状态取反 */
break;
case KEY2_PRES: /* 控制LED0(RED)翻转 */
LED0_TOGGLE(); /* LED0状态取反 */
break;
case KEY1_PRES: /* 控制LED1(GREEN)翻转 */
LED1_TOGGLE(); /* LED1状态取反 */
break;
case KEY0_PRES: /* 同时控制LED0, LED1翻转 */
LED0_TOGGLE(); /* LED0状态取反 */
LED1_TOGGLE(); /* LED1状态取反 */
break;
}
}
else
{
delay_ms(10);
}
}
}
十、总结