采用表驱动方式的基于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灯处理
}
}
}
本文通过实例说明表驱动的用法,掌握了原理,在单片机中一样有得心应手的应用。
此法适用于花样流水灯。
页:
[1]