stm32江协科技笔记
- 创建基础项目
- GPIO
-
- [GPIO 位结构](#GPIO 位结构)
-
- 保护二极管
- 施密特触发器
- 输入数据寄存器
- 模拟输入/复用功能输入
- 位设置/清除寄存器
- 端口位清除寄存器
- P-MOS/N-MOS
- [推挽/开漏/关闭 模式](#推挽/开漏/关闭 模式)
- GPIO模式
- 基础外设
- 面包板
- 点亮led
-
- 连接基础单元
- 初始化GPIO
- 调用GPIO_Init
- [调用GPIO输入输出函数(上面提到的8种)](#调用GPIO输入输出函数(上面提到的8种))
- led闪烁
- 点响蜂鸣器
创建基础项目
- 创建一个文件夹





-
在刚才创建的项目下创建Start,User,Library文件夹

-
到固件库的如下地址,选中以下文件,复制刚才创建的Start文件夹里

-
以及下面地址中的文件同样复制到刚才创建的Start文件夹里

-
下面也是复制到Start文件夹下

-
以下路径的文件复制到Library文件夹下:


- 以下文件复制到User文件夹下:

- 以下按照图片步骤








USE_STDPERIPH_DRIVER

下面第三步骤是需要选择第三栏Flash Download,忘记说明了

把原来main.c文件里的代码删除


GPIO
- General Purpose Input Output 通用输入输出口
- 可配置为8种输入输出模式
- 引脚电平:0v--3.3v,部分引脚可容忍5v,
容忍5v表示接收5v正电压而不是发出5v电压 - 输出模式下可控制端口输出高低电平,用以驱动LED,控制蜂鸣器,模拟通信协议输出时序等
- 输入模式下可读取端口的高低电平或电压,用于读取按键输入,外接模块电平信号输入,ADC电压采集,模拟通信协议接收数据等
GPIO基本结构图

stem32里所有的GPIO外设都是挂载在APB2总线的
GPIO外设的名称是安装GPIOA,GPIOB,GPIOC等等规则命名,每个GPIO外设,有16个引脚,编号是0~15,GPIOA的第0号引脚,一般被称作为PA0,接着是PA1,然后PA2,以此类推,直到PA15,GPIOB同理,PB0 ~ PB5
寄存器是一段特殊的存储器,内核可以通过APB2总线对寄存器进行读写,这样就可以完成输出电平和读取电平的功能了,寄存器的每一位对应于每一位引脚,寄存器的值是0就会输出低电平,1则是高电平,读取同理,0为低电平,1为高电平,因为stm32是32位单片机,所以内部寄存器都是32位的,但是端口只有16位(PA0~PA15),所以只有寄存器的低16位对应端口,驱动器是用来增加驱动能力的。
GPIO 位结构


《我们先来看输入部分》
保护二极管
图中两个保护二极管是对输入电压进行限幅的,上面VDD接3.3v,下面接VSS0V,如果输入电压比3.3V还要高,那么上方这个二极管就会导通,输入电压产生的电流会直接流入VDD而不会流入内部电路,防止了高电压对内部器件伤害,若输入的是负电压(相对于VSS电压),下方二极管可以流通,电流直接从VSS流过去,而不会从内部电路流过,保护内部电路和器件。若输入电压在0~3.3v之间,那么那两个二极管都不会导通,这时保护二极管不对流通电流产生影响。
上拉电阻vdd 与 下拉电阻vss,这个开关可以通过程序配置,如果上面导通,下面断开,则是上拉输入模式,如果下面导通,上面断开,就是下拉输入模式,如果两个都断开,就是浮空模式。
上拉和下拉的作用:为了给输入提供一个默认输入电平,对于一个数字端口,输入不是低电平就是高电平,若输入引脚什么都不接,那么算是低电平还是高电平呢,什么都不接的情况下,处于一个浮空状态,引脚的输入电平及其容易受到外界干扰而改变,就像是物体悬浮在太空一样,物体受到一点改变就变化,为了避免引脚悬空导致输入数据的不确定,完美就需要在这里加上拉或下拉电阻,,若接入上拉电阻,若引脚悬空,还有上拉电阻保证引脚的高电平,所以上拉输入又可以称作是(默认为高电平的输入模式),同理,下拉模式默认低电平的输入方式。
施密特触发器
图中所展示的肖特基触发器是施密特触发器,滞回比较器,他的执行逻辑是电压大于某一阈值,输出就会瞬间升为高电平,如果输入电压小于某一阈值,输出就瞬间变为低电平(注意,高低阈值是两个阈值,而不是同一个),因为实际电压会有波动,这一设计可以消除信号抖动。
输入数据寄存器
斯密特触发器输出的信号会被存储到寄存器的每一位上
模拟输入/复用功能输入
模拟输入/复用功能输入这两路线路,这些就是连接到片上外设的一些端口线路。
模拟输入,连接到ADC上ADC(模数转换器)和DAC(数模转换器)是电子系统中实现模拟信号与数字信号相互转换的核心器件,二者功能互补、应用场景各异,数字信号(Digit)是跳变的,模拟(Analog)信号是连续的,接收模拟量。可以看到模拟输入是接到斯密特触发器的前面,意味着不需要斯密特触发器处理,因为模拟信号本来就是连续的,不需要整形。
复用功能输入,这个是连接到其他需要读取端口的外设的,比如串口的输入引脚等,这根线接收的是数字量,需要结果数字信号整形处理,所以在斯密特触发器后面
《接着我们来看输出部分》
输出数据可以由输出数据寄存器或片上外设输出数据,通过数据选择器(图中连着输出控制的竖着的梯形)控制选择用哪一个作为输出,如果选择通过数据寄存器进行控制,就是普通的IO口输出,写着数据寄存器的某一位就可以控制某一位端口了,
位设置/清除寄存器
可以用来单独操作输出数据寄存器的某一位,而不影响其他位,若没有这个寄存器而想单独控制某一位就需要先读取这个16位寄存器然后按位与/或来修改再赋值。若采用该寄存器修改,我们只需要对位设置寄存器的某一位设置1或清除位置1即可,这样就可以直接对某一位赋值1或0,不影响效率。
- 设置位(Set Bits):将寄存器中的某一位或多位置为1。
- 清除位(Clear Bits):将寄存器中的某一位或多位置为0。
高16位是位清除,低16位是位设置
除了以上两种方式,还有"位带"的方式来设置1/0输出,(你没听错,是大胃袋!),stem32中有"位带"区域,作用和51单片机的寻址差不多,在stm32中,专门分配了一段地址区域,这段地址映射了RAM和外设寄存器的所有位,读或写这段地址中的数据,就相当于读写所映射的某一位。
端口位清除寄存器
和上面的那个不同,低16位和上面的高16位功能一样,用来清除,高16位无用,这个寄存器是为了方便设置的,因为处于低位更直观。
P-MOS/N-MOS
这个mos管就是一种电子开关,我们的信号来控制开关的导通和关闭,开关负责将IO口接到VDD或者VSS,这里可以选择推挽,开漏或关闭三种输出模式。
推挽/开漏/关闭 模式
输出模式下,输入模式也是可以有效的, 但是输入模式下,输出都是无效的。读时不可写,因为写将会篡改引脚信号;写时可以读,其实读的目的就是监控写的数据。
在推挽输出模式下,P-MOS和N-MOS均有效,数据寄存器为1时,上管(P-MOS)导通,下管(N-MOS)断开,输出直接接到VDD,就是输出高电平,数据寄存器为0时,就是上管断开,下管导通,输出直接接到VSS,就是输出低电平。这种模式高低电平都有较强的驱动能力,所以推挽输出模式也可以叫强推输出模式,这种模式下,stem32对IO口有绝对控制权,高低电平都由STM32说的算。
开漏输出模式下,P-MOS是无效的,只有N-MOS在工作,数据寄存器为1时,下管断开,这时输出相当于断开,也就是高阻模式。数据寄存器为0时,下管导通,输出接到VSS,输出低电平,这种模式下,只有低电平有驱动能力,高电平没有驱动能力。此开漏模式可以作为通信协议的驱动方式,如I2C通信的引脚,就是使用的开漏模式,多机通信的情况下,这个模式可以避免各个设备相互干扰,另外开漏模式可以用于输出5V的电平信号,比如在IO口外接入一个上拉电阻的5V电源,输出低电平时直接接到内部VSS,当输出高电平时,N-MOS和P-MOS都断开,此时由5v电源提供5v电压,用于兼容一些5v的设备 。
关闭模式,当配置输入模式的时候,两个MOS管都无效,也就是输出关闭,端口的电平都由外部信号来控制,以上是GPIO位结构的全部介绍。
GPIO模式
输出模式下,输入模式也是可以有效的, 但是输入模式下,输出都是无效的。读时不可写,因为写将会篡改引脚信号;写时可以读,其实读的目的就是监控写的数据。
通过配置GPIO的端口配置寄存器,端口可以配置为以下8种模式:

浮空/上拉/下拉输入
前三个输入模式电路结构图:

可以看到输入模式下输出是断开的
前三个输入模式电路结构基本一致,区别是上拉电阻和下拉电阻的连接,上拉在无信号接收时默认是高电平,下拉则是低电平,浮空则是不确定,浮空模式下电平是不确定的,所以使用浮空模式输入时,应该接一个连续的驱动源,不能出现悬空状态。
图中VDD和VDD_FT,FT表示可容忍5.5v的断开
模拟输入
GPIO无效,引脚直接接入内部ADC
开漏输出和推挽输出
主要区别是开漏输出的高电平输出为高阻态,无驱动能力。在GPIO位结构里 推挽/开漏/关闭 模式 介绍了主要细节
复用开漏和复用推挽输出
也就是普通开漏和推挽模式,输入设备变成了外设而已。
在GPIO这8个模式里,除了模拟输入这个模式会关闭数字输入功能,其他7个模式,所有输入都是有效的以上是Stem32的GPIO所有介绍;
基础外设
LED:发光二极管,正向通电点亮,反向通电不亮
有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音

我们使用是上图的有源蜂鸣器使用,右侧是其内部电路图;
下面左侧是GPIO驱动led灯的电路,右侧是蜂鸣器电路。
左上是低电平驱动,左下是高电平驱动,一般使用上面低电平驱动,一般单片机都是低电平弱驱动,高电平强驱动。
右侧是蜂鸣器电路,使用三极管驱动电路来完成驱动任务,三极管的左边是基极,带箭头的是发射极,剩下的是集电极。右上是PNP三极管驱动,右下是NPN三极管的驱动电路,他的驱动能力和右上的相反,基极给高电平导通,低电平断开。PNP的三极管最好接在上边,NPN三接管最好接在下边,三接管的通断是需要发射极和基极直接产生一点开启电压的,如果负载接在发射极,就可能导致三极管无法开启

面包板

面包板简单示例

点亮led
连接基础单元
注意红灯连接的是芯片A0处,后续调用的也是A0引脚,若接错则不亮!

下面是我本人连接的图片不同型号的stem32接口可能不一样,一定要根据上面图片看清楚再插,否则有可能点亮芯片!

- 点亮led的步骤
- 使用RCC开启GPIO时钟
STM32 RCC(Reset and Clock Control)是STM32微控制器中的复位和时钟控制模块,负责管理系统中的时钟信号和复位操作,是STM32的"心脏"和"管家",对系统的稳定运行和性能优化至关重要。
- 使用GPIO_Init函数初始化GPIO
- 使用输出或输入的函数控制GPIO口
以上涉及RCC和GPIO两个外设
初始化GPIO
了解rcc函数头文件
library文件夹下有stm32f10x_rcc.h文件

- 主要用到以下三个时钟使能控制函数

关于该处使能函数的误区(题外话)
- PB1 的含义
PB1 是 GPIO 引脚的标识,表示 Port B 的第 1 号引脚(即 GPIO_PIN_1)。
例如,在配置 GPIO 时,PB1 可能用于控制 LED 或读取按钮状态。
但 RCC_APB2PeriphClockCmd() 函数的参数 RCC_APB2Periph 与具体引脚无关,而是用于控制 整个外设模块的时钟(如 GPIOB、USART1 等)。 - RCC_APB2PeriphClockCmd() 的作用
该函数用于 使能或禁用 APB2 总线上的外设时钟,其参数是 外设模块的标识符,而非具体引脚
- RCC_APB2Periph_GPIOB 表示 整个 GPIOB 端口的时钟,而不是某个具体引脚(如 PB1 或 PB2)。
- 时钟使能后,才能操作该外设下的所有引脚(如 PB0~PB15)
在STM32微控制器中,时钟使能是控制外设和引脚的前提条件,这是由其硬件架构和低功耗设计决定的。以下是详细解释:
为什么要时钟使能才可以控制?
- 时钟的本质:外设的"电源开关"
时钟信号(Clock Signal)是数字电路的"心跳",它为外设提供同步工作的时序基准。
没有时钟,外设的寄存器无法读写,逻辑电路无法运行,相当于"断电"状态。
时钟使能后,外设才能根据时钟信号执行操作(如读取引脚状态、发送数据、生成PWM等)。
STM32的时钟树架构:
STM32的时钟系统分为多个层级(如HSI、HSE、PLL、AHB、APB1/APB2),外设的时钟最终来源于系统时钟(SYSCLK)的分频或倍频。
APB1/APB2总线:连接低速/高速外设,通过分频器为外设提供时钟(如APB2的PCLK2可达72MHz)。
外设时钟门控:每个外设的时钟通过独立的开关(如RCC_APB2Periph_GPIOB)控制,实现精细化的功耗管理。- 为什么必须时钟使能?
(1) 硬件设计要求
寄存器访问依赖时钟:
STM32的外设寄存器(如GPIO的ODR、IDR)是同步寄存器,必须有时钟信号才能完成读写操作。
例如:写入GPIOB->ODR = 0x0001(设置PB0为高电平)时,硬件会检查GPIOB的时钟是否使能。若未使能,写入操作会被忽略。
动态功耗控制:
STM32针对低功耗优化,未使用的外设默认关闭时钟以减少电流消耗。
例如:若不使用USART1,其时钟默认关闭,此时访问USART1寄存器会导致硬件错误(如总线错误或无响应)。
我们可以右键跳到函数的定义处查看函数,下图

若显示下面提醒,可能是删除了中间文件,需要重新编译一下。

跳过来后可以看到函数头部的注释,这种注释风格类似于javadoc,点击此处了解javadoc
此处表明了函数的作用和参数等等

初步了解stm32f10x_gpio.h
- 在stm32f10x_gpio.h的349行可以看到GPIO_DeInit函数,调用该函数,所指定的GPIO外设就会被复位。
- 下面的GPIO_AFIODeInit也是一样,可以复位AFIO外设
- 第三个函数GPIO_Init,其作用是,用结构体的参数来初始化GPIO,先
定义一个结构体变量,然后给结构体赋值,最后调用这个函数,这个函数会调用结构体的参数,自动根据结构体配置参数。这种Iinit函数在stm32中基本所有外设都有,初始化外设都是用Init函数来完成。 - GPIO_StructInit函数,这个函数可以把结构体变量赋值一个默认值
- 接着从GPIO_ReadInputDataBit下面4个是GPIO读入函数,从下面的GPIO_SetBits到GPIO_WriteBit都是GPIO写入函数,这8个函数就可以实现GPIO的读写功能。
- 剩下函数暂时用不到

所以当前主要学习GPIO_Init,和以上8个GPIO读写函数
外设时钟开启
首先调用RCC里面的APB2外设时钟控制函数
将RCC_APB2PeriphClockCmd 复制到main函数里,右键跳转到定义处。RCC_APB2PeriphClockCmd 是STM32微控制器中用于控制APB2总线上外设时钟开启或关闭的关键函数
这里我们要点亮PA0的LED,所以选择RCC_APB2外设GPIOA,放入第一个参数

如下这样:

继续跳转该函数的定义处,可以看到第二个参数的解释:

翻译过来就是:
NewState:指定外设时钟的新状态。
*此参数可以是:ENABLE或DISABLE。
这里我们目的是使能ENABLE
如下这样

调用GPIO_Init
第一个参数选择GPIOA,同样跳转定义,可以看到第二个参数解释是一个结构体,该结构体名称是GPIO_InitTypeDef,关于该结构体的介绍,该传入什么样的函数,可以跳转到该结构体定义。
我们也使用结构体实例,加一个点,编译器会提示有哪些成员变量如下图:

那么我们应该创建一个结构体,然后赋值该结构体,最后将结构体传入GPIO_Init注:对于有的老的c语言版本编译器要求所有局部变量定义必须在函数的最前面,根据自己的编译器调整位置,c99以上版本的就不必考虑
c
#include "stm32f1ox.h
int main(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//如果你的编译器是老版本,该定义需要放到最前面。
GPIO_InitTypeDef GPIO_InitStructure;
//下面的成员变量是通过上图的方式来调出对,下一步需要右键下面每个,跳转到定义处,查看如何赋值
GPIO_InitStructure.GPIO_Mode
GPIO_InitStructure.GPIO_Pin
GPIO_InitStructure.GPIO_Speed
GPIO_Init(GPIOA,
以下是跳转到GPIO_InitStructure.GPIO_Mode的定义处
翻译:指定所选引脚的工作模式。此参数可以是 @ref GPIOMode_TypeDef 中定义的某个值。

所以我们搜索GPIOMode_TypeDef,更简单的可以直接右键他,然后跳转到该定义,下图,红字是解释

AIN(Analog IN)是模拟输入
IN_GLOATING是浮空输入
IPD(In Pull Down 是下拉输入
IPU(In Pull Up)是上拉输入
Out_OD(Out Open Drain)是开漏输出
Out_PP(Atl Push Pull)是复用推挽输出
AF_OD(Atl Open Drain)是复用开漏
AF_PP(Atl Push Pull)是复用推挽
关于该处的知识点在GPIO的结构介绍过各种输出和输入的知识点
由于是电灯,所以我们使用强驱动力的推挽输出,选择Out_PP这一项!
所以代码Mode赋值为:
c
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
同理第二个参数GPIO_InitStructure.GPIO_Pin跳转到定义处,由于GPIO_Pin有多处定义,不会直接跳转,而是出现下面场景,我们选择带有member的一项

双击跳转:

所以同样,右键GPIO_pins_define,跳转定义,但是不知道为什么,弹窗,于是我们可以使用cctrl+F搜索该类型

搜索过来后是这个样子,由于灯是接在0号引脚,于是使用GPIO_Pin_0

所以代码赋值是:
c
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
同样跳转第三个参数GPIO_InitStructure.GPIO_Speed的定义处

同理跳转@ref类型,如下图选择50MHz的就行:

所以代码是注意:GPIO_Init的参数都是指针,所以传入结构体应该取地址&:
c
#include "stm32f10x.h" // Device header
int main(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1){
}
}
当以上GPIO_Init函数执行完,GPIOA外设的0号引脚就自动被配置为推挽输出,50MHz的速度了。其执行逻辑就是读取结构体参数,自动写入GPIO配置寄存器。有一定的封装思想
调用GPIO输入输出函数(上面提到的8种)
stm32f10x_gpio.h的353~360行为这8种函数
GPIO4个输出函数

文档介绍4种函数
第一个函数是GPIO_SetBits,第一个参数是GPIOx,第二个参数是GPIO_Pin,功能:将指定端口设置为高电平
第二个GPIO_ResetBits,参数和上面的一样,这个可以把指定的端口设置为低电平
第三个GPIO_WriteBit,这个函数有三个参数,前两个也是指定端口,第三个是BitValue,这个是根据第三个参数的值来设置指定的端口
第四个是GPIO_Write,第一个参数是GPIOx,选择外设,第二个参数是PortValue,这个函数可以同时对16个端口进行读写操作
- GPIO_SetBits
功能
将指定的GPIO引脚设置为高电平(逻辑1)。
函数原型
c
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
参数说明
| 参数 | 类型 | 描述 |
|---|---|---|
| GPIOx | GPIO_TypeDef* | GPIO端口号,可选:GPIOA、GPIOB、GPIOC等(取决于具体型号)。 |
| GPIO_Pin | uint16_t | 引脚号,使用库定义的宏(如GPIO_Pin_5表示第5号引脚),可多选(如`GPIO_Pin_5 |
示例
c
// 将GPIOA的第5号引脚设置为高电平
GPIO_SetBits(GPIOA, GPIO_Pin_5);
- GPIO_ResetBits
功能
将指定的GPIO引脚设置为低电平(逻辑0)。
函数原型
c
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
参数说明
(与GPIO_SetBits完全相同)
| 参数 | 类型 | 描述 |
|---|---|---|
| GPIOx | GPIO_TypeDef* | GPIO端口号(如GPIOA、GPIOB)。 |
| GPIO_Pin | uint16_t | 引脚号(如GPIO_Pin_5),支持多引脚组合(如`GPIO_Pin_5 |
示例
c
// 将GPIOA的第5号引脚设置为低电平
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
- GPIO_WriteBit
功能
根据第三个参数BitValue的值,动态设置GPIO引脚为高电平或低电平。
函数原型
c
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitValue);
参数说明
| 参数 | 类型 | 描述 |
|---|---|---|
| GPIOx | GPIO_TypeDef* | GPIO端口号(如GPIOA、GPIOB)。 |
| GPIO_Pin | uint16_t | 引脚号(如GPIO_Pin_5),仅支持单个引脚(不能多选)。 |
| BitValue | BitAction | 电平值,可选:- Bit_SET(高电平)- Bit_RESET(低电平)。可以是0或1,但是有警告。 |
若强行传入0或1作为第三个参数,需要强制转换类型(BitAction)0或(BitAction)1
示例
c
// 将GPIOA的第5号引脚设置为高电平
GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_SET);
// 将GPIOA的第5号引脚设置为低电平
GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET);
- GPIO_Write
在STM32标准外设库(Standard Peripheral Library)中,GPIO_Write 是一个用于写入整个GPIO端口输出数据寄存器(ODR)的函数,它可以一次性设置某个GPIO端口的所有引脚的电平状态(高或低)。与 GPIO_WriteBit(单引脚控制)不同,GPIO_Write 直接操作整个端口的16位数据(适用于STM32的16位GPIO端口)。
函数原型
c
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
参数说明
| 参数 | 类型 | 描述 |
|---|---|---|
| GPIOx | GPIO_TypeDef* | GPIO端口号,可选:GPIOA、GPIOB、GPIOC 等(取决于具体型号)。 |
| PortVal | uint16_t | 16位值,每位对应端口的一个引脚(如 0xFFFF 表示所有引脚高电平,0x0000 表示所有引脚低电平)。 |
功能
- 一次性设置整个GPIO端口的所有引脚电平(16位并行控制)。
- PortVal 的每一位(bit0 ~ bit15)对应 GPIOx 的 Pin_0 ~ Pin_15:
bit=1 → 对应引脚输出高电平。
bit=0 → 对应引脚输出低电平。
示例
- 设置整个GPIOA端口的电平
c
// 将GPIOA的所有引脚设置为高电平(0xFFFF = 16位全1)
GPIO_Write(GPIOA, 0xFFFF);
// 将GPIOA的所有引脚设置为低电平(0x0000 = 16位全0)
GPIO_Write(GPIOA, 0x0000);
// 设置GPIOA的部分引脚(如 Pin_0 和 Pin_1 高电平,其余低电平)
GPIO_Write(GPIOA, 0x0003); // 0000 0000 0000 0011 (二进制)
- 动态控制多个引脚
c
uint16_t port_value = 0;
// 设置 Pin_5 和 Pin_7 高电平,其余低电平
port_value |= (1 << 5); // Pin_5 高
port_value |= (1 << 7); // Pin_7 高
GPIO_Write(GPIOA, port_value); // 写入GPIOA
调用ResetBits点亮led灯
1.GPIO_ResetBits 把指定的端口设置为低电平

第一个GPIOx,x可以是A-->G
第二个GPIO_Pin_x,x可以是0~15
c
//将GPIOA 0号引脚赋值低电平
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
编译然后,下载

由于A0输出低电平,所以灯就会亮

- GPIO_SetBits
同理,调用GPIO_SetBits给高电平就会熄灭

3.GPIO_WriteBit 前两个参数一样,第三个参数注释翻译:参数可以是BitAction这个枚举中的一个值,其枚举包含Bit_RESET和Bit_SET,前者是低电平,后者是高电平

c
//高电平设置
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
//低电平设置
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
这样也可以设置A0为低电平,点亮led
- GPIO_Write 在STM32标准外设库(Standard Peripheral Library, SPL)中,GPIO_Write 是一个用于同时控制多个GPIO引脚的函数,与 GPIO_WriteBit(单引脚控制)不同,它可以一次性设置整个端口的输出状态。
函数原型
c
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
参数
- GPIOx:GPIO端口(如 GPIOA、GPIOB)。
- PortVal:16位无符号整数(uint16_t),每一位对应端口的一个引脚(0=低电平,1=高电平)。
功能 - 一次性设置整个GPIO端口的输出状态(所有引脚)。
- 适用于需要同时更新多个引脚的场景(如控制多位LED、数据总线等)。
led闪烁
我们可以使用while循环点亮led,需要用到延时,在学80c51单片机课程教学了Delay函数,这里直接提供延时函数库点击跳转复制库函数
在项目下创建System文件夹,用来存放系统资源,将Delay.h和Delay.c复制到文件夹下
同样点击三个箱子按钮

添加组System,将System所有文件添加进来


别忘记点击魔术棒按钮,c/c++一栏添加这个新文件夹的头文件路径


以后每次添加新库文件夹都要这么做
Delay库里有这三个函数,分别是微秒,毫秒,秒延时。这里使用SysTick实现延时,具体怎么实现的,可以不用管。

调用延时函数 #include "Delay.h",以下代码就可以实现闪烁led
c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1){
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
//延时500毫秒
Delay_ms(500);
GPIO_SetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);
}
}
关于推挽输出和开漏输出的驱动问题
运行上面代码后,若此时拔掉led灯,将正极接到A0上,负极接到负极线上(在面包板是是蓝线),此时是正极驱动led,仍然可以发亮,说明推挽高低电平驱动能力都强。若换成Out_OD(开漏模式)则不亮,说明开漏模式高电平驱动能力弱。一般情况电灯用推挽模式就行了。
插入流水灯

由于不方便,我少插了一个,如果你要全插,注意,A0的正极位于倒数第4个孔,否则插口不够

流水灯项目
复制以上项目,重命名为3-2LED流水灯,省去创建项目的功夫。

双击副本下带有keil图标的文件

整理复制的代码
要改动的只有main函数:
第一句:打开GPIOA时钟,我们连接的都是GPIOA的端口,不用删除
c
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
添加GPIO端口
c
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
之所以可以这样设置,是因为每个参数位于二进制的不同bit位上,0号是1,1号是0b10,2号是0b100,以此类推,GPIO_Pin_All是0b111111....表示选中所有引脚
除了引脚可以这样设置,GPIO时钟控制也可以这样类似:


以及GPIO_setBits等函数的第二个参数,也可以通过或来批量选择引脚,带有Bits表示可以设置多个引脚,一般都可以。

现在为了控制多个端口,我们使用GPIO_Write函数c语言不支持直接写二进制,由于低电平为亮,所以在while函数里,使用了取反~
c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
//添加其他7个端口
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_0);
while(1){
//依次点亮0~7端口
GPIO_Write(GPIOA,~0x0001); //0000 0000 0000 0001
Delay_ms(500);
GPIO_Write(GPIOA,~0x0002); //0000 0000 0000 0010
Delay_ms(500);
GPIO_Write(GPIOA,~0x0004); //0000 0000 0000 0100
Delay_ms(500);
GPIO_Write(GPIOA,~0x0008); //0000 0000 0000 1000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0010); //0000 0000 0001 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0020); //0000 0000 0010 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0040); //0000 0000 0100 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0x0080); //0000 0000 1000 0000
Delay_ms(500);
}
}
点响蜂鸣器
如图GND接到负极,I/O接到正极,vcc接到随便一个I/O口,这里选中PB12,注意:A15,B3,B4先别选,这三个口是JTAG的调试端口,如果想变成普通端口,还需要配置一下。

大致是这样接的

依旧复制上一个程序,改名3-3 蜂鸣器
以下代码基于上一个代码修改,是可以触发蜂鸣器的,更改的部分有注释和c51的无缘蜂鸣器不同,这个只要给了低电平信号就会发出声音,而不是给与脉冲信号:
c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void){
//更改为RCC_APB2Periph_GPIOB
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
//改为12口
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
//更改为GPIOB
GPIO_Init(GPIOB,&GPIO_InitStructure);
//代码逻辑更改
while(1){
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(500);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(500);
}
}
使用库函数的方法
1.转到.h头文件,跳转定义处,使用翻译软件查看解释。
2.查看库函数用户手册,里面有所有库函数的使用方法
