STM32系统结构
一、GPIO
GPIO(General Purpose Input Output)通用输入输出口
可配置为8种输入输出模式
引脚电平:0V~3.3V,部分引脚可容忍5V
输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等
输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等
1、GPIO位结构
左边的三个为寄存器,中间的部分为驱动器,右边是引脚。
首先引脚处的两个二极管用于对输入电压进行限幅,当电压大于VDD,则电流会通过上面的二极管流入VDD,如果电压小于VSS,则电流通过VSS流出IO;而电压处于VSS和VDD之间时电流正常流入,用于保护内部电路。
其次输入驱动器中有两个电阻,对应上拉输入模式和下拉输入模式,如果两个开关均断开,则是浮空输入模式。上拉和下拉用于提供输入的默认值为1和0,浮空模式时根据引脚电压随意改变。为不影响输入操作,这两个电阻一般较大。TTL施密特触发器用于电压整形,当输入电压大于某个阈值则输出瞬间变为高电平,反之低于某个阈值时则瞬间变为低电平,相当于硬件消抖。模拟输入连接ADC,所以需要在施密特触发器前。
接着看输出驱动器。PMOS和NMOS组成推挽输出模式,数据为1时,pmos通,nmos断;数据为0时pmos断,nmos通,这种方式下的驱动能力较强;开漏模式,就是PMOS不存在,数据为1时引脚为高阻态,数据为0时nmos通为低电平,用于I2C通信(“线与”)和提供5v输出(引脚外部上拉电阻5v)
最后通过位设置/清除寄存器设置输出数据寄存器的某一位或某几位,来让引脚输出0或者1;
2、8种输入输出模式
浮空输入可在引脚不用时完全断开引脚,其他模式可能存在电流损耗或者短路,否则如果要使用,就需要接连续的驱动源,不能让引脚悬空。
3、LED和蜂鸣器硬件电路
硬件电路
左为低电平有效,右为高电平有效,R1用于限流,避免烧坏LED
左为PNP,低电平有效(带箭头的是发射极,需要左边的基极为低电平时,发射极与下面的集电极导通,电流从发射极流向基极和集电极);右边是NPN,高电平有效(类比上面,电流从基极和集电极流向发射极)。忘记了可以看看下文。
一篇文章将三极管讲透:三极管从原理到应用,从参数到特性,从入门到精通_晶体三极管耗最高振荡频率(fm)-CSDN博客
需要注意:但凡是用三极管做开关信号,驱动负载,一律把负载接到集电极回路。
负载在集电极:
当GPIO输入高电平时,三极管进入饱和区。此时集电极-发射极之间的压降
Vce
非常小,通常在0.2V ~ 0.3V(称为饱和压降Vce(sat)
)。因此,负载(蜂鸣器)两端的电压为
Vload = Vcc - Vce(sat) ≈ Vcc
。蜂鸣器得到了几乎全部的电源电压,可以正常工作。这是一个理想的开关:导通时电阻极低,压降极小。
负载在发射极(射极跟随器):
在这种配置下,发射极电压
Ve
永远跟着基极电压Vb
变化,且总是Ve = Vb - 0.7V
。假设GPIO输出高电平为3.3V,那么发射极电压最高只能是
3.3V - 0.7V = 2.6V
。如果蜂鸣器的工作电压是5V,那么此时它最多只能获得2.6V的电压,无法达到额定电压,可能导致蜂鸣器不响、响声小或不稳定。
三极管无法进入饱和区,始终工作在线性区,
Vce
压降较大(Vce = Vcc - Ve
),导致三极管自身功耗大、发热严重。负载在集电极:
GPIO电压和负载电源电压
Vcc
可以完全不同。你可以用一个3.3V的微控制器GPIO去控制一个由5V、12V甚至24V供电的负载。只要三极管的耐压(Vceo
)足够,且基极电阻设置正确即可。这是该电路最大的优势之一。负载在发射极:
GPIO的电压直接决定了负载能得到的最高电压。你必须使用一个比负载所需电压至少高0.7V的GPIO来控制它。这在实际应用中限制极大,几乎无法用于驱动高于逻辑电压的负载。
4、使用
输出以流水灯为例:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//GPIO_SetBits(GPIOA,GPIO_Pin_0);
//GPIO_ResetBits(GPIOA,GPIO_Pin_0);
//GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
uint8_t offset = 0x1;
while (1)
{
GPIO_Write(GPIOA,~offset);
offset = (offset << 1) | (offset >> 7);
sysDelayms(500);
}
输入以按键为例:
硬件电路采用下拉方式,引脚内部采用上拉输入
uint8_t Key_GetNum(void)
{
uint8_t Key = 0; //定义变量,默认键码值为0
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下
{
sysDelayms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手
sysDelayms(20); //延时消抖
Key |= GPIO_Pin_1; //置键码为1
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下
{
sysDelayms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手
sysDelayms(20); //延时消抖
Key |= GPIO_Pin_2; //置键码为2
}
return Key; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}
void ledTurn(uint8_t Key)
{
uint8_t temp = 0,writeData = 0;
if(Key & GPIO_Pin_1)
{
temp = (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1));
writeData = (~temp)&0x1;
GPIO_WriteBit(GPIOA, GPIO_Pin_1,(BitAction)writeData);
}
if(Key & GPIO_Pin_2)
{
temp = (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2));
writeData = (~temp)&0x1;
GPIO_WriteBit(GPIOA, GPIO_Pin_2,(BitAction)writeData);
}
}
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB,ENABLE);//打开GPIOA和B时钟
GPIO_InitTypeDef ledGPIO_InitStructure;
ledGPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
ledGPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
ledGPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&ledGPIO_InitStructure);//初始化GPIOA led
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);
GPIO_InitTypeDef keyGPIO_InitStructure;
keyGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
keyGPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
keyGPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&keyGPIO_InitStructure);//初始化GPIOA led
volatile uint8_t KeyNum = 0x0;
while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if(KeyNum != 0x0)
{
ledTurn(KeyNum);
}
}
}