【51单片机实验笔记】前篇(三) 模块功能封装汇总(持续更新)


通用函数(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);
	}
}

串口通讯模块


ADC模块


PWM模块


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬铃木下的青春

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值