STM32学习记录2——GPIO

GPIO

什么是GPIO

General Purpose Input Output (通用输入/输出)简称为GPIO。
GPIO的英文全称General-Purpose Input /Output Ports,中文意思是通用I/O端口,在嵌入式系统中,经常需要控制许多结构简单的外部设备或者电路,这些设备有的需要通过CPU控制,有的需要为CPU提供输入信号。并且,许多设备或电路只要求有开/关两种状态就够了,比如LED的亮与灭。对这些设备的控制,使用传统的串口或者并口就显得比较复杂,所以,在嵌入式微处理器上通常提供了一种“通用可编程I/O端口”,也就是GPIO。一个GPIO端口需要多个寄存器,一个做控制用的“通用IO端口控制寄存器”,还有一个是存放数据的“通用I/O端口数据寄存器”。数据寄存器的每一位是和GPIO的硬件引脚对应的,而数据的传递方向是通过控制寄存器设置的,通过控制寄存器可以设置每一位引脚的数据流向。

IO资源

STM32F10x系列最多有7个16位并行I/O口:
PA、PB、PC、PD、PE、PF、PG
都是复用引脚,最少有2种功能,最多有6种功能
本人使用的芯片型号为STM32F103RCT6,如右图所示此芯片包含PA、PB、PC、PD共4个并行GPIO。
在这里插入图片描述

IO口的工作模式

STM32的输入输出管脚有以下8种可能的配置:(4输入+2输出+2复用输出)

  1. 浮空输入_IN_FLOATING
  2. 带上拉输入_IPU
  3. 带下拉输入_IPD
  4. 模拟输入_AIN
  5. 开漏输出_OUT_OD
  6. 推挽输出_OUT_PP
  7. 复用功能的推挽输出_AF_PP
  8. 复用功能的开漏输出_AF_OD

详细介绍:

  1. 浮空输入_IN_FLOATING
    浮空输入一般多用于外部按键输入,在浮空输入的状态下,IO口的电平状态是不确定的,完全由外部输入决定;如果在该引脚悬空的,读取端口的电平状态是不确定的。
    在这里插入图片描述

  2. 带上拉输入_IPU 内部有上电阻

  3. 带下拉输入_IPD 内部有下拉电阻
    在这里插入图片描述

  4. 模拟输入_AIN 应用ADC模拟输入,或者低功耗下省电
    在这里插入图片描述

  5. 开漏输出_OUT_OD
    输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)。
    开漏形式的电路有以下几个特点:
    i、利用外部电路的驱动能力,减少IC内部的驱动。当IC内部MOSFET导通时,驱动电流是从外部的VCC流经上拉电阻、MOSFET到GND。IC内部仅需很小的栅极驱动电流。
    ii、一般来说,开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。比如加上上拉电阻就可以提供TTL/CMOS电平输出等。(上拉电阻的阻值决定了逻辑电平转换的速度。阻值越大,速度越低功耗越小,所以负载电阻的选择要兼顾功耗和速度。)
    iii、开漏输出提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。
    vi、可以将多个开漏输出连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系,即“线与”。可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑0,相当于接地,与之并联的回路“相当于被一根导线短路”,所以外电路逻辑电平便为0,只有都为高电平时,与的结果才为逻辑1。
    在这里插入图片描述

  6. 推挽输出_OUT_PP
    可以输出高、低电平,连接数字器件;推挽结构一般是指两个三极管分别受两个互补信号的控制,总是在一个三极管导通的时候另一个截止。高低电平由IC的电源决定。
    在这里插入图片描述

  7. 复用功能的推挽输出_AF_PP
    可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用)
    片内外设功能(I2C的SCL,SDA)
    在这里插入图片描述

  8. 复用功能的开漏输出_AF_OD 可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用)
    片内外设功能(TX1,MOSI,MISO.SCK.SS)
    在这里插入图片描述

函数使用

GPIO初始化函数MX_GPIO_Init()
定义GPIO结构体:

GPIO_InitTypeDef GPIO_InitStruct = {0};

时钟使能:

  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();

设置输出引脚:

  GPIO_InitStruct.Pin = 
  //需要初始化的引脚号GPIO_PIN_0;
  GPIO_InitStruct.Mode = 
  /*输出模式:
  推挽输出:GPIO_MODE_OUTPUT_PP;
  开漏输出:GPIO_MODE_OUTPUT_OD;
  复用推挽:GPIO_MODE_AF_PP;
  复用开漏:GPIO_MODE_AF_OD;
  */
  GPIO_InitStruct.Pull =
  /*上下拉电阻配置:
  无电阻:GPIO_NOPULL;
  配置上拉电阻:GPIO_PULLUP;
  配置下拉电阻:GPIO_PULLDOWN;
  */
  GPIO_InitStruct.Speed = 
  /*最大输出速度设置
  低速:GPIO_SPEED_FREQ_LOW;
  中速:GPIO_SPEED_FREQ_MEDIUM;
  高速:GPIO_SPEED_FREQ_HIGH;
  */
  
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);//将结构体写入端口组:GPIO(A-D)

设置输入引脚:

  GPIO_InitStruct.Pin = 
    //需要初始化的引脚号GPIO_PIN_0;
  GPIO_InitStruct.Mode = 
  //将引脚设置为输入模式:GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = 
  /*上下拉电阻配置:
  无电阻:GPIO_NOPULL;
  配置上拉电阻:GPIO_PULLUP;
  配置下拉电阻:GPIO_PULLDOWN;
  */
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);//将结构体写入端口组:GPIO(A-D)

设置输出电平函数:

HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin, GPIO_PinStatePinState);

电平翻转函数:

HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);

电平读取函数:

HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);

流水灯:
小灯对应的引脚号

struct GPIO_smg//定义引脚结构体
{
	GPIO_TypeDef * port;//端口组
	uint16_t pin;//引脚编号
};
struct GPIO_smg LED[8]=
{
	{GPIOA,GPIO_PIN_4},
	{GPIOA,GPIO_PIN_3},
	{GPIOA,GPIO_PIN_2},
	{GPIOA,GPIO_PIN_1},
	{GPIOA,GPIO_PIN_0},
	{GPIOC,GPIO_PIN_3},
	{GPIOC,GPIO_PIN_2},
	{GPIOC,GPIO_PIN_1},
};
void water(void)//流水灯
{
	int i;
		for(i=0;i<8;i++)
		{
			HAL_GPIO_WritePin(LED[i].port,LED[i].pin,GPIO_PIN_RESET);//依次点亮流水灯
			if(i!=0)
			{
				HAL_GPIO_WritePin(LED[i-1].port,LED[i-1].pin,GPIO_PIN_SET);//熄灭上一个流水灯,使小灯单个点亮流动
			}
		}
				for(i=7;i>=0;i--)//反向流动流水灯
		{
			HAL_GPIO_WritePin(LED[i].port,LED[i].pin,GPIO_PIN_RESET);
			if(i!=7)
			{
				HAL_GPIO_WritePin(LED[i+1].port,LED[i+1].pin,GPIO_PIN_SET);
			}
		}
}
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  while (1)
  {
	water();
  }
}

数码管动态显示:
数码管对应的引脚号

struct GPIO_smg//定义引脚结构体
{
	GPIO_TypeDef * port;
	uint16_t pin;
};

struct GPIO_smg LED_TUBE_W[8]=//定义段码引脚
{
	{GPIOA,GPIO_PIN_15},
	{GPIOA,GPIO_PIN_11},
	{GPIOC,GPIO_PIN_9},
	{GPIOC,GPIO_PIN_7},
	{GPIOC,GPIO_PIN_8},
	{GPIOA,GPIO_PIN_12},
	{GPIOA,GPIO_PIN_8},
	{GPIOC,GPIO_PIN_6},
};

struct GPIO_smg LED_TUBE_X[4]=//定义位选引脚
{
	{GPIOD,GPIO_PIN_2},
	{GPIOB,GPIO_PIN_4},
	{GPIOB,GPIO_PIN_6},
	{GPIOB,GPIO_PIN_7},
};

uint8_t xs[]=//数字的段码状态
{
	0xc0,//0
	0xf9,//1
	0xa4,//2
	0xb0,//3
	0x99,//4
	0x92,//5
	0x82,//6
	0xf8,//7
	0x80,//8
	0x90,//9
};

int y=0;//计数变量

void display(uint8_t num,uint8_t ww)//单个数字显示,num:需要显示的数字,ww:显示该数字的数码管
{
	int a;
		
	for(a=0;a<8;a++)//遍历段码引脚,并控制段码引脚使能
	{
		HAL_GPIO_WritePin(LED_TUBE_W[a].port,LED_TUBE_W[a].pin,(GPIO_PinState)(xs[num]&(0x01<<a)));
	}
	for(a=0;a<4;a++)//遍历位选,并控制位选引脚使能
	{
		HAL_GPIO_WritePin(LED_TUBE_X[a].port,LED_TUBE_X[a].pin,(GPIO_PinState)(ww!=a));
	}	
}
void displayall(uint16_t num)//整体数字显示
{
	display(num%10,0);
	HAL_Delay(4);
	display(num/10%10,1);
	HAL_Delay(4);
	display(num/100%10,2);
	HAL_Delay(4);
	display(num/1000%10,3);
	HAL_Delay(4);
}
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  while (1)
  {
	displayall(y);//将变量y的值进行显示
  }
}

按键读取:

在这里插入图片描述

struct GPIO_smg//定义引脚结构体
{
	GPIO_TypeDef * port;
	uint16_t pin;
};

struct GPIO_smg cols[4]={//按键列引脚定义
	{GPIOB,	GPIO_PIN_0},
	{GPIOB,	GPIO_PIN_1},
	{GPIOB,	GPIO_PIN_13},
	{GPIOB,	GPIO_PIN_12},
};

struct GPIO_smg rows[4]={//按键行引脚定义
	{GPIOA,	GPIO_PIN_6},
	{GPIOA,	GPIO_PIN_7},
	{GPIOC,	GPIO_PIN_4},
	{GPIOC,	GPIO_PIN_5},
};
uint8_t ScanKey()//单个按键读取(已将第一列置低,第一行第二行为上拉读取)
{	
	if(HAL_GPIO_ReadPin(rows[0].port,rows[0].pin)==GPIO_PIN_RESET)//读取第一行状态,抬起时进行操作
	{
		HAL_Delay(10);//按键防抖
		if(HAL_GPIO_ReadPin(rows[0].port,rows[0].pin)==GPIO_PIN_RESET)
		{
			while(HAL_GPIO_ReadPin(rows[0].port,rows[0].pin) == GPIO_PIN_RESET);
			HAL_GPIO_TogglePin(LED[0].port,LED[0].pin);	//转换小灯状态			
		}
	}
	
	if(HAL_GPIO_ReadPin(rows[1].port,rows[1].pin)==GPIO_PIN_RESET)//读取第二行状态,按下时进行操作
	{
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(rows[1].port,rows[1].pin)==GPIO_PIN_RESET)
		{
			HAL_GPIO_TogglePin(LED[1].port,LED[1].pin)	
			while(HAL_GPIO_ReadPin(rows[1].port,rows[1].pin) == GPIO_PIN_RESET);				
		}
	}
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  while (1)
  {
	displayall(ScanKey());//将按键代表的数值进行显示,显示函数见上一个程序
  }
}	

按键扫描:

struct GPIO_smg//定义引脚结构体
{
	GPIO_TypeDef * port;
	uint16_t pin;
};

struct GPIO_smg cols[4]={//按键列引脚定义
	{GPIOB,	GPIO_PIN_0},
	{GPIOB,	GPIO_PIN_1},
	{GPIOB,	GPIO_PIN_13},
	{GPIOB,	GPIO_PIN_12},
};

struct GPIO_smg rows[4]={//按键行引脚定义
	{GPIOA,	GPIO_PIN_6},
	{GPIOA,	GPIO_PIN_7},
	{GPIOC,	GPIO_PIN_4},
	{GPIOC,	GPIO_PIN_5},
};

uint8_t ScanKey()
{
	uint8_t i,col=0,row=0;
	GPIO_InitTypeDef GPIO_InitStruct;
	for(i=0;i<4;i++)//行线设置为输出并输出低电平
	{
		HAL_GPIO_WritePin(rows[i].port,rows[i].pin,GPIO_PIN_RESET);
		GPIO_InitStruct.Pin = rows[i].pin;
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
		HAL_GPIO_Init(rows[i].port, &GPIO_InitStruct);
	}
	for(i=0;i<4;i++)//列线设置为输入并读取状态
	{
		GPIO_InitStruct.Pin = cols[i].pin;
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
		HAL_GPIO_Init(cols[i].port, &GPIO_InitStruct);
		if(HAL_GPIO_ReadPin(cols[i].port,cols[i].pin)==GPIO_PIN_RESET)
		{
			col += (i+1);
		}
	}
	for(i=0;i<4;i++)//列线设置为输出并输出低电平
	{
		HAL_GPIO_WritePin(cols[i].port,cols[i].pin,GPIO_PIN_RESET);
		GPIO_InitStruct.Pin = cols[i].pin;
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
		HAL_GPIO_Init(cols[i].port, &GPIO_InitStruct);
	}
	for(i=0;i<4;i++)//行线设置为输入并读取状态
	{
		GPIO_InitStruct.Pin = rows[i].pin;
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
		HAL_GPIO_Init(rows[i].port, &GPIO_InitStruct);
		if(HAL_GPIO_ReadPin(rows[i].port,rows[i].pin)==GPIO_PIN_RESET)
		{
			row += (i+1);
		}
	}
	if((row==0)&&(col==0))return 0;
	return ((col-1)*4)+row;
}
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  while (1)
  {
	displayall(ScanKey());//将按键代表的数值进行显示,显示函数见上一个程序
  }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值