55 Sec Time Delay in Assembly Language

Discussion in 'Embedded Systems and Microcontrollers' started by pauliewalnuts, Mar 9, 2008.

  1. pauliewalnuts

    Thread Starter New Member

    Mar 9, 2008
    I'm new to assembly language, I have read a couple of tutorials and am beginning to get the fundamentals.
    I have a fairly simple project to do, whereby I am using a PIC and need it to produce a time delay between 55-60 sec when an input goes high. after this delay, an output will go high.
    I would really appreciate some ideas, as I am unsure how to produce a delay of this length.
    Many thanks in advance for any info.
    Cheers, Paulie.
  2. John Luciani

    AAC Fanatic!

    Apr 3, 2007
    Use a counter-timer and an interrupt routine.

    For example in one of my uC boards I have 32768Hz crystal. The counter is set to interrupt
    when the count = 32768 (which is one second). You could change the counter
    to interrupt at a 60 second interval by using different settings for the counter
    clock or a larger counter.

    (* jcl *)
  3. SgtWookie


    Jul 17, 2007
  4. Papabravo


    Feb 24, 2006
    I'll give you the carpenter's answer. If you want to drive a BAN(big a** nail), you use a BAH(big a** hammer). If you want to time a long delay with a fast clock you need a LAC (long a** counter). In other words you keep making the counter longer until it has enough bits to exceed the interval of interest.

    Example: How many cycles of an 8 MHz. clock do you need to count in one day?

    Answer: 8e6 cycles/sec * 60 seconds/min * 60 Minutes/hour * 24 hours/day = 6.912 e 11

    Now log(6.912 e 11)/log(2) = 39.33 which means a 40 bit or 5 byte counter will do the trick. Just to check my math 2^40 = 1.099 e 12 which is > 6.912 e 11

    Wasn't that a dizzying mental math exercise?

    To implement the multibyte or multiword counter in the assembly language of your choice you need to locate and understand the instructions which produce and use the carry flag. For the least significant part of an addition you do an ordinary "ADD" instruction. For each of the higher order parts you use the "ADD with Carry" instruction that adds two things together AND adds the carry bit from the previous operation. With the carry flag you can make the number of bytes or words anything you desire within the limits of memory on your particular machine.

    Your machine might also have subtract and subtract with borrow instructions where the borrow flag is the complement of the carry flag.
  5. 0xFF


    Feb 26, 2008
    I think the answer depends on what you're trying to do with it, and how accurate you need your count to be. For me, I have a project where I use Timer1 to set a 10ms interrupt. Accordingly, I set a counter at 100 and decrement it with each interrupt. Once the counter = 0, 1 second has passed (10ms x 100 = 1000ms == 1 Second). Add that to a seconds counter and you can keep track of the seconds.

    However, this is an ESTIMATED interrupt rollover! It WILL NOT be accurate over time (ie. if you plan on using it for a clock, etc.). If you need the accuracy, set up your Timer to use an external crystal (32768Hz as pointed out above), which will keep accurate time.

    The reason I choose this method is that I can keep an "estimated" time of how long the unit has been running (and I don't need it to the second -- even days is fine for what I need), but the main reason is, I can handle as many counters as I need. My timers are for time delay -- you must wait a certain amount of time before being able to do a certain task. I, on occasion, have 3 timeout timers running at the same time, all from the same interrupt / timer setup. Here's how I do it;

    NOTE: this code was pulled from an 18F series chip -- 18F4620 to be precise. Please make sure to check your settings for your particular chip (ie. INTCON, T1CON, etc. -- just make sure to look them up in your chips datasheet and adjust as required.

    First, setup the timer to rollover every 10ms, which creates an interrupt.

    Code ( (Unknown Language)):
    2.     ; setup Timer1
    3.         movlw   b'00000000'             ; TMR1 prescaler 1:1, internal clock source
    4.         movwf   T1CON                   ;
    5.         movlw   0xD8                    ; load timer 1 with 0xD8EF
    6.         movwf   TMR1H                   ; 10ms rollover @ 4 MHz
    7.         movlw   0xEF                    ;
    8.         movwf   TMR1L                   ;
    Next, turn on the interrupts.

    Code ( (Unknown Language)):
    2.     ; enable/disable interrupts
    3.         bcf     PIR1, TMR1IF            ; clear the TMR1 interrupt flag
    4.         bsf     INTCON, PEIE            ; enable peripheral interrupts
    5.         bsf     PIE1, TMR1IE            ; enable TMR1 interrupts
    6.         bsf     INTCON, GIE             ; enable global interrupts
    Then, in your ISR (Interrupt Service Routine);

    Code ( (Unknown Language)):
    2. ISR:
    3.         bcf     INTCON, GIE             ; turn off global interrupts
    4.         btfsc   INTCON, GIE             ; test the flag to ensure it's off
    5.         bra     $-4                     ; still on, try again
    7. ISR_TMR1:
    8.         btfss   PIR1, TMR1IF            ;
    9.         bra     IRestore                ; jump if not TMR1 interrupt
    11.     ; handle the TMR1 event    
    12.         bcf     PIR1, TMR1IF            ; clear the TMR1 interrupt flag
    13.         movlw   0xD8                    ; reload timer 1 with 0xD8EF
    14.         movwf   TMR1H                   ; 10ms rollover @ 4 MHz
    15.         movlw   0xEF                    ;
    16.         movwf   TMR1L                   ;      
    18.         decfsz  second_count, F         ; decrement the seconds count
    19.                                         ; interrupt every 10ms x 100 = 1000ms (1 second)
    20.         bra     IRestore                ;
    22.         movlw   .100                    ; reset the seconds count
    23.         movwf   second_count            ;
    25. ITime_Delay:
    26.         decf    delay_md1_sec, F        ; decrement seconds
    27.         incfsz  delay_md1_sec, W        ; check for underflow
    28.         bra     ICheck_Zero             ;
    29.         movlw   .59                     ; reset seconds to 59
    30.         movwf   delay_md1_sec           ;
    31.         decf    delay_md1_min, F        ; decrement MIN
    32.         incfsz  delay_md1_min, W        ; check for underflow
    33.         nop                             ;
    35. ICheck_Zero:
    36.         movf    delay_md1_sec, F        ; test SEC for zero
    37.         bnz     IRestore            ; NOT ZERO
    38.         movf    delay_md1_min, F        ; test MIN for zero
    39.         bnz     IRestore            ; NOT ZERO
    41. ITimeout:
    42.                 {DO WHAT YOU NEED TO DO AFTER A TIMEOUT HERE}
    44. IRestore:
    45.         bcf     PIR1, TMR1IF            ; clear the TMR1 interrupt flag
    46.         retfie  FAST                    ; enable global interrupt (INTCON,GIE)
    So, basically, what happens is this -- you set a 10ms interrupt -- with every interrupt, you decrement the count -- when the count hits zero, 1 second has elapsed. When 1 second has elapsed, you send the ISR to the next function, which decrements the time out period you have set (in the example I have provided, delay_md1_sec and delay_md1_min are your seconds and minutes counters (respectively)). For example, if I wanted a 55 second timeout, I would set delay_md1_sec = 55 and delay_md1_min = 0. Each time you decrement the seconds, you check them for zero -- if they are zero, you set them back to 59 (assuming you haven't hit your timeout) and decrement the minutes counter. If they're both = 0, you have hit your timeout.

    I have removed some of the other checks for other timeouts (ITime_Delay2, ITime_Delay3, etc.) -- but it's easy to have multiple counts running at the same time.

    I have to run, so I don't have much time to proof read this -- hopefully there aren't many mistakes!

    If I haven't explained something well, please ask and I'll try to explain it better.
  6. SAE140

    New Member

    May 1, 2008
    Providing the processor isn't required to do anything else during this delay period, it's possible to achieve this kind of delay without the use of interrupts (to keep things as simple as possible). The key word here is 'nested-delay'.

    So - create a loop which keeps checking for your desired input state. On change, it enters a nested loop, then after the long delay it alters the output, then whatever .... (program ends, re-starts ... ?)

    The nested loop structure itself takes the form of an innermost loop with a down counter, starting from max (255), and exitting on zero. With a 1 uSec clock period (say), that gives you a delay of 255 or 256 uSecs (depending on whether you decrement the counter before or after the decision to exit) - so you've now created a 255 uSec delay.

    If this inner loop is then placed within an outer loop, also with a down-counter set at 255, then you'll have a 255x255x1 uSec delay (approximately). If you need even more delay, then arrange for this lot to sit (nest) within yet another loop, and so on ....

    You could make a timer to delay for many years like this.

    Couple of points:
    a) this is extremely wasteful of processing power, but for a demonstration it doesn't matter. Using interrupts is certainly better, but adds complexity if you've just started programming.
    b) consider toggling an LED from an output line when your delay gets up to around the 0.5 - 5 second mark, so that you have some indication that the program is still running and the processor hasn't gone off picking daisies ...
    c) In the loops there will probably be a timing difference depending on whether the program keeps looping, or whether it exits. This may be small difference, but is obviously magnified if it occurs in the innermost loop.
    Adjustment will be needed if precision is sought.

    Hope this helps. Try doing a Google for 'nested delay loops'.

  7. rathishag007

    New Member

    Jun 12, 2008
    I need a subprogram of 1 SECOND delay.......I want to call this routine frequently..Because i am doing my project traffic light.so i want to CALL the delay of 1 second ,20 times for highway road and 5 times for pocket road. please anyone can help me..I need my delay pgm in assembly language.................

    Advanced thanXXX
  8. nanovate

    Distinguished Member

    May 7, 2007
    Thanxxxx for the double post and hijack: http://forum.allaboutcircuits.com/showthread.php?t=12298
    shamsgul likes this.
  9. conanav


    Jun 14, 2008
    Amazing post, I think i learned more from your replays than when i read the chapter on timer control in the outdated Microcontrollers by Hintz.