PDA

View Full Version : 32 bit seconds math (how do I include the upper 16 bits?)



Heckler
- 25th January 2015, 15:06
Hey Group,

I am using a Dallas Semiconductor DS2417 RealTimeClock
This little OneWire device simply starts counting seconds from where ever you tell it to start from (your date reference)
and gives the count back to you in a 4 byte 32bit register
With 32 bits the clock can count up for 136 Years before rolling over.

I need some help figuring out the math to convert the 32bit seconds to HH:MM:SS

My code below is able to do the conversion correctly on the first 16bit ok but fails to take into account the second 16 bits of the 32 bit data.
so after 18 hrs 12 min and 15 sec (65535 or $FFFF) it rolls over to zero.

I need to know how to deal with and include the upper 16 bits of the 32 bit data.

My code takes the lower two bytes (C&D) and combines them into a Word (CD)
and then does the math to convert to HH:MM:SS

But I am at a loss as to what to do with the upper 16 bits.

For now I am not interested in the DAYS, I am only wanting to keep track of a rolling 24 hour clock.
So I expect to just devide the seconds down and discard multiples of 24 hrs.



'================================================= ============
' Read OneWire Clock
'================================================= ============
GetOWtime:
owout OWTPin,%000,[$cc,$66]
owin OWTPin,%010,[owctrl,ByteD,ByteC,ByteB,ByteA] 'ByteD=LSB ByteA=MSB

AB.byte1 = ByteA 'MSB
AB.byte0 = byteB
CD.byte1 = bytec
CD.byte0 = byteD 'LSB
return 'with time


'================================================= ============
' convert the one wire seconds count to HH:MM:SS
'================================================= ============
HMS:
owHH= CD/3600
owMM= (CD/60)-(owHH*60)
owSS= CD//60
return

I need help in figuring out what to do when the word "AB" starts counting above zero (which happens after the 18:12:15 point is reached (assuming I start from zero)

My plan is to start the clock at zero and store the actaul time I started the clock in a variable and add/subtract the stored time variable from the incrementing count to get the current real time.

I know there are other clocks that do all this for you (like the DS1307) but that one is I2C and I specifically need a One Wire solution.

thanks for any help or guidance you can give.

dwight

Tabsoft
- 25th January 2015, 17:25
Dwight,

You don't mention what PIC MCU you are using nor which version of Picbasic.

If your PIC and version of Picbasic supports Long variable types, that would be the simplest way to go. You of course would need to account for bit32 since PBPL uses Signed Long variable types and the DS2417 uses an Unsigned 32bit value.

Regards

HenrikOlsson
- 25th January 2015, 17:52
Hi Dwight,
Try the following:


ooDays VAR WORD
i VAR WORD
Temp VAR WORD

' This uses DIV32 by preloading the system variable it uses with
' the value we want to divide. To find out how many days has passed
' we divide the number of seconds with 86400 (number of seconds in a day).
' However, DIV32 only supports 16 bit divisor so we need to do the
' division in two steps, 21600 * 4 = 86400.

R0 = AB ' High word of seconds into system var
R2 = CD ' Low word of seconds into system var
ooDays = DIV32 21600 ' Divide by 86400
ooDays = ooDays / 4

' Now we need to subtract 86400 seconds from the running time
' one time for each day that has passed. Since we're working with
' 16-bit words we need to this sort of "manually".
' 65536 + 20864 = 86400

For i = 1 to ooDays
Temp = CD
CD = CD - 20864

IF Temp < CD THEN ' Did we underflow the low word?
AB = AB - 1 ' If so, decrement high word
ENDIF

AB = AB - 1 ' Subtract 65536
NEXT

' At this point ABCD contains anything from 0 to 86399 and we need to
' figure out how many "full hours" is in it. Because 86399 is more than
' what fits in a 16-bit word we're using DIV32 trick to divide the
' number of seconds by 3600 (number of seconds in an hour).

R0 = AB ' High word of what's left in seconds into system var
R2 = CD ' Low word of what's left in seconds into system var
ooHH = DIV32 3600 ' Divide by the number of seconds in an hour

' Subtract 3600 from the running time, one time for each hour.
For i = 1 to ooHH
Temp = CD
CD = CD - 3600

IF Temp < CD THEN ' Did we underflow the low word?
AB = AB - 1 ' Subtract one from high word.
ENDIF

' Now, we're down to minutes and there can be no more than 3599 seconds left
' so we can easily handle it with just the low word and normal math
ooMM = CD / 60

' And finally we can get the seconds by getting the reminder.
ooSS = CD // 60

It's based on a piece of code I originally wrote for doing SNTP with W5100 chip. SNTP basically gives you the numner of seconds passed since 1900-01-01 00:00:00 so I stripped out the year, leapyear, and month stuff and adopted it to your variables. I have not verified the above to be working.

/Henrik.

Heckler
- 25th January 2015, 22:55
@TABSoft

Sorry, I should have posted that info...

Using PBP 3
the PIC is 16F690 (but I also want to be able use the newer 16F1828)

I had thought about using Long's but I've never used PBPL and what little info is in the manual is very vague.
What does it take to use a Long?? and invoke PBPL?

I haven't done much searching on PBPL yet. I'm going to give the code that Henrik (thanks!!) posted and see what it does for me.

Heckler
- 25th January 2015, 23:45
Wow Henrik!!

You write amazingly good "unverified" code!
You did miss one "NEXT" down at the end.
I had to change your ooHH, etc. variables to match mine (owHH)

But it is working beautifully!


'================================================= ============
' convert the one wire seconds count to HH:MM:SS
'================================================= ============
HMS:

owDays VAR WORD
i VAR WORD
Temp VAR WORD

' This uses DIV32 by preloading the system variable it uses with
' the value we want to divide. To find out how many days has passed
' we divide the number of seconds with 86400 (number of seconds in a day).
' However, DIV32 only supports 16 bit divisor so we need to do the
' division in two steps, 21600 * 4 = 86400.

R0 = AB ' High word of seconds into system var
R2 = CD ' Low word of seconds into system var
owDays = DIV32 21600 ' Divide by 86400
owDays = owDays / 4

' Now we need to subtract 86400 seconds from the running time
' one time for each day that has passed. Since we're working with
' 16-bit words we need to this sort of "manually".
' 65536 + 20864 = 86400

For i = 1 to owDays
Temp = CD
CD = CD - 20864

IF Temp < CD THEN ' Did we underflow the low word?
AB = AB - 1 ' If so, decrement high word
ENDIF

AB = AB - 1 ' Subtract 65536
NEXT

' At this point ABCD contains anything from 0 to 86399 and we need to
' figure out how many "full hours" is in it. Because 86399 is more than
' what fits in a 16-bit word we're using DIV32 trick to divide the
' number of seconds by 3600 (number of seconds in an hour).

R0 = AB ' High word of what's left in seconds into system var
R2 = CD ' Low word of what's left in seconds into system var
owHH = DIV32 3600 ' Divide by the number of seconds in an hour

' Subtract 3600 from the running time, one time for each hour.
For i = 1 to owHH
Temp = CD
CD = CD - 3600

IF Temp < CD THEN ' Did we underflow the low word?
AB = AB - 1 ' Subtract one from high word.
ENDIF
next
' Now, we're down to minutes and there can be no more than 3599 seconds left
' so we can easily handle it with just the low word and normal math
owMM = CD / 60

' And finally we can get the seconds by getting the reminder.
owSS = CD // 60

return

Thank you so much.
I preloaded the clock with 23:59:00 and it rolled over to 00:00:00 as expected!

Now to go digest and try and understand what you actually told my little micro-conntroller to do.

after all that is what we are doing when we write code "telling these little pieces of silicone" how and when to do something :D

thanks again!

I actually have two clocks and a DS18b20 on my little breadboard...
the I2C DS1307 and the onewire DS2417 (boy its a liittle guy) you can see it just below the LCD and on the PCB in the breadboard.

You can see the time from the DS1307 (blue PCB) on the top line
you can see the 4 bytes of seconds count from the DS2417 on the 3rd line
on the bottom line is the result of you conversion code.

7687



Thanks

HenrikOlsson
- 26th January 2015, 06:00
Hi Dwight,
That's excellent news, glad I could help!
Sorry for the missing NEXT, I should have at least compiled it before posting but I was really short on time.

Regarding using LONGs. If you're using Microcode Studio as your IDE there's a checkbox in the View -> Compile and program options saying Use Compiler Long Words (18 series MCU Only). Tick that and you'll be compiling with LONGs enables. As the text says you have to use an 18F part so the 16F690 or 16F1828 will not work.

/Henrik.

Heckler
- 27th January 2015, 03:37
Sorry for the missing NEXT, I should have at least compiled it before posting but I was really short on time.

Henrik,
Please don't feel bad. I could only hope to quickly analyze someone elses code/project and toss out such a quick and good answer.

The value of forums like this is the willingness of folks like you, TABsoft and others to donate their time and talent to help others out.

The fact that you missed a "next" is trivial. The fact that you could understand my need and share a possible solution is the key.

I had looked at the DIV32 function in the PBP manual but could not understand how to apply it in my situation. The manual led me to believe that I first had to multiply two numbers. Which I did not need to do (or is that actually what one is doing by combining a LowWord and a HighWord??). ((don't think so... because FF x FF =FE01 not FFFF))

I did not know that one could pre-load the system variables manually (no mention of that fact in the manual) or which system variables would be involved (R0, R2)

HenrikOlsson
- 27th January 2015, 06:29
Hi Dwight,
No, that sort of info is not in the manual. I don't remember from where I got it but I'd be surprised if it wasn't from one of Darrels post.

Anyway, yes, normally what you do is multiply two 16bit number which results in a 32bit result then, imediately after, you execute the DIV32 which will divide that 32bit result by the 16bit divisor. The trick is to know that when you do the 16*16bit multiplication the result of that ends up in system variables R0 and R2. Once you know that there's nothing stopping you from manually loading those registers, the DIV32 function will not know the difference.

If you have a total of 1,125,000 seconds in your ABCD variable, looking at that in hex: 0011 2A88 what goes in R0 is 11h (17) and what goes on R2 is 2A88 (10888).
17*65536+10888=1,125,000.

I think there might be a possible issue with the code as posted. When the number of seconds passed is less than 86400 (one day) then owDays will be 0 and the FOR-NEXT loop subtracting 86400 seconds per day will execute 65535 times. I think the result will still be correct since it'll just wrap around but it may take a fair bit of time..... You might want to enclose that FOR/NEXT loop in an IF/THEN block checking if owDays > 0.

/Henrik.

Tabsoft
- 30th January 2015, 16:15
Henrik and Dwight.

I am not an expert by any stretch of the immagination, but I think I have uncovered some potential issues when using DIV32 in this manner.

After reviewing the algorithm provided by Henrik and the DIV32 command (much searching through the PBP manual, this forum, the assembler listing and using MPLAM Simulator), there are a few of rules regarding DIV32 that may impact the results from the algorithm.
These rules (including the obscure Rule 3 below) may not impact your particular application, but we might need to be aware of the implications if I am right.


Rules for using DIV32:
Rule 1. The dividend must be a maximum 31-bit number.
Rule 2. The divisor must be a maximum 15-bit number.
Rule 3: The quotient (result) of a DIV32 operation, (minding Rule 1 & 2), must not be more than a 16-bit number.

Here is where I derived these rules from.

From the manual "DIV32 is actually limited to dividing a 31-bit unsigned integer (max 2147483647) by a 15-bit unsigned integer (max 32767).
This should suffice in most circumstances."

This drives two rules.

Rule 1. The dividend must be a maximum 31-bit number.
Rule 2. The divisor must be a maximum 15-bit number.

From the forum LINK (http://www.picbasic.co.uk/forum/showthread.php?t=4422):
Q: "Is there any way of telling if the DIV32 operation result is greater than 16 bits?"
A: From Darrel Taylor: "If the result is too large, it will return 65535.
Consequently, 65535 isn't a valid result, even if that's really the answer."

This drives a new rule.

Rule 3: The quotient (result) of a DIV32 operation (minding Rule 1 & 2) must not be more than a 16-bit number.


With these Rules (1,2 & 3) plugged into your application with Henrik's algorithm, the implications as I see them are the following:
1. You will have to discard the MSB (bit-31) of the total seconds from your OW RTC.
This will initially limit the total number of seconds to 2,147,483,647($7FFF:FFFF) (24855 days, 3 hours, 14 minutes, 7 seconds).
Now this is ~68 years!

Note: Rule 2 is taken care of by Henrik in his 2-step division for owDays (owDays = DIV32 21600, owDays = owDays / 4)


2. Because of Rule 3, the total number of seconds from the OW RTC you can use reliably is 1,415,577,599($545F:FFFF)
(16383 days, 23 hours, 59 minutes, 59 seconds) this is ~45 years.
This number when the "owDays = DIV32 21600" operation is performed, returns a quotient of 65,535($FFFF) (16 bits).
This will be a valid result for the remaining operations in the algorithm.

I ran a few tests using pre-loaded values for total seconds.
The values I used were both <= $545F:FFFF and => $5460:0000.
The values equal to or below the 16-bit quotient limit returned the correct answer.
The values above the 16-bit quotient limit failed to return the correct answer.

Now I suppose if your total number of seconds is above 1,415,577,599($545F:FFFF) AND your "owDays = DIV32 21600" result should actually return 65,535($FFFF), then your ultimate results for days, hours, minutes, seconds should be correct.

Not that any of this might impact your particular application, but for other potential applications these rules may have significant impact.

I guess there might be multiple ways to handle this restriction in your particular application.
What I have come up with is a test within the "HMS:" subroutine that can then output an error value for HH:MM:SS.
This needs to take care of limiting the total seconds from the OW RTC to a 31-bit number and limiting the total seconds equal to or below the 16-bit quotient for the DIV32 operation.
1. Test the value of the AB word, if it is greater than "$545F" then shortcircut the algorithm, place decimal "99" for each of the owDays, owHH, OWMM and owSS.
Display : 99 days, 99:99:99

Here is a revised subroutine incorporating these rules. I also included Henrik's suggestion about owDays = 0 issue and I extrapolated that into the owHH as well.




'================================================= ============
' convert the one wire seconds count to HH:MM:SS
'================================================= ============
HMS:

'owDays VAR WORD
'i VAR WORD
'Temp VAR WORD

' This uses DIV32 by preloading the system variable it uses with
' the value we want to divide. To find out how many days has passed
' we divide the number of seconds with 86400 (number of seconds in a day).
' However, DIV32 only supports 16 bit divisor so we need to do the
' division in two steps, 21600 * 4 = 86400.

'Place the entire algorithm in an If/Then test to limit the total seconds to
'a 31-bit number and to check that the DIV32 will return a result <= 16-bits.

if AB < $5460 then ' Result of DIV32 will return a quotient that is 16-bit
' and caps the total seconds to 31-bits.

R0 = AB ' High word of seconds into system var
R2 = CD ' Low word of seconds into system var
owDays = DIV32 21600 ' Divide by 86400
owDays = owDays / 4

' Now we need to subtract 86400 seconds from the running time
' one time for each day that has passed. Since we're working with
' 16-bit words we need to this sort of "manually".
' 65536 + 20864 = 86400

if owDays > 0 then 'As per Henrik, Speeds up the computation instead of wrapping around
For i = 1 to owDays
Temp = CD
CD = CD - 20864

IF Temp < CD THEN ' Did we underflow the low word?
AB = AB - 1 ' If so, decrement high word
ENDIF

AB = AB - 1 ' Subtract 65536
NEXT
else

endif

' At this point ABCD contains anything from 0 to 86399 and we need to
' figure out how many "full hours" is in it. Because 86399 is more than
' what fits in a 16-bit word we're using DIV32 trick to divide the
' number of seconds by 3600 (number of seconds in an hour).

R0 = AB ' High word of what's left in seconds into system var
R2 = CD ' Low word of what's left in seconds into system var
owHH = DIV32 3600 ' Divide by the number of seconds in an hour

' Subtract 3600 from the running time, one time for each hour.
if owHH > 0 then 'Also speeds up computation instead of wrapping around
For i = 1 to owHH
Temp = CD
CD = CD - 3600

IF Temp < CD THEN ' Did we underflow the low word?
AB = AB - 1 ' Subtract one from high word.
ENDIF
next
endif
' Now, we're down to minutes and there can be no more than 3599 seconds left
' so we can easily handle it with just the low word and normal math
owMM = CD / 60

' And finally we can get the seconds by getting the reminder.
owSS = CD // 60

else 'Result of DIV32 21600 will return 65535 because it is > 16-bit
'or the total seconds was a 32-bit number.

'Create an error value for output
owDays = 99
owHH = 99
owMM = 99
owSS = 99
endif

return




I hope this is correct and it helps.

Heckler
- 30th January 2015, 23:33
TABSoft,

As best I can tell, your concern for accuracy comes into play when the clock is allowed to count up to a (relatively) large number.

While doing some testing on seconds counts in the 5 year or greater range, I found that Henrik's routine took over a second to complete (@ 4 MHz osc speed) so I added this bit of code...

IF owDays >=365 THEN
GOSUB calcOWtime
GOSUB setOWtime
owDays=0
ENDIF


This way the 32 bit counter will not get to large (assuming the microcontroller is turned on at least every year or so. Then this next bit of code is executed. Which converts the HH:MM:SS back into a "seconds count" and programmed back into the RTC, thus clearing the upper portion of the 32 bit counter.


'= = = = = = = = = = = = = = = = = = = = = = = = = = = =
' Subroutine to calculate OW time bytes A,B,C,D
'= = = = = = = = = = = = = = = = = = = = = = = = = = = =
'this routine take the three values HH, MM, SS and converts them to a
' 32 bit seconds count and loads the 4 Bytes A,B,C,D
calcOWtime:
AB= owHH ** 3600
CD= owHH * 3600 + owMM*60 + owSS
ByteA=AB.byte1
ByteB=AB.byte0
ByteC=CD.byte1
ByteD=CD.byte0
return

'= = = = = = = = = = = = = = = = = = = = = = = = = = = =
' Subroutine to write OW time
'= = = = = = = = = = = = = = = = = = = = = = = = = = = =
setOWtime:
OWOUT OWTPin,%011,[$CC,$99,%10001100,ByteD,ByteC,ByteB,ByteA] 'set OW clock %10001100 OSC on
return

I realize that this little routine might introduce a few milliseconds error once every year or so to reset the counter to <365 Days.

But, bear in mind that my project is a BOY SCOUT "Electronics" merit badge kit. The kit is powered by a CR2032 coin cell. It fills the final requirement to solder up a project.
Over the past 3-4 years we have built over 400 of these little kits.

I am just toying with the idea of allowing the kit to keep time.
Currently here is what the kit does...

* displays the Boy Scout Law
* display current temperature
* track and remember Low and High temperature (if left on,
goes to sleep to save batt and wakes up every 60 sec to check temp)
* play a little race car game
* display several smiley faces and other icons

I designed this kit to be useful and teach microcontrollers
(which is what most hobby "electronics" today is centered around)
Since I have to re-order more PC boards I thought I would add an RTC to the design.
(I would pre solder the RTC and crystal as it is surface mount)
the Scouts do amazingly well for such small soldering. I have an experienced solder coach sit across from them as they build the kit in about 30 min.

What do you think...

7695
7693
7694
7696

Heckler
- 30th January 2015, 23:46
PS
you can see the DS18b20 temp sensor at the top above the 8x8 LED matrix
the two large holes at the top corners are for a cord lanyard to be attached and the Scouts can wear it around their neck.
the two push buttons allow for different functions and for the race car game to be played when turned sideways.
The CR2032 battery lasts for a LONG! time... at least a couple of years of random usage

Here is a crude hand drawn schematic.

All the pins are in use on the 20 pin PIC
You can see that the DS18b20 is tied to one of the pushbuttons
I plan to tie the RTC to the other push button
Therefore either of the OneWire devices can only be read when the button is NOT depressed.
(I account for that limitataion in code)
No, there are no current limiting resistors used because of the internal impedance of the CR2032 batt
and I pulse the rows and columns of the LED matrix to limit current (haven't had a failure yet)

the Kit has been a great success and hopefully inspired some new electronics enthusiasts!

The DS2417 RTC would be powered directly from the battery and not powered down when the PIC is off.

I will tie the RTC to the other button input (this way I can ignore the One Wire address requirement as each input will only talk to one OneWire device)

7697

Thanks again to you and Henrik for helping out here
This is GREAT fun!!! (although my Wife can't figure out why:D)
To me it really is rewarding to develop code and be able to trouble shoot and see the results almost instantly with flash programming.

Heckler
- 31st January 2015, 17:16
Ok, I realized that pin 4 on the PIC is the MCLR pin, which is only capable of acting as an INPUT. It is not bi-directional.
The DS18b20 temp sensor and the DS2417 RTC are going to have to share pin 3 (PortA.3)

so now my code will have to at least take into account the family code 28h vs 27h when communicting with the two OneWire devices.

PS
Sorry about the photo size (I think it's a bit too big)

Tabsoft
- 1st February 2015, 00:58
Dwight,

Congratulations!

What a great project and an excellent reason for putting it together.
Providing real meaningful time to the Scouts.
You could have selected an off the shelf Time and Temp eKit and directed your Troop members to it.
But what real meaning would that have had?

Instead you created your own eKit that has direct and long-lasting meaning for a Scout.
Besides providing an electronics project for their merit badge and mentoring/overseeing their attainment of the badge.
You personalized the project.
Displaying the Boy Scout Law. Adding a game, having it display Smiley faces.
Creating a way to wear the project.

It is truly inspiring to see an individual not only donate charitable time to an organization,
but to also see them embody the primary tenets of the organization itself while doing so.

Outstanding!


Yes, my feedback on the DIV32 rules and how they affect accuracy only comes into play when your clock is allowed to count up to a large number (~45 years).
I doubt that the battery will last that long, no matter what power efficiency is.:)

I posted the information so you would see the issue/limitation but also for others that may come across this thread.
I have learned a lot from the great contributors to this forum over the years by just such actions.
Time for me to ante up as well.

Your checking and resetting the running count in the RTC every year is a good way to handle it in your project.
Introducing < 1 sec of error per year for the running count reset is 0.0000032%.
I think that is more than accurate enough!

You stated that during your testing for seconds values in the 5 year range, the routine was taking > 1 second to complete running at 4 MHz.
That is not unexpected since division in general is instruction intensive.
It's the iterative process for division that has to take place.
DIV32 only makes it worse, adding additional steps in the process with more registers to act on.

This being said, the time for the actual division subroutine "HMS" is probably less than a second for the Total Seconds your speaking of.
I ran some quick tests in MPLAB Simulator for the 16F690, seeding the Total Seconds with various values up to 5+ years.
Granted this is only simulation, but I have found it to be pretty close to actuals when you are not dealing with external device interactions.
You are really only dealing with instructions.


I attached the results to this post if your interested.

Too bad your out of pins on the PIC to keep your OW devices separate, but it looks like you got a good working idea to get around that too.

Again, I commend you on putting so much time, energy and passion into supporting the BSA.

HenrikOlsson
- 4th February 2015, 17:16
Hi guys,
Thanks a lot for testing the code and for the analasis!

I didn't write it with speed in mind but I certainly didn't expect it to run THAT FREAKIN slow, geeez 400ms.....can't have that.
It's clear that it's the owDays routine that is the culprit and the problem is not with the division (the DIV32 and div by 4) commands as these executes in about 600 instruction cycles. It varies a little depending on the actual numbers but around 600 instructions.

The big hog is the "subtraction loop" and to be honest I didn't see that one comming. It's a about 120 instructions which, at first doesn't seem like much but when it is being executed 4015 times (11 years) it does indeed add up.

So, here's a suggested workaround. What it does is subtracting the seconds in steps (if possible). First 250 days worth (for as many times as possible), then 25 days worth (for as many times as possible) untill it finally does day by day.

My test show the complete owDays routine for Test Case 5 now takes around 3400 cycles instead of 420 000, that's roughly 125 times faster. Yes, it comes at the cost of a little more code space but it might be worth it.

Here's the modified section, everything else is the same, run it through its paces.



if AB < $5460 then ' Result of DIV32 will return a quotient that is 16-bit
' and caps the total seconds to 31-bits.
R0 = AB ' High word of seconds into system var
R2 = CD ' Low word of seconds into system var

owDays = DIV32 21600 ' Divide by 86400
owDays = owDays / 4


' Now we need to subtract 86400 seconds from the running time
' one time for each day that has passed. Since we're working with
' 16-bit words we need to this sort of "manually".
' 65536 + 20864 = 86400.
' As it turns out, doing this incrementally takes A LOT of time
' so we do it in steps. First (if possible) subtract 250 days worth for
' for as many times as possible. Then 25 days worth for as long as possible
' and finally one days worth for as many times as needed.

if owDays > 0 then ' As per Henrik, Speeds up the computation instead of wrapping around

i = owDays

WHILE i > 250 ' 21600000 seconds = 250 days
Temp = CD
CD = CD - 38656
IF Temp < CD THEN
AB = AB - 1
ENDIF

AB = AB - 329

i = i - 250
WEND

WHILE i > 25 ' 2160000 seconds = 25 days
Temp = CD
CD = CD - 62848
IF Temp < CD THEN
AB = AB - 1
ENDIF

AB = AB - 32
i = i - 25
WEND

While i > 0
Temp = CD
CD = CD - 20864

IF Temp < CD THEN ' Did we underflow the low word?
AB = AB - 1 ' If so, decrement high word
ENDIF

AB = AB - 1 ' Subtract 65536
i = i - 1
WEND
endif

/Henrik.

Tabsoft
- 4th February 2015, 18:04
Henrik,

No worries on the simulation testing. It's fairly easy as you know using MPLAB Simulator.
The tools are all there for the using.

The ability to setup a PBP project directly in MPLAB so you can edit the PBP source, recompile.
Set the breakpoints you want.
The beauty though for this kind of testing is really the Stopwatch feature!
Couple Stopwatch with breakpoints (either PBP source or ASM), it's great.


Yes, my wording regarding the computationally intensive nature of division was perhaps inadequate.
My statement about Division in general being "instruction intensive" was my indirect referral to the iterative subtraction routine required.
By stating that DIV32 "only makes matters worse", was the fact that the division process (iterative subtraction routine) is now exacerbated by a larger dividend (31bit number).
Again, not the best choice of words.:wink:

On the surface your new routine does indeed look like it will drastically reduces the number of instructions, great thoughts.
When I get a few minutes I will give the new code a simulation run through and provide back the results.

Thanks,

HenrikOlsson
- 4th February 2015, 19:44
Hi,
OK, I think I've managed to get it a little better still. It struck me that we could just as well use the DIV32 trick in reverse as well, ie instead of preloading the variables we can do the multiplication and get the result as two 16 bit numbers. This allows us to to the subtraction with larger numbers instead of doing the iterative subtraction. Here's the code:


'================================================= ============
' convert the one wire seconds count to HH:MM:SS
'================================================= ============
HMS:

' This uses DIV32 by preloading the system variable DIV32 uses with
' the value we want to divide. To find out how many days has passed
' we divide the number of seconds with 86400 (number of seconds in a day).
' However, DIV32 only supports 16 bit divisor so we need to do the
' division in two steps, 21600 * 4 = 86400.

' Place the entire algorithm in an If/Then test to limit the total seconds to
' a 31-bit number and to check that the DIV32 will return a result <= 16-bits.
' This may seem like limit but it's still something like 44 years.

if AB < $5460 then ' Result of DIV32 will return a quotient that is 16-bit
' and caps the total seconds to 31-bits.

R0 = AB ' Load high word of seconds into system var
R2 = CD ' Load low word of seconds into system var

owDays = DIV32 21600 ' Divide by 86400 in two steps
owDays = owDays / 4

' Now we need to subtract 86400 seconds from the total
' running time for each full day that has passed.

' First we do a dummy multiplication. We would like to multiply by
' 86400 but we can't (since it doesn't fit in a WORD) so we start
' by multiplying by 1/2 of that, or 43200.
Temp1 = owDays * 43200

' Then we retreive the 32bit result of the multiplication from system variables
Temp2 = R0 ' Get high word from system
Temp3 = R2 ' Get low word from system

' And multiply by 2 to get to 86400
Temp2 = Temp2 << 1 ' Shift high word 1 bit
Temp2.0 = Temp3.15 ' Move high bit of LSW into low bit of MSW
Temp3 = Temp3 << 1 ' Shift low word on bit


' Now do the subtraction
Gosub SubtractTime



' At this point ABCD contains anything from 0 to 86399 and we need to
' figure out how many "full hours" is in it. Because 86399 is more than
' what fits in a 16-bit word we're using DIV32 trick to divide the
' number of seconds by 3600 (number of seconds in an hour).

R0 = AB ' High word of what's left in seconds into system var
R2 = CD ' Low word of what's left in seconds into system var
owHH = DIV32 3600 ' Divide by the number of seconds in an hour

' Now we need to subtract 3600 seconds per full hour from the
' total number of seconds passed. As before we do a dummy multiplication
' and retreive the result from the system variables.

Temp1 = owHH * 3600
Temp2 = R0 ' Get high word from system
Temp3 = R2 ' Get low word from system

' Now do the subtraction
Gosub SubtractTime

' Now, we're down to minutes and there can be no more than 3599 seconds left
' so we can easily handle it with just the low word and normal math
owMM = CD / 60

' And finally we can get the seconds by getting the reminder.
owSS = CD // 60

else 'Result of DIV32 21600 will return 65535 because it is > 16-bit
'or the total seconds was a 32-bit number.

'Create an error value for output
owDays = 99
owHH = 99
owMM = 99
owSS = 99
endif

return

SubtractTime:
Temp1 = CD ' Remember low word
CD = CD - Temp3 ' Subtract low word
If Temp1 < CD THEN ' Did we underflow
AB = AB - 1 ' Then borrow
ENDIF
AB = AB - Temp2 ' Subtract high word
RETURN


It does use two extra WORD variables (and we could possible do something about that if strictly needed) but that's a small price to pay. It's now smaller than before and the complete routine (when fed Test Case 5) runs in about 1730 instruction cycles compared to the original 420200.

Try it out if you want.

/Henrik.

Tabsoft
- 5th February 2015, 03:46
Henrik,

Great thinking!

I re-ran the tests with your original algorithm. I then ran the 5 test cases with your second and third algorithms.

Here are the execution times for test case 5.

Original: 420.205ms
New: 7.464ms
Final: 2.463ms !!

I also ran a 6th test case which uses the maximum allowable seconds ($545F:FFFF).
Original: 1701.548ms
New: 12.502ms
Final: 2.423ms

These are great reductions in execution time.
I have posted the full results.
Another interesting note is the 3rd algorithm has a near flat execution time as the Total Seconds increase.
Very efficient.
A larger dataset would give a better picture, but I might save that when I have further time.

Thanks for sharing your thoughts and experience.

Regards,