PIC Delay Loop Time Question (ASM) - Page 1
Forum section: electronics / general

author: blewisjr
author_rank: Frequent Contributor
timestamp: March 13, 2013, 06:14:16 pm
content: I have a delay loop from a Blinking LED tutorial and I am trying to understand the math behind it so I can effectively write my own delay loops. For some odd reason I can't wrap my head around it. Right now the delay is 1.579 seconds and I want to make it more accurate by decreasing the delay to 1 second.I am not looking for an answer on how to do it I can write the code I am trying to understand the equation behind calculating the delay time.The 2 loops for on and off are labeled as OnLoop and OffLoop.If you have any tips on a better way to do the looping that is easier to calculate off of feel free to give tips.Code:; =============================================================================; File: blink_test.asm;; Date: 3-13-2013;; The typical Hello World uC program.; This is a simple program that uses a timed delay to blink a LED.; The LED blinks at a rate of 1.579 seconds.;; PIC: 16F1829; Assembler: MPASM v5.49; IDE: MPLABX v1.70; Board: Breadboard; =============================================================================; Include file containing register mappings for PIC16F1829#include <p16f1829.inc>; Fuse configurations#define CFG_1_1 _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF#define CFG_1_2 _CP_OFF & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF#define CFG_1_3 _IESO_OFF & _FCMEN_OFF__CONFIG _CONFIG1, (CFG_1_1 & CFG_1_2 & CFG_1_3)__CONFIG _CONFIG2, (_WRT_OFF & _PLLEN_OFF & _STVREN_OFF & _LVP_OFF); Warning suppressionerrorlevel -302 ; not in bank0 warining; User file registerscblock 0x70 ; start of shared bank memoryDly1Dly2endcorg 0 ; start program at address 0Start:banksel OSCCON ; move to bank1movlw b'00111000' ; set internal cpu clock to 500kHZmovwf OSCCON ; move contents of w to OSCCONbcf TRISC, 0 ; make I/O pin RC0 and outputbanksel LATC ; move to bank2clrf LATC ; clr data latch for PORTC (set bits to 0)Loop:bsf LATC, 0 ; turn on pin RC0OnLoop:; Outer Loopdecfsz Dly1, f ; Decrement Dly1 store back in Dly1bra OnLoop ; branch to OnLoop; OnLoop takes 3 instructions per loop; * 256 iterations = 768 instructions.; Inner Loopdecfsz Dly2, f ; Decrement Dly2 store back in Dly2bra OnLoop ; takes 3 more instructions; per iteration (768+3) * 256 = 197376; 197376 inst. / 125k inst. per sec = 1.570 secbcf LATC, 0 ; turn off pin RC0OffLoop: ; same as OnLoopdecfsz Dly1, fbra OffLoopdecfsz Dly2, fbra OffLoopbra Loop ; start overend ; end of code

author: Kaluriel
author_rank: Contributor
timestamp: March 13, 2013, 06:37:03 pm
content: Looks like two for loops, wrapping around.Code:unsigned char Dly1 = 0;unsigned char Dly2 = 0;void Sleep(){do{do{--Dly1;} while(Dly1 != 0);--Dly2;} while(Dly2 != 0);}void main(){while(true){SetLEDOn(true);Sleep();SetLEDOn(false);Sleep();}}Each loop only takes 3 instructions to execute if it does nothing but decrement, compare to zero, and branch, a total cost of 256 iterations, so 3x256. But the internal one is executed 256x by the outer loop, so the outer loop cost 3x256 instructions plus its own 3 per loop, another 256 times.Judging by the comment in the code, 125k instructions are executed per second, assuming you haven't changed the clock speed.SoInner Loop: 3 instructions x 256 iterations = 768 instructionsOuter Loop: (3 instructions + 768 instructions) x 256 iterations = 197376 instructionsA total of 197376 instructions before both Dly1 and Dly2 are both 0.If it takes one second to perform 125k instructions, 197376 / 125000 = 1.579008 seconds

author: brainwash
author_rank: Frequent Contributor
timestamp: March 13, 2013, 06:40:25 pm
content: You might try to initialize Dly1 with value 162, it is by default initialized to 0 which causes 256 cycles to occur

author: Kaluriel
author_rank: Contributor
timestamp: March 13, 2013, 07:11:16 pm
content: As brainwash said, changing the initial value of Dly1 and Dly2 will change the timing.(3 + (3 * Dly1)) * Dly2 = timeInSeconds * 125000Where Dly1 and Dly2 are a number between 0 and 255, with 0 being treated as 256, and the rest being treated as their respective value.Keep Dly1 as a low number for more precision, and choose Dly2 as your multiplier to scale up to the time you require. Without more loops, 1.5 seconds is your maximum.

author: blewisjr
author_rank: Frequent Contributor
timestamp: March 13, 2013, 07:23:51 pm
content: Quote from: Kaluriel on March 13, 2013, 07:11:16 pmok thanks a bunch that is what I was looking for a solid equation.((3 + (3 * Dly1)) * Dly2) / ipsI was not sure where the delay variables went into the equation and it was messing up my calculations I guess.I should be able to easily modify the blink speed and get it very accurate.

author: SebG
author_rank: Regular Contributor
timestamp: March 13, 2013, 07:28:34 pm
content: HiThe delay loops are pretty common. Let's look at OnLoop first. The first decfsz and bra pair creates a stand alone loop and the second decfsz and bra pair creates a loop that includes the delay of the first pair. That is the first pair loops until Dly1 == 0, then the second decfsz instruction decrements Dly2 and loops (bra OnLoop) back to the start (assuming Dly is not Zero). At the start, the first decfsz and bra pair loops AGAIN until Dly1 == 0 and then the second decfsz instruction decrements Dly2 and so on until Dly2 == 0.The first pair works like this:1: decrement Dly1 which takes 1 instruction cycle if it does not skip (i.e. Dly1 != 0)2: branch back to decfsz instruction which takes 2 instruction cyclesSo each decrement step takes 3 instruction cycles. However, when decrement results in Dly1 == 0 then a skip will occur which will take 2 instruction cycles so the last decrement step will be 2 instead of 3 instruction cycles.The time it takes is as follows: the starting value of Dly1 is the number of times the loop will step. Since decfsz first decrements and then compares (to determine if zero) a value of Dly1 = 0 will loop 256 times. So the total time (in instruction cycles) is T1 = (Dly1 * 3) - 1 so if Dly1 = 0 then T1 = (256 * 3) - 1 = 767 instruction Cycles. Note: the minus 1 is there since the last step is 2 instruction cycles.The second pair works like this (and OnLoop overall):1: The first delay of 767 instruction cycles (T1) occurs2: Dly2 is decremented which takes 1 instruction cycle (assuming it does not skip)3: branch to OnLoop label and repeat it all again. The branch takes 2 instruction cycles.So The math is this: Total instruction cycles = [(T1 + 3) * Dly2] - 1. Why? Each time, from OnLoop label, that Dly2 is decremented it takes T1 and the 3 instruction cycles to go back to OnLoop label. Again -1 since when Dly2 == 0 a skip will occur which is 2 instruction cyclesTo get more precision on the delay you need assign a value to Dly2 before OnLoop label (you may also include the instruction time it takes to assign the value by simply adding to the total time equation). Even more precession you can get if you assign a value to Dly1 but that would change the Total Time formula a bit.Furthermore, since the Oscillator is set to 500kHz so each instruction cycle takes 1 / (500k/4) = 8e-6 = 8 us and the total time of OnLoop is {[(767 + 3) * 256] - 1} * 8us = 1.576952 seconds.I've noticed that the code you have doesn't accurately calculate the time as I have shown here. But if you want accuracy then you need to consider the "-1" in the calculations.

author: brainwash
author_rank: Frequent Contributor
timestamp: March 13, 2013, 07:41:19 pm
content: Anyway the oscillator is set to internal so you will not get a whole lot of precision.

author: SebG
author_rank: Regular Contributor
timestamp: March 13, 2013, 07:47:50 pm
content: Ok so I wrote the whole description and all but few posts came up in the time all of which are not quite accurate since ... well read my post above. But if you just need a way to control roughly the delay between toggling the light then it should be ok.Keep this in mind. Setting Dly2 is needed only once since the OnLoop or OffLoop is done when Dly2 == 0 but setting Dly1 needs to happen every time the fist (inner loop) is executed. Here is an example:Code:movlw0x00movwfDly2OnLoop:movlw0x00movwfDly1OnLoopInnerLoop:decfszDly1, fbraOnLoopInnerLoopdecfszDly2, fbraOnLoopThe equation for Instruction Cycle Delay is (accurately): ({[(Dly1 * 3) - 1] + 2 + 3} * Dly2) - 1 + 2 = ({[(Dly1 * 3) + 4] * Dly2} + 1 = Instruction Cycles. Multiply that by 8us to get the time of the loop.Also the precision of the internal oscillator is +/- 2% at Room Temperature

author: brainwash
author_rank: Frequent Contributor
timestamp: March 13, 2013, 07:56:00 pm
content: I agree with SebG, I didn't analyze the code throughly before saying about the Dly1 modification. Also, setting Dly1 will eat some extra cycles and the formula would need to be revised.But I actually meant setting Dly2 to 162, that would give you 1.00052 seconds.Nevertheless, you might need a combination of Dly1 and Dly2 setting to achieve the value closest to 1 second.

author: blewisjr
author_rank: Frequent Contributor
timestamp: March 13, 2013, 08:00:59 pm
content: Thanks a bunch SebG very informative. Yes dealing with the resetting of Dly1 would add 2 extra instructions per loop and for accuracy would modify the formula. Again thanks for all the info I learned a lot here and now I can modify the tutorial to be a tiny bit more accurate. It is really amazing how complex assembler can become great learning experience for me. These tiny details will make me a much better programmer in the long run for these little devices.

author: croberts
author_rank: Regular Contributor
timestamp: March 13, 2013, 08:20:55 pm
content: You may want to consider interrupt driven software timers. Please see the code in my posthttps://www.eevblog.com/forum/microcontrollers/a-simple-state-machine/

author: SebG
author_rank: Regular Contributor
timestamp: March 13, 2013, 08:43:22 pm
content: I'm glad it was beneficial to you. Programming in assembly is fun and gives you greater insight into programming, especially when working with hardware.

author: blewisjr
author_rank: Frequent Contributor
timestamp: March 14, 2013, 01:35:16 pm
content: Ok the new math seems to work out and I got to about a 1% accuracy level I believe off of a 500kHZ internal clock. I made some modifications to the formula to take into account the extra 2 instructions cycles to reset Dly1. So lets run through this and see if I am correct in this. Been a while since I had to modify math formulas.So to take into account the extra 2 instructions cycles I modified the formula to be:T = ({[(Dly1 * 3) - 1] + 2 + 2 + 3} - 1 + 4) * 8uS = ({[(Dly1 * 3) + 5] * Dly2} + 3) * 8uSSo if:Dly1 = 160Dly2 = 256T = ({[160 * 3) + 5] * 256} + 3) * 8uS = [(485 * 256) + 3] * 8uS = 124163 * 8uS = 0.993304 seconds of delayTo me that is really close to the 1 second delay I am looking for and at 500 kHz I don't think I can get much closer due to the accuracy limits of the internal clock.So how did I do on my formula modification?

author: SebG
author_rank: Regular Contributor
timestamp: March 14, 2013, 04:00:59 pm
content: It would be helpful if I saw the code that you have modified. The formula I showed in my previous post above includes setting Dly1 and Dly2.

author: blewisjr
author_rank: Frequent Contributor
timestamp: March 14, 2013, 04:48:32 pm
content: Oh ok I did not realize you already included the resetting the modified code would look something like this without exact values...Code:; Dly2 is set to 0x00OnLoop:movlw 0x00movwf Dly1decfsz Dly1, fbra $-1decfsz Dly2, fbra OnLoopbcf LATC, 0OffLoop:; ...

author: SebG
author_rank: Regular Contributor
timestamp: March 14, 2013, 05:08:07 pm
content: Ok so in your example the delay of OnLoop is: {[ (Dly1 * 3) - 1 + 2 + 3 ] * Dly2 - 1 + 1} * 8us = [(Dly1 * 3) + 4] * Dly2 * 8usThat is (Dly1 * 3) - 1 is the inner loop, 2 instructions that load Dly1 and 3 instruction cycles that decrement Dly2 and branch (except the last decrement that skips so -1 at the end). The last +1 is the bcf instruction. What you have is a bit different.I've copied your code in here since it is on the previous page:Code:; Dly2 is set to 0x00OnLoop:movlw 0x00movwf Dly1decfsz Dly1, fbra $-1decfsz Dly2, fbra OnLoopbcf LATC, 0OffLoop:; ...

author: blewisjr
author_rank: Frequent Contributor
timestamp: March 14, 2013, 05:56:05 pm
content: Ok I see where I screwed up on my equation I think I got this down now thanks a bunch again I finally see how the equation is heavily dependent on the code.