dumon
发表于 2024-4-17 22:28:58
Mark一下,因为已经快要学习到ADC后面的课程,再用自己手搓的开发板不能做相应的实验,就想申请下冲哥同款实验箱。昨天刚申请通过,今天就收到了,还是寄的顺丰,速度好快,本来预计可能要周六或周日才能到的。收到实验箱的第一眼就喜欢上了。它非常超乎我的意料,制作精美,工艺精湛,后续可以继续跟着冲哥的视频学习后面的课程了。
其他的小伙伴也要加油,STC真是不错,每天打卡学习真的可以免费得到实验箱,工欲善其事,必先利其器,STC都把这么好的学习武器提供给我们了,我们还有什么理由不好好学习单片机知识呢。
dumon
发表于 2024-4-17 22:31:49
dumon 发表于 2024-4-17 22:28
Mark一下,因为已经快要学习到ADC后面的课程,再用自己手搓的开发板不能做相应的实验,就想申请下冲哥同款 ...
太激动了,都忘记附上实验箱的照片了。{:titter:}
小涵子爸爸
发表于 2024-4-18 08:11:06
哇,鸟枪换大炮了{:4_250:}
dumon
发表于 2024-4-18 23:28:36
按键状态机
按键按状态来分:按键按下、按键松开
按电平来分:低电平、高电平
其中按键按下时按低电平时长分为5种状态:
<30 ms 消抖
=30 ms 单击
<3000ms单击结束
=3000ms长按3秒
>3000ms长按结束
可以看到时间是在一直累加的,当到达其中1个时间点时我们就执行这一步的程序,比如在30ms以内,我们认为按键是消抖状态,不进行任何操作,正好等于30ms时,执行单击程序,大于30ms小于3000ms这个区间是认为在长按过程,正好等于3000ms时认为达到长按的判定。超过3000ms,可以判定为长按结束。
这里引进了一个状态机的概念,状态机教程里说的是条件转移,即这个变量是按键按下后累计的时长ms,随着时间轴的进行,等于或满足哪个条件时就进行运行状态转移,就比如上面的消抖,单击,长按,他是要满足时间条件后才能运行对应的步。
下面分析下程序:
计时数组u16 count = {0,0,0,0,0,0,0,0}; //一个8位元素的数组,每个元素对应1个P口的8位IO口,用于记录每个按键按下后的时间,数据类型要为16位整型。比如count用于记录P3.0按下的时长
u8 LastState = 0; //8位整型变量用于记录上次哪个按键按下
检查按键状态函数,放在主程序中每10ms执行一次
void KEY_Deal(void)
{
u8 i = 0;
for(i=0; i<8; i++) //循环8次,用于判定P30~P37有无按键按下
{
if(~P3 & (1<<i)) //假设i= 0时,P30有按下为低电平,~P30 = ^(1111 1110)=0000 0001,1<<0 = 0000 0001,两者相与后为0000 0001,如果持续有P30按下,这个条件是一直满足的。
{
if(count<60000) //当有按键按下时,并且count的值小于60000时,对应的count就会一直进行累加,用于记录对应的按键按下时间。
count++;
}
else //当按键松开后,执行else里的语句
{
if(count>0) //当按键松开时,此时count是大于0的,会把按键状态更新给LastState,LastState用来记录哪个按键按下了
{
LastState |= (1<<i);//这个按键有按下过,假设是P35,i=5,LastState = 0000 0000,1<<5 = 0010 0000,更新后LastState = 0010 0000
}
else
{
LastState &= ~(1<<i);//两者相与,比如上面是P35按下,在i=0到4时,0010 0000 & 1111 1110,还是保持0010 0000不变,只有在i=5时才会将LastState清零,
} //所以在这段函数运行时,会记录下哪个按键有按下,按下了多长时间,并将按键状态传递给LastState。
count = 0; //计数变量count清零
}
}
}
读取指定的按键 状态
/**************变量声明**************/
#define KEY_NOPRESS 0 // 按键未按下
#define KEY_FILCKER 1 // 消抖
#define KEY_PRESS 2 // 单击
#define KEY_PRESSOVER 3 // 单击结束
#define KEY_LONGPRESS 4 // 长按3秒
#define KEY_LONGOVER 5 // 长按结束
#define KEY_RELAX 6 // 按键松开
u8 KEY_Readstate(u8 keynum) //keynum = 0~7;10ms执行一次
{
if(coount>0) //按键有按下时,count有计数,各种状态处理
{
if(count<3) //1~29ms按键消抖
{ return KEY_FILCKER;}
else if (count==3) //超过30ms,返回单击
{ return KEY_PRESS;}
else if (count<300) //31~2999ms,返回单击结束
{ return KEY_PRESSOVER;}
else if (count==300)//正好3000ms时,返回长按
{ return KEY_LONGPRESS;}
else // 大于3000ms,返回长按结束
{ return KEY_LONGOVER;}
}
else //按键松开后,进行判断按键之前有无按下过
{
if(LastState & (1<<keynum)) //假设之前P3.5有按下过,LastState会记忆P3.5的状态值,当P3.5再次按下,等其松开后就会返回”KEY_RELAX“,表示这个键上次有按下过
{ return KEY_RELAX;}
else
{ return KEY_NOPRESS;} //按键之前没有被按下过,比如上次是按的P3.5,这次是按的P3.0,两者不相等,就会返回这里语句的值。
}
}
以上,是对第13集多任务处理中,按键状态机的程序解读,LastState这个变量是用于检查按键检查函数中用于区分”按键松开“与”没有按下“。
通过计数数组count来统计按键按下时长,根据count的值来判定程序应该执行到哪一步,满足了某个条件就进行转移,这就是状态机,这也是程序中一种常用用法。后面还会经常运用到。
电子爱好者2024
发表于 2024-4-19 19:50:42
学的真好啊
dumon
发表于 2024-4-20 13:45:04
第13集 多任务处理的总结
附上开发板烧录程序后的演示视频
手写学习笔记
130
zuodesong
发表于 2024-5-21 19:24:39
大哥,真乃大佬也,佩服!我学的没你认真,现在都开始遗忘了{:cry:}
dumon
发表于 2024-5-28 23:31:49
第14集 矩阵按键
1.矩阵按键是什么?
相对于独立按键,每1个按键占用1个IO口,矩阵按键是将按键排列成矩阵排列的形式的按键排列。
课程里以P0口为例,P0.0-P0.3 4个IO为一组,P0.4-P0.7 4个IO为一组,可以组成4*4排列,通过8个IO口实现16个按键的输入。
具体怎么实现的呢,有些是说明行扫描法或列扫描法,执行逻辑为:
1.先将P0.4-P0.7输出为低电平,P0.0-P0.3为高电平,当某个按键按下时,检测P0.0-P0.3哪个为低电平,用异或记下这时的P0口的值;
2.反转,将P0.0-P0.3输出为低电平,P0.4-P0.7输出为高电平,当同样的按键按下时,检测P0.4-P0.7哪个为低电平,用异或记下这时的P0口的值;
3.将2次P0口的值相或得到新的组合值,根据组合值计算出这是哪个按键被按下了。
这里的本质还是独立按键的工作原理,因为不便于描述,这里还是附上手写笔记,方便大家更好理解。
总的原则,矩阵按键也是由独立按键演化而来的,只是其中一组在充当GND,矩阵按键就是用行列来确定哪个按键按下,所以会有一外键值表,我们可以根据键值表来确定是哪个按键被按下。
矩阵按键极大缓解了单片机IO口不够用的情况。
dumon
发表于 2024-5-28 23:37:51
dumon 发表于 2024-5-28 23:31
第14集 矩阵按键
1.矩阵按键是什么?
非常抱歉,在上传图片时没有提前预览,导致图片旋转了180度,不便于大家观看,已重新上传。
dumon
发表于 2024-5-29 23:38:20
矩阵键盘补充:
准双向口的含义是:即可以写入0或1,又可读取P口的状态,但有一点要注意的是两者不能同时进行。同一时间只能进行一个读或写操作。
怎么对IO口进行读操作:2种方法
1)使用“==”号
if(P0 == 0xC0),这里程序就会对P0口进行读操作,并与0xC0进行判定是否等于
2)用变量转存Pn口的高低电平,用(8位二进制或2位16进制)
u8 key_state;
key_state = P0;
我在这节课学习时也学了好久,但通过原理图的分析加深自己的理解,最终把矩阵键盘搞定了。
/**************************************************************************
“矩阵键盘:行列扫描得出按键键值,水平为列,垂直为行
先行写入低电平,列写入高电平,延时10ms后,再读取列电平状态,检测出哪列有低电平
再列写入低电平,行写入高电平,延时10ms后,再读取行电平状态,检测出哪行有低电平,
通过行列组合判断哪个按键按下。
这里相当于Arduino的digitalWrite和digitalRead,我想这就是准双向口的源来吧。
第1步:P0.0-P0.3写入低电平,P0.4-P0.7写入高电平,为1111 0000,延时10ms,读取P0.4-P0.7哪个为低电平,则确认列数,
如P0.7读取为0,0111 0000^1111 0000=1000 0000,异或的目的是与上次比较哪个P口状态有变。此次为P0.7口有变
第2步:P0.0-P0.3写入高电平,P0.4-P0.7写入低电平,为0000 1111,延时10ms,读取P0.0-P0.3哪个为低电平,则确认行数,
如P0.0读取为0,0000 1110^0000 1111=0000 0001,此次为P0.0口有变。
第3步:将行与列进行逻辑或组合,1000 0000|0000 0001=1000 0001=0x81,P0.0+P0.7的交点为KEY13,
下表为4x4矩阵键盘的键值
---行--------- P0.0 P0.1 P0.2 P0.3
| | | |
| | | | |
| P0.4--------KEY1-----KEY2----KEY3----KEY4---------
| 0x11 0x12 0x14 0x18
| | | | |
| P0.5--------KEY5-----KEY6----KEY7----KEY8---------
列 0x21 0x22 0x24 0x28
| | | | |
| P0.6--------KEY9----KEY10---KEY11----KEY12--------
| 0x41 0x42 0x44 0x48
| | | | |
| P0.7-------KEY13----KEY14---KEY15----KEY16--------
0x81 0x82 0x84 0x88
**************************************************************************/
#include <STC32G.H>
#include "comm/stc32_stc8_usb.h"
#define MAIN_Fosc 24000000L //定义主时钟
char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";
#define MatrixKEY P0 //定义P0为矩阵键盘
void delay_ms(u16 ms) //@24.000MHz
{
u16 i;
do{
i= MAIN_Fosc/6000;
while(--i);
} while(--ms);
}
u8 MatrixKEY_Read()
{
u8 key_State; //用于存储按键状态
u8 key_value; //用于存储按键键值
//第一步,P0.0-P0.3给低电平,P0.6-P0.7给高电平,如果检测到P0.6-P0.7为低电平,则确认哪一列按下
MatrixKEY = 0xC0; //“这里的MatrixKEY表示写入电平”1100 0000,
delay_ms(10); //假设P0.7为0
key_State = (MatrixKEY^0xC0); //“这里的MatrixKEY表示读取电平” 0100 0000~1100 0000=“1000 0000”
//第二步,P0.0-P0.3给高电平,P0.6-P0.7给低电平,如果检测到P0.0-P0.3为低电平,则确认哪一行按下
MatrixKEY = 0x0F; //0000 1111
delay_ms(10); //假设P0.0为0
key_State = key_State |(MatrixKEY^0x0F); //0000 1110~0000 1111= “0000 0001”,1000 0000|0000 0001=1000 0001
//第三步,行列组合,判断是哪个按键按下
switch (key_State)
{
case 0x41:
key_value = 1;
break;
case 0x42:
key_value = 2;
break;
case 0x44:
key_value = 3;
break;
case 0x48:
key_value = 4;
break;
case 0x81:
key_value = 5;
break;
case 0x82:
key_value = 6;
break;
case 0x84:
key_value = 7;
break;
case 0x88:
key_value = 8;
break;
default:key_value = 0;
break;
}
printf("现在按下的是%2d\r\n", key_value); //利用printf函数输出按键键值
return key_value;
}
void sys_init()
{
WTST = 0;//设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; //扩展寄存器(XFR)访问使能
CKCON = 0; //提高访问XRAM速度
P0M1 = 0x00; P0M0 = 0x00; //设置为准双向口
P1M1 = 0x00; P1M0 = 0x00; //设置为准双向口
P2M1 = 0x00; P2M0 = 0x00; //设置为准双向口
P3M1 = 0x00; P3M0 = 0x00; //设置为准双向口
P4M1 = 0x00; P4M0 = 0x00; //设置为准双向口
P5M1 = 0x00; P5M0 = 0x00; //设置为准双向口
P6M1 = 0x00; P6M0 = 0x00; //设置为准双向口
P7M1 = 0x00; P7M0 = 0x00; //设置为准双向口
//====== USB 初始化 ======
P3M0 &= ~0x03;
P3M1 |= 0x03;
IRC48MCR = 0x80;
while (!(IRC48MCR & 0x01));
}
void main()
{
sys_init();//系统初始化
usb_init();//USB CDC 接口配置
EA = 1; //打开总中断
while (1)
{
if(bUsbOutReady)
{
USB_SendData(UsbOutBuffer,64);//发送数据缓冲区,长度(接收数据原样返回, 用于测试)
usb_OUT_done(); //接收应答(固定格式)
}
MatrixKEY_Read();
}
}