STM32F103C8T6 引脚功能详述_stm32f103c8t6引脚图及功能-CSDN博客

在系统学习/回顾STM32之前,看看STM32F103C8T6的引脚 。

目录

GPIO结构详解

一、GPIO基本概念

二、GPIO位结构解析

​编辑​

2.1 保护二极管机制

2.2 上拉/下拉电阻

2.4 双MOS管输出结构

GPIO工作模式详解

3.1 输入模式

3.2 输出模式

 GPIO数据手册/寄存器配置

GPIOx_LCKR解析

五、实战应用案例 

1.硬件电路分析

2.KEIL5的相关操作 

2>为模块化编程新建c\h文件

3. 基于标准库开发

2.1、GPIO基础操作

2.2、标准库实战

2.3、HAL库实战 

GPIO结构详解

一、GPIO基本概念

GPIO架构图
GPIO引脚分配

GPIO通用输入输出)是微控制器与外部设备交互的核心接口,具有以下特性:

  • 可编程控制输入/输出模式
  • 支持数字信号的读取与输出
  • 集成多种保护机制
  • 复用功能支持片上外设连接

二、GPIO位结构解析

【上图的I/O引脚不是指特定的Px.x,而是Px16个一组引脚一起读入/输出】

2.1 保护二极管机制


【钳制输入电压在安全范围(0-0.7-3.3+0.7V),防止大电流流入GPIO内部。】

上方保护二极管:连接在GPIO引脚和VDD(电源电压,如3.3V)之间,阴极接VDD,阳极接GPIO引脚。
下方保护二极管:连接在GPIO引脚和VSS(地,0V)之间,阳极接VSS,阴极接GPIO引脚。
主要功能:限制GPIO引脚电压在安全范围内(VSS - 0.7V 到 VDD + 0.7V),防止过电压损坏内部电路。

STM32电源网络标签说明:

(1)VCC:C=circuit 表示电路的意思, 即接入电路的电压

(2)VDD:D=device 表示器件的意思, 即器件内部的工作电压;

(3)VSS:S=series 表示公共连接的意思,通常指电路公共接地端电压

(4)VEE:负电压供电;场效应管的源极(S)

(5)VBAT:当使用电池或其他电源连接到VBAT脚上时,当VDD 断电时,可以保存备份寄存器的内容和维持RTC的功能。如果应用中没有使用外部电池,VBAT引脚应接到VDD引脚上。

保护二级管工作原理:

1.正常工作情况:
当GPIO输入电压在0V到3.3V之间时,两个保护二极管均不导通。
电流通过GPIO引脚流入内部电路,进行正常的信号处理。
2.输入电压大于3.3V:
当GPIO输入电压 > VDD + 二极管正向压降(例如3.3V + 0.7V = 4.0V)时,上方保护二极管导通。
二极管导通后,提供一个低阻抗路径,使大部分电流流向VDD,而不是流入内部电路。
原因:
二极管的导通电阻(几欧姆到几十欧姆)远低于内部电路的输入阻抗(几千欧姆到几兆欧姆)。
根据欧姆定律(I=V/RI=V/R),电流会优先选择低阻抗路径流动。
注意:尽管有这样的保护,并不意味着 STM32 的引脚能直接外接大功率驱动器件,如直接驱动电机,强制驱动要么电机不转,要么导致芯片烧坏,必须要加大功率及隔离电路驱动

 3.输入电压小于0V:
当GPIO输入电压 < VSS - 二极管正向压降(例如0V - 0.7V = -0.7V)时,下方保护二极管导通。
二极管导通后,电流从GPIO引脚流向VSS,避免内部电路受到负电压的损害。

为什么大部分电流通过二极管流向VDD?

1.低阻抗路径:
导通后的二极管具有很低的阻抗(几欧姆到几十欧姆),而内部电路的输入阻抗较高(几千欧姆到几兆欧姆)。
电流会优先选择低阻抗路径流动,因此大部分电流通过二极管流向VDD。
2.内部电路的高阻抗特性:
内部电路(如GPIO的输入缓冲器或逻辑电路)通常设计为高阻抗输入,以减少功耗和提高信号灵敏度。
高阻抗意味着内部电路对电流的“吸引力”较小,因此电流更倾向于流向低阻抗的保护二极管。
3.钳位作用:
当二极管导通时,它会将GPIO引脚电压“钳位”在VDD + 0.7V左右,防止电压进一步升高。
这种钳位作用进一步限制了流入内部电路的电压和电流。

实例分析:
假设:

假设:GPIO输入电压为5V,VDD为3.3V,二极管正向压降为0.7V,二极管导通电阻为10Ω,内部电路输入阻抗为10kΩ。
电流分配:

1.通过二级管电流:I=0.7V/10\Omega =70mA

2.流过内部电路的电流:I=4/10K\Omega =0.4mA
总结:
保护二极管通过提供低阻抗路径,将过电压引起的电流引导到VDD或VSS,从而保护内部电路;这种设计确保了GPIO引脚在异常电压条件下仍能安全运行,避免损坏。

原文:

理解保护二极管在GPIO过电压保护中的作用_gpio保护二极管的作用-CSDN博客

2.2 上拉/下拉电阻


开/关:如果上面导通、下面断开,就是上拉输入模式,如果下面导通、上面断开,就是下拉输入模式,如果两个都断开,就是浮空输入模式。
上拉和下拉的作用——>为了给输入提供一个默认的输入电平
因为对应一个数字的端口,输入不是高电平就是低电平,那如果输入引脚什么都不接,那就不确定算高电平还是低电平。而实际情况是,如果啥也不接,这时输入就会处于一种浮空的状态,引脚的输入电平极易受外界干扰而改变。为了避免引脚悬空导致的输入数据不确定,我们就需要在这里加上拉或者下拉电阻了,如果接入上拉电阻,当引脚悬空时,还有上拉电阻来保证引脚的高电平,所以上拉输入又可以称作是默认为高电平的输入模式。下拉也是同理,就是默认为低电平的输入方式。


作用:避免浮空输入带来的电平不确定
配置模式:
上拉输入(默认高电平)
下拉输入(默认低电平)
浮空输入(需外接确定电平)
特性:弱上拉/弱下拉(约30-50KΩ),降低对信号的影响.


2.3 施密特触发器
【英文原文档是施密特触发器,(模电里这叫迟滞/滞回比较器,也就是施密特触发器的电路)】

什么是施密特触发器?施密特触发器工作原理+电路图,带你轻松搞定 - 知乎

功能:信号整形(消除抖动)
工作原理:
输入>正向阈值 ⇒ 输出高电平
输入<负向阈值 ⇒ 输出低电平
特征:提供约200mV的迟滞电压范围

接下来经过施密特触发器整形的波形就可以直接写入输入数据寄存器了,我们再用程序读取数据输存器对应某一位的数据,就可以知道端口的输入电平了。最后上面这还有两路线路,这些就是连接到片上外设的一些端口,其中有模拟输入,这个是连接到ADC上的,因为ADC需要接收模拟量,所以这根线是接到施密特触发器前面的;另一个是复用功能输入,这个是连接到其他需要读取端口的外设上的,比如串口的输入引脚等,这根线接收的是数字量,所以在施密特触发器后面。

2.4 双MOS管输出结构

输出部分可以由输出数据寄存器或片上外设控制,两种控制方式通过这个数据选择器接到了输出控制部分。
如果选择通过输出数据寄存器进行控制,就是普通的IO口输出,写这个数据寄存器的某一位就可以操作对应的某个端口了。
位设置/清除寄存器:这个可以用来单独操作输出数据寄存器的某一位,而不影响其它位。因为这个输出数据寄存器同时控制16个端口,并且这个寄存器只能整体读写,所以如果想单独控制其中某一个端口而不影响其他端口的话,就需要一些特殊的操作方式。

第一种方式:先读出这个寄存器,然后用 按位与 和 按位或 的方式更改某一位,最后再将更改后的数据写回去,在C语言中就是&=和 |=的操作,这种方法比较麻烦,效率不高,对于IO口的操作而言不太合适;
第二种方式:通过设置这个位设置和位清除寄存器,如果我们要对某一位进行置1的操作,在位设置寄存器的对应位写1便可,剩下不需要操作的位写0,这样它内部就会有电路,自动将输出数据寄存器中对应位置为1,而剩下写0的位则保持不变,这样就保证了只操作其中某一位而不影响其它位,并且这是一步到位的操作。如果想对某一位进行清0的操作,就在位清除寄存器的对应位写1即可,这样内部电路就会把这一位清0了,这就是第二种方式也就是这个位设置和位清除寄存器的作用。【作用:将设置/清除寄存器的某一位写1/0就能达到单独影响输出寄存器的某一位,从而单独影响某个端口


上面是P-MOS,下面是N-MOS,这个MOS管就是一种电子开关,我们的信号来控制开关的导通和关闭,开关负责将IO口接到VDD或者VSS,
在这里可以选择推挽、开漏或关闭三种输出方式。

推挽输出模式:
在推挽输出模式下,P-MOS和N-MOS均有效,数据寄存器为1时,上管导通,下管断开,输出直接接到VDD,就是输出高电平,数据寄存器为0时,上管断开,下管导通,输出直接接到VSS,就是输出低电平,这种模式下,高低电平均有较强的驱动能力,所以推挽输出模式也可以叫强推输出模式。在推挽输出模式下,STM32对IO口具有绝对的控制权,高低电平都由STM32说的算。
开漏输出模式:
在开漏输出模式下,这个P-MOS是无效的,只有N-MOS在工作,数据寄存器为1时,下管断开,这时输出相当于断开,也就是高阻模式;数据寄存器为0时,下管导通,输出直接接到VSS,也就是输出低电平;这种模式下,只有低电平有驱动能力,高电平是没有驱动能力的。那这个模式有什么用呢,这个开漏模式可以作为通信协议的驱动方式,比如I2C通信的引脚,就是使用的开漏模式,在多机通信的情况下,这个模式可以避免各个设备的相互干扰,另外开漏模式还可以用于输出5V的电平信号。

比如在I0口外接一个上拉电阻到5V的电源,当输出低电平时,由内部的N-MOS直接接VSS,当输出高电平时,由外部的上拉电阻拉高至5V,这样就可以输出5V的电平信号,用于兼容一些5V电平的设备,这就是开漏输出的主要用途。

开漏模式下,输出1时,两个mos管都相当于关断,左侧相当于断路。外接5V的电能只能流向右侧,故输出5V。反之,输出0时,左下方mos管导通,外接5V的电能流到左下方Vss,且两者之间几乎没有电压降,可看做5V电压降在了上拉电阻上,故引脚输出0V

关闭:
剩下的一种状态就是关闭,这个是当引脚配置为输入模式的时候,这两个MOS管都无效,也就是输出关闭,端口的电平由外部信号来控制。

GPIO工作模式详解

【STM32】GPIO工作原理(八种工作方式超详细分析,附电路图)_芯片管脚 gpic-CSDN博客

[GPIO各模式详解图]

3.1 输入模式

模式 特性
浮空输入 电压完全由外部决定
上拉输入 悬空时默认高电平
下拉输入 悬空时默认低电平
模拟输入 直连ADC(关闭数字部件)

首先是前三个,浮空输入、上拉输入和下拉输入。这三个模式的电路结构基本是一样的,区别就是上拉电阻和下拉电阻的连接,它们都属于数字的输入口,那特征就是,都可以读取端口的高低电平,当引脚悬空时,上拉输入默认是高电平,下拉输入默认是低电平,而浮空输入的电平是不确定的,所以在使用浮空输入时,端口—定要接上一个连续的驱动源,不能出现悬空的状态。
那我们来看一下这三种模式的电路结构,这里可以看到,在输入模式下,输出驱动器是断开的,端口只能输入而不能输出,上面这两个电阻可以选择为上拉工作、下拉工作或者都不工作,对应的就是上拉输入、下拉输入和浮空输入,然后输入通过施密特触发器进行波形整形后,连接到输入数据寄存器。

【另外右边这个输入保护这里,上面写的是VDD或者VDD_FT,这就是3.3V端口和容忍5V端口的区别。这个容忍5V的引脚,它的上边保护二极管要做一下处理,要不然这里直接接VDD 3.3V的话,外部再接入5V电压就会导致上边二极管开启,并且产生比较大的电流,这个是不太妥当的。】

怎么看STM32的哪些引脚是否兼容5V?_哪些mcu支持5v gpio-CSDN博客

接着我们再来看一下下面这一个模拟输入,特征是GPIO无效,引脚直接接入内部ADC,这个模拟输入可以说是ADC模数转换器的专属配置了。

 这里输出是断开的,输入的施密特触发器也是关闭的无效状态,所以整个GPIO的这些都是没用的,那么只剩下从引脚直接接入片上外设,也就是ADC,所以,当我们使用ADC的时候,将引脚配置为模拟输入就行了,其他时候,一般用不到模拟输入。

3.2 输出模式

输出模式对比

模式 控制源 特点
推挽输出 数据寄存器 双向强驱动
开漏输出 数据寄存器 需外接上拉电阻
复用推挽/开漏输出 片上外设 配合UART/SPI等外设使用

​ 

开漏输出和推挽输出,这两个电路结构也基本一样,都是数字输出端口,可以用于输出高低电平,区别就是开漏输出的高电平呈现的是高阻态,没有驱动能力,而推挽输出的高低电平都是具有驱动能力的。 这时候,输出是由输出数据寄存器控制的,如果P-MOS无效,就是开漏输出;如果P-MOS和N-MOS都有效,就是推挽输出。另外我们还可以看到,在输出模式下,输入模式也是有效的,但是在我们刚才的电路图,在所有输入模式下,输出都是无效的,这是因为,一个端口只能有一个输出,但可以有多个输入,所以当配置成输出模式的时候,内部也可以顺便输入一下,这个也是没啥影响的。

最后我们再来看一下复用开漏输出和复用推挽输出,这俩模式跟普通的开漏输出和推挽输出也差不多。

【这种模式其实算是硬件控制,片上外设于GPIO引脚有目前关系,比如PA0配置为复用推挽输出,那他肯定是UART2_rt输出或者TIM2_CH1输出】

​ 

可以看到通用的输出/数据寄存器没有连接的,引脚的控制权转移到了片上外设,由片上外设来控制,在输入部分,片上外设也可以读取引脚的电平,同时普通的输入也是有效的,顺便接收一下电平信号其实在GPIO的这8种模式中,除了模拟输入这个模式会关闭数字的输入功能,在其他的7个模式中,所有的输入都是有效的。

 GPIO数据手册/寄存器配置

每组GPIO端口都会有以下表寄存器,每个I/O端口位可以编程控制,然而必须按照32位字访问I/O端口寄存器(不允许半字或字节访 问)。GPIOx_BSRR和GPIOx_BRR寄存器允许对任何GPIO寄存器进行读/更改的独立访问。

GPIO寄存器 功能
GPIOx_CRL(端口配置低寄存器) 32位,一个引脚要4位配置,配置速度和模式;该寄存器配置Px_0-7引脚
GPIOx_CRH(端口配置高寄存器) 32位,一个引脚要4位配置,配置速度和模式;该寄存器配置Px_8-15引脚
GPIOx_IDR(端口输入数据寄存器) 32位,但只要配置低16位,一位代表一个引脚电平,只读,只能以16位操作
GPIOx_ODR(端口输出数据寄存器) 32位,但只要配置低16位,一位代表一个引脚电平,可读可写,只能以16位操作
GPIOx_BSRR(端口位设置/清除寄存器) 32位,低16位:只写、配置Px.0-15引脚高电平;高16位:只写、配置Px.0-15引脚低电平,如果同时设置了BSy和BRy的对应位,BSy位起作用。只能以16位操作
GPIOx_BRR(端口位清除寄存器) 32位,但只要配置低16位,一位代表一个引脚电平,配置Px.0-15引脚低电平,可读可写,只能以16位操作
GPIOx_LCKR 32位,但只要配置低16位和第16位,低16位一位代表是否锁定该引脚,可读可写但只能在LCKK位为0时写入,给高电平代表锁定该引脚。第16位看下文详述

其实由上表我们可以看出配置一个引脚为高/低电平。有好几个寄存器可以用,都一样。反正增大就行,这年头哪还有人用寄存器开发QAQ

GPIOx_LCKR解析

 首先第16位,即高16位的第1位为LCKK,要开启锁IO模式,必须先“开锁”,开锁密码:写1——>写0——>写1——>读0——>读1。最后的读1可省略,但其它“密码”顺序、内容都不能错。

uint32_t t;
GPIOB->LCKR|=0x00010000;//LCKK写入1
GPIOB->LCKR&=0x0000ffff;//LCKK写入0
GPIOB->LCKR|=0x00010000;//LCKK写入1
t=GPIOB->LCKR;//LCKK读0
t=GPIOB->LCKR;//LCKK读出1

此时寄存器解锁成功;需要注意的是,只有第16位——>LCKK为0时,GPIOx_LCKR寄存器才可以被写入,某位写入1,则对应的IO口被锁住;所以我们解锁后,要设置第16位——>LCKK为0

//开启锁定寄存器模式
GPIOB->LCKR&=0x0000ffff;//LCKK写入0
GPIOB->LCKR=1<<5;//锁定PB5

举例:同时配置LED0和LED1,但是LED0被GPIOB_LCKR寄存器锁住,观察两个LED能否都被点亮。

直接代码:

#include "sys.h"
#include "stm32f10x.h"
#include "delay.h"
 int main(void)
 {	
	 uint32_t t;
	 delay_init();
	 //LED0 PB5  
	//LED1  PE5
	 //RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOE, ENABLE);//时钟使能GPIOA和GPIOE
	 RCC->APB2ENR|=0x0048;
	 //RCC->APB2ENR=1<<3 ;//使能GPIOB
	 //RCC->APB2ENR=1<<6 ;//使能GPIOE
	 
	 /* 开锁*/
	 GPIOB->LCKR|=0x00010000;//LCKK写入1
	 GPIOB->LCKR&=0x0000ffff;//LCKK写入0
	 GPIOB->LCKR|=0x00010000;//LCKK写入1
	 t=GPIOB->LCKR;//LCKK读0
	 t=GPIOB->LCKR;//GPIOB_LCKR读出1
	 
	 
	 //开启锁定寄存器模式
	  GPIOB->LCKR&=0x0000ffff;//LCKK写入0
	 GPIOB->LCKR=1<<5;//锁定PB5
	 
	 GPIOB->CRL&=0xff0fffff;
	 GPIOB->CRL|=0x00300000;//配置PB5
	 GPIOE->CRL&=0xff0fffff;
	 GPIOE->CRL|=0x00300000;//配置PE5
	 GPIOB->BSRR=1<<5;//熄灭LED0
	 delay_ms(10);
	 
	 while(1)
	 {
		 GPIOB->BRR=1<<5;//点亮LED0
		GPIOE->BRR=1<<5;
	 }
 }

调试:
下载程序后,LED0一直熄灭,LED2一直亮。
分析:
程序开始时对GPIOB和GPIOE进行了时钟配置,接下来就是“开锁”,然后就是对PB5“上锁”,这些上面都总结了。

接下来配置PE5和PB5,然后把LED0熄灭(程序开始都清零,LED默认处于点亮状态),若LED0(PB5)的配置没有被锁住,则在死循环中LED0应该被点亮,调试时LED0应该常亮。

“开锁”时一定要注意:第0~15位的值不能被改变,所以写入0和1的时候要进行与运算和或运算。

参考:STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置_stm32f103关于gpio的寄存器有哪些-CSDN博客

所有部分外设GPIO配置推荐

见手册对应部分;不过注意一点,其中有一些部分是手册推荐的是浮空输入,最好不要,根据中间硬件的情况去设置成上拉/下拉输入;

五、实战应用案例 

1.硬件电路分析

 

2.KEIL5的相关操作 

【代码补全快捷键:Ctrl+Alt+空格

1>为工程新建一个文件夹

先在工程文件夹新建一个文件夹,重命名HandWare.以后拓展功能的C文件就放在里面。

将需要的Delay文件 c/h文件放在里面。

点击红圈图标。

1.先在KEIL的工程文件Group中建一个HandWare文件夹。

2.点Add Files,把之前HandWare文件夹中的Delay.c/h放入其中,最后点ok。

点红圈图标。

点C/C++--->点Include Paths最右边的省略号-->写入.\HandWare

[注意这个一定要和你在KEIL5中文件名一样,否则编译识别不到] 

2>为模块化编程新建c\h文件

点Add New Item to Group "HandWare",添加c\h文件,但要注意要更改文件放置位置,初始是在工程文件下,我们要把c\h文件放置在HandWare文件夹下。

h文件:

C文件:

3. 基于标准库开发

2.1、GPIO基础操作

1>操作STM32的GPIO总共需要3个步骤:

第一步:使用RCC开启GPIO的APB2总线时钟

(GPIO是挂在APB2总线上的外设,APB2总线是连接MCU的)
涉及的函数如下:

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)

作用:使能(开启)或失能(关闭)APB2外设时钟
参数说明:

RCC_APB2Periph 门控 APB2 外设时钟:指明需要开启的是哪一个APB2外设,取值范围在下图表明
NewState NewState:指定外设时钟的新状态 ,这个参数可以取:ENABLE(打开) 或者 DISABLE(关闭)

 其它两个外设时钟函数也是大差不差的,根据不同外设选择相应的函数开启就行。
在这里插入图片描述

第二步:使用GPIO_Init函数初始化GPIO
涉及的函数如下:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)

作用:根据GPIO_InitStruct中的指定参数初始化GPIOx外设。
参数说明:

GPIOx 其中x可以为(A…G)选择GPIO外设。
GPIO_InitStruct 指向GPIO InitTypeDef结构的指针,该结构包含指定GPIO外设的配置信息,如传输数据速度,输入/输出模式这些

 其中 GPIO InitTypeDef结构体配置信息如下:

typedef struct
{
  uint16_t GPIO_Pin;           
  GPIOSpeed_TypeDef GPIO_Speed;                             
  GPIOMode_TypeDef GPIO_Mode;   
}GPIO_InitTypeDef;

参数说明:

GPIO_Pin 指定要配置的GPIO引脚。例如:GPIO_Pin_14
GPIO_Speed 指定所选引脚的速率。在GPIO_Speed_10MHz,GPIO_Speed_2MHz,GPIO_Speed_50MHz中选择,库里已经定义好了
GPIO_Mode 指定所选引脚的工作模式,之前写的推挽输出什么的

引脚的工作模式如下:
在这里插入图片描述

【我们要在GPIO初始化前完成GPIO InitTypeDef结构体配置】

第三步:使用输出或者输入的函数控制GPIO口
涉及的函数如下:

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

作用:设置所选数据端口位。对某个端口写1,也就是高电平
参数说明:

GPIOx 其中x可以为(A…G)选择GPIO外设。
GPIO_Pin 指定要写入的端口位,该参数可以是GEIo_Pin_x的任意组合,其中x可以是(0…15)。

类似的还有:GPIO_ResetBits 函数,同样的用法,只不过这个函数是写0。

初始化和配置相关函数

1.void  GPIO_DeInit  (GPIO_TypeDef* GPIOx)

函数解释:gpio的反初始化函数,该函数的作用是把GPIO相关的寄存器配置成上电复位后的默认状态,在第一次初始化前或者不在使用某一接口后,可以调用该函数。

参数:GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

2.void  GPIO_Init  (GPIO_TypeDef* GPIOx,GPIO_InitTypeDef* GPIO_InitStruct)

函数解释:GPIO的初始化函数,该函数的作用是对io进行初始化。

参数:(1)GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

            (2)GPIO_InitStruct,GPIO的初始化相关结构体。该结构体里的成员变量决定了我们具体的初始化参数。以下进行说明:

              GPIO_Pin:指定具体的io脚,如GPIO_Pin_0,GPIO_Pin_1这样的宏定义。

              GPIO_Mode:指定GPIO的模式,有四种模式:

GPIO_Mode_IN(输入), GPIO_Mode_OUT(输出), GPIO_Mode_AF(第二功能), GPIO_Mode_AN(模拟)

              GPIO_Speed:指定IO最快翻转速度,也就是当使用IO产生频率(如PWM)的最大速度。有四种模式:

 GPIO_Low_Speed(低速), GPIO_Medium_Speed(中等速度), GPIO_Fast_Speed(快速), GPIO_High_Speed(低速)

               GPIO_OType:指定选择管脚的输出类型,有以下两种配置:

GPIO_OType_PP(推挽方式输出),GPIO_OType_OD(开漏方式输出)

推挽输出:推挽输出就是单片机引脚可以直接输出高电平电压。低电平时接地,高电平时输出单片机电源电压。这种方式可以不接上拉电阻。但如果输出端可能会接地的话,这个时候输出高电平可能引发单片机运行不稳定,甚至可能烧坏引脚。推挽方式的驱动力更大。

开漏输出:开漏输出就是不输出电压,低电平时接地,高电平时不接地。如果外接上拉电阻,则在输出高电平时,电压会拉到上拉电阻的电源电压。这种方式适合在连接的外设电压比单片机低的时候。

GPIO_PuPd:指定选择管脚的上拉和下拉模式。有三种配置:

GPIO_PuPd_NOPULL(不上拉也不下拉),GPIO_PuPd_UP(上拉),GPIO_PuPd_DOWN(下拉)

这些都是io内部的内部上拉或者下拉模式,也可以接上拉或下拉电阻,通过硬件连接,实现外部上拉或外部下拉。

3.void  GPIO_StructInit  (GPIO_InitTypeDef* GPIO_InitStruct)

函数解释:GPIO结构体的初始化。对GPIO_InitStruct结构体进行默认配置。

参数:GPIO_InitStruct,直接传入该结构体的指针。在该函数内会对结构体进行初始化。

4.void  GPIO_PinLockConfig (GPIO_TypeDef* GPIOx,uint16_t  GPIO_Pin)

函数解释:锁定GPIO寄存器,锁定的寄存器是GPIOx_MODER,   GPIOx_OTYPER,    GPIOx_OSPEEDR,   GPIOx_PUPDR,    GPIOx_AFRL    and    GPIOx_AFRH。在下一次复位前,被锁定的管脚不能被修改。

参数:(1)GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

            (2)  GPIO_Pin:指定具体的io脚,如GPIO_Pin_0,GPIO_Pin_1这样的宏定义。

GPIO的读写函数:

1.uint8_t  GPIO_ReadInputDataBit  (GPIO_TypeDef* GPIOx , uint16_t GPIO_Pin)

函数解释:读取io输入管脚的值

参数:(1)GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

            (2)  GPIO_Pin:指定具体的io脚,如GPIO_Pin_0,GPIO_Pin_1这样的宏定义。

返回值:输入管脚的值 Bit_SET(高电平),Bit_RESET(低电平)

2.uint16_t  GPIO_ReadInputData (GPIO_TypeDef* GPIOx )

函数解释:读取输入io数据,该函数用于读取一个io分组的所有数据

参数:GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

返回值:一个io端口的所有数据(输入状态)

3.uint8_t  GPIO_ReadOutputDataBit  (GPIO_TypeDef* GPIOx , uint16_t GPIO_Pin)

函数解释:读取io输出管脚的值

参数:(1)GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

            (2)  GPIO_Pin:指定具体的io脚,如GPIO_Pin_0,GPIO_Pin_1这样的宏定义。

返回值:输出管脚的值 Bit_SET(高电平),Bit_RESET(低电平)

4.uint16_t  GPIO_ReadOutputData (GPIO_TypeDef* GPIOx )

函数解释:读取输出io数据,该函数用于读取一个io分组的所有数据

参数:GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

返回值:一个io端口的所有数据(输出状态)

5.void  GPIO_SetBits(GPIO_TypeDef* GPIOx,uint16_t  GPIO_Pin)

函数解释:对io管脚进行置位(输出高电平)。这个函数使用GPIOx_BSRR寄存器来实现原子读或者修改操作。在这种情况下,在读和修改访问时发生一个IRQ中断是没有危险的。

参数:(1)GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

            (2)  GPIO_Pin:指定具体的io脚,如GPIO_Pin_0,GPIO_Pin_1这样的宏定义。

6.void  GPIO_ResetBits(GPIO_TypeDef* GPIOx,uint16_t  GPIO_Pin)

函数解释:对io管脚进行复位(输出低电平)。这个函数使用GPIOx_BSRR寄存器来实现原子读或者修改操作。在这种情况下,在读和修改访问时发生一个IRQ中断是没有危险的。

参数:(1)GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

            (2)  GPIO_Pin:指定具体的io脚,如GPIO_Pin_0,GPIO_Pin_1这样的宏定义。(GPIO_Setbits/ResetBits()函数是可以写入多个引脚的,同时把多个引脚置于高/低电平)

GPIO_ResetBits(GPIOA,GPIO_Pin_1|GPIO_Pin_2);

7.void  GPIO_WriteBit(GPIO_TypeDef* GPIOx,uint16_t  GPIO_Pin,BitActionBitVal)

函数解释:对某一位进行写入操作。

参数:(1)GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

            (2)GPIO_Pin:指定具体的io脚,如GPIO_Pin_0,GPIO_Pin_1这样的宏定义。

            (3)BitVal:写入高电平或者低电平(Bit_RESET:写入低电平 Bit_SET:写入高电平)

GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
//将PA0置于低电平,这个函数一次只能写入一个引脚

8.void  GPIO_Write(GPIO_TypeDef* GPIOx,uint16_t  PortVal)

函数解释:对GPIO端口进行写入操作,适用于对统一端口的多个管脚的写入。

参数:(1)GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

            (2)PortVal:指定写到输出寄存器RCC的值,比如0x0001就是把PA0置于1,其余PA1-15置于0(可以查看GPIO_Pin_x定义)

9.void  GPIO_ToggleBits(GPIO_TypeDef* GPIOx,uint16_t  GPIO_Pin)

函数解释:翻转指定的GPIO口电平,如果当前io是高电平,则变为低电平。如果当前io是低电平,则变为高电平。

参数:(1)GPIOx,GPIO的分组,如 GPIOA,GPIOB,GPIOC等的宏定义。

            (2)GPIO_Pin:指定具体的io脚,如GPIO_Pin_0,GPIO_Pin_1这样的宏定义。

参考:STM32库函数详解----(通用输入/输出GPIO)_stm32端口初始化输入代码库函数-CSDN博客

2.2、标准库实战

 

 

按一下按键则红灯(light1)亮,再按一下则绿灯(light2)亮;如此循环。

上文主要程序是写在main中的,要是我们把他模块化编程,写在Key.c中则要把sign变量设为全局变量,不然它不会像上述代码一样,每次运行Keynum函数都会是重新开始,导致sign只能为0/1.

key.c

Key.h 

主程序

 全局变量:

详解keil采用C语言模块化编程时全局变量、结构体的定义、声明以及头文件包含的处理方法 - prayer521 - 博客园

KEIL编程中变量类型:

关于嵌入式编程中的uint8_t、uint16_t......_嵌入式开发时候为什么大多定义uint 而不是int-CSDN博客

遇到的问题

这是一个带返回值的函数,我一开始没有加入注释那一行,相当于这个函数在if不执行时,不返回;KEIL 5 的编辑器没有报错[吐槽:正常的编辑器早就报错了]

在这种情况下,感觉是系统给sign变量随便付了一个值,但没用,整个程序根本不运行,在Key_Num()函数那里卡住了

以后,用这个由返回值的函数一定要注意,一定要有返回值,否则系统会完全卡死,让你不知道哪里出错。

2.3、HAL库实战 

【STM32】STM32CubeMX教程二--基本使用(新建工程点亮LED灯)-CSDN博客

HAL不着急学,还是以标准库为主

非常详细。

主要参考:

【STM32 学习笔记】GPIO输入与输出_gpio上拉输入和下拉输入-CSDN博客

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐