yyq_007 发表于 2026-7-8 19:11:30

采用表驱动方式的基于AI8051U的流水灯程序

    最近学习了表驱动方式,总算基本搞明白了,下面通过一个实例介绍一下表驱动方式。

   表驱动法其实就三步:
1.建表:根据需求弄个数组或者结构体数组,把逻辑和数据塞进去。2.查表:程序跑的时候,根据输入或者状态去表里找对应的条目。3.干活:找到后按表里的指示执行操作,完事。本例要完成的是流水灯程序,具体到哪一个灯,亮的时间长短,两函数,灭函数,所以这里定义了一个结构,名为tagLEDFuncCB,把这几个元素包装起来,当然元素的个数不受限(我认为的),数据类型也不受限,函数要求是无参数无返回值类型的,这里的函数泛指一般函数,只是说明有2个函数而已。//====定义一个结构,把同一个灯的操作封装起来====
structtagLEDFuncCB             //结构
{
        //封装元素的次序与表中对应,封装元素的个数不受限
        u8   Index;       //索引,全局变量
u16 Time;      //时间间隔,全局变量   
        void (*LedOn)(void);   //亮灯函数,必须无参数,无返回值
void (*LedOff)(void);         //灭灯函数,必须无参数,无返回值
};针对结构,然后定义要完成的任务表。本例4个LED流水点亮,所以数组要有4个元素,即要完成4个任务,可以根据需要添加更多任务,结构表名(即数组名)叫LedOpTable。//====定义需要操作到的灯的表=====
const   struct tagLEDFuncCBLedOpTable[ ] =   //结构表
{
   //表中每个元素与每一个灯的封装对应一致,4个灯,状态只能为0,1,2,3
       {1, 1, LED1_On, LED1_Off},   //LED1的状态,亮灯时间,亮灯函数,灭灯函数
   {0, 2, LED2_On, LED2_Off},   //LED2的状态,亮灯时间,亮灯函数,灭灯函数
   {2, 3, LED3_On, LED3_Off},//LED3的状态,亮灯时间,亮灯函数,灭灯函数
       {3, 4, LED4_On, LED4_Off},//LED4的状态,亮灯时间,亮灯函数,灭灯函数
};该数组有4个元素,由内层的4个大括号包括,每个大括号又包含4个元素,如{1, 1, LED1_On, LED1_Off}封装了LED1的元素,第1个元素1表示LED1的状态,第2个元素1表示亮灯时间,第3个元素LED1_On表示亮灯函数,第4个元素LED1_Off灭灯函数。干活就是处理任务,由void LED_Ctrl(void)来完成。void LED_Ctrl(void)      //LED灯处理下载成功
{
    static u16 sta = 0;      // 当前正在亮的灯在表中的位置
    static u16 cnt = 0;      // 当前灯已持续的时间计数
   
    // 时间到,切换到下一个灯
    if (cnt >= LedOpTable.Time)    //时间超过该状态的LED对应的时间值
    {
      // 熄灭所有灯
      u8 i;
      for (i = 0; i < sizeof(LedOpTable)/sizeof(LedOpTable); i++)
      {
            LedOpTable.LedOff();
      }
      
      // 切换到下一个灯
      sta++;
      if (sta >= (sizeof(LedOpTable) / sizeof(LedOpTable)))
      {
            sta = 0;
      }
      
      // 点亮当前灯
      LedOpTable.LedOn();
      
      // 重置计数器
      cnt = 0;
    }
    else
    {
      cnt++;            // 当前灯已持续的时间计数加 1
    }
}
这里代码 sizeof(LedOpTable)/sizeof(LedOpTable难理解,sizeof(LedOpTable) 求整个结构体数组占用的总字节数,sizeof(LedOpTable) 求数组中第一个元素占用的字节数,这两者相除,得到的就是数组的元素个数,也可以说任务的个数。完整代码如下://表驱动法   


//例4个流水灯

#include "AI8051U.h"               //AI8051U---32bit模式,Keil C251编译器
#include "stdio.h"
#include "intrins.h"

#define MAIN_Fosc      24000000UL//时钟
#define Timer0_Reload   (MAIN_Fosc / 500)      //Timer 0 中断,定时2毫秒,不分频


typedef         unsigned char        u8;
typedef         unsigned int        u16;
typedef         unsigned long        u32;
       
sbit LED1 = P2^0;//LED1位置
sbit LED2 = P2^1;//LED2位置
sbit LED3 = P2^2;//LED3位置
sbit LED4 = P2^3;//LED4位置

volatile u8 T0_flag = 0;    //100ms标志

//==========函数声明================

extern void LED1_On(void);   //LED灯1亮
extern void LED1_Off(void);//LED灯1灭

extern void LED2_On(void);   //LED灯2亮
extern void LED2_Off(void);   //LED灯2灭

extern void LED3_On(void);   //LED灯3亮
extern void LED3_Off(void);   //LED灯3灭

extern void LED4_On(void);   //LED灯4亮
extern void LED4_Off(void);   //LED灯4灭

extern void LED_Ctrl(void);    //LED灯处理
extern voidtimer0_init(void);       //timer0初始化函数


//=========函数定义=============       
void LED1_On(void)   //LED灯1亮
{
        LED1 = 0;
}
void LED1_Off(void)   //LED灯1灭
{
        LED1 = 1;
}
void LED2_On(void)   //LED灯2亮
{
        LED2 = 0;
}
void LED2_Off(void)   //LED灯2灭
{
        LED2 = 1;
}

void LED3_On(void)   //LED灯3亮
{
        LED3 = 0;
}
void LED3_Off(void)   //LED灯3灭
{
        LED3 = 1;
}
void LED4_On(void)   //LED灯4亮
{
        LED4 = 0;
}
void LED4_Off(void)   //LED灯4灭
{
        LED4 = 1;
}

//====把同一个灯的操作封装起来====
struct tagLEDFuncCB            //结构
{
        //封装元素的次序与表中对应,封装元素的个数不受限
        u8   Index;            //增加的索引,全局变量
u16 Time;               //增加时间间隔,全局变量   
        void (*LedOn)(void);      //亮灯函数
void (*LedOff)(void);       //灭灯函数
};
//====定义需要操作到的灯的表=====
const   struct tagLEDFuncCBLedOpTable[ ] =   //结构表
{
   //表中每个元素与每一个灯的封装对应一致,4个灯,状态只能为0,1,2,3
       {1, 1, LED1_On, LED1_Off},   //LED1的状态,亮灯时间,亮灯函数,灭灯函数
   {0, 2, LED2_On, LED2_Off},   //LED2的状态,亮灯时间,亮灯函数,灭灯函数
   {2, 3, LED3_On, LED3_Off},//LED3的状态,亮灯时间,亮灯函数,灭灯函数
       {3, 4, LED4_On, LED4_Off},//LED4的状态,亮灯时间,亮灯函数,灭灯函数
};

void LED_Ctrl(void)      //LED灯处理下载成功
{
    static u8 sta = 0;      // 当前正在亮的灯在表中的位置
    static u8 cnt = 0;      // 当前灯已持续的时间计数
   
    // 时间到,切换到下一个灯
    if (cnt >= LedOpTable.Time)    //时间超过该状态的LED对应的时间值
    {
      // 熄灭所有灯
      u8 i;
      for (i = 0; i < sizeof(LedOpTable)/sizeof(LedOpTable); i++)
      {
            LedOpTable.LedOff();
      }
      
      // 切换到下一个灯
      sta++;
      if (sta >= (sizeof(LedOpTable) / sizeof(LedOpTable)))
      {
            sta = 0;
      }
      
      // 点亮当前灯
      LedOpTable.LedOn();
      
      // 重置计数器
      cnt = 0;
    }
    else
    {
      cnt++;            // 当前灯已持续的时间计数加1
    }
}

//========================================================================
// 函数: void Timer0_init(void)
// 描述: timer0初始化函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2015-1-12
//========================================================================
void Timer0_init(void)
{
      TR0 = 0;    //停止计数

    #if (Timer0_Reload < 64)    // 如果用户设置值不合适, 则不启动定时器
      #error "Timer0设置的中断过快!"

    #elif ((Timer0_Reload/12) < 65536UL)    // 如果用户设置值不合适, 则不启动定时器
      ET0 = 1;    //允许中断
    //PT0 = 1;    //高优先级中断
      TMOD &= ~0x03;
      TMOD |= 0;//工作模式, 0: 16位自动重装, 1: 16位定时/计数, 2: 8位自动重装, 3: 16位自动重装, 不可屏蔽中断
    //T0_CT = 1;//计数
      T0_CT = 0;//定时
    //T0CLKO = 1; //输出时钟
      T0CLKO = 0; //不输出时钟

      #if (Timer0_Reload < 65536UL)
            T0x12 = 1;//1T mode
            TH0 = (u8)((65536UL - Timer0_Reload) / 256);
            TL0 = (u8)((65536UL - Timer0_Reload) % 256);
      #else
            T0x12 = 0;//12T mode
            TH0 = (u8)((65536UL - Timer0_Reload/12) / 256);
            TL0 = (u8)((65536UL - Timer0_Reload/12) % 256);
      #endif

      TR0 = 1;    //开始运行

    #else
      #error "Timer0设置的中断过慢!"
    #endif
}
//========================================================================
// 函数: void timer0_int (void) interrupt TIMER0_VECTOR
// 描述:timer0中断函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2015-1-12
//========================================================================
void timer0_int (void) interrupt 1
{
   static u8 T_Count = 0;
   T_Count++;
   if(T_Count == 50)                   //满100ms
    {
             T_Count = 0;
             T0_flag = 1;   //100ms标志置1
          }
}

//主函数运行
//间隔100ms,而表中的time=5,1,20,所以亮的时间分别是,100ms的5,1,20倍,
//即500ms,100ms,2s,完整循环用2.6s
voidmain(void)
{
          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 = 0x0b;   //设置为准双向口
    P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
    P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
    P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口

    EA = 1;   //打开总中断
        Timer0_init();       //timer0初始化函数
                // 初始点亮第一个灯,亮灯顺序,始终1-2-3
//LedOpTable.LedOn();    //屏蔽此句,初始亮灯顺序,2-3-1,之后1-2-3
       IE = 0x82;                                  //允许T0中断
while(1)
{
    if(1 == T0_flag)   //100ms标志为1
                {
                        T0_flag = 0;    //100ms标志清0
                        LED_Ctrl();   //LED灯处理
    }
}
}    采用AI8051U擎天柱核心板,32bit模式,使用Keil C251编译器,下载成功,第1个LED亮100ms,第2个LED亮200ms,第3个LED亮400ms,第4个LED亮400ms,编译结果Program Size: data=8.0 edata+hdata=300 xdata=0 const=56 code=1177,可能是该编译器有问题,有8个类似的警告:*** WARNING L57: UNCALLED FUNCTION, IGNORED FOR OVERLAY PROCESS                                                            NAME:    LED4_On/mainLED4_On函数定义了但未使用,但KeilC51则不会出现此警告。另外,STC的AI8051U例程提示:使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。实际上若不采用XSmall模式,下载失败,,这可能是Keil C251编译器的一个bug吧。如果改为8个LED流水点亮,那代码就太多了,把代码复制到STC AI助手让其优化一下,立即给出较好的方案,做了2点优化,下载成功,编译结果Program Size: data=8.1 edata+hdata=300 xdata=0 const=36 code=1199,完整代码如下。//表驱动法   

//例8个流水灯每个动作执行时间不同      索引访问从0开始,<span style="white-space: normal;">亮灯顺序,始终1-2-3...8</span>
//使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。

#include "AI8051U.h"         //AI8051U---32bit模式,Keil C251编译器
#include "stdio.h"
#include "intrins.h"

#define MAIN_Fosc      24000000UL//时钟
#define Timer0_Reload   (MAIN_Fosc / 500)      //Timer 0 中断,定时2毫秒,不分频


typedef         unsigned char        u8;
typedef         unsigned int        u16;
typedef         unsigned long        u32;

#define MAIN_Fosc      24000000UL
       
sbit LED1 = P2^0;//LED1位置
sbit LED2 = P2^1;//LED2位置
sbit LED3 = P2^2;//LED3位置
sbit LED4 = P2^3;//LED4位置
sbit LED5 = P2^4;//LED5位置
sbit LED6 = P2^5;//LED6位置
sbit LED7 = P2^6;//LED7位置
sbit LED8 = P2^7;//LED8位置


u8 T0_flag = 0;    //100ms标志

//==========函数声明================
extern void LED_Ctrl(void);
extern void timer0_init(void);

//=========函数定义=============       

//====优化点1:将16个独立的亮灭函数合并为2个通用函数====
void LED_Set(u8 num, bit on_off)
{
    switch(num)
    {
      case 0: LED1 = on_off ? 0 : 1; break;// 亮=0,灭=1
      case 1: LED2 = on_off ? 0 : 1; break;
      case 2: LED3 = on_off ? 0 : 1; break;
      case 3: LED4 = on_off ? 0 : 1; break;
      case 4: LED5 = on_off ? 0 : 1; break;// 亮=0,灭=1
      case 5: LED6 = on_off ? 0 : 1; break;
      case 6: LED7 = on_off ? 0 : 1; break;
      case 7: LED8 = on_off ? 0 : 1; break;
                          default: break;
    }
}
//====优化点2:结构体简化,去掉函数指针,改用索引====
struct tagLEDFuncCB
{
    u8   Index;            // LED编号(0/1/2)
    u8    Time;               // 亮灯时间(单位:100ms)
};

//====定义需要操作到的灯的表=====
const struct tagLEDFuncCB LedOpTable[] =
{
    {0, 1},    // LED1,亮100ms
    {1, 2},    // LED2,亮200ms
    {2, 3},   // LED3,亮300ms
    {3, 4},   // LED4,亮400ms
    {4, 5},    // LED5,亮500ms
    {5, 6},    // LED6,亮600ms
    {6, 7},   // LED7,亮700ms
    {7, 8},   // LED8,亮800ms
};

extern void LED_Ctrl(void);    //LED灯处理
extern voidtimer0_init(void);       //timer0初始化函数

void LED_Ctrl(void)      //LED灯处理下载成功
{
    static u8 sta = 0;      // 当前正在亮的灯在表中的位置
    static u8 cnt = 0;      // 当前灯已持续的时间计数
   
    // 时间到,切换到下一个灯
    if (cnt >= LedOpTable.Time)    //时间超过该状态的LED对应的时间值
    {
      // 熄灭所有灯
      u8 i;
      for (i = 0; i < sizeof(LedOpTable)/sizeof(LedOpTable); i++)
      {
            LED_Set(LedOpTable.Index, 0);// 灭
      }
      
      // 切换到下一个灯
      sta++;
      if (sta >= (sizeof(LedOpTable) / sizeof(LedOpTable)))
      {
            sta = 0;
      }
      
      // 点亮当前灯
         LED_Set(LedOpTable.Index, 1);    // 亮
      
      // 重置计数器
      cnt = 0;
    }
    else
    {
      cnt++;            // 当前灯已持续的时间计数加1
    }
}

//========================================================================
// 函数: void Timer0_init(void)
// 描述: timer0初始化函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2015-1-12
//========================================================================
void Timer0_init(void)
{
      TR0 = 0;    //停止计数

    #if (Timer0_Reload < 64)    // 如果用户设置值不合适, 则不启动定时器
      #error "Timer0设置的中断过快!"

    #elif ((Timer0_Reload/12) < 65536UL)    // 如果用户设置值不合适, 则不启动定时器
      ET0 = 1;    //允许中断
    //PT0 = 1;    //高优先级中断
      TMOD &= ~0x03;
      TMOD |= 0;//工作模式, 0: 16位自动重装, 1: 16位定时/计数, 2: 8位自动重装, 3: 16位自动重装, 不可屏蔽中断
    //T0_CT = 1;//计数
      T0_CT = 0;//定时
    //T0CLKO = 1; //输出时钟
      T0CLKO = 0; //不输出时钟

      #if (Timer0_Reload < 65536UL)
            T0x12 = 1;//1T mode
            TH0 = (u8)((65536UL - Timer0_Reload) / 256);
            TL0 = (u8)((65536UL - Timer0_Reload) % 256);
      #else
            T0x12 = 0;//12T mode
            TH0 = (u8)((65536UL - Timer0_Reload/12) / 256);
            TL0 = (u8)((65536UL - Timer0_Reload/12) % 256);
      #endif

      TR0 = 1;    //开始运行

    #else
      #error "Timer0设置的中断过慢!"
    #endif
}
//========================================================================
// 函数: void timer0_int (void) interrupt TIMER0_VECTOR
// 描述:timer0中断函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2015-1-12
//========================================================================
void timer0_int (void) interrupt 1
{
   static u8 T_Count = 0;
   T_Count++;
   if(T_Count >= 50)                   //满100ms
    {
             T_Count = 0;
             T0_flag = 1;   //100ms标志置1
          }
}

//主函数运行
//间隔100ms,而表中的time=5,1,20,所以亮的时间分别是,100ms的1,2,3...8倍
voidmain(void)
{
          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 = 0x0b;   //设置为准双向口
    P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
    P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
    P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口

    EA = 1;   //打开总中断
        Timer0_init();       //timer0初始化函数

while(1)
{
    if(1 == T0_flag)   //100ms标志为1
                {
                        T0_flag = 0;    //100ms标志清0
                        LED_Ctrl();   //LED灯处理
    }
}
}
本文通过实例说明表驱动的用法,掌握了原理,在单片机中一样有得心应手的应用。




wulin 发表于 2026-7-9 07:09:15

此法适用于花样流水灯。
页: [1]
查看完整版本: 采用表驱动方式的基于AI8051U的流水灯程序