最近学习了表驱动方式,总算基本搞明白了,下面通过一个实例介绍一下表驱动方式。
表驱动法其实就三步:
1.建表:根据需求弄个数组或者结构体数组,把逻辑和数据塞进去。 2.查表:程序跑的时候,根据输入或者状态去表里找对应的条目。 3.干活:找到后按表里的指示执行操作,完事。 本例要完成的是流水灯程序,具体到哪一个灯,亮的时间长短,两函数,灭函数,所以这里定义了一个结构,名为tagLEDFuncCB,把这几个元素包装起来,当然元素的个数不受限(我认为的),数据类型也不受限,函数要求是无参数无返回值类型的,这里的函数泛指一般函数,只是说明有2个函数而已。
- //====定义一个结构,把同一个灯的操作封装起来====
- struct tagLEDFuncCB //结构
- {
- //封装元素的次序与表中对应,封装元素的个数不受限
- u8 Index; //索引,全局变量
- u16 Time; //时间间隔,全局变量
- void (*LedOn)(void); //亮灯函数,必须无参数,无返回值
- void (*LedOff)(void); //灭灯函数,必须无参数,无返回值
- };
复制代码
针对结构,然后定义要完成的任务表。本例4个LED流水点亮,所以数组要有4个元素,即要完成4个任务,可以根据需要添加更多任务,结构表名(即数组名)叫LedOpTable。 - //====定义需要操作到的灯的表=====
- const struct tagLEDFuncCB LedOpTable[ ] = //结构表
- {
- //表中每个元素与每一个灯的封装对应一致,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[sta].Time) //时间超过该状态的LED对应的时间值
- {
- // 熄灭所有灯
- u8 i;
- for (i = 0; i < sizeof(LedOpTable)/sizeof(LedOpTable[0]); i++)
- {
- LedOpTable[i].LedOff();
- }
-
- // 切换到下一个灯
- sta++;
- if (sta >= (sizeof(LedOpTable) / sizeof(LedOpTable[0])))
- {
- sta = 0;
- }
-
- // 点亮当前灯
- LedOpTable[sta].LedOn();
-
- // 重置计数器
- cnt = 0;
- }
- else
- {
- cnt++; // 当前灯已持续的时间计数加 1
- }
- }
-
复制代码
这里代码 sizeof(LedOpTable)/sizeof(LedOpTable[0]难理解,sizeof(LedOpTable) 求整个结构体数组占用的总字节数,sizeof(LedOpTable[0]) 求数组中第一个元素占用的字节数,这两者相除,得到的就是数组的元素个数,也可以说任务的个数。 完整代码如下: - //表驱动法
-
-
- //例 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 void timer0_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 tagLEDFuncCB LedOpTable[ ] = //结构表
- {
- //表中每个元素与每一个灯的封装对应一致,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[sta].Time) //时间超过该状态的LED对应的时间值
- {
- // 熄灭所有灯
- u8 i;
- for (i = 0; i < sizeof(LedOpTable)/sizeof(LedOpTable[0]); i++)
- {
- LedOpTable[i].LedOff();
- }
-
- // 切换到下一个灯
- sta++;
- if (sta >= (sizeof(LedOpTable) / sizeof(LedOpTable[0])))
- {
- sta = 0;
- }
-
- // 点亮当前灯
- LedOpTable[sta].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
- void main(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[0].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/main LED4_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 void timer0_init(void); //timer0初始化函数
-
- void LED_Ctrl(void) //LED灯处理 下载成功
- {
- static u8 sta = 0; // 当前正在亮的灯在表中的位置
- static u8 cnt = 0; // 当前灯已持续的时间计数
-
- // 时间到,切换到下一个灯
- if (cnt >= LedOpTable[sta].Time) //时间超过该状态的LED对应的时间值
- {
- // 熄灭所有灯
- u8 i;
- for (i = 0; i < sizeof(LedOpTable)/sizeof(LedOpTable[0]); i++)
- {
- LED_Set(LedOpTable[i].Index, 0); // 灭
- }
-
- // 切换到下一个灯
- sta++;
- if (sta >= (sizeof(LedOpTable) / sizeof(LedOpTable[0])))
- {
- sta = 0;
- }
-
- // 点亮当前灯
- LED_Set(LedOpTable[sta].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倍
- void main(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灯处理
- }
- }
- }
复制代码
本文通过实例说明表驱动的用法,掌握了原理,在单片机中一样有得心应手的应用。
|