ReadPGM_BYTE,  ReadPGM_WORD,  and  ReadPGM_ULONG

Purpose: Read one (1), two (2), or four (4) successive BYTEs from FLASH, or PROGRAM, memory.
Assumes:  WORD and LONG values are "little-endian", i.e., stored Least Significant BYTE (LSB) first.
The address specified is valid.
Passed:     R25:R24  VOID *   vPtrPGM  address of the first or only 328P PROGRAM BYTE to read (OR)
R26:R25:R24  VOID *   vPtrPGM  address of the first or only 1284P or 2560 PROGRAM BYTE to read.
Returns: R24 BYTEbValue the BYTE value at that FLASH address (OR)
R25:R24  WORD wValue the WORD value at that FLASH address (OR)
R25:R24:R23:R22  ULONG ulValue the ULONG value at that FLASH address
Alters: Only the return value Registers and the FLAGs
Example: 
   .ORG  0x1000

PGMReads:
   MOVW    R24, R26              ; Copy the pointer to the address
   CALL    AParseValue
   SBRS    R21, BIT_PV_VALID     ; Exit if the parser could not extract a
   RET                           ;  numeric value from the string passed

   MOVW    R30, R22              ; Returns R25:R24:R23:R22; only need Low WORD
   MOVW    R24, R30              ; The saved address parsed above
   CALL    AReadPGM_BYTE         ; Returns the EEPROM BYTE in R24
   MOV     R22, R24              ; Transfer the EEPROM BYTE returned to R22
   LDI     R25, ACSZ_BYTE >> 8   ; Point to the first string
   LDI     R24, ACSZ_BYTE & 0xFF
   LDI     R19, '='              ; Specify the intermediate character
   CALL    APrintDescAndHexBYTE  ; Print description string, 0x and BYTE value

   MOVW    R24, R30              ; Same beginning address as before
   CALL    AReadPGM_WORD         ; This time read two (2) BYTEs
   MOVW    R22, R24
   LDI     R25, ACSZ_WORD >> 8
   LDI     R24, ACSZ_WORD & 0xFF
   CALL    APrintDescAndHexWORD

   LDI     R25, ACSZ_ULONG >> 8  ; This one is done differently since there is
   LDI     R24, ACSZ_ULONG & 0xFF;  no PrintDescAndHexULONG() function
   CALL    APrintFLASHASCIIz     ; Print a NULL-terminated string
   RCALL   ALocalReadULONG       ; Subroutine below to Read and setup next call
   CALL    AFormatULONGHex       ; Format and print the value in R25:R24:R23:R22
   LDI     R25, (ACSZ_ULONG + 7) >> 8      ; Point to the " = 0x"
   LDI     R24, (ACSZ_ULONG + 7) & 0xFF
   LDI     R22, 3                ; Specify that only three (3) BYTEs be printed
   CALL    APrintFLASHString     ; Differs from PrintFLASHASCIIz (specify count)
   RCALL   ALocalReadULONG       ; Reread the ULONG value into R25:R24:R23:R22
   CALL    AFormatULONG          ; Format and print the ULONG value as the
   JMP     APrintNewLine         ;  decimal equivalent to the HEX value

LocalReadULONG:
   MOVW    R24, R30              ; Same beginning address as the others
   CALL    AReadPGM_ULONG
;- MOVW    R20, R26              ; Point to the parsing buffer  (OR)
   LDI     R21, 0                ; Set the FormatULONGHex copy destination to
   LDI     R20, 0                ;  a NULL pointer to omit copying it anywhere
   LDI     R18, (1 << BIT_FMT_PRINT) | (1 << BIT_FMT_COMMAS)
   RET                           ; Only FormatULONG() utilizes BIT_FMT_COMMAS

CSZ_BYTE:  .STRING  "FLASH BYTE at that address"
CSZ_WORD:  .STRING  "; WORD"
CSZ_ULONG: .STRING  "; ULONG = 0x"
Test it:
m>@ 0x1000 0x3fad
FLASH BYTE at that address = 0x00; WORD = 0xCA00; ULONG = 0x3B9ACA00 = 1,000,000,000 
m>@ 0x1000 0x3fB1
FLASH BYTE at that address = 0x00; WORD = 0xE100; ULONG = 0x5F5E100 = 100,000,000
m>@ 0x1000 0x3fb5
FLASH BYTE at that address = 0x80; WORD = 0x9680; ULONG = 0x989680 = 10,000,000
m>@ 4096 0x3FB9
FLASH BYTE at that address = 0x40; WORD = 0x4240; ULONG = 0xF4240 = 1,000,000
m>@ 4096 0x3FBD
FLASH BYTE at that address = 0xA0; WORD = 0x86A0; ULONG = 0x186A0 = 100,000
m>@ 4096 0x3FC1
FLASH BYTE at that address = 0x10; WORD = 0x2710; ULONG = 0x2710 = 10,000
m>@ 4096 0x3fc5
FLASH BYTE at that address = 0xE8; WORD = 0x03E8; ULONG = 0x3E8 = 1,000
m>@ 4096 0x3fc9
FLASH BYTE at that address = 0x64; WORD = 0x0064; ULONG = 0x64 = 100
m>@ 4096 0x3fcd
FLASH BYTE at that address = 0x0A; WORD = 0x000A; ULONG = 0xA = 10
m>                                                                              
Notes: Specifying an address past the end of protected PROGRAM memory space (for MIRTOS Version 3.01, this is 0x4000 in a 328P, 0x1AE00 in a 1284P, and 0x3AC00 in a 2560) will always return 0xFF (or 0xFFFF or 0xFFFFFFFF.)
ReadPGM, ReadPGM_CHAR, ReadPGM_1BYTE, ReadFLASH, ReadFLASH_BYTE, ReadFLASH_CHAR, and ReadFLASH_1BYTE are all synonyms for ReadPGM_BYTE.
ReadPGM_INT, ReadPGM_2BYTEs, ReadFLASH_INT, ReadFLASH_WORD, and ReadFLASH_2BYTEs are all synonyms for AReadPGM_WORD.
ReadPGM_LONG, ReadPGM_4BYTEs, ReadFLASH_LONG, ReadFLASH_ULONG, and ReadFLASH_4BYTEs are all synonyms for AReadPGM_ULONG.
Dropin: 
(setup
code)
;   Setup for the ReadPGM_*() or ReadFLASH_*() call:
; Passed:          R25:R24 VOID * vPtrPGM 328P FLASH address from which to begin reading or
;              R26:R25:R24 VOID * vPtrPGM 1284P or 2560 FLASH address at which to begin
; Returns:             R24 BYTE   bValue the BYTE at that address
;                  R25:R24 WORD   wValue the WORD at that address
;          R25:R24:R23:R22 ULONG  ulValue the ULONG at that address
; Alters:  Only the Registers returned and the FLAGSs
Also see:  The ReadEEPROM* and WriteEEPROM* System Functions.