Skip to content

51单片机定时器

前提条件

什么是定时器?

时钟 -> 计数单元 -> 中断系统

  1. 定时器中有一个脉冲计数器, 每来一个脉冲计数加1(也可能是每多个脉冲加1, 可设置).
  2. 当计数器达到定时器中最大值时, 定时器会向中断系统发出中断申请.
  3. 单片机主程序暂停, 转而运行中断服务函数.

下面以 STC89C52RC 定时器0为例进行进一步讲解.

根据STC89C52RC数据表, 7.1 定时器/计数器0/1 章节中的内容. 了解到定时器一共有三部分组成. 时钟源, 控制开关, 计数器 (非专业名词, 仅用于指代下方图片中内容). picture 0

  1. 时钟源可以是内部时钟, 也可以是外部时钟. 如果是内部时钟, 那么要么是12个时钟计数器加1(12T模式), 要么就是6个时钟计数器加1(6T模式).
  2. 当计数器达到最大时(模式1为 2^16 = 65536), 中断标识位TF0会由硬件置为1, 向CPU请求中断, 直到中断被响应后, TF0重新被硬件置为0
  3. 控制开关涉及三个寄存器, 对于定时器0, 只有TR0被程序置为1时, 定时器才开始运作.

每秒切换一次LED灯的状态

理解完上述原理后, 我们开始构思程序.

  1. 定义一个中断服务函数, 每1ms由定时器触发一次, 累计触发1000次后切换 LED 灯状态
  2. 根据开发板原理图了解到时钟源是内部还是外部, 时钟频率是多少. 据此计算出每 1ms 定时器计数器会增加的值, 最后结合计数器的最大值, 计算出计数器的初始值. 比如说, 当前开发板上有一颗 11.0592MHz 的晶振, 接在了单片机的XTAL1XTAL2引脚上作为单片机的系统时钟.
    • 那么每秒晶振输出 11059200 个脉冲, 每秒计时器增加 (11059200 / 12) = 921600
    • 每 ms 计时器增加 (921600 / 1000) = 921.6
    • 假设定时器工作在模式1, 那么计数器的最大值为 65536, 在该模式下如果在程序初始化时, 程序将计数器的值设置为 (65536 - 922) = 64614. 那么 1ms 后, 计数器的值将达到65536, 中断触发.
    • 如果在中断服务函数中再次将计数器的值设置为 64614, 那么 1ms 后, 计数器的值将达到65536, 中断触发. 结果就是之后中断服务函数每1ms会被运行一次.
  3. 定义一段初始化程序, 它应当具备一下功能. 配置定时器的工作模式, 启动定时器, 启动中断服务.

示例代码 codes/demo204-51-timer

c
#include <8052.h>
#include "led.h"
#include "math.h"

#define OSCILLATOR_FREQUENCY  11059200L
// Counter number in 1ms (12T mode)
#define COUNTER_NUM_FOR_1MS       (OSCILLATOR_FREQUENCY/12/1000)
#define TIMER_1_START_NUM         (65536 - COUNTER_NUM_FOR_1MS)

int count = 0;

/*
 * Timer0 interrupt routine
 * Note: __interrupt (1) is a SDCC specific keyword,
 * other compilers use different keywords
 */
void timer0_isr(void) __interrupt (1) {
  TL0 = get_low_bit(TIMER_1_START_NUM);
  TH0 = get_high_bit(TIMER_1_START_NUM);

  if (count == 1000) {
    count = 0;
    toggle_led_state(D2);
  }
  count++;
}

void setup_timer0(void) {
  TMOD &= 0xF0;     // Clear the lower 4 bits of TMOD,
  TMOD |= 0x01;     // Timer 0, internal oscillator, mode1 (16-bit)
  TL0 = get_low_bit(TIMER_1_START_NUM);
  TH0 = get_high_bit(TIMER_1_START_NUM);
  TR0 = 1;          // Start Timer 0

  EA = 1;           // Enable global interrupts
  ET0 = 1;          // Enable Timer 0 interrupt
}

void main(void) {
  setup_timer0();

  while (1) {
  }
}
h
#include <8052.h>

#ifndef LED_H
#define LED_H

typedef enum {
  D1,
  D2,
  D3,
  D4,
  D5,
  D6,
  D7,
  D8,
} LedName;

typedef enum {
  OFF = 0,
  ON = 1,
} LedState;

void control_led(LedName led_name, LedState led_state);
LedState read_led_state(LedName led_name);
void toggle_led_state(LedName led_name);

#endif
c
#include "led.h"

void control_led(LedName led_name, LedState led_state)
{
  unsigned int val = led_state == ON ? 0 : 1;
  switch (led_name)
  {
  case D1:
    P2_0 = val;
    break;
  case D2:
    P2_1 = val;
    break;
  case D3:
    P2_2 = val;
    break;
  case D4:
    P2_3 = val;
    break;
  case D5:
    P2_4 = val;
    break;
  case D6:
    P2_5 = val;
    break;
  case D7:
    P2_6 = val;
    break;
  case D8:
    P2_7 = val;
    break;

  default:
    break;
  }
}

LedState read_led_state(LedName led_name) {
  switch (led_name)
  {
  case D1:
    return P2_0 == 0 ? ON : OFF;
  case D2:
    return P2_1 == 0 ? ON : OFF;
    break;
  case D3:
    return P2_2 == 0 ? ON : OFF;
    break;
  case D4:
    return P2_3 == 0 ? ON : OFF;
    break;
  case D5:
    return P2_4 == 0 ? ON : OFF;
    break;
  case D6:
    return P2_5 == 0 ? ON : OFF;
    break;
  case D7:
    return P2_6 == 0 ? ON : OFF;
    break;
  case D8:
    return P2_7 == 0 ? ON : OFF;
    break;

  default:
    return OFF;
  }
}

void toggle_led_state(LedName led_name) {
  LedState led_state = read_led_state(led_name);
  control_led(led_name, led_state == ON ? OFF : ON);
}
h
#include <stdint.h>

#ifndef MATH_H
#define MATH_H

uint8_t get_high_bit(uint16_t number);
uint8_t get_low_bit(uint16_t number);

#endif
c
#include "math.h"

uint8_t get_high_bit(uint16_t number) {
  return (number >> 8) & 0xFF;
}

uint8_t get_low_bit(uint16_t number) {
  return number & 0xFF;
}