问题描述:
当我们刚开始学习使用中断的时候,往往就是通过看流水灯的状态来判断程序是否按照我们预想的方式运行来判断中断的执行情况,但是有些时候往往会出现一些“莫名其妙”的问题,例如下面的这段程:
#include"STC15F2K60S2.H"
typedef unsigned char uchar;
typedef unsigned int uint;
uint ms = 0; //毫秒计时器
void init()
{
TMOD = 0x01; //定时器T0工作方式1
TH0 = (65536-1000)/256; //初值,定时1ms
TL0 = (65536-1000)%256;
ET0 = 1; //打开定时器T0中断
EA = 1; //打开总中断开关
TR0 = 1; //启动定时器T0
P2M0=0xff;
P2M1=0x00;
P0M0=0xff;
P0M1=0x00;
P0=0x01;
}
void T0mode() interrupt 1
{
TH0 = (65536-1000)/256; //重新赋初值,定时1ms
TL0 = (65536-1000)%256;
ms++; //毫秒计时器加1
}
void main()
{
init(); //初始化
while(1)
{
//主循环空转
if(ms%500==0) //每500ms向左移动一次LED
{
P0 = (P0<<1)|(P0>>7); //将LED左移一位,并将最高位移到最低位
}
}
}
它看起来只是一个普通的点亮流水灯的程序,这并没有任何不妥, 但是当我们把它烧录到板子上的时候会发现它并没有像我们预想的一样实现一个流水灯程序,而是毫无规律的随机乱跳。经过多次检查,仍然找不到原因。
问题分析:
让我们看看chatGPT怎么说的吧:
将T0mode中的判断语句放到主函数的while(1)中会导致流水灯程序失效的原因是,中断服务程序(比如T0mode)与主函数是异步执行的,不受主函数的控制。当中断发生时,程序会自动转移到中断服务程序中执行,执行完毕后再返回主函数。因此,在中断服务程序中修改变量或实现某些逻辑处理,不会被主函数感知。
在本例中,流水灯的效果是在定时器中断服务程序T0mode中实现的,每隔一段时间就左移LED控制引脚的状态,形成流水灯的效果。如果将判断语句放到主函数的while(1)中,那么这段代码永远不会被执行,因为主函数在等待中断的触发。而中断服务程序被触发时,会自动执行,但是此时判断语句并没有被执行,也就不会改变LED的状态。因此,流水灯的效果无法实现。
总之,对于定时器、中断等需要依赖硬件的程序,应该优先在中断服务程序中完成相应的操作,而不要依赖主函数。
对于第一段的回答是十分到位的,简单来说就是中断函数的优先级要高于主函数,当发生中断的时候,主函数就会被暂时放下,转而去做中断函数安排的事,这样会耽误主函数的执行从而导致一系列问题。但是对于第二段的回答就有些问题了,它说主函数的while(1)中的内容永远也不会被执行就有问题了,我们可以在开发板上看到流水灯会发生一系列的变化,这证明主函数里的if语句是会被执行的。ChatGPT的最后一句话我认为十分实用,我们尽量优先在终端上完成相应操作,尽可能不要去依赖主函数。
综上,我们可以认为在触发中断之前的1ms中,主函数已经将if语句执行了很多遍,至于它到底会停在哪就看在这1ms的时间里并且ms还没有发生改变的时候,主函数的while(1)循环到底执行了几遍。再者,流水灯之所以能停留我们设定的时间,是因为ms在不为500的时间有500ms所以会停留我们设定的时间,然后在下一次ms%500==0的1ms时间里重复上述步骤就发生了乱跳的现象。因此,流水灯在条约的时候其他的灯会闪烁的原因也就很浅显了。
问题解答:
所以针对这种状况,我们可以有两种解决办法。
方法一:
我们可以将if判断放到中断程序里来规避这种现象的发生:
#include"STC15F2K60S2.H"
typedef unsigned char uchar;
typedef unsigned int uint;
uint ms = 0; //毫秒计时器
void init()
{
TMOD = 0x01; //定时器T0工作方式1
TH0 = (65536-1000)/256; //初值,定时1ms
TL0 = (65536-1000)%256;
ET0 = 1; //打开定时器T0中断
EA = 1; //打开总中断开关
TR0 = 1; //启动定时器T0
P2M0=0xff;
P2M1=0x00;
P0M0=0xff;
P0M1=0x00;
P0=0x01;
}
void T0mode() interrupt 1
{
TH0 = (65536-1000)/256; //重新赋初值,定时1ms
TL0 = (65536-1000)%256;
ms++; //毫秒计时器加1
if(ms%500==0) //每500ms向左移动一次LED
{
P0 = (P0<<1)|(P0>>7); //将LED左移一位,并将最高位移到最低位
}
}
void main()
{
init(); //初始化
while(1)
{
//主循环空转
}
}
方法二:
我们可以额外定义一个lock变量来防止if判断语句被多次执行:
#include"STC15F2K60S2.H"
typedef unsigned char uchar;
typedef unsigned int uint;
bit lock=1;
uint ms = 0; //毫秒计时器
void init()
{
TMOD = 0x01; //定时器T0工作方式1
TH0 = (65536-1000)/256; //初值,定时1ms
TL0 = (65536-1000)%256;
ET0 = 1; //打开定时器T0中断
EA = 1; //打开总中断开关
TR0 = 1; //启动定时器T0
P2M0=0xff;
P2M1=0x00;
P0M0=0xff;
P0M1=0x00;
P0=0x01;
}
void T0mode() interrupt 1
{
TH0 = (65536-1000)/256; //重新赋初值,定时1ms
TL0 = (65536-1000)%256;
ms++; //毫秒计时器加1
}
void main()
{
init(); //初始化
while(1)
{
//主循环空转
if(ms%500==0&&lock) //每500ms向左移动一次LED,如果第一次条件成立则执行左移
{
P0 = (P0<<1)|(P0>>7); //将LED左移一位,并将最高位移到最低位
lock=0;//将lock置为0防止在条件成立的1ms内被多次执行
}
else if(ms%500)//当ms变化为不成立时将lock复位
lock=1;
}
}