你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

51单片机“定时器”,你知道怎么理解与应用吗?

[复制链接]
gaosmile 发布时间:2020-3-12 10:57
在 cpu 的一步步按照指令运行的过程中(主程序),可能会有其它的更紧急的需要做的事情(中断服务程序), 需要 cpu 暂时停止当前的程序(主程序),做完了(中断服务程序)之后,又可以继续去运行先前的程序(主程序)。

  C4 X; K* ~2 L2 s, ?! _3 O
就像你正在吃饭,一边又在给水桶里放水,吃着吃着,水满了,你就得赶快去把水龙头关掉或者换一个空的水桶,再回来吃饭。

0 c) M( |. x; u5 h. b


- r/ E1 @* j. ?: \  }' B' R$ }5 }
单片机的定时器就像是一个水桶
你让它启动了,也就是水龙头打开放水
定时器在每个机器周期自动加 1
水桶的水不断增加,最后就满出来了
定时器溢出时,你就要去做处理了
水桶的水满了,你应该处理一下了
处理完后,单片机又回到刚停止地方继续运行
水桶处理完,你也可以去做原来的事了
! v: [% M& d. q' x

微信图片_20200312105005.jpg
* p: J/ v4 q! Y- |) K

4 T6 T5 `: f* Y# m: r- v8 y
单片机的主程序是从 0x0000 开始运行的,
单片机服务程序从哪里开始运行呢?
4 M- {6 l! o! Y! p5 _+ e% E
在 51里,有多个中断服务程序入口,
0号入口是外中断 0,地址在 0x0003;
1号入口是定时器 0,地址在 0x000B;
2号入口是外中断 1,地址在 0x0013,
3号入口是定时器 2,地址在 0x001B。

% l, p0 A; L* a# }; ^- X3 M
当中断发生时,程序就记下当前运行的位置,跳到对应的中断入口去运行中断服务程序,运行完之后,又跳回到原来的位置继续运行。
3 ^, A  |! F7 s
在 C51 中,你不用理会中断服务程序放在哪里,会怎么跳转。你只要把某个函数标识为几号中断服务函数就可以了。在发生了对应的中断时,就会自动的运行这个函数。
  w( D. d& \9 ]' R5 ?) Q
我们将学习如何精确定时 1 秒钟闪灯。这里我们使用 T2 定时器,让它工作在 16bit 自动装载方式,这时,有另一个位置专门装着 16 位预装载值, T2 溢出时,预装载值立即被置入。这就保证了精确定时。
4 q8 p' ]  W  I  f
但是,即使是 16 位定时器,最长的溢出时间也就几十毫秒,要定时一秒,就需要一个变量来保存溢出的次数,积累到了多少次之后,才执行一次操作。这样就可以累加到 1 秒或者更长的时间才做一次操作了。T2 定时器有个特殊的地方,它进入中断后,需要自己清除溢出标记,而 51 的其他定时器是自动清除的。请参考 51 单片机相关书籍。3 w& _, a! @! O: J
6 @% F/ A7 F4 q6 \) h


, W  i: b& z$ a% y; ?. Z2 b9 I
如果使用 T2 定时器实现 1 秒精确定时?
6 U' R* T) t8 O; `3 ]# F/ ^7 ^0 k6 h
仿真器的晶振是 22118400 Hz,
每秒钟可以执行 1843200 个机器周期。

( p- k' N( y, K  r7 L
T2 每次溢出最多 65536 个机器周期。
我们尽量应该让溢出中断的次数最少,
这样对主程序的干扰也就最小。

+ ]& B5 F2 @1 s) S% U+ h
选择每秒中断24次
每次溢出1843200/24=76800个周期
(超出 65536,无效)

( h, s$ X. L: |5 X( d% E
选择每秒中断30次
每次溢出1843200/30=61440个周期
# G- a' l/ w9 h1 s
选择每秒中断32次
每次溢出1843200/32=57600个周期

4 u- |; ^* l$ H3 {  W2 S
选择每秒中断36次
每次溢出1843200/36=51200个周期
6 I7 Q) h. U5 l' J
选择每秒中断40次
每次溢出1843200/40=46080个周期
! g  w8 Q9 g" Y- k0 Z; X
从上面可以看到我们可以选择方式有很多,
但是最佳的是每秒中断 30 次,
每次溢出 61440 个机器周期。

# k% O! a. p, S- Q. {
也就是赋定时器T2初值
65536-61440=4096
换成十六进制就是 0x1000
/ J  _( D7 k) z2 r7 S1 T6 z
从上面的计算也可以看出晶振 2118400Hz 的好处,它可以整除的倍数多,要准确定时非常方便。更常见的应用是在串口波特率上,使用 22118400HZ 可以输出最多准确的标准波特率。
* {  o1 H6 o/ ~3 j1 F1 y3 _$ Y# [
微信图片_20200312105010.jpg
  X' W* f" T: B3 x" n4 b0 s/ K1 q
0 ~& W; o) M/ c* q: ~& t# m
我们在定时器服务函数里,设置了一个静态变量t,静态变量的值在进入函数时是不会被初始化的,而是保持上次的值。它用来计数定时器的溢出次数,也就是 T2 中断函数进入的次数,每溢出 30 次,就控制一次 LED 的反转显示。这时的时间就正好是 1 秒,而且是精确的 1 秒!只与晶振的精度有关。
8 f+ {7 X9 _- X* q9 M
一个编程经验是,
所有的中断都要尽快的运行和退出,
中断服务程序越短越好,
这样才不至于干扰主程序的工作,
以及其他中断的运行

; e* ~1 k8 ?7 e& o6 Q8 T6 B1 j5 _0 L
也就是,我们应该尽量把程序代码从中断服务函数里搬出来。对于定时器的中断的工作方式,我们可以建立一个全局的标记,在中断里置这个标记,然后就退出。在主程序里检查到这个标记之后,就运行相关的程序。
7 }3 |. C/ R4 \/ G: W3 c
对于 CPU 任务比较多的项目来说,这种工作方式可以获得最佳的工作效率。当然,对于非常实时的应用要求,比如时钟,还是建议在中断里做完,因为使用标记的方式时,主程序可能太忙而造成错过标记信号,就是这个标记还没有开始处理呢,下一个又来了。熟练的程序员还是可以避开这些异常的情况的。

. }/ y! A+ A1 V0 t- @$ [* C
在我们的这个例程中,前面的 1 秒钟输出信号,被换成了一个全局标记。在主程序中去检查这个标记,再清 0 标记和处理相应的工作。这一课的跑马灯输出方式也改变了,我们采用查表的方式,将要点亮的灯预先设置好,到了时间,就一起送到 P1 口。这样,程序的执行效率会更高。
' h, Z) _( U1 q) @6 L
+ n4 U% A" B0 N
下面认真学习和分析例程

6 n6 b* U/ q  A3 A( _0 ]8 z
#define uchar unsigned char //定义一下方便使用
* v; z1 H) a% G8 T# B#define uint unsigned int
+ \0 ^/ b3 j% C#define ulong unsigned long& _; H. c# w$ Y$ O
#include <reg52.h> //包括一个 52 标准内核的头文件
: @9 E& _, U* D3 Tsbit P10 = P1^0;
//头文件中没有定义的 IO 就要自己来定义了
. N' t9 J, M% O5 Q5 Usbit P11 = P1^1;9 f  I0 Q6 Y7 y2 ?1 ]  r# h
sbit P12 = P1^2;/ G' @: ~8 {" y! }
sbit P13 = P1^3;
" R+ h& z, \- d2 nbit ldelay=0; //长定时溢出标记,预置是 03 u( y4 N; [+ h6 u8 ~8 b
char code dx516[3] _at_ 0x003b;
//这是为了仿真设置的
# y( c, r, o: V3 A//定时器中断方式的跑马灯
# S. u2 `$ u- o" P$ h* `void main(void) // 主程序, u* K  V3 Y  m/ l" R. t5 Z
{
, E* j+ U: r$ u1 S- ]3 w0 n: ~uchar code ledp[4]={0xfe,0xfd,0xfb,0xf7};
//预定的写入 P1 的值9 y& Y/ g3 V. Z$ j
uchar ledi; //用来指示显示顺序7 I3 L3 X& q! b. i- |4 M
RCAP2H =0x10;
//赋 T2 的预置值 0x1000,溢出 30 次就是 1 秒钟
2 ^2 w: Y$ d9 l7 W. {" RRCAP2L =0x00;: E+ A& E0 _/ Q7 r
TR2=1; //启动定时器
4 B" a* W" X5 W7 K7 F& L. U: J' bET2=1; //打开定时器 2 中断. r' @1 a6 i; z5 E' |
EA=1; //打开总中断3 Z3 W) N: b. ~3 y. q3 C. f
while(1) //主程序循环
. Q; I  C) b$ B- b{
- a" Q; a5 _& ]: cif(ldelay) //发现有时间溢出标记,进入处理! p4 H% M( y; q1 ~) C' g  _$ y
{2 s8 ?7 [! z; V8 U& i2 n
ldelay=0; //清除标记
2 Q2 s' d# }$ |P1=ledp[ledi]; //读出一个值送到 P1 口% j( A- F7 g3 P' T9 x0 Y
ledi++; //指向下一个
3 X+ U6 w" R) c" O5 `if(ledi==4)ledi=0; //到了最后一个灯就换到第一个( |+ R! L9 J6 C. D6 y" `' g
}}! N1 q. N4 E" C3 Y$ E# [. [
}1 h0 l* _3 @  b4 s6 B; q/ S
//定时器 2 中断0 v+ P! G! V$ P# N4 Y2 f. b( i! V, ?
timer0() interrupt 5
# s8 b, r% _$ M, y9 n3 X$ j{/ G  E5 ]5 v  e6 U$ o
static uchar t;; M( V' y2 z- K$ m
TF2=0;
6 S. `7 N3 m4 Z3 v8 A) T2 wt++;5 M: h$ ]1 v% N$ _- W" ?' ?3 u$ _
if(t==30)
//T2 的预置值 0x1000,溢出 30 次就是 1 秒钟,
//晶振 22118400HZ9 d$ I/ j' T9 \! P! ?; B
{
4 P& j' U9 D: i+ O; i2 E. W# Ft=0;
$ _9 \. _7 z( F! nldelay=1;
//每次长时间的溢出,就置一个标记,以便主程序处理
( f9 m4 y% D$ N; i: b# w' a}
  J# a7 P4 S8 C/ B7 I0 \- V1 C  c}
赞 收藏 评论0 发布时间:2020-3-12 10:57

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版