本文为百问网&韦东山【物联网智能家居实战训练营】课程笔记
1 模电知识
VDD
:D=device 表示芯片内部工作电源(一般VDD<VCC)。端口引脚电路中的VDD表示能够容忍3.3V电压(最大3.6V),如果是VDD_FT则表示能够忍5V。 VSS
:S=series 表示公共连接,通常指电路公共接地端电压。
TTL肖特基触发器即为用肖特基管构成的施密特触发器,施密特触发器利用门阀电压将引脚信号变成信号,进行转化为0/1数字信号存入输入数据寄存器。
当引脚电压高于VDD 时,上方的二极管导通,当引脚电压低于VSS 时,下方的二极管导通,防止不正常电压引入芯片导致芯片烧毁。(虽有这样的保护,但不能驱动大功率器件,如直接驱动电机,电机堵转的反向电流会烧毁芯片)
PMOS栅极低电平导通,NMOS栅极高电平导通。一般PMOS源极接VDD,NMOS源极接GND。 MOS管中寄生二极管作用是防止VDD过压的情况下,烧坏mos管。
推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务,电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载拉取电流。下面分析电路:
当输入为高电平时,经过反向后输出到MOS管栅极为低电平,PMOS的SD导通,OUT输出为高电平VDD
;当输如为低时,NMOS导通,OUT输出为低电平
。当引脚高低电平切换时,两个管子轮流导通,P 管负责灌电流,N 管负责拉电流,使其负载能力和开关速度都比普通的方式有很大的提高。
-
-
开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。
-
上拉电阻的阻值决定了逻辑电平转换的沿的速度 :阻值越大,速度越低,功耗越小;所以负载电阻的选择要兼顾功耗和速度。一般会带来上升沿的延时,因为上升沿是通过外接上拉电路对负载供电,所以若对延时有要求,建议用下降沿输出。
-
开漏输出可以实现功能,可以将多个开漏输出的Pin,经上拉电阻连接到一条总线上,实现与逻辑,主要用于IIC、SMBus总线。
线与:当在很多个开漏引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑0,相当于短路接地,所以外电路逻辑电平便为0;只有当所有引脚均输出高阻态时,才由上拉电阻提供高电平,即为逻辑1。
2 GPIO模块电路结构
2.1 输入模式
- 输出驱动器关闭(N/PMOS关闭)
- 施密特触发器打开,可以获取引脚状态
- 通过寄存器使能上/下拉电阻配置输入模式下的三种状态
- 出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器
- 引脚电平状态将存入输入数据寄存器
-
浮空输入
:上下拉电阻全断开 -
上拉输入
:上拉电阻打开,下拉电阻关闭 -
下拉输入
:下拉电阻打开,上拉电阻关闭
:
- 设计按键电路时可以利用芯片内部弱上拉和下拉电阻,这样省去了外接的电阻。
- 状态下,IO的电平状态是不确定的,完全由外部输入决定。
2.2 输出模式
- 输出驱动器打开
- 施密特触发器打开
- 弱上拉和下拉电阻被禁止
- 在每个APB2时钟周期,出现在I/O脚上的数据被采样到输入数据寄存器
- 在开漏模式时,对输入数据寄存器的读访问可得到I/O状态;在推挽式模式时,对输出数据寄存器的读访问得到最后一次写的值。
-
通用推挽输出
: -
当输出数据寄存器相应位为1时,同相端输出为1,但经过反相器(小圆圈)后,到PMOS栅极输出为0,反相端反相后输出为1,此时PMOS导通;NMOS截止,引脚输出
高电平
。() -
当输出数据寄存器相应位为0时,PMOS截止,NMOS导通,引脚输出
低电平
。()
通用开漏输出
: 开漏输出时,PMOS关闭(输出数据寄存器的1将端口置于),只有NMOS工作,但是此时只能输出低电平,要输出高电平必须外加上拉电阻。
上图为加了上拉电阻的开漏输出(需用户外接):当输出数据寄存器相应位为1时,反相端输出0,此时NMOS截止,由外加的上拉电阻提供高电平
。当输出数据寄存器相应位为0时,NMOS导通,引脚输出低电平
。
2.3 模拟输入模式
- 输出驱动器关闭
- 施密特触发器关闭
- 弱上拉和下拉电阻被禁止
- 输入数据寄存器的值为
0
(处于高阻抗)
因为模拟信号经过施密特触发器后只有0/1两种状态,因此信号源输入在施密特触发器前。类似地,当GPIO 引脚用于DAC 作为模拟电压输出通道时,DAC 的模拟信号输出就不经过双MOS 管结构,模拟信号直接输出到引脚。
模拟状态与模拟外设复用引脚:
- 模拟状态:表示引脚功能选择为模拟模式,但不作为任何片内模拟外设(ADC)的复用引脚,只是为了减少系统功耗。
- 模拟外设复用引脚:表示引脚作为片内模拟外设的复用引脚,用于完成相应功能操作,如ADC信号采集。
2.4 复用模式
- 输出可配置为推挽或者开漏模式,的信号驱动输出驱动器
- 施密特触发器打开
- 弱上拉和下拉电阻被禁止
- 在每个APB2时钟周期,出现在I/O脚上的数据被采样到输入数据寄存器
- 在开漏模式时,对输入数据寄存器的读访问可得到I/O状态;在推挽式模式时,对输出数据寄存器的读访问得到最后一次写的
复用推挽输出
:复用开漏输出
:
- 对于复用的输入功能,端口必须配置成输入模式(浮空、上拉或下拉)且输入引脚必须由外部驱动。
- 对于复用输出功能,端口必须配置成复用功能输出模式(推挽或开漏)。
- 对于双向复用功能,端口位必须配置复用功能输出模式(推挽或开漏)。
如果把端口配置成复用输出功能,则引脚和输出寄存器断开,并和片上外设的输出信号连接。 如果软件把一个GPIO脚配置成复用输出功能,但是外设没有被激活,它的输出将不确定。
:
- STM32复位后,IO端口处于
浮空输入
状态(CNFx[1:0]=01b,MODEx[1:0]=00b
);JTAG引脚复位以后,处于上拉或者下拉状态。 - stm32具有GPIO锁定机制,即锁定GPIO配置,下次复位前不能再修改端口位的配置。
- 所有IO端口都具有能力,端口必须配置成输入模式,才能使用外部中断功能。
- 当LSE振荡器关闭时,
OSC32_IN/OSC32_OUT
可以用作通用GPIOPC14/PC15
。当进入待机模式或者备份域由Vbat供电时,不能使用PC14/PC15
的GPIO口功能。 PC13/PC14/PC15
只能用于2MHz
的输出模式(LSE关闭,PC13
关闭入侵检测),最多只能带30pF
的负载,而且这些I/O口绝对不能当作电流源(如驱动LED)。(参考STM32中文手册4.1.2)- 一般上下拉电阻的阻值都在
30-50K
之间。这样可以增强MCU的抗干扰能力。 - 芯片内部上/下拉电阻不影响GPIO输出模式。
3 GPIO模块寄存器
: 以字(32位)的方式操作GPIO外设寄存器!
端口模式与输出速度配置: GPIO所有寄存器思维导图一览: GPIO寄存器地址映像和复位值: GPIO外设基地址与相对于APB2总线(0x4001 0000
)的偏移地址:
GPIO外设 | 基地址 | 相对APB2总线偏移地址 |
---|---|---|
GPIOA | 0x4001 0800 | 0x0000 0800 |
GPIOB | 0x4001 0C00 | 0x0000 0C00 |
GPIOC | 0x4001 1000 | 0x0000 1000 |
GPIOD | 0x4001 1400 | 0x0000 1400 |
GPIOE | 0x4001 1800 | 0x0000 1800 |
GPIOF | 0x4001 1C00 | 0x0000 1C00 |
GPIOG | 0x4001 2000 | 0x0000 2000 |
4 应用示例
直接使用寄存器点灯(PA8-红灯 PD2-黄灯),系统时钟启动文件跳转自动配置。 led.h
:
#ifndef __LED_H
#define __LED_H
#include <stdio.h>
typedef unsigned int uint32_t;
#define _IO volatile
#define _I volatile const
#define _O volatile
#define PERIPH_BASE 0x40000000UL
#define APB1_BASE PERIPH_BASE
#define APB2_BASE (PERIPH_BASE + 0x10000)
#define AHB_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_BASE (APB2_BASE + 0x0800)
#define GPIOD_BASE (APB2_BASE + 0x1400)
#define RCC_BASE (AHB_BASE + 0x1000)
typedef struct
{
_IO uint32_t CRL;
_IO uint32_t CRH;
_I uint32_t IDR;
_IO uint32_t ODR;
_IO uint32_t BSRR;
_IO uint32_t BRR;
_IO uint32_t LCKR;
} GPIO_Init_t;
typedef struct
{
_IO uint32_t CR;
_IO uint32_t CFGR;
_IO uint32_t CIR;
_IO uint32_t APB2RSTR;
_IO uint32_t APB1RSTR;
_IO uint32_t AHBENR;
_IO uint32_t APB2ENR;
_IO uint32_t APB1ENR;
_IO uint32_t BDCR;
_IO uint32_t CSR;
} RCC_t;
#define GPIOA ((GPIO_Init_t*)GPIOA_BASE)
#define GPIOD ((GPIO_Init_t*)GPIOD_BASE)
#define RCC ((RCC_t *) RCC_BASE)
#define RED_LED_GPIO_PORT GPIOA
#define RED_LED_GPIO_PIN (0x0100) // PIN8
#define YELLOW_LED_GPIO_PORT GPIOD
#define YELLOW_LED_GPIO_PIN (0x0004) // PIN2
#define RED_LED_ON (RED_LED_GPIO_PORT->BRR |= RED_LED_GPIO_PIN)
#define RED_LED_OFF (RED_LED_GPIO_PORT->BSRR |= RED_LED_GPIO_PIN)
#define RED_LED_TOGGLE (RED_LED_GPIO_PORT->ODR ^= RED_LED_GPIO_PIN)
#define YELLOW_LED_ON (YELLOW_LED_GPIO_PORT->BRR |= YELLOW_LED_GPIO_PIN)
#define YELLOW_LED_OFF (YELLOW_LED_GPIO_PORT->BSRR |= YELLOW_LED_GPIO_PIN)
#define YELLOW_LED_TOGGLE (YELLOW_LED_GPIO_PORT->ODR ^= YELLOW_LED_GPIO_PIN)
void LED_Init(void);
#endif /* __LED_H */
led.c & main.c
:
void LED_Init(void)
{
RCC->APB2ENR |= 1 << 2; // PortA
RCC->APB2ENR |= 1 << 5; // PortD
RED_LED_GPIO_PORT->CRH &= ~(0x0f << (0 * 4));
RED_LED_GPIO_PORT->CRH |= 0x03 << (0 * 4);
RED_LED_GPIO_PORT->BSRR |= 0x01 << 8;
YELLOW_LED_GPIO_PORT->CRL &= ~(0x0f << (2 * 4));
YELLOW_LED_GPIO_PORT->CRL |= 0x03 << (2 * 4);
YELLOW_LED_GPIO_PORT->BSRR |= 0x01 << 2;
}
int main()
{
LED_Init();
while(1)
{
YELLOW_LED_TOGGLE;
HAL_Delay(500);
}
}
END