【疑问】CH549 单片机 Timer0 与 Timer1,在相同配置下,两者定时效果不一致?

MCU Myj ⋅ 于 2021-09-07 14:51:53 ⋅ 最后回复由 Myj 2021-09-08 01:12:04 ⋅ 194 阅读

使用CH549的外部中断与定时中断时发现的问题

为了实现 “由一个外部中断按键来启动/关闭定时器,由定时中断来控制LED亮灭” 的效果,同时验证中断优先级顺序。
参考VeriMake推出的CH549单片机教程 ——《以 CH549 为例的 51 教程 | 图文 3. 时钟、定时器中断与外部中断》一节,改写了一个小程序,出现了这样两个问题:

1.Timer1 的定时周期与预设理论值不符

2.外部中断0与Timer1定时中断,两者的优先级顺序与手册描述不符(或者外部中断0有失效的可能)

操作过程

选择外部中断0和定时中断1(Timer1),根据沁恒官方给的手册《CH549DS1》中对中断优先级的描述,外部中断0的中断号为0,定时中断1的中断号为3,外部中断0优先级较高。

file

那么,要实现 “由一个外部中断按键来启动/关闭定时器,由定时中断来控制LED亮灭”的效果,程序框架可以这样设计:

file

参考《以 CH549 为例的 51 教程 | 图文 3. 时钟、定时器中断与外部中断》一节中,

外部中断0 初始化(下降沿触发、开启外部中断0、开启总中断)

Timer1 定时中断初始化(16位定时工作,写TH1、TL1的定时初值、开启Timer1 定时中断、开启总中断、置位TR1 启动Timer1)

void INT0_Init()
{
  IT0 = 1;    //下降沿触发
  EX0 = 1;    //开启外部中断0
  EA = 1;    //开启总中断
}

void Timer1_Init() 
{
  TMOD |= 0x01;    //Timer1工作模式:16位定时工作
  TH1 = 0xFC;       //设置定时器初值为0xFC18,即64536,根据教程描述,可获得1ms的定时周期
  TL1 = 0x18;
  ET1 = 1;         //开启Timer1定时中断
  EA = 1;           //开启总中断
  TR1 = 1;         //启动Timer定时器
}

完整代码如下:

#include <CH549_sdcc.H>

#define Led P2_2        //Led引脚
#define Key P3_2        //外部中断0引脚
static int TimerInterruptFlag=0;         //用于定时器溢出记数,以获得更大的定时周期

void INT0_Init(void);
void Timer1_Init(void);
void Led_Init(void);
void delay(int time);

void main()
{
  Led_Init();    //Led初始化
  INT0_Init();    //外部中断0初始化
  Timer1_Init();    //Timer1中断初始化
  while(1){}
}

void Led_Init()
{
  Led = 0;
}

void INT0_Init()
{
  IT0 = 1;    //下降沿触发
  EX0 = 1;    //开启外部中断0
  EA = 1;    //开启总中断
}

void Timer1_Init() 
{
  TMOD |= 0x01;    //Timer1工作模式:16位定时工作
  TH1 = 0xFC;       //设置定时器初值为0xFC18,即64536,根据教程描述,可获得1ms的定时周期
  TL1 = 0x18;
  ET1 = 1;         //开启Timer1定时中断
  EA = 1;           //开启总中断
  TR1 = 1;         //启动Timer定时器
}

void INT0_Callback() __interrupt  0
{
  delay(10000);        //软件消除按键抖动
  if(Key == 0)
  {
    TR0 = !TR0;        //启动/停止定时器1
  }
}

void Timer1_Callback() __interrupt 3
{
  TH1 = 0xFC;        //定时器1溢出,触发中断处理时,重置定时器1的初值
  TL1 = 0x18;

  TimerInterruptFlag++;
  if(TimerInterruptFlag == 1000)        //以1ms定时周期为基础,获得1秒的周期
  {
    TimerInterruptFlag = 0;
    Led = !Led;        //改变Led亮灭状态
  }

}

void delay(int time)
{
  while(time--)
  {}
}

运行效果

设想的运行效果:Led灯两秒亮灭一次,各自持续1秒

实际的运行效果:目测Led灯两秒亮灭10次,并且按下外部中断按键不能切换Timer1的启动/停止状态

尝试查出问题的起因

首先查手册,检查操作定时器寄存器的方式是否有问题。
《CH549DS1》中对Timer0和Timer1的描述是在模式1下,两者都是16位定时器,并且寄存器操作方式是一致的。

file

file

file

既然Timer1与Timer0的操作方式一致,那么参考《以 CH549 为例的 51 教程 | 图文 3. 时钟、定时器中断与外部中断》一节中的程序,将其改写成Timer1定时中断的程序,所获得的定时周期理应是一致的,但是却有明显差异。

作为对比,我另写一个用Timer0作为定时中断的程序,运行效果就和设想的一致了,两秒亮灭一次,各自持续1秒,按下外部中断按键也能切换Timer0的启动/停止状态了。

使用Timer0的完整代码如下:

#include <CH549_sdcc.H>

#define Led P2_2
#define Key P3_2
static int TimerInterruptFlag=0;
void INT0_Init(void);
void Timer0_Init(void);
void Led_Init(void);
void delay(int time);

void main()
{
  Led_Init();
  INT0_Init();
  Timer0_Init();
  while(1){}
}

void Led_Init()
{
  Led = 0;
}

void INT0_Init()
{
  IT0 = 1;
  EX0 = 1;
  EA = 1;
}

void Timer0_Init() 
{
  TMOD |= 0x01;
  TH0 = 0xFC;
  TL0 = 0x18;
  ET0 = 1;
  EA = 1;
  TR0 = 1;
}

void INT0_Callback() __interrupt  0
{
  delay(10000);
  if(Key == 0)
  {
    TR0 = !TR0;
  }
}

void Timer0_Callback() __interrupt 1
{
  TH0 = 0xFC;
  TL0 = 0x18;

  TimerInterruptFlag++;
  if(TimerInterruptFlag==1000)
  {
    TimerInterruptFlag=0;
    Led = !Led;
  }
}

void delay(int time)
{
  while(time--)
  {}
}

为了进一步确认,我在使用Timer1的程序,将溢出计数变量TimerInterruptFlag的上限改为10000后,就能观察到Led两秒亮灭一次,各自持续1秒的现象了;但是按下外部中断0按键仍然不能切换Timer0的启动/停止状态。

小结

那么到这里,能够初步确定使用Timer1的程序中有这样两处问题:

1.Timer1 的定时周期与预设理论值不符

2.外部中断0与Timer1定时中断,两者的优先级顺序不正常(或者外部中断0有失效的可能)

虽然不能确定到底是我写的程序中对Timer1和外部中断0的操作有问题,还是单片机本身存在问题,或者是开发环境的问题?

不论哪种情况导致了这个问题,希望能有学习CH549单片机教程的同好们,帮我看看,复现一下,进而讨论出解决方法。

成为第一个点赞的人吧 :bowtie:
回复数量: 4
  • karb0n test
    2021-09-07 22:36:12

    你好,我发现你的第一段代码的按键中断里边控制的似乎是定时器0

    void INT0_Callback() __interrupt  0
    {
      delay(10000);        //软件消除按键抖动
      if(Key == 0)
      {
        TR0 = !TR0;        //启动/停止定时器1 ←注释和代码不一致
      }
    }
  • Myj
    2021-09-08 00:03:32

    @karb0n
    感谢指出错误,控制定时器1的启停应该通过TR1去操作,这里我是写错了。
    我改正这里的操作对象之后,能够用外部中断0来控制定时器1的启停了。

    但是定时器1的周期仍然是和计算值不符的。改正外部中断处理的程序贴在下边,希望能帮我再看一下。
    我第一次摸C51,有不对的地方还请多指教。

    改后的程序

    #include <CH549_sdcc.H>
    
    #define Led P2_2        //Led引脚
    #define Key P3_2        //外部中断0引脚
    static int TimerInterruptFlag=0;         //用于定时器溢出记数,以获得更大的定时周期
    
    void INT0_Init(void);
    void Timer1_Init(void);
    void Led_Init(void);
    void delay(int time);
    
    void main()
    {
      Led_Init();    //Led初始化
      INT0_Init();    //外部中断0初始化
      Timer1_Init();    //Timer1中断初始化
      while(1){}
    }
    
    void Led_Init()
    {
      Led = 0;
    }
    
    void INT0_Init()
    {
      IT0 = 1;    //下降沿触发
      EX0 = 1;    //开启外部中断0
      EA = 1;    //开启总中断
    }
    
    void Timer1_Init() 
    {
      TMOD |= 0x01;    //Timer1工作模式:16位定时工作
      TH1 = 0xFC;       //设置定时器初值为0xFC18,即64536,根据教程描述,可获得1ms的定时周期
      TL1 = 0x18;
      ET1 = 1;         //开启Timer1定时中断
      EA = 1;           //开启总中断
      TR1 = 1;         //启动Timer定时器
    }
    
    void INT0_Callback() __interrupt  0
    {
      delay(10000);        //软件消除按键抖动
      if(Key == 0)
      {
        TR1 = !TR1;        //启动/停止定时器1
      }
    }
    
    void Timer1_Callback() __interrupt 3
    {
      TH1 = 0xFC;        //定时器1溢出,触发中断处理时,重置定时器1的初值
      TL1 = 0x18;
    
      TimerInterruptFlag++;
      if(TimerInterruptFlag == 1000)        //以1ms定时周期为基础,获得1秒的周期
      {
        TimerInterruptFlag = 0;
        Led = !Led;        //改变Led亮灭状态
      }
    
    }
    
    void delay(int time)
    {
      while(time--)
      {}
    }
  • karb0n test
    2021-09-08 00:17:50

    @Myj 不客气。定时间隔不对的话,我刚才猜想可能是初始化定时器的那个函数里边给 TMOD 写入的值有问题,因为我发现你操作两个定时器的时候,都是给第 0 位写入 1。
    然后我看了下 CH549 芯片文档的第 31 页,TMOD 的第 0 位是控制定时器 0 的,而第 4 位才是控制定时器 1 的。可以试试改成对第 4 位写入 1。

  • Myj
    2021-09-08 01:12:04

    @karb0n
    感谢老哥,问题解决了。

    解决问题

    file
    file

    原来我写的 TMOD |= 0x01 ; 其实是设置了Timer0 模式1——16位 Timer1 模式0——13位

    应该写 TMOD |= 0x10 ; 设置Timer0 模式1——13位 Timer1 模式0——16位

    至于写错TMOD时为什么会获得预想周期(1ms)的十分之一

    file

    根据手册给出的说明,在13位模式下,TH 和 TL 的使用情况是 TH 的 0-7位 + TL 的 0-4 位,

    在16位模式下,要在1000次计数后溢出,需要设置初值为64536,确实是写 TH1 = 0xFC; TL1 = 0x18;

    但在13位模式下,TH1 是 0-7位有效 ,TL1 只有 0-4位有效 ,如果仍然写的是 TH1 = 0xFC; TL1 = 0x18;

    有效位所构成的13位二进制数,其实是 0x1F98 , 所以设置的初值是8088。

    (2^13 - 1) + 1 - 8088 = 104, 在默认Fsys = 12Mhz,12分频下,定时器1的溢出周期就是104 us,

    大约是 1ms 的十分之一,所以就目测到两秒内Led亮灭十次的现象了。

暂无评论~~
  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
Ctrl+Enter