Delay routines
Delay routines
Hi guys
What do you folks use for delay routines? Primarily, I wish to use it with the LCD module I ahve connected, but this will no doubt come up again.
Currently I use a NOP loop, but I suspect that there is a better way. Perhaps using a VIA timer? Or would adding a real time clock IC be better (interfaced with a VIA) - although that might not be so great if trying to measure milliseconds.
What do you folks use for delay routines? Primarily, I wish to use it with the LCD module I ahve connected, but this will no doubt come up again.
Currently I use a NOP loop, but I suspect that there is a better way. Perhaps using a VIA timer? Or would adding a real time clock IC be better (interfaced with a VIA) - although that might not be so great if trying to measure milliseconds.
- BigDumbDinosaur
- Posts: 9425
- Joined: 28 May 2009
- Location: Midwestern USA (JB Pritzker’s dystopia)
- Contact:
Re: Delay routines
banedon wrote:
What do you folks use for delay routines? Primarily, I wish to use it with the LCD module I ahve connected, but this will no doubt come up again.
Currently I use a NOP loop, but I suspect that there is a better way. Perhaps using a VIA timer? Or would adding a real time clock IC be better (interfaced with a VIA) - although that might not be so great if trying to measure milliseconds.
Currently I use a NOP loop, but I suspect that there is a better way. Perhaps using a VIA timer? Or would adding a real time clock IC be better (interfaced with a VIA) - although that might not be so great if trying to measure milliseconds.
In my POC unit, I have a Maxim DS1511 real-time clock that generates a 10ms jiffy IRQ, which gives me a convenient time delay source via a 16 bit down counter that gets updated at one second intervals (there's also a 32 bit interrupt-driven uptime counter). I have another time source in the counter/timer (C/T) that is part of the 26C92 DUART. The C/T is driven from the 3.6864 MHz clock used by the DUART's baud rate generator and can generate intervals as short as 820ns.
My only beef with using a VIA timer for timekeeping purposes is that the timer is synched to the Ø2 clock, rather than a separate time base. Changing the Ø2 frequency will affect anything that is dependent on timer operation.
x86? We ain't got no x86. We don't NEED no stinking x86!
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Delay routines
Consider three general ranges of delay:
For really short delays, a software delay loop is often the best. In some cases though, software timing loops for delays are not acceptable because the computer has far too much to do to be stopping and twiddling its thumbs for the duration of the delay. That's especially true when it must time several things at once and the timing of one event cannot be allowed to interfere with the timing of other ones.
I use the free-running T1 in a VIA producing regular, evenly spaced interrupts, not to directly time individual events but as a base for an interrupt-driven software real-time clock (RTC) (as BDD was talking about). On my workbench computer, it has 10ms resolution. On various microcontroller projects for work, the resolution has been arbitrary and usually much finer. At each T1 interrupt, you increment the RTC counter variable in RAM. What you do then is that when you begin timing something, you read the RTC counter variable bytes and add the desired time to that, and keep a record of the result in another variable, then let the computer do other useful stuff while waiting for that time to arrive. As you cycle through the various things that need attention, each time it comes back to the routine for the thing you're timing, you compare the current time to the target time and see if you're there yet. If you're not, go do other useful things.
For the longest delays, you can add alarms to the RTC above. I've done this for making measurements every 15 minutes for example and printing them out. The computer is doing something else, and every 15 minutes it takes care of this job and then goes back to what it was doing (which may have other timed processes).
These things are written up in more detail (and probably more clearly as I put more time and care into it), with example code, in my article on simple methods for multitasking without a multitasking OS, at http://wilsonminesco.com/multitask/index.html .
- super short (like a few microseconds, like you might need for the LCD)
- medium (like fractions of a second, to time light flashes or key debouncing)
- long (like many seconds or minutes or hours or longer)
For really short delays, a software delay loop is often the best. In some cases though, software timing loops for delays are not acceptable because the computer has far too much to do to be stopping and twiddling its thumbs for the duration of the delay. That's especially true when it must time several things at once and the timing of one event cannot be allowed to interfere with the timing of other ones.
I use the free-running T1 in a VIA producing regular, evenly spaced interrupts, not to directly time individual events but as a base for an interrupt-driven software real-time clock (RTC) (as BDD was talking about). On my workbench computer, it has 10ms resolution. On various microcontroller projects for work, the resolution has been arbitrary and usually much finer. At each T1 interrupt, you increment the RTC counter variable in RAM. What you do then is that when you begin timing something, you read the RTC counter variable bytes and add the desired time to that, and keep a record of the result in another variable, then let the computer do other useful stuff while waiting for that time to arrive. As you cycle through the various things that need attention, each time it comes back to the routine for the thing you're timing, you compare the current time to the target time and see if you're there yet. If you're not, go do other useful things.
For the longest delays, you can add alarms to the RTC above. I've done this for making measurements every 15 minutes for example and printing them out. The computer is doing something else, and every 15 minutes it takes care of this job and then goes back to what it was doing (which may have other timed processes).
These things are written up in more detail (and probably more clearly as I put more time and care into it), with example code, in my article on simple methods for multitasking without a multitasking OS, at http://wilsonminesco.com/multitask/index.html .
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
-
teamtempest
- Posts: 443
- Joined: 08 Nov 2009
- Location: Minnesota
- Contact:
Re: Delay routines
In a single-tasking environment where you have total control you can do whatever you want. I've used the Time-of-Day (TOD) clock of a 6526 CIA to implement delays lasting seconds during which the computer did nothing but watch to see if the desired time period had elapsed (but it was okay, the program had nothing to do but wait for the period to expire).
One advantage of using hardware instead of software for delays is that it is unaffected by interrupts; an interrupt-driven software "clock" can get thrown off if interrupts are turned off for any length of time. For periods longer than hardware can easily handle, a hybrid approach is to use a hardware timer to correct any slippage in a software clock.
One advantage of using hardware instead of software for delays is that it is unaffected by interrupts; an interrupt-driven software "clock" can get thrown off if interrupts are turned off for any length of time. For periods longer than hardware can easily handle, a hybrid approach is to use a hardware timer to correct any slippage in a software clock.
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Delay routines
The software RTC I spoke of is run by having an interrupt every 10ms from the VIA's free-running T1. The ISR (interrupt service routine) increments the time record in RAM. So although it's software, it is firmly governed by the hardware timer, regardless of other interrupts going too, and you can be running lots of things at once without the RTC slipping. I put the one VIA on NMI\, mostly to cut the polling overhead when the other VIAs and ACIAs are on IRQ\, since I seldom turn off the RTC whereas other interrupt sources are enabled and disabled depending on what I'm doing at the moment. Admittedly I do have to turn it off when I need low jitter on audio digitizing whose samples are timed by another VIA's T1. That problem is minor, but eventually I want to implement an I²C RTC in 8-pin DIP, an NXP PCF8593 which has an alarm interrupt output, unlike the Maxim/Dallas one discussed in another recent topic. Although the VIA used for the RTC time source is on NMI\, you can still turn it off by clearing the T1 interrupt-enable bit in the VIA's IER (interrupt enable register).
For routines for short delays (without an RTC), see the 6502.org wiki page at http://6502org.wikidot.com/software-delay .
For routines for short delays (without an RTC), see the 6502.org wiki page at http://6502org.wikidot.com/software-delay .
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
- BigDumbDinosaur
- Posts: 9425
- Joined: 28 May 2009
- Location: Midwestern USA (JB Pritzker’s dystopia)
- Contact:
Delay Routines
GARTHWILSON wrote:
The software RTC I spoke of is run by having an interrupt every 10ms from the VIA’s free-running T1. The ISR (interrupt service routine) increments the time record in RAM.
Here’s the code that does the timekeeping:
Code: Select all
;—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—-—
;
;RTC INTERRUPT PROCESSING
;
iirq02 pla ;recover RTC control register...
;
; —————————————————————————————————————————————————————————————————————
; The control register is read & stored early in the interrupt handler.
; The read operation automatically clears the pending IRQ.
; —————————————————————————————————————————————————————————————————————
;
bit #d11ismsk ;watchdog interrupt?
beq iirq03 ;no, skip ahead...
;
; —————————————————————————————————————————————————————————————————
; The watchdog timer is the only interrupt source programmed in the
; in the RTC. Any other interrupt would be spurious.
; —————————————————————————————————————————————————————————————————
;
;
; process time delay counter...
;
longa ;16 bit accumulator & memory
ldx tdirqct ;msecs*10 counter (8 bits)
bne .iirq020 ;not zero, just decrement
;
lda tdsecct ;seconds counter
beq .iirq030 ;done
;
.iirq010 dec tdsecct ;secs=secs-1
ldx #hz+1 ;reset msecs
;
.iirq020 dex ;msecs=msecs-10
stx tdirqct ;new counter value
;
;
; process uptime counter...
;
.iirq030 ldx utirqct ;msecs*10 counter
inx
cpx #hz ;reach 1 sec?
bcc .iirq040 ;no
;
ldx #0 ;reset msecs*10 counter
inc utsecct ;bump uptime LSW...
;
; ——————————————————————————————————————————————————————————————————
; LSW is least significant word, Similarly, MSW is most significant
; word.
; ——————————————————————————————————————————————————————————————————
;
bne .iirq040 ;done with uptime
;
inc utsecct+s_word ;bump uptime MSW
;
.iirq040 stx utirqct ;new msec*10 counter valueAll timer values are stored on direct page. In the above code, hz is the jiffy interrupt rate in IRQs per second (100 in this case) and s_word is the size of a 65C816 word in bytes (2 in this case). I never embed values like that directly into instructions, as it’s too easy to mistype a number and introduce a pernicious bug.
—————————————————————————————————————————
EDIT: Not too long after this topic started, it was discovered that a 10ms IRQ from the DS1511’s watchdog timer was not spacing IRQs at exactly 10ms, resulting in “timer creep.” While that might not matter for a delay function, it does matter if the IRQ is being used to keep accurate time. So I no longer use the watchdog IRQ in my POC units and instead use a DUART timer, which is driven by the DUART’s X1 clock and is much more accurate.
Last edited by BigDumbDinosaur on Thu Oct 16, 2025 9:20 am, edited 1 time in total.
x86? We ain't got no x86. We don't NEED no stinking x86!
- BigDumbDinosaur
- Posts: 9425
- Joined: 28 May 2009
- Location: Midwestern USA (JB Pritzker’s dystopia)
- Contact:
Re: Delay routines
teamtempest wrote:
In a single-tasking environment where you have total control you can do whatever you want. I've used the Time-of-Day (TOD) clock of a 6526 CIA to implement delays lasting seconds during which the computer did nothing but watch to see if the desired time period had elapsed (but it was okay, the program had nothing to do but wait for the period to expire).
In the record and file locking code I concocted for use with the Lt. Kernal hard drive subsystem ISAM driver I wrote, I used the TOD's 0.1 seconds register to produce random time delays to detect and prevent contention between workstations on a multiplexed system. The TOD was nudged into action as part of the ISAM driver startup routine and polled in a loop when a delay was needed. The loop polled for a change in the register, which could happen in as little as a few microseconds or could take as long as 100 milliseconds to occur. It all depended on when the polling loop was entered, which gave it a good deal of randomness.
x86? We ain't got no x86. We don't NEED no stinking x86!
Re: Delay routines
Delay routines, eh?
Reminds me of the Diablo daisy-wheel printers I reverse-engineered years ago. A fascinating design -- it was amazing how much was achieved using so little! I'm talking about 4 axes of simultaneous motion controlled in real-time by a primitive, discrete-TTL CPU that had no counter/timer and no interrupts.
The loop (simplified substantially) ran something like this:
One solution would be to rewrite the code for constant execution speed. Take, for example, the test for whether a print-wheel update is required. You either go do the update or go do a delay of equal duration. Same for all the other tasks in the loop -- either do them, or instead do an equivalent delay. In fact the code is full of conditional branches -- far more so than the summary suggests.
Such a scheme might work alright, but no way was there room for all those delay loops! Did I mention the program memory only held 512 instructions?
What they did was reserve one of their 32 bytes of RAM (!) as a sort of accumulator for delay. According to the result of significant conditional branches, the delay byte would have various values added to it -- and the resulting delay was postponed, not immediate.
The one-and-only delay loop was like a fifth task added to the list above. Only once per iteration would there be a pause, during which the CPU would decrement the delay accumulator and repeat until it reached zero. This had the desired effect of enforcing a speed limit on all four tasks. I believe the technique is known as a "paced loop."
On my web site there's more info about the discrete-TTL CPU -- including the actual schematic. Not much to it, but it ran those Diablo printers at 45 characters per second. The noise was like a cross between a weed-eater and a machine gun!
-- Jeff
The loop (simplified substantially) ran something like this:
- if necessary, update the print-wheel servo
- if necessary, update the carriage servo
- if necessary, issue a pulse to the ribbon-advance stepper motor
- if necessary, issue a pulse to the paper-advance stepper motor
- (repeat)
One solution would be to rewrite the code for constant execution speed. Take, for example, the test for whether a print-wheel update is required. You either go do the update or go do a delay of equal duration. Same for all the other tasks in the loop -- either do them, or instead do an equivalent delay. In fact the code is full of conditional branches -- far more so than the summary suggests.
Such a scheme might work alright, but no way was there room for all those delay loops! Did I mention the program memory only held 512 instructions?
What they did was reserve one of their 32 bytes of RAM (!) as a sort of accumulator for delay. According to the result of significant conditional branches, the delay byte would have various values added to it -- and the resulting delay was postponed, not immediate.
The one-and-only delay loop was like a fifth task added to the list above. Only once per iteration would there be a pause, during which the CPU would decrement the delay accumulator and repeat until it reached zero. This had the desired effect of enforcing a speed limit on all four tasks. I believe the technique is known as a "paced loop."
On my web site there's more info about the discrete-TTL CPU -- including the actual schematic. Not much to it, but it ran those Diablo printers at 45 characters per second. The noise was like a cross between a weed-eater and a machine gun!
-- Jeff
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html
https://laughtonelectronics.com/Arcana/ ... mmary.html
Re: Delay routines
I'll give the vIA timer a go. Just go to get my head around how to program it. 
- floobydust
- Posts: 1394
- Joined: 05 Mar 2013
Re: Delay routines
I recently worked on a simple BIOS for a 6551 and 6522 and used both timers, one for RTC and one for delays. I posted the BIOS in this post:
viewtopic.php?f=12&t=3045
It might be worth a look, the BIOS contains a extendable interrupt structure as well. The VIA isn't that difficult to code up, good luck.
viewtopic.php?f=12&t=3045
It might be worth a look, the BIOS contains a extendable interrupt structure as well. The VIA isn't that difficult to code up, good luck.
Regards, KM
https://github.com/floobydust
https://github.com/floobydust
Re: Delay routines
Fantastic - many thanks, flooby
.
Can I ask which 6551 you're using?
Can I ask which 6551 you're using?
- floobydust
- Posts: 1394
- Joined: 05 Mar 2013
Re: Delay routines
I'm using some Rockwell 65C51P4 chips which work fine (at 4MHz). I also have some older NMOS 6551 (Rockwell, Syntertek, AMI) chips which work and one older W65C51 which I'm running at 10MHz but using a can oscillator for the baud clock as the internal clock is unstable with a crystal.
Regards, KM
https://github.com/floobydust
https://github.com/floobydust
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Re: Delay routines
floobydust wrote:
I'm using some Rockwell 65C51P4 chips which work fine (at 4MHz). I also have some older NMOS 6551 (Rockwell, Syntertek, AMI) chips which work and one older W65C51 which I'm running at 10MHz but using a can oscillator for the baud clock as the internal clock is unstable with a crystal.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
Re: Delay routines
floobydust wrote:
I'm using some Rockwell 65C51P4 chips which work fine (at 4MHz). I also have some older NMOS 6551 (Rockwell, Syntertek, AMI) chips which work and one older W65C51 which I'm running at 10MHz but using a can oscillator for the baud clock as the internal clock is unstable with a crystal.
- floobydust
- Posts: 1394
- Joined: 05 Mar 2013
Re: Delay routines
Last year sometime, WDC sent me a few different LOT number W65C51 chips, all older and none are the same as what is currently shipping. They all seem to have some issues, but one does work quite well provided you use an external oscillator. I did have to add quite a few extra decoupling caps on the supply lines but it's been pretty stable at 10MHz and running for months. I'm thinking the 10MHz limit is more of the EEPROM (Atmel 28C256 150ns), but I need to put one of 70ns PLCC EEPROMs on the carrier I have which will allow me to use that and hopefully will be able to crank it a bit faster.
My board layout has the Xtal cap and regardless, it's unstable with a crystal. I did double up on the can oscillator frequency and am running with 38.4K baud however, which is nice. It interfaces directly to a FTDI UART/USB interface that installs in the DB-9 connector layout. Note that the newer, albeit defective W65C51 chips work fine with the Xtal on my board and the baud clock is very stable, just the fated xmit bit bug.
Also, as Mouser has stock again of the PDIP W65C51 chips, I opted to order one this week... same LOT number, same xmit bug, ugh.
My board layout has the Xtal cap and regardless, it's unstable with a crystal. I did double up on the can oscillator frequency and am running with 38.4K baud however, which is nice. It interfaces directly to a FTDI UART/USB interface that installs in the DB-9 connector layout. Note that the newer, albeit defective W65C51 chips work fine with the Xtal on my board and the baud clock is very stable, just the fated xmit bit bug.
Also, as Mouser has stock again of the PDIP W65C51 chips, I opted to order one this week... same LOT number, same xmit bug, ugh.
Regards, KM
https://github.com/floobydust
https://github.com/floobydust