STM32F103---矩阵按键

一、业务需求与分析

业务需求:当按键按下的时候,串口调试助手会打印出对应的按键

业务分析:1、原理:矩阵按键通常为低电平有效,那么我们需要根据行和列来确定是哪一个按键

按下了,只有当行和列都是低电平的时候,检测按键的函数(uint16_t KEY_Scanf_Config)才会检测到低电平,从而确定该按键;

2、首先,需要四行输入高电平,四列输出高电平做准备工作,初始化行引脚置为上拉输入(PB5-PD2),列引脚置为复用推挽输出

3、然后,将一列置为输出低电平。例如“1”按键,我将第一列置为低电平输出,因为我初始化了第一行为上拉输入,所以它默认输入高电平,当我“1”按下后,它就会变成低电平输入。行和列都是低电平就能确定是“1”按键被按下

4、准备工作都做好后,我们需要写一个检测按键的函数,一直处于检测状态(while(1)),我们需要将哪个按键按下打印到串口调试助手,就需要该函数返回值打印出来

二、实现步骤

1、查看原理图,找8个连续的GPIO的引脚

2、查看参考手册,查找这些引脚能否使用

        注意!!!在我们做引脚初始化之前,我们第一步要做的是先去参考手册查看哪些引脚已经被使用,如果我们需要去复用这些引脚,那么就需要重映射(先大概知道是哪些引脚被使用)

       

        对比我们需要使用的引脚(原理图)和该表格看出,在复位状态也就是正常状态下,调试接口信号被映射到GPIO端口上,我们所使用的PB3、PB4、PA15是不可用的,只有在JTAG-DP和SW-DP关闭下,我们才能使用,所以我们需要去固件库手册找到重映射的函数将它关闭

3、完整代码

//主函数代码

#include "stm32f10x.h"                  // Device header
#include "USART.h"
#include "stdio.h"
#include "KEY_GROUP.h"
uint8_t flag;
char ptr[20];
 
uint16_t val;
int main()
{		
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
	USART_Config_Init();
	Key_Config();
	printf("进入while循环\n");	
	while(1){		
		val = KEY_Scanf_Config();
		if(val){			
			printf("s %d 被按下\n",val); 
			val = 0;
		}			
	}			
}

 

#include "USART.h"
extern uint8_t flag;

void USART_Config_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	USART_InitTypeDef  USART_InitStruct;
	NVIC_InitTypeDef  NVIC_InitStruct;
	//初始化GPIOA_PIN9|10号引脚,USART1,AFIO复用功能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1|RCC_APB2Periph_AFIO, ENABLE);

	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9 ;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;

	//初始化GPIO引脚
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;	//浮空输入
	GPIO_InitStruct.GPIO_Pin  = GPIO_Pin_10;	
	GPIO_Init(GPIOA,&GPIO_InitStruct);

	//初始化串口
	USART_InitStruct.USART_BaudRate = 115200;									//波特率
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
	USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; 				    //发送接收模式
	USART_InitStruct.USART_StopBits = USART_StopBits_1;							//停止位
	USART_InitStruct.USART_Parity    = USART_Parity_No;							//奇偶校验
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;					//数据位
	USART_Init(USART1, &USART_InitStruct);
	
	//配置接受中断源:USART_IT_RXNE
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

	//配置中断优先级NVIC
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;	
	NVIC_Init(&NVIC_InitStruct);
	
	USART_ClearFlag(USART1,USART_FLAG_TC|USART_FLAG_TXE);
	//打开串口1
	USART_Cmd(USART1, ENABLE );	
}

void Send_Msg(uint8_t data)
{
	USART_SendData(USART1,data);							//发送信息
	while(SET != USART_GetFlagStatus(USART1,USART_FLAG_TC));//发送标志位获取,知道发送全
}

void Rev_Msg(void)
{	
	if(SET==USART_GetFlagStatus(USART1,USART_FLAG_RXNE)){	//接受数据
		USART_ClearFlag(USART1,USART_FLAG_RXNE);			//清除标志位置
		USART_SendData(USART1,USART_ReceiveData(USART1));	//发送信息到串口助手
	}
}

extern uint8_t flag;	//判断是否接受完全的标志位
extern char ptr[10];  	//接受数据数组
volatile uint8_t recv_data;
volatile uint32_t strIndex = 0;

void USART1_IRQHandler(void)
{	
	//中断服务函数
	//总线空闲标志位
	if(USART_GetITStatus(USART1, USART_IT_IDLE)!=RESET){
	if(SET == USART_GetFlagStatus(USART1,USART_IT_RXNE))
		{
			//清除标志位
			USART_ClearITPendingBit(USART1,USART_IT_RXNE);
			recv_data = USART_ReceiveData(USART1);		//把接受到的数据放到变量中			
			if(recv_data =='\n'){
				ptr[strIndex] = '\0'; 
				flag = 1; 
				strIndex = 0;			
			}else {
				ptr[strIndex] = recv_data;
				strIndex++;			
			}			
		}		
	}					
}

int fputc(int ch,FILE *f)
{
	USART1->DR=(uint8_t)ch;   		//将其强转为char类型数据放入数据寄存器中	
	while(0==(USART1->SR&(1<<6)));  //循环发送
	USART1->SR &=~(1<<6);
	return ch;
	
}
#ifndef __USART_H
#define __USART_H

#include "stm32f10x.h"                  // Device header

void Send_Msg(uint8_t data);
void Rev_Msg(void);
void USART_Config_Init(void);
#include "stdio.h"
#include "string.h"
int fputc(int ch,FILE*f);
#endif

 

//key_group.c文件

#include "KEY_GROUP.h"
#define ROWS 4	//行
#define COLS 4	//列

//这里一一对应好每个引脚
GPIO_TypeDef * row_ports[ROWS] = {GPIOB, GPIOB, GPIOB, GPIOD};	
uint16_t row_pins[ROWS] = {GPIO_Pin_5, GPIO_Pin_4, GPIO_Pin_3, GPIO_Pin_2};
GPIO_TypeDef * col_ports[COLS] = {GPIOC, GPIOC, GPIOC, GPIOA};
uint16_t col_pins[COLS] = {GPIO_Pin_12, GPIO_Pin_11, GPIO_Pin_10, GPIO_Pin_15};

void Key_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD, ENABLE);
	GPIO_PinRemapConfig( GPIO_Remap_SWJ_Disable,ENABLE);	//失能SWJ引脚,PA15,PB3,PB4
	// 配置行引脚为输入模式,并启用内部上拉电阻
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_IPU;		//将每一行设置为上拉输入模式
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;	//频率为2MHz
    for (int i = 0; i < ROWS; i++) {				//循环初始化数组
        GPIO_InitStruct.GPIO_Pin = row_pins[i];
        GPIO_Init(row_ports[i], &GPIO_InitStruct);
    }
    // 配置列引脚为输出模式,并设置初始输出值为高电平
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_Out_PP;	//配置为推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
    for (int i = 0; i < COLS; i++) {
        GPIO_InitStruct.GPIO_Pin = col_pins[i];
        GPIO_Init(col_ports[i], &GPIO_InitStruct);
		GPIO_SetBits(col_ports[i],col_pins[i]);
    }	
	
}

uint16_t KEY_Scanf_Config(void)
{
	uint16_t val = 0;
	 for (int i = 0; i < COLS; ++i) {
		 
        // 设置当前列为低电平
        GPIO_ResetBits(col_ports[i], col_pins[i]);		 
        // 延时一段时间以稳定电平
		Delay_ms(30);
        // 检测列引脚的电平状态,并根据不同的状态设置按键状态
        for (int j = 0; j <ROWS; ++j) {
			  
            if (GPIO_ReadInputDataBit(row_ports[j], row_pins[j])) {
				
            } else if(!GPIO_ReadInputDataBit(row_ports[j], row_pins[j])){
					val = (j* ROWS) + i + 1; // 计算按键值	
			}            
		}
		//恢复高电平,一行为低电平其余行为高电平
		GPIO_SetBits(col_ports[i], col_pins[i]);	
	}
		
	if(val != 0){
		printf("%d",val);	//打印获得值
		return val;			//将只返回出去
	}	
		return 0;			//没有按下按键时候返回0,
}

void KEY_Work(void)
{
		uint16_t val;
		val = KEY_Scanf_Config();
		if(val){				
			printf("s %d 被按下\n",val);
			val = 0;
		}
}
#ifndef __KEY_GROUP_H
#define __KEY_GROUP_H
#include "stm32f10x.h" 
#include "Delay.h"
#include "stdio.h"
#include "USART.h"

uint16_t KEY_Scanf_Config(void);
void Key_Config(void);
void KEY_Work(void);

#endif
//延时函数

#include "Delay.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);
	}
} 
#ifndef __DELAY_H
#define __DELAY_H

#include "stm32f10x.h"
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);

#endif

4、测试结果

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值