millis, millisLowWORD, and millisLowBYTE

Purpose: Read either the complete milliseconds variable maintained by the Operating System, or only the low order WORD, or low order BYTE portion of it.
Assumes:  System interrupts are enabled, so the Operating System is able to update ulT0_Millis.
Passed: Nothing
Returns:    R25:R24:R23:R22  ULONG  ulmSec  4-byte value contained in ulT0_Millis (for millis)
R25:R24  WORD  wmSec  2-byte value contained in the low 2 bytes of ulT0_Millis (for millisLowWORD)
R24  BYTE  bmSec  1-byte value contained in the low byte of ulT0_Millis (for millisLowBYTE)
Alters: Only the Registers returned (not even the FLAGS)
Example:  
ShowAll3MillisValues:
   CALL    AmillisLowBYTE        ; Obtain the Low Order BYTE and save it to R30,
   MOV     R30, R24              ;  which the other calls below do not change
   CALL    AmillisLowWORD        ; Obtain the Low Order WORD and save that to
   MOVW    R26, R24              ;  R27:R26, which the other calls preserve
   CALL    Amillis               ; Get the complete R25:R24:R23:R22 ULONG value
   CALL    ADumpRegs_Begin       ; View all the Registers before the output

   CALL    APrint0xHexWORD       ; Print the millisecond value High Order WORD
   MOVW    R24, R22              ; Move the Low Order WORD into R25:R24 and
   CALL    APrintHexWORD         ;  print that, without a "0x" prefix or space
   RCALL   APrintCommaSpace      ; Use the subroutine below to print ", "
   MOVW    R24, R26              ; The millisLowWORD saved in R27:R26 above
   CALL    APrint0xHexWORD       ; Print the WORD with the "0x" prefix
   RCALL   APrintCommaSpace
   MOV     R24, R30              ; The millisLowBYTE obtained and saved above
   CALL    APrint0xHexBYTE
   JMP     APrintNewLine         ; Print a Carriage Return, Line Feed and exit  

PrintCommaSpace:
   LDI     R24, ','              ; Specify the character to print, then jump to
   JMP     APrint1CHARAnd1Space  ;  output the 2 chars and return to our caller
Notes: The Operating System updates the UNSIGNED LONG variable ulT0_Millis, for which the range of values is 0 to 4,294,967,295 mSec. It automatically rolls over upon reaching that maximum value, but a little over 49.7 days pass between occurrences. The ulT0_Millis may be read directly, and even changed, noting that a Timer0 interrupt might occur in between reading or writing bytes. It is preferable to use these routines, since they assure reading a integral WORD or ULONG variable. That caveat does not apply to a BYTE variable, of course.
The Operating System uses the MCU Timer0 Interrupt Service Routine to update the ULONG value, with the Timer0 prescalar configured for clkIO/64. With interrupts enabled, a Timer0 Overflow interrupt will occur every 16,384 (= 256 * 64) system oscillator ticks. If the MPU is running at precisely 8 MHz, Timer0 overflows every 2.048 milliseconds; at precisely 16 MHz, that is every 1.024 milliseconds. The thing to realize is that ulT0_Millis is not "incremented", that is, it is updated, or advanced, but not necessarily by one (1) count. For example, at 8 MHz, ulT0_Millis is updated by 2 counts, but there is occasionally (6 times per second, in this example) one additional count added. Every 125 Timer0 ticks, the actual mSec time passed and ulT0_Millis match, whether at 16 MHz, 8 MHz, 4 MHz, 2 MHz, or 1 MHz.
Also see:  The Seconds variables, the msTimerBYTE, msTimerWORD, and msTimerULONG timers, and the speed decrease and increase commands.