一、GPIO
1、GPIO的八种输入输出模式
GPIO位结构图如下图所示:
上图分两部分,上半部分为输入控制,下半部分为输出控制,先从最右边的“保护二极管”看起:
保护二极管的作用是防止电压过高或过低而产生对内部电路的损坏。当输入电压大于Vdd时(stm32的Vdd是3.3v),上方的二极管就会导通,电流由I/O引脚流入通过保护二极管直接流入Vdd而不会进入内部电路从而保护了内部电路;相同的,当输入电压小于Vss时,下方的二极管就会导通,电流由Vss通过保护二极管直接流出,而不会从内部电路汲取电流;如果输入电压在Vss和Vdd之间,那么两个二极管都不导通,从而保证电路正常工作。
(1)输入模式
接下来先看上半部分,也就是GPIO的输入模式,首先是下图这里:
这里当上方导通下方断开则为上拉输入模式,上方断开下方导通则为下拉输入模式,两者都断开则为浮空输入模式。但因为浮空输入模式非常不稳定,易受外界信号干扰,所以就引出了上拉输入和下拉输入模式的作用,即事先给定一个高/低电平,使引脚在悬空状态下仍能固定输出高低电平。但这里上拉下拉电路中电阻较大,是一种弱上拉或弱下拉,这样能够尽量不影响正常输入。
下一步来看这个施密特触发器(江协科技中讲到这里的肖特基触发器应该是个翻译错误)如下图:
这个触发器的执行逻辑为:当输入电压大于某一阈值,那就输出高电平;当输入电压小于某一阈值,那就输出低电平。这样可以减少信号输入失真抖动时的影响。
经过施密特触发器整形后的信号就可以直接写入寄存器然后读取某一位的高低电平了。除此之外还有两类输入:1.模拟输入,需要读取模拟量所以在施密特触发器之前,用来接ADC,因为ADC就是读取模拟量。2.复用功能输入:需要读取数字量所以在施密特触发器之后,比如串口的输入引脚等。
(2)输出模式
接下来是输出部分,如下图:
输出可由输出数据寄存器和片上外设控制,经过输出控制器后来到P-MOS和N-MOS两电子开关处,此处可选择推挽、开漏或关闭输入模式。1、推挽输出:当数据寄存器为1时,P-MOS导通,N-MOS断开,输出直接接到Vdd,输出高电平;当数据寄存器为0时,P-MOS断开,N-MOS导通,输出直接接到Vss,输出低电平。此时高低电平均有较强的驱动能力,stm32对I/O口有绝对的控制权,高低电平由stm32决定。2、开漏输出:在开漏输入模式下P-MOS无效,当数据寄存器为1时,N-MOS断开,也就是输出断开,相当于高阻模式,当数据寄存器为0时,输出低电平。这个模式下只有低电平有驱动能力,在I2C通信协议中采用这个方式能避免多个通信之间相互干扰。此外,开漏输出还可以用来输出5V的电平信号。3.关闭输出:P-MOS和N-MOS两电子开关都无效。
(3)总结
程序名称 | 模式名称 | 性质 | 特征 |
---|---|---|---|
GPIO_Mode_IN_FLOATING | 浮空输入 | 数字输入 | 可读取引脚电平,若引脚悬空则电平不确定 |
GPIO_Mode_IPU | 上拉输入 | 数字输入 | 可读取引脚电平,悬空时默认高电平 |
GPIO_Mode_IPD | 下拉输入 | 数字输入 | 可读取引脚电平,悬空时默认低电平 |
GPIO_Mode_AIN | 模拟输入 | 模拟输入 | GPIO无效,引脚直接接入内部ADC |
GPIO_Mode_Out_PP | 推挽输出 | 数字输出 | 可输出引脚电平,高电平接Vdd,低电平接Vss |
GPIO_Mode_Out_OD | 开漏输出 | 数字输出 | 可输出引脚电平,高电平为高阻态,低电平接Vss |
GPIO_Mode_AF_PP | 复用推挽输出 | 数字输出 | 由片上外设控制,高电平接Vdd,低电平接Vss |
GPIO_Mode_AF_OD | 复用开漏输出 | 数字输出 | 由片上外设控制,高电平为高阻态,低电平接Vss |
2、操作GPIO
操作GPIO口的步骤为三个部分:
(1)使用RCC开启GPIO时钟
(2)使用GPIO_Init函数初始化GPIO
(3)使用输入输出函数控制GPIO
2.1 GPIO输出:
以LED灯闪烁代码为例:
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
\\第一步,开启RCC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
\\第二步,使用GPIO_Init函数初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
\\第三步,使用输入输出函数控制GPIO
while(1)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
Delay_ms(500);
//GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)0);
//GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)1);
}
}
由此可以看出GPIO输出的运行规则。
2.2 GPIO输入:
首先来看按键输入。按键的原理其实就是按下接通,松手断开,这个过程会出现一定的机械抖动。
按键的硬件电路如下:
由这个原理图可以看出当按键按下接通时,PA0引脚直接接到GND,此时读取端口的电平为低电平;当按键松开时,PA0引脚处于悬空状态,此时端口的电压如果是浮空输入就会出现电平不确定的错误情况,所以这种接法一定要配置上拉输入模式。相较而言,以下硬件电路接法就很好的规避了这个问题:
以上是一些硬件基础,接下来还要复习一些软件基础,比如c语言的一些知识:
2.2.1 c语言的数据类型
关键字 | 位数 | 表示范围 | stdint关键字 | ST关键字 |
---|---|---|---|---|
char | 8 | -128~127 | int8_t | s8 |
unsighed char | 8 | 0~255 | uint8_t | u8 |
short | 16 | -32768~32767 | int16_t | s16 |
unsighed short | 16 | 0~65535 | uint16_t | u16 |
int | 32 | -2147483648~2147483647 | int32_t | s32 |
unsighed int | 32 | 0~4294967295 | uint32_t | u32 |
long | 32 | -2147483648~2147483647 | s32 | |
unsighed long | 32 | 0~4294967295 | u32 | |
long long | 64 | -2^64/2~2^64/2-1 | int64_t | s64 |
unsighed long long | 64 | 0~2^64-1 | uint64_t | u64 |
float | 32 | -3.4e38~3.4e38 | ||
double | 64 | -1.7e308~1.7e308 |
2.2.2 c语言的宏定义
关键字:#define
用途:用一个字符串代替一串数字,便于理解,防止出错。
注意:定义后不加分号,定义的新名字在左边
2.2.3 c语言的typedef
关键字:typedef
用途:将一个比较长的变量类型名换个名字,便于使用
注意:定义后加分号,定义的新名字在右边,只能专门给变量类型换名字
2.2.4 c语言的结构体
关键字:struct
用途:数据打包,不同类型变量的集合
定义结构体变量:struct{char x;int y; float z;} StructName;
引用结构体成员:StructName.x = 'A';
StructName.y = 66;
2.2.5 c语言的枚举
关键字:enum
用途:定义一个取值受限制的整型变量,用于限制变量的取值范围;宏定义的集合
定义枚举变量:
enum{FALSE = 0,TRUE = 1} EnumName;
因为枚举变量类型较长,所以通常用typedef更改变量类型名
引用枚举成员:
EnumName = FALSE;
EnumName = TRUE;