找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4209|回复: 32
收起左侧

STC 1T单片机的奇怪情况

  [复制链接]
ID:161164 发表于 2021-10-24 12:27 | 显示全部楼层 |阅读模式
先上单片机代码
  1. #include <reg52.h>

  2. sbit LED=P3^5;

  3. unsigned short TMR_XX_OT;

  4. sfr AUXR = 0x8E;
  5. void Timer0Init(void)                //1毫秒@12.000MHz
  6. {
  7.         AUXR &= 0x7F;                //定时器时钟12T模式
  8.         TMOD &= 0xF0;                //设置定时器模式
  9.         TMOD |= 0x01;                //设置定时器模式
  10.         TL0 = 0x18;                //设置定时初始值
  11.         TH0 = 0xFC;                //设置定时初始值
  12.         TF0 = 0;                //清除TF0标志
  13.         TR0 = 1;                //定时器0开始计时
  14. }
  15. void timer0_int (void) interrupt 1
  16. {
  17.         TL0 = 0x18;                //设置定时初始值
  18.         TH0 = 0xFC;                //设置定时初始值
  19.         if(TMR_XX_OT)TMR_XX_OT--;
  20. }

  21. void main()
  22. {
  23.     Timer0Init();
  24.         IE = 0;
  25.                 ET0 = 1;
  26.                 EA = 1;
  27.     while(1)
  28.                 {                        
  29.                         if(!TMR_XX_OT)
  30.                         {TMR_XX_OT=270;
  31.                                 LED = !LED;
  32.                         }
  33.                 }
  34. }
复制代码

这是STC89C52的运行情况
STC89_270.gif


这是STC15的运行情况
STC15_270.gif


这是STC15改为TMR_XX_OT=250;的运行情况

STC15_250.gif


TMR_XX_OT的数据类型已经是unsigned short (0 to 65535)
赋270不会溢出吧?

回复

使用道具 举报

ID:57657 发表于 2021-10-24 18:15 | 显示全部楼层
8位单片机向16位变量赋值,是分成两条指令完成的。
先赋值高8位,此时中断触发改变了该变量的值。
中断返回后再去赋值低8位,导致程序出错。
回复

使用道具 举报

ID:624769 发表于 2021-10-24 18:52 | 显示全部楼层
TMOD = 0x00;     //16位自动重载模式

bit     T_OT_Flag;    //增加一个标志位

void timer0_int (void) interrupt 1
{
//        TL0 = 0x18;                //设置定时初始值
//        TH0 = 0xFC;                //设置定时初始值    自动重载不需要设置
        if(--TMR_XX_OT==0)
        {
              TMR_XX_OT = 270;
              T_OT_Flag = 1;
        }
}

while 里面这么写:

    while(1)
                {                        
                        if(T_OT_Flag)
                        {
                                T_OT_Flag = 0;
                                LED = !LED;
                        }
                }


然后试试看。
回复

使用道具 举报

ID:161164 发表于 2021-10-24 19:06 | 显示全部楼层
npn 发表于 2021-10-24 18:15
8位单片机向16位变量赋值,是分成两条指令完成的。
先赋值高8位,此时中断触发改变了该变量的值。
中断返 ...

不太可能
我试过停止定时器再赋值再运行定时器都会出现这种情况
TR0 = 0;TMR_XX_OT=270;TR0 = 1;

反而在while(1)底加上4个_nop_()就可以正常运作
是因为1T单片机太快了吗?
回复

使用道具 举报

ID:624769 发表于 2021-10-24 19:25 | 显示全部楼层
lkc8210 发表于 2021-10-24 19:06
不太可能
我试过停止定时器再赋值再运行定时器都会出现这种情况
TR0 = 0;TMR_XX_OT=270;TR0 = 1;

你的问题不是 定时器赋值,而是那个TMR_XX_OT 的判断,必须放到定时器里判断是否为0,不能放在外面判断。
回复

使用道具 举报

ID:161164 发表于 2021-10-24 19:36 来自手机 | 显示全部楼层
188610329 发表于 2021-10-24 19:25
你的问题不是 定时器赋值,而是那个TMR_XX_OT 的判断,必须放到定时器里判断是否为0,不能放在外面判断。

为什么?
回复

使用道具 举报

ID:624769 发表于 2021-10-24 21:08 | 显示全部楼层

你按我写的改了之后,不就知道了?
回复

使用道具 举报

ID:213173 发表于 2021-10-24 21:16 | 显示全部楼层

从逻辑上看程序没有问题,但同一个16位变量在主函数和中断中都可以操作容易出错,有前辈就此问题详细阐述过。择录如下:
        /* 注释二:
        * ET0=0;uiTimeCnt=0;ET0=1;----在清零 uiTimeCnt 之前,为什么要先禁止定时中断?
        * 因为 uiTimeCnt 是 unsigned int 类型,本质上是由两个字节组成。
        * 在 C 语言中 uiTimeCnt=0 看似一条指令,实际上经过编译之后它不只一条汇编指令。
        * 由于定时中断函数里也对这个变量进行累加操作,如果不禁止定时中断,
        * 那么 uiTimeCnt 这个变量在 main()函数中还没被完全清零的时候,如果这个时候
        * 突然来一个定时中断,并且在中断里又更改了此变量,这种情况在某些要求高的
        * 项目上会是一个不容易察觉的漏洞,为项目带来隐患。当然,大部分的普通项目,
        * 都可以不用那么严格,可以不用禁止定时中断。在这里只是提醒各位初学者有这种情况。
        */
建议改写为:
void timer0_int (void) interrupt 1
{
        TL0 = 0x18;                //设置定时初始值
        TH0 = 0xFC;                //设置定时初始值
        if(--TMR_XX_OT==0)
        {
                TMR_XX_OT=270;
                flag=1;
        }
}

void main()
{
        Timer0Init();
        IE = 0;
        ET0 = 1;
        EA = 1;
        while(1)
        {
                if(flag)
                {
                        flag=0;
                        LED = !LED;
                }
        }
}
回复

使用道具 举报

ID:161164 发表于 2021-10-24 21:38 来自手机 | 显示全部楼层
188610329 发表于 2021-10-24 21:08
你按我写的改了之后,不就知道了?

改了后可以是可以,但是为什么1T会这样,12T就正常?
回复

使用道具 举报

ID:161164 发表于 2021-10-24 22:34 | 显示全部楼层
本帖最后由 lkc8210 于 2021-10-24 23:01 编辑

改用联合体又正常了
  1. #include <reg52.h>

  2. sbit LED1=P3^5;

  3. typedef union{
  4.    unsigned char Dat_c[2];
  5.    unsigned int Dat_i;
  6. }EData;

  7. EData TMR_XX;
  8. sfr AUXR = 0x8E;
  9. void Timer0Init(void)                //1毫秒@12.000MHz
  10. {
  11.         AUXR &= 0x7F;                //定时器时钟12T模式
  12.         TMOD &= 0xF0;                //设置定时器模式
  13.         TMOD |= 0x00;                //设置定时器模式
  14.         TL0 = 0x18;                //设置定时初始值
  15.         TH0 = 0xFC;                //设置定时初始值
  16.         TF0 = 0;                //清除TF0标志
  17.         TR0 = 1;                //定时器0开始计时
  18. }
  19. void timer0_int (void) interrupt 1
  20. {
  21.                 if(TMR_XX.Dat_i)TMR_XX.Dat_i--;
  22. }

  23. void main()
  24. {
  25.     Timer0Init();
  26.                 ET0 = 1;
  27.                 EA = 1;
  28.     while(1)
  29.                 {                        
  30.                         if(!TMR_XX.Dat_c[0] && !TMR_XX.Dat_c[1])
  31.                         {TMR_XX.Dat_i=270;
  32.                                 LED1 = !LED1;
  33.                         }
  34.                 }
  35. }
复制代码



编译出来发现if(!TMR_XX_OT)是用ORL
  1.     30:                         if(!TMR_01_OT)
  2. C:0x0013    E509     MOV      A,0x09
  3. C:0x0015    4508     ORL      A,TMR_01_OT(0x08)
  4. C:0x0017    70FA     JNZ      C:0013
  5.     31:                         {TMR_01_OT=270;
  6. C:0x0019    750801   MOV      TMR_01_OT(0x08),#0x01
  7. C:0x001C    75090E   MOV      0x09,#0x0E
复制代码



if(!TMR_XX.Dat_c[0] && !TMR_XX.Dat_c[1]) 是用两个JNZ
  1.     34:                         if(!TMR_XX.Dat_c[0] && !TMR_XX.Dat_c[1])
  2. C:0x0013    E508     MOV      A,TMR_XX(0x08)
  3. C:0x0015    70FC     JNZ      C:0013
  4. C:0x0017    E509     MOV      A,0x09
  5. C:0x0019    70F8     JNZ      C:0013
  6.     35:                         {TMR_XX.Dat_i=270;
  7. C:0x001B    750801   MOV      TMR_XX(0x08),#0x01
  8. C:0x001E    75090E   MOV      0x09,#0x0E
复制代码



是ORL有问题吗?
回复

使用道具 举报

ID:624769 发表于 2021-10-25 00:24 | 显示全部楼层
lkc8210 发表于 2021-10-24 21:38
改了后可以是可以,但是为什么1T会这样,12T就正常?

不是 1T 的问题, 12T也会有这个问题,只是相比1T不容易出现(或者说不容易显现出来)。而且你原程序,不是那么简单的几句吧?应该有更多的内容,这次是为了测试才变那么“迷你”的吧?这是好事,以后程序复杂了,你没有注意这点的话,发生“诡异”事件,反而更麻烦。

其实道理很简单,中断和主程序 或者 高级中断和低级中断之间传参的话,8位机(51属于8位机)必须避免16位传参,因为不可必免会发生这样的事情(程序越简单发生概率越高):主程序在处理16位的变量,处理了其中的8位,这个时候中断打断,改写了整个16位,这个时候返回主程序,主程序继续处理剩下没处理的8位(这8位已经是新的数据了),那么这个结果会错的离谱。
所以,一旦有16位甚至32位的数据在中断里累加累减计数,达到你预期值的时候,必须转换成一个标志(bit),然后让主程序按这个标志去执行。
回复

使用道具 举报

ID:401564 发表于 2021-10-25 08:34 | 显示全部楼层
lkc8210 发表于 2021-10-24 21:38
改了后可以是可以,但是为什么1T会这样,12T就正常?

这跟什么T的没有关系
12T能正常只是运气好而已,程序本身就是错误的
回复

使用道具 举报

ID:420836 发表于 2021-10-25 08:50 | 显示全部楼层
应该是代码的原因。 请仔细检查程序。
回复

使用道具 举报

ID:161164 发表于 2021-10-25 11:12 | 显示全部楼层
188610329 发表于 2021-10-25 00:24
不是 1T 的问题, 12T也会有这个问题,只是相比1T不容易出现(或者说不容易显现出来)。而且你原程序,不 ...

10楼有新发现但审批迟了

我试过停止定时器再赋值再运行定时器
TR0 = 0;TMR_XX_OT=270;TR0 = 1;
已经确保定时器中断不会打断变量的赋值
也会出现这种情况
回复

使用道具 举报

ID:161164 发表于 2021-10-25 11:16 | 显示全部楼层
wulin 发表于 2021-10-24 21:16
从逻辑上看程序没有问题,但同一个16位变量在主函数和中断中都可以操作容易出错,有前辈就此问题详细阐述 ...

10楼有新发现但审批迟了

我试过停止定时器再赋值再运行定时器
TR0 = 0;TMR_XX_OT=270;TR0 = 1;
已经确保定时器中断不会打断变量的赋值
也会出现这种情况
停止运行定时器和禁止定时中断在防止打断赋值的功能上是等价的吧?
回复

使用道具 举报

ID:161164 发表于 2021-10-25 11:22 | 显示全部楼层
TTQ001 发表于 2021-10-25 08:50
应该是代码的原因。 请仔细检查程序。

在STC89上可以正常运行
应该不会是代码或逻辑的问题
回复

使用道具 举报

ID:624769 发表于 2021-10-25 13:56 | 显示全部楼层
lkc8210 发表于 2021-10-25 11:12
10楼有新发现但审批迟了

我试过停止定时器再赋值再运行定时器

说了,不是赋值的问题,是判断的问题,
你要关定时器的话,得在 if 之前关,然后,整个 if 结束之后开才能彻底杜绝这个问题,而且,在if之前关,因为是15系列,要在ET0=0 之后放一个NOP,

其实,一个简单的传标志搞定的事情,不知道为啥你非要传short……
回复

使用道具 举报

ID:959346 发表于 2021-10-25 16:07 | 显示全部楼层
lkc8210 发表于 2021-10-25 11:22
在STC89上可以正常运行
应该不会是代码或逻辑的问题

TR0 = 0;TMR_XX_OT=270;TR0 = 1;
你在关闭定时器后清除一下定时器中断标志,然后加2个NOP,看看。
按你10楼回复,你把TMR_XX.Dat_i=270;改成插入汇编,然后先赋值低字节的,然后赋值高字节的,
MOV      0x09,#0x0E
MOV      TMR_XX(0x08),#0x01
或者直接TMR_XX.Dat_i=270;TMR_XX.Dat_i=270;写2次,
看看是什么情况?
回复

使用道具 举报

ID:123289 发表于 2021-10-25 17:31 | 显示全部楼层
都是不认真读手册的结果:

15.        TMOD &= 0xF0;                //设置定时器模式

在89C52中,这是13位定时器模式(注意不是16位),TH0TL0=FC18,中只有13位起作用,即时常数不是真正的FC18!
在STC中,设计者将定时器做了改进,增加了一个TH0TL0,的影子TH0TL0’,它们共用一个地址,这样做的好处是,可以进行16自动重装,所以在
在STC中,它是16位自动重装模式,时常数是真正的16位:FC18!
回复

使用道具 举报

ID:57657 发表于 2021-10-25 19:38 | 显示全部楼层
你遇到的问题和这个程序一样,很多新人都会遇到:
  1. #include "reg51.h"
  2. #include "intrins.h"
  3. #define u8 unsigned char
  4. #define u16 unsigned int
  5. u16 i;
  6. sbit CLK = P3 ^ 0;
  7. sbit ERR = P3 ^ 1;
  8. sbit ERR2 = P3 ^ 2;

  9. void InitTimer0(){
  10.     TMOD = 0x01;
  11.     TH0 = 0xFC;
  12.     TL0 = 0x18;
  13.     EA = 1;
  14.     ET0 = 1;
  15.     TR0 = 1;
  16. }

  17. void main() {
  18.     InitTimer0();
  19.     ERR = 1; ERR2 = 1;
  20.         while (1) {
  21.                 i = 0xABCD;     //16位变量:先赋值高8位,再赋值低8位
  22.                 i = 0x1234;     //触发中断只等待指令执行完成,不等待低8位赋值完成,中断返回后才会去赋值低8位
  23.         //正确写法:
  24.         //_push_(IE); IE = 0; i = 0xABCD; _pop_(IE);
  25.         //_push_(IE); IE = 0; i = 0x1234; _pop_(IE);
  26.         }
  27. }

  28. void Timer0Interrupt() interrupt 1{
  29.     TH0 = 0xFC;
  30.     TL0 = 0x18;
  31.     CLK = !CLK;
  32.     if (i != 0xABCD && i != 0x1234) {       //中断读取变量i,既不是0xABCD,也不是0x1234
  33.         ERR = 0;        //读取出错点亮
  34.         if (i != 0xAB34 && i != 0x12CD) {   //如果读取出错,会读到这两个值
  35.             ERR2 = 0;       //此灯如果被点亮则程序跑飞
  36.         }
  37.     }

  38. }
复制代码
回复

使用道具 举报

ID:161164 发表于 2021-10-26 01:02 | 显示全部楼层
188610329 发表于 2021-10-25 13:56
说了,不是赋值的问题,是判断的问题,
你要关定时器的话,得在 if 之前关,然后,整个 if 结束之后开才能彻 ...

因为一个TMR_XX就加一个传标志
如果有几个TMR_XX就要加几个传标志

借鉴了你的代码
已找到解决方法
回复

使用道具 举报

ID:161164 发表于 2021-10-26 01:07 | 显示全部楼层
Jiang_YY 发表于 2021-10-25 16:07
TR0 = 0;TMR_XX_OT=270;TR0 = 1;
你在关闭定时器后清除一下定时器中断标志,然后加2个NOP,看看。
按你 ...

不懂如何插入汇编
试了赋值两次,都是重覆那个问题

已找到解决方法
回复

使用道具 举报

ID:161164 发表于 2021-10-26 01:10 | 显示全部楼层
yzwzfyz 发表于 2021-10-25 17:31
都是不认真读手册的结果:

15.        TMOD &= 0xF0;                //设置定时器模式

你是不认真看贴子的结果

就算是用了13位定时器也不会出现闪烁不均匀的情况
回复

使用道具 举报

ID:161164 发表于 2021-10-26 01:14 | 显示全部楼层
npn 发表于 2021-10-25 19:38
你遇到的问题和这个程序一样,很多新人都会遇到:

有点覆杂

借鉴了你, 188610329总和wulin总的回覆
得出改善的代码如下

  1. #include <reg52.h>

  2. typedef         unsigned char        u8;  //0 to 255
  3. typedef         unsigned int        u16;  //0 to 65535
  4. typedef         unsigned long        u32;  //0 to 4294967295
  5. sbit LED1=P3^5;
  6. sbit LED2=P3^4;
  7. sbit LED3=P3^3;
  8. sbit LED4=P3^2;
  9. bit T0_INT;
  10. u16 TMR_01_OT;
  11. u16 TMR_02_OT;
  12. u16 TMR_03_OT;
  13. u16 TMR_04_OT;

  14. sfr AUXR = 0x8E;
  15. void Timer0Init(void)                //1毫秒@12.000MHz
  16. {
  17.         AUXR &= 0x7F;                //定时器时钟12T模式
  18.         TMOD &= 0xF0;                //设置定时器模式
  19.         TMOD |= 0x00;                //设置定时器模式
  20.         TL0 = 0x18;                //设置定时初始值
  21.         TH0 = 0xFC;                //设置定时初始值
  22.         TF0 = 0;                //清除TF0标志
  23.         TR0 = 1;                //定时器0开始计时
  24. }
  25. void timer0_int (void) interrupt 1
  26. {
  27.         INT0_EN = 1;
  28. }

  29. void main()
  30. {
  31.     Timer0Init();
  32.                 ET0 = 1;
  33.                 EA = 1;
  34.     while(1)
  35.                 {                       
  36.                         if(!TMR_01_OT)
  37.                         {TMR_01_OT=270;
  38.                                 LED1 = !LED1;
  39.                         }
  40.                         if(!TMR_02_OT)
  41.                         {TMR_02_OT=273;
  42.                                 LED2 = !LED2;
  43.                         }
  44.                         if(!TMR_03_OT)
  45.                         {TMR_03_OT=277;
  46.                                 LED3 = !LED3;
  47.                         }
  48.                         if(!TMR_04_OT)
  49.                         {TMR_04_OT=281;
  50.                                 LED4 = !LED4;
  51.                         }
  52.                         if(INT0_EN)
  53.                         {
  54.                                 INT0_EN = 0;
  55.                                 if(TMR_01_OT)TMR_01_OT--;
  56.                                 if(TMR_02_OT)TMR_02_OT--;
  57.                                 if(TMR_03_OT)TMR_03_OT--;
  58.                                 if(TMR_04_OT)TMR_04_OT--;
  59.                         }
  60.                 }
  61. }
复制代码
回复

使用道具 举报

ID:624769 发表于 2021-10-26 17:59 | 显示全部楼层
lkc8210 发表于 2021-10-26 01:14
有点覆杂

借鉴了你, 188610329总和wulin总的回覆

虽然,你这个修正解决了你目前的问题,但是,这个方案是有缺陷的:如果你程序比较长,定时器定时比较短,会出现掉帧的问题,即:当定时器把 INT0_EN 置1之后,你的主程序还没有来的及判断 INT0_EN 是否为1,你的定时器再次触发,又一次把 INT0_EN 置一, 这个时候,你的主程序 才开始第一次判断,那么就会少减一次(那么频率就会不对)。所以这时候,通常会用传char来避免这样的情况发生。

代码如下:

unsigned char INT0_Times,Temp;

void timer0_int (void) interrupt 1
{
        INT0_Times++;
}

void main()
{
    Timer0Init();
                ET0 = 1;
                EA = 1;
    while(1)
                {                        
                         if(INT0_Times != 0)                        {
                               Temp = INT0_Times;                               INT0_Times  -= Temp;     //防止这个时候中断触发又改写了  INT0_Times  所以不直接 = 0,而用减法
                               TMR_01_OT  -= Temp;
                               if(CY)                       //有借位则反转LED以及赋新值
                               {
                                   TMR_01_OT  +=270;
                                   LED1 = !LED1;
                                }
                                TMR_02_OT -=  Temp;
                                if(CY)
                               {         
                                    TMR_02_OT   +=273;
                                    LED2 = !LED2;
                                }                                TMR_03_OT -=  Temp;
                                if(CY)
                               {
                                     TMR_03_OT   +=277;
                                     LED3 = !LED3;
                               }                               TMR_04_OT -=  Temp;
                               if(CY)
                               {
                                     TMR_04_OT   +=281;
                                      LED4 = !LED4;
                               }
                        }
                }
}




这样,就能避免掉帧的问题,并且如果这次少了1拍,下次会补回来,总的频率不会发生变化,你可以参考一下。
回复

使用道具 举报

ID:161164 发表于 2021-10-28 00:55 | 显示全部楼层
188610329 发表于 2021-10-26 17:59
虽然,你这个修正解决了你目前的问题,但是,这个方案是有缺陷的:如果你程序比较长,定时器定时比较短, ...

受教了
回复

使用道具 举报

ID:235055 发表于 2022-1-5 22:23 | 显示全部楼层
怎么后面的几个程序中定时器中断响应后没有重复初值了?
回复

使用道具 举报

ID:155507 发表于 2022-1-5 22:52 | 显示全部楼层
sunny118 发表于 2022-1-5 22:23
怎么后面的几个程序中定时器中断响应后没有重复初值了?

因为是 自动重装
回复

使用道具 举报

ID:161164 发表于 2022-1-5 22:54 | 显示全部楼层
sunny118 发表于 2022-1-5 22:23
怎么后面的几个程序中定时器中断响应后没有重复初值了?

2022-01-05_225356.png
回复

使用道具 举报

ID:235055 发表于 2022-1-6 22:49 | 显示全部楼层
受教了,还以为是13位的方式0呢!
回复

使用道具 举报

ID:476652 发表于 2022-1-28 12:50 | 显示全部楼层
大神过招,我只能看来看去,啥也看不懂!!
回复

使用道具 举报

ID:849913 发表于 2024-7-7 16:42 | 显示全部楼层
引脚设置了 吗?P0M0 P0M1--P7M0 P7M1
回复

使用道具 举报

ID:161164 发表于 2024-7-7 23:10 | 显示全部楼层
老董 发表于 2024-7-7 16:42
引脚设置了 吗?P0M0 P0M1--P7M0 P7M1

不知道为啥旧帖子被推上来了
问题已经解决很久了
就是8楼wulin 说的原因
8位机要避免同时在中断和主循环运算和比较多于8位的变量
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表