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)
    {
    }
}

页: 1 [2] 3 4
查看完整版本: 冲哥32位8051视频学习日记-已看到27集-实验箱到了