注:此单片机型号为 STC15F2K60S2.
矩阵按键
原理
首先,把跳线帽连接1、2,使其进入矩阵按键模式。
此时,即为矩阵按键模式。
按键原理 :
由图可看出,行接口有:P30、P31、P32、P33.
——————列接口有:P44、P42、P35、P34.
以S7举例,假设要检测S7被按下,则首先要设置其左端为低电平,即设置P44为低电平并且其余端口保持高电平,再检测其右端即P30是否为低电平即可。
(原理与独立按键相似,只是要自己设置一段为低电平。)
代码
来源:蓝桥杯官方。
unsigned char Key_Read(void)
{
unsigned int Key_New;
unsigned char Key_Val;
P44 = 0; P42 = 1; P35 = 1; P34 = 1; // 第1列
Key_New = P3;
P44 = 1; P42 = 0; // 第2列
Key_New = (Key_New<<4) | (P3&0x0f);/*这里需要注意,先将P3与一个0x0f,将前面的清零,
然后再将结果与前值左移四位的数相或,或是有1则为1,其他都不变,
所以前四项保持不变,后四项看是否按下而改变。*/
P42 = 1; P35 = 0; // 第3列
Key_New = (Key_New<<4) | (P3&0x0f);
P35 = 1; P34 = 0; // 第4列
Key_New = (Key_New<<4) | (P3&0x0f);
switch(~Key_New) //因为每次只按下一个按键,就算你以为同时按下,实际上仍为依次按下。
{
case 0x8000: Key_Val = 4; break; // S4 0111 1111 1111 1111
case 0x4000: Key_Val = 5; break; // S5 1011 1111 1111 1111
case 0x2000: Key_Val = 6; break; // S6 1101 1111 1111 1111
case 0x1000: Key_Val = 7; break; // S7 1110 1111 1111 1111
case 0x0800: Key_Val = 8; break; // S8 1111 0111 1111 1111
case 0x0400: Key_Val = 9; break; // S9 1111 1011 1111 1111
case 0x0200: Key_Val = 10; break; // S10
case 0x0100: Key_Val = 11; break; // S11
case 0x0080: Key_Val = 12; break; // S12
case 0x0040: Key_Val = 13; break; // S13
case 0x0020: Key_Val = 14; break; // S14
case 0x0010: Key_Val = 15; break; // S15
case 0x0008: Key_Val = 16; break; // S16
case 0x0004: Key_Val = 17; break; // S17
case 0x0002: Key_Val = 18; break; // S18
case 0x0001: Key_Val = 19; break; // S19
default: Key_Val = 0;
}
return Key_Val;
}
自己写的代码
key.h
#ifndef __KEY_H
#define __KEY_H
#include "STC15F2K60S2.H"
void key_resd(int* temp);
#endif
key.c
#include "key.h"
void key_resd(int* temp){ //按键选择函数
unsigned int val = 0; //定义一个无符号整型变量val
//按列扫描 被检测管脚为 P30 P31 P32 P33
P44=0,P42=1,P35=1,P34=1; //第一列 S7-S4 从右往左
val = P3 & 0x0f; //P3 & 上一个0x0F,若后四位有低电平,即后四位有0,则会清零
P44 = 1,P42 = 0,P35 = 1,P34 = 1; //第二列 S11-S8
val = (val<<4)|(P3 & 0x0f); //val向左移四位,再承接第二位的检测值
P44 = 1,P42 = 1,P35 = 0,P34 = 1; //第三列 S15-S12
val = (val<<4)|(P3 & 0x0f); //val向左移四位,再承接第三位的检测值
P44 = 1,P42 = 1,P35 = 1,P34 = 0; //第四列 S19-S16
val = (val<<4)|(P3 & 0x0f); //val向左移四位,再承接第四位的检测值,最左边的四位数(P37 P36 P35 P34)出了范围,自动消除,只留下第一列至第四列的检测值
switch(~val){
case 0x0001:(*temp) = 19;break; //即为 1111 1111 1111 1110 第四列,从右往左,第一个为 S19
case 0x0002:(*temp) = 18;break;
case 0x0004:(*temp) = 17;break;
case 0x0008:(*temp) = 16;break;
case 0x0010:(*temp) = 15;break;
case 0x0020:(*temp) = 14;break;
case 0x0040:(*temp) = 13;break;
case 0x0080:(*temp) = 12;break;
case 0x0100:(*temp) = 11;break;
case 0x0200:(*temp) = 10;break;
case 0x0400:(*temp) = 9;break;
case 0x0800:(*temp) = 8;break;
case 0x1000:(*temp) = 7;break;
case 0x2000:(*temp) = 6;break;
case 0x4000:(*temp) = 5;break;
case 0x8000:(*temp) = 4;break;
}
}
seg.h
#ifndef __SEG_H
#define __SEG_H
#include "STC15F2K60S2.H"
void pos_FF(); //位选消隐
void value_FF(); //段选消隐
void seg_pos(unsigned int pos); //位选
void seg_value(); //段选
void seg_disp(int* value); //数码管显示
#endif
seg.c
#include <stdio.h>
#include "seg.h"
#include "tim.h"
/*段选消隐*/
void value_FF(){
P0 = 0xFF;
P2 = P2&0x1F|0xE0;
P2 &= 0x1F;
}
/*位选*/
void seg_pos(unsigned int pos){
P0 = 1<<(pos - 1); //选择com接口
P2 = P2 & 0x1F | 0xC0; //选择Y6
P2 &= 0x1F; //锁存
}
/*段选*/
void seg_value(void){
P2 = P2 & 0x1F | 0xE0; //选择Y7
P2 &= 0x1F; //锁存
}
/*显示函数*/
void seg_disp(int* value){
unsigned char smgduan[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};//显示0~9的值
unsigned int pos; //定义一个位选变量pos
if((*value) >= 0 && (*value) < 10){ //当value为个位数时
pos = 8; //只显示第一个数码管
value_FF(); //段选消隐
seg_pos(pos); //调用位选函数
P0=smgduan[(*value)]; //选择段选显示
seg_value();
}
else if((*value) < 100){ //当value为两位数时
pos = 8; //第一个数码管显示
value_FF(); //段选消隐
seg_pos(pos); //调用位选函数
P0=smgduan[(*value)%10]; //个位显示
seg_value(); //调用段选显示函数
value_FF(); //段选消隐
pos = 7; //第二个数码管显示
seg_pos(pos); //调用位选函数
P0=smgduan[(*value)/10]; //十位显示
seg_value(); //调用段选函数
}
}
tim.h
#ifndef __TIM_H
#define __TIM_H
#include "STC15F2K60S2.H"
void Cls_Peripheral(void); //关闭外设
void time(void); //延时函数
#endif
tim.c
#include "tim.h"
void Cls_Peripheral(void) //关闭外设。
{
P0 = 0xFF;
P2 = P2 & 0x1F | 0x80; // P27~P25清零,再定位Y4C
P2 &= 0x1F; // P27~P25清零
P0 = 0;
P2 = P2 & 0x1F | 0xA0; // P27~P25清零,再定位Y5C
P2 &= 0x1F; // P27~P25清零
}
void time(void)
{
int i,j;
for(i=0;i<5;i++){
for(j=0;j<5;j++);
}
}
main.c
#include <stdio.h>
#include <STC15F2K60S2.h>
#include "tim.h"
#include "seg.h"
#include "key.h"
int temp = 0;
int* p = &temp;
int main()
{
Cls_Peripheral();
while(1){
key_resd(p);
seg_disp(p);
}
}
问题
之前一直显示不出来,知道是消隐的问题,但就是改不好。然后最近看了别人的代码,发现只要段选消隐就够了,位选可以不用消隐,现在改了下,可以实现功能了。但是当数字为两位数时,个位的亮度只有一半,这应该是消隐时长的问题,但我还解决不了 orz,知道是用定时器去写,但定时器前两天才学,还没完全搞懂,先这样吧,等我搞懂了再回过头来改。 2021.10.20.
问题解决了,详见蓝桥杯 —— 单片机学习(4—— 数码管显示)
原理就是用定时器中断来显示数码管,因为定时器中断的频率是不变的,所以每位数码管的显示时间都一致。
推荐写法
按键不推荐加延时函数,最好放定时器里扫描;
sbit KEY_IN_1 = P4^4;
sbit KEY_IN_2 = P4^2;
sbit KEY_IN_3 = P3^5;
sbit KEY_IN_4 = P3^4;
sbit KEY_OUT_1 = P3^0;
sbit KEY_OUT_2 = P3^1;
sbit KEY_OUT_3 = P3^2;
sbit KEY_OUT_4 = P3^3;
u8 KeySta[4][4] = {{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}}; //当前按键状态
//按键扫描函数,在定时器中断程序里调用,定时器间隔1ms,通过全局变量来获取键值;
void Key_Scan(void)
{
u8 i;
static u8 keyout = 0; //矩阵按键扫描输出索引
static u8 keybuff[4][4] = {{0xff, 0xff, 0xff, 0xff},\
{0xff, 0xff, 0xff, 0xff},\
{0xff, 0xff, 0xff, 0xff},\
{0xff, 0xff, 0xff, 0xff}}; //矩阵按键扫描缓存区
// \ 符号表示下一行继续,记得这个符号后面不能加任何东西,包括空格
keybuff[keyout][0] = (keybuff[keyout][0] << 1) | KEY_IN_1; //将每一行的4个按键值移入缓存区
keybuff[keyout][1] = (keybuff[keyout][1] << 1) | KEY_IN_2;
keybuff[keyout][2] = (keybuff[keyout][2] << 1) | KEY_IN_3;
keybuff[keyout][3] = (keybuff[keyout][3] << 1) | KEY_IN_4;
//消抖后更新按键状态
for (i = 0;i < 4;i ++)
{
if ((keybuff[keyout][i] & 0x0f) == 0x00)
KeySta[keyout][i] = 0; //连续4次扫描值都是0,即4×4us内都是按下状态,认为按键已平稳按下
else if ((keybuff[keyout][i] & 0x0f) == 0x0f)
KeySta[keyout][i] = 1; //连续4次扫描值都是1,即4×4us内都是松开状态,认为按键已稳定弹起
}
//执行下一次的扫描输出
keyout ++;
keyout = keyout & 0x03; //索引加到4就归零
switch (keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
{
case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
default:break;
}
}