Program Description

   SIMTHEO Decoder
   http://greatfractal.com/MorseDecoded.html

   (Please note that this page was originally generated in HTML. If you
   are reading this as a text README file inside the source tarball, it
   will not contain any hyperlinks and the program lines may be
   truncated.)

   Warning: This software is highly experimental! I created it for myself
   to test my TRILLSAT-1 prototype (see
   http://greatfractal.com/TrillSat.html for more information); it is
   probably full of bugs and not recommended for reliable, dependable use
   without significant changes and improvements--it may not follow best
   programming practices, either.

   Also note that I have created this system for my own use and
   self-education only, with the hope that providing information about the
   project will be useful to others in some way. It might not be suitable
   for you, and I am not responsible for the correctness of the
   information and do not warrant it in any way.

   The SIMTHEO Decoder is a basic C library implementation of the SIMTHEO
   Morse Code decoding algorithm specifically designed for the ATtiny 1634
   microcontroller, but has since been ported to the Raspberry Pi 1, 2,
   and 3. It was originally designed for use with my A Tiny Room in a
   Tiny World roguelike game and THUMP (Tethered Haptics Using Morse
   Pulses) backup communication subsystem for my TrillSat robotic
   radio platform, but can be adapted to other experimental projects with
   the caveats mentioned above.

   It simply takes a key input (usually a single microcontroller pin that
   is pulled to a logic 0/ground, but this can be defined) and echoes back
   the output (a single microcontroller pin that controls an LED, piezo
   element, or even multiple output pins to control the H-Bridge vibration
   of a haptic motor, for example). Once all elements are received, it
   replays the letter in Morse code at the keying speed of the user, but
   corrected for perfect proportion, for feedback confirmation.

   It is a discrete, single letter classifier for only the 26-letter
   subset of International Morse Code (plus 4 non-Morse control codes) and
   does not contain additional input validation or line buffering, which
   are left to the external program to handle.

   To save RAM, it stores the Morse alphabet and 4 control codes using
   only the last 5 bits of a 30-character array in flash memory program
   space (18.75 bytes).

   It has two primary modes of operation: SIMTHEO, which is standard,
   continuous-wave style Morse Code, and SIMTHEO SHH, an inverted style of
   Morse Code which recognizes the pauses instead of the tonal lengths,
   using timing and/or final pulse delimiter to identify the last element.

Static and Dynamic Libraries

   When I first published the source code to this project (version 1.0 and
   1.1) under LGPL version 3.0, I intended to eventually modularize the
   code to allow it to compile as a dynamic library (an .so shared object
   file), the primary reason due to the LGPL itself--so that I could give
   more flexibility to the user, as the dynamic library already existing
   on a computer has no bearing on the project that uses it. From my
   understanding, without dynamic-linked libraries, the LGPL library user
   has to ensure (if they decide to release their code to others) that the
   library can be modified without breaking the program/^, a more
   difficult task which usually requires the providing of the object code
   at minimum.

   At version 1.2, I then modularized my code enough to allow this to
   work. However for the ATtiny 1634 code, I was surprised that I could
   not find any information on how to do this. Dynamic libraries seem to
   be a function of an OS (which doesn't exist on an ATtiny 1634). The OS
   tools have to set up these connections dynamically. So my only option
   for the ATtiny was to release it as a static library, libsimtheo.a
   which has to be linked at compile time. For me, as copyright holder, I
   don't have to worry about it, but unfortunately, for you, under the
   LGPL 3.0, if you want to use my code in an ATtiny 1634 project that you
   decide to give away to others, you have to at least make available to
   them a binary object file of your code that can be linked to
   libsimtheo.a, so libsimtheo.a can then be modified without breaking the
   program. At least that's my interpretation of the LGPL 3.0.

   But, obviously, in many situations this is overkill and not ideal for
   distribution of tiny 8-bit chips like an ATtiny 1634, which might not
   be connected to anything and has no Ethernet or wireless network
   abilities. Please be aware of this caveat.

   But for the Raspberry Pi 1, 2, and 3, though, I was able to generate
   both static and dynamic libraries, which means that the libraries can
   be modified without requiring the code that connects to it, allowing
   more flexibility to the user.

   I created a very basic makefile that compiles the following when "make"
   is run from the same directory:

   The static libraries:
     * libsimtheo-ATtiny1634.a
     * libsimtheo-ARMv6h.a
     * libsimtheo-ARMv7h.a
     * libsimtheo-ARMv8.a

   The dynamic libraries:
     * libsimtheo-ARMv6h.so
     * libsimtheo-ARMv7h.so
     * libsimtheo-ARMv8.so

   When using dynamic libraries, simlinks should be created. For example,
   for ARMv8 (64-bit Raspberry Pi 3) version 1.2:

   ln -s libsimtheo-ARMv8.so.1.2 libsimtheo.so.1
   ln -s libsimtheo.so.1 libsimtheo.so

   Then the OS has to be able to see the new library. On Arch Linux, for
   example, I put my path in a file called simtheo.conf and then place
   this file in /etc/lib.so.conf.d and run ldconfig -v, but there are a
   variety of ways to do this.

   Then one could use the simtheo functions and global variables (say, in
   a myfile.c program), by including the libsimtheo.h file and then
   compile it and reference the library:

   gcc -mcpu=cortex-a53 -mfpu=neon-fp-armv8 -funsafe-math-optimizations
   -mfloat-abi=hard -std=c11 myfile.c -o myfile.o -lwiringpi -L. -lsimtheo

   Then myfile.o, when executed, will connect to the dynamic library on
   startup.

   On my system, when compiling, it first chooses the dynamic library, if
   found, instead of the static one unless otherwise indicated. Note above
   that wiringpi.so also has to be added to the parameters to allow the Pi
   to mimic the GPIO functions of the ATtiny. At the time of this writing,
   WiringPi was also licensed under LGPL V3.0 by its author.

   A similar process is used for the Pi 1 and 2, but the gcc architecture
   parameters need to be changed.

   But for the ATtiny, the only option is to statically compile:

   avr-gcc -std=c11 -mmcu=attiny1634 -Os myfile.c -o myfile.o -L.
   -lsimtheo-ATtiny1634

   Please keep in mind that this is the first C library I've ever created,
   primarily for my own use, and it is probably full of bugs, may not
   follow conventions, and may break when I release new versions. It is
   experimental only!

Functions

   The module provides 5 functions:

   uint8_t read_morse (void) - this is the primary input function that
   reads the keyed input, echoes the individual elements, replays the
   recognized letter, and returns the letter code (0-29) which can be
   added to 65 to obtain the ASCII value. In SIMTHEO mode, in the case of
   letter code 26 (----), which I call F1, it changes it to 7 (....). But
   in SIMTHEO SHH mode, F1 is simply returned as letter code 26, for
   situations where the external program needs to use it as a control
   code. And in standard SIMTHEO mode, 31 is used as a hold function, and
   32 as a reset function, but these don't apply to SIMTHEO SHH since it
   doesn't recognize the pulse lengths. In both SIMTHEO and SIMTHEO SHH,
   the value of 30 is an error code.

   void letter_to_morse (uint8_t lettercode) - this is the primary output
   function that takes a letter code and outputs a letter in Morse code.

   uint8_t morse_to_letter (uint8_t pulses) - this function takes an 8-bit
   integer which stores the raw Morse element sequence (binary with
   padding), and returns its letter code value. This saves many times more
   RAM than using a char string to store the elements (Memory is precious
   on the ATtiny 1634, having only 1024 bytes of data SRAM). The value of
   30 is considered an error code.

   void morse_vibrate (uint16_t length, uint8_t delay) - this function
   takes a length as a 16-bit integer to use as a duration, and takes an
   8-bit integer to use as a period for an oscillation or vibration. The
   delay is configured by a VIBRATE_DELAY define value and changes the
   frequency of the oscillation, which is used for sound (piezo elements)
   or haptic (motor pulses).

   void insertion_sort(uint16_t A, uint8_t positionA) - this function
   takes two 4-element arrays, main and position, and performs an
   insertion sort of the main array, but it retains memory of the original
   position of the values in the 8-bit array. The main array is 16-bit,
   since it stores time values, but the position array is 8-bit. It is
   used by the read_morse function to sort dits from dahs.

Requirements

   When compiling, pre-processor directives look for the
   __AVR_ATtiny1634__ macro which is set by the -mmcu=attiny1634 parameter
   in avr-gcc. In this mode, it compiles a static library for the ATtiny
   1634 only. The ATtiny 1634 requires the the avr-libc library. It uses
   the 16-bit timer, assumes that it is running at 8 Mhz (set in fuses and
   defined by F_CPU 8000000UL), and it sets the timer prescaler to 1024
   (if running at 1 Mhz F_CPU 1000000UL, the prescaler can be set to 64
   and the T_DIT value in the defines can be multiplied by 2 to
   compensate...) So it could conflict with other things that might be
   using the timer with a different prescaler value.

   However, if this macro is not set, it assumes that compiling is
   occurring on a Linux OS (for the Raspberry Pi 1, 2, or 3) which does
   not require avr-libc but requires libc and WiringPi to emulate the
   original ATtiny 1634 functions. It then compiles static and dynamic
   libraries for the ARMv6h, ARMv7h, and ARMv8 architectures.

Defines that need to be customized

   To save RAM, I use defines a lot. By default, the code makes
   assumptions about how the morse key and LED and/or piezo element are
   connected, unless otherwise changed.

   For the ATtiny 1634, it assumes that the LED, piezo, or haptic motor is
   connected to PA6. It assumes that a morse key switch of some sort is
   connected to PC1.

   For the Raspberry Pi, it assumes that the LED, piezo, or haptic motor
   is connected to BCM GPIO 15. It assumes that a morse key switch of some
   sort is connected to BCM GPIO 23.

   These can be changed by editing these defines in the libsimtheo.h file
   before compiling via the makefile:

   KEY_UP (read key up as true)
   KEY_DOWN (read key down as true)

   MORSE_ON (turn on morse signal circuit)
   MORSE_OFF (turn off morse signal circuit)

   The code also assumes a 1 ms delay in how fast the LED, piezo, or
   haptic motor vibrates when turned on:

   VIBRATE_DELAY (0 for no delay, 1-255 for delay in ms)

   And the code assumes that it is in normal SIMTHEO mode, not the
   inverted SIMTHEO SHH mode by default:

   SIMTHEO_MODE (0 is SIMTHEO, 1 is the inverted SIMTHEO SHH)

   For example, something like this might be added if the key is on PB3
   and an LED is on PB2 (assuming the ports are set correctly):
   bc. define KEY_DOWN bit_is_clear(PINB,PB3)
   define MORSE_ON DDRB &= ~(1<<PB2)

Parallel Operation

   I designed the decoder so it can either be run on a single
   microcontroller or also run in tandem on multiple microcontrollers (or
   even a mixed microcontroller/Raspberry Pi chain) at the same time, with
   the output of one decoder daisy-chained to the input of another. All
   CPUs echo to the others, but do not replay the signal, and the last in
   the chain replays the signal but doesn't echo. The echo and replay
   states, as global uint8_t 8-bit unsigned integers can be turned on and
   off for each microcontroller:

   morse_echo (0 off, 1 on)
   morse_replay (0 off, 1 on)

   On the ATtiny 1634, morse_echo and morse_replay are initialized to 1,
   but on the Raspberry Pi they are initialized to 0. These can simply be
   changed in your calling program. The reason I turned off the output on
   the Raspberry Pi by default is because I use the Pi to program the
   ATtiny, and don't want its output lines (which I have connected for
   programming and accessing it over UART) to interfere with the ATtiny.
   But, of course, if I want the Raspberry Pi to drive an LED, piezo, or
   haptic motor directly (or via a special passthrough mode I created), I
   would set morse_echo and morse_replay to 1.

   In my TrillSat haptic system, for example, I run two ATtinys
   daisy-chained in parallel, and only the last ATtiny should be set to 1,
   which is connected to the haptic motor.

   So there are various cases and the morse_echo and morse_replay global
   variables can be set accordingly.

Pin Change Interrupt

   It's a good idea to allow the external program to use an interrupt for
   initial key detection, since Morse pulses are short and do not tolerate
   delays. It's handled differently for the ATtiny and Raspberry Pi.

   For the ATtiny 1634, the pin change interrupt and mask (for just that
   pin) should to be enabled in the calling program for that range of pins
   that include the keying pin, if it is not already present. This varies
   depending on pin. For example, for PB3, which uses PCINT11:

   GIMSK |= (1<<PCIE1);PCMSK1 |= (1<<PCINT11);

   You may want to enable sleep mode to allow the CPU to power down after
   Morse is received and a command is performed:

   set_sleep_mode(SLEEP_MODE_PWR_DOWN);
   PCMSK2 |= (1 << PCINT13 );
   GIMSK |= (1 << PCIE2);

   And then, create an ISR function for PB3, for example:
ISR(PCINT1_vect) {
   PCMSK1 &= ~(1<<PCINT10) //disable just the morse key to keep it from calling
itself
   sei(); //this allows other interrupts (like I2C) to continue to work

   uint8_t received_morse_letter;
   received_morse_letter = read_morse();

   //validate it

   //do something

   PCMSK1 |= (1<<PCINT11); //now re-enable the morse key again

   //now put CPU to sleep
   sleep_enable();
   sleep_cpu();
   sleep_disable();
   cli(); // turn off global interrupts
}

   For me, I had other interrupts that didn't like to be delayed, so I had
   to use sei() to allow them to work, yet I didn't want PB3 to call
   itself, so I just turned off that pin temporarily while inside the ISR.

   However, on the Raspberry Pi, I had to use WiringPi to emulate the
   ATtiny's interrupts.
wiringPiSetupGpio();
pinMode (23, INPUT);
wiringPiISR (23, INT_EDGE_FALLING,  *Morse_Interrupt_Function)

   Then I created a function called Morse_Interrupt_Function() to handle
   the code, similar to the ATtiny's ISR function.

Morse alphabet

   Below are the 26 Morse Code letters, and 4 control codes, that are
   recognized, showing traditional SIMTHEO Morse and the inverted SIMTHEO
   SHH DIT/pause patterns with occasional terminating pulse for ending
   DAHs. They are converted to binary using padding, using the 5 lowest
   bits only, stored in AVR program space. Anything can be stored in the
   highest 3 bits, since the actual values are read using a mask of
   00011111 applied by an AND operation. This frees up around 11 bytes.
                SIMTHEO  SIMTHEO SHH
000 00101 A 0   .-       ..            .
000 11000 B 1   -...     .   . . .
000 11010 C 2   -.-.     .   . .   .
000 01100 D 3   -..      .   . .
000 00010 E 4   .        .
000 10010 F 5   ..-.     ...   .
000 01110 G 6   --.      .   .   .
000 10000 H 7   ....     ....
000 00100 I 8   ..       ..
000 10111 J 9   .---     ..   .   ..
000 01101 K 10  -.-      .   ..            .
000 10100 L 11  .-..     ..   ..
000 00111 M 12  --       .   .            .
000 00110 N 13  -.       .   .
000 01111 O 14  ---      .   .   .            .
000 10110 P 15  .--.     ..   .   .
000 11101 Q 16  --.-     .   .   ...
000 01010 R 17  .-.      ..   .
000 01000 S 18  ...      ...
000 00011 T 19  -        .            .
000 01001 U 20  ..-      ...            .
000 10001 V 21  ...-     .....
000 01011 W 22  .--      ..    .            .
000 11001 X 23  -..-     .   ....
000 11011 Y 24  -.--     .   ..   ..
000 11100 Z 25  --..     .   .   ..

000 11111 F1 26 ----     .   .   .   ..
000 11110 F2 27 ---.     .   .   .   .
000 10101 F3 28 .-.-     ..   ...
000 10011 F4 29 ..--     ...   ..

Disclaimer

   Warning, this project is experimental and not recommended for real data
   or production. Do not use this software (and/or schematic, if
   applicable) unless you read and understand the code/schematic and know
   what it is doing! I made it solely for myself and am only releasing the
   source code in the hope that it gives people insight into the program
   structure and is useful in some way. It might not be suitable for you,
   and I am not responsible for the correctness of the information and do
   not warrant it in any way. Hopefully you will create a much better
   system and not use this one.

   I run this software because it makes my life simpler and gives me
   philosophical insights into the world. I can tinker with the system
   when I need to. It probably won't make your life simpler, because it's
   not a robust, self-contained package. It's an interrelating system, so
   there are a lot of pieces that have to be running in just the right way
   or it will crash or error out.

   There are all kinds of bugs in it, but I work around them until I later
   find time to fix them. Sometimes I never fix them but move on to new
   projects. When I build things for myself, I create structures that are
   beautiful to me, but I rarely perfect the details. I tend to build
   proof-of-concept prototypes, and when I prove that they work and are
   useful to me, I put them into operation to make my life simpler and
   show me new things about the world.

   I purposely choose to not add complexity to the software but keep the
   complexity openly exposed in the system. I don't like closed,
   monolithic systems, I like smaller sets of things that inter-operate.
   Even a Rube Goldberg machine is easy to understand since the
   complexities are within plain view.

   Minimalism in computing is hard to explain; you walk a fine line
   between not adding enough and adding too much, but there is a "zone", a
   small window where the human mind has enough grasp of the unique
   situation it is in to make a difference to human understanding. When I
   find these zones, I feel I must act on them, which is one of my
   motivating factors for taking on any personal project.

   Here is an analogy: you can sit on a mountaintop and see how the tiny
   people below build their cities, but never meet them. You can meet the
   people close-up in their cities, but not see the significance of what
   they are building. But there is a middle ground where you can sort of
   see what they are doing and are close enough to them to see the
   importance of their journey.

   The individual mind is a lens, but, like a single telescope looking at
   the night sky, we can either see stars that are close or stars that are
   much farther away, but we can't see all stars at the same time. We have
   to pick our stars.

   I like to think of it like this:

   It is not within our power to do everything, but it is within our
   power to do anything.
     __________________________________________________________________


