yuyy1989
发表于 2023-5-7 21:43:06
本帖最后由 yuyy1989 于 2023-5-7 21:48 编辑
第9集,数码管的静态使用
数码管是用led组成的,通过控制不同位置的led的亮灭来显示图案
//没有数码管调试用,所以不知最终效果如何
#define KEY0 P32
#define KEY1 P33
#define BEEP P54
#define LEDSEG_COM_PIN0 P40 //共阳共阴控制脚
#define LEDSEG_COMON_TYPE 0//共阳共阴控制脚导通电平 0拉低导通 1拉高导通
#define LEDSEG_LED_PORT P2 //假设bit0-bi7与abcdefg小数点依次相连
#define LEDSEG_LEDON_TYPE 0 //led控制脚导通电平 0拉低导通 1拉高导通
/*
a
----
f| g |b
----
e| |c
---- .h
d
字符 hgfedcba16进制数值
0 00111111 0x3F
1 00000110 0x06
2 01011011 0x5B
3 01001111 0x4F
4 01100110 0x66
5 01101101 0x6D
6 01111101 0x7D
7 00000111 0x07
8 01111111 0x7F
9 01101111 0x6F
- 01000000 0x40
A 01110111 0x77
b 01111100 0x7C
c 01011000 0x58
C 00111001 0x39
d 01011110 0x5E
E 01111001 0x79
F 01110001 0x71
H 01110110 0x76
J 00011110 0x1E
L 00111000 0x38
o 01011100 0x5C
P 01110011 0x73
q 01100111 0x67
U 00111110 0x3E
t 01111000 0x78
r 00110011 0x33
将对应的数值写入寄存器即可显示相应符号
*/
const uint8_t ledsegnumdatas[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
uint8_t currentdat = 0;
uint8_t ledsegcount = 0;//0-9计数
uint8_t ledsegbeepcount = 0;//蜂鸣器鸣叫计数
//控制数码管亮灭
void yuyy_segled_onoff(uint8_t on)
{
if(on > 1)
{
on = 1;
}
#if(LEDSEG_COMON_TYPE == 0)
on = 1- on;
#endif
LEDSEG_COM_PIN0 = on;
}
//刷新数码管
void yuyy_segled_showdat(uint8_t dat)
{
#if(LEDSEG_LEDON_TYPE == 0)
dat = ~dat;
#endif
LEDSEG_LED_PORT = dat;
}
//控制小数点显示
void yuyy_segled_showdot(uint8_t show)
{
if(show)
{
currentdat |= 0x80;
}
else
{
currentdat &= 0x7F;
}
}
//显示字符,传入符号对应的数值
void yuyy_segled_shownumchar(uint8_t num)
{
currentdat &= 0x80;
currentdat |= num;
yuyy_segled_showdat(currentdat);
}
//显示0-9数字
void yuyy_segled_show09num(uint8_t num)
{
if(num < 10)
{
yuyy_segled_shownumchar(ledsegnumdatas);
}
}
//按下一次KEY0,数码管计数+1
void key0down_ledsegcount()
{
if(KEY0 == 0)
{
delay_ms(10);//去抖
if(KEY0 == 0)
{
if((keydown&0x01) == 0)
{
keydown |= 0x01;
ledsegcount++;
if(ledsegcount > 9)
{
ledsegcount = 0;
}
ledsegbeepcount = 0;
yuyy_segled_show09num(ledsegcount);
}
}
}
else
{
keydown &= 0xFE;
}
}
//按下一次KEY1,蜂鸣器按照数码管显示响
void key1down_ledsegcountbeep()
{
if(KEY1 == 0)
{
delay_ms(10);//去抖
if(KEY1 == 0)
{
if((keydown&0x02) == 0)
{
keydown |= 0x02;
ledsegbeepcount = ledsegcount;
}
}
}
else
{
keydown &= 0xFD;
}
if (ledsegbeepcount)//由while改为if减少阻塞时间
{
BEEP = 1;
delay_ms(150);
BEEP = 0;
delay_ms(150);
ledsegbeepcount--;
}
}
//在main函数进入while(1)前先点亮LED
yuyy_segled_onoff(1);
yuyy_segled_show09num(ledsegcount);
//在while(1)中调用
key0down_ledsegcount();
key1down_ledsegcountbeep();
yuyy1989
发表于 2023-5-9 08:57:58
本帖最后由 yuyy1989 于 2023-5-9 08:59 编辑
第10集,数码管的动态显示
多位数码管的同时显示是一位一位不停切换实现的,和视频的原理一样利用了人眼的视觉残留现象,最好保证整个数码管刷新耗时在20ms以内
没有试验箱也没有数码管,下面的代码没有经过验证
#define LEDSEG_NUM 8 //数码管位数
#define LEDSEG_COM_PORT P4 //共阳共阴控制端口,假设bit0-bi7控制1-8位
#define LEDSEG_COMON_TYPE 0//共阳共阴控制脚导通电平 0拉低导通 1拉高导通
#define LEDSEG_LED_PORT P2 //假设bit0-bi7与abcdefg小数点依次相连
#define LEDSEG_LEDON_TYPE 0 //led控制脚导通电平 0拉低导通 1拉高导通
#define BEEP P54
/*
a
----
f| g |b
----
e| |c
---- .h
d
字符 hgfedcba16进制数值
0 00111111 0x3F
1 00000110 0x06
2 01011011 0x5B
3 01001111 0x4F
4 01100110 0x66
5 01101101 0x6D
6 01111101 0x7D
7 00000111 0x07
8 01111111 0x7F
9 01101111 0x6F
- 01000000 0x40
A 01110111 0x77
b 01111100 0x7C
c 01011000 0x58
C 00111001 0x39
d 01011110 0x5E
E 01111001 0x79
F 01110001 0x71
H 01110110 0x76
J 00011110 0x1E
L 00111000 0x38
o 01011100 0x5C
P 01110011 0x73
q 01100111 0x67
U 00111110 0x3E
t 01111000 0x78
r 00110011 0x33
将对应的数值写入寄存器即可显示相应符号
*/
const uint8_t ledsegnumdatas[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
uint8_t currentdatas = {0};
uint8_t currentseg = 0;
uint16_t segcunter = 0;//计数0-9999 每10ms+1
uint8_t segtimer = 0;//0-10
uint8_t segkeycount = 0;//0初始状态 1计数 2停止
//选择数码管的一位0-7
void yuyy_segled_select(uint8_t seg)
{
uint8_t segdat = 0;
if (seg < 8)
{
segdat = 1<<seg;
#if(LEDSEG_COMON_TYPE == 0)
segdat = ~segdat;
#endif
LEDSEG_COM_PORT = segdat;
}
}
//数码管显示字符
void yuyy_segled_showdat(uint8_t dat)
{
#if(LEDSEG_LEDON_TYPE == 0)
dat = ~dat;
#endif
LEDSEG_LED_PORT = dat;
}
//刷新显示
void yuyy_segled_show()
{
if(currentseg > 7)
{
currentseg = 0;
}
yuyy_segled_select(currentseg);
yuyy_segled_showdat(currentdatas);
currentseg++;
}
//控制小数点显示,bit0-1每个bit代表一个小数点
void yuyy_segled_showdots(uint8_t dots)
{
uint8_t i=0;
while (i<8)
{
if(dots&0x01)
{
currentdatas |= 0x80;
}
else
{
currentdatas &= 0x7F;
}
dots= dots>>1;
i++;
}
}
//设置指定位置的字符,传入符号对应的数值
void yuyy_segled_shownumcharat(uint8_t num, uint8_t pos)
{
if(pos < 8)
{
currentdatas &= 0x80;
currentdatas |= num;
}
}
//设置指定位置的数字
void yuyy_segled_show09numat(uint8_t num, uint8_t pos)
{
if(num < 10)
{
yuyy_segled_shownumcharat(ledsegnumdatas,pos);
}
if(num == 0xFF)
{
yuyy_segled_shownumcharat(0,pos);
}
}
//显示4位数字 num 0000-9999 count数字位数 pos起始位置
void yuyy_segled_shwownums(uint16_t num,uint8_t count,uint8_t pos)
{
uint8_t i=0,d=0;
while (count<5 && i<count)
{
d = num % 10;
yuyy_segled_show09numat(d,pos+i);
num = num / 10;
i++;
}
}
//按下一次KEY0,开始计数,再按停止,再按重置
void key0down_ledsegcounter()
{
if(KEY0 == 0)
{
delay_ms(10);//去抖
if(KEY0 == 0)
{
if((keydown&0x01) == 0)
{
keydown |= 0x01;
if(segkeycount == 0)
{
//开始计数
segkeycount = 1;
}
else if(segkeycount == 1)
{
//停止计数
segkeycount = 2;
}
else
{
//重置
segkeycount = 0;
segcunter = 0;
yuyy_segled_shwownums(segcunter,4,0);
}
}
}
}
else
{
keydown &= 0xFE;
}
if(segkeycount == 1 && segtimer == 10)
{
if(segcunter < 9999)
{
segcunter++;
yuyy_segled_shwownums(segcunter,4,0);
}
segtimer = 0;
}
yuyy_segled_show();
delay_ms(1);
segtimer++;
}
//实战小练main部分
yuyy_segled_shwownums(1000,4,4);
yuyy_segled_shwownums(0,4,0);
yuyy_segled_showdots(0x44);
while(1)
{
key0down_ledsegcounter();
}
//课后练习简易时钟
uint16_t clockms = 0; //毫秒
uint8_t clocksec = 0; //秒
uint8_t clockmin = 0; //分
uint8_t clockhour = 0; //时
void clocktest()
{
if(clockms == 1000)
{
clocksec++;
clockms = 0;
yuyy_segled_shwownums(clocksec,2,0);
}
if(clocksec == 60)
{
clockmin++;
clocksec = 0;
yuyy_segled_shwownums(clocksec,2,0);
yuyy_segled_shwownums(clockmin,2,3);
}
if(clockmin == 60)
{
clockhour++;
clockmin = 0;
yuyy_segled_shwownums(clockmin,2,3);
yuyy_segled_shwownums(clockhour,2,6);
}
if(clockhour == 24)
{
clockhour = 0;
yuyy_segled_shwownums(clockhour,2,6);
}
if(clockhour == 0 && clockmin == 0 && clocksec == 30) // 00-00-30闹钟开始响
{
BEEP = 1;
}
if(clockhour == 0 && clockmin == 0 && clocksec == 33) // 00-00-33闹钟结束
{
BEEP = 0;
}
yuyy_segled_show();
delay_ms(1);
clockms++;
}
//课后练习的main部分
yuyy_segled_showdots(0x00);
yuyy_segled_shownumcharat(0x40,2);
yuyy_segled_shownumcharat(0x40,5);
yuyy_segled_shwownums(0,2,0);
yuyy_segled_shwownums(0,2,3);
yuyy_segled_shwownums(0,2,6);
while(1)
{
clocktest();
}
yuyy1989
发表于 2023-5-9 16:56:13
第11集,定时器的使用
介绍了中断的概念,主程序收到中断请求后暂停当前程序进入中断响应函数,中断响应函数处理完成后返回主程序继续执行,中断频率越低越好。
定时一定时间之后产生的中断是定时器中断。
定时器是定时器和计数器的统称。
1.设置为定时器时,可实现硬件计时,或者使程序每隔一固定时间完成一项操作。
2.设置为计数器时能够对脉冲进行计数。
3.替代长时间的delay,提高CPU的运行效率和处理速度,能及时响应某个事件
STC32G定时器相关寄存器
使用STC-ISP软件可以方便地生成定时器代码
关于中断函数后面的中断号,也可以在手册中查到
以下代码因为没有实验箱和数码管所以没有验证过
void Timer0_Init(void)
{
// Timer0初始化
TMOD = 0x00; //16位重载
TH0 = 0xF8; // = 65536 - (MAIN_Fosc*time)/T/(TM0PS+1) 其中T=1或12 TM0PS为定时器0时钟预分频寄存器默认0
TL0 = 0x30; //当前数据为24M下1ms的定时器配置
ET0 = 1; //开启中断
TR0 = 1; //运行
}
//针对按键开始停止计数的程序修改
//修改这个函数
void key0down_ledsegcounter()
{
if(KEY0 == 0)
{
delay_ms(10);//去抖
if(KEY0 == 0)
{
if((keydown&0x01) == 0)
{
keydown |= 0x01;
if(segkeycount == 0)
{
//开始计数
segkeycount = 1;
}
else if(segkeycount == 1)
{
//停止计数
segkeycount = 2;
}
else
{
//重置
segkeycount = 0;
segcunter = 0;
yuyy_segled_shwownums(segcunter,4,0);
}
}
}
}
else
{
keydown &= 0xFE;
}
}
void timer0_int(void) interrupt 1//1ms 中断函数
{
if(segkeycount == 1 && segtimer == 10)
{
if(segcunter < 9999)
{
segcunter++;
yuyy_segled_shwownums(segcunter,4,0);
}
segtimer = 0;
}
segtimer++;
yuyy_segled_show();
}
//main函数内
Timer0_Init();
EA = 1;
while(1)
{
key0down_ledsegcounter();
}
//针对简易时钟的修改
//修改这个函数
uint16_t clockms = 0; //毫秒
uint8_t clocksec = 0; //秒
uint8_t clockmin = 0; //分
uint8_t clockhour = 0; //时
uint8_t clockrun = 0; //1运行 0暂停
uint16_t beeptime = 0;
void clocktest()
{
if(clockms == 1000)
{
clocksec++;
clockms = 0;
yuyy_segled_shwownums(clocksec,2,0);
}
if(clocksec == 60)
{
clockmin++;
clocksec = 0;
yuyy_segled_shwownums(clocksec,2,0);
yuyy_segled_shwownums(clockmin,2,3);
}
if(clockmin == 60)
{
clockhour++;
clockmin = 0;
yuyy_segled_shwownums(clockmin,2,3);
yuyy_segled_shwownums(clockhour,2,6);
}
if(clockhour == 24)
{
clockhour = 0;
yuyy_segled_shwownums(clockhour,2,6);
}
if(clockhour == 0 && clockmin == 0 && clocksec == 30) // 00-00-30闹钟开始响
{
BEEP = 1;
beeptime = 3001;
}
if(beeptime > 0) // 00-00-33闹钟结束
{
beeptime--;
if(beeptime == 0)
{
BEEP = 0;
}
}
if(clockrun)
{
clockms++;
}
yuyy_segled_show();
}
//按下一次KEY0,时钟停止再按继续
void key0down_clockrunstop()
{
if(KEY0 == 0)
{
delay_ms(10);//去抖
if(KEY0 == 0)
{
if((keydown&0x01) == 0)
{
keydown |= 0x01;
if(clockrun)
{
clockrun = 0;
}
else
{
clockrun = 1;;
}
}
}
}
else
{
keydown &= 0xFE;
}
}
void timer0_int(void) interrupt 1//1ms 中断函数
{
clocktest();
}
//main函数内
Timer0_Init();
EA = 1;
while(1)
{
key0down_clockrunstop();
}
yuyy1989
发表于 2023-5-10 21:42:38
第12集,计数器的使用
对于输出信号带有电平变化的,想要计算个数就可以用到计数器
各定时器计数器模式外部中断IO
T0--P3.4
T1--P3.5
T2--P1.2
T3--P0.4/P0.0
T4--P0.6/P0.2
一个转速测量的例程,使用P2.0 P2.1 P2.2 P2.3分别输出500Hz 250Hz 100Hz 50Hz的脉冲,分别连接P3.5模拟不同转速,通过USB-HID打印转速
#define KEY0 P32
#define COUNTPERLOOP 10 //一圈10个计数
#define CHECKINTERVAL 2000 //检测周期
uint8_t speedunint = 0; //速度单位 0转每秒 1转每分钟
uint32_t motorspeed = 0;
uint32_t motorcounter = 0;
uint32_t time0counter = 0;
void Timer0_Init(void) //1毫秒@24.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x30; //设置定时初始值
TH0 = 0xF8; //设置定时初始值
TF0 = 0; //清除TF0标志
ET0 = 1; //开启中断
TR0 = 1; //定时器0开始计时
}
void timer0_int(void) interrupt 1//1ms 中断函数
{
P20 = !P20; //模拟500Hz的脉冲
if(time0counter % 2 == 0)
{
P21 = !P21; //模拟250Hz的脉冲
}
if(time0counter % 5 == 0)
{
P22 = !P22; //模拟100Hz的脉冲
}
if(time0counter % 10 == 0)
{
P23 = !P23; //模拟50Hz的脉冲
}
//上面4个IO与P3.5连接模拟编码器输入
if(time0counter < CHECKINTERVAL)
{
time0counter++;
}
else
{
time0counter = 0;
motorcounter |= ((TH1 << 8)&0xFF00) | TL1;
motorspeed = motorcounter/(CHECKINTERVAL/1000)/COUNTPERLOOP; //结果是转每秒
if(speedunint == 1)
{
motorspeed *= 60;
printf_hid("转速:%ld 转每分\r\n",motorspeed);
}
else
{
printf_hid("转速:%ld 转每秒\r\n",motorspeed);
}
motorspeed = 0;
motorcounter = 0;
TR1 = 0;
TH1 = 0x00;
TL1 = 0x00;
TR1 = 1; //测试时不先停一下不能重置T1计数
}
}
void Timer1counter_init(void)
{
// Timer1计数器初始化
TMOD |= 0x40; //计数器模式
TH1 = 0x00; //计数器初始值
TL1 = 0x00; //计数器初始值
TF1 = 0; //清除TF1标志
ET1 = 1; //开启中断
TR1 = 1; //运行
}
void timer1_int(void) interrupt 3
{
uint16_t h;
h = motorcounter>>16;
h += 1;
motorcounter &= 0x0000FFFF;
motorcounter |= ((h << 16)&0xFFFF0000);
}
//按键0按下切换转速单位
void key0down_changespeedunit()
{
if(KEY0 == 0)
{
delay_ms(10);//去抖
if(KEY0 == 0)
{
if((keydown&0x01) == 0)
{
keydown |= 0x01;
speedunint = 1-speedunint;
}
}
}
else
{
keydown &= 0xFE;
}
}
//main函数部分
Timer1counter_init();
Timer0_Init();
EA = 1; //打开总中断
P3PU = 0x20; //P3.5内部上拉
while(1)
{
key0down_changespeedunit();
}测试结果如下
P3.5连接P2.0
P3.5连接P2.1
P3.5连接P2.2
P3.5连接P2.3
yuyy1989
发表于 2023-5-11 09:21:29
第13集,简易多任务处理上
理清程序的逻辑思路,规范程序
修饰符extern用在变量或者函数的声明前,用来说明“此变量或函数是在别处定义的,要在此处引用”
注意extern修饰的变量不能赋初始值
模块化的编程,按照功能归类将相同的功能放到一组.h和.c文件中
重要的函数添加说明,使用keil的模板可以方便快捷的添加
keil默认的模板中可能没有我们需要的,我们可以自己添加,右键点击模板的空白处,选择配置模板
在弹出的窗口中点击新建
输入模板名称和内容后点击OK
如果新建的模板名称没出现在列表中关闭keil再重新打开
在右面的编辑区域将光标定位到要插入模板的地方,再双击模板名称即可插入模板中的内容
yuyy1989
发表于 2023-5-11 14:03:10
第13集,简易多任务处理中
善用Keil的搜索和替换功能,可以方便的修改变量或函数名称
使用分时复用的方法让LED和数码管显示不互相干扰
使用#define定义状态常量
yuyy1989
发表于 2023-5-11 19:20:24
第13集,简易多任务处理下
把按键功能封装起来,仿照写了个按键处理文件
yuyy_key.h
#ifndef __YUYY_KEY_H_
#define __YUYY_KEY_H_
#include "config.h"
#define KEYNUMS 4 //按键总数
#define KEYSCANINTERVAL 10//按键扫描间隔
#define DEBOUNCETIME 30 //去抖时间
#define LONGPRESSTIME 2000 //长按识别时间
#define KEYPORT P3 //按键io端口号
#define KEYIOSTART 2 //按键io起始值 屠龙刀的按键从P3.2开始
enum YUYY_KEYSTATE
{
KEY_UP = 0, //按键未按下
KEY_DOWN, //按键按下
KEY_SINGLECLICK, //单击按键 按下按键后在LONGPRESSTIME之前松开
KEY_LONGPRESS, //长按按键 按下按键的时间超过LONGPRESSTIME
KEY_LONGPRESSUP//长按后松开
};
//定义回调
typedef void (*yuyy_key_cb)(uint8_t keynum,enum YUYY_KEYSTATE state);
/**
* 10ms一次检查按键状态
*/
void yuyy_keyloop(void);
/**
* 设置回调函数
* 外部文件通过回调函数监控按键状态
*/
void yuyy_setkeycb(yuyy_key_cb cb);
#endifyuyy_key.c
#include "yuyy_key.h"
uint16_t keydowncount = {0};
yuyy_key_cb keycb;
void sendkeystate(uint8_t keynum,enum YUYY_KEYSTATE state)
{
if(keycb)
{
keycb(keynum,state);
}
}
void yuyy_keyloop(void)
{
uint8_t i = 0;
while (i<KEYNUMS)
{
if((~KEYPORT) & (1<<(i+KEYIOSTART)))
{
if(keydowncount < 0xFFFF)
{
keydowncount++;
}
if(keydowncount*KEYSCANINTERVAL == DEBOUNCETIME)
{
sendkeystate(i,KEY_DOWN);
}
if(keydowncount*KEYSCANINTERVAL == LONGPRESSTIME)
{
sendkeystate(i,KEY_LONGPRESS);
}
}
else
{
if(keydowncount*KEYSCANINTERVAL > DEBOUNCETIME)
{
if(keydowncount*KEYSCANINTERVAL < LONGPRESSTIME)
{
sendkeystate(i,KEY_SINGLECLICK);
}
else
{
sendkeystate(i,KEY_LONGPRESSUP);
}
}
keydowncount = 0;
}
i++;
}
}
void yuyy_setkeycb(yuyy_key_cb cb)
{
keycb = cb;
}main.c
#include "yuyy_key.h"
uint16_t time0counter = 0;
//使用USB-HID打印按键状态变化
void test_keystate_changed(uint8_t keynum,enum YUYY_KEYSTATE state)
{
switch (state)
{
case KEY_DOWN:
printf_hid("按键%d 按下\r\n",keynum);
break;
case KEY_SINGLECLICK:
printf_hid("按键%d 单击\r\n",keynum);
break;
case KEY_LONGPRESS:
printf_hid("按键%d 长按\r\n",keynum);
break;
case KEY_LONGPRESSUP:
printf_hid("按键%d 长按松开\r\n",keynum);
break;
default:
break;
}
}
void Timer0_Init(void) //1毫秒@24.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x30; //设置定时初始值
TH0 = 0xF8; //设置定时初始值
TF0 = 0; //清除TF0标志
ET0 = 1; //开启中断
TR0 = 1; //定时器0开始计时
}
void timer0_int(void) interrupt 1//1ms 中断函数
{
if(time0counter < 10)
{
time0counter++;
}
else
{
time0counter = 0;
yuyy_keyloop();
}
}
//main函数中(省略了其它初始化代码)
Timer0_Init();
yuyy_setkeycb(test_keystate_changed);
while(1)
{
}
yuyy1989
发表于 2023-5-12 09:05:31
第13集,简易多任务处理终
仿照视频封装了一下蜂鸣器和定时器功能
yuyy_beep.h
#ifndef __YUYY_BEEP_H_
#define __YUYY_BEEP_H_
#include "config.h"
#define BEEPPIN P54 //蜂鸣器控制io
#define BEEPON 0 //控制蜂鸣器开启的IO电平
/**
* 蜂鸣器开启指定时间
* ms10:0关闭 1-0xFFFE开启对应时间x10 0xFFFF一直开启
*/
void yuyy_beep(uint16_t ms10);
/**
* 10ms调用一次
*/
void yuyy_beep_loop();
#endifyuyy_beep.c
#include "yuyy_beep.h"
uint16_t beepontime = 0;
void yuyy_beep(uint16_t ms10)
{
beepontime = ms10;
if(ms10 == 0) //传入的是0立即关闭
{
BEEPPIN = !BEEPON;
}
}
void yuyy_beep_loop()
{
if(beepontime>0)
{
BEEPPIN = BEEPON;
if(beepontime < 0xFFFF)
{
beepontime--;
}
}
else
{
BEEPPIN = !BEEPON;
}
}yuyy_timer.h
#ifndef __YUYY_TIMER_H_
#define __YUYY_TIMER_H_
#include "config.h"
//定义回调
typedef void (*yuyy_timer0_cb)(void);
/**
* 初始化timer0,
* us:定时时间 1-32768 24M主频时
* cb:定时回调
* return:0成功 1失败
*/
uint8_t yuyy_timer0init(uint16_t us,yuyy_timer0_cb cb);
#endifyuyytimer.c
#include "yuyy_timer.h"
yuyy_timer0_cb t0cb;
uint8_t yuyy_timer0init(uint16_t us,yuyy_timer0_cb cb)
{
uint32_t thl = (MAIN_Fosc/1000000*us)/12/(TM0PS+1);
if(thl > 65535)
{
return 1;
}
t0cb = cb;
thl = 65536 - thl;
TMOD &= 0xF0; //设置定时器模式
T0x12 = 0; //12T模式
TL0 = thl&0xFF; //设置定时初始值
TH0 = (thl>>8)&0xFF; //设置定时初始值
TF0 = 0; //清除TF0标志
ET0 = 1; //开启中断
TR0 = 1; //定时器0开始计时
return 0;
}
void timer0_int(void) interrupt 1//timer0中断函数
{
if(t0cb)
{
t0cb();
}
}测试
main.c
void test_timer0_cb(void)
{
static uint32_t timer0counter = 0;
if(timer0counter < 100)
{
timer0counter++;
}
else
{
P20 = !P20;
yuyy_beep(10);
timer0counter = 0;
}
yuyy_beep_loop();
}
//main函数(省略了其它初始化代码)
yuyy_timer0init(10000,test_timer0_cb); //10ms定时
EA = 1; //打开总中断
while(1)
{
}编译烧录后P20上的led每1秒切换一次状态,蜂鸣器每1秒响100ms
yuyy1989
发表于 2023-5-14 12:41:11
本帖最后由 yuyy1989 于 2023-5-14 14:17 编辑
第14集,矩阵按键
在按键数量较多时,为了减少IO口的占用,将按键排列成矩阵排列的形式的按键阵列我们称位矩阵按键。使用扫描法识别按键。
按键识别原理:端口默认为高电平,实时读取到引脚为低电平是表示按下。
第一步:现将 P0.0-P0.3 输出低电平,P0.6-P0.7 输出高电平,如果有按键按下,按下的那一列的IO就会变成低电平,就可以判断出哪一列按下了。
第二步:现将 P0.0-P0.3 输出高电平,P0.6-P0.7 输出低电平,如果有按键按下,按不的那一行的IO就会变成低电平,就可以判断出哪一行按下了。
第三步:行列组合就能判断哪个按键被按下了
不确定计算优先级最好加上括号
矩阵按键功能实现,和上节课的按键功能整合到一起了
yuyy_key.h
#ifndef __YUYY_KEY_H_
#define __YUYY_KEY_H_
#include "config.h"
//普通按键
#define KEYNUMS 4 //按键总数
#define KEYSCANINTERVAL 10//按键扫描间隔
#define DEBOUNCETIME 30 //去抖时间
#define LONGPRESSTIME 2000 //长按识别时间
#define KEYPORT P3 //按键io端口号
#define KEYIOSTART 2 //按键io起始值 屠龙刀的按键从P3.2开始
#define KEYDOWNLEV 0 //按键按下的io电平
//矩阵按键
#define MATRIXKEYCOLS 4 //按键列数
#define MATRIXKEYROWS 2 //按键行数
#define MATRIXKEYNUMS MATRIXKEYCOLS*MATRIXKEYROWS //矩阵按键总数
/**
*按键分布示例
* P00 P01 P02 P03
* P060 1 2 3
* P074 5 6 7
*/
#define MATRIXKEYROW0PIN P06 //第0行IO
#define MATRIXKEYROW1PIN P07 //第1行IO
#define MATRIXKEYCOL0PIN P00 //第0列IO
#define MATRIXKEYCOL1PIN P01 //第1列IO
#define MATRIXKEYCOL2PIN P02 //第2列IO
#define MATRIXKEYCOL3PIN P03 //第3列IO
enum YUYY_KEYSTATE
{
KEY_UP = 0, //按键未按下
KEY_DOWN, //按键按下
KEY_SINGLECLICK, //单击按键 按下按键后在LONGPRESSTIME之前松开
KEY_LONGPRESS, //长按按键 按下按键的时间超过LONGPRESSTIME
KEY_LONGPRESSUP//长按后松开
};
//定义回调
typedef void (*yuyy_key_cb)(uint8_t keynum,enum YUYY_KEYSTATE state);
/**
* 10ms一次检查按键状态
*/
void yuyy_keyloop(void);
/**
* 设置回调函数
* 外部文件通过回调函数监控按键状态
*/
void yuyy_setkeycb(yuyy_key_cb cb);
#endifyuyy_key.c
#include "yuyy_key.h"
#if(KEYNUMS|MATRIXKEYNUMS)
uint16_t keydowncount = {0};
#else
uint16_t keydowncount = {0};
#endif
yuyy_key_cb keycb;
void sendkeystate(uint8_t keynum,enum YUYY_KEYSTATE state)
{
if(keycb)
{
keycb(keynum,state);
}
}
void yuyy_refreshkey(uint8_t key,uint8_t down)
{
if(down)
{
if(keydowncount < 0xFFFF)
{
keydowncount++;
}
if(keydowncount*KEYSCANINTERVAL == DEBOUNCETIME)
{
sendkeystate(key,KEY_DOWN);
}
if(keydowncount*KEYSCANINTERVAL == LONGPRESSTIME)
{
sendkeystate(key,KEY_LONGPRESS);
}
}
else
{
if(keydowncount*KEYSCANINTERVAL > DEBOUNCETIME)
{
if(keydowncount*KEYSCANINTERVAL < LONGPRESSTIME)
{
sendkeystate(key,KEY_SINGLECLICK);
}
else
{
sendkeystate(key,KEY_LONGPRESSUP);
}
}
keydowncount = 0;
}
}
void matrixkeydelay(void) //根据之前的毫秒延时函数计算,在24M主频时延时大约10us
{
uint8_t i = 60;
while (--i);
}
void yuyy_keyloop(void)
{
uint8_t key = 0;
#if(MATRIXKEYNUMS)
uint8_t i,j,col = 0,row = 0;
#endif
while (key<KEYNUMS)
{
if(((KEYPORT >> (key+KEYIOSTART)) & 0x01) == KEYDOWNLEV)
{
yuyy_refreshkey(key,1);
}
else
{
yuyy_refreshkey(key,0);
}
key++;
}
#if(MATRIXKEYNUMS)
MATRIXKEYROW0PIN = 1;
MATRIXKEYROW1PIN = 1;
MATRIXKEYCOL0PIN = 0;
MATRIXKEYCOL1PIN = 0;
MATRIXKEYCOL2PIN = 0;
MATRIXKEYCOL3PIN = 0;
matrixkeydelay();
if (MATRIXKEYROW0PIN == 0)
{
row |= 0x01;
}
if (MATRIXKEYROW1PIN == 0)
{
row |= 0x02;
}
MATRIXKEYROW0PIN = 0;
MATRIXKEYROW1PIN = 0;
MATRIXKEYCOL0PIN = 1;
MATRIXKEYCOL1PIN = 1;
MATRIXKEYCOL2PIN = 1;
MATRIXKEYCOL3PIN = 1;
matrixkeydelay();
if (MATRIXKEYCOL0PIN == 0)
{
col |= 0x01;
}
if (MATRIXKEYCOL1PIN == 0)
{
col |= 0x02;
}
if (MATRIXKEYCOL2PIN == 0)
{
col |= 0x04;
}
if (MATRIXKEYCOL3PIN == 0)
{
col |= 0x08;
}
MATRIXKEYROW0PIN = 1;
MATRIXKEYROW1PIN = 1;
MATRIXKEYCOL0PIN = 1;
MATRIXKEYCOL1PIN = 1;
MATRIXKEYCOL2PIN = 1;
MATRIXKEYCOL3PIN = 1;
for(i=0;i<MATRIXKEYROWS;i++)
{
for (j = 0; j < MATRIXKEYCOLS; j++)
{
if((row&(1<<i)) && (col&(1<<j)))
{
yuyy_refreshkey(key,1);
}
else
{
yuyy_refreshkey(key,0);
}
key++;
}
}
#endif
}
void yuyy_setkeycb(yuyy_key_cb cb)
{
keycb = cb;
}main.c
void test_keystate_changed(uint8_t keynum,enum YUYY_KEYSTATE state)
{
switch (state)
{
case KEY_DOWN:
printf_hid("按键%d 按下\r\n",keynum);
break;
case KEY_SINGLECLICK:
printf_hid("按键%d 单击\r\n",keynum);
yuyy_beep(5);
break;
case KEY_LONGPRESS:
printf_hid("按键%d 长按\r\n",keynum);
break;
case KEY_LONGPRESSUP:
printf_hid("按键%d 长按松开\r\n",keynum);
break;
default:
break;
}
}
void test_timer0_cb(void)
{
yuyy_keyloop();
}
//main函数中(省略了其它初始化)
yuyy_timer0init(10000,test_timer0_cb); //10ms定时
EA = 1; //打开总中断
yuyy_setkeycb(test_keystate_changed);
while(1)
{
}
yuyy1989
发表于 2023-5-14 14:46:16
第14集,矩阵按键
密码锁示例,增加了退格和清空已输入密码的功能
#include "config.h"
#include "string.h"
//已经写过的直接调用
#include "yuyy_key.h"
#include "yuyy_beep.h"
#include "yuyy_timer.h"
#define LED0 P35
//USB调试及复位所需定义
char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#"; //设置自动复位到ISP区的用户接口命令
const uint8_t ledsegcodes[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x40};
const uint8_t password[] = {1,2,3,4,5,6,7,8}; //开锁密码
uint8_t currentpw; //当前密码
uint8_t currentpwindex = 0;
uint16_t timetoclose = 0;
uint16_t timetoledsegoff = 0;
void test_show(void)//显示,没有数码管使用虚拟数码管代替
{
uint8_t segdatas={0};
uint8_t i;
for ( i = 0; i < 8; i++)
{
segdatas = ledsegcodes];
}
SEG7_ShowCode(segdatas);
}
void test_keystate_changed(uint8_t keynum,enum YUYY_KEYSTATE state)
{
switch (state)
{
case KEY_DOWN:
printf_hid("按键%d 按下\r\n",keynum);
break;
case KEY_SINGLECLICK:
printf_hid("按键%d 单击\r\n",keynum);
yuyy_beep(2);
if(keynum == 0) //内部开锁
{
LED0 = 0;
timetoclose = 500;
}
else if(keynum == 1) //后退一位
{
if(currentpwindex>0)
{
currentpwindex--;
}
currentpw = 10;
}
else if(keynum == 2) //清空已输入密码
{
memset(currentpw,10,8);
currentpwindex = 0;
}
else if(keynum > 3)
{
currentpw = keynum - 3;
currentpwindex++;
if(currentpwindex == 8)
{
if(memcmp(currentpw,password,8) == 0) //密码正确
{
LED0 = 0;
timetoclose = 500;
}
else
{
LED0 = 1;
yuyy_beep(200);
}
memset(currentpw,10,8);
currentpwindex = 0;
}
}
test_show();
timetoledsegoff = 1000;
break;
case KEY_LONGPRESS:
printf_hid("按键%d 长按\r\n",keynum);
break;
case KEY_LONGPRESSUP:
printf_hid("按键%d 长按松开\r\n",keynum);
break;
default:
break;
}
}
void test_timer0_cb(void)
{
yuyy_beep_loop();
yuyy_keyloop();
if(timetoclose > 0)
{
timetoclose--;
if(timetoclose == 0)
{
LED0 = 1;
}
}
if(timetoledsegoff > 0)
{
timetoledsegoff--;
if(timetoledsegoff == 0)
{
uint8_t segdatas={0};
memset(currentpw,10,8);
currentpwindex = 0;
SEG7_ShowCode(segdatas);
}
}
}
/******************** 主函数 **************************/
void main(void)
{
WTST = 0;//设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; //扩展寄存器(XFR)访问使能
CKCON = 0; //提高访问XRAM速度
RSTFLAG |= 0x04; //设置硬件复位后需要检测P3.2的状态选择运行区域,否则硬件复位后进入USB下载模式
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));
usb_init();
//-------------------------
yuyy_setkeycb(test_keystate_changed);
yuyy_timer0init(10000,test_timer0_cb); //10ms定时
EUSB = 1; //IE2相关的中断位操作使能后,需要重新设置EUSB
EA = 1; //打开总中断
LED0 = 1;
printf_hid("STC32G 测试程序\r\n");
memset(currentpw,10,8);
test_show();
while(1)
{
}
}