Advertisement  

Thursday, 03 July 2025
     
 
Main Menu
Home Home
Shop Shop
News News
BASCOM-AVR BASCOM-AVR
BASCOM-8051 BASCOM-8051
Products Products
Application Notes Application Notes
Publications Publications
Links Links
Support Center Support Center
Downloads Downloads
Forum Forum
Resellers Resellers
Contact Us Contact Us
Updates Updates
MCS Wiki MCS Wiki
Online Help
BASCOM-AVR Help BASCOM-AVR Help
BASCOM-8051 Help BASCOM-8051 Help
Contents in Cart
Show Cart
Your Cart is currently empty.
Search the Shop

Products Search

User Login
Username

Password

If you have problem after log in with disappeared login data, please press F5 in your browser

RSS News
 
     
 

 
   
     
 
AN #174 - Kixlines - Tetrasexagesimal number compression to speed up serial communication Print
by Natalius Kiedro

'                           _    _      _ _
'                          | | _(_)_  _| (_)_ __   ___  ___
'                          | |/ / \ \/ / | | '_ \ / _ \/ __|
'                          |   <| |>  <| | | | | |  __/\__ \
'                          |_|\_\_/_/\_\_|_|_| |_|\___||___/
'
'                          A tetrasexagesimal number format
'                          and how to compress numbers for
'                          standard serial communication.
'
'                          by Natalius Kiedro, March 2010
'                          Natalius272 at yahoo dot de
'
'     _________________________________________________________________________
'    |Please direct questions on Kixline applications to the AR7212 subforum of|
'    |the BASCOM forum. Non commercial code utilization only. Commercial utili-|
'    |zation requires contacting the author via the email address given above. |
'    ===========================================================================
'
'    Keywords: KIX kixlines coding encryptation GPS AHRS IMU AR7212 autopilot
'
'
'    Summary
'    =======
'    Kixlines contain numbers on the base/radix 64. Numbers are converted into the
'    tetrasexagesimal KIX format by taking consecutive chunks of 6 bits from the LSB
'    side of 1 byte, 2 bytes (words/integers) or 4 bytes (long integers) and assigning
'    these chunks to readable ASCII characters. This is similar to Base64-encoding; KIX
'    however uses an alphabet which is linearily mapped to the order of characters in the
'    ASCII alphabet. KIX digits range from "0" (ASCII48) to "o" (ASCII 48+63 = 117).
'    This note introduces elementary operations for the encoding and decoding of KIX-
'    based numbers in various number types. Transparent ASCII-based number are often
'    very space consuming. A NMEA 183 message, for example, used for GPS strings may
'    take more than 70 bytes for just a few numbers. On the other side, the binary format
'    needs input-output software different from what is used for standard ASCII and is
'    often more cumbersome than standard text I/O. KIX can be understood as an expanded
'    HEX with 6 instead of 4-bit-"nibbles". By choosing proper variable types, KIX can
'    be as space-efficient as binary while being readable and printable by standard textI/O.
'    KIX is recommended for situations where data exchange between components of a system
'    is rate limiting as compared to the processing of data within such components.
'    Such situations often arise in wireless networks or bandwidth shared point to point
'    connections, such as telemetry and remote controlled applications. As example it is
'    shown how to compress $GPGGA and $GPRMC for RC-flight applications.
'
'    Indroduction
'    ============
'    Code standards have something special. Once they are fixed they don't evolve anymore.
'    Only little changes might occur. The oldest example we know is the genetic code which
'    was fixed around 3+/-1 gigayears ago. Nobody knows exactly when, where, and why. The
'    code assigns triplets of bases in DNA to amino-acid "letters" in proteins. It
'    contains "control characters" such as those for the start and stop of a genetic
'    message to be translated into a protein. Once the code was fixed, cellular life (as
'    we know it) started to evolve on our planet. Code fixation events seem to enable new
'    ways of doing things, let systems interchange information with other systems in a
'    different way. What would we do, for example, without ASCII and HEX today?
'
'    Standard ASCII defines a 7 bit code, as only the lower characters (0..127) are
'    really "fixed". Many "standards" exist for the upper 127 characters, mainly due to
'    the fact that diffent human languages also require different characters. Attempts
'    to unify things (e.g. Unicode) necessarily have to leave the 8-bit coding range.
'    While "localization" above 8-bit is acceptable for characters describing human
'    languages, "the language of numbers" should be universal and print on a Japanese
'    computer exactly as on a French, Canadian one Russion ones. Standard ASCII just
'    offers the digits "0" to "9", as well as seperators like comma, space, and dots
'    which already come with a context-dependant meaning. For example, decimal seperators
'    like dots and commas are context-dependant; American and German bankers reverse
'    their meaning. The only universal standard for representing numbers - only binary
'    numbers however - is HEX using the digit "0..9" and "A..F".
'
'    The goal of kixlines is to have a number format which is text-processable
'    like HEX and standard ASCII but more compact than these format. If we reserve 6 bit
'    of information for a digit (64 characters), we have the lower 32 control characters
'    of ASCII to maintain compatibility with standard text I/O, and further 31 characters
'    to encode "meaning". As a result of "meaning characters" or the latters missing,
'    Kixlines may look (and then in a way are) encrypted as:
'
'                       hg500Ab:Ac8U;xCbTVaDA19FhkRoM5fg[/__0R>
'
'    as well as "structured" similar to HEX:
'
'                       0[Ga cB@b 01A` 000o 0[0] 0000 oooo 0F0F
'
'    Each 4-character item in the latter line represents 3 bytes of information.
'    What is fascinating to me is the fact that the genetic code is also a tetrasexa-
'    gesimal one - the total number of possible base tripletts is 64 - only twenty of
'    these encode amino acids.
'
'    The KIX digit alphabet
'    ======================
'    The following table shows the ordering of KIX digits mapped to ASCII/DEC and HEX:
'
'         SYM KIX ASC HEX    SYM KIX ASC HEX    SYM KIX ASC HEX    SYM KIX ASC HEX
'           0   0  48  30      @  16  64  40      P  32  80  50      `  48  96  60
'           1   1  49  31      A  17  65  41      Q  33  81  51      a  49  97  61
'           2   2  50  32      B  18  66  42      R  34  82  52      b  50  98  62
'           3   3  51  33      C  19  67  43      S  35  83  53      c  51  99  63
'           4   4  52  34      D  20  68  44      T  36  84  54      d  52 100  64
'           5   5  53  35      E  21  69  45      U  37  85  55      e  53 101  65
'           6   6  54  36      F  22  70  46      V  38  86  56      f  54 102  66
'           7   7  55  37      G  23  71  47      W  39  87  57      g  55 103  67
'           8   8  56  38      H  24  72  48      X  40  88  58      h  56 104  68
'           9   9  57  39      I  25  73  49      Y  41  89  59      i  57 105  69
'           :  10  58  3A      J  26  74  4A      Z  42  90  5A      j  58 106  6A
'           ;  11  59  3B      K  27  75  4B      [  43  91  5B      k  59 107  6B
'           <  12  60  3C      L  28  76  4C      \  44  92  5C      l  60 108  6C
'           =  13  61  3D      M  29  77  4D      ]  45  93  5D      m  61 109  6D
'           >  14  62  3E      N  30  78  4E      ^  46  94  5E      n  62 110  6E
'           ?  15  63  3F      O  31  79  4F      _  47  95  5F      o  63 111  6F
'
'    The symbols "0".."o" are assigned to KIX-values 0..63 (ASCII 48..11 or 30..6F in
'    HEX) making up a tetrasexagesimal number scheme.
'
'    Base-64 coding has been previously employed (e.g. for generating MIME
'    attachments in Emails) or within data encryptation (e.g. PGP). The base-64
'    alphabet which is in use in these schemes however starts with capital "A" as
'    zero and puts the decimal digits to the high side of Base-64-digits followed by
'    "+" (ASCII 43) and "/" (ASCII 47). This is inconsistent with the ordering of
'    ASCII which puts "0..9" (ASCII 48-57) before "A..Z" (ASCII 65-90) followed by
'    "a..z" (ASCII 97-122). To decrease the overhead of coding and decoding and to
'    make things as straightforward as possible, digits in the KIX alphabet reflect
'    ASCII order directly. Decoding a KIX digit from ASC only means the substraction
'    of 48 while coding means just the addition of 48. The absence of any further
'    analysis per digit makes the KIX format particularily attractive for the com-
'    pression of numbers on small microcontrollers.
'
'    Unfortunately, ASCII is not grouped in a way that capital and small letters
'    follow the decimal digits directly. This is due to the fact that the English
'    language has 26 instead of 32 letters. This in turn caused that the ASCII fathers
'    needed to fill the gaps with special characters that don't appear to us (as
'    humans) as "ordered" as the "ABC" or the "0..9" we learn as childs in the first
'    school days. We need "fool's bridges" to memorize special characters. Here are
'    two "fool's bridges" for characters which separate decimal digits from capital
'    letters, and for characters between capital and small letters. These are:
'
'         :;<=>?@  "Eyes with a smile are smaller, equal, or larger than
'                   questions in emails"
'
'         [\]^_`   "My home has a left wall, stairs, a right wall, a roof,
'                   a basement, and a chimney"
'
'    If one now remembers:
'
'         "Digits have no eyes but capital letters have a home for small letters"
'
'    one has got an idea how ASCII symbols are ordered in the range of the KIX digits;-)
'
'
'    Let's start: How to encode numbers in KIX?
'    ==========================================
'    Here is source snippet in BASCOM. Let us assume that the integer array Master()
'    contains numbers between -384 and +384, and that Master(Dx7throttle) is -350. Iby,
'    Jby, Kby, Lby, ..., Iwo, Jwo, Kwo, Lwo... are what I like to call "general over-
'    writables", viz. globals that are used for temporary storage over and over again.
'    This helps to speed up BASCOM and to keep the code rather small.
'
'         Iwo = 512                        ' Iwo = 512
'         Iwo = Iwo + Master(dx7throttle)  ' Iwo = 512 - 350 = 162 = &B0000000010100010
'         Jwo = Iwo And &B0000000000111111 ' Jwo = &B0000000000100010 = 34
'         Kwo = Iwo And &B0000111111000000 ' Kwo = &B0000000010000000 = 128
'         Rotate Kwo , Right , 6           ' Kwo = &B0000000000000010 = 2
'         Jby = Jwo                        ' Jby = &B00100010 = 34
'         Jby = Jby + 48                   ' Jby = 34 + 48 = 82
'         Kby = Kwo                        ' Kby = &B00000010 = 2
'         Kby = Kby + 48                   ' Kby = 2 + 48 = 50
'         Lst = Chr(kby) + Chr(jby)        ' Lst = "2";"R"
'         Print #2 , Lst;                  ' ====> 2R
'
'    So, -350 comes out as 2R, 2 characters less. The binary format would also use 2 bytes.
'    Of course, the addition of 512 in the beginning requires your knowledge that the
'    numbers in the array are less than 10bit - if the receiving end shares this knowledge
'    as a "code key", it will add the 512 to the decoded 2R to arrive at -350.
'
'    There are numerous ways to improve the snippet. More elegant is a dedicated
'    byte array instead of the general overwritables kby and jby. If this is overlayed
'    with an ASCIIZ string, the BASCOM CHR conversion and string concatenation does not
'    need to be carried out anymore. More efficient is to do the bit shifting and the
'    logical AND using $ASM...
'
'    Here is another example. Imagine that you have converted GPS-time from NMEA into the
'    long integer Ggatime with millisec resolution. Do it for yourself (one time please)
'    manually - what is the KIX outcome, if Ggatime is 42123456?
'
'        Ilo = Ggatime / 10
'        Jlo = Ilo And &B00000000000000000000000000111111
'        Klo = Ilo And &B00000000000000000000111111000000
'        Llo = Ilo And &B00000000000000111111000000000000
'        Mlo = Ilo And &B00000000111111000000000000000000
'        Rotate Klo , Right , 6
'        Rotate Llo , Right , 12
'        Rotate Mlo , Right , 18
'        Jby = Jlo
'        Jby = Jby + 48
'        Kby = Klo
'        Kby = Kby + 48
'        Lby = Llo
'        Lby = Lby + 48
'        Mby = Mlo
'        Mby = Mby + 48
'        Lst = Chr(mby) + Chr(lby) + Chr(kby) + Chr(jby)
'        Print #2 , Lst;
'
'   Variable Types recommended for KIX
'   ==================================
'   The low granularity of current variable types (1,2,4,8 byte-wise: byte, word/integer,
'   long, ...) is optimal for the data processing in current computers but is expected to
'   be suboptimal if not the processing but the transmission of data is the overall
'   bottleneck of a multi-component system. 6-bit-chunks of data can lead to a more
'   fine-grained structuring of  number space than 8-bit chunks, especially when the
'   latter are concatenated in an exponential (1,2,4,8,..) instead of linear
'   (1,2,3,4,5,6,7,8) fashion. For a digital computer it makes perfect sense to go the
'   exponential road, but I/O of numbers may enjoy higher granularity, especially if we
'   look at microcontrollers in sensor-networks, wireles remote control and such things.
'   This in turn, requires more types of variables to be defined.
'
'   So lets go the practical way: Instead of defining number space by bytes/word,integers/
'   longs/single/double or whatsoever, let us use multiples of 6-bit characters to do it.
'   Remember that every digit holds a number between 0 and 63, so:
'
'        Type KW1, range : 0 to o         (= 0 to 63)
'             KW2, range : 00 to oo       (= 0 to 4095)
'             KW3, range : 000 to ooo     (= 0 to 262143)
'             KW4, range : 0000 to oooo   (= 0 to 16777215)
'             KW5, range : 00000 to ooooo (= 0 to 1073741823)
'             KI1, range : 0 to o         (= -31 to +31)
'             KI2, range : 00 to oo       (= -2047 to +2047)
'             KI3, range : 000 to ooo     (= -131071 to +131071)
'             KI4, range : 0000 to oooo   (= -8388607 to +8388607)
'             KI5, range : 00000 to ooooo (= -536870911 to +536870911)
'
'             KW1 means a KIX word with one character, KWn for n characters accordingly.
'             KI1 means a KIX integer with one characters, KIn for n characters
'                 accordingly.
'
'   The above types should hold for a while. If not, just expand them whenever needed.
'   As microcontrollers and embedded computers typically do not need to deal not with
'   really huge numbers (such as the number of hydrogen atoms in our universe, 1E+80)
'   floating point numbers can be encoded in a more realistic fixed point format. Well,
'   if not, you are free to define  your own KF12 format.
'
'   Comma or Comma-free?
'   ====================
'   The genetic code is an example for a "comma free" code - base triplets called codons
'   are concatenated without seperators. I borrowed the term just to indicate absence of
'   seperators not necessarily commas (literally).
'
'   Transparent formats such as NMEA 183 often contain separators such as commas or spaces
'   to let us read such messages directly. "Readable HEX" also contains blanks to help us
'   recognizing which nibble belongs to which byte. KIX can be as readable as HEX if we
'   put blanks between numbers - as in the example above. This however gives sense only
'   for the representation of binary information - although it helps to compress readings.
'   Strings of 4 characters compress exactly 3 bytes of information, simply because 3 * 8
'   = 4 * 6.
'
'   My recommendation is to use the remaining readable characters of the ASCII Alphabet
'   to encode "meaning". These characters are:
'
'        SP!"#$%&'()*+,-./ (ASCII 32..47),
'        pqrstuvwxyz{|}~   (ASCII 112..126)
'
'   The following as number-preceeding seperators indicating the type and format of the
'   next KIX number:
'
'        SP               (ASCII 32)     for a "binary" KIX format grouped in 4 characters
'                                        encoding 3 bytes as well for word based (ordinal,
'                                        non-signed) KIX numbers of type KW1..KW5
'        !                (ASCII 33)     for KIX integers KI1..KI5
'
'   Kixlines to be processed using standard ASCII-I/O should be identifyable at least by
'   a stop character. Reserve:
'
'        <CR>             (ASCII 13) as the stop character.
'
'   The remaining characters may find applications at a later stage. They may encode
'   fixed formatting of words or integers to be interpreted as fixed-point numbers.
'   They may encode logical or arithmetic operations on Kix numbers or they may encode a
'   given type of message for the kixline. Once the general idea has been grasped, there
'   is plenty of room to define new contexts.
'
'   One of the most foreseeable application of KIX numbers are comma-free kixlines. These
'   allow two things: Compression combined with "poor man's encryptation". Comma-free
'   kixlines are smaller and faster to process on small microcontrollers but require
'   the knowledge how the digits are grouped into individual numbers on both the sender's
'   and the receiver's side. This knowledge define a second layer of coding/decoding
'   which can be described as a table.
'
'   Here is an example:
'        -----------------------------------
'         NR ITEM            TYP POS LEN .XX
'        -----------------------------------
'        (1) GUI characters (CHR , 1 , 4 , 0)
'        (2) Warning dec    (DEC , 5 , 1 , 0)
'        (3) Signal dec     (DEC , 6 , 1 , 0)
'        (4) Autonomous dec (DEC , 7 , 1 , 0)
'        (5) Flightmode dec (DEC , 8 , 1 , 0)
'        (6) Master Thr     (KW2 , 9 , 2 , 0)
'        (7) Master Ail     (KW2 ,11 , 2 , 0)
'        (8) Master Ele     (KW2 ,13 , 2 , 0)
'        (9) Master Rud     (KW2 ,15 , 2 , 0)
'       (10) Bat_V          (KW3 ,17 , 3 , 2)
'       (11) Bat_I          (KW3 ,20 , 3 , 2)
'       (12) Temp           (KI2 ,23 , 2 , 1)
'       (13) RPM            (KW3 ,25 , 3 , 1)
'       (14) Airspeed       (KW3 ,28 , 3 , 1)
'       (15) Pulse          (KW2 ,31 , 2 , 0)
'       (16) reserved       (KW2 ,33 , 2 , 0)
'       (17) GPS time       (KW4 ,35 , 4 , 2)
'       (18) Dlate          (KI3 ,39 , 3 , 1)
'       (19) Dlone          (KI3 ,42 , 3 , 1)
'       (20) Dalte          (KI3 ,45 , 3 , 1)
'       (21) roll           (KI2 ,48 , 2 , 1)
'       (22) pitch          (KI2 ,50 , 2 , 1)
'       (23) yaw            (KI2 ,52 , 2 , 1)
'       (24) speed over gnd (KW3 ,54 , 3 , 1)
'       (25) Nsat           (KW1 ,57 , 1 , 0)
'       (26) Horiz Dil      (KW2 ,58 , 2 , 1)
'       (27) Fix            (DEC ,60 , 1 , 0)
'       (28) reserved       (KW4 ,61 , 4 , 0)
'       (29) *              (CHR ,65 , 1 , 0)
'       (30) Checksum       (HEX ,66 , 2 , 0)
'
'   Note that such Kixlines may contain information outside KIX words and integers:
'   "Printable" CHR (ASCII 32..126), DEC (ASCII 48..57), and HEX (ASCII 48..57, 61..66).
'   The latter require the substring lenght LEN which is redundant at the level of
'   KIX-types. POS is also redundant as it is given by the sum of previous LENs.
'   XX in the table defines how many decimal digits follow after the decimal seperator.
'
'   Decoding simple Kixlines
'   ========================
'   If the receiving end is a PC - e.g. connected via a wireless link to the Kixline
'   sender - one can do it like in the snippets below (PBWIN8.0). The Kixline is first
'   handled to a KIX parser which uses the above table as a "codec" information. The
'   parser calls a KIX analyzer which converts the actual KIX number into its decimal
'   pendant:
'
'       SUB Kixparse (BYVAL Kixline AS STRING)
'           ..
'           CALL Kixalyze ("DEC" , 5 , 1 , 0) : Warning = Kixlng
'           CALL Kixalyze ("DEC" , 6 , 1 , 0) : Signal = Kixlng
'           CALL Kixalyze ("DEC" , 7 , 1 , 0) : Autonomous = Kixlng
'           CALL Kixalyze ("DEC" , 8 , 1 , 0) : Fltmode = Kixlng
'           CALL Kixalyze ("KW2" , 9 , 2 , 0) : JoyU = Kixlng
'           CALL Kixalyze ("KW2" ,11 , 2 , 0) : JoyX = Kixlng
'           CALL Kixalyze ("KW2" ,13 , 2 , 0) : JoyY = Kixlng
'           CALL Kixalyze ("KW2" ,15 , 2 , 0) : JoyZ = Kixlng
'           CALL Kixalyze ("KW3" ,17 , 3 , 2) : BatV = Kixdbl
'           CALL Kixalyze ("KW3" ,20 , 3 , 2) : BatA = Kixdbl
'           ..
'       END SUB
'
'       SUB Kixalyze (BYVAL Kixins AS STRING, BYVAL Kixbeg AS LONG, BYVAL Kixlen AS LONG,_
'                     BYVAL Kixdec AS LONG)
'           LOCAL Kix AS STRING, I1$, I2$, I3$, I4$, I$, J$, Kixnum AS LONG
'           Kix = MID$(Kixline, Kixbeg, Kixlen)
'           SELECT CASE Kixins
'                  CASE "CHR"
'                       Kixstr = Kix
'                  CASE "HEX"
'                       Kixlng = VAL("&H" & Kix)
'                  CASE "DEC"
'                       Kixlng = VAL(Kix)
'                  CASE "KW1"
'                       Kixlng = ASC (Kix) - 48
'                  CASE "KW2"
'                       Kixlng = ASC (MID$(Kix, 2, 1)) - 48
'                       Kixlng = Kixlng + 64 * (ASC (MID$(Kix, 1, 1)) - 48)
'                  CASE "KW3"
'                       Kixlng = ASC (MID$(Kix, 3, 1)) - 48
'                       Kixlng = Kixlng + 64 * (ASC (MID$(Kix, 2, 1)) - 48)
'                       Kixlng = Kixlng + 4096 * (ASC (MID$(Kix, 1, 1)) - 48)
'                  CASE "KW4"
'                       Kixlng = ASC (MID$(Kix, 4, 1)) - 48
'                       Kixlng = Kixlng + 64 * (ASC (MID$(Kix, 3, 1)) - 48)
'                       Kixlng = Kixlng + 4096 * (ASC (MID$(Kix, 2, 1)) - 48)
'                       Kixlng = Kixlng + 262144 * (ASC (MID$(Kix, 1, 1)) - 48)
'                  ..
'                  CASE "KI2"
'                       Kixlng = ASC (MID$(Kix, 2, 1)) - 48
'                       Kixlng = Kixlng + 64 * (ASC (MID$(Kix, 1, 1)) - 48)
'                       Kixlng = Kixlng - 2047
'                  CASE "KI3"
'                       Kixlng = ASC (MID$(Kix, 3, 1)) - 48
'                       Kixlng = Kixlng + 64 * (ASC (MID$(Kix, 2, 1)) - 48)
'                       Kixlng = Kixlng + 4096 * (ASC (MID$(Kix, 1, 1)) - 48)
'                       Kixlng = Kixlng - 131071
'                  ..
'                  CASE ELSE
'           END SELECT
'           Kixdbl = Kixlng * 10^-Kixdec
'       END SUB
'
'   If the receiving end is a microcontroller it is better to analyze the Kix number
'   not by string processing and numerical operation but by bit shifting as follows.
'   Let's assume that we are dealing with the KIX number "abcd", type KW4. Let us put
'   this string into four (plus zero for ASCIIZ) consecutives bytes in memory:
'
'
'         ASCIIZ                       -------a -------b -------c -------d 00000000
'         ASCII (dec)                        97       98       99      100
'         Binary                       01100001 01100010 01100011 01100100
'         Byte Vars overlay                  V1       V2       V3       V4
'         -48 for V1..V4 gives         00110001 00110010 00110011 00110100
'         generally (A..D as bits)     00AAAAAA 00BBBBBB 00CCCCCC 00DDDDDD
'
'         Rotate V2&V4 left, 2, gives  00110001 11001000 00110011 11010000
'         generally                    00AAAAAA BBBBBB00 00CCCCCC DDDDDD00
'
'         Integer Vars overlay                        I1                I2
'         Rotate I1 right, 2, gives    00001100 01110010 00110011 11010000
'         Rotate I2 left, 2, gives     00001100 01110010 11001111 01000000
'         generally                    0000AAAA AABBBBBB CCCCCCDD DDDD0000
'
'         Long Integer overlay                                          L1
'         Rotate L1 right, 4, gives    00000000 11000111 00101100 11110100
'         generally                    00000000 AAAAAABB BBBBCCCC CCDDDDDD
'
'         example, decimal equivalent                             13053172
'         100-48 +(99-48)*64 + (98-48)*4096 + (97-48)*262144 =    13053172
'
'   If "abcd" were defined as KI4 one would need to substract 8388607 to arrive at the
'   result. The above algorithm "evaporates" all of the two leading zeros in each byte
'   just by simple bit shifting. For a KW5 number the operation would look as follows
'   after the subtraction of 48 - here only in general form:
'
'         After -48:          00AAAAAA 00BBBBBB 00CCCCCC 00DDDDDD 00EEEEEE
'         C&E byte shifts     00AAAAAA 00BBBBBB CCCCCC00 00DDDDDD EEEEEE00
'         I1&I2 shifts        00AAAAAA 0000BBBB BBCCCCCC DDDDDDEE EEEE0000
'         L1 shift            00AAAAAA 00000000 BBBBBBCC CCCCDDDD DDEEEEEE
'         A-byte copy         00AAAAAA 00AAAAAA BBBBBBCC CCCCDDDD DDEEEEEE
'         Result in L1                 00AAAAAA BBBBBBCC CCCCDDDD DDEEEEEE
'
'   From an original ASCII string of 5 character we can thus generate numbers as
'   large as 1073741823 with 10 decimal digits. What is now a bit confusing is that
'   the order of bytes in memory does not reflect the logical order of a "humanized"
'   bitstring as shown above. For us, the most significant bit of a bitstring is almost
'   left. For AVR microcontrollers, the directions left and right are the same at the
'   level of bits in a byte but reversed when it comes to the order of bytes. The most
'   significant byte is at a higher address than the least significant one. Maybe that
'   this "counterintuitive" ordering was seen by Atmel engineers when they introduced
'   the Xmega line of processors with 16bit-registers.
'
'   Application Example:
'   ====================
'   The following one brought me to think about Kixlines: Imagine that your micro is
'   short in UARTs, that you have lots of sensors like those required for autonomous
'   flight. In the AR7212x mentioned in AN#171, two UARTs are used for the 2.4 GHz
'   satellites (unidirectional), one (bidirectionally) for a small commercially RC gadget
'   called Unilog which brings in voltage, current, airspeed and altitude (from a baro-
'   metric sensor), and one UART is shared for GPS (NMEA instream) and telemetry
'   (outstream). Outstream is limited to an effective rate of 9600bps, not very much for
'   the info specified in the above "codec" table. For a transmission of data as "real
'   time" as possible, a more efficient packing of data sent to ground was a must.
'   Recently Kixlines became even more attractive, as the "port sharing trick" may be
'   also applicable for a new generation of MEMS-accelerometer/gyro/magnetometer sensors
'   (e.g. CHRobotic's CHR6dm and Sparkfun's Razor) which deliver the Euler Angles (yaw,
'   pitch, roll) usable for the autopilot in the AR7212x. Sensors which just "stream"
'   (like the Razor) may enjoy sharing the UART with an GPS "instream".
'   For "localized" GPS applications such as RC-flight a maximal spatial distance of
'   +/-2047m is sufficient as even the largest electric glider will be hard to recognize
'   more than 1Km away. The horizontal dilution (GPS error) usually exceeds 1m, so that
'   a resolution of 1m seems adequate. A range of -2km to +2km just needs the KI2 format.
'   The same format may encode Euler angles (yaw, pitch, roll) ranging from -180.0° to
'   180.0° or 0 to 360.0° alternatively. Text readable messages for transporting orientation
'   or position may be thus as short of as 6 chars + Start + Stop = 8 characters. Roughly
'   a tenth of a NMEA string. While positional information is 10 Hz max (about 800 bps),
'   Euler angles will need an update every servo cycle (45 Hz equivalent to about 3600 bps).
'   4400 bps needed in toto max(!) should "just" fit into the 4800 bps of a rate-limiting
'   standard GPS module. As the bottleneck and limitation of UART-sharing is the slowest
'   device, Kixline messages should be no problem when using more advanced
'   GPS equipment.
'
'   The code below shows how to produce kixlines which code the difference of latitute,
'   longitude and altitude in meters from the point where the GPS found its first fix.
'   These data are taken from the NMEA $GPGGA messages. In addition it packs speed over
'   ground and heading from $GPRMC into the kixline. You may run it without a GPS module
'   in the Bascom simulator to see how it works. At the hardware side you just need a micro
'   with ONE(!) UART. Connect it as follows to generate a hardware translator:
'
'                  nc   _____________________     _____________________    nc
'                  |   |                     |   |                     |   |
'               ___|___|___               ___|___|___               ___|___|___
'              |   RX  TX  |             |   RX  TX  |             |   RX  TX  |
'              |           |             |           |             |           |
'              |   NMEA    |             | NMEA2KIX  |             |    KIX    |
'              |  Talker   |             |Translator |             | Listener  |
'              |           |             |           |             |           |
'              |__Gnd_Vcc__|             |__Gnd_Vcc__|             |__Gnd_Vcc__|
'                  |   |                     |   |                     |   |
'        Vcc _____ | __|____________________ | __|____________________ | __|
'                  |                         |                         |
'        Gnd ______|_________________________|_________________________|
'
'
'   It is a straightforward task to have the translator also producing Kixline messages
'   from its own sensors (e.g. for yaw, pitch, roll) - coding and decoding is fast!
'
'

$regfile = "m1280def.dat"
$crystal = 11059200

$hwstack = 64
$swstack = 32
$framesize = 64

Declare Sub Kixencode()
Declare Sub Kixdecode()
Declare Sub Loby2bin()
Declare Sub Findhome()
Declare Sub Ggaparse()
Declare Sub Rmcparse()
Declare Sub Homeset()
Declare Sub Gpstime2millisec()
Declare Sub Late_lone_dec_conversion()
Declare Sub Example()
Declare Sub Mainloop()

Const Kix2 = 2047                                           'for making KI2 integers
Const Kix3 = 131071                                         'for making KI3 integers
Const Kix4 = 8388607                                        'for making KI4 integers
Const Kix5 = 536870911                                      'for making KI5 integers

'General overwritables
Dim Iby As Byte , Jby As Byte , Kby As Byte
Dim Ilo As Long , Jlo As Long , Klo As Long
Dim Ist As String * 15 , Jst As String * 15 , Kst As String * 15
Dim Isi As Single , Jsi As Single , Ksi As Single , Lsi As Single

'Globals Kix
Dim Kixinlo As Long
Dim Kixlo(5) As Long
Dim Kixloby(6) As Byte
Dim Kixout As String * 5 At Kixloby(1) Overlay
Dim Kixinby(6) As Byte
Dim Kixin As String * 5 At Kixinby(1) Overlay
Dim Kixint1 As Integer At Kixinby(1) Overlay
Dim Kixint2 As Integer At Kixinby(3) Overlay
Dim Kixdeclo As Long At Kixin Overlay
Dim Kixlineout As String * 50
Dim Kixlinein As String * 50
Dim Intflag As Byte

'Globals Gps
Dim Gpsst As String * 80
Dim Commas(16) As String * 10
Dim Gpsreadyflag As Byte
Dim Rmcvalidflag As Byte
Dim Ggatime As Long
Dim Ggalate As Long
Dim Ggalone As Long
Dim Ggaalte As Integer
Dim Ggahode As Integer
Dim Ggafixe As Byte
Dim Ggasate As Byte
Dim Late2m As Single
Dim Lone2m As Single
Dim Hometime As Long
Dim Homelate As Long
Dim Homelone As Long
Dim Homealte As Long
Dim Rmcspeed As Long
Dim Rmcheading As Long
Dim Dlate As Long
Dim Dlone As Long
Dim Dalte As Long
Dim Dots(2) As String * 15

Dim Loopist As String * 1

Config Com1 = 4800 , Synchrone = 0 , Parity = None , Stopbits = 1 , Databits = 8 , Clockpol = 0
Enable Interrupts
Enable Serial

Example
''Coding and decoding of a kixline with five numbers up to the KI5/KW5 format:
''The transparent pendant might contain a total of 50 decimal digits. Even with so
''many digits, coding takes 591 µs - decoding takes 279 µs. This is roughly the
''time needed to calculate the distance D between 2 points in 3D space
''(D=SQR(DX*DX+DY*DY+DZ*DZ)) which takes 359 µs. Transporting a single 70 char
''NMEA message from the talker to the translator takes about 140 ms at 4800 bps,
''280 ms for both messages. The transport of the kixline excerpting these messages
''(12 characters) takes about 25ms from the translator to the listener, roughly a
''tenth of time needed for the NMEA messages. Encoding and decoding is negligible
''(less than 1 ms for both together).


'Mainloop
'Decomment mainloop if you want to play around with your own translator.

End


'*******************************************************************************
'********************************** Kix encoder ********************************
'*******************************************************************************
Sub Kixencode()
    Kixlo(1) = Kixinlo And &B00000000000000000000000000111111
    Kixlo(2) = Kixinlo And &B00000000000000000000111111000000
    Kixlo(3) = Kixinlo And &B00000000000000111111000000000000
    Kixlo(4) = Kixinlo And &B00000000111111000000000000000000
    Kixlo(5) = Kixinlo And &B00111111000000000000000000000000
    Jby = 0
    For Iby = 1 To 5
       Rotate Kixlo(iby) , Right , Jby
        Jby = Jby + 6
        Kby = Kixlo(iby)
        Kixloby(iby) = Kby + 48
    Next Iby
    'Print Kixout
End Sub


'*******************************************************************************
'********                         Kix decoder                           ********
'******** Copy this code + variable definitions to the Kixline listener ********
'*******************************************************************************
Sub Kixdecode
    'Kixin = "dcba0"
    'Print "0abcd"
    'Loby2bin
    For Iby = 1 To 5
         Kixinby(iby) = Kixinby(iby) - 48
    Next Iby
    'Print "-48"
    'Loby2bin
    Rotate Kixinby(3) , Left , 2
    Rotate Kixinby(1) , Left , 2
    'Print "V3 & V5 left 2"
    'Loby2bin
    Rotate Kixint1 , Left , 2
    Rotate Kixint2 , Right , 2
    'Print "I1 Right2, I2 Left 2"
    'Loby2bin
    Rotate Kixdeclo , Right , 4
    'Print "L1 Right 4"
    'Loby2bin
    Kixinby(4) = Kixinby(5)
    Kixinby(5) = 0
    Jby = Kixinby(4) And &B00100000
    If Intflag = 1 Then 'note that this flag decides whether
       If Jby = &B00100000 Then 'the Kix output is in a long integer
           Kixinby(4) = Kixinby(4) Or &B11000000             'format
           'Loby2bin
       End If
    End If
    'Print Kixdeclo
End Sub


'*******************************************************************************
'***************** For following the course of decoding ************************
'*******************************************************************************
Sub Loby2bin
    For Iby = 1 To 5
         Kby = 6 - Iby
        Print Bin(kixinby(kby)) ; " ";
    Next Iby
    Print Bin(jby)
End Sub


'*******************************************************************************
'** Example for those who just want to test the code in the Bascom simulator ***
'*******************************************************************************
Sub Example()

    Gpsst = "$GPGGA,120757,5152.8557,N,00205.7338,E,1,06,2.5,121.954,M,49.4,M,,*XX"
    Print "This is the GPS GGA string at the homepoint:"
    Print Gpsst
    Ggaparse
    Findhome
    Gpsst = "$GPGGA,120859,5152.7133,N,00205.8130,E,1,06,2.5,459.123,M,49.4,M,,*XX"
    Print "This is one GPS GGA string during flight:"
    Print Gpsst
    Ggaparse
    Gpsst = "$GPRMC,120859,A,5152.7133,N,00205.8130,E,073.8,231.8,130694,004.2,W*XX"
    Print "This is the GPS RMC string for the same fix:"
    Print Gpsst
    Rmcparse
    Dlate = Ggalate - Homelate : Isi = Dlate : Isi = Isi * Late2m : Dlate = Isi
    Dlone = Ggalone - Homelone : Isi = Dlone : Isi = Isi * Lone2m : Dlone = Isi
    Dalte = Ggaalte - Homealte
   
Print "Now let's make a kixline which codes DX, DY, DZ from home in Meter,"
    Print "as well as heading in ° and speed over ground in km/h."
    Print "Kixline coding started..."
    Kixlineout = "x" 'example how how code a message type
    Kixinlo = Dlate + Kix2 : Kixencode : Kixlineout = Kixlineout + Left(kixout , 2) 'KI2.0
    Kixinlo = Dlone + Kix2 : Kixencode : Kixlineout = Kixlineout + Left(kixout , 2) 'KI2.0
    Kixinlo = Dalte + Kix2 : Kixencode : Kixlineout = Kixlineout + Left(kixout , 2) 'KI2.0
    Kixinlo = Rmcheading : Kixencode : Kixlineout = Kixlineout + Left(kixout , 2) 'KW2.1
    Kixinlo = Rmcspeed : Kixencode : Kixlineout = Kixlineout + Left(kixout , 2) 'KW2.1
    Print "This is the kixline:"
    Print
    Print Kixlineout
    Print
    Kixlinein = Kixlineout : Intflag = 0
    Print "Kixline received - decoding.."
    Kixin = Mid(kixlinein , 2 , 2) + "000" : Kixdecode : Dlate = Kixdeclo - Kix2
    Kixin = Mid(kixlinein , 4 , 2) + "000" : Kixdecode : Dlone = Kixdeclo - Kix2
    Kixin = Mid(kixlinein , 6 , 2) + "000" : Kixdecode : Dalte = Kixdeclo - Kix2
    Kixin = Mid(kixlinein , 8 , 2) + "000" : Kixdecode : Rmcheading = Kixdeclo
    Kixin = Mid(kixlinein , 10 , 2) + "000" : Kixdecode : Rmcspeed = Kixdeclo
   
Print "Difference X from home [m]: " ; Dlate
    Print "Difference Y from home [m]: " ; Dlone
    Print "  Altitude Z over home [m]: " ; Dalte
    Isi = Dlate : Jsi = Dlone : Ksi = Dalte
    Isi = Isi * Isi : Jsi = Jsi * Jsi : Ksi = Ksi * Ksi
    Isi = Isi + Jsi : Isi = Isi + Ksi : Isi = Sqr(isi) : Ilo = Isi
    Print "    Distance from home [m]: " ; Ilo
    Isi = Rmcheading / 10
    Print "               Heading [°]: " ; Fusing(isi , "#.#")
    Isi = Rmcspeed / 10
    Print "  Speed over ground [km/h]: " ; Fusing(isi , "#.#")
End Sub


'*******************************************************************************
'***************************** Main loop (to be tested) ************************
'*******************************************************************************
Sub Mainloop
    Do
      Iby = Ischarwaiting()
     If Iby <> 0 Then
         Iby = Inkey()
         Loopist = Chr(iby)
        If Loopist = "$" Then
            Gpsst = Loopist
        Elseif Loopist = Chr(13) Then '
            Ist = Left(gpsst , 6)
           If Ist = "$GPGGA" Then
               Ggaparse
           Elseif Ist = "$GPRMC" Then
               Rmcparse
               'Make Kixline:
               Kixlineout = "x" 'example o how to code a message type x
               Kixinlo = Dlate + Kix2 : Kixencode : Kixlineout = Kixlineout + Left(kixout , 2) 'KI2.0
               Kixinlo = Dlone + Kix2 : Kixencode : Kixlineout = Kixlineout + Left(kixout , 2) 'KI2.0
               Kixinlo = Dalte + Kix2 : Kixencode : Kixlineout = Kixlineout + Left(kixout , 2) 'KI2.0
               Kixinlo = Rmcheading : Kixencode : Kixlineout = Kixlineout + Left(kixout , 2) 'KW2.1
               Kixinlo = Rmcspeed : Kixencode : Kixlineout = Kixlineout + Left(kixout , 2) 'KW2.1
              Print Kixlineout
           Else
               'Do nothing
           End If
        Elseif Loopist = Chr(10) Then 'Line feed
            'Do nothing
        Else
            Gpsst = Gpsst + Loopist
        End If
     Else
         '........
         'Do other things like reading ADC, SPI, I2C, etc.
         '........
     End If
   Loop
End Sub


'*******************************************************************************
'*************************** Parse NMEA GGA string *****************************
'*******************************************************************************
Sub Ggaparse()
    'Sentence GGA

    'Function: Global Positioning Fix Data
    'Example: $GPGGA,120757,5152.985,N,00205.733,W,1,06,2.5,121.9,M,49.4,M,,*52  (64)
    '
    'NMEA3.01 $GPGGA,115108.618,8960.000000,N,0000.000000,E,0,0,,137.000,M,13.000,M,,*40 (74)
    '                      2        3       4     5       6 7 8 9   10   11  12  13 14 15
    'Synopsis:

    ' 2  time of fix (hhmmss)
    ' 3  latitude
    ' 4  N/S
    ' 5  longitude
    ' 6  E/W
    ' 7  Fix quality (0=invalid, 1=GPS fix, 2=DGPS fix)
    ' 8  number of satellites being tracked
    ' 9  horizontal dilution of position
    '10  altitude above sea level
    '11  M (meters)
    '12  height of geoid (mean sea level) above WGS84 ellipsoid
    '13  time in seconds since last DGPS update
    '14  DGPS station ID number
    '15  checksum

    Iby = Split(gpsst , Commas(1) , ",")
    If Iby <> 14 Then Exit Sub
    'Kst = Commas(2) : Gpstime2millisec                              'Time
    Kst = Commas(3) : Late_lone_dec_conversion : Ggalate = Ilo       'Latitude
    If Commas(4) = "S" Then Ggalate = 0 - Ggalate           'N/S
    Kst = Commas(5) : Late_lone_dec_conversion : Ggalone = Ilo       'Longitude
    If Commas(6) = "W" Then Ggalone = 0 - Ggalone           'E/W
    Ggafixe = Val(commas(7)) 'Fix quality
    Ggasate = Val(commas(8)) 'Number of sats
 'Isi = Val(commas(9)): Isi = 10 * Isi: Ggahode = Isi
    Ggaalte = Val(commas(10))
    'Incr Iby
End Sub


'*******************************************************************************
'*************************** Parse NMEA RMC string *****************************
'*******************************************************************************
Sub Rmcparse()
    'Sentence RMC
    'Function: Recommended minimum specific GPS transit data
    'eg3. $GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70
    '              2    3    4    5    6     7    8    9      10     11  12 13
    '
    '       2   220516     Time Stamp
    '       3   A          validity - A-ok, V-invalid
    '       4   5133.82    current Latitude
    '       5   N          North/South
    '       6   00042.24   current Longitude
    '       7   W          East/West
    '       8   173.8      Speed in knots
    '       9   231.8      True course
    '      10   130694     Date Stamp
    '      11  004.2      Variation
    '      12  W          East/West
    '      13  *70        checksum


    Rmcvalidflag = 0

    Iby = Split(gpsst , Commas(1) , ",")
    If Iby <> 12 Then Exit Sub
    'Kst = Commas(2): Gpstime2millisec                       'Time
    If Commas(3) = "A" Then Rmcvalidflag = 1                'Validity
    'Kst = Commas(4) : Late_lone_dec_conversion : Ggalate = Ilo       'Latitude
    'If Commas(5) = "S" Then Ggalate = 0 - Ggalate           'N/S
    'Kst = Commas(6) : Late_lone_dec_conversion : Ggalone = Ilo       'Longitude
    'If Commas(7) = "S" Then Ggalone = 0 - Ggalone           'E/W
    Isi = Val(commas(8)) : Isi = Isi * 18.53 : Rmcspeed = Isi       'speed in 10 * Km/h
    Isi = Val(commas(9)) : Isi = Isi * 10 : Rmcheading = Isi       'heading in 10 * °
    'Incr Iby
    'NOTE THAT YOU NEED TO ADOPT GGAPARSE AND RMCPARSE ACCORDING TO YOUR
    'GPS MODULE. FORMATTING IS SLIGHTLY DIFFERENT. SOME DEVICES HAVE FOUR
    'OTHERS HAVE 6 DECIMAL DIGITS FOLLOWING THE DECIMAL SEPARATOR. MY
    'ADVICE: STAY AT BASCOM'S SPLIT COMMAND IF SPEED COUNTS.
End Sub


'*******************************************************************************
'**************************** GPS Time to millisec *****************************
'*******************************************************************************
Sub Gpstime2millisec()
    'Find Time and convert into seconds (long)
    'Ggatime = 3600 * Val(mid(gpsparseist , 1 , 2))
    '+ 60 * Val(mid(gpsparseist , 3 , 2))
    '+ Val(gpsparseist , 5 , 2))
    'Ggatim0 = 0
    Ist = Mid(kst , 1 , 2) 'h
    Jlo = Val(ist)
    Jlo = 3600 * Jlo
    Ilo = Jlo
    Ist = Mid(kst , 3 , 2) 'm
    Jlo = Val(ist)
    Jlo = 60 * Jlo
    Ilo = Ilo + Jlo
    Ist = Mid(kst , 5 , 2) 's
    Jlo = Val(ist)
    Ilo = Ilo + Jlo
    Ggatime = 1000 * Ilo                                    'NEW Ki!!! FEB 2010 Time now in Millisec!!!!
    Iby = Instr(kst , ".")
    If Iby = 0 Then
       Ilo = 0
    Else
       Ist = Right(kst , 3)
       Ist = Ist + "000"
       Ist = Left(ist , 3)
       Ilo = Val(ist)
    End If
    Ggatime = Ggatime + Ilo
End Sub


'*******************************************************************************
'******** Latitude and longitude conversion from °°MM.SSSS to °°xxxxxx *********
'*******************************************************************************
Sub Late_lone_dec_conversion()
    'Zby = Len(kst)                                          '5125.6558 = 51° 25.6558' has len 9
    Iby = Split(kst , Dots(1) , ".") 'Dots(1) = "5125"  Dots(2) = "6558"
    Ist = Left(dots(1) , 2) 'Xst = "51"
    Jst = Right(dots(1) , 2) 'Yst = "25"
    Jst = Jst + Dots(2) 'Yst = "256558"
    Ilo = Val(ist) 'Ilo = 51
    Ilo = Ilo * 1000000                                     'Ilo = 51000000
    Jlo = Val(jst) 'Jlo = 256558
    Jlo = Jlo * 10                                          'Jlo = 2565580
    Jlo = Jlo / 6                                           'Jlo = 427596
    Ilo = Ilo + Jlo                                         'Ilo = 51427569
End Sub


'*******************************************************************************
'*************************** Find Home GPS Position ****************************
'*******************************************************************************
Sub Findhome()
    If Gpsreadyflag = 0 Then 'If no first fix found so far
        If Ggafixe >= 1 And Ggasate >= 4 Then 'test if sufficient number of sats
          Gpsreadyflag = 1                                  'let first fix found
          Hometime = Ggatime                                'determine home position
          Homelate = Ggalate
          Homelone = Ggalone
          Homealte = Ggaalte
          Homeset
          Gpsreadyflag = 1
      End If
    End If
End Sub


'*******************************************************************************
'*********** Homeset: Calculate navigation parameter from home GPS *************
'*******************************************************************************
'DON'T USE IBY, JBY, JST, KST
Sub Homeset()

 'FROM: http://williams.best.vwh.net/avform.htm
 '
 'Local, flat earth approximation
 'If you stay in the vicinity of a given fixed point (lat0,lon0), it may be a good
 'enough approximation to consider the earth as "flat", and use a North, East, Down
 'rectangular coordinate system with origin at the fixed point. If we call the
 'changes in latitude and longitude dlat=lat-lat0, dlon=lon-lon0
 ''(Here treating North and East as positive!), then
 '       distance_North=R1*dlat
 '       distance_East=R2*cos(lat0)*dlon
 '
 'R1 and R2 are called the meridional radius of curvature and the radius of curva-
 'ture in the prime vertical, respectively.
 '      R1=a(1-e^2)/(1-e^2*(sin(lat0))^2)^(3/2)
 '      R2=a/sqrt(1-e^2*(sin(lat0))^2)
 '
 'a is the equatorial radius of the earth (=6378.137000km for WGS84),
 'and e^2=f*(2-f) with the flattening f=1/298.257223563 for WGS84.
 '
 'In the spherical model used elsewhere in the Formulary, R1=R2=R, the earth's
 'radius. (using R=1 we get distances in radians, using R=60*180/pi distances are
 ' in nm.)
 '
 'In the flat earth approximation, distances and bearings are given by the usual
 'plane trigonometry formulae, i.e:
 '
 '    distance = sqrt(distance_North^2 + distance_East^2)
 '    bearing to (lat,lon) = mod(atan2(distance_East, distance_North), 2*pi)
 '                        (= mod(atan2(cos(lat0)*dlon, dlat), 2*pi) in the spherical case)
 '
 'These approximations fail in the vicinity of either pole and at large distances.
 'The fractional errors are of order (distance/R)^2.

    Isi = 1 / 298.257223563                                 'f  = 0.00335281
    Jsi = 2 - Isi                                           '2-f = 1.9966472
    Isi = Isi * Jsi                                         'Isi = e^2 = f*(2-f) = 0.00669438

    Jsi = Homelate                                          'Jsi = 51427596
    Jsi = Homelate / 1000000                                'Jsi = 51.428
    Jsi = Deg2rad(jsi) 'Jsi = lat0 = 0.448790
    Lone2m = Cos(jsi) 'Lone2m = 0.90097275
    Jsi = Sin(jsi) 'Jsi = sin(lat0) = 0.433876
    Jsi = Jsi * Jsi                                         'Jsi = (sin(lat0))^2 = 0.18824844
    Jsi = Isi * Jsi                                         'Jsi = e^2 * (sin(lat0))^2 = 0.0012602
    Jsi = 1 - Jsi                                           'Jsi = 1 - e^2 * (sin(lat0))^2 = 0.9987398
    Jsi = Sqr(jsi) 'Jsi = sqr(1 - e^2 * (sin(lat0))^2) = 0.9993697

    Ksi = Jsi ^ 3                                           'Ksi = (1 - e^2 * (sin(lat0))^2)^(3/2) = 0.9981103
    Isi = 1 - Isi                                           'Isi = 1-e^2 = 0.9933056
    Isi = Isi / Ksi                                         'Isi = (1-e^2)/(1 - e^2 * (sin(lat0))^2)^(3/2) = 0.9951862
    Late2m = 6378137 * Isi                                  'Late2m = R1 = a * (1-e^2)/(1 - e^2 * (sin(lat0))^2)^(3/2) = 6347434
    Lsi = 1 / 1000000                                       'Lsi for dlat = 1
    Lsi = Deg2rad(lsi) 'Lsi = dlat = 1.745329252e-8
    Late2m = Late2m * Lsi                                   'Late2m = 0.1107836 meter per unit

    Jsi = 6378137 / Jsi                                     'Jsi = R2=a/sqrt(1-e^2*(sin(lat0))^2) = 6382160
    Jsi = Jsi * Lsi                                         'Jsi 0.1113897 meter per unit
    Lone2m = Lone2m * Jsi                                   '0.100359084 meter per unit at latitude

End Sub