51单片机定时器
前提条件
- 已完成搭建51单片机开发环境
什么是定时器?
时钟 -> 计数单元 -> 中断系统
- 定时器中有一个脉冲计数器, 每来一个脉冲计数加1(也可能是每多个脉冲加1, 可设置).
- 当计数器达到定时器中最大值时, 定时器会向中断系统发出中断申请.
- 单片机主程序暂停, 转而运行中断服务函数.
下面以 STC89C52RC 定时器0为例进行进一步讲解.
根据STC89C52RC数据表
, 7.1 定时器/计数器0/1 章节中的内容. 了解到定时器一共有三部分组成. 时钟源, 控制开关, 计数器 (非专业名词, 仅用于指代下方图片中内容).
- 时钟源可以是内部时钟, 也可以是外部时钟. 如果是内部时钟, 那么要么是12个时钟计数器加1(12T模式), 要么就是6个时钟计数器加1(6T模式).
- 当计数器达到最大时(模式1为 2^16 = 65536), 中断标识位
TF0
会由硬件置为1, 向CPU请求中断, 直到中断被响应后,TF0
重新被硬件置为0 - 控制开关涉及三个寄存器, 对于定时器0, 只有TR0被程序置为1时, 定时器才开始运作.
每秒切换一次LED灯的状态
理解完上述原理后, 我们开始构思程序.
- 定义一个
中断服务函数
, 每1ms由定时器触发一次, 累计触发1000次后切换 LED 灯状态 - 根据开发板原理图了解到时钟源是内部还是外部, 时钟频率是多少. 据此计算出每 1ms 定时器计数器会增加的值, 最后结合计数器的最大值, 计算出
计数器的初始值
. 比如说, 当前开发板上有一颗 11.0592MHz 的晶振, 接在了单片机的XTAL1
和XTAL2
引脚上作为单片机的系统时钟.- 那么每秒晶振输出 11059200 个脉冲, 每秒计时器增加 (11059200 / 12) = 921600
- 每 ms 计时器增加 (921600 / 1000) = 921.6
- 假设定时器工作在模式1, 那么计数器的最大值为 65536, 在该模式下如果在程序初始化时, 程序将计数器的值设置为 (65536 - 922) = 64614. 那么 1ms 后, 计数器的值将达到65536, 中断触发.
- 如果在中断服务函数中再次将计数器的值设置为 64614, 那么 1ms 后, 计数器的值将达到65536, 中断触发. 结果就是之后中断服务函数每1ms会被运行一次.
- 定义一段
初始化程序
, 它应当具备一下功能. 配置定时器的工作模式, 启动定时器, 启动中断服务.
示例代码 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;
}