文章目录
通用函数(2023.09.03)
包含常用头文件,宏定义,自定义类型,函数工具等。当需要更多通用功能时,可以平替delay.h
延时函数头文件。
public.h
#ifndef _PUBLIC_H_
#define _PUBLIC_H_
/*---------------------常用头文件---------------------------------*/
#include <REGX52.H>
#include <intrins.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#define false 0
#define true 1
typedef unsigned char u8;
typedef unsigned int u16;
void delay_10us(u16);
void delay_ms(u16);
u8 * int2String(int, bit);
u8 * float2String(float, u8);
#endif
public.c
#include "public.h"
/**
** @brief 通用函数
** @author QIU
** @data 2023.09.03
**/
/*-------------------------------------------------------------------*/
/**
** @brief 延时函数(10us)
** @param t:0~65535,循环一次约10us
** @retval 无
**/
void delay_10us(u16 t){
while(t--);
}
/**
** @brief 延时函数(ms)
** @param t:0~65535,单位ms
** @retval 无
**/
void delay_ms(u16 t){
while(t--){
delay_10us(100);
}
}
/**
** @brief 整数转字符串
** @param num:接受整型值
** @param sign:是否带符号
** @retval 返回字符串指针
**/
u8 * int2String(int num, bit sign){
static u8 str[8];
// 是否带符号
if(sign) sprintf(str, "%d", num);
else sprintf(str, "%u", num);
// 返回指针
return str;
}
/**
** @brief 浮点数转字符串
** @param num:接受浮点数
** @param len:指定精度,小数点位数0~6(四舍五入)
** @retval 返回字符串指针
**/
u8 * float2String(float num, u8 len){
static u8 str[10];
sprintf(str, "%.*f", (int)len, num);
// 返回指针
return str;
}
延时函数(2023.08.23)
包含常用延时函数。通用函数兼容延时函数。
delay.h
#ifndef _DELAY_H_
#define _DELAY_H_
#include <REGX52.H>
#define false 0
#define true 1
typedef unsigned char u8;
typedef unsigned int u16;
void delay_10us(u16);
void delay_ms(u16);
#endif
delay.c
#include "delay.h"
/**
** @brief 通用函数
** @author QIU
** @data 2023.08.23
**/
/*-------------------------------------------------------------------*/
/**
** @brief 延时函数(10us)
** @param t:0~65535,循环一次约10us
** @retval 无
**/
void delay_10us(u16 t){
while(t--);
}
/**
** @brief 延时函数(ms)
** @param t:0~65535,单位ms
** @retval 无
**/
void delay_ms(u16 t){
while(t--){
delay_10us(100);
}
}
LED模块(2023.08.23)
包含常用LED
显示函数。新增了流水灯和跑马灯的定时器刷新函数。
led.h
#ifndef _LED_H_
#define _LED_H_
#include "delay.h"
#define LED_PORT P2
extern u8 flag_led_stream; // 用于控制流水灯何时执行
void led_on(u8);
void led_turn(u8);
void led_stream(u16);
void led_run(u16);
void led_stream_byTimer();
void led_run_byTimer();
#endif
led.c
#include "led.h"
/**
** @brief LED控制程序
** @author QIU
** @data 2023.08.23
**/
/*-------------------------------------------------------------------*/
// 控制定时器流水灯执行的开关
u8 flag_led_stream = false;
/**
** @brief 指定某个LED亮
** @param pos: 位置(1~8)
** @retval 无
**/
void led_on(u8 pos){
LED_PORT &= ~(0x01<<(pos-1));
}
/**
** @brief 指定某个LED灭
** @param pos: 位置(1~8)
** @retval 无
**/
void led_off(u8 pos){
LED_PORT |= 0x01<<(pos-1);
}
/**
** @brief 指定位置LED翻转
** @param pos:1—8
** @retval 无
**/
void led_turn(u8 pos){
u8 port;
port = (LED_PORT>>(pos-1))&0x01;
if(port){
led_on(pos);
}else{
led_off(pos);
}
}
/**
** @brief LED流水灯
** @param time 延时时间
** @retval 无
**/
void led_stream(u16 time){
u8 i;
for(i=0;i<8;i++){
led_on(i+1);
delay_10us(time);
}
// 全部熄灭
for(i=0;i<8;i++){
led_off(i+1);
}
}
/**
** @brief LED跑马灯
** @param time 延时时间
** @retval 无
**/
void led_run(u16 time){
u8 i;
for(i=0;i<8;i++){
led_on(i+1);
delay_10us(time);
led_off(i+1);
}
}
/**
** @brief LED流水灯(定时器控制)
** @param 无
** @retval 无
**/
void led_stream_byTimer(){
static u8 pos = 1;
u8 i;
if(pos > 8){
// 只执行一次
// flag_led_stream = false;
pos = 1;
// 全部熄灭
for(i=0;i<8;i++){
led_off(i+1);
}
}else{
led_on(pos);
pos++;
}
}
/**
** @brief LED跑马灯(定时器控制)
** @param 无
** @retval 无
**/
void led_run_byTimer(){
static u8 pos = 1;
if(pos == 1 || pos > 8){
pos = 1;
led_on(pos);
led_off(8);
pos++;
}else{
led_off(pos-1);
led_on(pos);
pos++;
}
}
数码管模块(2024.02.13)
主要实现了延时法刷新和定时器法刷新两种方式。提供字符静态写入函数、整数写入函数、浮点数写入函数和字符串写入函数。
主要解决了小数点显示和负号显示的问题。
定时器法仅提供字符串写入函数,而将整数、浮点数与字符串的转换函数写在了public.c
通用函数文件中,
smg.h
#ifndef _SMG_H_
#define _SMG_H_
#include "public.h"
#define SMG_PORT P0
// 位选引脚,与38译码器相连
sbit A1 = P2^2;
sbit A2 = P2^3;
sbit A3 = P2^4;
void smg_showChar(u8, u8, bit); // 静态字符显示函数
void smg_showString(u8*, u8); // 动态字符串显示函数(延时法)
void smg_showInt(int, u8); // 动态整数显示函数(延时法)
void smg_showFloat(double, u8, u8); // 动态浮点数显示函数(延时法)
void smg_showString_Bytimer(u8*, u8); // 动态字符串显示函数(定时器法)
#endif
smg.c
#include "smg.h"
/**
** @brief 数码管封装
** 1. 延时刷新
** (1) 字符静态显示:仅需一次输入。输入字符。可用于初始清屏。
** (2) 字符串数据动态显示
** (3) 浮点型数据动态显示:可以显示小数。
** (4) 整型数据动态显示:可以显示负数。
** 2. 定时器刷新
** @author QIU
** @date 2024.02.13
**/
/*-------------------------------------------------------------------*/
//共阴极数码管字形码编码
u8 code smgduan[] = {0x3f,0x06,0x5b,0x4f,0x66, //0 1 2 3 4
0x6d,0x7d,0x07,0x7f,0x6f, //5 6 7 8 9
0x77,0x7c,0x58,0x5e,0x79, //A b c d E
0x71,0x76,0x30,0x0e,0x38, //F H I J L
0x54,0x3f,0x73,0x67,0x50, //n o p q r
0x6d,0x3e,0x3e,0x6e,0x40};//s U v y -
/**
** @brief 指定第几个数码管点亮,38译码器控制位选(不对外声明)
** @param pos:从左至右,数码管位置 1~8
** @retval 无
**/
void select_38(u8 pos){
u8 temp_pos = 8 - pos; // 0~7
A1 = temp_pos % 2; //高位
temp_pos /= 2;
A2 = temp_pos % 2;
temp_pos /= 2;
A3 = temp_pos % 2; //低位
}
/**
** @brief 解析数据并取得相应数码管字形码编码
** @param dat:想要显示的字符
** @retval 对应字形码编码值
**/
u8 parse_data(u8 dat){
switch(dat){
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':return smgduan[dat-'0'];
case 'a':
case 'A':return smgduan[10];
case 'b':
case 'B':return smgduan[11];
case 'c':
case 'C':return smgduan[12];
case 'd':
case 'D':return smgduan[13];
case 'e':
case 'E':return smgduan[14];
case 'f':
case 'F':return smgduan[15];
case 'h':
case 'H':return smgduan[16];
case 'i':
case 'I':return smgduan[17];
case 'j':
case 'J':return smgduan[18];
case 'l':
case 'L':return smgduan[19];
case 'n':
case 'N':return smgduan[20];
case 'o':
case 'O':return smgduan[21];
case 'p':
case 'P':return smgduan[22];
case 'q':
case 'Q':return smgduan[23];
case 'r':
case 'R':return smgduan[24];
case 's':
case 'S':return smgduan[25];
case 'u':
case 'U':return smgduan[26];
case 'v':
case 'V':return smgduan[27];
case 'y':
case 'Y':return smgduan[28];
case '-':return smgduan[29];
default:return 0x00; //不显示
}
}
/**
** @brief 根据输入的ASCII码,显示对应字符(1字节)
** @param dat:字符数据,或其ASCII值
** @param pos:显示位置 1~8
** @retval 无
**/
void smg_showChar(u8 dat, u8 pos, bit flag){
// 解析点亮哪一个数码管
select_38(pos);
// 解析数据
SMG_PORT = parse_data(dat);
// 加标点
if(flag) SMG_PORT |= 0x80;
}
/*-------------------------------------------------------------------*/
/*-----------------------延时法刷新----------------------------------*/
/*-------------------------------------------------------------------*/
/**
** @brief 延时法刷新
** @param dat:字符数组,需以'\0'结尾
** @param pos:显示位置
** @param dot:小数点位置
** @retval 无
**/
void smg_flush_Bydelay(u8 dat[], u8 pos, u8 dot){
u8 i;
// 超出部分直接截断
for(i=0;(i<9-pos)&&(dat[i]!='\0');i++){
// 如果是小数点,跳过,往前移一位
if(dat[i] == '.'){
pos -= 1;
continue;
}
// 显示
smg_showChar(dat[i], pos+i, (dot == i+1)?true:false);
// 延时1ms
delay_ms(1);
// 消影
SMG_PORT = 0x00;
}
}
/**
** @brief 显示字符串(动态显示)
** @param dat:字符数组,需以'\0'结尾
** @param pos:显示位置
** @retval 无
**/
void smg_showString(u8 dat[], u8 pos){
u8 i = 0, dot = 0;
// 先判断是否存在小数点
while(dat[i]!='\0'){
if(dat[i] == '.') break;
i++;
}
// 记录下标点位置
if(i < strlen(dat)) dot = i;
// 延时法刷新
smg_flush_Bydelay(dat, pos, dot);
}
/**
** @brief 数码管显示整数(含正负)
** @param dat: 整数
** @param pos: 显示位置
** @retval 无
**/
void smg_showInt(int dat, u8 pos){
xdata u8 temp[9];
sprintf(temp, "%d", dat); // 含正负
smg_showString(temp, pos);
}
/**
** @brief 数码管显示浮点数(含小数点)
** @param dat: 浮点数
** @param len: 指定精度
** @param pos: 显示位置
** @retval 无
**/
void smg_showFloat(double dat, u8 len, u8 pos){
xdata u8 temp[10];
int dat_now;
dat_now = dat * pow(10, len) + 0.5 * (dat>0?1:-1); // 四舍五入(正负),由于浮点数存在误差,结果未必准确
sprintf(temp, "%d", dat_now); // 含正负
smg_flush_Bydelay(temp, pos, len?(strlen(temp) - len):0);
}
/*-------------------------------------------------------------------*/
/*--------------------------定时器法刷新-----------------------------*/
/*-------------------------------------------------------------------*/
/**
** @brief 数码管显示字符串(定时器法刷新)
** @param dat:字符数组,需以'\0'结尾
** @param pos:显示位置
** @retval 返回值
**/
void smg_showString_Bytimer(u8 dat[], u8 pos){
// 数码管计数器, 小数点位置
static u8 smg_counter = 0, dot_counter = 0, dot_port[8];
// 暂存当前位置
u8 temp;
// 先消影
SMG_PORT = 0x00;
// 如果是小数点,跳出。
if(dat[smg_counter] == '.'){
// 记录小数点位置,下一轮刷新
dot_port[smg_counter-1] = true;
// 计数器后移一位
smg_counter++;
// 小数点计数器自增
dot_counter++;
return;
}
// 计算当前位置
temp = pos+smg_counter-dot_counter;
// 判断是否加小数点(检测到小数点的后面几位整体前移)
smg_showChar(dat[smg_counter], temp, dot_port[smg_counter]);
// 如果是结束符,跳出(超出部分截断)
if(temp == 8 | dat[smg_counter] == '\0'){
// 重置
smg_counter = 0;
// 根据标志决定是否清除小数点
if(dot_counter){
// 清零
dot_counter = 0;
}else{
// 清空
strcpy(dot_port, "");
}
return;
}else{
smg_counter++;
}
}
LED点阵模块(2023.08.23)
本质上是对驱动芯片74HC595
的驱动逻辑的封装。更新了延时法刷新与定时器法刷新两种方式。
LED_Matrix.h
#ifndef _LED_MATRIX_
#define _LED_MATRIX_
#include "public.h"
#define LED_Matrix_PORT P0
#define SPEED 300
#define COL_NUM 1 //每帧移动列数
#define LEN sizeof(Animation_Array)/sizeof(Animation_Array[0]) - 8 // 动画数组长
sbit SER = P3^4; //串行输入
sbit ST = P3^5; //存储寄存器时钟引脚
sbit SH = P3^6; //移位存储器时钟引脚
void LED_Matrix_Init();
void LED_Matrix_Flush_Delay();
void LED_Matrix_Flush_Timer();
#endif
LED_Matrix.c
#include "LED_Matrix.h"
/**
** @brief LED点阵封装(74HC595芯片驱动代码)
** @author QIU
** @data 2023.08.23
**/
/*-------------------------------------------------------------------*/
// 画面帧(2022年12月30日)
u8 code Animation_Array[] = {
0x61,0x83,0x85,0x89,0x71,0x00,0x7E,0x81,
0x81,0x7E,0x00,0x61,0x83,0x85,0x89,0x71,
0x00,0x61,0x83,0x85,0x89,0x71,0x00,0x44,
0xDC,0x54,0x7F,0x54,0x44,0x00,0x40,0xFF,
0x00,0x61,0x83,0x85,0x89,0x71,0x00,0x01,
0xFE,0xA8,0x82,0xFF,0x00,0x42,0x91,0x99,
0x66,0x00,0x7E,0x81,0x81,0x7E,0x00,0xFF,
0x91,0x91,0xFF,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,};
/**
** @brief 74HC595芯片(串转并)驱动代码
** @param dat:8位串行数据
** @retval 无
**/
void LED_control(u8 dat){
u8 i;
//将一个字节拆分成串行输入
for(i=0;i<8;i++){
SER = dat >> 7; //先将最高位送入SER中
dat <<= 1; //左移1位(去掉最高位)更新数据
SH = 0; //给移位寄存器时序脉冲
_nop_();
SH = 1; //检测到上升沿时将SER数据读入移位寄存器中
_nop_();
}
ST = 0; //当一个字节传输完毕,此时移位寄存器已满。给存储寄存器时序脉冲
_nop_();
ST = 1;//检测到上升沿时将移位寄存器中的8位数据全部读入存储寄存器中。通过并行输出引脚可以直接检测到
_nop_();
}
/**
** @brief 清屏函数
** @param 无
** @retval 无
**/
void LED_Matrix_Init(){
LED_Matrix_PORT = 0xff;
}
/**
** @brief 动态显示对应静态画面帧(8*8)
** @param datX:阴极,datY:阳极
** @retval 无
**/
void LED_Animation_Show(u8 datX, u8 datY){
LED_Matrix_Init(); //消影
LED_control(datY); //阳极码
LED_Matrix_PORT = ~(0x80>>datX);
}
/**
** @brief 控制进入下一帧画面的时间
** @param 无
** @retval 动画数组索引偏移值
**/
u16 Next_LED_Index(){
static u16 count; // count用于记录进入下一帧画面的时间
static u16 j; // j为画面帧移动的列数
count++;
if(count > SPEED){
count = 0;
j += COL_NUM;
if(j > LEN) j = 0;
}
return j;
}
/**
** @brief 延时法刷新
** @param 参数说明
** @retval 返回值
**/
void LED_Matrix_Flush_Delay(){
u8 i; // 当前点亮的列号
// 动态显示一帧画面
for(i=0;i<8;i++){
LED_Animation_Show(i, Animation_Array[i + Next_LED_Index()]);
delay_10us(100);
}
}
/**
** @brief 定时器法刷新
** @param 参数说明
** @retval 返回值
**/
void LED_Matrix_Flush_Timer(){
static u8 i;
LED_Animation_Show(i, Animation_Array[i + Next_LED_Index()]);
if(i >= 8) i = 0;
else i++;
}
独立按键模块(2024.02.08)
主要实现了延时法按键检测和外部中断+定时器法按键检测两种方式。具有短按、长按、单击、双击、组合键等功能。
key.h
#ifndef __KEY_H__
#define __KEY_H__
#include "delay.h"
// 按键单次响应(0)或连续响应(1)开关,针对长按事件
#define KEY1_MODE 0
#define KEY2_MODE 0
#define KEY3_MODE 0
#define KEY4_MODE 1
#define KEY_1_2_MODE 0
#define KEY_1_3_MODE 0
#define KEY_1_4_MODE 0
#define KEY_2_3_MODE 0
#define KEY_2_4_MODE 0
#define KEY_3_4_MODE 1
// 按键时长(短按、长按分界)
# define KEY_DOWN_DURATION 300
// 双击时长(单击、双击分界),从第一次短按松开开始计算。取0时退化为单击
# define KEY_DOUBLE_DURATION 80
// 按键引脚定义
sbit key1 = P3^1;
sbit key2 = P3^0;
sbit key3 = P3^2;
sbit key4 = P3^3;
// 按键基础状态枚举(低四位可表示4个独立按键)
typedef enum{
KEY_UNPRESS = 0x00, // 0000 0000
KEY1_PRESS = 0x01, // 0000 0001
KEY2_PRESS = 0x02, // 0000 0010
KEY3_PRESS = 0x04, // 0000 0100
KEY4_PRESS = 0x08, // 0000 1000
KEY1_2_PRESS = 0x03, // 0000 0011
KEY1_3_PRESS = 0x05, // 0000 0101
KEY1_4_PRESS = 0x09, // 0000 1001
KEY2_3_PRESS = 0x06, // 0000 0110
KEY2_4_PRESS = 0x0A, // 0000 1010
KEY3_4_PRESS = 0x0C, // 0000 1100
}Key_State;
// 按键事件模式枚举(最低位表示按键长短事件,次低位表示按键双击单击事件)
typedef enum{
FREE_MODE = 0xff, // 按键模式待确定阶段
SHORT_PRESS = 0x00, // 0000 0000
LONG_PRESS = 0x01, // 0000 0001
DOUBLE_CLICK = 0x02, // 0000 0010
}Key_Mode;
// 外部声明时,尽量带上数据类型,否则会产生重复定义的错误
extern Key_State key_state;
void scan_key();
void check_key();
void scan_double_click();
void scan_key_ByTimer();
#endif
key.c
#include "key.h"
// 包含的头文件(按需求修改)
#include "led.h"
#include "smg.h"
/**
** @brief 独立按键的函数封装
** 1. 单键的短按与长按事件(长按事件分单次或连续响应)
** 2. 单键的单击与双击事件
** 3. 组合键的短按与长按事件
** @author QIU
** @date 2024.02.07
**/
/*-------------------------------------------------------------------*/
// 实时按键状态、当前确认状态、前确认状态
Key_State key_state, key_now_state, key_pre_state;
// 当前按键模式
Key_Mode key_mode = FREE_MODE;
// 双击标志
u8 Double_Click_flag = false;
// 双击最大延时计数器
u16 Double_Click_Counter = 0;
// 按键累积
u16 num = 0;
// LED流水灯速度
u16 led_speed = 10000;
/*------------------------按键响应函数定义区------------------------------*/
/**
** @brief 按键3短按,短按响应,在按键松开后判定执行
** @param 参数说明
** @retval 返回值
**/
void key3_short_press(){
led_turn(1);
}
/**
** @brief 按键3长按,按下响应
** @param 参数说明
** @retval 返回值
**/
void key3_long_press(){
led_run(led_speed);
}
/**
** @brief 按键3双击
** @param 参数说明
** @retval 返回值
**/
void key3_double_click(){
led_turn(2);
}
/**
** @brief 按键4短按,短按响应,在按键松开后判定执行
** @param 参数说明
** @retval 返回值
**/
void key4_short_press(){
led_turn(3);
}
/**
** @brief 按键4长按,按下响应
** @param 参数说明
** @retval 返回值
**/
void key4_long_press(){
led_stream(led_speed);
}
/**
** @brief 按键四双击
** @param 参数说明
** @retval 返回值
**/
void key4_double_click(){
led_turn(4);
}
/**
** @brief 按键3、4组合键,短按响应
** @param 参数说明
** @retval 返回值
**/
void key3_4_combination(){
led_turn(5);
}
/**
** @brief 按键3、4组合键,长按响应
** @param 参数说明
** @retval 返回值
**/
void key3_4_long_combination(){
num++;
smg_showInt(num, 1);
}
/*--------------------------延时法按键检测-------------------------------*/
#if 0
/**
** @brief 按键松开响应
** @param 无
** @retval 无
**/
void key_unpress(){
switch(key_pre_state){
case KEY_UNPRESS: break;
case KEY1_PRESS: key1_unpressed();break;
case KEY2_PRESS: key2_unpressed();break;
case KEY3_PRESS: key3_unpressed();break;
case KEY4_PRESS: key4_unpressed();break;
case KEY1_2_PRESS: key1_2_unpressed();break;
case KEY2_3_PRESS: key2_3_unpressed();break;
case KEY3_4_PRESS: key3_4_unpressed();break;
}
}
/**
** @brief (轮询方式)扫描独立按键,判断哪个键按下
** @param 无
** @retval 无
**/
void scan_key(){
static u8 flag = 1;
// 如果有按键按下
if(flag && (!key1||!key2||!key3||!key4)){
flag = 0; // 清零
delay_ms(10); // 延时10ms消抖
delay_ms(50); // 延时50ms 容许间隔
// 获取当前所有按下的键
if(!key1) key_now_state |= KEY1_PRESS;
if(!key2) key_now_state |= KEY2_PRESS;
if(!key3) key_now_state |= KEY3_PRESS;
if(!key4) key_now_state |= KEY4_PRESS;
// 如果按键全部松开
}else if(key1&&key2&&key3&&key4){
flag = 1;
delay_ms(10); // 延时10ms消抖,松开响应有逻辑判断时,需要加上消抖。否则可以省略。
if(key1&&key2&&key3&&key4)key_now_state = 0;
}
}
/**
** @brief (轮询方式)判断按键, 进行相应处理
** @param 无
** @retval 无
**/
void check_key(){
switch(key_now_state){
case KEY_UNPRESS:
// 松开响应
key_unpress();
key_pre_state = KEY_UNPRESS;
break;
case KEY1_PRESS:
key_pre_state = KEY1_PRESS;
// 如果是单次响应
if(!KEY1_MODE) key_now_state = KEY_NON_DEAL;
key1_pressed();
break;
case KEY2_PRESS:
key_pre_state = KEY2_PRESS;
if(!KEY2_MODE) key_now_state = KEY_NON_DEAL;
break;
case KEY3_PRESS:
key_pre_state = KEY3_PRESS;
if(!KEY3_MODE) key_now_state = KEY_NON_DEAL;
key3_pressed();
break;
case KEY4_PRESS:
key_pre_state = KEY4_PRESS;
if(!KEY4_MODE) key_now_state = KEY_NON_DEAL;
key4_pressed();
break;
case KEY1_2_PRESS:
key_pre_state = KEY1_2_PRESS;
if(!KEY_1_2_MODE) key_now_state = KEY_NON_DEAL;
key_1_2_pressed();
break;
case KEY2_3_PRESS:
key_pre_state = KEY2_3_PRESS;
if(!KEY_2_3_MODE) key_now_state = KEY_NON_DEAL;
key_2_3_pressed();
break;
case KEY3_4_PRESS:
key_pre_state = KEY3_4_PRESS;
if(!KEY_3_4_MODE) key_now_state = KEY_NON_DEAL;
key_3_4_pressed();
break;
case KEY_NON_DEAL:
// 按下不处理
break;
}
}
#endif
/*---------------------------定时器法按键检测--------------------------------*/
/**
** @brief 双击事件检测
** @param 参数说明
** @retval 返回值
**/
void scan_double_click(){
if(Double_Click_flag){
Double_Click_Counter++;
// 如果已经超过了双击时间界线
if(Double_Click_Counter >= KEY_DOUBLE_DURATION){
// 上次短按视为单击事件
key_mode = SHORT_PRESS;
Double_Click_Counter = 0;
Double_Click_flag = false;
}
}else{
Double_Click_Counter = 0;
}
}
/**
** @brief (轮询方式)判断按键状态与模式, 进行相应处理
** @param 无
** @retval 无
**/
void check_key(){
// 检查按键模式
switch(key_mode){
case SHORT_PRESS:
// 按键模式流转
key_mode = FREE_MODE;
// 短按只有松开响应,故考虑前状态
// 短按均为单次响应
switch(key_pre_state){
case KEY1_PRESS:break;
case KEY2_PRESS:break;
case KEY3_PRESS:key3_short_press();break;
case KEY4_PRESS:key4_short_press();break;
case KEY1_2_PRESS:break;
case KEY1_3_PRESS:break;
case KEY1_4_PRESS:break;
case KEY2_3_PRESS:break;
case KEY2_4_PRESS:break;
case KEY3_4_PRESS:key3_4_combination();break;
}
break;
case LONG_PRESS:
// 长按只有按下响应,故考虑当前状态
// 长按分为单次响应和连续响应
switch(key_now_state){
case KEY1_PRESS:break;
case KEY2_PRESS:break;
case KEY3_PRESS:
if(!KEY3_MODE) key_mode = FREE_MODE;
key3_long_press();
break;
case KEY4_PRESS:
if(!KEY4_MODE) key_mode = FREE_MODE;
key4_long_press();
break;
case KEY1_2_PRESS:break;
case KEY1_3_PRESS:break;
case KEY1_4_PRESS:break;
case KEY2_3_PRESS:break;
case KEY2_4_PRESS:break;
case KEY3_4_PRESS:
// 按键模式流转
if(!KEY_3_4_MODE) key_mode = FREE_MODE;
key3_4_long_combination();
break;
}
break;
case DOUBLE_CLICK:
key_mode = FREE_MODE;
// 双击只有按下响应,故考虑当前状态
// 双击只有单次响应
switch(key_now_state){
case KEY1_PRESS:break;
case KEY2_PRESS:break;
case KEY3_PRESS:key3_double_click();break;
case KEY4_PRESS:key4_double_click();break;
case KEY1_2_PRESS:break;
case KEY1_3_PRESS:break;
case KEY1_4_PRESS:break;
case KEY2_3_PRESS:break;
case KEY2_4_PRESS:break;
case KEY3_4_PRESS:break;
}
break;
default:
break;
}
}
/**
** @brief (定时器)确认按键是否稳定按下/松开,去抖
** @param 无
** @retval 无
**/
void scan_key_ByTimer(){
static u16 counter = 0;
static u8 flag = 0xff;
// 长按事件开关
static bit flag_LongMode = true;
// 双击事件开关
static bit flag_DoubleClickMode = false;
// 组合事件开关
static bit flag_CombinationMode = false;
flag <<= 1;
if(key_state & KEY3_PRESS) flag |= key3; // 如果实时按键状态中包含键3,则检查键3引脚状态(按下为0)
if(key_state & KEY4_PRESS) flag |= key4; // 如果实时按键状态中包含键4,则检查键4引脚状态(按下为0)
switch(flag){
case 0xff:
// 连续8次检测到按键松开,视为松开状态。
key_state &= ((~key3*KEY3_PRESS) | (~key4*KEY4_PRESS));
// 按键状态更新
key_pre_state = key_now_state;
key_now_state = key_state;
if(flag_DoubleClickMode){
flag_DoubleClickMode = false;
}else{
if(flag_CombinationMode){
// 解除屏蔽
flag_CombinationMode = false;
}else{
// 如果小于阈值,则为短按操作
if(counter < KEY_DOWN_DURATION){
// 等待给定双击阈值,判断是否为双击事件
Double_Click_flag = true;
// 清零
counter = 0;
}else{
// 长按只有按下响应
key_mode = FREE_MODE;
// 清零
counter = 0;
flag_LongMode = true;
}
}
}
// 如果键尚未完全松开,则剩余键被屏蔽
if(key_now_state != KEY_UNPRESS) flag_CombinationMode = true;
break;
case 0x00:
// 如果按键状态未变化
if(key_state == key_now_state){
if(flag_DoubleClickMode){
// 如果是双击事件,则不再关心第二次单击所耗时长
// 按住期间会屏蔽其他按键
}else if(flag_CombinationMode){
// 如果处于组合按键屏蔽的状态,不做响应
}else{
if(counter >= KEY_DOWN_DURATION){
if(flag_LongMode){
// 视为该键的长按模式
flag_LongMode = false;
key_mode = LONG_PRESS;
}
}else{
counter++; // 开始计时
}
}
}else{
// 连续8次检测到按键按下,视为按下状态。
// 如果按键动作相同且Double_Click_flag为真(即未超时),可视为双击事件
if((key_state == key_pre_state) && Double_Click_flag){
key_mode = DOUBLE_CLICK;
flag_DoubleClickMode = true;
Double_Click_flag = false;
}else{
// 重新计时(开始记录组合键时长)
counter = 0;
}
// 更新按键状态
key_pre_state = key_now_state;
key_now_state = key_state;
}
break;
}
}
矩阵按键模块(2024.02.17)
matrix_key.h
#ifndef _MATRIX_KEY_H_
#define _MATRIX_KEY_H_
#include "public.h"
#define MATRIX_PORT P1
// 矩阵按键单次响应(0)或连续响应(1)开关
#define MatrixKEY_MODE 0
sbit ROW_PORT_1 = P1^7;
sbit ROW_PORT_2 = P1^6;
sbit ROW_PORT_3 = P1^5; // 共用了蜂鸣器引脚
sbit ROW_PORT_4 = P1^4;
sbit COL_PORT_1 = P1^3;
sbit COL_PORT_2 = P1^2;
sbit COL_PORT_3 = P1^1;
sbit COL_PORT_4 = P1^0;
// 对外声明键值
extern u8 key_val;
// 矩阵按键反转法状态机
typedef enum{
COL_Test = 0, // 列检测(空闲状态)
Filter, // 滤抖
ROW_Test, // 行检测
}Turn_State;
void check_matrixKey_turn();
void check_matrixKey_scan();
void check_matrixKey_turn_ByTimer();
void check_matrixKey_scan_ByTimer();
#endif
matrix_key.c
#include "matrix_key.h"
/**
** @brief 实现了矩阵按键的两种扫描方式
** 1. 实现了延时法和定时器法两种刷新方式
** @author QIU
** @date 2024.02.18
**/
/*-------------------------------------------------------------------*/
// 存储按下的行列
u8 row, col;
// 按键事件处理状态,true已处理,false未处理
u8 key_is_dealed = false;
// 键值对应显示数值
u8 key_val = 0;
// 反转法状态机
Turn_State turn_state = COL_Test;
/**
** @brief 读取电平
** @param state: 0-列,1-行
** @retval 返回列(行)数
**/
u8 read_port(bit state){
u8 dat;
if(state) dat = MATRIX_PORT >> 4; // 如果是行,取高四位
else dat = MATRIX_PORT & 0x0f; // 如果是列,取低四位
// 从左上开始为第一行,第一列
switch(dat){
// 0000 1110 第4列(行)
case 0x0e: return 4;
// 0000 1101 第3列(行)
case 0x0d: return 3;
// 0000 1011 第2列(行)
case 0x0b: return 2;
// 0000 0111 第1列(行)
case 0x07: return 1;
// 0000 1111 没有按下
case 0x0f: return 0xff;
// 多键同时按下不响应
default: return 0;
}
}
/**
** @brief 矩阵按键处理函数
** @param 参数说明
** @retval 返回值
**/
void key_pressed(){
// 如果不是连续模式,则按键事件标记为已处理
if(!MatrixKEY_MODE) key_is_dealed = true;
// 数码管数据
key_val = (row - 1) * 4 + (col - 1);
}
/**
** @brief (反转法)检测按键(单键),按住过程中屏蔽其他按键。同列需全部松开才能再次响应
** @param 无
** @retval 无
**/
void check_matrixKey_turn(){
// 所有行置低电平,列置高电平
MATRIX_PORT = 0x0f;
// 读取所有列电平
col = read_port(0);
// 如果按键松开
if(col == 0xff) {key_is_dealed = false; return;}
// 如果有效键按下,延时消抖
else if(col && !key_is_dealed) delay_ms(10);
else return;
// 所有列置低电平,行置高电平
MATRIX_PORT = 0xf0;
// 读取所有行电平
row = read_port(1);
// 如果有键按下,响应
if(row && row != 0xff) key_pressed();
else return;
}
/**
** @brief (扫描法)检测按键,本例扫描列
** @param 无
** @retval 无
**/
void check_matrixKey_scan(){
u8 i;
for(i=0;i<4;i++){
MATRIX_PORT = ~(0x08>>i); // 逐列置0,且所有行置1
row = read_port(1); // 读取行
// 保证之前记录按下的列为当前扫描列
if(!row && col == i+1) continue; // 当前扫描列无有效键按下
else if(row == 0xff && col == i+1) {key_is_dealed = false; continue;}
else if(row && !key_is_dealed){ // 有效键按下且为未处理状态
delay_ms(10);
row = read_port(1); // 再次读取行
if(row && row != 0xff) {col = i+1;key_pressed();}
}
}
}
/**
** @brief (反转法)采用定时器
** @param 参数说明
** @retval 返回值
**/
void check_matrixKey_turn_ByTimer(){
static u8 counter = 0;
switch(turn_state){
case COL_Test:
// 所有行置低电平,列置高电平
MATRIX_PORT = 0x0f;
// 读取所有列电平
col = read_port(0);
// 如果按键未按下(已松开)
if(col == 0xff) {key_is_dealed = false; break;}
// 如果有效键按下,且按键未处理时,状态流转
else if(col && !key_is_dealed) turn_state = Filter;
break;
case Filter:
counter++;
// 一般定时1ms,即过滤10ms防抖
if(counter >= 10){
counter = 0;
turn_state = ROW_Test;
}
break;
case ROW_Test:
// 所有列置低电平,行置高电平
MATRIX_PORT = 0xf0;
// 读取所有行电平
row = read_port(1);
// 如果有键按下,响应
if(row && row != 0Xff) key_pressed();
// 状态流转
turn_state = COL_Test;
break;
}
}
/**
** @brief (扫描法)采用定时器
** @param 无
** @retval 无
**/
void check_matrixKey_scan_ByTimer(){
static u8 i, counter;
// 开始滤抖
if(counter){
if(counter > 10){
counter = 0;
row = read_port(1); // 再次读取行
if(row && row != 0xff) {col = i+1; key_pressed();}
}else{
counter++;
}
}else{
MATRIX_PORT = ~(0x08>>i); // 逐列置0,且所有行置1
row = read_port(1); // 读取行
// 保证之前记录按下的列为当前扫描列
if(row == 0xff && col == i+1) {key_is_dealed = false;}
else if(row && row != 0xff && !key_is_dealed) {counter++; return;} // 有效键按下且为未处理状态
i++;
if(i >= 4) i = 0;
}
}
外部中断模块(2023.08.31)
interrupt.h
#ifndef __INTERRUPT_H__
#define __INTERRUPT_H__
#include "delay.h"
void INTx_init(u8);
#endif
interrupt.c
#include "interrupt.h"
/**
** @brief 外部中断封装
** @author QIU
** @data 2023.08.31
**/
/*-------------------------------------------------------------------*/
/**
** @brief 配置外部中断x
** @param x:对应外部中断编号
** @retval 无
**/
void INTx_init(u8 x){
switch(x){
case 0:
IT0 = 1; // 设置外部中断0触发方式,下降沿触发
EX0 = 1; // 使能外部中断0
break;
case 1:
IT1 = 1; // 设置外部中断1触发方式,下降沿触发
EX1 = 1; // 使能外部中断1
break;
}
EA = 1; // 使能总中断
}
// 外部中断0的中断服务程序模板
//void INT1_serve() interrupt 0{
// ;
//}
// 外部中断1的中断服务程序模板
//void INT1_serve() interrupt 2{
// ;
//}
定时器模块(2023.08.31)
timer.h
#ifndef __TIMER_H__
#define __TIMER_H__
#include "delay.h"
void TIMERx_init(u8, u16);
#endif
timer.c
#include "timer.h"
/**
** @brief 定时器封装
** @author QIU
** @data 2023.08.31
**/
/*-------------------------------------------------------------------*/
/**
** @brief 定时器x的初始化
** @param num:定时器初值
** @retval 无
**/
void TIMERx_init(u8 x, u16 num){
switch(x){
case 0:
//1.配置TMOD工作方式
TMOD |= 0x01; //配置定时器T0,同时不改变T1的配置
//2.计算初值,存入TH0、TL0
TL0 = (65536-num)%256; //低8位
TH0 = (65536-num)/256; //高8位
//3.打开中断总开关EA和定时器中断允许位ET0
EA = 1;
ET0 = 1;
//4.打开定时器
// TR0 = 1;
break;
case 1:
//1.配置TMOD工作方式
TMOD |= 0x10; //配置定时器T1,同时不改变T0的配置
//2.计算初值,存入TH1、TL1
TL1 = (65536-num)%256; //低8位
TH1 = (65536-num)/256; //高8位
//3.打开中断总开关EA和定时器中断允许位ET1
EA = 1;
ET1 = 1;
//4.打开定时器
// TR1 = 1;
break;
}
}
// 定时器0的中断服务程序模板
//void TIMER0_serve() interrupt 1{
// TL0 = (65536-num)%256; //低8位
// TH0 = (65536-num)/256; //高8位
//}
// 定时器1的中断服务程序模板
//void TIMER1_serve() interrupt 3{
// TL1 = (65536-num)%256; //低8位
// TH1 = (65536-num)/256; //高8位
//}
蜂鸣器模块(2023.09.01)
beep.h
#ifndef _BEEP_H_
#define _BEEP_H_
#include "delay.h"
sbit BEEP_PORT = P1^5;
void beep_once(u8, u16);
#endif
beep.c
#include "beep.h"
/**
* @brief 蜂鸣器单响
* @param t 持续时长, fre 频率HZ
* @retval
*/
void beep_once(u8 t, u16 fre){
while(t--){
BEEP_PORT = !BEEP_PORT; // 取反
delay_10us(1e5/2/fre);
}
}