sujingliang 发表于 2025-1-1 14:26:30

《Ai8051U教学视频》学习心得

开篇从1月1日开始


热爱电子技术的人大抵是热爱学习的。做为一个从小就对电子有些许喜好而从事的工作与电子无关的人,应该只能被定义为电子爱好者。那是23年,也许是为了使生活有更多的期许,也许是儿时梦想的延续,开始自学STC单片机。在一次不经意打开STC-ISP准备下载程序的时候,我偶然间发现了STC官方赠送的屠龙刀开发板活动。免费获得STC32G12K128,这对于才刚刚完成STC89C52点灯的老初学者来说诱惑实在太大。但是我既不是学生,又不是从业人员,如果贸然申请会被通过吗?不会是被人歧视吧。尽管有种种顾虑,但还是鼓起勇气联系了STC客服经理。出乎意料的是,客服经理竟然非常友好地通过了我的申请。那一刻,我深刻感受到了电子行业人的谦逊、好学和乐于助人。


随着学习的深入,我逐渐意识到,电子工程师的世界是一个充满挑战与机遇的天地。他们最关注的是自身技术的进步,最开心的事情是解决一个困扰自己多时的问题。在这个领域里,没有人会因为你的身份或背景而轻视你,只要你愿意学习、愿意分享,你就会受到大家的尊重和欢迎。

当得知《Ai8051U教学视频》这一优质学习资源时,我毫不犹豫地加入了学习计划。这套视频不仅内容详实、讲解清晰,而且还赠送实验箱。这怎么能错过。


2025年伊始,开始新的学习篇章,开贴记之。期待与坛友共同学习,共同进步,也鞭策自己可以坚持下去。

目录
1.《第三集 点亮第一颗LED》学习

2.《第四集 USB不停机下载》学习

3.《第六集 I/O输入输出》学习

4.《第七集 定时器中断》学习

5. 《第八集 定时器周期性调度任务》学习

6.《第九集 数码管》学习

7.《第十集 虚拟键盘LED和数码管》学习

8《第十一集 矩阵按键》学习







sujingliang 发表于 2025-1-1 15:06:41

学习单片机必须要上手,所以从点灯开始
《第三集 点亮第一颗LED》学习



为了点亮一个LED,我们需要做什么?
1、从硬件上确定使用哪个IO
2、配置IO
3、为IO设置电平、延时程序


一、硬件上确定使用哪个IO
根据开发板实际情况,查看管脚定义,确认使用的LED已经与之相邻的IO。我使用的P46。


二、配置IO
对于89C51入门级的51单片机来说,是不用配置IO的,可以直接使用。因为系统已经缺省做好了配置。
但是Ai8051U需要先做配置再使用,这部分涉及寄存器的位操作,幸好STC-ISP提供了IO配置工具可以勾选一下就获得配置程序:

获得的程序片段:
    P4M0 = 0x40; P4M1 = 0x00;

三、点灯之旅
1、延时函数
用STC-ISP提供的软件延时计算器生成延时1ms函数

生成的函数,及写个延时ms函数:
void Delay1ms(void)        //@24.000MHz
{
        unsigned long edata i;

        _nop_();
        _nop_();
        _nop_();
        i = 5998UL;
        while (i) i--;
}

void delay_ms(unsigned short ms)
{
        unsigned short i;
        for(i=0;i<ms;i++) Delay1ms();
}

2、main函数
void main(void)
{
          P4M0 = 0x40; P4M1 = 0x00;
       
        while(1)
        {
                P46=1;
                delay_ms(1000);
                P46=0;
                delay_ms(1000);
        }
}P46=1;//熄灭

P46=0;//低电平点灯


3、包含头文件
#include "stc8051u.h"//我手头的是STC8051U
#include "INTRINS.h"//_nop_()函数需要


四、运行效果




sujingliang 发表于 2025-1-1 19:18:43

《第四集 USB不停机下载》学习


STC单片机需要复位才能下载程序:
STC单片机在启动时,会首先执行一段出厂时固化的程序。这段程序的主要作用是检测串口是否有下载程序的需求。如果没有下载需求,单片机将继续执行内部的用户程序。这就是为什么在每次下载程序时需要进行复位。
一般STC单片机具有以下几种复位方式可以选择,每一种复位都可以用来触发下载程序。
1、上电复位
2、低压复位(掉电复位)
3、复位脚复位(RST)
4、看门狗复位
5、软件复位(寄存器IAP_CONTR的SWRST位)

IAP_CONTR = 0x60;   //触发软件复位,从ISP开始执行

但是随着单片机3.3v工作电压的普及,导致部分单片机可以通过串口供电而使复位变得困难;还有一些单片机取消了RST复位引脚,从而导致复位的手段越来越少,复位可靠性也较低,传统的下载方式不断接收挑战。
于是不停机下载应运而生。个人理解不停机下载主要是利用软件复位,在不手动接触开发板的情况下,通过各种手段发出软件复位命令,如串口、USB。

因为Ai8051U具有USB功能,可以将USB配置为USB CDC或USB HID进行下载。而CDC可以理解为串口,所以本质上就是ISP通过串口发送了”@STCISP#"指令,单片机串口接收处理函数判断接收成功后,触发软件复位,进而带动程序下载。

教程中给出的程序如下:

#include "ai8051u.h"                        //调用头文件
#include "stc32_stc8_usb.h"                //调用头文件


char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";

void main(void)
{
    P_SW2 |= 0x80;                //B7位写1,使能访问XFR
      
    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_init();                                     //USB CDC 接口配置

    IE2 |= 0x80;                                    //使能USB中断
    EA = 1;                                                                                        //IE |= 0X80;
      
      while (DeviceState != DEVSTATE_CONFIGURED);   //等待USB完成配置
      
      while(1)
      {
               
      if (bUsbOutReady)
      {
            USB_SendData(UsbOutBuffer,OutNumber);   //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
            
            usb_OUT_done();
      }
               
                P40 = 0;      //P40端口输出0V
                P00 = 0;      //P00端口输出0V
                P02 = 0;      //P02端口输出0V
                //P01 = 0;      //P01端口输出0V
                //
      }
}

char *USER_STCISPCMD = "@STCISP#";是可以修改的,对应STC-ISP如下位置也需要修改:





关于USB的驱动和不停机处理程序以静态库方式提供:


当然这个程序还提供串口接收转发的功能,从串口工具上发送什么内容,就接收到什么内容:




关于“当目标文件变化时自动装载并发送下载命令”,看个人喜好吧,因为并不是每次编译都要下载,有的时候会在编译的时候发现逻辑问题,可能不想直接下载。






sujingliang 发表于 2025-1-1 20:17:11

《第六集 I/O输入输出》学习




点灯和按键是单片机永恒的两大主题,IO输入输出是实现这个主题的具体实现方式

一、先定义一下用到的IO
#define KEY1 P11
#define KEY2 P12
#define KEY3 P13

#define LED2 P46
#define LED3 P14
#define LED4 P20

二、IO初始化
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00; 都初始化为准双向口。

三、按键轮询控制LED翻转
while(1)
{
      if(KEY1==0)
   {
          delay_ms(10);
          if(KEY1==0)
             LED2=~LED2;
             while(KEY1==0);
      }
      if(KEY2==0)
      {
            delay_ms(10);
            if(KEY2==0)
                LED3=~LED3;
                while(KEY2==0);
      }
      if(KEY3==0)
      {
             delay_ms(10);
             if(KEY3==0)
               LED4=~LED4;
            while(KEY3==0);
       }按键消抖处理:判断低电平、延时、判断低电平、低电平循环、高电平退出。
四、实验效果



五、简单实现下按一键亮一灯

if(KEY1==0)
                {
                        delay_ms(10);
                        if(KEY1==0)
                        {
                                ledcount++;
                                if(ledcount>3){
                                        ledcount=0;

                                }
                               
                                if(ledcount==1)LED2=0;
                                if(ledcount==2)LED3=0;
                                if(ledcount==3)LED4=0;
                                if(ledcount==0){LED2=1;LED3=1;LED4=1;}
                        }
                        while(KEY1==0);
                }





wlhet 发表于 2025-1-1 22:29:43

不错,自己焊接的板子?

sujingliang 发表于 2025-1-2 12:45:12

《第七集 定时器中断》学习


为了实现练习,需要进一步实现串口驱动(我没有采用USB CDC方式,所以需要单独实现)
一、串口2驱动
本来想用AIapp-ISP中的串口波特率工具生成代码,但是怎么配置出的代码运行起来都是乱码,还是通过从例程中找了一段代码解决
1、主时钟和波特率定义
#define MAIN_Fosc      24000000L   //定义主时钟
#define Baudrate2   (65536 - MAIN_Fosc / 115200 / 4)2、UART2初始化
void SetTimer2Baudraye(u32 dat)
{
    T2R = 0;                //Timer stop
    T2_CT = 0;      //Timer2 set As Timer
    T2x12 = 1;      //Timer2 set as 1T mode
    T2H = (u8)(dat / 256);
    T2L = (u8)(dat % 256);
    ET2 = 0;    //禁止中断
    T2R = 1;                //Timer run enable
}

void UART2_config(u8 brt)    // 选择波特率, 2: 使用Timer2做波特率, 其它值: 无效.
{
    if(brt == 2)
    {
      SetTimer2Baudraye(Baudrate2);

      S2CFG |= 0x01;   //使用串口2时,W1位必需设置为1,否则可能会产生不可预期的错误
      S2CON = (S2CON & 0x3f) | 0x40;    //UART2模式, 0x00: 同步移位输出, 0x40: 8位数据,可变波特率, 0x80: 9位数据,固定波特率, 0xc0: 9位数据,可变波特率
      ES2   = 1;         //允许中断
      S2REN = 1;         //允许接收
      S2_S= 0;         //UART2 switch to: 0: P1.2 P1.3,1: P4.2 P4.3

      B_TX2_Busy = 0;
    }
}波特率:115200;串口引脚复用:P1.2\P1.3

3、串口中断
void Uart2_Isr(void) interrupt 8
{
      if (S2CON & 0x02)      //检测串口2发送中断
      {
                S2CON &= ~0x02;      //清除串口2发送中断请求位
               B_TX2_Busy = 0;
      }
      if (S2CON & 0x01)      //检测串口2接收中断
      {
                S2CON &= ~0x01;      //清除串口2接收中断请求位
      }
}UART2中断向量为8,这个可以从头文件中查到:




4、printf重定向

char putchar(char c)
{
      S2BUF = c;
B_TX2_Busy = 1;
while(B_TX2_Busy);
      return c;
}

二、定时器驱动

1、TIMER0初始化
void Timer0_Init(void)                //500毫秒@24.000MHz
{
      TM0PS = 0xB7;                        //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
      AUXR |= 0x80;                        //定时器时钟1T模式
      TMOD &= 0xF0;                        //设置定时器模式
      TL0 = 0x3F;                              //设置定时初始值
      TH0 = 0x01;                              //设置定时初始值
      TF0 = 0;                              //清除TF0标志
      TR0 = 1;                              //定时器0开始计时
      ET0 = 1;                              //使能定时器0中断
}2、TIMER0中断处理
void Timer0_Isr(void) interrupt 1
{
      out_put_flag=1;
      Timer0_count++;
}

三、main函数
Timer0_Init();
      UART2_config(2);
      EA=1;
      while(1)
      {
               
                if(out_put_flag==1)
                {
                        out_put_flag=0;
                        printf("定时器500ms触发一次串口输出,中断触发次数:%d\r\n",Timer0_count);
                }

四、运行

sujingliang 发表于 2025-1-2 15:58:23

《第八集 定时器周期性调度任务》学习


不得不为了这一集拍案叫绝,也许我们都使用过实时操作系统,freertos、rethread、调度器等等,但真正愿意深入了解原理的并不多,本集看完有豁然开朗的感觉。
调度任务可以理解为将MCU的时间进行分时处理,对于优先级高的任务,优先执行。这样使每个任务都可以被mcu执行,而又不独占系统。这样程序也优雅起来了。

一、任务注册
typedef struct
{
      u8 Run;               //任务状态:Run/Stop
      u16 TIMCount;         //定时计数器
      u16 TRITime;          //重载计数器
      void (*TaskHook) (void); //任务函数
} TASK_COMPONENTS; 首先定义了一个任务结构体,用于记录任务的状态;执行倒计时(定时);倒计时重新设定值(可以理解为优先级,重载计数器越小优先权越高);任务函数,这是一个函数指针,根据任务声明执行不同的任务函数。

static TASK_COMPONENTS Task_Comps[]=
{
//状态计数周期函数
      
{0, 300,   300,   LED0_Blink},      /* task 1 Period: 300ms */
{0, 600,   600,   LED1_Blink},      /* task 1 Period: 600ms */
{0, 900,   900,   LED2_Blink},      /* task 1 Period: 600ms */
{0, 10,    10,    KEY_Task},      /* task 1 Period: 600ms */         
};定义任务数组,可以根据需要添加。这里注册了4个任务。

二、轮询调度
void Task_Marks_Handler_Callback(void)
{
    u8 i;
    for(i=0; i<Tasks_Max; i++)
    {
      if(Task_Comps.TIMCount)      /* If the time is not 0 */
      {
            Task_Comps.TIMCount--;   /* Time counter decrement */
            if(Task_Comps.TIMCount == 0) /* If time arrives */
            {
                /*Resume the timer value and try again */
                Task_Comps.TIMCount = Task_Comps.TRITime;
                Task_Comps.Run = 1;      /* The task can be run */
            }
      }
    }
}此函数会在定时器中断回调中调用,依次减少每个任务的定时倒计时,当倒计时到0时,设置任务run状态为1。

三、调度执行函数
此函数需要在main函数while(1)中调用,当任何一个任务处于run==1状态,执行任务中的注册函数
void Task_Pro_Handler_Callback(void)
{
    u8 i;
    for(i=0; i<Tasks_Max; i++)
    {
      if(Task_Comps.Run) /* If task can be run */
      {
            Task_Comps.Run = 0;      /* Flag clear 0 */
            Task_Comps.TaskHook();   /* Run task */
      }
    }
}

四、main

void main(void)
{
      Sys_init();                                                                              //系统初始化
      usb_init();                                     //USB CDC 接口配置

    IE2 |= 0x80;                                    //使能USB中断
      Timer0_Init();                                                                        //定时器初始化
    EA = 1;                                                                                        //IE |= 0X80;
      
      P40 = 0;
      
      while (DeviceState != DEVSTATE_CONFIGURED);   //等待USB完成配置
      
      while(1)
      {
               
      if (bUsbOutReady)                                                      //如果接收到了数据
      {
            //USB_SendData(UsbOutBuffer,OutNumber);   //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
                        
            usb_OUT_done();                                                      //
      }
                Task_Pro_Handler_Callback();                              //执行功能函数

      }
}



void Timer0_Isr(void) interrupt 1                //1MS执行一次
{

      Task_Marks_Handler_Callback();                                        //系统计时

}

至此,以后增加功能就是增加一个TASK。




sujingliang 发表于 2025-1-2 21:37:26

《第九集 数码管》学习

因为手头没有595驱动的数码管,用595驱动的8x8点阵屏代替
课后练习完成如下,不知如何@冲哥




一、倒计时任务
void wash_Task(void)
{
      if(washing_machine_status==2)
      {
                washing_machine_times--;                //倒计时显示
                if(washing_machine_times==0) washing_machine_status=0;
      }
}二、显示任务

void matrix_Task(void)
{
      //show_matrix8x8(&zimo);
      
      
      clear_matrix8x8();
      if(washing_machine_status==0){
                set_matrix8x8(0,1);                //设置显示1,清洗模式
      }else if(washing_machine_status==1){
                if(washing_machine_mode!=0) set_matrix8x8(4,washing_machine_mode);      //设置显示模式选择
      }else if(washing_machine_status==2){
                set_matrix8x8(4,washing_machine_times%10);                //设置显示时间倒计时
                set_matrix8x8(0,washing_machine_times/10);
      }
      show_matrix8x8(matrix1);                //显示
}

三、key任务

void KEY_Task(void)
{
      
      if(KEY1==0)
                {
                        delay_ms(10);
                        if(KEY1==0){
                              LED2=~LED2;
                              washing_machine_status=0;
                        }
                        while(KEY1==0);
                }
               
                if(KEY2==0)
                {
                        delay_ms(10);
                        if(KEY2==0){
                              LED3=~LED3;
                              washing_machine_status=1;                //mode选择
                              washing_machine_mode++;
                              if(washing_machine_mode>5)washing_machine_mode=1;
                        }
                        while(KEY2==0);
                }
               
                if(KEY3==0)
                {
                        delay_ms(10);
                        if(KEY3==0){
                              LED4=~LED4;
                              washing_machine_times=washing_machine_times_by_mode;
                              washing_machine_status=2;                //确认开始清洗
                        }
                        while(KEY3==0);
                }
      
}
四、任务注册

static TASK_COMPONENTS Task_Comps[]=
{
//状态计数周期函数
{0, 500,   500, wash_Task},
{0, 10,    10,    KEY_Task},      /* task 1 Period: 600ms */
{0, 200,   200,    matrix_Task},         
};

文字很少,相信代码自己会说话

sujingliang 发表于 2025-1-3 09:45:08

《第十集 虚拟键盘LED和数码管》学习



虚拟键盘LED和数码管是Aiapp-ISP提供的功能,可以通过如下菜单打开。



工具很贴心提供了帮助信息



一、实验一:通过串口工具发送数码管协议指令显示数字
1、注册一个CDC_Task任务用于处理串口收发
static TASK_COMPONENTS Task_Comps[]=
{
//状态计数周期函数
{0, 200,   200, CDC_Task},
{0, 500,   500, wash_Task},
{0, 10,    10,    KEY_Task},      /* task 1 Period: 600ms */
{0, 200,   200,    matrix_Task},         
};2、CDC_Task任务处理函数
void CDC_Task(void)
{
      if (bUsbOutReady)                                                      //如果接收到了数据
{
      USB_SendData(UsbOutBuffer,OutNumber);   //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
            usb_OUT_done();                                                      //
}
}
将USB配置为CDC虚拟串口,串口在收到数据后,原路返回。

3、按照数码管串口数据协议发送数据


功能1:在数码管上显示字符串
命令格式:37H 53H 45H 47H 53H 00H 00H 00H s1 s2 s3 s4 ...
命令说明:

第1~4字节:命令头
第5字节:功能选择(53H显示字符串)
第6~8字节:保留
第9~n字节:字符的ASCII码,字符串必须以‘\0’结尾
示例:发送37H 53H 45H 47H 53H 00H 00H 00H 31H 32H 2EH 33H 00H

在数码管上会显示“12.3”
库函数声明:int SEG7_ShowString(const char *fmt, ...);
库函数调用:SEG7_ShowString("%08lx", 0x1234abcdL);


1)通过串口工具先MCU发送指令37H 53H 45H 47H 53H 00H 00H 00H 31H 32H 2EH 33H 00H,
2)MCU指令数据后,原路返回
3)窗口工具在收到指令数据后,判定为虚拟数码管驱动指令,解析后在虚拟数码管上显示12.3。

二、实验二:密码锁

完成如下:

491


1、任务注册
static TASK_COMPONENTS Task_Comps[]=
{
//状态计数周期函数
{0, 200,   200, CDC_Task},
{0, 500,   500, SEG7_Task},
{0, 10,    10,    KEY_Task},      /* task 1 Period: 600ms */
};

2、CDC_Task负责串口接收虚拟键盘指令
void CDC_Task(void)
{
        if (bUsbOutReady)                                                        //如果接收到了数据
{
      //USB_SendData(UsbOutBuffer,OutNumber);   //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
                        UART_NUM = UsbOutBuffer;
                        UART_RX_FLAG=1;
          usb_OUT_done();                                                        //
}
}

3、SEG7_Task用于发送SEG显示指令

void SEG7_Task(void)
{
        u8 i;
        if(UART_RX_FLAG==1)
        {
                UART_RX_FLAG=0;
                SEG7_DATA=UART_NUM;
                UART_CUR++;
                if(UART_CUR==8){
                        if(SEG7_DATA==0x31&&SEG7_DATA==0x32&&SEG7_DATA==0x33&&SEG7_DATA==0x34&&
                               SEG7_DATA==0x35&&SEG7_DATA==0x36&&SEG7_DATA==0x37&&SEG7_DATA==0x38)
                        {
                                SEG7_status=2;
                        }
                        else{
                                SEG7_status=0;
                                for(i=0;i<8;i++)
                                {
                                        SEG7_DATA=' ';
                                }
                        }
                        UART_CUR=0;
                }else
                {
                        SEG7_status=1;
                }
                       
               
                if(SEG7_status==0){                                //状态1:显示“--------”
                        SEG7_show_one_line_char('-');
                }else if(SEG7_status==1){        //状态2:显示密码
                        SEG7_ShowString("%s",(char*)SEG7_DATA);
                }else if(SEG7_status==2){        //状态3:密码对了显示OPEN
                        SEG7_show_open();
                }
        }else if(SEG7_status==0) SEG7_show_one_line_char('-');
}

4、显示OPEN
void SEG7_show_open(void)
{
        BYTE cod;
        cod = 0x00;
        cod = 0x00;
        cod = 0x00;
        cod = 0x00;
        cod = 0x3F;
        cod = 0x73;
        cod = 0x79;
        cod = 0x37;
        SEG7_ShowCode(cod);

}5、显示一行指定字符
void SEG7_show_one_line_char(u8 c)
{
        u8 i;
        u8 str;
        for(i=0;i<8;i++) str=c;
        SEG7_ShowString("%s",str);
}

sujingliang 发表于 2025-1-3 13:32:51

《第十一集 矩阵按键》学习


492


一、注册任务
static TASK_COMPONENTS Task_Comps[]=
{
//状态计数周期函数

{0, 500,   500, wash_Task},
{0, 10,    10,    KEY_Task},      
{0, 10,   10,    KEYBOARD_Task},         /*矩阵键盘*/
{0, 200,   200,    matrix_Task},         
};KEYBOARD_Task是矩阵按键任务


二、矩阵按键处理函数
这部分基本都是抄冲哥的,只是改成了4*4矩阵按键
void KEYBOARD_Task(void)
{
//①第一步:现将P0.0-P0.3输出低电平,P0.6-P0.7输出高电平,如果有按键按下,按下的那一行的IO就会变成低电平,就可以判断出哪一行按下了。
      KEY_COL1 = 0;
      KEY_COL2 = 0;
      KEY_COL3 = 0;
      KEY_COL4 = 0;
      KEY_ROW1 = 1;
      KEY_ROW2 = 1;
      KEY_ROW3 = 1;
      KEY_ROW4 = 1;

      
      if(( KEY_ROW1 == 0 ) || ( KEY_ROW2 == 0 )|| ( KEY_ROW3 == 0 )|| ( KEY_ROW4 == 0 ))                //如果行按键有按下
      {
                if(( KEY_ROW1 ==0 ) && ( KEY_ROW2 ==0 )&& ( KEY_ROW3 ==0 )&& ( KEY_ROW4 ==0 ))      //如果两行都有按键按下,不处理
                {
                        
                }
                else if( KEY_ROW1==0||KEY_ROW2==0||KEY_ROW3 ==0||KEY_ROW4 ==0 )      //如果有按键按下,而且只有一颗
                {
                        if( KEY_ROW1 ==0 )                              //判断哪一行,输出行开始的序号
                              key_num = 0;
                        else if( KEY_ROW2 ==0 )
                              key_num = 4;
                        else if( KEY_ROW3 ==0 )
                              key_num = 8;
                        else if( KEY_ROW4 ==0 )
                              key_num = 12;
                              
                        //②第二步:现将P0.0-P0.3输出高电平,P0.6-P0.7输出低电平,如果有按键按下,按下的那一列的IO就会变成低电平,就可以判断出哪一列按下了。
                        KEY_COL1 = 1;
                        KEY_COL2 = 1;
                        KEY_COL3 = 1;
                        KEY_COL4 = 1;
                        KEY_ROW1 = 0;
                        KEY_ROW2 = 0;
                        KEY_ROW3 = 0;
                        KEY_ROW4 = 0;
                        
                        if( KEY_COL1 ==0 )                              //判断哪一列,叠加按键的序号
                        {
//                              key_num = key_num ;
                        }
                        else if( KEY_COL2 ==0 )
                        {
                              key_num = key_num + 1;
                        }
                        else if( KEY_COL3 ==0 )
                        {
                              key_num = key_num + 2;
                        }
                        else if( KEY_COL4 ==0 )
                        {
                              key_num = key_num + 3;
                        }
                        
      
                }
                KEY_COL1 = 0;
                KEY_COL2 = 0;
                KEY_COL3 = 0;
                KEY_COL4 = 0;
                KEY_ROW1 = 1;
                KEY_ROW2 = 1;               
                KEY_ROW3 = 1;
                KEY_ROW4 = 1;
      }
      else
      {
                key_num = 0xff;
      }
      
      //③第三步:行列组合一下就可以判断出是哪个按键按下了。
}
三、在点阵屏上显示

void matrix_Task(void)
{
    static u8 num=0;
      
    if(key_num!=0xff) num=key_num;
      
    clear_matrix8x8();
      
    if(num!=0xff){
      set_matrix8x8(4,num%10);
      set_matrix8x8(0,num/10);
    }
    show_matrix8x8(matrix1);                //显示
}

下面是595的驱动控制:
void show_matrix8x8(u8 *pBuff)
{
    unsigned char k;
    unsigned int m,n;

       for(m=0;m<32;m++)    //为移动预留
       {
         
               for(k=0;k<8;k++)   //行扫描
               {
                                                               for(n=0;n<32;n++)//控制显示速度,防止闪烁
                                                                {
                   HC595(~pos,0);
                   HC595(pBuff,0);
                   //delay_us(10);

                   LED_RCK_CLR;
                   LED_RCK_SET;    //并行输出
                                                                         delay_us(5);
                }
                                                      
            }
       }
}

void HC595(u8 c,u8 PN)
{
    u8 i;
    for(i=0;i<8;i++){

      if(PN==0){                     //正常显示
            if((c>>i) & 0x01) LED_SER_SET;
            else LED_SER_CLR;
      }
      else{                              //反显
            if((c>>i) & 0x01) LED_SER_CLR;
            else LED_SER_SET;
      }
      LED_SRCK_CLR;
      LED_SRCK_SET;   // 上升沿进行一次数据移入
    }
}


void set_matrix8x8(u8 startPos,u8 num)
{
      u8 i;
      for(i=0;i<3;i++)
      {
                matrix1=NUM;
      }
}

void clear_matrix8x8()
{
      u8 i;
      for(i=0;i<8;i++)
      {
                matrix1=0x00;
      }
}


全貌


页: [1] 2
查看完整版本: 《Ai8051U教学视频》学习心得