STM32【H7】理论——GPIO

STM32【H7】理论——GPIO文章目录1.GPIO1.1GPIIO功能模式分析1.2推挽输出1.3开漏输出1.4复用推挽和开漏1.5四种输入模式1.6GPIO的拉电流负载和灌电流负载能力1.7IO补偿单元(高速IO)1.8GPIIO兼容CMOS和TTL电平使用芯片:STM32H743XIH6.开发板:安富莱V71.GPIO对于不使用的引脚,推荐设置为模拟模式,悬空即可。GPIO的速度等级高的时候,最好使能IO补偿单元。GPIO还涉及到一个注入电流的问题,详见:http:/

使用芯片:STM32H743XIH6 .
开发板:安富莱V7

1. GPIO


注意事项:

  1. 对于不使用的引脚,推荐设置为模拟模式,悬空即可。
  2. GPIO 的速度等级高的时候,最好使能 IO 补偿单元。
  3. GPIO 还涉及到一个注入电流的问题,详见:http://www.armbbs.cn/forum.php?mod=viewthread&tid=87675 .
  4. STM32H7的GPIO控制三极管驱动各种负载的安全措施和注意事项

1.1 GPIIO 功能模式分析

STM32H7 的 GPIO 特性如下:

  1. 输出状态:开漏/推挽 + 上拉/下拉电阻。
  2. 通过输出数据寄存器(GPIOx_ODR)或者外设(GPIO 设置为复用模式时)输出数据。
  3. GPIO 速度等级设置。
  4. 输入状态:浮空,上拉/下拉,模拟。
  5. 通过输入数据寄存器(GPIOx_IDR)或者外设(GPIO 设置为复用模式)输入数据。
  6. 通过寄存器 GPIOx_BSRR 实现对寄存器 GPIOx_ODR 的位操作。
  7. 通过配置寄存器 GPIOx_LCKR 的锁机制,实现冻结 IO 口配置。
  8. 每两个时钟周期就可以翻转一次 IO。
  9. 高度灵活的引脚复用功能,允许 IO 引脚既可以做 GPIO 也可以做功能复用。

STM32H7 的 GPIO 端口可以配置为如下的 8 种模式:
11. 输入浮空
12. 输入上拉
13. 输入下拉
14. 模拟功能
15. 具有上拉或下拉功能的开漏输出
16. 具有上拉或下拉功能的推挽输出
17. 具有上拉或下拉功能的复用功能推挽
18. 具有上拉或下拉功能的复用功能开漏

另外,由于上拉和下拉是可选配置,对应的 HAL 库配置使用下面 6 种就可以表示:
19. GPIO_MODE_INPUT 输入模式
20. GPIO_MODE_OUTPUT_PP 推挽输出
21. GPIO_MODE_OUTPUT_OD 开漏输出
22. GPIO_MODE_AF_PP 复用推挽
23. GPIO_MODE_AF_OD 复用开漏
24. GPIO_MODE_ANALOG 模拟模式

1.2 四种输出模式

1.2.1 推挽输出

推挽电路是两个参数相同的三极管或 MOSFET,以推挽方式存在于电路中。 电路工作时,两只对称的开关管每次只有一个导通,导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级提高电路的负载能力。 相对于开漏输出模式,推挽输出最大优势是输出高电平时,上升时间快,电压驱动能力强。
在这里插入图片描述

1.2.2 开漏输出

开漏端相当于 MOS 管的漏极(三极管的集电极),要得到高电平状态必须外接上拉电阻才行,因此输出高电平的驱动能力完全由外接上拉电阻决定,但是其输出低电平的驱动能力很强。
在这里插入图片描述
开漏形式的电路有以下几个特点:

  1. 输出高电平时利用外部电路的驱动能力,减少 IC 内部的驱动。
  2. 开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平。如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。上拉电阻的阻值决定了逻辑电平转换的速度。阻值越大,速度越低,功耗越小。
  3. 开漏输出提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。
  4. 可以将多个开漏输出连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系,即“线与”。可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑 0,相当于接地,与之并联的回路“相当于被一根导线短路”,所以外电路逻辑电平便为 0,只有都为高电平时,与的结果才为逻辑 1 .

1.2.3 复用推挽和开漏

复用指的是 GPIO 切换到 CPU 内部设备(比如 SPI,I2C,UART 等电路),也就是 GPIO 不是作为普通IO 使用,是由内部设备直接驱动。推挽和开漏的特征同上。

1.3 四种输入模式

在这里插入图片描述
在这里插入图片描述

  1. 浮空输入:CPU 内部的上拉电阻、下拉电阻均断开的输入模式。
  2. 下拉输入:CPU 内部的下拉电阻使能、上拉电阻断开的输入模式。
  3. 上拉输入:CPU 内部的上拉电阻使能、下拉电阻断开的输入模式。
  4. 模拟输入模式: GPIO 引脚直接连接内部 ADC。

任何不使用的引脚都推荐设置为模拟输入模式:主要从功耗和防干扰考虑。

  1. 所有用作带上拉电阻输入的 I/O 都会在引脚外部保持为低时产生电流消耗。此电流消耗的值可通过使用的静态特性中给出的上拉 / 下拉电阻值简单算出。
  2. 对于输出引脚,还必须考虑任何外部下拉电阻或外部负载以估计电流消耗。
  3. 若外部施加了中间电平,则额外的 I/O 电流消耗是因为配置为输入的 I/O。此电流消耗是由用于区分输入值的输入施密特触发器电路导致。除非应用需要此特定配置,否则可通过将这些 I/O 配置为模拟模式以避免此供电电流消耗。 ADC 输入引脚应配置为模拟输入就是这种情况。
  4. 任何浮空的输入引脚都可能由于外部电磁噪声,成为中间电平或意外切换。为防止浮空引脚相关的电流消耗,它们必须配置为模拟模式,或内部强制为确定的数字值。这可通过使用上拉 / 下拉电阻或将引脚配置为输出模式做到。

1.4 GPIO 的拉电流负载和灌电流负载能力

  • 拉电流负载:一种负载电流从驱动门流向外电路,称为拉电流负载。比如使用 STM32H7 的 GPIO 直接驱动 LED (MCU输出高电平)就是拉电流形式。
  • 灌电流负载:负载电流从外电路流入驱动门,称为灌电流负载。如下面这种形式的 LED 驱动电路。
    在这里插入图片描述

  • STM32H7 的 IO 驱动能力(截图来自 STM32H7 数据手册):通过截图可知STM32H7 总的拉电流和灌电流不可超过 140mA,单个引脚灌电流最大不可超过 20mA .
    在这里插入图片描述

1.5 IO 补偿单元(高速IO)

IO 补偿单元用于控制 I/O 通信压摆率(tfall / trise)以此来降低 I/O 噪声。当前 STM32H7 的速度等级可以配置为以下四种:

/** @defgroup GPIO_speed_define GPIO speed define * @brief GPIO Output Maximum frequency * @{ */ 
#define GPIO_SPEED_FREQ_LOW ((uint32_t)0x00000000U) /*!< Low speed */
#define GPIO_SPEED_FREQ_MEDIUM ((uint32_t)0x00000001U) /*!< Medium speed */
#define GPIO_SPEED_FREQ_HIGH ((uint32_t)0x00000002U) /*!< Fast speed */
#define GPIO_SPEED_FREQ_VERY_HIGH ((uint32_t)0x00000003U) /*!< High speed */

使用后两种速度等级的话,最好使能 IO 补偿单元。另外不同速度等级下,IO 补偿使能与否对 IO 最大速度的影响可以看此贴:http://www.armbbs.cn/forum.php?mod=viewthread&tid=87677

1.6 GPIIO 兼容 CMOS 和 TTL 电平

详见http://www.armbbs.cn/forum.php?mod=viewthread&tid=87676

下面是CMOS、TTL电平图:

在这里插入图片描述

1.* 【实验】无源蜂鸣器控制

资料来源:安富莱STM32H7 开发板

1.*.1 蜂鸣器简介

蜂鸣器主要有电磁式电压式两种,而且都有无源蜂鸣器和有源蜂鸣器两类。这里使用的是电磁式无源蜂鸣器。

  • 有源和无源的区别:有源蜂鸣器内部自带振荡器,给个电压就能发声,但频率是固定的,只能发出一种声音,而无源蜂鸣器频率可控,给个方波才可以发声,并且根据不同频率发出不同的声音效果。

蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器,而且都含有源和无源

1.*.2 电路设计

在这里插入图片描述

1.*.3 程序设计

1.*.3.1 bsp_beep.h

  • 控制蜂鸣器的结构体变量
typedef struct _BEEP_T
{ 
   
uint8_t ucEnalbe; // 用于使能或者禁止蜂鸣器
uint8_t ucState; // 状态变量,用于蜂鸣器鸣叫和停止的区分
uint16_t usBeepTime; // 鸣叫时间,单位 10ms
uint16_t usStopTime; // 停止鸣叫时间,单位 10ms
uint16_t usCycle; // 鸣叫和停止的循环次数
uint16_t usCount; // 用于鸣叫和停止时的计数
uint16_t usCycleCount; // 用于循环次数计数
uint8_t ucMute; // 用于静音
}BEEP_T;

1.*.3.2 bsp_beep.c

  • 宏定义与全局变量
include "bsp.h"

//#define BEEP_HAVE_POWER /* 定义此行表示有源蜂鸣器,直接通过GPIO驱动, 无需PWM , 不定义即使用无源蜂鸣器*/ 

#ifdef BEEP_HAVE_POWER /* 有源蜂鸣器 */

	/* PA8 */
	#define GPIO_RCC_BEEP RCC_AHB1Periph_GPIOA
	#define GPIO_PORT_BEEP GPIOA
	#define GPIO_PIN_BEEP GPIO_PIN_8

	#define BEEP_ENABLE() GPIO_PORT_BEEP->BSRRL = GPIO_PIN_BEEP /* 使能蜂鸣器鸣叫 */
	#define BEEP_DISABLE() GPIO_PORT_BEEP->BSRRH = GPIO_PIN_BEEP /* 禁止蜂鸣器鸣叫 */
#else /* 无源蜂鸣器 */
	/* PA0 ---> TIM5_CH1 */

	/* 1500表示频率1.5KHz,5000表示50.00%的占空比 */
	#define BEEP_ENABLE() bsp_SetTIMOutPWM(GPIOA, GPIO_PIN_0, TIM5, 1, 1500, 5000);

	/* 禁止蜂鸣器鸣叫 */
	#define BEEP_DISABLE() bsp_SetTIMOutPWM(GPIOA, GPIO_PIN_0, TIM5, 1, 1500, 0);
#endif

BEEP_T g_tBeep;		/* 定义蜂鸣器全局结构体变量 */
  • 初始化蜂鸣器硬件
/* ********************************************************************************************************* * 函 数 名: BEEP_InitHard * 功能说明: 初始化蜂鸣器硬件 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_InitHard(void)
{ 
   
#ifdef BEEP_HAVE_POWER /* 有源蜂鸣器 */
	GPIO_InitTypeDef GPIO_InitStructure; 

	/* 打开GPIOF的时钟 */
	RCC_AHB1PeriphClockCmd(GPIO_RCC_BEEP, ENABLE);

	BEEP_DISABLE();

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;		/* 设为输出口 */
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;		/* 设为推挽模式 */
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;	/* 上下拉电阻不使能 */
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	/* IO口最大速度 */

	GPIO_InitStructure.GPIO_Pin = GPIO_PIN_BEEP;
	GPIO_Init(GPIO_PORT_BEEP, &GPIO_InitStructure); // 初始化GPIO
#endif
	
	g_tBeep.ucMute = 0;	/* 关闭静音 */
}
  • 启动蜂鸣音
/* ********************************************************************************************************* * 函 数 名: BEEP_Start * 功能说明: 启动蜂鸣音。 * 形 参: _usBeepTime : 蜂鸣时间,单位10ms; 0 表示不鸣叫 * _usStopTime : 停止时间,单位10ms; 0 表示持续鸣叫 * _usCycle : 鸣叫次数, 0 表示持续鸣叫 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_Start(uint16_t _usBeepTime, uint16_t _usStopTime, uint16_t _usCycle)
{ 
   
	if (_usBeepTime == 0 || g_tBeep.ucMute == 1)
	{ 
   
		return;
	}

	g_tBeep.usBeepTime = _usBeepTime; // 设置蜂鸣时间,单位 10ms,配置为 0 表示不鸣叫
	g_tBeep.usStopTime = _usStopTime; // 设置蜂鸣时间,单位 10ms,配置为 0 表示不鸣叫
	g_tBeep.usCycle = _usCycle; // 设置鸣叫次数,配置为 0 表示持续鸣叫
	g_tBeep.usCount = 0;
	g_tBeep.usCycleCount = 0;
	g_tBeep.ucState = 0;
	g_tBeep.ucEnalbe = 1;	/* 设置完全局参数后再使能发声标志 */

	BEEP_ENABLE();			/* 开始发声 */
}



/* ********************************************************************************************************* * 函 数 名: BEEP_KeyTone * 功能说明: 发送按键音 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_KeyTone(void)
{ 
   
	BEEP_Start(5, 1, 1);	/* 鸣叫50ms,停10ms, 1次 */
}




/* ********************************************************************************************************* * 函 数 名: BEEP_Pro * 功能说明: 此函数需每隔10ms调用1次该函数,用于控制蜂鸣器发声。该函数在 bsp_timer.c 中被调用。 * 该函数是蜂鸣器的主处理函数,用于实现鸣叫时间、停止鸣叫时间和循环次数的处理。 * 形 参: 无 * 返 回 值: 无 * 特别说明: * 1. 如果是裸机使用,将此函数放在 bsp.c 文件的 bsp_RunPer10ms 函数里面即可,这个函数是由滴答定时器调用的,前提是定时器的初始化函数 bsp_InitTimer 一定要调用。 2. 如果是 RTOS 使用,需要开启一个 10ms 为周期的任务调用函数 BEEP_Pro . ********************************************************************************************************* */
void BEEP_Pro(void)
{ 
   
	if ((g_tBeep.ucEnalbe == 0) || (g_tBeep.usStopTime == 0) || (g_tBeep.ucMute == 1))
	{ 
   
		return;
	}

	if (g_tBeep.ucState == 0)
	{ 
   
		if (g_tBeep.usStopTime > 0)	/* 间断发声 */
		{ 
   
			if (++g_tBeep.usCount >= g_tBeep.usBeepTime)
			{ 
   
				BEEP_DISABLE();		/* 停止发声 */
				g_tBeep.usCount = 0;
				g_tBeep.ucState = 1;
			}
		}
		else
		{ 
   
			;	/* 不做任何处理,连续发声 */
		}
	}
	else if (g_tBeep.ucState == 1)
	{ 
   
		if (++g_tBeep.usCount >= g_tBeep.usStopTime)
		{ 
   
			/* 连续发声时,直到调用stop停止为止 */
			if (g_tBeep.usCycle > 0)
			{ 
   
				if (++g_tBeep.usCycleCount >= g_tBeep.usCycle)
				{ 
   
					/* 循环次数到,停止发声 */
					g_tBeep.ucEnalbe = 0;
				}

				if (g_tBeep.ucEnalbe == 0)
				{ 
   
					g_tBeep.usStopTime = 0;
					return;
				}
			}

			g_tBeep.usCount = 0;
			g_tBeep.ucState = 0;

			BEEP_ENABLE();			/* 开始发声 */
		}
	}
}
  • 停止蜂鸣音
/* ********************************************************************************************************* * 函 数 名: BEEP_Stop * 功能说明: 停止蜂鸣音。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_Stop(void)
{ 
   
	g_tBeep.ucEnalbe = 0;

	if ((g_tBeep.usStopTime == 0) || (g_tBeep.usCycle == 0))
	{ 
   
		BEEP_DISABLE();	/* 必须在清控制标志后再停止发声,避免停止后在中断中又开启 */
	}
}
  • 暂停/恢复蜂鸣音
/* ********************************************************************************************************* * 函 数 名: BEEP_Pause * 功能说明: 由于TIM冲突等原因,临时屏蔽蜂鸣音。通过 BEEP_Resume 恢复 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_Pause(void)
{ 
   
	BEEP_Stop();
	
	g_tBeep.ucMute = 1;		/* 静音 */
}

/* ********************************************************************************************************* * 函 数 名: BEEP_Resume * 功能说明: 恢复蜂鸣器正常功能 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void BEEP_Resume(void)
{ 
   
	BEEP_Stop();
	
	g_tBeep.ucMute = 0;		/* 静音 */
}

2. GPIO FIFO


FIFO 是 First Input First Output 的缩写,先入先出队列。下面以按键FIFO 实验进行讲解。

这里以已被定义的 5 个字节的 FIFO 空间进行说明。Write 变量表示写位置,Read 变量表示读位置。初始状态时,Read = Write = 0,每向队列写入一个数据,则Write 加1 ,同理,每从队列中读出一个数据,Read 加1 ,直到Write= Read ,表示没有新的读写时间发生。
在这里插入图片描述
如果 Write!= Read,则认为有新的按键事件。现在通过函数 bsp_GetKey 读取一个按键值进行处理后,Read 变量变为 1,而Write 变量不变。
在这里插入图片描述
继续通过函数 bsp_GetKey 读取 3 个按键值进行处理后,Read 变量变为 4 . 此时 Read = Write = 4 ,表示已经没有新的按键事件需要处理。

另外值得注意的是,如果 FIFO 空间写满了,Write 会自动被重新赋值为 0,即重新从第一个字节空间填数据进去,如果这个地址空间的数据未被及时读取,那么则会被后来的数据覆盖掉。


设计按键 FIFO 主要有三个方面的好处

  1. 可靠地记录每一个按键事件,避免遗漏按键事件。特别是需要实现按键的按下、长按、自动连发、弹起等事件时。
  2. 读取按键的函数可以设计为非阻塞的,不需要等待按键抖动滤波处理完毕。
  3. 按键 FIFO 程序在嘀嗒定时器中定期的执行检测,不需要在主程序中一直做检测,这样可以有效地降低系统资源消耗。

2.1 程序设计

程序运行框图
在这里插入图片描述

2.1.1 bsp_key.h

下面按键事件的定义推荐使用枚举 enum, 不用#define 的原因

  1. 便于新增键值,方便调整顺序。
  2. 使用{ } 将一组相关的定义封装起来便于理解。
  3. 编译器可帮我们避免键值重复。

对于简单的程序,可以借用按键 FIFO 来进行少量的信息传递。对于复杂的应用,推荐使用bsp_msg 专门来做这种任务间的通信。因为bsp_msg 除了传递消息代码外,还可以传递参数结构。

#define KEY_FIFO_SIZE 10 // 定义FIFO 队列大小为10 
typedef struct // 定义FIFO 结构体
{ 
   
	uint8_t Buf[KEY_FIFO_SIZE]; /* 键值缓冲区 */
	uint8_t Read; /* 缓冲区读指针 1 */
	uint8_t Write; /* 缓冲区写指针 */
	uint8_t Read2; /* 缓冲区读指针 2 */
}KEY_FIFO_T;

#define KEY_COUNT 10 /* 按键个数, 8 个独立建 + 2 个组合键 */
// 每个按键对象都分配一个结构体变量,这些结构体变量以数组的形式存在将便于简化程序代码行数。
// 使用函数指针 IsKeyDownFunc 可以将每个按键的检测以及组合键的检测代码进行统一管理。
typedef struct
{ 
   
	/* 下面是一个函数指针,指向判断按键手否按下的函数 */
	uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1 表示按下 */
	uint8_t Count; /* 滤波器计数器 */
	uint16_t LongCount; /* 长按计数器 */
	uint16_t LongTime; /* 按键按下持续时间, 0 表示不检测长按 */
	uint8_t State; /* 按键当前状态(按下还是弹起) */
	uint8_t RepeatSpeed; /* 连续按键周期 */
	uint8_t RepeatCount; /* 连续按键计数器 */
}KEY_T;

// 按键事件
typedef enum
{ 
   
	KEY_NONE = 0, /* 0 表示按键事件 */
	KEY_1_DOWN, /* 1 键按下 */
	KEY_1_UP, /* 1 键弹起 */
	KEY_1_LONG, /* 1 键长按 */
	KEY_2_DOWN, /* 2 键按下 */
	KEY_2_UP, /* 2 键弹起 */
	KEY_2_LONG, /* 2 键长按 */
	KEY_3_DOWN, /* 3 键按下 */
	KEY_3_UP, /* 3 键弹起 */
	KEY_3_LONG, /* 3 键长按 */
	KEY_4_DOWN, /* 4 键按下 */
	KEY_4_UP, /* 4 键弹起 */
	KEY_4_LONG, /* 4 键长按 */
	KEY_5_DOWN, /* 5 键按下 */
	KEY_5_UP, /* 5 键弹起 */
	KEY_5_LONG, /* 5 键长按 */
	KEY_6_DOWN, /* 6 键按下 */
	KEY_6_UP, /* 6 键弹起 */
	KEY_6_LONG, /* 6 键长按 */
	KEY_7_DOWN, /* 7 键按下 */
	KEY_7_UP, /* 7 键弹起 */
	KEY_7_LONG, /* 7 键长按 */
	KEY_8_DOWN, /* 8 键按下 */
	KEY_8_UP, /* 8 键弹起 */
	KEY_8_LONG, /* 8 键长按 */
	/* 组合键 */
	KEY_9_DOWN, /* 9 键按下 */
	KEY_9_UP, /* 9 键弹起 */
	KEY_9_LONG, /* 9 键长按 */
	KEY_10_DOWN, /* 10 键按下 */
	KEY_10_UP, /* 10 键弹起 */
	KEY_10_LONG, /* 10 键长按 */
}KEY_ENUM;

/* 按键滤波时间 50ms, 单位 10ms。 只有连续检测到 50ms 状态不变才认为有效,包括弹起和按下两种事件 即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件 */
#define KEY_FILTER_TIME 5
#define KEY_LONG_TIME 100 /* 单位 10ms, 持续 1 秒,认为长按事件 */

2.1.2 bsp_key.c

static KEY_FIFO_T s_tKey; /* 定义 FIFO 结构体变量 */
static KEY_T s_tBtn[KEY_COUNT];
static KEY_FIFO_T s_tKey; /* 按键 FIFO 变量,结构体 */


/* ********************************************************************************************************* * 函 数 名: bsp_PutKey * 功能说明: 将 1 个键值压入按键 FIFO 缓冲区。可用于模拟一个按键。 * 形 参: _KeyCode : 按键代码 * 返 回 值: 无 * 说明: 当按键事件触发后,即可调用该函数,将键值写入到FIFO 队列 ********************************************************************************************************* */
void bsp_PutKey(uint8_t _KeyCode)
{ 
   
	s_tKey.Buf[s_tKey.Write] = _KeyCode;
	if (++s_tKey.Write >= KEY_FIFO_SIZE)
	{ 
   
		s_tKey.Write = 0;
	} 
}

/* ********************************************************************************************************* * 函 数 名: bsp_GetKey * 功能说明: 从按键 FIFO 缓冲区读取一个键值。 * 形 参: 无 * 返 回 值: 按键代码 * 说明: 读取FIFO 队列中的值 ********************************************************************************************************* */
uint8_t bsp_GetKey(void)
{ 
   
	uint8_t ret;
	if (s_tKey.Read == s_tKey.Write) // 若Read = Write,则认为没有新事件需要处理
	{ 
   
		return KEY_NONE;
	}
	else
	{ 
   
		ret = s_tKey.Buf[s_tKey.Read];
		if (++s_tKey.Read >= KEY_FIFO_SIZE)
		{ 
   
			s_tKey.Read = 0;
		}
			return ret;
	} 
}


/* ********************************************************************************************************* * 函 数 名: bsp_GetKey2 * 功能说明: 从按键 FIFO 缓冲区读取一个键值。独立的读指针。 * 形 参: 无 * 返 回 值: 按键代码 ********************************************************************************************************* */
uint8_t bsp_GetKey2(void)
{ 
   
	uint8_t ret;
	if (s_tKey.Read2 == s_tKey.Write)
	{ 
   
		return KEY_NONE;
	}
	else
	{ 
   
		ret = s_tKey.Buf[s_tKey.Read2];
		if (++s_tKey.Read2 >= KEY_FIFO_SIZE)
		{ 
   
			s_tKey.Read2 = 0; // 按键缓冲区为空,即所有的按键时间已经处理完毕
		}
			return ret;
	} 
}

/* ********************************************************************************************************* * 函 数 名: bsp_InitKey * 功能说明: 初始化按键. 该函数被 bsp_Init() 调用。 * 形 参: 无 * 返 回 值: 无 * 说 明:因为函数指针必须先赋值,才能被作为函数执行。因此在定时扫描按键之前,必须先执行下面初始化函数(即bsp_InitKey)来设置每个按键的函数指针和参数。 ********************************************************************************************************* */
void bsp_InitKey(void)
{ 
   
	bsp_InitKeyVar(); /* 初始化按键变量 */
	bsp_InitKeyHard(); /* 初始化按键硬件 */
}

/* ********************************************************************************************************* * 函 数 名: bsp_InitKeyVar * 功能说明: 初始化按键变量 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
static void bsp_InitKeyVar(void)
{ 
   
	uint8_t i;
	/* 对按键 FIFO 读写指针清零 */
	s_tKey.Read = 0;
	s_tKey.Write = 0;
	s_tKey.Read2 = 0;
	/* 给每个按键结构体成员变量赋一组缺省值 */
	for (i = 0; i < KEY_COUNT; i++)
	{ 
   
		s_tBtn[i].LongTime = KEY_LONG_TIME; /* 长按时间 0 表示不检测长按键事件 */
		s_tBtn[i].Count = KEY_FILTER_TIME / 2; /* 计数器设置为滤波时间的一半 ,没有设置为 0,是为了避免主板上电的瞬间,检测到一个无效按键按下或弹起事件。*/
		s_tBtn[i].State = 0; /* 按键缺省状态,0 为未按下 */
		s_tBtn[i].RepeatSpeed = 0; /* 按键连发的速度,0 表示不支持连发 */
		s_tBtn[i].RepeatCount = 0; /* 连发计数器 */
	}
	/* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */
	/* 摇杆上下左右,支持长按 1 秒后,自动连发 */
	bsp_SetKeyParam(KID_JOY_U, 100, 6);
	bsp_SetKeyParam(KID_JOY_D, 100, 6);
	bsp_SetKeyParam(KID_JOY_L, 100, 6);
	bsp_SetKeyParam(KID_JOY_R, 100, 6);
}
/* ********************************************************************************************************* * 函 数 名: KeyPinActive * 功能说明: 判断按键是否按下 * 形 参: 无 * 返 回 值: 返回值 1 表示按下(导通),0 表示未按下(释放) ********************************************************************************************************* */
static uint8_t KeyPinActive(uint8_t _id)
{ 
   
	uint8_t level;
	if ((s_gpio_list[_id].gpio->IDR & s_gpio_list[_id].pin) == 0)
	{ 
   
		level = 0;
	}
	else
	{ 
   
		level = 1;
	}
	if (level == s_gpio_list[_id].ActiveLevel)
	{ 
   
		return 1;
	}
	else
	{ 
   
		return 0;
	} 
}

/* ********************************************************************************************************* * 函 数 名: IsKeyDownFunc * 功能说明: 判断按键是否按下。单键和组合键区分。单键事件不允许有其他键按下。 * 形 参: 无 * 返 回 值: 返回值 1 表示按下(导通),0 表示未按下(释放) ********************************************************************************************************* */
static uint8_t IsKeyDownFunc(uint8_t _id)
{ 
   
	/* 实体单键 */
	if (_id < HARD_KEY_NUM)
	{ 
   
		uint8_t i;
		uint8_t count = 0;
		uint8_t save = 255;
		/* 判断有几个键按下 */
		for (i = 0; i < HARD_KEY_NUM; i++)
		{ 
   
			if (KeyPinActive(i)) 
			{ 
   
				count++;
				save = i;
			} 
		}
		if (count == 1 && save == _id)
		{ 
   
			return 1; /* 只有 1 个键按下时才有效 */
		}
		return 0;
	}
	
	/* 组合键 K1K2 */
	if (_id == HARD_KEY_NUM + 0)
	{ 
   
		if (KeyPinActive(KID_K1) && KeyPinActive(KID_K2))
		{ 
   
			return 1;
		}
		else
		{ 
   
			return 0;
		} 
	}
	
	/* 组合键 K2K3 */
	if (_id == HARD_KEY_NUM + 1)
	{ 
   
		if (KeyPinActive(KID_K2) && KeyPinActive(KID_K3))
		{ 
   
			return 1;
		}
		else
		{ 
   
			return 0;
		} 
	}
	return 0;
}
/* ********************************************************************************************************* * 函 数 名: bsp_InitKeyHard * 功能说明: 配置按键对应的 GPIO * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
static void bsp_InitKeyHard(void)
{ 
   
	GPIO_InitTypeDef gpio_init;
	uint8_t i;
	/* 第 1 步:打开 GPIO 时钟 */
	ALL_KEY_GPIO_CLK_ENABLE();
	/* 第 2 步:配置所有的按键 GPIO 为浮动输入模式(实际上 CPU 复位后就是输入状态) */
	gpio_init.Mode = GPIO_MODE_INPUT; /* 设置输入 */
	gpio_init.Pull = GPIO_NOPULL; /* 上下拉电阻不使能 */
	gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* GPIO 速度等级 */
	for (i = 0; i < HARD_KEY_NUM; i++)
	{ 
   
		gpio_init.Pin = s_gpio_list[i].pin;
		HAL_GPIO_Init(s_gpio_list[i].gpio, &gpio_init);
	} 
}

void bsp_RunPer10ms(void)
{ 
   
	bsp_KeyScan10ms(); /* 扫描按键 */
}

/* ********************************************************************************************************* * 函 数 名: bsp_KeyScan10ms * 功能说明: 扫描所有按键。非阻塞,被 systick 中断周期性的调用,每10ms 执行一次 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */
void bsp_KeyScan10ms(void)
{ 
   
	uint8_t i;
	for (i = 0; i < KEY_COUNT; i++)
	{ 
   
		bsp_DetectKey(i);
	} 
}



2.1.3 main.c

#include "bsp.h"

int main(void)
{ 
   
	uint8_t ucKeyCode;
	bsp_Init();
	IRD_StartWork(); /* 启动红外解码 */
	while(1)
	{ 
   
		bsp_Idle();
		/* 处理按键事件 */
		ucKeyCode = bsp_GetKey();
		if (ucKeyCode > 0)
		{ 
   
			/* 有按键按下 */
			switch (ucKeyCode)
			{ 
   
				case KEY_DOWN_K1: /* K1 键按下 */
						bsp_LedOn(1); /* 点亮 LED1 */
						break;
				case KEY_UP_K1: /* K1 键弹起 */
						bsp_LedOff(1); /* 熄灭 LED1 */
						break;
				case IR_KEY_POWER: /* 遥控器 POWER 键按下 */
						bsp_LedOn(1); /* 点亮 LED2 */
						break;
				case IR_KEY_MENU: /* 遥控器 MENU 键按下 */
						bsp_LedOff(1); /* 熄灭 LED2 */
						break;
				case MSG_485_RX: /* 通信程序的发来的消息 */
						/* 执行通信程序的指令 */
						break;
				default:
						break;
			} 
		} 
	} 
}

2.2 按键检测采用中断方式或查询方式

结论:推荐使用 GPIO 查询方式。

  • 从裸机的角度分析

    • 中断方式:中断方式可以快速地检测到按键按下,并执行相应的按键程序,但实际应用时由于按键的机械抖动特性,在程序进入中断后必须进行滤波处理才能判定是否有效的按键事件。如果每个按键都是独立的接一个 IO 引脚,则需要给每个 IO 都设置一个中断,程序中过多的中断会影响系统的稳定性。另外中断方式跨平台移植困难。
    • 查询方式:查询方式有一个最大的缺点就是需要程序定期的去执行查询,耗费一定的系统资源。实际上耗费不了多大的系统资源,因为这种查询方式也只是查询按键是否按下,按键事件的执行还是在主程序里面实现。
  • 从 OS 的角度分析

    • 中断方式:在 OS 中要尽可能少用中断方式,因为在 RTOS 中过多的使用中断会影响系统的稳定性和可预见性(抢占式调度的 OS 基本没有可预见性)。只有比较重要的事件处理需要用中断的方式。
    • 查询方式:对于用户按键推荐使用这种查询方式来实现,现在的 OS 基本都带有 CPU 利用率的功能,这个按键 FIFO 占用的还是很小的,基本都在 1%以下。

今天的文章STM32【H7】理论——GPIO分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/28279.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注