今天早上7点20才睡。 -_-!
原因是我一整晚都在调试一个单片机程序,本来一个非常简单的程序,但是却一直有问题,整晚都抓狂的很,不过最终问题的答案找到了,是
编译器的bug ! 我的汗啊……
贴出我的源码:
1, main.c :
[code:c]
#include "kernel.h"
#include "stdio.h"
unsigned char print_timer = NO_TASK ;
int print ()
{
printf ( "i am in print \n" ) ;
}
void flash_led ()
{
printf ("flash \n" ) ;
}
void reg()
{
static bit f = 1 ;
if ( f )
{
if ( print_timer == NO_TASK )
{
print_timer = reg_task ( print, 40 ) ;
printf(" task create seccess ! id : %d \n", (int)print_timer ) ;
}
}
else
{
unreg_task ( &print_timer ) ;
printf (" task print has been unreg ! \n" ) ;
}
f = ~f ;
}
void main()
{
SBUF = ' ' ;
kernel_init () ;
//reg_task ( flash_led, 40 ) ;
reg_task ( flash_led, 400 ) ;
reg_task ( reg, 80 ) ;
while (1) ;
}
[/code]
2, kernel.c :
[code:c]
#include "kernel.h"
void kernel_init ()
{
TMOD |=0x01;
PT0=1; //设置定时器的优先级高,精确定时
TH0=0x9e; //40HZ,25ms kernel freq
TL0=0x57;
EA = 1 ;
ET0=1; //允许定时器0中断
TR0=1; // 核心定时器运行
}
static TASK funp[MAX_TASK+1] = { (TASK)0 } ; //存放函数地址
static int freq[MAX_TASK+1] = { (int)0 } ; // 每隔多少个25ms运行一次
static int counter = 1 ;
static int max_id = 0 ;
int reg_task ( TASK t, int f ) // 定时器的时间是 time*25ms
{
disable_kernel ;
if ( (max_id == MAX_TASK) || (f==0) || (t==(TASK)0) )
{
enable_kernel ;
return 0 ;
}
max_id ++ ;
funp[max_id] = (TASK)t ;
freq[max_id] = f ;
enable_kernel ;
return max_id ;
}
void unreg_task ( int * task_id )
{
int id ;
disable_kernel ;
id = *task_id ;
if ( id == 0 )
{
enable_kernel ;
return ; // 没有0这个任务,不用注销任务
}
funp[id] = funp[max_id] ;
freq[id] = freq[max_id] ;
max_id -- ;
*task_id = 0 ; // 这样在外面检测任务ID的时候,显示为0,表示这个任务不存在。
enable_kernel ;
}
void time_0_kernel() interrupt 1 //using 2 但是为什么我不能切换寄存器组呢????一旦去掉注释,输出结果马上不同
{
int id ;
ET0 = 0; // 禁用中断
if ( counter++ == 26400 ) counter = 1 ; // 最长定时器为一个小时
//定时器任务调度
id = max_id ;
while ( id )
{
if ( freq[id] > 0 )
if ( (counter%freq[id]) == 0 )
(funp[id])() ;
id -- ;
}
TH0=0x9e; //40HZ,25ms kernel freq
TL0=0x57;
ET0 = 1 ; // 开启定时器中断
}
[/code]
3, kernel.h :
[code:c]
#ifndef MY_KERNEL_H
#define MY_KERNEL_H
#include "REGX51.H"
#include "stdio.h"
#include "intrins.h"
#define disable_interrupt ( EA = 0 )
#define enable_interrupt ( EA = 1 )
#define disable_kernel (TR0 = 0 )
#define enable_kernel ( TR0 = 1 )
#define MAX_TASK 6
typedef void ( * TASK ) () ;
void kernel_init () ;
int reg_task ( TASK timer_task, int time ) ;
void unreg_task ( int * id ) ;
#endif
[/code]
上面是没有问题的代码可以在Keil uVison3 for 51 中仿真运行,结果如图:
![]()
src='http://music.blogcn.com/wp-content/uploads/154/15462/2008/08/right_result.jpg' alt='right_result.jpg' />但是,如果我把 kernel.c中的那个注释去掉,即我切换寄存器组,马上就会出问题。输出的结果立即不同 :
![]()
src='http://music.blogcn.com/wp-content/uploads/154/15462/2008/08/wrong_result.jpg' alt='wrong_result.jpg' />这甚至只是错误结果的一种,因为还有好几种输出情况,但都是错误的。
一开始我以为是我切换寄存器有问题,不能切换,但是我找半天也找不到不能切换的理由,而是找到要切换的理由:我可以省下8字节的堆栈空
间。怎么想都得不到答案,最后我拼了,把它反汇编之后从汇编指令一条条查看,最后发现错误的原因在这里:
![]()
src='http://music.blogcn.com/wp-content/uploads/154/15462/2008/08/keil_bug.JPG' alt='keil_bug.JPG' />下面我来分析一下这几行汇编指令:
这一段代码的目的是把我传递过来的函数指针值存放在适当的地方,这样,适当
时候我可以从这个地方取出函数指针,并进行函数调用。
这里编译器设置把函数地址存放在2AH开始后的一个数组里,因为在keil C51里一
个函数指针占用三个字节,第一个字节存放的是指针类型,第二个和第三个字节
分别存放高位和地位的地址。所以,第一个函数的地址存放在2BH(高位)和2CH(低
位),第二个就存放在2EH和2FH了,依次类推。
第1行 : 把我传递过来的函数指针值的高位存到寄存器R2
第2行 : 把我传递过来的函数指针值的低位存到寄存器R1
第3行 : 3FH里存放的是函数指针值数组的偏移量,把偏移量赋值给A
第4行 : 把每个地址占用的长度赋值给B
第5行 : 做一个乘法,得到的结果就是A,其中就是我现在应该存放函数指针值
的地址的偏移量
第6行 : 把得到的偏移量加上数组的基址2AH得到最终应该存放的地址
第7行 : 把地址值给R0,即将用R0做寄存器寻址之用
第8行 : 把该指针的类型值(存放在13H)先存入
第9行 : 寄存器指针指向下一个字节
第10行: 把函数指针值的高位存入
第11行: 寄存器指针指向下一个字节
第12行: 把函数指针值的低位存入
如果只看我的注释的话是一点问题也没有的,如果你看代码就应该发现一点不对劲了,在第10行和第12行,分别把函数指针值的高位和地位存
入,而函数指针值的高位和地方在前面两行就暂存在了R2和R1中,所以这个时候应该用:
MOV @R0, R2和MOV @R0, R1才对吧?对,这是最保险的,但是图中编译器生成的代码如果使用的是寄存器第0组的话也是没有问题的,因为第0
组的R2和R1地址就是02H和01H,但是目前的问题是,我已经切换了寄存器组,所以应该用R2和R1才是对的。我不知道编译器这个时候在耍什么
小聪明,好好的R2和R1不用,用绝对地址来表示。
找到问题的原因后,通过直接修改汇编代码,程序又成功运行了。
这个时候是早上四点半,接着上网和jerome聊了会天,五点半了,细细刷刷,6点了,开电视看,七点二十,睡了……
八点四十又起来了,因为我一天多没去实验室,据说今天要调试,我就过去了,不一会儿,调试完毕,写篇日志,睡了……