写一个延迟子程序?

发布于 2024-10-19 17:27:39 字数 151 浏览 2 评论 0原文

我需要编写一个延迟子程序。它应该延迟大约 1 秒。它必须适用于 8051 环境、DS89C430 微控制器 (11.0592 MHz XTAL)。我该如何编写这个子程序?

Delay1sec: ...
....
...
....
...

I need to write a delay subroutine. It should delay about 1 second. It has to be for 8051 environment, DS89C430 micrcontroller ( 11.0592 MHz XTAL). How can I write this subroutine?

Delay1sec: ...
....
...
....
...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

一片旧的回忆 2024-10-26 17:27:39

要获得在中断期间也有效的精确 1 秒延迟,您需要使用硬件定时器,而不是软件定时器。我建议您使用可用的板载计时器之一按照 Jerry Coffin 的建议< /a>.

这是一种涉及内置定时器和计数定时器溢出的方法。由于运行定时器默认每 12 个时钟周期更新一次以保持与 8051 的兼容性,因此每秒将更新 921,600 次。一点乘法告诉我们,从 0 计数到 46080 需要 50 ms,这也告诉我们,我们可以在 19456 处启动一个 16 位定时器,等待它溢出 ​​20 次来延迟 1 秒*

代码可能如下所示:

        CLR     T0M          ; set timer 0 to use a divide-by-12 of
                             ; the crystal frequency (default)

        MOV     R0,TMOD      ; set timer 0 to 16-bit mode without
        ORL     R0,#01h      ; affecting the setup of timer 1
        MOV     TMOD,R0

        LCALL   Delay1sec    ; call the delay subroutine

Delay1sec:
        MOV     R0,#20d      ; set loop count to 20

loop:   CLR     TR0          ; start each loop with the timer stopped
        CLR     TF0          ; and the overflow flag clear. setup
        MOV     TH0,#4Ch     ; timer 0 to overflow in 50 ms, start the
        MOV     TL0,#00h     ; timer, wait for overflow, then repeat
        SETB    TR0          ; until the loop count is exhausted
        JNB     TF0,$
        DJNZ    R0,loop
        RET   

注意:示例中不包括指令执行时间的开销。

* 数学如何分解:
11059200 / 12 = 921600
0.05 * 921600 = 46080
65536 - 46080 = 19456 = 0x4C00


软件延迟循环浪费处理器时间并受到中断的干扰。话虽这么说,您可以通过硬编码的方式来完成。

一种方法涉及了解每个机器周期的时钟周期数以及执行各种指令所需的机器周期数。根据数据表,DS89C430通常使用一台机器每个指令字节需要一个周期,执行需要一个周期。每条指令的周期数在 Ultra-高速闪存微控制器用户指南

由于您的晶体频率为 11.0592 MHz,因此您的例程将需要延迟 11,059,200 个时钟周期。这通常是通过已知长度的嵌套循环来完成的,然后包括任何额外的循环设置以及可能的子例程调用和返回指令*

该函数可能如下所示:

Delay1sec:                   ; <------------------------------+
;       LCALL   Delay1sec    ; 3 cycles                       |
        MOV     R2,#42d      ; 2 cycles                       |
        MOV     R1,#00d      ; 2 cycles                       |
        MOV     R0,#00d      ; 2 cycles                       |
loop:   DJNZ    R0,loop      ; 4 cycles <-- l1 <- l2 <- l3    Delay1sec
        DJNZ    R1,loop      ; 4 cycles <---------+     |     |
        DJNZ    R2,loop      ; 4 cycles <---------------+     |
        RET                  ; 3 cycles <---------------------+

让我们看看数学如何分解**

l1 = 4 * 256 = 1024 个周期
l2 = (1024 + 4) * 256 = 263168 个周期
l3 = (263168 + 4) * 42 = 11053224 个周期
Delay1sec = 11072668 + 3 + 2 + 2 + 2 + 3 = 11053236 个周期

11053236 个周期 * 1/11059200 秒/周期 = 999.461 ms

* 子程序调用和返回指令可以根据需要省略。
** 我使用 Microsoft Excel 来协助进行与确定循环计数器相关的计算。

To get an exact 1 second delay that also works during interrupts, you need to use a hardware timer, not a software timer. I would advise you to use one of the available on-board timers as suggested by Jerry Coffin.

Here's an approach involving a built-in timer and counting timer overflows. Since the running timer is updated every 12 clock cycles by default to maintain compatibility with the 8051, it will be updated 921,600 times per second. A bit of multiplication tells us that it takes 50 ms to count from 0 to 46080, which also tells us that we can start a 16-bit timer at 19456 and wait for it to overflow 20 times to delay for 1 second*.

The code could look something like this:

        CLR     T0M          ; set timer 0 to use a divide-by-12 of
                             ; the crystal frequency (default)

        MOV     R0,TMOD      ; set timer 0 to 16-bit mode without
        ORL     R0,#01h      ; affecting the setup of timer 1
        MOV     TMOD,R0

        LCALL   Delay1sec    ; call the delay subroutine

Delay1sec:
        MOV     R0,#20d      ; set loop count to 20

loop:   CLR     TR0          ; start each loop with the timer stopped
        CLR     TF0          ; and the overflow flag clear. setup
        MOV     TH0,#4Ch     ; timer 0 to overflow in 50 ms, start the
        MOV     TL0,#00h     ; timer, wait for overflow, then repeat
        SETB    TR0          ; until the loop count is exhausted
        JNB     TF0,$
        DJNZ    R0,loop
        RET   

Note: Overhead of instruction execution time excluded from example.

* How the math breaks down:
11059200 / 12 = 921600
0.05 * 921600 = 46080
65536 - 46080 = 19456 = 0x4C00


Software delay loops waste processor time and are disturbed by interrupts. That being said, you can do it the hardcoded way.

One approach involves knowing the number of clock cycles per machine cycle and the number of machine cycles various instructions take to execute. According to the data sheet, the DS89C430 usually uses one machine cycle for each instruction byte and requires one cycle for execution. The number of cycles for each instruction is provided in the Ultra-High-Speed Flash Microcontroller User's Guide.

Since your crystal frequency is 11.0592 MHz, your routine will need to delay for 11,059,200 clock cycles. This is often accomplished via nested loops of known length and then including any extra loop setup and possibly subroutine call and return instructions*.

The function could look something like this:

Delay1sec:                   ; <------------------------------+
;       LCALL   Delay1sec    ; 3 cycles                       |
        MOV     R2,#42d      ; 2 cycles                       |
        MOV     R1,#00d      ; 2 cycles                       |
        MOV     R0,#00d      ; 2 cycles                       |
loop:   DJNZ    R0,loop      ; 4 cycles <-- l1 <- l2 <- l3    Delay1sec
        DJNZ    R1,loop      ; 4 cycles <---------+     |     |
        DJNZ    R2,loop      ; 4 cycles <---------------+     |
        RET                  ; 3 cycles <---------------------+

Let's see how the math breaks down**:

l1 = 4 * 256 = 1024 cycles
l2 = (1024 + 4) * 256 = 263168 cycles
l3 = (263168 + 4) * 42 = 11053224 cycles
Delay1sec = 11072668 + 3 + 2 + 2 + 2 + 3 = 11053236 cycles

11053236 cycles * 1/11059200 seconds/cycle = 999.461 ms

* The subroutine call and return instructions may be omitted as necessary.
** I used Microsoft Excel to assist with calculations related to determining loop counters.

飘过的浮云 2024-10-26 17:27:39

This microcontroller has three on-board timers (see section 11 of the User's Manual) connected to the system clock (divided by 12), so it's a matter of programming them to generate an interrupt when the time expires. Since the divided input is just under 1 MHz, and the largest counter is 16 bits, you'll need to count 14 interrupts to reach one second (at least if I've done the math correctly).

厌倦 2024-10-26 17:27:39

A) 引用硬件定时器。

B) 引用 CPU 定时器。一些处理器有一个非常宽的定时器,即 64 位宽,在时钟滴答处运行。

C) 软件循环。为了获得最佳结果,代码和所有数据应驻留在具有可预测时序的内部存储器中。从 SDRAM 运行可能会导致时序问题。

您不必计算装配周期即可完成此操作。相反,您可以在引脚上“绘制”脉冲(在循环之前拉高,在循环之后拉低),使用逻辑分析仪测量脉冲宽度,然后更改循环计数以调整时序。为了获得最佳结果,您应该通过使用频率计数器来测量外部 CPU 时钟/晶体来进行补偿,然后对偏离中心频率的情况进行补偿,因为大多数便宜的晶体都不是准确的。

您可以通过使用计时器来计算循环时间来进行自校准。

A) Reference a hardware timer.

B) Reference a CPU timer. Some processors have a very wide timer, i.e. 64-bit wide, that runs at the clock tick.

C) Software Loop. For best results, the code and all data should reside in internal memory that has predictable timing. Running from SDRAM can cause timing problems.

You don't have to count assembly cycles to do it. Instead you can "draw" a pulse on a pin (pull high before the loop, pull low after the loop), measure pulse width using a logic anlayzer, then change loop count to tweak your timings. For best results, you should compensate against the external CPU clock/crystal, by using a frequency counter to measure it, then compensate for being away from center frequency, because most cheap crystals aren't dead-on.

You could self-calibrate by using a timer to calculate the loop timing.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文