STM32F1单片机-GPIO
一、C语言部分
- 数据类型和关键字
stdint关键字使用了新名称来定义数据类型,ST是老版本的数据类型名(例:int8_t有符号8位,uint8_t无符号8位)
- 宏定义
#define:用一个字符串去代替一个数字。例#define ABC 12345
typedef:将一个比较长的变量类型名换个名字(针对变量类型)。例typedef unsigned char uint_8,即unsigned char a等效于uint_8 a
- 结构体:数据打包,不同变量类型的集合
定义结构体变量(struct {数据类型} 结构体名):
struct {char x;int y;float z;} StructName
因为结构体变量类型较长,一般用typedef更改变量类型名
例:typedef struct{char x;int y;float z;} StructName_t
则可以写成StructName_t StructName;
引用结构体成员:StryctName.x = ‘A’
- 枚举变量:定义一个取值受限制的整数变量,用于限制变量的取值范围
定义枚举变量(enum{} 枚举名):
enum {FALSE = 0,TRUE = 1} EnumName;
因为枚举变量类型较长,所以一般用typedef更改变量类型名
例:typedef enum{MON = 1,TUE,WED} week_t
则可以写成week_t week
引用枚举成员:week = MON即week=1
二、GPIO(通用输入输出口)
-
GPIO总共可以配置成8种输入输出模式
-
输入模式:浮空输入(电平不确定)、上拉输入IPU(上拉电阻,默认高电平)、下拉输入IPD(下拉电阻,默认低电平)、模拟输入(GPIO无效,引脚直接接入内部ADC)
-
输出模式:推挽输出PP(输出高低电平都可以)、开漏输出OD(输出低电平有效)、复用推挽输出AF_PP(电平由片上外设控制)、复用开漏输出AF_OD(电平由片上外设控制)、关闭(都无效)
开漏输出模式下,当引脚输出低电平时,内部开关导通,引脚接地,低电平有驱动能力,当配置成高电平时,内部开关断开,引脚处于高阻态模式,不驱动任何电平,如果需要驱动高电平,则需要外接上拉电阻
STM32单片机的GPIO口内部接了一个弱上、下拉电阻,但开漏输出模式下会禁用内部上下拉电阻,但可以在外部(外设)接上拉电阻,从而实现在高阻态下由上拉电阻拉高GPIO口,从而在开漏输出模式下输出高电平
- 输出模式下可以控制端口输出高低电平,用以驱动LED、蜂鸣器、模拟通信协议输出时序
- 输入模式下单片机可以读取端口的高低电平或者电压,用以读取按键输入、外接模块电平信号输入、ADC电压采集、模块通信协议接收数据等
- GPIO引脚电平可以配置成0-3.3V,部分引脚可以容忍到5V,即输出最大3.3V,这是由于单片机供电的3.3V,有些端口输入电压(FT)可以到5V
- GPIO速度:F1系列最高50MHz
下图为GPIO的基本结构图
开漏模式:输出寄存器的0激活N-MOS,输出寄存器的1将端口置于高阻态模式(P-MOS从不被激活),读输入寄存器同样也可以得到GPIO状态(外部信号),即同样可以输入(依赖外部上拉电阻提供的电压来确定输入状态),高阻态模式下允许外置的上拉电阻可以拉高电平,从而可以准确读取到外部输入信号
推挽模式:输出寄存器的0激活N-MOS,输出寄存器的1激活P-MOS,推挽模式下,必须重新初始化GPIO配置成输入模式才可以输入(读取)外部的信号(由于较强主动地驱动高低电平,会影响外部引脚的对电平的控制),但是可以读取当前引脚输出的电平
有的GPIO可以容忍5V电压是因为保护二极管不一致
下图为GPIO的基本框架
GPIO是挂载在APB2外设总线上的,每个GPIO有16个引脚
三、LED&蜂鸣器&按键
下图是按键部分的电路原理图
左边表示K1悬空接到单片机PA0口,低电平有效,所以在配置GPIO输入模式时,需要配置上拉输入模式,使得空闲为高电平;
右边接了一个上拉电阻,空闲时默认高电平,所以配置GPIO输入模式时,可以选择浮空输入模式或者上拉输入模式
四、传感器模块
- 传感器模块包括:光敏电阻、热敏电阻、红外接收管等
- 传感器的电阻会随着外界因素的变化而变化,通过与定值电阻分压得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出(0或1)
如下图是通用的传感器模块电路图
从左到右,依次为电压比较器、阈值电路、分压电路(N1就是光敏电阻or热敏电阻等等)、电源指示灯、DO输出电平指示灯电路(低电平点亮)
AO是模拟电压输出(接收光照、热量强度),DO是数字电压输出(0和1),数字输出就是对AO二值化的结果,是由电压比较器来完成的;电压比较器中IN+接到AO,IN-接到阈值电路(电位器),调节阈值,两个电压进行比较最终输出DO,接到引脚输出端;
下图为传感器模块的接线原理图
五、功能实现(标准库)
5.1 GPIO输出
5.1.1 寄存器配置方式点亮LED
寄存器配置点亮PC13的LED,PC13口是挂载在APB2总线上的,先配置APB2外设时钟使能寄存器(RCC_APB2ENR),然后配置PC13口的模式,找到端口配置高寄存器(x=8-15),最后配置端口输出数据寄存器
RCC->APB2ENR = 0x00000010; //STM32的寄存器为32位,GPIOPC13使能给1
GPIOC->CRH = 0x00300000; //配置端口模式-通用推挽输出
GPIOC->ODR = 0x00000000; //配置端口输出数据寄存器
配置寄存器方式去操作STM32系列单片机较为繁琐,位数较多
5.1.2 LED闪烁
使用库函数去实现LED闪烁,在使用外设时,需要先配置RCC的时钟,LED负极接在PA0口,GPIOA挂载在APB2总线上,所以第一步需要开启RCC_APB2PeriphClockCmd时钟,第二步需要初始化GPIO_Init(配置模式、Pin口和速度),最后一步给定GPIO口高低电平
#include "stm32f10x.h"
#include "Delay.h"
//涉及外设:GPIO
int main(void)
{
//开启RCC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //Pin0
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //给PA0低电平
Delay_ms(100);
GPIO_SetBits(GPIOA,GPIO_Pin_0); //给PA0高电平
Delay_ms(100);
}
}
5.1.3 LED流水灯
多个LED接在不同的GPIO口,配置GPIOA时需要同时配置不同的Pin,可以|上需要的Pin或者直接给GPIO_Pin = GPIO_Pin_ALL
#include "stm32f10x.h"
#include "Delay.h"
int main(void)
{
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_Pin_0|GPIO_Pin_1...
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1)
{
GPIO_Write(GPIOA,~(0x0001)); //0000 0000 0000 0001,PA0点亮
Delay_ms(500);
GPIO_Write(GPIOA,~(0x0002)); //PA1点亮
Delay_ms(500);
GPIO_Write(GPIOA,~(0x0004)); //PA2点亮
Delay_ms(500);
}
}
5.1.4 蜂鸣器
使用有源蜂鸣器,只要给蜂鸣器端口低电平,蜂鸣器就会响
#include "stm32f10x.h"
#include "Delay.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启APB2时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PA12
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
while(1)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12); //响
Delay_ms(100);
GPIO_SetBits(GPIOB, GPIO_Pin_12); //不响
Delay_ms(100);
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
Delay_ms(100);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
Delay_ms(700);
}
}
5.2 GPIO输入
5.2.1 按键控制LED亮灭
按键端口配置成上拉输入模式
按键控制需要按键消抖
下面给出Key.c
#include "stm32f10x.h"
#include "Delay.h"
/*
@brief:GPIOB初始化
*/
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_Initstructure;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_11;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_Initstructure);
}
/*
@brief:获取按键键码值
@retval:按键键码值:PB1按下返回1,PB11按下返回2
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0;
if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
{
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0);
Delay_ms(20);
KeyNum = 1;
}
if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0)
{
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0);
Delay_ms(20);
KeyNum = 2;
}
return KeyNum;
}
下面给出LED.c
#include "stm32f10x.h"
/*
@brief:GPIOA初始化
*/
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_Initstructure;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Initstructure);
GPIO_SetBits(GPIOA,GPIO_Pin_1 | GPIO_Pin_2); //初始为高电平
}
/*
@brief:LED1-PA1电平翻转
*/
void LED1_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1) == 0) //读取GPIOA_Pin_1为低
{
GPIO_SetBits(GPIOA,GPIO_Pin_1); //给GPIOA_Pin_1高
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1); //给GPIOA_Pin_1低
}
}
/*
@brief:LED2-PA2电平翻转
*/
void LED2_Turn(void)
{
if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2) == 0) //读取GPIOA_Pin_2为低
{
GPIO_SetBits(GPIOA,GPIO_Pin_2); //给GPIOA_Pin_2高
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_2); //给GPIOA_Pin_2低
}
}
/*
@brief:点亮LED1
*/
void LED1_ON(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
/*
@brief:熄灭LED1
*/
void LED1_OFF(void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
/*
@brief:点亮LED2
*/
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_2);
}
/*
@brief:熄灭LED2
*/
void LED2_OFF(void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_2);
}
5.2.2 光敏电阻电阻控制蜂鸣器
光敏电阻模块采用上拉输入模式,较暗环境输入1,较亮环境输入0
传感器DO数字量(01)通过GPIO输入,单片机读GPIO口
下面为Buzzer.c
#include "stm32f10x.h"
/*
@brief:蜂鸣器初始化
*/
void Buzzer_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
/*
@brief:开启蜂鸣器
*/
void Buzzer_ON()
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
}
/*
@brief:蜂鸣器关闭
*/
void Buzzer_OFF()
{
GPIO_SetBits(GPIOB,GPIO_Pin_12);
}
/*
@brief:蜂鸣器状态翻转
*/
void Buzzer_Turn()
{
if(GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_12) == 0)
{
GPIO_SetBits(GPIOB,GPIO_Pin_12);
}
else
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
}
}
下面为LightSensor.c
#include "stm32f10x.h"
/*
@brief:光敏电阻初始化
*/
void LightSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
/*
@brief:返回光敏电阻数字量(01),较暗环境输入1,较亮环境输入0
@retval:光敏传感器数字量
*/
uint8_t LightSensor_Get()
{
return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13);
}
六、功能实现(HAL库)
通用外设驱动模型(四步法)
- 初始化:时钟设置、参数设置、IO设置、中断设置(开中断、设NVIC)-可选
- 读函数(可选):从外设读取数据
- 写函数(可选):往外设写入数据
- 中断服务函数(可选):根据中断标志,处理外设各种中断事务
GPIO配置步骤
- 使能时钟:stm32f1xx_hal_rcc.h中__HAL_RCC_GPIOx_CLK_ENABLE()
- GPIO初始化:stm32f1xx_hal_gpio.h中HAL_GPIO_Init()
- 写/翻转GPIO:stm32f1xx_hal_gpio.h中HAL_GPIO_WritePin()和HAL_GPIO_TogglePin()
- 读取GPIO:stm32f1xx_hal_gpio.h中GPIO_PinState HAL_GPIO_ReadPin()