【STM32】GPIO输出

1 GPIO简介

(1)GPIO(General Purpose Input Output)通用输入输出口

(2)可配置为8种输入输出模式

(3)引脚电平:0V~3.3V,部分引脚可容忍5V(可以输入5V,但是输出还是3.3V)

I/O口电平带FT是可以接受输入5V

(4)输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等

(5)输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等

1.1 GPIO的基本结构

在STMB2中,所有的GPIO都是挂载在APB2外设总线上的。其中GPIO外设的名称是按照GPIOA、GPIOB、GPIOC等等这样来命名的;每个GPIO外设。总共有16的引脚。编号是从0到15(PA0,PA1,...,PA15)。

在每个GPIO模块肉。主要包含了寄存器驱动器这些东西。

寄存器就是一段特殊的存储器。内核可以通过APB2总线对寄存器进行读写,这样就完成输出电平和读取电平的功能了。寄存器的每一位对应一个引脚;其中输出寄存器写1。对应的引脚就会输出高电平;写0,就输出低电平。输入寄存器读取为1,就证明对应的端口目前是高电平;读取为0,就是低电平。因为STM32是32位的单片机,所以STM32内部的寄存器都是32位的,但这个端口只有16位。所以这个寄存器只有低16位对应的端口,高16位是没有用到的。

上一节电灯实验使用的是gpioc端回配置高寄存器控制板子上pc13的输出模式

驱动器是用来增加信号的驱动能力的,寄存器只负责存储数据,如果要进行点灯这样的操作的话,还是需要驱动器来负责增大驱动能力。

1.2 GPIO位结构

具体的1

具体2:上面输入,下面输出

先看输入部分:

IO引脚接了两个保护二极管,这个是对输入电压进行限幅的;上面VDD接3.3V,下面VSS接0V;

如果输入电压大于3.3V,上面的二极管就会导通,输入电压产生的电流就会直接流入VDD,而不会流入电路内部,这样可以避兔过高的电压对内部这些电路产生伤害;

如果输入电压小于0V(这个电压是相对于VSS的电压。所以是可以有负电压的),那这时下方这个二极管就会导通,电流会从VSS(引脚?)直接流出去,而不会从内部电路汲取电流,也是可以保护内部电路的。

如果输入电压在0~3.3V之间。那两个二极管均不会导通。

这时候电路到了上拉电阻和下拉电阻处。

这个开关是可以通过程序进行配置的:

如果上面导通、下面断开。就是上拉输入模武(默认为高电平的输入模式);

如果下面导通、上面断开。就是下拉输入模式(默认为低电平的输入模式);

如果两个都断开,就是浮空输入模式;

接下来是肖特基触发器(施密特触发器),这个施密特触发器的作用就是对输入电压进行整形的。它的执行逻辑是:如果输入电压大于某阈值,输出就会瞬间开为高电平;如果输入电压小于某─阈值,输出就会瞬闻降为低电平。

如果输入电压是上图这样的;定义两个阈值,比阈值1大输出高电平;比阈值2小输出低电平,则输出电压变为:

接下来经过施密特触发器整形的波形就可以直接写入输入数据寄存器了,然后读取就是了。

最后上面这述有两路线路。这些就是连接到片上外设的一些端口。

其中有模拟输入,这个是连接到ADC上的,因为ADC需要接收模拟量,所以这根线是接到施密特触发器前面的;

另一个是复用功能输入,这个是连接到其他需要读取端口的外设上的,比如串口的输入引脚等,这根线接收的是数字量,所以在施密特触发器后面。

接下来看输出部分:

数字部分可以由输出数据寄存器或片上外设控制,两种控制方式通过这个数据选择器接到了输出控制部分。如果选择通过输出数据寄存器进行控制,就是普通的IO口输出,写这个数据寄存器的某一位就可以操作对应的某个端口了。

左边的位设置/清除寄存器,这个可以用来单独操作输出数据寄存器的某一位,而不影响其它位。因为这个输出数据寄存器同时控制16个端口,并且这个寄存器只能整体读写,所以如果想单独控制其中某端口而不影响其他端口的话,就需要一些特殊的操作方式;

(1)先读出这个寄存器。然后用按位与和按位或的方式更改某位,最后再将更改后的数据写回去,在C语言中就是&=和|=的操作。(麻烦,效率不高)

(2)通过设置这个位设置和位清除寄存器,如果我们要对某1位进行置1的操作,在位设置寄存器的对应位写1即可,剩下不需要操作的位写0,这样它内部就会有电路自动将输出数据寄存器中对应应置为1,而剩下写0的位则保持不变。如果想对某一位进行清0的操作。就在位清除寄存器的对应位写1即可。

(3)读写STM32中的"位带"区域。在STM32中,专门分配的有一段地址区域,这段地址映射了RAM和外设寄存器所有的位,读写即可。

然后输出控制就接到了两个mos管

MOS管是一种电子开关,信号控制开关的导通和关闭,开关负责将IO口接到VDD或者VSS。

在这里回以选择推挽、开漏或关闭三种输出方式。

(1)在推挽输出模式下,P-MOS和N-MOS均有效。**数据寄存器为1时。上管导通,下管断开。输出直接接到VDD,就是输出高电平;数据寄存器为0时。上管断开,下管导通。输出直接接到VSS,就是输出低电平。**这种模式下。高低电平均有较强的驱动能力,所以推挽输出模武也可以叫强推输出模式。在推挽输出模式下。STM32对IO口具有绝对的控制权,高低电平都由STM32说的算。

(2)在开漏输出模式下,这个P-MOS是兔效的,只有N-MOS在工作。数据寄存器为1时,下管断开,这时输出相当于断开,也就是高阻模式;数据寄存器为0时。下管导通,输出直接接到VSS,也就是输出低电平。这种模式下。只有低电平有驱动能为,高电平是没有驱动能力的。可以作为通信协议的驱动方式,比如I2C通信的引脚,在多机通信的情况下,这个模式可以避免各个设备之间的相互干扰。另外开漏模式还可以用于输出5V的电平信号。

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

1.3 GPIO的模式

通过配置GPIO的端口配置寄存器,端口可以配置成以下8种模式

|----------|--------|--------------------------------|
| 模式名称 | 性质 | 特征 |
| 浮空输入 | 数字输入 | 可读取引脚电平,若引脚悬空,则电平不确定 |
| 上拉输入 | 数字输入 | 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平 |
| 下拉输入 | 数字输入 | 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平 |
| 模拟输入 | 模拟输入 | GPIO无效,引脚直接接入内部ADC |
| 开漏输出 | 数字输出 | 可输出引脚电平,高电平为高阻态(无驱动能力),低电平接VSS |
| 推挽输出 | 数字输出 | 可输出引脚电平,高电平接VDD,低电平接VSS |
| 复用开漏输出 | 数字输出 | 由片上外设控制,高电平为高阻态,低电平接VSS |
| 复用推挽输出 | 数字输出 | 由片上外设控制,高电平接VDD,低电平接VSS |

(1)浮空/上拉/下拉输入

输出驱动器是断开的,端口只能输入而不能输出,

(2)模拟输入

从引脚直接接片上外设,也就是ADC,所以,当我们使用ADC的时候。将引脚配置为模拟输入就行了。

(3)开漏/推挽输出

P-MOS无效就是开漏输出;如果P-MOS和N-MOS都有效,就是推挽输出。

另外还可以看到。在输出模式下,输入模式也是有效的,一个端口只能有一个输出,但是可以有多个输入。

(4)复用开漏/推挽输出

和普通的开漏/推挽输出差不多。

1.4 参考手册GPIO

(1)GPIO配置寄存器。

每一个端口的模式由4位进行配置,16位寄存器就需要64位,所以配置寄存器有两个,一个低寄存器,一个高寄存器

(2)端口输入数据寄存器

对应这个

低16位对应16个引脚,高16位没有使用。

(3)端口输出数据寄存器

对应这个

同样,低16位对应16个引脚,高16位没有使用。

(4)端口位设置/清除寄存器

对应这个

这个寄存器的高16位是进行位清除的,低16位是进行位设置的。

(5)端口位清除寄存器

这个寄存器的低16位和端口位设置/清除寄存器的高16位是一样的,为了方便操作设置的。

(6)端口配置锁定寄存器

1.5 LED和蜂鸣器简介

LED:发光二极管,正向通电点亮,反向通电不亮

有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定

无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音

LED硬件电路

低电平触发(一般使用这种)

高电平触发

蜂鸣器硬件电路

PNP型

NPN型

面包板

2 GPIO输出之LED闪烁

2.1 接线图

点亮LED,这里是低电平点亮

2.2 步骤

操作STM32的GPIO需要3个步骤: 使用RCC开启GPIO的时钟、使用GPIO_Init函数初始化GPIO、使用输入/输出函数控制GPIO口。

这里总共涉及了RCC和GPIO两个外设,接下来在library中查看这两个外设涉及的库函数

RCC

常用这三个

cpp 复制代码
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

// 使用
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); 

GPIO

目前使用的

cpp 复制代码
// 初始化
void GPIO_DeInit(GPIO_TypeDef* GPIOx);                                    // 外设被复位
void GPIO_AFIODeInit(void);                                               // 外设被复位
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);   // 用结构体初始化GPIO口
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);                  // 把结构体变量赋默认值

// GPIO的读取函数
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);


// GPIO的写入函数
// 把指定端口设置位高电平
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
// 把指定端口设置位低电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
// 根据第三个参数设置高低电平
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
// 可以同时对16个端口进行写入操作
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

使用

cpp 复制代码
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;      // 推挽输出
			// GPIO的8种工作模式
			typedef enum
			{ GPIO_Mode_AIN = 0x0,                // 模拟输入
			GPIO_Mode_IN_FLOATING = 0x04,         // 浮空输入
			GPIO_Mode_IPD = 0x28,                 // 下拉输入
			GPIO_Mode_IPU = 0x48,                 // 上拉输入
			GPIO_Mode_Out_OD = 0x14,              // 开漏输出
			GPIO_Mode_Out_PP = 0x10,              // 推挽输出
			GPIO_Mode_AF_OD = 0x1C,               // 复用开漏
			GPIO_Mode_AF_PP = 0x18                // 复用输出
}GPIOMode_TypeDef;


// 选择引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;

// 输出速率
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

// 调用函数
GPIO_Init(GPIOA, &GPIO_InitStructure);

点亮代码

cpp 复制代码
#include "stm32f10x.h"                  // Device header


int main()
{
	// 1使用RCC开启GPIO的时钟,点亮PA0口的外设
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2使用GPIO_Init函数初始化GPIO[推挽输出50MHz]
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;			// GPIOA的0号外设
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	// 推挽输出
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// 3使用GPIO的输入输出函数控制GPIO
//	GPIO_SetBits(GPIOA, GPIO_Pin_0);					// 将GPIOA的0号端口设置高电平(不亮)
//	GPIO_ResetBits(GPIOA, GPIO_Pin_0);					// 将GPIOA的0号端口设置低电平(亮)
	
//	GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);			// 高电平(不亮)
	GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);		// 低电平(亮)
	
	// 强转
	GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);    	// 点亮
//	GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);    	// 熄灭
	
	while (1)
	{
		
	
	}

}

2.3 LED闪烁

闪烁需要加延时函数。新加System模块,放延时函数。

代码

Delay.h

cpp 复制代码
#ifndef __DELAY_H
#define __DELAY_H

void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);

#endif

Delay.c

cpp 复制代码
#include "stm32f10x.h"

/**
  * @brief  微秒级延时
  * @param  xus 延时时长,范围:0~233015
  * @retval 无
  */
void Delay_us(uint32_t xus)
{
	SysTick->LOAD = 72 * xus;				//设置定时器重装值
	SysTick->VAL = 0x00;					//清空当前计数值
	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
	SysTick->CTRL = 0x00000004;				//关闭定时器
}

/**
  * @brief  毫秒级延时
  * @param  xms 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_ms(uint32_t xms)
{
	while(xms--)
	{
		Delay_us(1000);
	}
}
 
/**
  * @brief  秒级延时
  * @param  xs 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_s(uint32_t xs)
{
	while(xs--)
	{
		Delay_ms(1000);
	}
} 

闪烁代码

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main()
{
	// 1使用RCC开启GPIO的时钟,点亮PA0口的外设
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2使用GPIO_Init函数初始化GPIO[推挽输出50MHz]
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;			// GPIOA的0号外设
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	// 推挽输出
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	while (1)
	{
        // 3使用GPIO的输入输出函数控制GPIO
		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);			// 高电平(不亮)
		Delay_ms(500);										// 延迟
		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);		// 低电平(亮)
		Delay_ms(500);										// 延迟
        
        // GPIO_ResetBits、GPIO_SetBits也可以
	}

}

推挽输出和开漏输出的驱动问题

cpp 复制代码
// 一般都是推挽模式

// 推挽情况下,高低电平都是有驱动能力的。高低电平都能亮
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

// 开漏情况下,低电平是有驱动能力的,高电平没有驱动能力
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;

3 GPIO输出之LED流水灯

3.1 接线图

3.2 步骤

步骤同2.2,其实是每个灯点亮,然后延迟,再点亮下一个灯,延迟,...

3.3 LED流水灯

点亮8个灯

cpp 复制代码
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;

代码

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main()
{
	// 1使用RCC开启GPIO的时钟,点亮PA0口的外设
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2使用GPIO_Init函数初始化GPIO[推挽输出50MHz]
	GPIO_InitTypeDef GPIO_InitStructure;
	
//	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;			// GPIOA的0号外设
	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	// 推挽输出
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	while (1)
	{
		// 3使用GPIO的输入输出函数控制GPIO
//		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);
		
		
		for (unsigned int i = 0; i < 8; i++)
		{
			GPIO_Write(GPIOA, ~(0x0001 << i));
			Delay_ms(500);
		}
	}

}

4 GPIO输出之蜂鸣器

4.1 接线图

有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定(使用的是这个)

无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音

A15、B3、B4先別选,默认是JTAG的调试端口,如果要用的时候,还需要一些配置。

4.2 蜂鸣器

代码

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main()
{
	// 1使用RCC开启GPIO的时钟,点亮PB12口的外设
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	// 2使用GPIO_Init函数初始化GPIO[推挽输出50MHz]
	GPIO_InitTypeDef GPIO_InitStructure;
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;			// GPIOB的12号外设
	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	// 推挽输出
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	while (1)
	{
		GPIO_ResetBits(GPIOB, GPIO_Pin_12);
		Delay_ms(100);
		GPIO_SetBits(GPIOB, GPIO_Pin_12);
		Delay_ms(100);
		
		
		GPIO_ResetBits(GPIOB, GPIO_Pin_12);
		Delay_ms(500);
		GPIO_SetBits(GPIOB, GPIO_Pin_12);
		Delay_ms(500);
	}
}
相关推荐
网易独家音乐人Mike Zhou6 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
zy张起灵6 小时前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程
PegasusYu9 小时前
STM32CUBEIDE FreeRTOS操作教程(九):eventgroup事件标志组
stm32·教程·rtos·stm32cubeide·free-rtos·eventgroup·时间标志组
lantiandianzi13 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
文弱书生65613 小时前
输出比较简介
stm32
哔哥哔特商务网13 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式13 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
电子科技圈14 小时前
IAR与鸿轩科技共同推进汽车未来
科技·嵌入式硬件·mcu·汽车
东芝、铠侠总代1361006839315 小时前
浅谈TLP184小型平面光耦
单片机·嵌入式硬件·物联网·平面
lantiandianzi15 小时前
基于单片机中医药柜管理系统的设计
单片机·嵌入式硬件