一.STM32 GPIO
1. GPIO_MODE_AIN 模拟输入
输入信号不经施密特触发器直接接入,输入信号为模拟量而非数字量,其余输入方式输入数字量。
2. GPIO_MODE_IN_FLOATING 浮空输入
输入信号经过施密特触发器接入输入数据存储器。当无信号输入时,电压不确定。因为浮空输入既高阻输入,可以认为输入端口阻抗无穷大,这样可以检测到微弱的信号。(相当于电压表测电压,如果电压表内阻不够大而外部阻抗比较大,则电压表分压会比较小)。此时输入高电平即高电平,输入低电平即低电平。但是外界没有输入时输入电平却容易受到外界电磁以及各种玄学干扰的影响。如按键采用浮空输入,则在按键按下时输入电平为低,但是当松开按键时输入端口悬空,外界有微弱的干扰都会被端口检测到。此时端口可能高,也可能低。
3. GPIO_MODE_IPD 下拉输入
浮空输入在外界没有输入时状态不确定,可能对电路造成干扰。为了使得电路更加稳定,不出现没有输入时端口的输入数据被干扰 (比如手碰一下电压就发生变化)。这时就需要下拉电阻(或上拉电阻),此电阻与端口输入阻抗相比仍然较小。有输入信号时端口读取输入信号,无输入信号时端口电平被拉到低电平(高电平)。
4. GPIO_MODE_IPU 上拉输入
上拉输入与下拉输入类似,只是无输入信号时端口电平被拉到高电平。例如按键信号,当按下时输入低电平,松开时电平被拉到高电平。这样就不会出现按键松开时端口电平不确定的情况。即不知道时按下还是松开。
5. GPIO-MODE_OUT_OD 开漏输出
开漏输出即漏极开路输出。这种输出方式指场效应管漏极开路输出。需要接上拉电阻才能输出1。漏极经上拉电阻接到电源,栅极输出0时,场效应管截止(阻抗无线大),电压被分到场效应管上,此时输出为1。当栅极输出1时,场效应管导通,输出端口相当于接地,此时输出0。开漏输出高电平时是由外接电源输出的,因此可以实现高于输出端口电压的输出。可以实现电平的转换。开漏输出可以实现线与功能,方法是多个输出共接一个上拉电阻。但是漏极开路输出上升沿慢,因为上升沿是外接电源对上拉电阻以及外接负载充电。当上拉电阻较大或者负载容性较大时时间常数较大,充电较慢。需要较快反映时可以采用下降沿触发,此时没有电阻接入,电路的时间常数较小,充电较快。
6. GPIO_MODE_OUT_PP 推挽输出
推挽输出既可以输出1,又可以输出0。但是无法调节输出电压,因为输出高低电平均为三极管输入端电压,此电压在由芯片内部供电,无法改变。推挽输出任意时刻只有一路工作。上图为输出高电平时电路工作状态。只有三极管导通电阻,无外接电阻。因此推挽输出损耗小、速度快。
7. GPIO_MODE_AF_OD 复用开漏输出
STM32单片机内部有其他的外设,比如定时器、DAC等。复用开漏输出与普通开漏输出区别在于,开漏输出输出的是输出数据寄存器中的数据,复用开漏输出输出的是来自外设的数据。
8. GOIO_MODE_AF_PP 复用推挽输出
复用推挽输出原理与复用开漏输出原理相同
二.GPIO初始化
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
第一个参数是用来指定 GPIO,取值范围为 GPIOA~GPIOG。
第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。
GPIO_TypeDef
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
第一个参数是用来指定 GPIO,取值范围为 GPIOA~GPIOG。
GPIO_InitTypeDef
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。
实例:下面我们通过一个 GPIO 初始化实例来讲解这个结构体的成员变量的含义。
通过初始化结构体初始化 GPIO 的常用格式是:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度 50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);//根据设定参数配置 GPIO
上面代码的意思是设置 GPIOB 的第 5 个端口为推挽输出模式,同时速度为 50M。从上面初始
化代码可以看出,结构体 GPIO_InitStructure 的第一个成员变量 GPIO_Pin 用来设置是要初始化
哪个或者哪些 IO 口;第二个成员变量 GPIO_Mode 是用来设置对应 IO 端口的输出输入模式,
这些模式是上面我们讲解的 8 个模式,在 MDK 中是通过一个枚举类型定义的:
typedef enum
{ GPIO_Mode_AIN = 0x0, //模拟输入
GPIO_Mode_IN_FLOATING = 0x04, //浮空输入
GPIO_Mode_IPD = 0x28, //下拉输入
GPIO_Mode_IPU = 0x48, //上拉输入
GPIO_Mode_Out_OD = 0x14, //开漏输出
GPIO_Mode_Out_PP = 0x10, //通用推挽输出
GPIO_Mode_AF_OD = 0x1C, //复用开漏输出
GPIO_Mode_AF_PP = 0x18 //复用推挽
}GPIOMode_TypeDef;
第三个参数是 IO 口速度设置,有三个可选值,在 MDK 中同样是通过枚举类型定义:
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
三.在固件库中操作寄存器读取 和设置IO 端口数据
1.在固件库中操作IDR寄存器读取IO 端口数据
1)IDR寄存器原理
IDR 是一个端口输入数据寄存器,只用了低 16 位。该寄存器为只读寄存器,并且只能以
16 位的形式读出。该寄存器各位的描述如图所示:
要想知道某个 IO 口的电平状态,你只要读这个寄存器,再看某个位的状态就可以了。使
用起来是比较简单的。
2)函数
(1)GPIO_ReadInputDataBit 函数
在固件库中操作 IDR 寄存器读取 IO 端口数据是通过 GPIO_ReadInputDataBit 函数实现的:
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
作用:读取某个GPIO的输入电平
例:比如我要读 GPIOA.5 的电平状态,那么方法是:
GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);
返回值是 1(Bit_SET)或者 0(Bit_RESET);
(2)uint16_t GPIO_ReadInputData函数
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
作用:读取某组GPIO的输入电平
2.固件库中设置 ODR 寄存器的值来控制 IO 口的输出状态
1)寄存器原理
ODR 是一个端口输出数据寄存器,也只用了低 16 位。该寄存器为可读写,从该寄存器读
出来的数据可以用于判断当前 IO 口的输出状态。而向该寄存器写数据,则可以控制某个 IO 口
的输出电平。该寄存器的各位描述如图所示:
2)函数
在固件库中设置 ODR 寄存器的值来控制 IO 口的输出状态是通过函数 GPIO_Write 来实现
的:
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
作用:该函数一般用来往一次性一个 GPIO 的多个端口设值
3.通过 BSRR 和 BRR 寄存器设置 GPIO 端口输出
1)寄存器原理
BSRR 寄存器是端口位设置/清除寄存器。该寄存器和 ODR 寄存器具有类似的作用,都可
以用来设置 GPIO 端口的输出位是 1 还是 0。下面我们看看该寄存器的描述如下图:
该寄存器通过举例子可以很清楚了解它的使用方法。
例:设置 GPIOA 的第 1 个端口值为 1,那么只需要往寄存器 BSRR 的低 16 位对应位写 1 即可:
GPIOA->BSRR=1<<1;
设置 GPIOA 的第 1 个端口值为 0,只需要往寄存器高 16 位对应为写 1 即可:
GPIOA->BSRR=1<<(16+1)
该寄存器往相应位写 0 是无影响的,所以我们要设置某些位,我们不用管其他位的值。
BRR 寄存器是端口位清除寄存器。该寄存器的作用跟 BSRR 的高 16 位雷同,这里就不做
详细讲解。在 STM32 固件库中,通过 BSRR 和 BRR 寄存器设置 GPIO 端口输出是通过函数
GPIO_SetBits()和函数GPIO_ResetBits()来完成的。
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
2)函数
在多数情况下,我们都是采用这两个函数来设置 GPIO 端口的输入和输出状态。
比如我们要设置 GPIOB.5 输出 1,那么方法为:
GPIO_SetBits(GPIOB, GPIO_Pin_5);
如果要设置 GPIOB.5 输出位 0,方法为:
GPIO_ResetBits (GPIOB, GPIO_Pin_5);
四.跑马灯库函数实现
1.硬件
LED
LED0 ---GPIOB_5
LED1 ---GPIOE_5
跑马灯实验我们主要用到的固件库文件是:
stm32f10x_gpio.c /stm32f10x_gpio.h
stm32f10x_rcc.c/stm32f10x_rcc.h
misc.c/ misc.h
stm32f10x_usart /stm32f10x_usart.h
其中 stm32f10x_rcc.h 头文件在每个实验中都要引入,因为系统时钟配置函数以及相关的外设时钟使能函数都在这个其源文件 stm32f10x_rcc.c 中。stm32f10x_usart.h 和 misc.h 头文件在我们
SYSTEM 文件夹中都需要使用到,所以每个实验都会引用。
首先,找到之前 3.3 节新建的 Template 工程,在该文件夹下面新建一个 HARDWARE 的文
件夹,用来存储以后与硬件相关的代码,然后在 HARDWARE 文件夹下新建一个 LED 文件夹,
用来存放与 LED 相关的代码。
然后我们打开 USER 文件夹下的 LED.uvprojx 工程(如果是使用的上面新建的工程模板,那
么就是 Template. uvprojx,大家可以将其重命名为 LED. uvprojx),新建一个文件,然
后保存在 HARDWARE->LED 文件夹下面,保存为 led.c。在该文件中输入如下代码:
函数 void LED_Init(void)的功能就是用来实现配置 PB5和 PE5 为推挽输出
#include "led.h"
//初始化 PB5 和 PE5 为输出口.并使能这两个口的时钟
//LED IO 初始化
ALIENTEK 精英 STM32F103 V1 开发板教程
152
STM32 F1 开发指南(精英板- - 库函数 版) )
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|
RCC_APB2Periph_GPIOE, ENABLE); //使能 PB,PE 端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 推挽输出
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_SetBits(GPIOE,GPIO_Pin_5); /PE.5 输出高
}
这里需要注意的是:在配置 STM32 外设的时候,任何时候都要先使能该外设的时钟。GPIO 是挂载在 APB2 总线上的外设,在固件库中对挂载在 APB2 总线上的外设时钟使能是通过函数 RCC_APB2PeriphClockCmd()来实现的。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);
//使能 GPIOB,GPIOE 端口时钟
这行代码的作用是使能 APB2 总线上的 GPIOB 和 GPIOE 的时钟。其中GPIOB 和 GPIOE用|或运算。
保存 led.c 代码,然后我们按同样的方法,新建一个 led.h 文件,也保存在 LED 文件夹下面。
在 led.h 中输入如下代码:
#ifndef __LED_H
#define __LED_H
#include "sys.h"
//LED 端口定义
#define LED0 PBout(5)// DS0
#define LED1 PEout(5)// DS1
void LED_Init(void);//初始化
#endif
操作 IO 口输出高低电平的三种方法
1)通过位带操作 PB5 输出高低电平从而控制 LED0 的方法如下:
LED0=1; //通过位带操作控制 LED0 的引脚 PB5 输出高电平
LED0=0; //通过位带操作控制 LED0 的引脚 PB5 输出低电平
2)使用固件库操作和寄存器操作来实现 IO 口操作。库函数操作方法如下:
GPIO_SetBits(GPIOB, GPIO_Pin_5); //设置 GPIOB.5 输出 1,等同 LED0=1;
GPIO_ResetBits (GPIOB, GPIO_Pin_5); //设置 GPIOB.5 输出 0,等同 LED0=0;
3)直接操作寄存器BRR 和 BSRR 的方式来操作 IO 口输出高低电平
GPIOB->BRR=GPIO_Pin_5; //设置 GPIOB.5 输出 1,等同 LED0=1;
GPIOE->BSRR=GPIO_Pin_5; //设置 GPIOB.5 输出 0,等同 LED0=0;