1. GPIO概述
GPIO(General-purpose input/output):通用型输入输出。
简单理解就是我们可以控制输入输出的STM32引脚,统称为GPIO。GPIO存在的意义就是用程序控制或读取他们的输出或输入。
1.1. GPIO总体说明
STM32有多组GPIO,比如我们使用的芯片:STM32F103ZET6共有 7 组GPIO端口,他们分别是GPIOx(x从A-G),每组控制16个引脚,共有 112个 GPIO引脚。
1.2. GPIO的8种工作模式
1.2.1. 输入模式(四种)
(1)浮空输入(Input floating)
(2)上拉输入(Input pull-up)
(3)下拉输入(Input-pull-down)
(4)模拟输入(Analog)
输入模式下可以读取端口的高低电平,用于读取外接按键,外接模拟信号的输入,ADC电压采集,模拟通信协议接受数据等。
1.2.2. 输出模式(四种)
(1)通用开漏输出(Output open-drain)
(2)通用推挽式输出(Output push-pull)
(3)推挽式复用功能(Alternate function push-pull)
(4)开漏复用功能(Alternate function open-drain)
输出模式下可以控制端口输出高电平低电平,用于驱动LED,蜂鸣器等,如果是大功率器件(比如电机),还需要加上驱动器(小电流控制大电流)。
1.3. GPIO 输出流程
1.3.1. 推挽输出
(1)输出模式下,输出控制器被激活;
(2)当GPIO为推挽输出模式:
输出数据寄存器上输出1 将激活P-MOS,输出高电平。
输出数据寄存器上输出0 将激活N-MOS,输出低电平。
1.3.2. 开漏输出
输出模式下:
(1)输出缓冲器被激活;
(2)当GPIO为开漏输出模式:PMOS永远关闭。
输出寄存器上的输出 0 激活N-MOS,而输出寄存器上的1 将端口置于高阻状态
如果要输出1,外部必须要接上拉电阻。
1.3.3. 复用推挽输出
1.3.4. 复用开漏输出
输出模式下:
(1)施密特(肖特基)触发器输入被激活。
(2)弱上拉和下拉电阻被禁止。
(3)出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器。
(4)在开漏模式时,对输入数据寄存器的读访问可得到I/O状态。
(5)在推挽模式时,对输出数据寄存器的读访问得到最后一次写的值。
1.4. GPIO输入流程
1.4.1. 模拟输入
当配置为模拟输入时:
(1)输出部分被禁止。
(2)禁止施密特触发输入,实现了每个模拟I/O引脚上的零消耗。施密特触发输出值被强置为0。
(3)读取输入数据寄存器时数值永远为0。
(4)弱上拉和下拉电阻被禁止。
1.4.2. 上拉输入
1.4.3. 下拉输入
1.4.4. 浮空输入
1.4.5. 输入模式下元器件作用
(1)2个保护二极管的作用是保护我们的芯片不会由于电压过高或过低而烧毁。
VDD是接电源(3.3V),VSS接地(0V)。如果IO引脚的输入电压高于VDD的值到一定程度,上方保护二极管导通,则引脚电压被拉低到VDD。
如果IO引脚的输入电压(负电压)低于VSS到一定程度,则下方保护二极管导通,电压被拉高到VSS。
(2)2个开关控制引脚没有输入的时候是上拉,下拉还是浮空。
当上面的开关闭合的时候,输入被拉高到高电平。
当下面的开关闭合的时候,输入被拉低到低电平。如果两个都不闭合,输入就是悬空状态。两个同时闭合,就是费电了,不会这么做的。
(3)施密特(图中翻译成肖特基触发器应该是翻译错误,英文版手册是TTL Schmitt trigger)触发器是包含正反馈的比较器电路。可以对信号进行波形整形。
(4)输出控制器
(5)寄存器
2. GPIO相关的7个寄存器
2.1. GPIOx_CRL(端口配置低寄存器)
2.2. GPIOx_CRH(端口配置高寄存器)
2.3. GPIOx_IDR(端口输入数据寄存器)
2.4. GPIOx_ODR(端口输出数据寄存器)
2.5. GPIOx_BSRR(端口位设置/清除寄存器)
2.6. GPIOx_BRR(端口位清除寄存器)
2.7. GPIOx_LCKR(端口配置锁定寄存器)
3. GPIO点灯
3.1. 基于寄存器开发
3.1.1. 开启对应GPIO的时钟
1)找到GPIO是挂载在APB2上
2)找到APB2时钟使能寄存器对应为使能置 1(APB2时钟使能寄存器)
3.1.2. 给IO口设置工作模式: 输出模式 通用推挽输出
3)设置IO口工作模式(CRL端口配置低位寄存器)
3.1.3. 给对应的IO设置值: 1/0(ODR端口输出数据寄存器)
3.1.4. 寄存器点灯代码
#include "stm32f10x.h"
int main(void)
{
// 1. 开启对应GPIO的时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2. 给IO口设置工作模式: 输出模式 通用推挽输出
GPIOA->CRL &= ~GPIO_CRL_CNF0_1;
GPIOA->CRL &= ~GPIO_CRL_CNF0_0;
GPIOA->CRL |= GPIO_CRL_MODE0_1;
GPIOA->CRL |= GPIO_CRL_MODE0_0;
// 3. 给对应的IO设置值: 1/0
GPIOA->ODR &= ~GPIO_ODR_ODR0;
//while循环作用:防止程序跑飞
while (1)
{
}
}
3.2. 基于HAL库函数开发
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
static uint8_t previousState = GPIO_PIN_RESET; // 上一次PA8的状态
uint8_t currentState = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8); // 读取PA8当前状态
// 去抖动处理
if (currentState != previousState)
{
HAL_Delay(50); // 等待去抖动时间
currentState = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8); // 再次读取状态
}
if (currentState == GPIO_PIN_SET) // 如果PA8为高电平
{
HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET); // 设置PB0为低电平
HAL_GPIO_WritePin(LED0_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); // 设置PB1为低电平
}
else // 如果PA8为低电平或状态改变导致的不确定性已去除,确保PB0为高电平
{
HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET); // 设置PB0为高电平
HAL_GPIO_WritePin(LED0_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 设置PB1为高电平
}
previousState = currentState; // 更新PA8的上一次状态记录
}
4. GPIO点亮流水灯
4.1. 流水灯基于寄存器开发
#include "Driver_LED.h"
#include "Delay.h"
int main()
{
uint32_t leds[] = {LED_1, LED_2, LED_3};
/* 1. ³õʼ»¯LED */
Driver_LED_Init();
Drviver_LED_OffAll(leds, 3);
while (1)
{
for (uint8_t i = 0; i < 3; i++)
{
Drviver_LED_OffAll(leds, 3);
Drviver_LED_On(leds[i]);
Delay_ms(500);
}
Drviver_LED_OffAll(leds, 3);
Drviver_LED_On(leds[1]);
Delay_ms(500);
}
}
#include "Delay.h" // Device header
void Delay_us(uint16_t us)
{
/* 定时器重装值 */
SysTick->LOAD = 72 * us;
/* 清除当前计数值 */
SysTick->VAL = 0;
/*设置内部时钟源(2位->1),不需要中断(1位->0),并启动定时器(0位->1)*/
SysTick->CTRL = 0x5;
/*等待计数到0, 如果计数到0则16位会置为1*/
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG));
/* 关闭定时器 */
SysTick->CTRL &= ~SysTick_CTRL_ENABLE;
}
void Delay_ms(uint16_t ms)
{
while (ms--)
{
Delay_us(1000);
}
}
void Delay_s(uint16_t s)
{
while (s--)
{
Delay_ms(1000);
}
}
#ifndef __delay_h
#define __delay_h
#include "stm32f10x.h" // Device header
void Delay_us(uint16_t us);
void Delay_ms(uint16_t ms);
void Delay_s(uint16_t s);
#endif
#include "Driver_LED.h"
/**
* @description: 对LED进行初始化
*/
void Driver_LED_Init(void)
{
/* 1. 打开GPIOA的时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/* 2. 给用到的端口的所有 PIN (PA0 PA1 PA8) 设置工作模式: 通用推挽输出 MODE:11 CNF:00 */
GPIOA->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_CNF1);
GPIOA->CRH |= GPIO_CRH_MODE8;
GPIOA->CRH &= ~GPIO_CRH_CNF8;
/* 3. 关闭所有灯 */
Drviver_LED_Off(LED_1);
Drviver_LED_Off(LED_2);
Drviver_LED_Off(LED_3);
}
/**
* @description: 点亮指定的LED
* @param {uint32_t} led 要点亮的LED
*/
void Drviver_LED_On(uint32_t led)
{
GPIOA->ODR &= ~led;
}
/**
* @description: 关闭指定的LED
* @param {uint32_t} led 要关闭的LED
*/
void Drviver_LED_Off(uint32_t led)
{
GPIOA->ODR |= led;
}
/**
* @description: 翻转LED的状态
* @param {uint32_t} led 要翻转的LED
*/
void Drviver_LED_Toggle(uint32_t led)
{
/* 1. 读取引脚的电平,如果是1(目前是关闭), 打开, 否则就关闭 */
if ((GPIOA->IDR & led) == 0)
{
Drviver_LED_Off(led);
}
else
{
Drviver_LED_On(led);
}
}
/**
* @description: 打开数组中所有的灯
* @param {uint32_t} leds 所有灯
* @param {uint8_t} size 灯的个数
*/
void Drviver_LED_OnAll(uint32_t leds[], uint8_t size)
{
for (uint8_t i = 0; i < size; i++)
{
Drviver_LED_On(leds[i]);
}
}
/**
* @description: 关闭数组中所有的灯
* @param {uint32_t} leds 所有灯
* @param {uint8_t} size 灯的个数
*/
void Drviver_LED_OffAll(uint32_t leds[], uint8_t size)
{
for (uint8_t i = 0; i < size; i++)
{
Drviver_LED_Off(leds[i]);
}
}
#ifndef __DRIVER_LED_H
#define __DRIVER_LED_H
#include "stm32f10x.h"
#define LED_1 GPIO_ODR_ODR0
#define LED_2 GPIO_ODR_ODR1
#define LED_3 GPIO_ODR_ODR8
void Driver_LED_Init(void);
void Drviver_LED_On(uint32_t led);
void Drviver_LED_Off(uint32_t led);
void Drviver_LED_Toggle(uint32_t led);
void Drviver_LED_OnAll(uint32_t leds[], uint8_t size);
void Drviver_LED_OffAll(uint32_t leds[], uint8_t size);
#endif