知识回顾:
N-MOS和P-MOS
1、N-MOS的工作原理
给两块N型半导体分别引出两个电极,分别为MOS管的源极(S)和漏极(D),当给漏极和源极接上电,因为形成了两个背靠背的二极管,这时候是截止的
在P区加上了一层很薄的二氧化硅绝缘层,之后在绝缘层上加上了一块金属板,形成了栅极,当我们给栅极接上正电压后,金属板形成了电场,方向如上图所示,P区中的电子向绝缘层附近移动
当自由电子吸引的足够多的时,就形成了N沟道,使得之前的PN结不复存在,此时,MOS管导通
导通条件:VGS > 一定值(栅极与源极的电压)
2、P-MOS的工作原理
P-MOS管的工作原理与N-MOS管的工作原理正好相反,当给栅极施加负电压,N型区的空穴(少子)向绝缘层附近移动,当自由空穴吸引的足够多的时,就形成了P沟道,此时MOS管导通
导通条件:VGS < 一定值(栅极与源极的电压)
3、MOS管的区别
两者的箭头方向代表电子的流动方向
常见电路连接图:
在MOS管制作过程中,通常将源极(S)和衬底内部相连,源极与衬底之间的二极管被短接,不起作用,对外只显示漏极与衬底之间的二极管,也就是寄生二极管
寄生二极管的作用:
(1)易于区分源极和漏极
(2)当电路出现瞬间反向电流时,可以通过寄生二极管排出,而不会对MOS管造成伤害
如果将源极和漏极反接,由于寄生二极管的作用,MOS管就会失去开关的作用
一、GPIO简介
• GPIO(General Purpose Input Output)通用输入输出口
• 引脚电平:0V~3.3V,部分引脚可容忍5V(FT)
• 功能:实现STM32单片机与外部硬件之间的连接与数据交互
• 有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存 器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)
①保护二极管:防止芯片因外部过高或过低的电压而损坏。当引脚输入的电压高于VDD时,上方的二极管就会导通,输入电压产生的电流就会直接流入VDD,而不会流入芯片内部,对内部电路造成伤害;当引脚输入的电压低于VSS时,下方的二极管就会导通,电流就会从VSS流出,而不会从内部汲取电流;当输入电压介于VDD和VSS之间,两个二极管均不会导通,对电路没有什么影响
②上拉/下拉电阻:当引脚处于悬空状态时,给GPIO一个确定的电平信号。当引脚悬空时,上拉电阻可使引脚电平默认为高电平;下拉电阻,反之亦然。
③输出控制器:控制其两侧线路连接方式,可理解为一种特殊开关
模式一:将左边线路连接N/P-MOS管的栅极,推挽输出
模式二:断开P-MOS,只连接N-MOS管的栅极,开漏输出
模式三:当引脚处于输入模式下,处于关闭状态
TTL施密特触发器:将模拟信号转化为数字信号。以下图模型为例,当输入电压大于高参考电压,输出为低电平;当输入电压小于于低参考电压,输出为高电平
刚开始,输入电压为0,而V+ > V-,输出为高电平,几乎等于VCC。此时,R1和R3就可以看作并联,之后与R2串联。
根据串联分压原理,可得高参考电压
当输入电压大于高参考电压,输出低电平,即0V。此时,R2和R3就可以看作并联,之后与R1串联。根据串联分压原理,可得低参考电压
不用电压比较器的原因:由于噪声的存在,输入的信号总会存在一些波动,而电压比较器会随着波动而产生错误波形
二、GPIO工作模式
• GPIO_Mode_IPU 上拉输入
• GPIO_Mode_IPD 下拉输入
• GPIO_Mode_IN_FLOATING 浮空输入
• GPIO_Mode_AIN 模拟输入
• GPIO_Mode_Out_OD 开漏输出
• GPIO_Mode_Out_PP 推挽输出
• GPIO_Mode_AF_OD 复用开漏输出
• GPIO_Mode_AF_PP 复用推挽输出
(1)上拉输入(Input Pull Up,IPU)
当I/O口没有信号输入或者处于悬空状态,I/O口默认为高电平;当引脚输入高电平,那么CPU读取到的就是高电平;当引脚输入低电平,那么CPU读取到的就是低电平
I/O端口输入 | CPU读取 |
0 | 0 |
1 | 1 |
悬空 | 1 |
适用场合:需要引脚无外部输入时保持高电平状态或外部中断的触发条件为下升沿触发的场景
(2)下拉输入(Input Pull Down,IPD)
当I/O口没有信号输入或者处于悬空状态,I/O口默认为低电平;当引脚输入高电平,那么CPU读取到的就是高电平;当引脚输入低电平,那么CPU读取到的就是低电平
I/O端口输入 | CPU读取 |
0 | 0 |
1 | 1 |
悬空 | 0 |
适用场合:需要引脚无外部输入时保持低电平状态或外部中断的触发条件为上升沿触发的场景
(3)浮空输入(Input Floating,IN_FLOATING)
I/O口的电平是不确定,完全由外部输入决定。当引脚处于悬空(无信号输入)时,引脚电平呈现不确定性,极易受外界干扰而改变
I/O端口输入 | CPU读取 |
0 | 0 |
1 | 1 |
悬空 | 不确定 |
应用场景:适用于需要检测外部信号电平变化而不需要特定电平偏置的场合,如按键检测(按键未按下时引脚呈高阻态)等。
(4)模拟输入(Analog Input,AIN)
外部输入信号(模拟信号)直接进入片上外设ADC中,经过转换获得数字信号。此时,TTL施密特触发器和输出控制均处于关闭状态
值得注意的是,除了模拟输入模式下,TTL施密特触发器处于关闭状态,其他模式下,均处于打开状态
应用场景: 当GPIO引脚作为ADC通道输入或低功耗下省电时,需要配置为模拟输入模式。例如,连接温度传感器等模拟信号源,通过ADC采集模拟信号并转化为数字值进行处理。
(5)推挽输出(Push-Pull,Out_PP)
值得注意的是,输出控制器中存在一个反相器
当输出寄存器输出0时,经过输出控制变为1,此时P-NOS截止,N-MOS导通,引脚输出低电平
当输出寄存器输出1时,经过输出控制变为0,此时N-NOS截止,P-MOS导通,引脚输出高电平
(6)开漏输出(Open Drain Output,Out_OD )
当输出寄存器输出0时,经过输出控制变为1,此时N-MOS导通,引脚输出低电平
当输出寄存器输出1时,经过输出控制变为0,此时N-MOS截止,引脚呈现高阻态,没有驱动能力
同时,开漏输出还具有“线与”的功能
(7)复用推挽输出(AF_PP)
复用推挽输出与推挽输出类似,只是控制源来自片上外设。比如,PWM、串口
(8)复用开漏输出(AF_OD)
复用开漏输出与开漏输出类似,只是控制源来自片上外设。比如,I2C
开漏输出只能拉低输出信号,吸收电流能力强,高电平需要靠外部电阻拉高,适合电平不匹配的情况下,并且支持线与(在多机通信的模式,可以避免相互干扰)
推挽输出可以提供较强的驱动能力,但不支持线与
各个外设的引脚配置:
三、寄存器
必须以字(32位)的方式操作这些寄存器,对于寄存器操作,常常适用&=、|=进行操作
3.1 配置低寄存器(GPIOx_CRL)
3.2 配置高寄存器(GPIOx_CRH)
3.3 输入数据寄存器(GPIOx_IDR)
3.4 输出数据寄存器(GPIOx_ODR)
3.5 位设置/清除寄存器(GPIOx_BSRR)
3.6 位清除寄存器(GPIOx_BRR)
3.7 配置锁定寄存器(GPIOx_LCKR)
四、GPIO库函数
五、GPIO的配置流程
普通GPIO配置:
(1)配置GPIO的时钟
(2)利用函数GPIO_Init配置引脚,包括引脚名称、传输速率、工作模式
(3)完成GPIO_Init的设置
(4)对I/O口进行相应的操作
六、应用示例
(1)GPIO输出
将8个LED灯的负极依次连接在A0~A7,实现间隔为500ms的流水灯
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main()
{
uint8_t i = 0;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_All ; //选中GPIOA的所有引脚
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct); //初始化A0
while(1)
{
for(i = 0;i < 8;i++)
{
GPIO_Write(GPIOA,~(0x0001<<i)); //对GPIOA进行赋值
Delay_ms(500);
}
}
}
Delay.c
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
Delay.h
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
(2)GPIO输入
两个LED分别连接在A0(LED0)和A1(LED1),两个按键分别连接在B1(按键1)、B11(按键2)。实现按键1控制LED0的亮灭,按键2控制LED1的亮灭
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
int main()
{
uint8_t KeyNum = 0;
LED_Init();
Key_Init();
while(1)
{
KeyNum = Key_Num();
if(1 == KeyNum)
{
LED0_Turn();
}
if(2 == KeyNum)
{
LED1_Turn();
}
}
}
LED.c
#include "stm32f10x.h" // Device header
/**
* @brief 初始化LED连接的I/O口(A0、A1)
* @param 无
* @retval 无
*/
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //选中A0、A1
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct); //初始化A0、A1
GPIO_SetBits(GPIOA,GPIO_Pin_0 | GPIO_Pin_1); //上电后,端口默认输出低电平
}
/**
* @brief 反转连接LED0负极的I/O(A0)电平
* @param 无
* @retval 无
*/
void LED0_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_0)) //读取A0输出的电平
{
GPIO_ResetBits(GPIOA,GPIO_Pin_0);;
}
else
{
GPIO_SetBits(GPIOA,GPIO_Pin_0);
}
}
/**
* @brief 反转连接LED1负极的I/O(A1)电平
* @param 无
* @retval 无
*/
void LED1_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1)) //读取A0输出的电平
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);;
}
else
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
}
LED.h
#ifndef _LED_H_
#define _LED_H_
void LED_Init(void);
void LED0_Turn(void);
void LED1_Turn(void);
#endif
Key.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* @brief 初始化按键连接的I/O口(B1、B11)
* @param 无
* @retval 无
*/
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //打开GPIOB的时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11; //选中B1、B11
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct); //初始化B1、B11
}
/**
* @brief 获取按键的键值
* @param 无
* @retval num:返回一个无符号整型的键值,范围:0~255
*/
uint8_t Key_Num(void)
{
uint8_t num = 0; //当无按键按下,默认键值为0
if(!GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1))
{
Delay_ms(10); //消抖
while(!GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1));
Delay_ms(10); //消抖
num = 1;
}
if(!GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11))
{
Delay_ms(10);
while(!GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11));
Delay_ms(10);
num = 2;
}
return num;
}
Key.h
#ifndef _KEY_H_
#define _KEY_H_
void Key_Init(void);
uint8_t Key_Num(void);
#endif