江协科技STM32课程笔记(一)

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);
      }

    }
}