diff --git a/Documentation/Protocol Description/M32 Protocol.md b/Documentation/Protocol Description/M32 Protocol.md new file mode 100644 index 0000000..b4a0390 --- /dev/null +++ b/Documentation/Protocol Description/M32 Protocol.md @@ -0,0 +1,421 @@ +# The M32 Serial Protocol + + Date: June 10, 2023 + Version: 1.0 + Authors: Willi, OE1WKL, and Christof, OE6CHD + +The USB bus can be used for two-way communication with a connected computer. Apart from keyed or generated characters (this had been implemented already previously) the Morserino can send information about user actions (selecting menus, configuring preferences etc), or about current settings etc to the computer, and the computer can send various commands to the Morserino (which enables full control over parameters and menus). + + +## Receiving information from Morserino + +With the exception of keyed or generated characters (this can be controlled through the "Serial Output" parameter) the Morserino always sends a valid JSON object, delimited by curly braces (see https://de.wikipedia.org/wiki/JavaScript_Object_Notation for an explanation of JSON object notation). + +Morserino sends a message whenever a user action is executed on the device (navigation within menus, executing a menu, navigation within parameter menu and setting parameters etc.). This can be used for displaying user actions on a larger screen, or for generating speech output as a feedback for all user actions (thus making the Morserino usable for blind and visually impaired persons). + + + +## Sending commands to Morserino + +Commands sent to the Morserino start with "GET" (read values) or "PUT" (write values) and additional parameters. Commands are ended by a single carriage return (\n or ascii 10). Responses to GET commands are of course JSON objects, while as a rule there are no responses to PUT commands. + + + +### Syntax of commands + +GET *object* + +GET *object*/*specifier* + +PUT *object*/*specifier*/*value* + +GET and PUT are separated by a blank from the following parts of the command, while object, specifier and value are separated by a slash (/). + +GET, PUT, *object* and *specifier* can be upper or lower case, but value is case sensitive. + + + +## Enabling and disabling the M32 Protocol + +After establishing the connection physically, the M32 protocol needs to be enabled on the Morserino. This is done with the following command: + +`PUT device/protocol/on +` + +As a confirmation device information is returned, e.g.: +`{"device":{"hardware":"2nd edition","firmware":"5.0","protocol":"1.0"}}` + +From this response the connected computer program gets not only confirmation that the communication has been established, but also information about the hardware used, as well as the firmware and protocol versions. + +While the protocol is enabled, the time-out of the connected Morserino is disabled, i.e. it will not go into sleep modus. + +The protocol can also be disabled with the command: +`PUT device/protocol/off` + + + +## Success and Error Feedback + +When the M32 could parse and execute the command, an "ok" object is returned, e.g. + + `{"ok":{"content":"OK"}}. + + Exceptions are the `PUT control` commands: they return the "control" objects, in the same way as `GET control`. + +When invalid commands are sent to the Morserino, an "error" object is returned, e.g. + +`{"error":{"name":"INVALID Value xxx"}}` . + + + +## JSON objects sent by Morserino as a result of user activities + +### "menu" + +As a result of menu navigation, a *menu* object is sent with the following properties: + +* "content" (type String): The complete menu path, with elements separated by slash (/). +* "menu number" (type Number): A number to identify this menu item. +* "executable" (type Boolean): If false, there are sub-menu items, if true this can be executed. +* "active" (type Boolean): Always false when resulting from user activity. + + +Example: + + {"menu":{"content":"CW Generator/..", + "menu number":2,"executable":false,"active":false}} + +### "control" + +As a result of changing the speed of the keyer, or the audio volume, a control object is sent with the following properties: + +* "name" (type String): "speed" or "volume". +* "value" (type Number): For "speed": keyer speed in words per minute, + for "volume" a number between 0 and 19. + +Example: + + `{"control":{"name":"speed","value":16}}` + +### "config" + +As a result of navigating the parameters menu, a config object is sent with the following properties: + +* "name" (type String): The name of the currently selected parameter. +* "value" (type Number): The current value, as stored internally, as a number. +* "displayed" (type String): How the value is displayed (often as a meaningful text). + +Example: + + {"config":{"name":"External Pol.","value":0,"displayed":"Normal"}} + + +### "activate" + +As a result of activating a menu, an activate object is sent with the following property: + +* "state" (type String): can be any of "EXIT", "ON", "SET", "CANCELLED", "RECALLED", "CLEARED" + +Example: + + {"activate":{"state":"ON"}} + +### "message" + +Sometimes, as a result of some user action, a message is displayed on the screen. In these cases a message object is sent with the following property: + +* "content" (type String): The message body. + +Example: + + {"message":{"content":"Generator Start / Stop press Paddle "}} + + + +## Objects and their GET and PUT commands + + + +### Device Information + +`GET device` +Returns the properties „hardware“ (can currently be "1st edition“, or “2nd edition“) +„firmware“ (the firmware version number, and +„protocol“ (the M32 Protocol version). + +Example: + + {"device":{"hardware":"2nd edition","firmware":"5.0","protocol":"1.0"}} + +`PUT device/protocol/on` +This switches the M32 protocol on (you will get device information back; you will also get device info when command is sent while protocol is already ON); +while this is on, there will be no time-outs, whatever the parameter says! + +`PUT device/protocol/off` +This switches the M32 Protocol off (time-out as defined in its parameter will be in effect again). + + + + +### Control Speed and Volume + +`GET control/speed` + +This returns the properties "name", "value", "minimum" and "maximum" for keyer speed (in words per minute - wpm. + +`GET control/volume` + +This returns the properties "name", "value", "minimum" and "maximum" for the volume control. + +`PUT control/speed/` + +Use this to set the keyer speed to (numeric value, words per minute). + +`PUT control/volume/ ` + +Use this to set the speaker volume to (numeric value, 0 = no sound, 19 = maximum volume). + + + +### The Menu + + +`GET menus` + +This returns a list of all menu items, with the properties "content" (a string giving the complete menu path), "menu number" (a unique number for each menu item), and „executable“ (a boolean value that indicates if this can be executed - if "true" - or it has sub-menu items - if "false"). + +Example: + + { + "menus": [ + { + "content": "CW Keyer", + "menu number": 1, + "executable": true + }, + { + "content": "CW Generator/..", + "menu number": 2, + "executable": false + }, + { + "content": "CW Generator/Random", + "menu number": 3, + "executable": true + }, ... + ]} + + +`GET menu` + +Returns the current menu object with properties "content" (= menu path), "menu number", "executable" and "active"; if called while a mode (eg CW Keyer) is running, (and not just selected in the menu), the property "active" shows "true", otherweise "false". + +Example: + + {"menu":{"content":"CW Generator/English Words", + "menu number":5,"executable":true,"active":true}} + + + +`PUT menu/set/` + +Set the main menu to the requested menu entry (even if it is not executable!). You have to use the menu number. + +`PUT menu/start` + +If in menu mode, start the currently selected menu, if it is executable (do nothing when not in menu mode, or when the current menu entry is not executable). + +`PUT menu/start/` + +Start the command that has the number `` (only if it is executable!). + +For this, the M32 must be in the main menu (if not sure, execute put menu/stop before doing this, or check with `GET menu` if the "active" property is "false") + + +`PUT menu/start now` + +`PUT menu/start now/` + +These commands perform basically the same task as `PUT menu/start` and `PUT menu/start/` ; there is a difference only when starting one of the CW Generator modes: normally these modes, after starting them, wait for a paddle (or key) action to get going. Startimng them with any these commands will get them going immediately, without waiting that the user presses a key or touches a paddle. + +`PUT menu/stop ` + + +If M32 is active or in the preferences menu, stop it and go to main menu. + + + +### Configuration (Parameters) + + +`GET configs` + +This returns a list of all configurable parameters, with the properties "name" (= parameter name), "value" (as numerical values), and "displayed" (how the value has to be displayed). + +Example: + + {"configs":[{"name":"Encoder Click","value":1,"displayed":"On"}, + {"name":"Tone Pitch","value":10,"displayed":"622 Hz e2"}, + {"name":"External Pol.","value":0,"displayed":"Normal"},... + ]} + +`GET config/` + +This returns all details of that parameter (or an error if an invalid parameter name has been given; upper and lower case in parameter names are not significant). The returned properties are: + +* "name" (type String): the name of the parameter, +* "value" (type Number): the current value of the parameter, +"description" (type String): a more verbuous description what the parameter is about, +* "minimum" (type Number), +* "maximum" (type Number), +* "step“ (type Number), +* "isMapped“ (type Boolean): "true" if the value has to be shown as some character string, and +* "mapped values" (type Array of Strings): for the value to text mappings, if applicable + +Example: + + {"config":{"name":"Keyer Mode","value":2, + "description":"Iambic Modes, Non-squeeze mode, Straight Key mode", + "minimum":1,"maximum":5,"step":1,"isMapped":true, + "mapped values":["","Iambic A","Iambic B","Ultimatic", + "Non-Squeeze","Straight Key"]}} + +`PUT config//` + +This sets the named parameter to the specified value - the value must be the NUMERIC value, and not the textual representation! + +Example to set Keyer Mode to Iambic B: + + PUT config/keyer mode/2 + + + +### Snapshots + +`GET snapshots` + +This command returns a list of all currently stored snapshots. + +Example: + + {"snapshots":{"existing snapshots":[1,2,3]}} + +`PUT snapshot/store/` + +Store the current parameters (and the currently selected menu item) in snapshot n (n = 1..8). + +`PUT snapshot/recall/` + +Recall parameters (and menu selection) from snapshot n. + +`PUT snapshot/clear/` + +Clear (i.e., delete) snapshot n (n = 1..8). + + + +### Stored Text File + +`GET file/size` + +This command return some usage statistics of the SPIFFS file system (used for the file the file player can play), with properties "size" (type Number; the size of the current player file in bytes) and "free" (type Number; the size of the available storage in bytes. Be aware that the actual free space might be a little bit less than indicated, because file space is allocated in chunks). + +Example: + + {"file":{"size":21,"free":1498219}} + +`GET file/text` + +This command returns the contents of the text file used by the file player, in an object named "file", as a property (type String) called "text". + +Example: + + {"file":{"text":"The first line of a small text file.\n + The second line, with a pause at the end.

\n"}} + +`GET file/first line` + +This returns the contents of the first line of the text file used by the file player, in an object named "file", as a property (type String) called "first line". + +If you use the comment feature for text files (a line beginning with "" or "/c" is regarded as a comment and not played), you can use this to give you some indication about the file contents. + +Example: + + {"file":{"first line":" This file contains sample QSO text."}} + +`GET file` This is just a short version of `GET file/first line`. + +`PUT file/new/` + +This command replaces an existing existing file with a new file, and place a line of text into the new file. + +`PUT file/append/` + +This will append a line of text to the existing file. By using `PUT file/new/` and then a series of `PUT file/append/` commands you can upload a text file with many lines of text. + + +### WiFi Configuration + +`GET wifi` + +This command will return the currently defined wifi entries (the properties "ssid" and "trxpeer", but NOT "password" (!), and if the property "selected" (type Boolean) is true, this entry will be used selected for connection. + +Example: + + {"wifi":[{"ssid":"Shackrouter","trxpeer":"cq.morserino.info","selected":false}, + {"ssid":"MyHomeRouter","trxpeer":"cq.morserino.info","selected":true}, + {"ssid":"","trxpeer":"","selected":false}]} + + + +`PUT wifi/ssid//` + +This sets the SSID for WiFi setting (n = 1..3). + +`PUT wifi/password//` + +This sets password for WiFi setting . + +`PUT wifi/trxpeer//` + +This sets the entry for TRX Peer for WiFi setting . + +`PUT wifi/select/` + +This selects one of the three WiFi entries as the one to be used for connecting. + + +### Koch Lessons + +`GET kochlesson` + +This returns the currently selected Koch lesson, with properties "value" (type Number; the numeric value of the currently set Koch lesson), "minimum" (type Number), "maximum" (type Number), and "characters" (type Array of Strings; thee are the characters in the currently selected order of characters (parameter "Koch Sequence"). + + +Example: + + {"kochlesson":{"value":9,"minimum":1,"maximum":51,"characters": + ["m","k","r","s","u","a","p","t","l","o","w","i",".","n","j","e","f", + "0","y","v",",","g","5","/","q","9","z","h","3","8","b","?","4","2", + "7","c","1","d","6","x","-","=","","+","","","", + "","","@",":"]}} + +`PUT kochlesson/` + +This sets Koch lesson to lesson number . + +### Automated CW Keyer + +`PUT cw/play/` + +This command plays the text string in CW (like a memory keyer - needs to be in CW Keyer mode or in Morse Trx mode, cannot work in LoRa or WiFi Trx modes). Keying will be stopped as soon as something is being keyed manually, or with the `PUT cw/stop` command. + +`PUT cw/repeat/ ` + +This is similar to the command above, but the text string is repeated until stopped. + +`PUT cw/stop` + +This will stop the CW player (it has the same effect as beginning to key manually while the commands `PUT cw/play` or `PUT cw/repeat` are active). + +Note: you can use " or "\p" within to generate a short pause. diff --git a/Documentation/Protocol Description/M32 Protocol.pdf b/Documentation/Protocol Description/M32 Protocol.pdf new file mode 100644 index 0000000..e037888 Binary files /dev/null and b/Documentation/Protocol Description/M32 Protocol.pdf differ diff --git a/Documentation/Protocol Description/morse_code_over_packet_protocol.md b/Documentation/Protocol Description/morse_code_over_packet_protocol.md index 8d26783..9028e2d 100644 --- a/Documentation/Protocol Description/morse_code_over_packet_protocol.md +++ b/Documentation/Protocol Description/morse_code_over_packet_protocol.md @@ -1,5 +1,9 @@ # Morse Code Over Packet Protocol (MOPP) + Date: June 06, 2020 + Version: 1.0 + Authors: Willi, OE1WKL + ## Introduction In order to send Morse code over either LoRa or over IP, both of which transmit byte strings as packet payloads, we need a protocol to represent Morse code within these byte strings. For the Morserino-32 the following protocol (calles MOPP for short) has been established (which could of course be used by other hardware or software tools). diff --git a/Documentation/Protocol Description/morse_code_over_packet_protocol.pdf b/Documentation/Protocol Description/morse_code_over_packet_protocol.pdf new file mode 100644 index 0000000..45e4cdc Binary files /dev/null and b/Documentation/Protocol Description/morse_code_over_packet_protocol.pdf differ diff --git a/Documentation/User Manual/Version 5.x/M32_layout.jpg b/Documentation/User Manual/Version 5.x/M32_layout.jpg new file mode 100644 index 0000000..d11802a Binary files /dev/null and b/Documentation/User Manual/Version 5.x/M32_layout.jpg differ diff --git a/Documentation/User Manual/Version 5.x/M32c.png b/Documentation/User Manual/Version 5.x/M32c.png new file mode 100644 index 0000000..d009245 Binary files /dev/null and b/Documentation/User Manual/Version 5.x/M32c.png differ diff --git a/Documentation/User Manual/Version 5.x/Morserino.jpg b/Documentation/User Manual/Version 5.x/Morserino.jpg new file mode 100644 index 0000000..2c87b0c Binary files /dev/null and b/Documentation/User Manual/Version 5.x/Morserino.jpg differ diff --git a/Documentation/User Manual/Version 5.x/README.md b/Documentation/User Manual/Version 5.x/README.md new file mode 100644 index 0000000..208aeb7 --- /dev/null +++ b/Documentation/User Manual/Version 5.x/README.md @@ -0,0 +1,7 @@ +# Morserino-32 User Manual +Morserino-32 multi-functional Morse code machine, based on ESP32 + +## User Manual in German and English. +You may submit translations into other languages. If you do so, please translate the *source code* in ASCIIDOC format - if you do so, you can focus on the translation, and the formatting will happen automagically! + +(This documents are copyrighted as Creative Commons, CC BY-NC-SA 3.0 AT) diff --git a/Documentation/User Manual/Version 5.x/m32-theme.yml b/Documentation/User Manual/Version 5.x/m32-theme.yml new file mode 100644 index 0000000..2463394 --- /dev/null +++ b/Documentation/User Manual/Version 5.x/m32-theme.yml @@ -0,0 +1,73 @@ +extends: default +page: + layout: portrait + size: A4 + margin: [1.5cm, 2cm, 1.5cm, 2cm] + margin-inner: 4cm + margin-outer: 2.5cm +title-page: + logo: + image: m32c.png + title: + font-size: 24 + font_color: D32F2F + subtitle: + font-family: Noto Serif + font-size: 28 + font-style: bold + font-color: D32F2F +base: + font-family: Helvetica + font-color: 111111 + font-size: 10.5 + line-height-length: 14 + line-height: $base-line-height-length / $base-font-size +vertical-spacing: $base-line-height-length +heading: + font-family: Helvetica + font-size: 14 + font-style: bold + line-height: 1.2 + margin-bottom: $vertical-spacing +header: + font-size: 10 + font_color: 777777 + height: 1cm + recto: + center: + content: '_{organization}_' + verso: + center: + content: '_{organization}_' +footer: + font-size: 11 + font_color: 444444 + height: 1cm + recto: + right: + content: '{chapter-title} | {page-number}' + left: + content: image:m32c.png[pdfwidth=0.25in] + verso: + left: + content: '{page-number} | {chapter-title}' + right: + content: image:m32c.png[pdfwidth=0.25in] +table: + border-color: 888888 + stripe-background-color: AAAAAA +toc: + font-size: 10 + title: + font-family: Noto Serif + font-size: 24 + dot-leader: + font-color: 888888 +heading: + font-color: D32F2F + h2-font-family: Noto Serif + h2-font-size: 24 + h3-font-size: 18 + h4-font-size: 14 +literal: + background-color: dddddd diff --git a/Documentation/User Manual/Version 5.x/m32_user-Manual_v5.adoc b/Documentation/User Manual/Version 5.x/m32_user-Manual_v5.adoc new file mode 100644 index 0000000..f1a1121 --- /dev/null +++ b/Documentation/User Manual/Version 5.x/m32_user-Manual_v5.adoc @@ -0,0 +1,1139 @@ += Morserino-32: User Manual +W. Kraml (OE1WKL) +v5.0 June 2023 +:organization: Morserino-32 User Manual +:doctype: book +// Settings: +:experimental: +:reproducible: +:icons: font +:listing-caption: Listing +//:sectnums: +:toc: macro +:toclevels: 4 +ifeval::["{asciidoctor-version}" < "1.5.7"] +:legacy-footnoteref: +endif::[] +ifdef::backend-pdf[] +:pdf-theme: m32 +:pdf-themesdir: {docdir} +:source-highlighter: rouge +//:rouge-style: github +:media: prepress +endif::[] + +toc::[] + +[preface] +== Preface + +image::Morserino.jpg[Morserino-32] + +[.lead] +"`*_Morserino-32 -- A multi-functional Morse Code Device, perfect for Learning and Training_*`" + +This manual reflects the features of firmware Version 5.x of the Morserino-32. In its pictures it shows the hardware of the 2nd edition M32, but is equally useful for the older 1st edition. It has been created using **asciidoc** (instead of Markdown for the earlier versions), and the pdf version rendered through +**asciidoctor-pdf**, to create a manual that is more readable and more pleasing to the eye. + +I'd like to thank everybody who through comments, criticism and suggestions has helped to make the Morserino-32 a successful and outstanding product. + +== Connectors and Controls [[controls]] + +image::M32_layout.jpg[Where are the connectors and controls] + +[cols="^.1,.<3,.<10",options=header] +|=== +|# +|Connector / Control +|Usage + +|1 +|3.5mm Phone Jack (3 poles): to TX +|Connect this to your transmitter or transceiver if you would like to key them with this device. Only the tip and sleeve are being used. + +|2 +|3.5mm Phone Jack (4 poles): Audio In / Line Out +|**Audio input** for the CW decoder; connect the audio output of a receiver for decoding CW signals. **Audio output** (pretty close to a pure sine wave) that is not influenced by setting the loudspeaker volume. The assignments to the jack are as follows: Tip and 1st ring - audio in; 2nd ring: ground; sleeve: audio out. + +|3 +|Audio Input level +|Adjust audio input level with the help of this potentiometer; +there is a special function to help with level adjustment, see section <> at the end of the document. + +|4 +|3.5 mm Phone Jack (3 poles): Headphones +|Connect your headphones (any stereo headphones with standard phone jacks from mobile phones should work) here to listen through headphones and switch off the speaker. You cannot attach a loudspeaker directly to this jack without providing some interface (headphone out needs a DC connection to ground through 50 - 300 Ohms). + +|5 +|Phones Level Trimmer +|Used to adjust the headphone level for maximum comfort. 1st edition M32 does not have this. + +|6 +|Power Switch +|Connect / disconnect the LiPo battery from the device. For frequent use of the Morserino-32 you can leave the battery connected. +If you will not use the device for several days, disconnect the battery (through the Power Switch), as otherwise it will be slowly discharged. + +|7 +|SMA female Antenna Connector +|Connect an antenna suitable for the operating frequency (standard is around 433 MHz, but there also modules available for 860-925 MHz) for LoRa operation. Do not transmit LoRa without an antenna! + +|8 +|RED (Power/Vol/Scroll) Button +|When the device has gone into deep sleep, this wakes up and restarts your Morserino. +When the device is up and running (performing one of the modi), a short press of this button swaps the rotary encoder between adjusting the keyer speed and volume control. +A long press of the button allows you to scroll the display with the rotary encoder, pressing the button again changes the function back to speed control. +A double click of this button reduces display brightness. +While in the menu, a long press starts the mode to adjust audio input level. See the section <> below for further details. + +|9 +|BLACK Rotary Encoder +|Used to make your selection within menus, to adjust speed, volume, or scroll the display, and to set various parameters and options. +Can be rotated and is also a push-button switch. See the section <> below for further details. + +|10 +|Connectors for touch paddles +|These PCB connectors accept the capacitive touch paddles. +If you are only using an external paddle (or for transport), you may remove the touch paddles. + + +|11 +|Serial Interface +|You can connect a cable (directly or through a 4-pole pinhead connector) to an external serial device, e.g. a GPS receiver module (this is currently not supported by software, but not very difficult to do). The 4 poles are T (Transmit), R (Receive), + and - (3.3V power from the Heltec module). + +|12 +|3.5 mm Phone Jack (3 poles): External Paddle +|Use this to connect either an external (mechanical) paddle (tip is left paddle, ring is right paddle, sleeve is ground), +or a straight key (tip is the key). + + + +|13 +|Reset Button +|Through a small hole you can reach the Reset button of the Heltec module (rarely needed). + +|14 +|USB +|Use a normal 5V USB Charger to power the device and charge its LiPo Battery. The microcontroller firmware can also be reprogrammed through USB (through the software development environment on a computer, or using a special update utility - see <>; another method is to update the Morserino-32 firmware through a WiFi connection). + +You can also output keyed or decoded characters on the USB serial device to use this information in a computer program - see the parameter "Serial Output" for further information. + +|15 +|PRG Button +|Through a small hole you can reach the Programming Button of the Heltec module (normally not needed). +|=== + +== Quick Guide to Using the M32 + +(This is for the impatient, but is not a replacement for reading the whole manual!) + +==== Controls to be used: +* ON/OFF (battery) switch: sliding switch at the rear side, near the loudspeaker. Connects / disconnects battery. +* BLACK: The black knob (encoder), you can rotate it, and press it. +* RED: The red button switch. + + +=== How To Turn On the M32 +Either connect a USB power supply, or, if you have a battery installed, turn the battery switch ON (I). + +A start-up screen will appear momentarily, showing firmware version and battery status, and then you will be + in the Main Menu (“Select Modus:“), unless you selected the quick start parameter, then the last modus you had chosen will be started automatically. + +When the M32 is turned on, but there is no change in the display for a longer period of time, the M32 will go into sleep mode. You can wake it up by clicking RED. + +=== How To Select a Modus (= one of the functions of the M32): +Rotate BLACK to find wanted function, click BLACK to select or to enter next lower menu level, long press of BLACK to exit / go up one level. + +=== How to Change Speed or Volume, and How To Scroll the Display +This is done with BLACK and RED when you are in one of the operation functions (these do not work while you are in the menu): + +* Change speed: rotate BLACK. +* Change volume: click RED, rotate BLACK to adjust volume, click RED again to revert to speed setting. +* Scroll display: long press of RED, scroll back and forth with BLACK, exit with RED click. + +=== How To Change the Brightness of the Display +There are 5 levels of brightness. Each double-click of the RED button reduces the brightness a bit, when the lowest level has been reached a double click resets the display to full brightness again. + + +=== How To Change Parameters (settings): +Double click BLACK, rotate BLACK to select the parameter you want to change. Long press of BLACK to exit parameter menu. + +(When a function is active, only the relevant parameters for this function are shown, when called from a menu, all parameters are shown.) + +There are numerous parameters, read the manual to find out what they are for. + +You can also store and recall parameters in so called „snapshots“. + +=== How to Use External Paddles and Keys +You can connect external paddles (dual lever or single lever), or straight keys (normal, or sideswiper) to your M32, by using the 3.5 mm connector for external keys (12). + +To use a straight key, you can either use the CW Decoder modus without changing any parameters (this modus decodes Morse coming either through the audio I/O connector, or from your key). If you want to use the Echo Trainer function, or any of the Transceiver functions with a straight key, you need to change the parameter "Keyer Mode" to "Straight Key" (please note that the function "CW Keyer" will not work when the keyer mode is set to straight key - with a straight key you are the keyer, not the Morserino!). + +TIP: You can use the built-in capacitive paddles like a sideswiper / cootie key when the Keyer Mode is Straight Key! + +=== How To Charge the Battery +Connect USB power, switch battery switch to ON (I), orange LED will be lit very brightly, when orange LED is dark the battery is fully charged. When orange LED is lit or flickers dimly, the battery is not connected / not switched on. + + + +== Using the M32, Step by Step + +=== Powering On and Off / Charging the Battery [[power]] + + +If you want to use the device with a USB power, just plug a USB cable in from virtually any USB charger (it consumes a max of 200 mA, so any 5V charger will do). + +If you run it from battery power, slide the sliding switch to the ON position. + +When the device is off but with the battery connected (sliding power switch is on), it is in deep sleep in reality: almost all functions of the microcontroller are turned off, and power consumption is minimal (less than 5% of normal operation). + +To turn the device on from deep sleep, just press the RED (Power/Vol/Scroll) button momentarily. + +When the Morserino-32 boots up, you will see a startup screen for a couple of seconds. +On the top line you will see an indication for which LoRa frequency the M32 is configured (as a 5-digit number), and +at the bottom of the display you will see an indication of how much battery power is still left. +If this goes way towards empty, you should connect your device to a USB power source. +(The battery will be drained even if you never turn the device on - although this is rather minimal in its deep sleep status, +a full battery will be empty after a couple of days. +Therefore, if you intend not to use the Morserino for a longer period of time, disconnect the battery from the device using the slider switch at the back...) + +WARNING: If the battery voltage is dangerously low when you attempt to turn it on, an empty battery symbol will show on the screen and the device will refuse to boot up. +If you see this symbol, you should begin charging your battery as soon as possible. + +TIP: First edition od M32 only: After using any of the WiFi functions, battery measurement does not work correctly until the Morserino-32 is powered down and up again (or a reset with the Reset button has been performed). This is due to a hardware problem on the Heltec board V2.0. In such cases the Morserino-32 displays "Unknown" instead of the battery voltage, and the battery symbol is shown with an inscribed question mark. After a power cycle everything should work OK again. + +TIP: If the display shows the empty battery symbol although sufficient power should still be available, it is advisable to perform a battery measurement calibration. See <>. + +To disconnect the device from the battery (turning it off, unless you are USB powered), slide the sliding switch to the OFF position. + +To put the device into deep sleep, you have two options: + +* In the main menu, select the option "Go To Sleep" +* If in the parameter menu a "Time Out" value has been set, do nothing. If there is no display update, the device will power itself off and go into deep sleep after the time set there has passed. + +**To charge the battery**, connect it with a USB cable to a reliable USB 5V power source, like your computer, or a USB charger like your phone charger. + +WARNING: Make sure the hardware switch of the device is *ON* while charging - if you disconnect the battery through the switch, +the battery cannot be charged. When charging, the orange LED on the ESP32 module is lit brightly. +When the battery is disconnected, this LED will not be lit brightly, but rather be blinking nervously or half lit. + +Once the battery has been fully charged, the orange LED will not be lit anymore. + +You can of course always use the device when it is powered by USB, if the battery is charging or not. + +[WARNING] +==== +To prevent deep discharging of the LiPo battery, always turn the Morserino-32 off via the main slide switch. Do not leave it in 'sleep mode' for long periods of time (up to a day or maybe two is ok, if it was well charged; a fully charged 600 mAh battery will be discharged to the level of about 3.2 V within 3 to 4 days during deep sleep). + +The Heltec module has electronics on board for charging the battery, and it it prevents overcharging quite well. But it has no prevention of deep discharge! **Deep discharge leads to diminished battery capacity and eventually early death of the battery!** +==== + +=== Setting Up the LoRa Band and Frequency + +The standard version of the Morserino-32 comes with a pre-configured frequency within the 433 MHz Amateur and ISM band (ISM only in ITU Region 1). **If this fits your requirements, you have nothing to do at this stage.** + +If your regulations do not allow the use of this frequency, you need to get a Heltec mpodule (version 2.0 for 1st edition, 2.1 for 2nd edition) that supports LoRa bands between 860 and 925 Mhz. **In this case you have to configure the correct band and frequency before you are going to use the LoRa functionality of the M32.** + +[WARNING] +==== +Please be aware that you need a special version of the Heltec module for the use of the 868 or 920 MHz band. +The "standard" version only supports the 433 MHz band, and the alternative version only supports the 868 and 920 MHz bands! + +If you currently have a standard M32 and want to use the higher frequency bands, you can order a Heltec module (plus antenna) for these bands. +**After replacing the Heltec module you have to perform the LoRa setup for the required band before using LoRa!** +==== + +**See <> at the end of this document** to learn how you can configure LoRa for modules that support the 868 and 929 MHz bands, and how to change the LoRa frequency settings. + + +=== Using the BLACK Knob and RED Button [[buttons]] +Selections of the various modes, and setting all sorts of parameters is being done using the **rotary encoder** and its BLACK **button**. + +*Rotating* the encoder leads you through the options or values, *clicking* the button once selects an option or a value, +or brings you to the next level of the menu (there are up to three levels in the menu). + +A ***double click*** of the BLACK knob brings you to the parameter setting menu. If you do this from the menu, all parameters can be changed. +If done from within a modus, only the parameters that are relevant for the current modus are being shown and can be changed. + +A ***long press*** brings you back to the menu from any of the modi, and within the menu promotes you a level up. + +A **double click** of the **RED** button reduces display brightness; there are 5 levels of brightness. When the lowest level has been reached a double click resets the display to full brightness again. + +While you are selecting a menu (e.g. immediately after power-on), a **long press** of the **RED button** starts a function +to adjust the audio input level (and possibly the output level on a device you connected to the Morserino-32's line-out port). +See <> towards the end of this document. + +When you left the menu to execute one of the modi (keyer, generator, echo trainer etc.) +the **RED (Power/Vol/Scroll) Button** allows you to quickly toggle between **speed control** and **volume control** with a **single click**. + +A **long click** of the **RED** button while a modus is active (i.e. when the menu is not shown) changes the display and encoder into **scroll mode** (the display has a buffer of 15 lines, and normally only the bottom three lines can be seen; in scroll mode you can scroll back to the previous lines; while you are in scroll mode, a **scroll bar** is shown at the far right side of the display, indicating roughly where you are within the 15 lines of text buffer). **Clicking** again in scroll mode changes the screen into its normal operating mode and brings the encoder back to speed control. + +When you are in the parameter setting menu, a **short click** of the **RED** button **recalls** a parameter snapshot, and a **long press** of the RED button **stores** a parameter snapshot. +See the section <> for further details. + + + +=== The Display + +The display is divided into two main sections: on top is the status line, that gives important information according to the current state of the device, and below is an **area of three scrolling lines** where the generated Morse code characters are shown in clear text. All characters from Morse code are shown in lower case, for better readability; Pro signs are shown as letters in brackets, like `` or ``. In addition, when in Echo Trainer modus (see below), the result of your attempt to enter the correct Morse code is shown as `ERR` or `OK` (together with some audible signals). + +Although only three lines of scrolling text are shown, there is internally a buffer of 15 lines -- after a long press of the RED (Vol/Scroll) button you can use the encoder to scroll back and make the previous lines visible again. +This works while you are in any of the modi and screen output is being generated - nothing is lost and the display reverts to its normal behaviour once you leave the scroll mode. + +==== The Status Line + +While you are presented a menu (either the start menu, or a menu to select preferences), the status line tells you what to do (**Select Modus** or **Set Preferences:**). + +When in Keyer Modus, CW Generator Modus or Echo Trainer Modus, the status line shows the following, from left to right: + + +* **A**,**B** , **U**, **N** or **S**, indicating the automatic **keyer mode**: Iambic **A**, Iambic **B**, **U**ltimatic, **N**on-Squeeze or **S**traight Key (for details on these modi see below in section <>). + +* The currently set **speed** in words per minute (the reference word is the word PARIS, which also means that 1 wpm equals 5 characters per minute). +In CW Keyer modus as **nn**WpM, in CW Generator or Echo Trainer modus as (nn)**nn**WpM. The value in brackets shows the effective speed, which differs when inter-word spacing or inter-character spacing are set to other values than those defined by the norm (length of 3 dits for inter-character spacing, and length of 7 dits for inter-word spacing). See the notes in section <> regarding the parameters you can set in CW Generator modus. ++ +When in a transceiver modus, you also see two values for speed -- the one in brackets is the speed of the signal received, the other one the speed of your keyer. + +When using a straight key, the speed shows how fast your keying actually is. ++ +When the digits indicating the speed are shown as **bold**, turning the rotary encoder will change the speed. When they are shown in normal characters, turning the rotary encoder changes the volume. +* A horizontal "progress" bar that extends from left to right indicates the **volume** of the side tone generated by the device (full length of the bar means top volume). This normally shows a white frame around the black progress bar (an extension of the rest of the status line); if this is reversed (white progress bar within black surroundings - and the WpM digits are not bold), turning the rotary encoder will change the volume and not the speed. +* On the very right hand end of the status line there will be an indicator (showing concentric half-circles) symbolizing radio transmission whenever the **LoRa** modus is active (if the Morserino-32 is in LoRa Transceiver mode, or you have set a parameter to transmit LoRa while in one of the CW generator modi). + + +== The Top Menu and the Morserino Modi + +You select the Modus of your Morserino-32 by rotating the black encoder knob, and quickly pressing ("clicking") that knob to select that function (or, in several cases, a sub-menu for a more detailed selection). + + +=== CW Keyer [[keyer]] + +This is an automatic keyer that supports Iambic A, Iambic B (these are sometimes also called Curtis A and Curtis B), and Ultimatic mode, +as well as Non-squeeze mode (emulating a single lever key with a dual lever paddle). +You can either use the built-in capacitive paddle, or connect an external paddle (dual or single lever paddle). +Internal and external paddles work in parallel, so there is no need to configure this. + +There are a number of **parameters** that determine how the automatic keyer works. +See the section <> for the details. The following are particularly important here: + + +`External Pol.` : If your external key is wired "the wrong way around", you can correct this here. + +`Paddle Polarity`: On which side do you want the dits and on which the dahs? + +`Keyer Mode`: Select Iambic A or B, Ultimatic mode, Non-Squeeze mode or Straight Key mode. + +What are theses **Iambic Modes**? +When you press both paddles of a iambic keyer, dahs and dits will be generated alternatively, while both paddles are being pressed, +starting with the one you have hit first (the name "Iambic", by the way, comes from the fact that in a iambic verse there are alternating +short and long syllables; the name "Curtis" on the other hand comes from The developer of the groundbreaking Curtis Morse keyer chip, +John G. “Jack” Curtis, K6KU, ex W3NSJ). + +The difference between modes A and B is the behavior when both paddles are released when the current element is being generated: +in Mode A the keyer stops after the current element, in Mode B the keyer will add another element opposite to the one during which +you released the paddles. + +In other words, in Curtis B mode the opposite paddle is checked while the current element (dit or dah) is being output, +and if a paddle is pressed during that time, another opposite element is added to the current one. +In mode A this is not the case. As mode B is a bit tricky to use, this was later changed, so that only after a certain percentage +of the duration of the element the paddles are being checked. This is the percentage you can set here with the parameters "**CurtisB DahT%**" +and "**CurtisB DitT%**". + +If you set them to 0, the lowest value, the Mode is identical with the original Curtis B Mode; +the later developed "enhanced" Curtis B mode uses a percentage of roughly 35%-40%. +If you set the percentage to 100, the highest value, the behavior is the same as in Curtis A mode. + +This parameter allows you to set any behavior between Curtis A and original Curtis B modes on a continuous scale, +and you can set the percentage for dits and dahs separately (this makes sense, as the timing for dits is just a third of that for dahs, +and so you might find that you want a higher percentage for dits to feel comfortable). + +**Ultimatic Mode**: In Ultimatic Mode, when you keep both paddles pressed, a dit or a dah is generated, +depending on which paddle you hit first, and afterwards the opposite element is being generated continuously. +This is of advantage for characters like j, b, 1, 2, 6, 7. + +**Non-Squeeze Mode**: This „simulates“ the behavior of a single lever paddle when using a dual lever paddle. +Operators used to single lever paddles tend to have difficulties using dual-lever paddles, as they sometimes inadvertently squeeze the paddles, +especially at higher speeds. The non-squeeze mode just ignores squeezing, making it easier for these operators to use a dual lever paddle. + +TIP: Iambic modes and Ultimatic mode can only be used with the built-in touch paddle or an external dual-lever paddle; the selection of these modes is irrelevant when you use an external single-lever paddle. + + +The parameter `Latency` defines, how long after generating the current element (dot or dash) the paddles will be „deaf“. +In early firmware versions this used to be 0, with the effect, that especially at higher speeds you would generate more dots than intended, +as you had to release the paddle while the last dot was still „on“. Now you can set this to a value between 0 and 7, +meaning 0/8 to 7/8 of a dot length (default is 4, i.e. half a dot length). If you still tend to generate unwanted dits, increase this value. + +For the parameter `AutoChar Spce` (defining a minimum length for the space between characters) see the section <> for details. + +**Straight Key Mode**: This is not really an automatic keyer mode, but it enables the Morserino-32 to be used with a simple straight key. The "CW Keyer" function will not work when this parameter has been set, but you can use the Echo Trainer and the Transceiver modes using a straight key! + + +=== CW Generator [[generator]] + +This either generates randomized groups of characters and words for CW training purposes, or plays the contents of a text file in Morse code. You can set a number of options by choosing appropriate parameters (see the section <> below). + +You can **start** and **stop** the CW Generator **by quickly pressing a paddle** (either one side or both), or **by clicking the BLACK knob** (when using a straight key, you can also press that key to start and stop the session). + +When it starts, it will first alert you by generating "`vvv`" (`+..._ ..._ ..._ _._._+`) in Morse code, before it actually begins generating groups or words. + +If you enable the parameter `Stop/Next/Rep', only one word or group of characters will be played, and then the Morserino stops and and waits for paddle input. A press of the left paddle will repeat the current word, while a press on the right paddle will generate the next word. This is useful for training your head copy proficiency: let it play a word (without looking at the screen), and try to decode it in your head, if you are not sure, press left for repeat; if you think you got it right, compare it with the display. Now you can either repeat it again (left press), or look away and press the right paddle for the next word. (You can remember the functions of left and right paddle by thinking of typical music player buttons - left is back, right is forward.) Please note that the options Word Doubler and Stop/Next/Repeat are incompatible with each other - if you set one to ON, the other will be set to OFF automatically. + +Once you touch a paddle, it shows what it just had played, so you can check if you decoded it correctly. +When you touch a paddle again, it will play the next word. This is useful for learning to decode in your head. + +Normally the Morserino-32 just continues to generate until you pause it manually, +but there is a parameter that can be set which makes the device pause after a certain number of words (or letter groups). +See `Max # of Words` in the section <>. + +**Other important parameters** for CW Generator are: + +`Intercharacter Space` This describes how much space is inserted between characters. The "norm" is a space which has the length of three dits. To make it easier to copy code that is being sent at high speeds, and as a good method to learn Morse code, this space can be extended. The code should be sent at rather high speeds ( > 18 wpm) , to make it impossible to "count" dits and dahs, so that you rather learn the "rhythm" of each character. In general, it is better to rather increase the space between words, and not so much the space between characters; therefore it is recommended to set this value between 3 and max. 6. See below. + +`Interword Space`. Normally this is defined as the length of 7 dits. When in CW Keyer modus, we determine a new word after a pause 6 dits long, to avoid text appearing on the display without spaces between words. In CW Trainer modus, you can set the interword space to values between 6 and 45 (which is more than 6 times the normal space) to make it easier to copy code in your head at high speeds. In analogy to Farnsworth spacing, this is also being called Wordsworth spacing. This is an even better way to learn copying high speed code word by word in your head. Of course you can combine both interword and intercharacter spacing methods. + +As character spacing can be set independently, this would mean that you can set character spacing higher than interword spacing, which would be rather confusing. In order to avoid this confusion, interword space will always be at least 4 dit lengths longer than the character spacing, even if a smaller interword space has been set. + +The ARRL and some Morse code training programs use something they call *"Farnsworth Spacing":* here the spaces between characters and between words are lengthened proportionately by a certain factor. You can emulate Farnsworth Spacing by incrementing both inter-character and inter-word space, e.g. setting inter-character space to 6 and inter-word space to 14, thus effectively doubling all spaces between characters and words. if you do this at a character speed of 20 WpM, the resulting effective speed will be 14 WpM. This will be shown on the status line as (14)**20**WpM. + +`Random Groups`: Defines which characters should be contained in the random character groups. You can choose between Alpha / Numerals / Interpunct. / Pro Signs / Alpha + Num / Num+Interp. / Interp+ProSn / Alpha+Num+Int / Num+Int+ProS / All Chars. + +`Length Rnd Gr`: Defines how many characters there should be in a random group. You can either select a fix length ( 1 to 6), or a randomly chosen length between 2 to 3 and 2 to 6 (length chosen randomly within these limits). + +`Length Calls`: The length of call signs that will be generated. Choose a value between 3 and 6 or Unlimited. + +`Length Abbrev` and `Length Words`: The length of common CW abbreviations or common English words, respectively, that will be generated. Choose between 2 and 6, or Unlimited. + +`Each Word 2x`: Each "word" (characters between spaces) will be output twice, as a help to learn to copy by ear (ON). If an increased space between the characters has been selected ("Farnsworth Spacing"), the repetition can also be generated with a smaller space (ON less ICS) or without Farnsworth Spacing (ON true WpM). + +For the less frequently used parameters `Key ext TX` , `CW Gen Displ` and `Send via LoRa` see the section <>. + +==== What can be generated? + +You can choose between the following at the second level of the menu: + +* **Random**: Generates groups of random characters. The length of the groups as well as the choice of characters can be selected in the parameters, by double clicking the black rotary knob (see the description of parameters for details). +* **CW Abbrevs**: Random abbreviations that are very common in CW transmissions (through a parameter setting you can choose the maximum length of the abbreviations you want to train). +* **English Words**: Random words from a list of the 370 most common words in the English language (again you can set a maximum length through a parameter). +* **Call Signs**: Generates random strings that have the structure and appearance of amateur radio call signs (these are not real call signs, and there will be some generated that could not exist in the real world, as either the prefix is not in use or a country's administration would not hand out certain suffixes). The maximum length can be selected through a parameter. +* **Mixed**: Selects randomly from the previous possibilities (random character groups, abbreviations, English words and call signs). +* **File Player**: Plays the content of a file in Morse code, that has been uploaded to the Morserino-32. +Currently it can hold just one file, as soon as you upload a new one, the old one will be overwritten. +Upload works through WiFi from your PC (or Mac or tablet or smartphone or whatever - see the section <> for instructions how to do this). ++ +The file player modus remembers where you stopped (by pressing the BLACK knob long in order to exit this mode; do not just switch off - if you do this, the Morserino +has no chance to remember where you were), +and will continue there the next time you restart the File Player. +Once the end of the file is reached, it will commence at the beginning again. ++ +The file should contain ASCII characters only (upper or lower case does not matter) - +characters that cannot be represented in Morse code are just ignored. +Pro signs can be in the file, they need to be written as 2 character representations with either [] or <> around them, e.g. `` or `[ka]`, or prepend them with a backslash, e.g. `\kn`. ++ +The following pro signs are recognized: +==== +** `` : will be shown on display as + (plus sign) +** `` : will be shown on display as = (equal sign) +** `` +** `` +** `` +** `` +** `` +** `` +==== + +There are three more "special characters", formed in the same way like pro signs, that are recognized while playing a file: + +===== Pauses +It is possible to introduce **pauses** (useful e.g., when you play a QSO text - you can have longer pauses between phrases or when switching from station A to station B). Do this by using

or \p (with a space before and after): each

(or [p] or \p) introduces a pause of three regular inter-word spaces. Use several pause markers (e.g. like \p \p \p ) if you want longer pauses. *Be careful to have the pause marker separated with spaces from each other and from the rest of the text - if not, the whole word (e.g. cq

) will be replaced by a pause!* + +===== Tone Changes +With the second special character you can introduce **tone changes** in the file (useful, when you play QSO text, to distinguish station A from station B, e.g.) Do this by inserting or \t or [t] (as a separate word, i.e. with at least a blank space before and after!) as a tone marker. At this point, the tone will change (unless you have set the parameter „Tone Shift“ to „No Tone Shift“), and at the next occurrence of the tone marker it will change back to the original tone. *Be careful to have the tone marker separated with spaces from the rest of the text - if not, the whole word (e.g. cq ) will be considered as the tone marker, and the rest of the word (in our case „cq“) will be lost!* + +In Echo Trainer Mode, the tone marker is ignored. + +===== Comments +The third special character within text files serves the purpose of inserting **comments**. or \c in a word or by itself make this word and the rest of the line a comment that will not be played by file player. + +===== Randomizing +There is also a parameter for file player called `Randomize File`. If set to „On“ (default is „Off“), +the device will skip n words after each word sent (n = random number between 0 and 255); +as file reads wrap around at end-of-file, you will see all the words in the file eventually (but it could take a while). +If your file is for example an alphabetical word list, words generated will still be in alphabetical order during one pass of the file; +so to get more unpredictable results, it will be best to start with a random list of words. + +What can this be used for? You could for example take a list of call signs and upload this file to the Morserino-32 +(Check the Morserino-32 GitHub repository to get a file with calls that actually have been active in HF contests!). +Now File Player lets you train these call signs in a random fashion. +You might want to visit the Morserino-32 GitHub repository in order to find other suitable files for training! + +=== Echo Trainer + +Here the Morserino-32 generates a word (or a group of characters; you have the same selection available as with the CW Generator), and then waits for you to repeat these characters using the paddle. If you wait too long, or if you response is not identical to what has been generated, an error is indicated (on display and acoustically), and the prompt word is being repeated. If you keyed the correct characters, this is also indicated acoustically and on screen, and you are prompted for the next word. + +In this modus, the prompt word will not normally be shown on the display -- only your response is shown. + + +The sub-menus are the same as for the CW Generator: **Random, CW Abbrevs, English Words, Call Signs, Mixed** and **File Player**. + + +Like in CW Generator modus, you **start the generation by pressing a paddle** (or the back knob, or - if you are using one - the straight key), and then the sequence "`vvv`" will be generated as an alert before the echo training starts. You cannot stop or interrupt this modus by pressing the paddle or the straight key -- after all, you use the paddle (or straight key) to generate your responses! So **the only way to stop this modus is a click of the BLACK encoder button**. + +During your response, if you realize you made an error, you can "reset" your response by entering the character for "ERROR", i.e. a series of 8 dots (the Morserino accepts any series of dots longer than 7 dots). will show on the display, and you can restart your entry from the beginning. + +Again, like with the CW Generator, you can set a huge range of parameters to fine tune the generation of things. Of particular interest for the Echo Trainer are: + +`Echo repeats`: how often a word is repeated when the response is either too late or erroneous, before a new word is being generated + +`Echo Prompt`: This defines how you are prompted in Echo Trainer mode. The possible settings are: „Sound only“ (default; best for learning to copy in your head), „Display only“ (the word you are supposed to enter is shown on the screen, no audible code is generated; good for training paddle input), and „Sound & Display“, i.e you hear the prompt AND you can see it on the display. + +`Confrm. Tone`: Normally an audible confirmation tone is sounded in Echo Trainer modus. If you turn it off, the device just repeats the prompt when the response was wrong, or sends a new prompt. The visual indication of "OK" or "ERR" will still be visible when the tone is turned off. + +`Max # of Words`: As with CW generator, you can make the M32 pause after a specified number of words. + +TIP: If this parameter is set to a value between 5 and 250 (and not to "Unlimited"), the M32, when pausing after that number of words, will show (for 5 seconds) how many incorrect entries you made (and the number of words) on the top line of the display (be aware that you can make repeated errors regarding one word, and all of them will be counted). + +`Adaptv. Speed`: This should help you to train for maximum speed. Whenever your response was correct, the speed will be increased by 1 wpm (word per minute); whenever you make a mistake, it will decrease by 1 wpm. Thus you will eventually always train at your limit, which certainly is the best way to push your limits... + + + +=== Koch Trainer + +The German psychologist Koch developed a method for learning Morse code (in the 1930s), by which each lesson adds an additional character. +The order is neither alphabetical, nor sorted by the length of the Morse codes, but follows a certain rhythmical pattern, +so that the individual characters are learned as rhythm, and not as a succession of dits and dahs. + +Should you want to use the Koch method for learning Morse code (learning and training one character after the other), +**you will find everything you need in the Menu item "Koch Trainer"**. +It has a submenu to enter the lesson you want to add, one to practice just this one new letter +(using the echo trainer modus, so you are encouraged to repeat what you hear), and the modi "CW Generator" and "Echo Trainer", +each of the last two with the submenus for "Random" (groups of random characters out of the so far encountered characters), +"CW Abbrevs" (the abbreviations usually used in CW QSOs), "English words" (the most common English words) and "Mixed" +(random groups, abbreviations and words mixed randomly). +Of course, only the already learned characters will be used - which means, that while you are still struggling with your first characters, +the number of abbreviations and words will be quite limited). + + +In order to prevent counting dits and dahs, or thinking of and reconstructing what you heard, the speed should be sufficiently high (min. 18 wpm), +pauses between characters and words should not be lengthened enormously (and it is always better to just lengthen the pauses between words, +and keep the inter-character spaces to more or less the normal space). +With our device you can set interword space independently from intercharacter space, so you can find a setting that perfectly fits your needs. + + + +==== Koch: Select Lesson [[koch]] + +Select a "Koch lesson" between 1 and 50 (you will learn 50 characters in total through the Koch method). The number of the lesson and the character associated with that lesson will be displayed in the menu. + +The order of the characters learned has not been strictly defined by Koch, and therefore different learning courses use slightly different orders. Here we use the same order of characters as defined by the program "Just Lean Morse Code", which again is almost identical to the order used by the "SuperMorse" software package (see http://www.qsl.net/kb5wck/super.html). The order is as follows: + +[cols=">.3,3,>.3,3",options=header,width=88%,stripes=odd] +|=== +| Lesson # | Character | Lesson # | Character +| 1 | m | 26 | 9 +| 2 | k | 27 | z +| 3 | r | 28 | h +| 4 | s | 29 | 3 +| 5 | u | 30 | 8 +| 6 | a | 31 | b +| 7 | p | 32 | ? +| 8 | t | 33 | 4 +| 9 | l | 34 | 2 +| 10 | o | 35 | 7 +| 11 | w | 36 | c +| 12 | i | 37 | 1 +| 13 | . (dot) | 38 | d +| 14 | n | 39 | 6 +| 15 | j | 40 | x +| 16 | e | 41 | - (minus) +| 17 | f | 42 | = +| 18 | 0 (zero) | 43 | SK (Pro Sign) +| 19 | y | 44 | AR (Pro Sign, also +) +| 20 | v | 45 | AS (Pro Sign) +| 21 | , (comma) | 46 | KN (Pro Sign) +| 22 | g | 47 | KA (Pro Sign) +| 23 | 5 | 48 | VE (Pro Sign) +| 24 | / | 49 | BK (Pro Sign) +| 25 | q | 50 | @ +| | | 51 | : (Colon) +|=== + + There is also an option to select the sequence of characters. In addition to the native sequence of characters, you can choose the sequence that is used by the popular on-line training tool "Learn CW On-line" (LCWO), or the sequence the CW Ops CW Academy courses are using, or the order of "Carousel" curriculum of the Long Island CW (LICW) Club. This can be set in the parameters menu of the Morserino-32, under "Koch Sequence". + + In the case of attending a course with LICW, you should also set the parameter "LICW Carousel" according to your entry point into their curriculum (eg. if you start a course within BC1 - Basic Course 1 - with the characters p, g and s, set this pareameter to "BC1: p g s". All further characters you are going to learn in BC1 will be reflected in the same order as your Koch lessons in teh Morserino. Once you have finished BC1, you will enroll in BC2, say beginning with characters 7, 3 and ?, and so you should now set this parameter to "BC2: 7 3 ?".) + +The sequence of characters when "LCWO" is chosen is as follows: + +k m u r e s n a p t l w i . j z = f o y , v g 5 / q 9 2 h 3 8 b ? 4 7 c 1 d 6 0 x - SK AR(+) KA AS KN VE @ : + +And the CW Academy sequence of characters is this: + +t e a n o i s 1 4 r h d l 2 5 u c m w 3 6 ? f y p g 7 9 / b v k j 8 0 = x q z . , - SK AR(+) KA AS KN VE @ : + +The sequence of the LICW courses is as follows: +r e a t i n p s g l c d h o f u w b k m y 5 9 , q x v 7 3 ? + SK = 1 6 . Z J / 2 8 BK 4 0 + + +===== Koch: Train with a customized set of characters + +You can also use the Koch Trainer to train your specific character set: You upload a text file for the file player that contains the characters you want to train (as one „word“ or several, in one line or more), and then set the parameter 'Koch Sequence' to the new option „Custom Chars“. This reads the characters from the file. Now you can use the Koch Trainer (CW Generator or Echo Trainer), and it will use exactly those characters for your training (the setting of the Koch lesson has no influence at this point). If you want to change the character set, upload a new text file, and re-select the option „Custom Chars“ (even if it had been selected before), to prepare the new character set (if you just upload a new text file, the custom character set will not change - you have to go into parameters and re-select „Custom Chars“ again; this is a feature, not a bug: it means you can switch between training your characters, and using a (different) text file for file player …). Setting „Koch Sequence“ to M32, LCWO, LICW or CW Academy will revert to the „normal“ Koch trainer option. + +==== Koch: Learn New Chr + +Selecting this the new character (according to the Koch lesson selected) will be introduced - you will hear the sound, and see the sequence of dots and dashes quickly on the screen, as well as the character displayed on the screen. This will be repeated until you stop by pressing the BLACK knob. After each occurence you have the opportunity to repeat with the paddles what you have heard, and the device will let you know if this was correct or not. + +Once you have mastered the new character, you can progress to either CW generator or Echo Trainer within the Koch Trainer, in order to practice the newly learned character in conjunction with all the characters you have learned so far. + +==== Koch: CW Generator and Echo Trainer + +The functionality is the same as described above for these two functions, with the following small differences: + +- Only the characters up to the selected Koch lesson will be generated (or the characters defined through your specific character set, see above) +- The parameter "Random Groups" will be ignored. +- There is no sub-menu "File Player". +- In Koch Echo Trainer there is also a sub-menu "Adapt. Rand.", see below. + +==== Koch Echo Trainer: Adaptive Random + +The "Adaptive Random" mode modifies the random selection of characters with feedback from the keyed responses. A wrong character will increase its probability to be selected. A correctly keyed character will reduce its probability. + +To start the adaptive mode start: Koch Trainer > Echo Trainer > Adapt. Rand. + +===== Remarks: + +- Probabilities will be reset to its default every time you start "Adaptive Random" mode. + +- The last koch lessons / characters have a higher probability at the beginning of the session. + +- At the beginning of the session, every character will be selected once (in random order). + +- After every character was selected once, the next characters are selected randomly, characters that have been keyed wrong will have a higher probability to be selected. + +- A wrong keyed character will also increase the probability of the character left and right. E.g. "z/?" is asked and you reply with "g/?". Then the probability of z will be increased and probability of / will also be increased a little. + +- Only the first wrong character will be analyzed. Subsequent input will not be analyzed. E.g. "z/?" is asked and you reply with "gz/?". Probabilities will be increased the same way as in previous example. + +- Do not expect to have any fun in this mode. The adaptive mode will tease you with the characters that you cannot key 100% correctly every time. Once you have keyed a character wrong, that will give you the chance to key the character wrong again and thus increasing its probability to be selected again. If you reached a total level of frustration, switch back to Koch random mode and relax some time before using the "Adaptive Random" mode again. + + +=== Transceiver + +There are three transceiver modi in the Morserino-32. The first one is a self contained transceiver for communication with Morse code, using LoRa spread spectrum radio technology (in the standard version on the 433 MHz band, but versions for 868 and 920 MHz bands are available). The next one uses the Internet Protocol (specifically UDP on port 7373) for communicating across an IP network (using WiFi). The third one is a transceiver mode that can be used either with an external transceiver (e.g. a shortwave amateur radio transceiver) or with a protocol like iCW (CW over Internet). In all three cases the CW Keyer and a CW Decoder are active at the same time. + + + +==== LoRa Trx + +As stated above, this is a Morse code transceiver, using LoRa for transmitting Morse code to other Morserino-32s. +In addition to the functionality of the CW keyer, this sends out whatever you key through the LoRa transceiver +(using a special data format that encodes the dots and dashes you keyed, regardless if these are legal Morse code characters or not), +and it listens on the band when you are not keying; therefore you can really have an interactive conversation in Morse code +between two or more Morserino-32 devices! +Please be aware that characters are being transmitted word by word, +therefore there is a little delay on the receiving end - QSK is therefore not possible. It encourages you to use proper hand-over procedures! + +===== More information about the Modus "LoRa Trx" +Basically, this uses the same interface as the CW Keyer. But as soon as you receive something, the status line also shows the speed of the sending station in addition to your own speed - you see something like **18r20sWpM**, which indicates you are receiving a station with a speed of 18 Wpm, and you are sending at 20 WpM. +In addition, the volume bar on the right of the status line changes its function: instead of indicating the current volume level, it gives you an indication of the signal strength - a crude form of an S-Meter, if you like. +the full bar indicates an RSSI level of roughly -20dB, and the bar begins to show at a level of roughly -150dB. + +Pressing the RED Pwr/Vol/Scroll Button still enables you to set the audio level. + +Morse characters received by the transceiver +are shown in bold in the (scrollable) text area on the display, while everything you are sending is shown in regular characters. + +Another feature is worth mentioning here: The frequency of the tone you are hearing when you are receiving the other station is adjusted through the "Pitch" parameter, as in the other modi. +When you are transmitting the pitch of the tone can be the same, or a half tone higher or lower then the receiving tone - +this is being set through the `Tone Shift` parameter, in the same way as in Echo Trainer modus. + +One other thing you might want to know: the LoRa CW Transceiver does not work like a CW transceiver on shortwave, where an unmodulated carrier is being keyed, and the delay between sender and receiver is just defined by the delay in the path of the electromagnetic waves carrying the signals. LoRa uses a spread spectrum technology to send data packets - in a way a bit similar to WiFi that you use on your phone or PC. +Therefore all you are keying in is being encoded into data first - essentially the speed and all the dots, dashes and pauses between characters. +As soon as the pause is long enough to be recognized as a pause between words (as a blank space, as it were), +the whole data packet assembled so far is being transmitted and in due course being played back at the indicated speed by the receiving Morserino-32. + +When morse code is packed into a LoRa data packet, dots, dashes and pauses are encoded; it is not so that the clear text would be sent as ASCII characters. Therefore it is possible to send "illegal" morse code characters, or characters that might only be used in certain languages. They will be transmitted correctly (but shown on the display as non-decodable). + +Sending the code word by word means there is a significant delay between sender and receiver, and the delay depends to a large degree on the length of the words being sent, and on the speed that is being used. As most words in a typical CW conversation are rather short (7 characters or more already constitutes a very long word), this is nothing to worry about (unless you are sitting both in the same room using no headphones - then it will be really confusing). But try sending really long words, say 10 or more character long, at really low speed (5 WpM), and you will see what I am talking about! + +===== Using two different LoRa "Channels" +LoRa data packets are addressed with a so called "Sync Word" - receivers discard packets that do not show the sync word they are expecting. + +Morserino-32 as of Version 2.0 can make use of two different sync words, thus effectively creating two different "channels" +over which it can communicate. This can be used, for example, in a class room situation, +to create two independent groups that do not interfere with each other. + +Normally M32 LoRa works with sync word 0x27 (we call it the "Standard" channel), but through the setting `LoRa Channel` in the parameters menu +can be switched to 0x66 (called "Secondary" channel). + +===== Using different LoRa frequency bands and/or frequencies +By default the Morserino-32 kits are being shipped with a LoRa module that works in the 70 cm band, +and as standard frequency within that band on 434.150 MHz (within 70cm Amateur band and within region 1 ISM band). + +If for whatever reason you cannot use this frequency (maybe because of band plans, regulatory reasons etc.), you can change the frequency on the standard LoRa Module between 433.65 and 434.55 MHz in steps of 100 kHz. + +Should you require a LoRa frequency either around 868 MHz or around 920 MHz, you need to get a Heltec module that support this higher frequency range. In that case, you MUST configure your Morserino to use the correct band and frequency. + +**See <> at the end of this document** to learn how you can configure LoRa for modules that support the 868 and 929 MHz bands, and how to change the LoRa frequency settings. + + +===== Technical Details of LoRa Trx +* Frequency: Default is 434.150 MHz (within 70cm Amateur band and within region 1 ISM band) - but see the notes above for choosing other frequencies +* LoRa Spreading Factor: 7 +* LoRa Bandwidth: 250 kHz +* LoRa CRC: no CRC +* LoRa Sync Word: 0x27 (= decimal 39) for standard channel, and 0x66 (= decimal 102) for secondary channel +* HF Output: 20 dBm (100 mW) + +==== Wifi Trx [[wifitrx]] + +You can use this transceiver mode to communicate with your CW buddy using the Internet protocol, either on your local area network, or across the Internet. As it uses WiFi, you need to make sure you can connected to WiFi - so you must have performed the function "WiFi Config" before. On your local network it is very easy to use this transceiver mode: just select it from the menu, and you will be able to communicate (without configuring a peer address it will send to the IP address 255.255.255.255, which is a broadcast address and can be received by all devices on this network). The Morserino-32 uses UDP port 7373 for asynchronous communication. + +When you start Wifi Trx, the IP address of your peer (or "IP Broadcast") will be shown for a moment on the display. + +If you want to communciate with a specific Morserino-32 over the Internet, you need to configure the IP address of your buddy - this is done through the menu item 'Config WiFi', which shows now a third field beyond SSID and Password. In this field you need to enter the IP address of your peer (or its DNS host name), and then the Wifi Transceiver will send the packets to that specific IP address. + +If that IP address is not on your local network, and if you are behind some form of firewall or a router that treats your network as a private network, the Morserino will be able to send out to the Internet (unless specific firewall rules are blocking most UDP ports), but the packets coming from your buddy will be blocked at the router. In this case you need to configure "Port Forwarding", telling the router to send all UDP packets on port 7373 to your Morserino. At the same time, you need to tell your buddy your OUTSIDE IP address (i.e. the IP address of your router interface to your Internet provider), and your buddy has to do the same (configure port forwarding, and telling you his Internet-facing IP address, which you will enter into your Morserino). Sounds a bit complicated at first, but isn't really that bad. + +Another option, perhaps a bit more complicated, would be to set up a VPN (Virtual Private Network), so that both your Morserinos are on the same "virtual network" and hence can talk to each other without any firewall rules blocking the traffic. How to do this goes clearly beyond the scope of this manual -- ask an Internet guru for further details! + +==== iCW/Ext Trx + +In this modus a transceiver connected to the Morserino-32 is being keyed, or you can use the line-out audio to either key +for example an FM transceiver, or use CW over the Internet (iCW - this uses Mumble as an audio exchange protocol). +Any CW signals coming in as audio through the audio-in port are being decoded and displayed on the screen. +An external transceiver connected through the connector #1 will be keyed by the keyer, or you can use the audio output +on connector # 2 to feed it into a computer, or into an FM transceiver. + +=== CW Decoder + +In this modus, Morse code characters are being decoded and shown on the screen. The Morse code can either be entered via a Morse key ("straight key" - connected to the jack where you would normally connect an external paddle; you can also use one of the touch paddles to manually key the decoder). Using the decoder in this way, you can control and improve your keying with a straight key, by checking, if the decoder decodes correctly what you tried to send. + +You can also decode a tone input (at the audio input port) taken for example from a receiver. The tone should be at around 700 Hz. Optionally there is a pretty sharp filter (implemented in software) that detects just tones in a very narrow range around 700 Hz, and disregards all others. This is being used by selecting the Parameter "Narrow" (see the section <>). + +The status line is slightly different from the other modi. First of all, the rotary encoder is always in the volume setting mode - speed is determined from the decoded Morse code and cannot be set manually. Pressing the encoder button will end the decoder modus and bring you back to the Start Menu. + +On the left of the status display at the top, you will see a black rectangle whenever the key is pressed (or a 700 Hz tone is detected) - this replaces the indicator for the keyer mode. + +The current speed as detected by the decoder is displayed as WpM on the status line. + +This modus does not have many parameters (see the section <>); maybe the most important is the ability to switch the filter bandwidth of the audio decoder between narrow (ca 150 Hz) and wide (ca 600 Hz). For decoding signals from a transceiver (where there might be other signals in the vicinity), it is usually best to set the bandwidth to "Narrow" and tune the signal to precisely 700 Hz. For decoding signals from an FM transceiver, or from iCW or other environments with little interference, it is better to use the "Wide" setting - in that case the audio frequency does not need to be exactly 700 Hz. + +=== WiFi Functions + +You can use the WiFi feature of the Heltec ESP32 Wifi LoRa Module used in the Morserino-32 for two functions of the device: + +* Uploading a text file to the Morserino-32 that can then be played in CW Generator Modus oder Echo Trainer modus. +* Uploading the binary file of a new firmware version. + +For both of these functionalities the file to be uploaded (be it a text file or the compiled binary file for the software update) must be on your computer (even a tablet or smartphone will work, as you only need basic web-browser functionality on that device), and your Morserino must be connected to the same WiFi network as your computer. + +In order to connect your Morserino-32 to your local WiFi network, you usually need to know the SSID (the "name") of the network, and the password to connect to it. And you must enter these two items into your Morserino-32. As it does not have a keyboard for convenient entry of this information, we use another way of doing it, and for this end another WiFi function has been implemented: network configuration, which is the first you have to use before you can use the upload or update functions. + +For home networks that use a list of allowed MAC addresses (for security reasons), you have to configure your router and enter the M32's MAC address before you can connect your M32 to the network. In order to be able to do so, there is also a function implemented to show the MAC address on the display. + +All network related functions can be found under the menu entry "**WiFi Functions**" + +IMPORTANT: In software version before 2.0 the WiFi functions were not integrated into the main menu. In case you want to update from version 1.x to version 2.x through WiFi, please read section <> at the end of the document. + +==== Displaying the MAC Address +This is the first entry under the menu "Wifi Functions", and it displays the Morserino's MAC address in the status line. Each Morserino has a unique MAC address. + +You can use this information to allow the Morserino access to your WiFi network, if your router is configured to recognize only certain MAC addresses. + +If you press the RED button, the Morserino-32 will restart normally. if you do nothing, the Morserino will go into deep sleep, depending on the settings you defined for that, as usual. + + +==== Network Configuration + +Select the sub-menu **"WiFi Config"** to proceed with network configuration. + +The device will start WiFi as an *access point*, thus creating its own WiFi Network (with the SSID "morserino"). If you check the available networks with your computer or smartphone, you will find it easily; please select this network on your computer (or tablet, or smartphone -- you will not need a password to connect). + +Once you are connected, enter "http://m32.local" into your browser on your computer. If your computer or smartphone does not support mDNS (Android, for example, is not supporting it, and Windows only rudimentary), you have to enter the IP address **192.168.4.1** into the browser instead of m32.local. You will then see a little form with just 3 times 3 empty fields in your browser: "SSID of WiFi network?", "WiFi Password?" and "WiFi TRX Peer IP?". + +You only need to fill in one set of fields, but you can use two or three sets if you want to store **different network configurations** for different usage scenarios (e.g.,connection to different WiFi networks). There is a separate entry in the WiFi menu to select which configuration you want to use. + +Enter the name of your local WiFi network, and the corresponding password (you can leave the third field empty for now), and click on the "Submit" button. Your Morserino-32 will store these network credentials and then restart itself (so the network "morserino" will disappear). + +The third field ("WiFi TRX Peer IP/Host?") is used, when you want to use the Wifi Transceiver functionality, i.e. to talk to another Morserino user over the Internet. In such a case you have to enter the IP address or the DNS host name, if it has any, of the other Morserino into this field. See section <> above. If you communicate with other Morserinos in your local network, you don't need an IP address there (it will use the broadcast address by default, so all Morserinos can receive what one of them sends). + +IMPORTANT: Your Morserino cannot make use of a WiFi network with a "captive portal", as they are often used on public networks. These networks require that a browser is available on the device that wants to connect to the network, and the Morserino-32 does not have a browser... + +IMPORTANT: Your Morserino-32 only supports WiFi networks in the 2.4 GHz band, not in the 5 GHz band. It also sometimes seems to have problems with Apple Airport routers. + + +TIP: If you have configured your WiFi before, and perform this step again, the previously entered SSID name will be pre-filled in the form, and you only need to change it if necessary. The password field will be empty, but if you do not enter a new one, the old password will still be used. The TRX Peer IP address field will also be pre-filled with a value if you have entered one before. If you now delete the values in this field, this IP address will be deleted. + +TIP: You can configure three different network settings; from version 4.5.1 on the network configurations will not be stored in Snapshots, this means you cannot use snapshots to recall different network settings. + +==== Checking your network connectivity +Use the sub-menu entry "Check WiFi" under "WiFi Functions" to test network connectivity. + +This either shows an error message ("No WiFi" and the SSID you had entered), or a success message ("Connected!"), the SSID and the IP address the Morserino got from your WiFi router. + +TIP: You might have to move your Morserino pretty close to your WiFi router (within the same room is usually OK)! The WiFi antenna of the Heltec module is very small and will not pick up weak WiFi signals. + + +TIP: When you get an error message although you had entered the correct credentials and the Morserino is in direct vicinity of your WiFi router, you should try again - sometimes the first try is not successful, for whatever reasons... + +If you press the RED button, this functions returns to the menu. If you do nothing, the Morserino will go into deep sleep, depending on the settings you defined for that, as usual. + + +==== Uploading a Text File [[upload]] + +Once you configured your Morserino-32 with your local WiFi credentials, you are ready to upload a text file to use for your Morse code training. Currently only one file can reside on the Morserino-32, This means, whenever you upload a new file, the old one will be overwritten. + +The **file** that you upload should be a plain ASCII text file without any formatting (no Word files, pdf documents etc.). German characters (ÄÖÜäöüß) encoded as UTF-8 are allowed and will be converted to ae, oe, ue and ss. The file can contain uppercase and lowercase letters, and all the characters that are part of the Koch method set (50 characters in total). Any other characters will just be disregarded when the file is played in Morse code. The file that you upload can be pretty large - you have almost 1 MB space available for it (enough to store a copy of Mark Twain's "The Adventures of Huckleberry Finn"). + +In order to upload the file, select "File Upload" from the "WiFi Functions" menu. After a few seconds (it needs to connect to your Wifi network first) Morserino-32 will indicate that it is waiting for upload. You point the browser of your computer to "http://m32.local" (or, if that does not work, replace "m32.local" with the IP address shown on the display). + +TIP: For the upload function your Morserino-32 (and of course your PC or tablet etc.) must be on your local WiFi network again! + +First you will see a **Login** screen on your browser. Use "**m32**" as User ID and "**upload**" as password. On the next screen in your browser you will find a file selection dialog - select the file you want to upload (its name or extension doesn't matter) and click the button labelled "Begin". Once the upload is completed (it will not take long) the Morserino-32 will restart itself, and you can now use the uploaded file in *CW Generator* or *Echo Trainer* modus. + +IMPORTANT: If for any reason you need to abort the process, you have to restart the device either by completely disconnecting it from power (battery off and USB disconnect), or pressing the Reset button with the help of a tiny screwdriver or a ball point pen (the reset button can be reached through the hole next to the USB connector, towards the external paddle connector). + +==== Updating the Morserino-32 Firmware + +Updating the firmware of the Morserino-32 through WiFi is one way of doing it; you can also do this by using the Arduino IDE on your computer (you also need to install a bunch of specific files and libraries for support of the Heltec module and the ESP32 processor, and then compile the binary from the source code), or by using a special update utility (see <>). + +TIP: You can update to any version, you can "jump" versions, you can also go back to an older version. + +Updating the firmware is very similar to uploading a text file. You first need to get the binary file from the Morserino-32 repository on GitHub (https://github.com/oe1wkl/Morserino-32 - look for a directory under "Software" called "Binaries". Get the latest version and download it to your computer. The file name looks like this: + +`morse_3_vx.y.ino.wifi_lora_32.bin` with x.y being the version number. + +Now get the WiFi Functions menu again and select the item "**Update Firmw**". Similar to file upload, you point the browser of your computer to "http://m32.local" (or, if that does not work, the IP address shown on the display, http://n1.n2.n3.n4 - replace n1.n2.n3.n4 with that IP address), and you will eventually see a Login screen. This time you use the user name "**m32**" and the password "**update**". + +Again you will see a file selection screen next, you select your binary file and click the button labelled "Begin". This time the upload will take longer - it can take a few minutes, so be patient. The file is big, needs to be uploaded and written to the Morserino-32 and needs to be verified to make sure it is an executable file. Finally, the device will restart itself and you should notice the new version number on the display during start-up. + +[TIP] +==== +To sum it up, these are the steps for updating the firmware through WiFi: + +1. Do the network configuration as described above (for this the Morserino sets up its own WiFi network, and you use your browser to enter the name and password of your home WiFi network). You do this only once, as the Morserino will remember these credentials for future use. You might want to use the "Check WiFi" function to make sure your Morserino can connect to your network. Remember that your Morserino has to be pretty close to your WiFi router! + +2. You download the new binary to your computer. + +3. You start „Update firmware“ on your Morserino. After a while it will show you an IP address (which is on your home network!) and a message, that it is waiting for an update. + +4. You leave your computer on your home network, and point your browser either to the IP address shown on the Morserino (http://ww.xx.yy.zz), or to „http://m32.local“ (this works on Macs and iPhones, usually, it does not work on Windows PCs or Android devices). + +5. You will get a login screen on your browser. Enter „m32" as username and „update“ as password. + +6. You will see a file selection dialogue. You select the binary file in your download folder, and then click „Begin“. You will see a progress bar, and after some time (can take a few minutes - even when the progress bar already shows 100%) the Morserino will restart itself, and show the new version number on the startup screen. Then you know the update was successful. +==== + + +==== WiFi Select +Here you can select which of your stored network configurations should be used, when more than one network has been configured. + + +=== Go To Sleep + +This menu item, when selected, puts the Morserino-32 into a deep sleep mode, where it will consume considerable less power than when operating normally. But it will still drain the battery within a few days, so this is only meant for shorter breaks between your training sessions. See the section <> further up in this manual. + +== Parameters [[parameters]] + +You always reach the parameters menu by **double clicking** the **BLACK rotary encoder button**. This provides you with a menu of settings (you will see a `**>**` character in front the of the current parameter, and the line underneath shows the current value). Use the encoder to lead you through the available parameters. If you want to leave the parameter setting menu, just press the encoder button a bit longer, and you will be back in the operational modus from which you called the parameter setting menu (or back in the menu, if you entered a double click from the menu). + +When you have reached the parameter you want to change, click once. Now the "**>**" character will be at the bottom line in front of the parameter value, indicating that rotating the encoder will change this value. Once you are satisfied with the value, **click once** to return to the selection of parameters, or **press the button a bit longer** to leave the parameter menu. + +Obviously the parameters that can be set vary depending on the modus you are in: **When you double click while in a particular modus, you will only get to those parameters that are relevant for the current modus.** Did you double click from the Start Menu, you will be presented the complete range of parameters. + + +=== Snapshots [[snapshots]] +For different types of training you usually need different settings of the parameters - you might want to change the inter-character- or inter-word spaces, or the length of character groups or words, etc. So going from one type of training to the next would require you to change various settings every time. + +In order to make this easier, you can use "snapshots" of the settings: once you have changed everything for your first mode of training, you store all current parameters in one of eight snapshots; then you do the same with your other training modes. You can then quickly recall the settings by recalling a particular snapshot. + +TIP: The "Koch Lesson" that you selected will be stored in non-volatile storage and hence will be available after a restart, but it will not be stored or overwritten in one of the snapshots. The same is true for WiFi settings, the "Serial Out" parameter, or your setting of speed and speaker volume. + +==== Storing a snapshot + +First, double click to get into the parameter menu. Now a long press of the RED button gives you an opportunity to select with the encoder at which location you want to store the current settings, from "Snapshot 1" to "Snapshot 8"; a further option reads "Cancel Store" and allows you to get out without storing a snapshot. Snapshot locations that are already in use are shown in **bold**, but you can overwrite those as well. Clicking on the black knob stores the snapshot in the desired location, and gives you a quick indication about its success. + +==== Recalling a snapshot + +Again, you double click the black knob first to get into the parameters menu. Now a **short** click on the RED button lets you select with the encoder which of the stored snapshots you want to recall, and you recall it by clicking the black encoder button; again, there is an option that reads "Cancel Recall", which allows you to get out without recalling a snapshot. + If there are no snapshots stored, you get a message "NO SNAPSHOTS" and you can leave by clicking any of the buttons. + +==== Deleting a snapshot + +You can also delete a snapshot that is no longer needed, or that was created in error. Proceed as if you wanted to recall a snapshot, select the one you want to delete, and then click the RED button for deleting it. Like with storing and recalling snapshots, a short message will indicate that the action was successful. + + +=== List of All Morserino-32 Parameters +Bold values are standard or recommended ones. When called from the start menu, all parameters are available for modification, when called from a running modus, only those that are relevant for this modus are available. + +===== General Parameters + +A number of parameters are very generic in nature, and therefore apply to all modi of the Morserino-32. + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + + +| Encoder Click | Turning the encoder may generate a short tone burst, or be silent | Off / On + +| Tone Pitch Hz | The frequency of the side tone, in Hz | A series of tones between 233 and 932 Hz, corresponding to the musical notes of the B flat major scale from b flat to b'' flat (2 octaves) + +| Time Out | If the time specified in this parameter passes without any display updates, the device will go into deep sleep mode. You can restart it by pressing the RED button. | No timeout / **5 min** / 10 min / 15 min + +| Quick Start | Allows you to bypass the initial menu selection, i.e. at startup the device will immediately begin executing the modus that had been in effect before last shutdown. | ON / **OFF** + +| Serial Output | Here you control what is being sent to serial port (USB connector); distinction is made between keyed characters (output from the iambic keyer), decoded characters (from CW decoder or using a straight key), and "generated" characters (from CW Generator etc., also from the recevier side of LoRa or WiFi Transceiver modes). "Nothing" sends out none of these characters (but certain system or error messages might still appear), while "All" send out everything. In addition, other information can be sent and received via the serial port through the M32 Serial protocol, if the connected computer software supports this. See also <>.| Nothing / Keyer / Decoded / Keyed+Decoded / Generated / **All** (default since 4.3) +|=== + + +===== Parameters regarding Key, Paddles and Keyer + +These parameters control the behavior of the paddles (built in or external), in particular also the timing parameters relevant for Iambic Keying, or an external straight key (set the *Keyer Mode* to *Straight Key* in order to use a straight key). + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + +| Paddle Polarity | Defines which paddle side is for dits, and which for dahs | ` _. dah-dit` / **`._ di-dah`** + +| External Pol. | Allows to reverse the polarity of an external paddle. Use this if your external paddle is wired "the wrong way", so that dots and dashes of internal and external paddle are all on the same side. | Normal / Reversed + +| Keyer Mode | Sets the Iambic Mode (A or B), Ultimatic, Non-Squeeze or Straight Key; see the section <> | Curtis A / Curtis B / Ultimatic / Non-Squeeze / Straight Key + +| CurtisB DahT% | Timing in Curtis B mode for dahs; see below | 0 -- 100, in steps of 5 [**35 - 55**] + +| CurtisB DitT% | Timing in Curtis B mode for dits; see below | 0 -- 100, in steps of 5 [**55 - 100**] + +| AutoChar Spce | Minimum spacing between characters | Off / min. 2 / **3** / 4 dots + +| Latency | Defines how long after generating the current element (dot or dash) the paddles will be „deaf“. If it is 0, you have to release the paddle while the last element is still „on“. If set to 7, the paddles will only react to a paddle press after 7/8 of a dot length. | A value between 0 and 7, meaning 0/8 to 7/8 of a dot length (default is **4**, i.e. half a dot length). + +|=== + +===== Parameters regarding Koch Character Sequence + +If you follow courses by various institutions, they will follow a certain order introducing Morse code characters to you. Here you can select which order you want to follow. + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + +| Koch Sequence | This determines the sequence of characters when you use the Koch method for learning and training. You can also use your customized character set by choosing Custom Chars - see the section <>, the last paragraph. | **M32** (native order, also used by JLMC - Just Learn Morse Code) / LCWO / CW Academy / LICW Carousel / Custom Chars / + +| LICW Carousel | This defines the "Entry Point" into the LICW Carousel curriculum (only relevant if the parameter "Koch Sequence" is set to "LICW Carousel"). When you start a course in BC1, you should set this accordingly, and also set it again when you join the carousel classes for BC2. | **BC1: r e a** / BC1: t i n / BB1: p g s / BC1: l c d / BC1: h o f / BC1: u w b / BC2: k m y / BC2: 5 9 , / BC2: q x v / BC2: 7 3 ? / BC2: ar sk = / BC2: 1 6 . / BC2: z j / / BC2: 2 8 bk / BC2: 4 0 + +|=== + + +===== Parameters regarding CW Generation + +The following parameters control how characters are generated and played randomly, or how text files are being played as Morse characters. I'd like to draw your attention to "Interchar Spc" and "Interword Spc" in particular, as through those parameters you can achieve what is otherwise known as "Farnsworth Speed" or "Wordsworth Speed", respectively. Of course, these parameters are also relevant for the Echo Trainer! + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + +| Interchar Spc | The time (in lengths of a dit) that is inserted between characters (see section <> ) | 3 -- 15 [**3**] + +| Interword Spc | The time (in lengths of a dit) that is inserted between words (see section <> ) | 6 -- 45 [**7**] + +| Random Groups | For the output of groups of random characters, determine which character subsets should be included | Alpha / Numerals / Interpunct. / Pro Signs / Alpha + Num / Num+Interp. / Interp+ProSn / Alpha+Num+Int / Num+Int+ProS / All Chars + +| Length Rnd Gr | Here you select how many characters there should be in each group of random characters; traditionally this is 5, but for training it might make sense to start with a smaller number. | Fixed lengths 1 -- 6, and 2 to 3 -- 2 to 6 (length chosen randomly within these limits) [**5**] + +| Length Calls | Select the maximum length of generated call signs | Unlimited / max. 3 -- max. 6 + +| Length Abbrev | Select the maximum length of the randomly generated common CW abbreviations and Q groups | Unlimited / max. 2 -- max. 6 + +| Length Words | Select the maximum length of the randomly generated common English words | Unlimited / max. 2 -- max. 6 + +| Max # of Words | When the specified number of words or letter groups has been generated, the Morserino-32 will generate a final AR ("+") pro sign to indicate that this sequence is over, and then pause and wait - with a touch of a paddle (or clicking the black knob) it will continue and generate the next sequence of words. (When "Auto Stop" is active, this parameter will be ignored in CW Generator modus.) | **Unlimited** / 5 to 250 in steps of 5 + +| Stop/Next/Rep | Stops the generating of morse characters after each word in CW Generator and Koch Generator modes to help with learning head copying. Continue by touching the right paddle to play the next word, or by touching the left paddle to repeat the word. This option and the option 'Each Word 2x' are not compatible with each other, setting one to ON, will set the other to OFF automatically. | ON / **OFF** + +| CW Gen Displ | Select, how the CW Generator, or the LoRa or CW Transceiver should display what is generated or received | Display off / **Char by Char** / Word by word + +| Randomize File | If set to „On“, file player will skip n words after each word sent (n = random number between 0 and 255) | **Off** / On + +| Each Word 2x | In the CW Generator modus, each "word" (characters between spaces) will be output twice, as a help to learn to copy by ear. This option and the option 'Stop/Next/Rep' are not compatible with each other, setting one to ON, will set the other to OFF automatically. There are three ON settings: normal (if an increased inter-character space has been set, it will also be honoured during the repetition; ON less ICS: the additional inter-character space will be reduced during the repetition; ON true WpM: the increased inter-character space will be ignored during the repetition.| **OFF** / ON / ON (less ICS) / ON (true WpM) + +|=== + +===== Parameters regarding Echo Trainer + +The following parameters control the essential properties of the Echo Trainer (however, Tone Shift is also interesting for the transceiver modes). + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + + +|Echo Repeats |Here you can set how often a word is repeated if the answer is either too late or incorrect before the Echo Trainer generates a new word. If the value is 0, then the next word is always a new one, regardless of whether the response was right or wrong. | 0 -- 6 / Forever + +|Echo Prompt | This defines how you are prompted in Echo Trainer mode. The possible settings are: „Sound only“ (default; the standard behavior in previous versions; best for learning to copy in your head), „Display only“ (the word you are supposed to enter is shown on the screen, no audible code is generated; good for training paddle input), and „Sound & Display“, i.e you hear the prompt AND you can see it on the display. | **Sound only** / Display only / Sound&Displ + +| Confrm. Tone | This defines if an audible confirmation tone should be sounded in Echo Trainer modus. If you turn it off, the device just repeats the prompt when the response was wrong, or sends a new prompt. The visual indication of "OK" or "ERR" will still be visible when the tone is turned off. | **On** / Off + +| Tone Shift | The pitch of the tone, when you are using the Echo Trainer modus or transmitting in a transceiver mode, can either be the same as the one you get from the receiver (or from the prompt in Echo Trainer modus), or can be a half tone lower or a half tone higher. |**No Tone Shift** / Up 1/2 Tone / Down 1/2 Tone + +| Adaptv. Speed | If this is set to ON, the speed will be increased by 1 WpM whenever you gave a correct response in Echo Trainer modus, and will be decreased by 1 whenever you made a mistake. | ON / **OFF** + +|=== + +===== Parameters regarding Transmitting and Decoding + +These Parameters control some functions available for transmitting (either directly through LoRa or Wifi, or through keying an external transmitter), or for decoding Morse code characters. + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + + +|Key ext TX | Here you determine, if a connected Transmitter will be keyed when you use the device. Gen = generator modi, RX = LoRa or Internet Receiver modi | Never / **CW Keyer only** / Keyer & Gen. / Keyer&Gen.&RX + +| Generator Tx (used to be called "Send via LoRa") | This allows the CW Generator to send, what it generates, eiher via LoRa or via WiFi - so you can have one device generating something, and several others receiving the same sequence. This can be used in all CW Generator and Koch / CW Generator modes, including File Player. Could be useful for groups of learners, as you can transmit e.g. contents of a file to a group of people. Obviously this should only be used with caution (and not for extended period of time) on public M32 chat servers, but can be very handy for a group on the same network segment, using broadcast as TrX peer, or a privately set up chat server, or via LoRa when all participants are close enough together. +Be aware that you must have an antenna connected when you transmit via LoRa, otherwise the LoRa transceiver will eventually be destroyed! | **„Tx OFF“** (= do not transmit generated CW), „LoRa Tx ON“ (transmit generated code through LoRa) and „WiFi Tx ON“ (transmit generated code through WiFi). + +| LoRa Channel | Selects which virtual channel LoRa is using. | **Standard Ch** / Secondary Ch + +| Bandwidth | Defines the bandwidth the CW decoder is using (this is implemented in software using a so called Goertzel filter). (Wide = ca. 600 Hz, Narrow = ca. 150 Hz; center frequency = ca 700 Hz) | **Wide** / Narrow + +| Decoded on I/O | Normally, decoded CW that comes from an external source (when using any of the transceiver modi, or using the decoder to decode audio input) is played on the speaker (or headphones), but not sent to the external audio I/O port. With this parameter set to „ON“, the audio is also sent to the external audio I/O port. | On / **Off** + + +|=== + +== Appendices + +=== Appendix 1: Hardware Configuration (LoRa and Calibration of Battery Measurement) + +There is a hardware configuration menu that can be reached by pressing a paddle (or external paddle or straight key) while switching the M32 on. You can then select the configuration you want to perform by rotating the encoder knob, and pressing it once the right option shows up. + +The selectable options are "Calibr. Batt." (calibration of battery measurement), "LoRa Config." and "Cancel" (which just leaves this menu and continues with regular start-up of the M32). + +==== Appendix 1.1: Configuring LoRa Bands, Frequencies and Output Power [[appendix1_1]] + +If you have a standard 433 MHz Heltec module in your Morserino-32, it has been already preconfigured for the right band and a default frequency within that band. + +IMPORTANT: If you have to change either the frequency within the standard band, or you use a Heltec module for the 868 and 920 MHz bands, you have to configure your Morserino-32 before you use the LoRa capabilities. + +The following bands and frequency ranges can be configured in the Morserino-32 for Heltec modules supporting the upper UHF LoRa modules: + +* 868 MHz band: + 866.25 to 869.45 MHz in steps of 100 kHz (default: 869.15 MHz) +* 920 MHz band: + 920.25 to 923.15 MHz in steps of 100 kHz (default: 920.55 MHz) + +The default Heltec modules supports the 433 MHz band only, and the Morserino-32 can be configured to use 433.65 to 434.55 MHz in steps of 100 kHz (default: 434.15 MHz). + +**In order to configure the Morserino-32 for non-standard frequencies and bands, or to configure the output power, please proceed as follows:** + + +* Start your M32 while holding the touch paddles (or external paddles, or straigth key) pressed. +* When you see a message, release the black knob. +* Select the Option "LoRa Config." with the rotary encoder. +* First you will be asked to select the desired band (select 433 for the default LoRa module, and either 868 or 920 for the upper UHF LoRa module); rotate the encoder to the desired band, and click the black knob once. **The band selection has to fit the Heltec module you are using!** +* Now your are being asked to select a frequency within your selected band. The first frequency shown is the default for that band - if that is OK, just click the black knob once, otherwise select a frequency by rotating the encoder and clicking the knob once you have found the correct frequency. +* In a further step you can configure the output power of the LoRa transceiver. The default is 14 dBm (= 25 mW), and you can set it in several steps between 10 dBm (=10 mW) and 20 dBm (=100 mW). **Be aware of applicable regulations in your jurisdiction, there might be a legal limit regarding output power!** Be also aware that the higher the output power, the higher the risk of destroying the LoRa transceiver when used without proper termination (a suitable antenna or a dummy load). +* Immediately after that the Morserino-32 will start normally, with the now selected LoRa settings in effect. On the top line of the Startup Screen you will see the configured QRG for LoRa as a 5-digit number (e.g. 43415 for the default in the 433 MHz band). + +==== Appendix 1.2: Calibration of Battery Measurement [[appendix1_2]] + +The built-in capability of Heltec modules to measure battery voltage unfortunately is not very reliable. Various factors apparently contribute to the problem: a measurement error within the ESP32 processor due to a slight variation of the reference voltage for each chip (leading to a relatively small error), and problems with the voltage divider circuit on the Heltec module (leading to pretty big variations among the modules). Although measuring the battery is not very crucial for the operation of the Morserino-32, it is nevertheless a nuisance, and can also lead to the situation that the M32 cannot be switched on, as the firmware thinks that the voltage is too low, when in reality it would still be sufficient. + +In order to calibrate the voltage measurement, you have to measure the actual battery voltage of your Morserino-32 with the help of a multimeter. Once you know this value, you perform the following steps: + +* Start your M32 while holding the touch paddles (or external paddles, or straigth key) pressed. +* Select the Option "Calibr. Batt." with the rotary encoder. +* You will see a voltage value (in Millivolts) on the display. Now rotate the encoder until the displayed value is as close as possible to the measured battery voltage. +* Press the BLACK encoder knob to store the calibration value, and to continue with the boot-up of the M32. + + +=== Appendix 2: Adjusting Audio Input Level [[app2]] + +You can also reach one **other function** while you are positioned within the Start Menu - not through a menu selection, but through **a long press on the RED button**: + +This starts a function to adjust the audio input level: make sure a tone signal is available on the input, for example from your shortwave receiver (see <> at the beginning of this document, #2), and a bar graph will indicate the voltage of the input signal. Adjust it with the blue trimmer potentiometer, so that the left and right ends of the solid bar are within the two outer rectangles. At the same time, a sinus signal is output on line-out, and the transceiver output is shortened (keying a transmitter, should you have it connected to one - disconnect your transceiver first if this is not what you want!). You can now, for example, adjust the level of the output signal on a connected computer, or check whether a transmitter is being keyed. + +A simple test or demo for the audio-in adjustment is to connect line-out with audio in (connect tip with sleeve), feeding the output sine wave into the audio input. You can see the solid bar graph changing when you turn the potentiometer, leaving just a tiny solid bar in the middle and exposing the two rectangles on both ends of the graph at one end of the potentiomenter range (essentially you are just measuring the noise on the operation amplifiers input), and with the solid bar graph extending beyond the rectangles on both ends on the other end of the potentiometer sweep. Now you can set the potentiometer so that the solid bar is almost touching the outer bounds of the rectangles. This is the optimal setting for the audio in level. Obviously you have to perform this for the audio source you are planning to use, e.g. for your radio receiver. + +TIP: Only while you are in the menu will the RED button **long press** activate the level adjustment function. While you are executing one of the Morserino modes (Keyer, Generator, Echo Trainer, Transceiver etc.) a long press of the RED button activates the scroll mode of the display to enable you to read text that has already scrolled away... + +=== Appendix 3: Updating the Firmware from Versions < 2.0 [[appendix3]] + +With firmware versions 1.x the WiFi functions were not accessible directly from the main menu, but by quickly pressing the RED button three times. Hence the update procedure has to be performed as follows: + +If not already done before, you have to do the WiFi configuration first. + +While your Morserino-32 is displaying the Start menu, click the RED button three times quickly, in order to get into the WiFi Menu. The top entry is "WiFi Config", select it to proceed. + +The device will start WiFi as an access point, thus creating its own WiFi Network (with the SSID "Morserino"). If you check the available networks with your computer or smartphone, you will find it easily; please switch your computer to use this network (you will not need a password to connect). + +Once you are connected, enter "m32.local" into your browser on your computer. If your computer or smartphone does not support mDNS (Android, for example, is not supporting it), you have to enter the IP address 192.168.4.1 into the browser instead of m32.local. You will then see a little form with just 2 empty fields in your browser: SSID and password. Enter the name of your local WiFi network, and the corresponding password, and click on the "Submit" button. Your Morserino-32 will store these network credentials and then restart itself (so the network "Morserino" will disappear). + +Now get the WiFi menu again by clicking quickly three times on the RED button, and select the entry "**Update Firmw.**". Similar to file upload, you point your browser to "m32.local" (or the shown IP address), and you will eventually see a Login screen. This time you use the user name "**m32**" and the password "**update**". + +Again you will see a file selection screen next, you select your binary file and click the button labelled "Begin". This time the upload will take longer - it can take a few minutes, so be patient. The file is big, needs to be uploaded and written to the Morserino-32 and needs to be verified to make sure it is an executable file. Finally, the device will restart itself and you should notice the new version number on the display during start-up. + +Of course you can also update through USB when you are still on an older software version (see next appendix). + +=== Appendix 4: Updating the Firmware via USB [[appendix4]] +This simple update procedure, currently available for the Windows operating system, has become possible through work by Matthias Jordan and Joe Wittmer. + +First make sure you have a driver for the Silicon Labs CP210x USB to serial device, used by the Heltec Modul for its USB interface. Current versions of Windows 10 install this automatically; if yours doesn't, you can get the driver from here: + https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers + +To check if you have the correct driver installed, and to see to which port it connects, open the Device Manager on your computer (in the search field in the lower left of the screen start typing "settings: device" and it will come up for selection). + +Connect your Morserino with a USB cable to your computer. The device manager should update its screen and show an entry "Ports" - open it and it should indicate something like: Silicon Labs CP210x ... (COM3). Could be another COM port in your case, so please remember your correct port name. + +TIP: Make sure you have a cable that is a "proper" USB cable, not just a cable for a charger! + +Now download the update utility from Joe's GitHub repository: +https://github.com/joewittmer/Morserino-32-Firmware-Updater/releases + +Unzip that file. You will find a program "update_m32.exe" - copy that to a folder of your choice (I usually prefer the folder Downloads). Now get the binary Morserino file for the version you want to install from the Morserino GitHub, ideally into the same directory. + +Now open a command box on your computer (in the search field in the lower left of the screen start typing "cmd" and it will come up for selection). First "cd" (change directory) to the directory where the utility and the binary file are located; e.g., if you used the Downloads directory: + +`cd Downloads` + +Then enter the following command line: + +`update_m32 -p -f ` + +replacing with your COM port name, and with the correct name of the Morserino binary file. +In my case that was: + +`update_m32 -p COM3 -f m32_v4.1.ino.wifi_lora_32_V2.bin` + +After a short while your Morserino should restart, showing the updated version number. + +=== Appendix 5: Using the Serial Output of the Morserino-32 [[appendix5]] + +Der Morserino-32 ist in der Lage, Daten über die serielle USB-Schnittstelle auszugeben. Damit kann man sich beispielsweise die Zeichen, die auf dem Display angezeigt werden, in einem Terminalfenster eines Computers anzeigen lassen. Auf diese Weise können Sie die Morserino-Ausgabe auf einer großen Leinwand oder einem Projektor zeigen; dies könnte für Präsentationen oder den Einsatz im Klassenzimmer nützlich sein. + +In Version 5 wurde ein vollständiges Zwei-Weg-Protokoll namens „M32 Serial Protocol“ implementiert. Dies ermöglicht (über eine Software auf einem über USB angeschlossenen Computer) die Bildschirm- oder Sprachausgabe von Menüs und Einstellungen (z.B. um den M32 für blinde oder sehbehinderte Menschen nutzbar zu machen), und ermöglicht auch die Fernsteuerung aller Morserino-Funktionen vom Computer aus (z.B. Einstellungen, Parameter, Ändern von Geschwindigkeit und Lautstärke, Verlassen und Aufrufen von Menüs und sogar automatische CW Generierung). Das Protokoll wird in einem separaten Dokument beschrieben, das auf GitHub verfügbar ist. + +Für den verwendeten seriellen Port des angeschlossenen Computers muss man eine Baudrate von 115200 auswählen. + +Man kann die serielle Kommunikation in Verbindung mit Computersoftware verwenden, die speziell für den Morserino-32 geschrieben wurde, um seine Trainingsfähigkeiten zu verbessern. Derzeit stehen hierfür drei Softwareprodukte zur Verfügung: + +* Morserino-32 CW Training von Christof, OE6CHD (siehe https://tegmento.org/; dies läuft auf Chrome-Browsern, auf Mac, Windows und Linux und erfordert keine Installation; es nutzt auch bereits die Funktionen des seriellen Protokolls), + +* CW Trainer for Morserino by Enzo, IW7DMH (see https://iw7dmh.jimdofree.com/other-projects/cw-trainer-for-morserino-32/), and + +* Morserino Phrases Trainer by Tommy, OZ1THC (see https://github.com/Tommy-de-oz1thc/Morserino-32-Phrases-trainer). + +Siehe auch die Beschreibung des Parameters "Serial Output" im Abschnitt <>. diff --git a/Documentation/User Manual/Version 5.x/m32_user-Manual_v5.pdf b/Documentation/User Manual/Version 5.x/m32_user-Manual_v5.pdf new file mode 100644 index 0000000..7885b69 Binary files /dev/null and b/Documentation/User Manual/Version 5.x/m32_user-Manual_v5.pdf differ diff --git a/Documentation/User Manual/Version 5.x/m32_user-Manual_v5_de.adoc b/Documentation/User Manual/Version 5.x/m32_user-Manual_v5_de.adoc new file mode 100644 index 0000000..651ebd6 --- /dev/null +++ b/Documentation/User Manual/Version 5.x/m32_user-Manual_v5_de.adoc @@ -0,0 +1,1139 @@ += Morserino-32: Benutzerhandbuch +W. Kraml (OE1WKL) +v5.0 Juni 2023 +:organization: Morserino-32 Benutzerhandbuch +:doctype: book +// Settings: +:experimental: +:reproducible: +:icons: font +:listing-caption: Listing +//:sectnums: +:toc: macro +:toc-title: Inhaltsverzeichnis +:toclevels: 4 +ifeval::["{asciidoctor-version}" < "1.5.7"] +:legacy-footnoteref: +endif::[] +ifdef::backend-pdf[] +:pdf-theme: m32 +:pdf-themesdir: {docdir} +:source-highlighter: rouge +//:rouge-style: github +:media: prepress +endif::[] + +toc::[] + +[preface] +== Vorwort + +image::Morserino.jpg[Morserino-32] + +[.lead] +"`*_Morserino-32 -- Ein multifunktionales Morsegerät, perfekt zum Lernen und Üben_*`" + +Dieses Handbuch spiegelt die Funktionen der Firmware Version 5.x des Morserino-32 wider. In seinen Abbildungen nimmt es Bezug auf die 2. Auflage des M32, ist aber natürlich auch für die ältere 1. Auflage benutzbar. + +Es wurde mit **asciidoc** (anstelle von Markdown für die früheren Versionen) erstellt, und die PDF-Version wurde mit +**asciidoctor-pdf** gerendert, um ein Handbuch zu erstellen, das besser lesbar und für das Auge angenehm ist. + +Ich möchte mich bei allen bedanken, die durch Kommentare, Kritik und Anregungen dazu beigetragen haben, den Morserino-32 zu einem erfolgreichen und hervorragenden Produkt zu machen. + +== Anschlüsse und Bedienelemente [[controls]] + +image::M32_layout.jpg[Wo sind die Anschlüsse und Bedienelemente] + +[cols="^.1,.<3,.<10",options=header] +|=== +|# +|Anschluss / Bedienelement +|Gebrauch + +|1 +|3.5mm Klinkenbuchse (3-polig): zum Sender +|Verbinde diese mit deinem Sender oder Transceiver, wenn du diesen mit dem Morserino-32 tasten willst. Nur Spitze und Hülse sind in Benutzung. + +|2 +|3,5 mm Klinkenbuchse (4 Pole): Audio In / Line Out +| **Audioeingang** für den CW-Decoder; Schließe den Audioausgang eines Empfängers an, um CW-Signale zu dekodieren. **Audioausgang** (fast reiner Sinus), der nicht durch die Einstellung der Lautsprecherlautstärke beeinflusst wird. Die Zuordnungen zur Buchse lauten wie folgt: Spitze (Tip) und 1. Ring - Audioeingang; 2. Ring: Masse; Hülse (Sleeve): Audioausgang. + +|3 +| Audio-Eingangspegel +| Passe den Audio-Eingangspegel mit Hilfe dieses Trimmers an. Für die Pegelanpassung gibt es eine spezielle Funktion, siehe Abschnitt <> am Ende dieses Handbuchs. + +|4 +|3,5-mm-Klinkenbuchse (3 Pole): Kopfhörer +|Schließe hier deine Kopfhörer an (alle Stereokopfhörer von Mobiltelefonen mit Klinkenbuchsen sollten geeignet sein), um über Kopfhörer zu hören und den Lautsprecher auszuschalten. Man kann keinen Lautsprecher direkt an diese Buchse anschließen, ohne zusätzliches Interface (dieser Ausgang benötigt eine Gleichstromverbindung mit Masse über 50 - 300 Ohm.) + +|5 +|KH-Pegel Trimmer |Wird dazu benutzt, den Pegel des Kopfhörers auf ein komfortables Niveau zu bringen. Nicht vorhanden bei der ersten Auflage des M32. + +|6 +|Ein-/Aus-Schalter +|Verbindet den LiPo-Akku mit dem Gerät, bzw. trennt ihn davon. Bei häufigem Gebrauch des Morserino-32 kann man den Akku angeschlossen lassen. Wenn man aber das Gerät mehrere Tage nicht benutzt, sollte es mit diesem Schalter vom Akku getrennt werden, da dieser sonst langsam entladen wird. + +|7 +|SMA-Antennenbuchse +|Schließ eine für die Betriebsfrequenz geeignete Antenne für den Funkbetrieb mit LoRa an (Standard ist ca. 433 MHz, aber es sind auch Module für 860-925 MHz erhältlich). Man darf nicht ohne Antenne senden! + +|8 +|ROT (Power/Vol/Scroll) Taste +|Wenn das Gerät in den Tiefschlaf gegangen ist, wacht es auf und startet den Morserino neu. +Wenn das Gerät in Betrieb ist (d.h. einer der Modi gerade aktiviert ist), wird durch kurzes Drücken dieser Taste die Funktion des Drehgebers zwischen der Einstellung der Geschwindigkeit und dem Lautstärkeregler umgeschaltet. +Ein langer Druck auf die Taste ermöglicht es, die Anzeige mit dem Drehgeber zu scrollen, ein erneuter Druck auf die Taste schaltet die Funktion wieder auf Geschwindigkeitsregelung um. +Doppelklick dieser Taste reduziert die Displayhelligkeit. +Befindet man sich im Menü, wird durch langes Drücken dieser Taste die Funktion zum Einstellen des Audioeingangspegels aktiviert. Weitere Informationen dazu im Abschnitt <<>> unten. + +|9 +|SCHWARZER Drehknopf +|Dient zur Auswahl innerhalb von Menüs, zur Einstellung von Geschwindigkeit oder Lautstärke, oder zum Scrollen der Anzeige, sowie zur Einstellung verschiedener Parameter und Optionen. +Kann gedreht werden und ist auch ein Drucktastenschalter. Weitere Informationen dazu im Abschnitt <<>> unten. + +|10 +|Anschlüsse für Touchpaddel +|Diese Leiterplattensteckverbinder nehmen die kapazitiven Touchpaddel auf. +Wenn du nur ein externes Paddel verwendest (bzw. auch für den Transport), können die Touchpaddel entfernt werden. + + +|11 +|Serielle Schnittstelle +|Man kann ein Kabel (direkt angelötet oder über einen 4-poligen Steckverbinder) an ein externes serielles Gerät, z.B. ein GPS-Empfängermodul, anschließen (dies wird derzeit von der Software nicht unterstützt, ist aber nicht sehr schwer zu realisieren). Die 4 Pole sind T (Transmit), R (Receive), + und - (3,3V Stromversorgung vom Heltec-Modul). + +|12 +|3,5 mm Klinkenstecker (3-polig): Externes Paddel +|Verwende diesen Anschluss, um entweder ein externes (mechanisches) Paddel anzuschließen (Spitze ist linkes Paddel, Ring ist rechtes Paddel, Hülse ist Masse), +oder eine einfache Morsetaste (Spitze ist die Taste). + + + +|13 +|Reset-Taste +|Durch ein kleines Loch erreicht man den Reset-Taster des Heltec-Moduls (selten benötigt). + +|14 +|USB +|Verwende ein normales 5V USB-Ladegerät, um das Gerät mit Strom zu versorgen und den LiPo-Akku aufzuladen. Die Mikrocontroller-Firmware kann auch über USB neu programmiert werden (über die Programmierumgebung auf einem PC, oder mithilfe eines speziellen Update-Hilfsprogramms (siehe <>); eine andere Möglichkeit ist es, die Morserino-32-Firmware über eine WLAN-Verbindung zu aktualisieren). + +Man kann auch die durch den Keyer oder Decoder erzeugten Zeichen auf dem seriellen Anschluss ausgeben lassen, um sie etwa in einem externen Computerprogramm zu verarbeiten; siehe dazu die Beschreibung des Parameters "Serial Output". + +|15 +|PRG-Taste +|Durch ein kleines Loch erreicht man den Programmiertaster des Heltec-Moduls (normalerweise nicht benötigt). +|=== + +== Kurzanleitung zur Benutzung des M32 + +(Als Hilfe für die Ungeduldigen; ersetzt aber nicht das Lesen des kompletten Handbuchs!) + +==== Zu verwendende Steuerelemente: +* EIN / AUS-Schalter (Batterieschalter): Schiebeschalter auf der Rückseite in der Nähe des Lautsprechers. Verbindet / trennt die Batterie. +* SCHWARZ: Der schwarze Knopf (Encoder), kann gedreht und gedrückt werden. +* ROT: Der rote Schaltknopf. + + +=== So schaltet man den M32 ein +Schließe entweder ein USB-Netzteil an oder schalte den Batterieschalter auf ON (I), wenn du einen Akku installiert hast. + +Kurz wird ein Startbildschirm mit der Firmware-Version und dem Batteriestatus angezeigt, dann befindest du dich im Hauptmenü („Select Modus:“), außer der Quick Start Parameter ist aktiviert, dann wird der zuletzt ausgewählte Modus automatisch gestartet. + +Wenn der M32 eingeschaltet ist, sich die Anzeige am Display jedoch über einen längeren Zeitraum nicht ändert, wechselt der M32 in den Ruhezustand. Du kannst ihn reaktivieren, indem du auf ROT klickst. + +=== So wählt man einen Modus aus (= eine der Funktionen des M32) +Drehe SCHWARZ, um die gewünschte Funktion zu finden. Klicke auf SCHWARZ, um die Funktion zu wählen oder die nächstniedrige Menüebene auszuwählen . Drücke länger auf SCHWARZ, um eine Funktion zu verlassen / nach oben zu gehen. + +=== So ändert man die Geschwindigkeit oder Lautstärke, und so scrollt man die Anzeige +Dies geschieht mit SCHWARZ und ROT, wenn man sich in einer der Modi (Funktionen) befindet (funktioniert nicht, während man sich im Menü befindet): + +* Geschwindigkeit ändern: SCHWARZ drehen. +* Lautstärke ändern: Klicke auf ROT, drehe SCHWARZ, um die Lautstärke anzupassen, und klicke erneut auf ROT, um zur Geschwindigkeitseinstellung zurückzukehren. +* Bildlaufanzeige: Langes Drücken von ROT, Scrollen mit SCHWARZ vor und zurück, Beenden mit ROTEM Klick. + +=== So ändert man die Helligkeit des Displays +Es gibt 5 Helligkeitsstufen. Jeder Doppelklick der ROTEN Taste reduziert die Helligkeit ein wenig; wenn die niedrigste Helligkeitsstufe erreicht ist, wird mit dem Doppelklick wieder die volle Displayhelligkeit eingestellt. + +=== So ändert man Parameter (Einstellungen): +Doppelklicke auf SCHWARZ, drehe SCHWARZ, um den Parameter auszuwählen, den du ändern möchtest. Langes Drücken von SCHWARZ, um das Parametermenü zu verlassen. + +(Wenn eine Funktion aktiv ist, werden nur die relevanten Parameter für diese Funktion angezeigt. Wenn Sie über ein Menü aufgerufen werden, werden alle Parameter angezeigt.) + +Es gibt zahlreiche Parameter. Lies das Handbuch, um herauszufinden, wofür sie bestimmt sind. + +Man kann Parameter auch in sogenannten „Snapshots“ abspeichern und wieder abrufen. + +=== Verwendung externer Paddles und Tasten +Man kann externe Paddles (Doppelhebel oder Einhebel) oder Handtasten (normal oder "Sideswiper") mittels des 3,5-mm-Anschlusses für externe Tasten (12) anschließen. + +Um eine Handtaste zu verwenden, kann man entweder den CW-Decoder-Modus benutzen, ohne irgendwelche Parameter zu ändern (dieser Modus decodiert Morse, das entweder über den Audio-I/O-Anschluss oder von der Taste kommt). Wenn man die Echo Trainer-Funktion oder eine der Transceiver-Funktionen mit einer Handtaste verwenden möchte, muss man den Parameter "Keyer Mode" auf "Straight Key" ändern (bitte beachte, dass die Funktion "CW Keyer" nicht funktioniert, wenn der Keyer-Modus auf Straight Key eingestellt ist - mit einer Handtaste bist du der Keyer, nicht der Morserino!). + +TIP: Du kannst die eingebauten kapazitiven Paddles wie einen Sideswiper (Cootie Key) verwenden, wenn der Keyer-Modus Straight Key ist! + +=== So lädt man den Akku auf +Schließe die USB-Stromversorgung an, schalte den Batterieschalter auf ON (I). Die orangefarbene LED leuchtet sehr hell. Wenn die orangefarbene LED dunkel ist, ist der Akku vollständig aufgeladen. leuchtet die orange LED schwach (oder flackert), ist der Akku nicht angeschlossen / nicht eingeschaltet. + + +== Benutzung des M32, Schritt für Schritt + +=== Ein- und Ausschalten / Aufladen des Akkus [[power]] + + +Wenn du das Gerät mit einer USB-Stromversorgung betreiben möchtest, schließe es einfach mit einem Micro-USB-Kabel an ein beliebiges USB-Ladegerät an (es verbraucht max. 200 mA, also reicht jedes 5V-Ladegerät). + +Wenn du den Morserino mit dem Akku als Stromquelle betreiben möchtest, schiebe den Schiebeschalter in die Position ON. + +Wenn das Gerät ausgeschaltet ist, aber die Batterie angeschlossen ist (Schiebeschalter ist eingeschaltet), befindet es sich in Wirklichkeit im Tiefschlaf: Fast alle Funktionen des Mikrocontrollers sind ausgeschaltet, und der Stromverbrauch ist minimal (weniger als 5% des normalen Betriebs). + +Um das Gerät aus dem Tiefschlaf einzuschalten, drücke einfach die ROTE Taste (Power/Vol/Scroll) kurz. + +Wenn der Morserino-32 hochfährt, siehst du für ein paar Sekunden einen Startbildschirm. +In der oberen Zeile wird angezeigt, für welche LoRa-Frequenz die M32 konfiguriert ist (als 5-stellige Zahl), und +unten im Display wird gezeigt, wie viel Akkuladung noch übrig ist. +bevor der Akku ganz leer ist, solltest du das Gerät an eine USB-Stromversorgung anschließen. +(Der Akku wird auch dann entladen, wenn du das Gerät nie einschaltest - obwohl dies in seinem Tiefschlafzustand eher minimal ist, +ist ein voller Akku dennoch nach ein paar Tagen leer. +Wenn de den Morserino also für längere Zeit nicht benutzen willst, trenne mit dem Schiebeschalter auf der Rückseite den Akku vom Gerät ....) + +WARNING: Wenn die Batteriespannung beim Einschalten gefährlich niedrig ist, erscheint ein leeres Batteriesymbol auf dem Display und das Gerät weigert sich, hochzufahren. +In diesem Fall solltest du so schnell wie möglich mit dem Laden des Akkus beginnen. + +TIP: Nur für M32 der ersten Generation: Nach dem Benutzen einer WLAN Funktion funktioniert das Messen der Batteriespannung nicht mehr, bis das Gerät komplett ein- und wieder ausgeschaltet wurde, oder ein Reset mit dem Reset-Button durchgeführt wurde. Der Grund ist ein Hardware Designfehler des Heltec Boards V2.0. In solchen Fällen zeigt der Morserino nun "Unknown" anstelle der Batteriespannung an, und das Batteriesymbol ist mit einem Fragezeichen überschrieben. Nach dem Ein- und Ausschalten sollte alles wieder normal funktionieren. + +TIP: Falls leere Batterie angezeigt wird, aber eigentlich noch genügend Spannung vorhanden sein müsste, ist es ratsam, eine Kalibrierung der Batteriemessung durchzuführen. Siehe dazu <>. + +Um das Gerät von der Batterie zu trennen (auszuschalten), es sei denn, es ist USB-versorgt, schiebe den Schiebeschalter in die Position OFF. + +Um das Gerät in den Tiefschlaf zu versetzen, gibt es zwei Möglichkeiten: + +*Im Hauptmenü die Option "Go To Sleep" zu wählen. +*Nichts zu tun - wenn im Parametermenü ein "Time Out"-Wert eingestellt wurde. Wenn es keine Aktualisierung der Anzeige gibt, schaltet sich das Gerät selbst aus und geht nach Ablauf der dort eingestellten Zeit in den Tiefschlaf. + +**Um den Akku** aufzuladen, verbinde ihn mit einem USB-Kabel mit einer zuverlässigen USB-5V-Stromquelle, wie z.B. einem Computer, oder einem USB-Ladegerät, wie z.B. einem Telefonladegerät. + +WARNING: Vergewissere dich, dass der Hardware-Schalter des Geräts während des Ladevorgangs auf *ON* steht - wenn du den Akku über den Schalter trennst, +kann er nicht geladen werden. + +Während des Ladevorgangs leuchtet die orangefarbene LED am ESP32-Modul hell auf. +Wenn der Akku abgeklemmt ist, leuchtet diese LED nicht hell, sondern blinkt nervös oder mit halber Intensität. + +Sobald der Akku vollständig geladen ist, leuchtet die orangefarbene LED nicht mehr. + +Man kann das Gerät natürlich immer verwenden, wenn es über USB mit Strom versorgt wird, ob der Akku geladen wird oder nicht. + +[WARNING] +==== +Um eine Tiefentladung des LiPo-Akkus zu vermeiden, schalte den Morserino-32 immer über den Schiebeschalter aus. Lass es nicht über einen längeren Zeitraum im "Schlafmodus" (bis zu einem Tag oder vielleicht zwei Tage sind OK, wenn es gut aufgeladen war; ein voll aufgeladener 600 mAh-Akku wird im Tiefschlaf innerhalb von 3 bis 4 Tagen auf ein Niveau von etwa 3,2 V entladen). + +Das Heltec-Modul hat eine Elektronik zum Laden des Akkus an Bord und verhindert eine Überladung sehr gut. Aber es hat keine Verhinderung von Tiefentladung! **Eine Tiefentladung führt zu einer verminderten Akkukapazität und schließlich zum vorzeitigen Tod der Batterie!** +==== + +=== Einstellung des LoRa-Bandes und der Frequenz + +Die Standardversion des Morserino-32 verfügt über eine vorkonfigurierte Frequenz innerhalb des 433 MHz Amateur- und ISM-Bandes (ISM nur in ITU-Region 1). **Wenn das deinen Anforderungen entspricht, musst du jetzt nichts weiter tun.** + +Wenn die Nutzung dieser Frequenz in deiner Region nicht erlaubt ist, muss man eine Version des Heltec Moduls (Version 2.0 für die erste Edition des Morserino, V.2.1 für die zweite Edition) kaufen, welche die LoRa-Bänder zwischen 860 und 925 MHz unterstützt. In diesem Fall muss man das richtige Band und die richtige Frequenz konfigurieren, bevor man die LoRa-Funktionalität des M32 nutzen kann. + +[WARNING] +==== +Bitte beachte, dass man eine spezielle Version des Heltec Moduls für die Nutzung des 868- oder 920-MHz-Bandes benötigt. +Die "Standard"-Version unterstützt nur das 433 MHz-Band, und die alternative Version unterstützt nur das 868er und 920er Band! + +Wenn du derzeit einen Standard M32 hast und die höheren Frequenzbänder verwenden möchtest, kannst du ein Heltec-Modul (plus Antenne) für diese Bänder bestellen. +**Nach dem Austausch des Heltec-Moduls muss vor der Verwendung von LoRa der LoRa-Setup für das gewünschte Band durchgeführt werden!**. +==== + +**Siehe <<> am Ende dieses Dokuments**, um zu erfahren, wie man LoRa für Module konfigurieren kann, welche die Bänder 868 und 929 MHz unterstützen, und wie du die LoRa-Frequenzeinstellungen ändern kannst. + + +=== Verwendung des SCHWARZEN Knopfs und der ROTEN Taste [[tasten]] +Die Auswahl der verschiedenen Modi und die Einstellung aller möglichen Parameter erfolgt mit dem **Drehgeber** und seinem **SCHWARZEN Knopf**. + +*Durch Drehen* kann man mit dem Drehgeber durch die Optionen oder Werte, **durch einmaliges Klicken** mit dem Knopf wird eine Option oder ein Wert ausgewählt, +oder bringt dich zur nächsten Ebene des Menüs (es gibt bis zu drei Ebenen im Menü). + +Ein **Doppelklick** auf den SCHWARZEN Knopf führt zum Menü der Parametereinstellung. Tust du dies innerhalb des Menüs, können alle Parameter geändert werden; +innerhalb eines aktiven Modus können nur die Parameter geändert werden, die für den aktuellen Modus relevant sind. + +Ein **langes Drücken** führt von jedem der Modi zurück zum Menü, und innerhalb des Menüs um eine Stufe nach oben. + +Wenn man sich im Menü befindet (z.B. sofort nach dem Einschalten), startet ein **langes Drücken** der **ROTEN Taste** eine Funktion +um den Audioeingangspegel (und eventuell den Ausgangspegel eines Geräts, das an den Line-Out-Anschluss des Morserino-32 angeschlossen ist) einzustellen. +Siehe <<> am Ende dieses Dokuments. + +Hat man das Menü verlassen, um einen der Modi (Keyer, Generator, Echo-Trainer usw.) auszuführen, +kann man mit der **ROTEN (Power/Vol/Scroll) Taste** schnell zwischen **Geschwindigkeitsregelung** und **Lautstärkeregelung** mit einem **einfachen Klick** umschalten. + +Durch einen **Doppelklick** der **Roten Taste** wird die Helligkeit des Displays reduziert. Es gibt 5 Helligkeitsstufen. Wenn die niedrigste Helligkeitsstufe erreicht ist, wird mit dem Doppelklick wieder die volle Displayhelligkeit eingestellt. + +Durch einen **langen Druck** der **ROTEN** Taste während ein Modus aktiv ist (d.h. wenn das Menü nicht angezeigt wird) wechselt die Anzeige und der Drehgeber in den **Scroll-Modus** (die Anzeige hat einen Puffer von 15 Zeilen, und normalerweise sind nur die unteren drei Zeilen sichtbar; im Scroll-Modus kann man zu den vorherigen Zeilen zurückblättern; während man im Scroll-Modus ist, wird ein **Scrollbalken** ganz rechts auf der Anzeige sichtbar, der ungefähr anzeigt, wo man sich innerhalb der 15 Zeilen des Textpuffers befindet). Mit einem **erneuten Klick** auf *ROT* wird der Scroll-Modus verlassen, und der Drehgeber dient wieder der Geschwindigkeitsregelung. + +Wenn man sich im Menü zur Parametereinstellung befindet, wird mit einem kurzen Klick auf die **ROTE** Taste eine Funktion zum Laden eines Parameter-Snapshots aktiviert, und mit einem langen Druck auf die **ROTE** Taste kann man einen Parameter-Snapshot abspeichern. +Siehe den Abschnitt <> für weitere Details. + +=== Das Display + +Die Anzeige ist in zwei Hauptabschnitte unterteilt: oben ist die Statuszeile, die wichtige Informationen über den aktuellen Zustand des Gerätes liefert, und unten ist ein **Bereich von drei Scrollzeilen**, in dem die erzeugten Morsecode Zeichen im Klartext angezeigt werden. Alle Zeichen werden zur besseren Lesbarkeit in Kleinbuchstaben dargestellt; Betriebsabkürzungen (Pro Signs) werden als Buchstaben in Klammern dargestellt, wie `` oder ``. Darüber hinaus wird im Echo-Trainer-Modus (siehe unten) das Ergebnis als "ERR" oder "OK" angezeigt (zusammen mit einigen akustischen Signalen). + +Obwohl nur drei Zeilen Lauftext angezeigt werden, gibt es intern einen Puffer von 15 Zeilen -- nach langem Drücken der ROTEN (Vol/Scroll) Taste kann man mit dem Drehgeber zurück scrollen und die vorherigen Zeilen wieder sichtbar machen. +Dies funktioniert, während man sich in einem der Modi befindet und die Ausgabe auf dem Display erfolgt - nichts geht verloren und die Anzeige kehrt zum normalen Verhalten zurück, sobald man den Scrollmodus verlässt. + +==== Die Statuszeile + +Während ein Menü (entweder das Startmenü oder ein Menü zur Auswahl von Einstellungen) angezeigt wird, zeigt die Statuszeile, was zu tun ist (**Select Modus** oder **Set Preferences:**). + +Wenn man sich im Keyer Modus, CW Generator Modus oder Echo Trainer Modus befindet, zeigt die Statuszeile folgendes an, von links nach rechts: + + +* **A**,**B**, **U**, **N** oder **S**, was den (automatischen) **Keyermodus** anzeigt: Iambic **A**, Iambic **B**, **U**ltimatic, **N**on-Squeeze oder **S**traight Key (Handtaste; für Details zu diesen Modi siehe unten im Abschnitt <>). + +* Die aktuell eingestellte **Geschwindigkeit** in Worten pro Minute (das Bezugswort ist das Wort PARIS, was auch bedeutet, dass 1 wpm 5 Zeichen pro Minute entspricht), +im CW Keyer Modus als **nn**WpM, im CW Generator oder Echo Trainer Modus als (nn)**nn**WpM. Der Wert in Klammern zeigt die effektive Geschwindigkeit, die sich unterscheidet, wenn der Zwischenwortabstand oder der Zwischenzeichenabstand auf andere als die durch die Norm definierten Werte eingestellt wird (Länge von 3 dits für den Zwischenzeichenabstand und Länge von 7 dits für den Zwischenwortabstand). Beachte die Hinweise im Abschnitt <> zu den Parametern, die man im CW-Generator-Modus einstellen kann. ++ +Im Transceiver-Modus sieht man auch zwei Werte für die Geschwindigkeit -- der eine in Klammern ist die Geschwindigkeit des empfangenen Signals, der andere die Geschwindigkeit deines Keyers. ++ +Verwendet man die Handtaste, wird die aktuell ermittelte Gegegeschwindigkeit angezeigt. + +Wenn die Ziffern, die die Geschwindigkeit anzeigen, als **fett** angezeigt werden, ändert das Drehen des Drehgebers die Geschwindigkeit. Wenn sie in normalen Zeichen dargestellt werden, ändert das Drehen des Drehgebers die Lautstärke. +* Ein horizontaler Balken, der sich von links nach rechts erstreckt, zeigt die **Lautstärke** des vom Gerät erzeugten Mithörtons an (volle Länge des Balkens bedeutet höchste Lautstärke). Dies zeigt normalerweise einen weißen Rahmen um den schwarzen Fortschrittsbalken (eine Verlängerung der restlichen Statuszeile); wenn dieser umgekehrt ist (weißer Fortschrittsbalken in schwarzer Umgebung -- und die WpM-Ziffern sind nicht fett gedruckt), ändert das Drehen des Drehgebers die Lautstärke und nicht die Geschwindigkeit. +* Am ganz rechten Ende der Statuszeile befindet sich eine Anzeige (mit konzentrischen Halbkreisen), die die Funkübertragung symbolisiert, wenn der **LoRa**-Modus aktiv ist (wenn sich das Morserino-32 im LoRa-Transceiver-Modus befindet oder du den Parameter zum Übertragen von LoRa in einem der CW-Generatormodi eingestellt hast). + +== Das Top-Menü und die Morserino-Modi + +Man wählt den Modus des Morserino-32, indem man den schwarzen Drehgeberknopf drehet und diesen kurz drückt ("anklickt"), um die gewählte Funktion auszuwählen (oder um in einigen Fällen ein Untermenü für eine detailliertere Auswahl anzuzeigen). + + +=== CW Keyer [[keyer]] + +Dies ist ein automatischer Keyer, der Iambic A, Iambic B (diese werden manchmal auch als Curtis A und Curtis B bezeichnet) und Ultimatic Mode unterstützt, +sowie den Non-Squeeze-Modus (Nachahmung einer Einhebel-Taste mit einem Zweihebelpaddel). +Man kann entweder das eingebaute kapazitive Paddel verwenden oder ein externes Paddel (Dual- oder Einhebelpaddel) anschließen. +Interne und externe Paddel arbeiten parallel, so dass eine Konfiguration nicht erforderlich ist. + +Es gibt eine Reihe von **Parametern**, die bestimmen, wie der automatische Keyer funktioniert. +Siehe Abschnitt <<>> für Details. Die folgenden Parameter sind hier besonders wichtig: + + +`External Pol.`: Wenn die externe Taste "verkehrt herum" verdrahtet ist, kann man dies hier korrigieren. + +`Paddle-Polarity`: Auf welcher Seite willst du die Dits und auf welcher Seite die Dahs? + +`Keyer-Modus`: Wähle Iambic A oder B, Ultimatic-Modus, Non-Squeeze-Modus und Straight Key-Modus. + +Was sind diese **Iambischen Modi**? + +Wenn man beide Paddel eines iambischen Keyers drückt, werden alternativ Dahs und Dits erzeugt, solange beide Paddel gedrückt werden, +beginnend mit dem, welches zuerst berührt wurde (die Bezeichnung "iambisch" kommt übrigens daher, dass es in einem iambischen Vers abwechselnd +kurze und lange Silben gibt; der Name "Curtis" hingegen stammt vom Entwickler des bahnbrechenden Curtis Morse Keyer Chips, +John G. "Jack" Curtis, K6KU, ex W3NSJ). + +Der Unterschied zwischen den Modi A und B besteht im Verhalten, wenn beide Paddel beim Erzeugen des aktuellen Elements freigegeben werden: +bei Iambic A stoppt der Keyer nach dem aktuellen Element, bei Iambic B fügt der Keyer ein weiteres Element hinzu, demjenigen entgegengesetzt, bei dem +das Paddel losgelassen wurde. + +Mit anderen Worten, im Curtis B-Modus wird das gegenüberliegende Paddel überprüft, während das aktuelle Element (dit oder dah) ausgegeben wird, +und wenn während dieser Zeit ein Paddel gedrückt wird, wird dem aktuellen Element ein weiteres entgegengesetztes Element hinzugefügt. +Im Modus A ist dies nicht der Fall. Da der Modus B etwas schwierig zu bedienen ist, wurde dies später so geändert, dass erst nach einem bestimmten Prozentsatz +der Dauer des Elements die Paddel überprüft werden. Dies ist der Prozentsatz, den man mit den Parametern `CurtisB DahT%` und `CurtisB DitT%` einstellen kann. + +Wenn man sie auf 0, den niedrigsten Wert, einstellt, ist der Modus identisch mit dem ursprünglichen Curtis B-Modus; +Der später entwickelte "verbesserte" Curtis B-Modus verwendet einen Prozentsatz von etwa 35%-40%. +Stellt man den Prozentsatz auf 100, den höchsten Wert, ein, ist das Verhalten das gleiche wie im Curtis A-Modus. + +Mit diesem Parameter kann man daher jedes Verhalten zwischen Curtis A und dem ursprünglichen Curtis B auf einer kontinuierlichen Skala einstellen, +und man kann den Prozentsatz für Dits und Dahs separat einstellen (das macht Sinn, da das Timing für Dits nur ein Drittel desjenigen für Dahs beträgt, +und so könnte es sein, dass du bei diesen einen höheren Prozentsatz willst, damit die Eingabe von Dits komfortabel ist). + +**Ultimatic Mode**: Wenn man im Ultimatic-Modus beide Paddel gedrückt hält, wird ein dit oder ein dah erzeugt, +je nachdem, welches Paddel man zuerst berührte, und danach wird das entgegengesetzte Element kontinuierlich erzeugt. +Dies ist von Vorteil für Zeichen wie j, b, 1, 2, 6, 7. + +**Non-Squeeze Mode**: Dies "simuliert" das Verhalten eines Einhebelpaddels bei Verwendung eines Zweihebelpaddels. +Leute, die mit Einhebelpaddeln vertraut sind, haben in der Regel Schwierigkeiten bei der Verwendung von Zweihebelpaddeln, da sie die Paddel manchmal versehentlich zusammendrücken, +besonders bei höheren Geschwindigkeiten. Der Non-Squeeze-Modus ignoriert das Zusammendrücken einfach, was es für diese Leute einfacher macht, ein Doppelhebelpaddel zu verwenden. + +TIP: Iambic-Modi und Ultimatic-Modus können nur mit dem eingebauten Touchpaddel oder einem externen Doppelhebelpaddel verwendet werden; die Auswahl dieser Modi ist irrelevant, wenn man ein externes Einhebelpaddel verwendet. + + +Der Parameter **`Latency`** legt fest, wie lange nach dem Erzeugen des aktuellen Elements (Punkt oder Strich) die Paddel "taub" sind. +In frühen Firmware-Versionen war dies 0, mit dem Effekt, dass man gerade bei höheren Geschwindigkeiten mehr Punkte erzeugte als gewollt, +da man das Paddel loslassen musste, während der letzte Punkt noch "an" war. Nun kann man diesen Wert auf einen Wert zwischen 0 und 7 einstellen, +was 0/8 bis 7/8 einer Punktlänge bedeutet (Defaultwert ist 4, d.h. eine halbe Punktlänge). Wenn man immer noch dazu neigt, unerwünschte Dits zu erzeugen, kann man diesen Wert erhöhen. + +Für den Parameter `AutoChar Spce` (Definition einer Mindestlänge für den Abstand zwischen den Zeichen) siehe den Abschnitt <> für Details. + +**Straight Key Mode**: Das ist natürlich kein automatischer Modus, sondern man kann damit den Morserino-32 auch mit einer normalen Handtaste ("Klopftaste") verwenden. Der Modus "CW Keyer" funktioniert nicht, wenn dieser Parameter gesetzt ist, aber man kann die Handtaste für den Echo Trainer und für die Transceiver Modi verwenden! + + +=== CW Generator [[generator]] + +Dieser erzeugt entweder zufällige Gruppen von Zeichen und Wörtern für das CW-Training oder spielt den Inhalt einer Textdatei als Morsezeichen ab. Man kann eine Reihe von Optionen einstellen, indem man die entsprechenden Parameter auswählt (siehe den Abschnitt über <> weiter unten). + +Man **startet** und **stoppt** den CW-Generator, indem man kurz ein Paddel (entweder einseitig oder beidseitig) berührt, oder **durch Klicken auf den SCHWARZEN Knopf** (bei Benutzung einer Handtaste kann man auch diese zum Starten und Stoppen verwenden). + +Zu Beginn kündigt der CW Generator seine Aktivität durch ""`vvv``" an (`+..._ ..._ ..._ ..._ _._._+`), bevor er tatsächlich beginnt, Gruppen oder Wörter zu erzeugen. + +Wenn man den Parameter 'Stop/Next/Rep' aktiviert, wird nur ein Wort oder eine Gruppe von Zeichen abgespielt. Anschließend stoppt der Morserino und wartet auf die Paddeleingabe. Durch Drücken des linken Paddels wird das aktuelle Wort wiederholt, während durch Drücken des rechten Paddels das nächste Wort generiert wird. Dies ist nützlich, um das Gehörlesen zu trainieren: Spiel ein Wort ab (ohne auf den Bildschirm zu schauen) und versuche, es im Kopf zu dekodieren. Bist du nicht sicher, drücke zur Wiederholung das linke Paddle. Glaubst du, es richtig verstanden zu haben, vergewissere dich mit der Anzeige am Display. Jetzt kannst du entweder dieses Wort noch einmal wiederholen (linkes Paddle drücken) oder wegschauen und das rechte Paddel für das nächste Wort drücken. (Man kann sich an die Funktionen des linken und rechten Paddels erinnern, indem man an typische Musik-Player-Tasten denkt - links ist zurück, rechts ist vorwärts.) Bitte beachte, dass die Optionen Word Doubler und Stop/Next/Repeat nicht miteinander kompatibel sind - stellt man das eine auf ON, wird das andere automatisch auf OFF gesetzt. + +Normalerweise erzeugt der Morserino-32 einfach weiter Morsezeichen, bis man ihn manuell anhält, +aber es kann auch ein Parameter eingestellt werden, der die Ausgabe nach einer bestimmten Anzahl von Wörtern (oder Buchstabengruppen) pausieren lässt. +Siehe `Max # of Words` im Abschnitt <>. + +**Weitere wichtige Parameter** für den CW-Generator sind: + +`Intercharacter Space` Hier wird beschrieben, wie viel Abstand zwischen den Zeichen eingefügt wird. Die "Norm" ist ein Abstand mit der Länge von drei Dits. Um das Mitlesen bei hoher Geschwindigkeit zu erleichtern und als eine gute Methode, um Morsezeichen zu lernen, kann dieser Abstand erweitert werden. Die Morsezeichen sollten mit ziemlich hoher Geschwindigkeit ( > 18 wpm) gesendet werden, um es unmöglich zu machen, Dits und Dahs zu "zählen", so dass man besser den "Rhythmus" jedes Zeichens lernt. Im Allgemeinen ist es besser, den Abstand zwischen den Wörtern zu vergrößern und nicht so sehr den Abstand zwischen den Zeichen; daher wird empfohlen, diesen Wert zwischen 3 und max. 6 einzustellen. Siehe unten. + +`Interword Space`. Normalerweise ist dies definiert als die Länge von 7 dits. Im CW Keyer Modus bestimmen wir nach einer Pause von 6 dits ein neues Wort, um zu vermeiden, dass Text auf dem Display ohne Leerzeichen zwischen den Wörtern erscheint. Im CW Trainer Modus kann man den Abstand zwischen Wörtern auf Werte zwischen 6 und 45 einstellen (was mehr als das 6-fache des normalen Abstands ist), um das Gehörlesen bei hohen Geschwindigkeiten zu erleichtern. In Analogie zu "Farnsworth Spacing" (siehe unten) wird dies auch als "Wordsworth Spacing" bezeichnet. Dies ist die beste Methode, das Gehörlesen bei hohen Geschwindigkeiten zu erlernen. Natürlich kann man die Verlängerung des Zeichenabstands mit der des Wortabstands kombinieren. + +Da der Zeichenabstand unabhängig vom Wortabstand eingestellt werden kann, würde dies bedeuten, dass man den Zeichenabstand höher einstellen könnte als den Wortabstand, was ziemlich verwirrend wäre. Um diese Verwirrung zu vermeiden, wird der Wortabstand immer mindestens um 4 dit Längen größer sein als der Zeichenabstand, auch wenn ein kleinerer Wortabstand gesetzt wurde. + +Die ARRL und einige Morsetrainingsprogramme verwenden etwas, das sie **"Farnsworth Spacing"** nennen: Hier werden die Abstände zwischen den Zeichen und zwischen den Wörtern um einen bestimmten Faktor proportional verlängert. Man kann Farnsworth Spacing emulieren, indem man sowohl den Buchstaben- als auch den Wort-Abstand erhöht, und z.B. den Abstand zwischen den Zeichen auf 6 und den Wortabstand auf 14 setzt und so alle Abstände zwischen Zeichen und Wörtern effektiv verdoppelt. Tut man dies mit einer Zeichengeschwindigkeit von 20 WpM, beträgt die resultierende effektive Geschwindigkeit 14 WpM. Dies wird in der Statuszeile als (14)**20**WpM angezeigt. + +`Random Groups`: Definiert, welche Zeichen in den zufälligen Zeichengruppen enthalten sein sollen. Man kann wählen zwischen Alpha (Buchstaben) / Numerals (Ziffern) / Interpunct. (Satzzeichen)/ Pro Signs (Betriebsabkürzungen)/ Alpha + Num / Num+Interp. / Interp+ProSn / Alpha+Num+Int / Num+Int+ProS / All Chars (alle Zeichen). + +`Length Rnd Gr`: Definiert, wie viele Zeichen es in einer zufälligen Gruppe geben soll. Man kann entweder eine feste Länge (1 bis 6) wählen, oder eine zufällig gewählte Länge zwischen 2 bis 3 und 2 bis 6 (innerhalb dieser Grenzen zufällig gewählte Länge). + +`Length Calls`: : Die Länge der Rufzeichen, die generiert werden. Wähle einen Wert zwischen 3 und 6 oder Unlimited (unbegrenzt). + +`Length Abbrev` und `Length Words`: Die Länge der gebräuchlichen CW-Abkürzungen bzw. gebräuchlichen englischen Wörter, die generiert werden. Wähle zwischen 2 und 6 oder Unlimited (unbegrenzt). + +`Each Word 2x`: Jedes "Wort" (Zeichen zwischen Leerzeichen) wird zweimal ausgegeben, um das Gehörlesen zu unterstützen (ON). Falls ein vergrößerter Abstand zwischen den Zeichen gewählt wurde ("Farnsworth Spacing"), kann die Wiederholung auch mit geringerem Abstand (ON less ICS) oder ohne Farnsworth Spacing (ON true WpM)erzeugt werden. + +Für die weniger häufig verwendeten Parameter `Key ext TX`, `CW Gen Displ` und `Send via LoRa` siehe den Abschnitt <>. + + +==== Was kann generiert werden? + +Auf der zweiten Ebene des Menüs kann man zwischen den folgenden Optionen wählen: + +* **Random**: Erzeugt Gruppen von zufälligen Zeichen. Die Länge der Gruppen sowie die Wahl der Zeichen kann in den Parametern durch Doppelklick auf den schwarzen Drehknopf ausgewählt werden (siehe den Abschnitt <> für nähere Details). +* **CW Abbrevs**: Zufällige Abkürzungen, die im CW-Funkverkehr sehr häufig vorkommen (durch eine Parametereinstellung kann man die maximale Länge der zu trainierenden Abkürzungen wählen). +* **English Words**: Zufällige Wörter aus einer Liste der 370 häufigsten Wörter der englischen Sprache (wiederum kann man über einen Parameter eine maximale Länge einstellen). +* **Call Signs**: Erzeugt zufällige Zeichenketten, die die Struktur und das Aussehen von Amateurfunk-Rufzeichen haben (dies sind keine echten Rufzeichen, und es werden auch welche erzeugt, die in der realen Welt nicht existieren könnten, da entweder das Präfix nicht verwendet wird oder die Verwaltung eines Landes bestimmte Suffixe nicht zuteilen würde). Die maximale Länge kann über einen Parameter eingestellt werden. +* **Mixed**: Wählt zufällig aus den bisherigen Möglichkeiten (zufällige Zeichengruppen, Abkürzungen, englische Wörter und Rufzeichen). +* **File Player**: Spielt den Inhalt einer Datei, die auf den Morserino-32 hochgeladen wurde, im Morse-Code ab. +Derzeit kann der Morserino nur eine Datei enthalten, sobald man eine neue hochlädt, wird die alte überschrieben. +Der Upload funktioniert über WLAN von einem PC (oder Mac oder Tablett oder Smartphone oder was auch immer - siehe Abschnitt <<>> für Anweisungen, wie man das macht). ++ +Der File-Player-Modus merkt sich, wo man angehalten hat (indem man den SCHWARZEN Knopf lange drückt, um diesen Modus zu verlassen; schalte nicht einfach aus - wenn du das tust, hat der Morserino keine Chance, sich zu erinnern, wo du warst), +und es wird dann dort fortgesetzt, wenn man den File Player das nächste Mal neu startet. +Sobald das Ende der Datei erreicht ist, beginnt as Abspielen wieder am Anfang. ++ +Die Datei sollte nur ASCII-Zeichen enthalten (Groß- oder Kleinschreibung spielt keine Rolle) - +Zeichen, die nicht im Morsealphabet dargestellt werden können, werden einfach ignoriert. +Betriebsabkürzungen (pro signs) dürfen vorhanden sein, sie müssen als 2-Zeichen-Kombinationen, eingeschlossen in [] oder <>, geschrieben werden, z.B. `` oder `[ka]`, oder stelle einen verkehrten Schrägstrich davor, z.B. \kn. ++ +Die folgenden Betriebsabkürzungen werden erkannt: +==== +** `` : wird auf dem Display als + (Pluszeichen) angezeigt. +** `` : wird auf dem Display angezeigt als = (Gleichheitszeichen) +** `` +** `>` +** `` +** `` +** `` +** `` +==== + +Es gibt drei weitere "Sonderzeichen", die wie Betriebsabkürzungen gebildet werden und beim Abspielen einer Datei erkannt werden: + +===== Pausen + +Es ist jetzt möglich, **Pausen** einzuführen (nützlich z.B. wenn man einen QSO-Text abspielt - man kann so längere Pausen zwischen Phrasen haben oder beim Wechsel von Station A zu Station B). Verwende dazu

oder \p (mit einem Leerzeichen davor und danach): Jedes

(oder [p] oder \p) leitet eine Pause von drei regulären Wortabständen ein. Verwende mehrere Pausenmarkierungen (z. B. \p \p \p), wenn längere Pausen gewünscht sind. *Achte darauf, dass die Pausenmarkierung durch Leerzeichen voneinander und vom Rest des Textes getrennt ist. Andernfalls wird das gesamte Wort (z.B. cq

) durch eine Pause ersetzt!* + +===== Tonhöhenänderungen +Mit dem zweiten Sonderzeichen kann man *Tonhöhenänderungen* in die Datei einfügen (nützlich z.B., wenn man QSO-Text abspielt, um Station A von Station B zu unterscheiden). Füge dazu die Tonmarkierung oder \t oder [t] als ein separates Wort ein, d.h. mit mindestens einem Leerzeichen davor und danach). An dieser Stelle ändert sich der beim Abspielen der Ton (es sei denn, man hat den Parameter „Tone Shift“ auf „No Tone Shift“ gesetzt), und beim nächsten Auftreten der Tonmarkierung wechselt er wieder zum ursprünglichen Ton. *Achte darauf, dass die Tonmarkierung durch Leerzeichen vom Rest des Textes getrennt ist. Andernfalls wird das gesamte Wort (z.B. cq) als Tonmarkierung betrachtet und "cq") geht verloren!* + +Im Echo Trainer Modus wird der Tonmarker ignoriert. + +===== Kommentare + +Das dritte mögliche Sonderzeichen innerhalb von Textdateien dient dazu, **Kommentare** einzufügen. oder \c in einem Wort oder auch für sich alleine machen dieses Wort und den Rest der Zeile zu einem Kommentar, der nicht vom File Player abgespielt wird. + +===== Zufallswiedergabe +Es gibt auch einen Parameter für den File Player namens `Randomize File`. Wenn dieser auf "ON" gesetzt wird (Standardwert ist "OFF"), +überspringt der Morserino nach jedem gesendeten Wort n Wörter (n = Zufallszahl zwischen 0 und 255); +Da am Dateiende wieder von vorne begonnen wird, werden irgendwann alle Wörter in der Datei vorgekommen sein (aber es kann eine Weile dauern). +Wenn es sich zum Beispiel um eine alphabetische Wortliste handelt, werden die erzeugten Wörter in einem Durchgang immer noch in alphabetischer Reihenfolge angezeigt (allerdings mit Lücken); +um zufälligere Ergebnisse zu erzielen, ist es daher am besten, schon mit einer zufälligen Liste von Wörtern zu beginnen. + +Wofür kann man das nutzen? Man kann zum Beispiel eine Liste von Rufzeichen nehmen und diese Datei auf den Morserino-32 hochladen. +(Es gibt im Morserino-32 GitHub-Repository eine Datei mit Rufzeichen, die tatsächlich in HF-Contesten aktiv waren!) +Mit dem File Player kann man diese Rufzeichen nun nach dem Zufallsprinzip trainieren. +Du solltest das Morserino-32 GitHub Repository besuchen, um auch andere geeignete Dateien für das Training zu finden! + +=== Echo Trainer + +Hier erzeugt der Morserino-32 ein Wort (oder eine Gruppe von Zeichen; man hat die gleichen Auswahlmöglichkeiten wie beim CW-Generator) und wartet dann darauf, dass du diese Zeichen mit dem Paddel wiederholst. Wenn du zu lange wartest oder wenn deine Antwort nicht korrekt ist, wird ein Fehler angezeigt ("ERR" auf dem Display und auch akustisch) und das betreffende Wort wird wiederholt. Wenn du die richtigen Zeichen eingegeben hast, wird dies auch akustisch und auf dem Display ("OK") angezeigt und es wird das nächste Wort abgefragt. + +In diesem Modus wird das zu wiederholende Wort normalerweise nicht auf dem Display angezeigt - nur deine Antwort wird angezeigt. + + +Die Untermenüs sind die gleichen wie beim CW-Generator: **Random, CW Abbrevs, English Words, Call Signs, Mixed** and **File Player**. + + +Wie im CW-Generator-Modus startet man **die Generierung durch Drücken eines Paddles** (oder Drücken des schwarzen Knopfs, oder - falls man eine solche verwendet - der Handtaste), und dann wird die Sequenz "`vvv`" als Ankündigung generiert, bevor das Echo-Training beginnt. Du kannst diesen Modus nicht stoppen oder unterbrechen, indem du das Paddel (oder die Morsetaste)drückst - schließlich benutzt du das Paddel, um deine Antworten zu generieren! **Die einzige Möglichkeit, diesen Modus zu stoppen, ist ein Klick mit dem SCHWARZEN Knopf des Drehgebers**! + +Wenn du während deiner Antwort feststellst, dass du einen Fehler gemacht hast, kannst du deine Antwort "zurücksetzen", indem du das Zeichen für "FEHLER" eingibst, d.h. eine Reihe von 8 Punkten (der Morserino akzeptiert jede Folge von 8 oder mehr Punkten. wird auf dem Display angezeigt und du kannst deine Eingabe von Anfang an neu starten. + +Auch hier kann man, wie beim CW-Generator, eine Vielzahl von Parametern einstellen, um zu beeinflussen, was generiert wird. Von besonderem Interesse für den Echo-Trainer sind: + +`Echo repeats`: wie oft ein Wort wiederholt wird, wenn die Antwort entweder zu spät oder fehlerhaft ist, bevor ein neues Wort erzeugt wird. + +`Echo Prompt`: Hiermit wird festgelegt, wie die Eingabeaufforderung beim Echo Trainer aussieht. Die möglichen Einstellungen sind: "Sound Only" (nur akustisch -- das ist der Standardwert; am besten geeignet, um das Gehörlesen zu lernen), "Display Only" (nur Anzeige auf dem Display -- das Wort, das eingegeben werden soll, wird auf dem Display angezeigt, es wird aber kein hörbarer Code erzeugt; gut für Lernen der Eingabe mit dem Paddel) und "Sound & Display", d.h. man hört UND sieht die Eingabeaufforderung. + +`Confrm. Tone`: Normalerweise ("ON") ertönt im Echo-Trainer-Modus ein akustischer Bestätigungston. Wenn man diesen ausschaltet ("OFF"), wiederholt das Gerät nur die Eingabeaufforderung, wenn die Antwort falsch war, oder sendet eine neue Eingabeaufforderung bei richtiger Antwort. Die optische Anzeige von "OK" oder "ERR" ist auf jeden Fall sichtbar. + +`Max # of Words`: Wie beim CW-Generator kann man den M32 nach einer bestimmten Anzahl von Wörtern pausieren lassen. + +TIP: Wenn dieser Parameter auf einen Wert zwischen 5 und 250 (und nicht auf "Unlimited") eingestellt ist, zeigt der M32 bei einer Pause nach dieser Anzahl von Wörtern in der obersten Zeile des Displays (für 5 Sekunden) an, wie viele falsche Eingaben du gemacht hast (und die Anzahl der Wörter). Beachte, dass man bei ein und demselben Wort wiederholt Fehler machen kann, die alle mitgezählt werden. + +`Adaptv. Speed`: Dies sollte dir helfen, auf Höchstgeschwindigkeit zu trainieren. Wann immer deine Antwort richtig war, wird die Geschwindigkeit um 1 wpm (Wort pro Minute) erhöht; hast du einen fehler gemacht, wird sie um 1 wpm reduziert. So wirst du schließlich immer an deinem Limit trainieren, was sicherlich der beste Weg ist, um deine Grenzen weiter hinaus zu schieben ... + + + +=== Koch Trainer + +Der deutsche Psychologe Koch entwickelte eine Methode zum Erlernen des Morsens (in den 1930er Jahren), wobei bei jeder neuen Lektion ein zusätzliches Zeichen hinzugefügt wird. +Die Reihenfolge ist weder alphabetisch noch nach der Länge der Morsezeichen geordnet, sondern folgt einem bestimmten rhythmischen Muster, +so dass die einzelnen Zeichen als Rhythmus und nicht als Folge von Dits und Dahs gelernt werden. + +Wenn du die Koch-Methode zum Morsen Lernen anwenden willst (Lernen und Trainieren eines Zeichens nach dem anderen), +**findest du alles, was du dazu brauchst, im Menüpunkt "Koch Trainer"**. +Es gibt ein Untermenü, um die Lektion auszuwählen, die man trainieren möchte, eine, um nur diesen einen neuen Buchstaben zu lernen +(wie beim Echotrainer-Modus, so dass man ermutigt wird, das Gehörte zu wiederholen), sowie die Modi "CW-Generator" und "Echo-Trainer", +und die letzten beiden mit den Untermenüs für "Random" (Gruppen von zufälligen Charakteren aus den bisher gefundenen Charakteren), +"CW Abbrevs" (die Abkürzungen, die normalerweise in CW QSOs verwendet werden), "English Words" (die gebräuchlichsten englischen Wörter) und "Mixed" +(Gruppen zufälliger Zeichen, Abkürzungen und Wörter, die zufällig gemischt werden). +Natürlich werden nur die bereits erlernten Zeichen verwendet - das heißt, während du noch mit den ersten Buchstaben kämpfst, wird +die Anzahl der Abkürzungen und Wörter logischerweise sehr begrenzt sein). + +Um zu verhindern, dass man Dits und Dahs zählt oder darüber nachdenkt und rekonstruiert, was man gehört hat, sollte die Geschwindigkeit ausreichend hoch sein (min. 18 wpm), und die +Pausen zwischen Zeichen und Wörtern sollten nicht extrem verlängert werden (und es ist immer besser, nur die Pausen zwischen den Wörtern zu verlängern, +und die Leerzeichen zwischen den Zeichen auf mehr oder weniger dem normalen Zeichenabstand zu halten). +Mit dem M32 kann man den Wortabstand unabhängig vom Zeichenabstand einstellen, so dass du immer eine Einstellung finden kannst, die perfekt zu deinen Bedürfnissen passt. + + + +==== Koch: Select Lesson (Auswahl der Lektion) [[koch]] + +Wähle eine "Koch-Lektion" zwischen 1 und 50 (Man lernt insgesamt 50 Zeichen nach der Koch-Methode). Die Nummer der Lektion und das Zeichen, das mit dieser Lektion verbunden ist, werden im Menü angezeigt. + +Die Reihenfolge der gelernten Zeichen ist von Koch nicht streng definiert worden, so dass verschiedene Lernkurse leicht unterschiedliche Ordnungen verwenden. Hier verwenden wir die gleiche Zeichenfolge wie beim Programm "Just Learn Morse Code", das wiederum fast identisch ist mit der Reihenfolge des Softwarepakets "SuperMorse" (siehe http://www.qsl.net/kb5wck/super.html). Die Reihenfolge ist wie folgt: + + +[cols=">.3,3,>.3,3",options=header,stripes=odd] +|=== +| Lektion Nr | Zeichen | Lektion nr | Zeichen +| 1 | m | 26 | 9 +| 2 | k | 27 | z +| 3 | r | 28 | h +| 4 | s | 29 | 3 +| 5 | u | 30 | 8 +| 6 | a | 31 | b +| 7 | p | 32 | ? +| 8 | t | 33 | 4 +| 9 | l | 34 | 2 +| 10 | o | 35 | 7 +| 11 | w | 36 | c +| 12 | i | 37 | 1 +| 13 | . (Punkt) | 38 | d +| 14 | n | 39 | 6 +| 15 | j | 40 | x +| 16 | e | 41 | - (minus) +| 17 | f | 42 | = +| 18 | 0 (zero) | 43 | SK (Betriebsabkürzung) +| 19 | y | 44 | AR (Betriebsabkürzung, auch +) +| 20 | v | 45 | AS (Betriebsabkürzung) +| 21 | , (Comma) | 46 | KN (Betriebsabkürzung) +| 22 | g | 47 | KA (Betriebsabkürzung) +| 23 | 5 | 48 | VE (Betriebsabkürzung) +| 24 | / | 49 | BK (Betriebsabkürzung) +| 25 | q | 50 | @ +| | | 51 | : (Colon) +|=== + +Es besteht auch die Möglichkeit, die Reihenfolge der zu lernenden Zeichen auszuwählen. Neben der nativen Zeichenfolge kann man die Reihenfolge wählen, die vom beliebten Online-Trainingstool "Learn CW Online" (LCWO) verwendet wird, oder die Reihenfolge, welche die CW Ops CW Academy-Kurse benutzen, oder auch die Reihenfolge entsprechend dem "Carousel" Curriculum des Long Island CW (LICW) Clubs. Dies kann im Parametermenü des Morserino-32 unter "Koch Sequence" eingestellt werden. + +Falls du einen Kurses bei LICW machst, solltest du zusätzlich den Parameter „LICW Carousel“ entsprechend deinem Einstiegspunkt in deren Curriculum einstellen (zB. wenn du innerhalb von BC1 – Basic Course 1 – einen Kurs mit den Buchstaben p, g und s beginnst, setze diesen Parameter auf "BC1: p g s". Alle weiteren Zeichen, die du in BC1 lernen wirst, werden in der gleichen Reihenfolge in den Morserino Koch-Lektionen widergespiegelt. Wenn du BC1 absolviert hast, steigst du in BC2 ein, zB. beginnend mit Zeichen 7, 3 und ?, also solltest du nun diesen Parameter jetzt auf "BC2: 7 3 ?" setzen.) + +Die Zeichenfolge bei der Auswahl von "LCWO" ist wie folgt: + +k m u r e s n a p t l w i . j z = f o y , v g 5 / q 9 2 h 3 8 b ? 4 7 c 1 d 6 0 x - SK AR(+) KA AS KN VE @ : + +Und die Reihenfolge der CW Academy sieht so aus: + +t e a n o i s 1 4 r h d l 2 5 u c m w 3 6 ? f y p g 7 9 / b v k j 8 0 = x q z . , - SK AR(+) KA AS KN VE @ : + +Die Reihenfolge der LICW Kurse ist folgende: +r e a t i n p s g l c d h o f u w b k m y 5 9 , q x v 7 3 ? + SK = 1 6 . Z J / 2 8 BK 4 0 + + + +===== Koch: Üben mit einer maßgeschneiderten Zeichenmenge + +Man kann den Koch-Trainer auch verwenden, um einen spezifischen Satz von Zeichen zu trainieren: Lade eine Textdatei für den File Player hoch, der die zu trainierenden Zeichen enthält (als ein „Wort“ oder mehrere, in einer Zeile oder mehr), und setze dann den Parameter 'Koch Sequence' auf die Option 'Custom Chars'. Damit werden die Zeichen aus der Datei eingelesen. Jetzt kann man den Koch-Trainer (CW-Generator oder Echo-Trainer) benutzennun, der genau diese Zeichen für das Training verwendet (die Einstellung der Koch-Lektion hat zu diesem Zeitpunkt keinen Einfluss). Wenn du den Zeichensatz ändern möchtest, lade eine neue Textdatei hoch und wähle die Option 'Custom Chars' erneut aus (auch wenn diese zuvor schon ausgewählt war), um den neuen Zeichensatz vorzubereiten (wenn man nur eine neue Textdatei hochlädt wird sich der benutzerdefinierte Zeichensatz nicht ändern - man muss in die Parameter gehen und erneut 'Custom Chars' auswählen. Dies ist ein Feature, kein Fehler: Man kann so zwischen dem Trainieren einmzelner bestimmter Zeichen und der Verwendung des File Players mit einer anderen Textdatei wechseln.). Wenn man 'Koch Sequence' auf M32, LCWO oder CA Academy einstellt, wird die „normale“ Koch-Trainer-Option wieder hergestellt. + +==== Koch: Learn New Chr (Neues Zeichen Lernen) + +Wählt man diesen Menüpunkt aus, wird das neue Zeichen (entsprechend der gewählten Koch-Lektion) vorgestellt - Man hören den Klang und sieht die Reihenfolge der Punkte und Striche rasch auf dem Display, und auch das Zeichen wird angezeigt. Dies wird so lange wiederholt, bis man durch Drücken des SCHWARZEN Knopfes stoppt. Nach jedem Wiederholung hat man die Möglichkeit, mit den Paddles einzugeben, was man gehört hat, und man wird darüber informiert, ob dies korrekt war oder nicht. + +Sobald du das neue Zeichen gemeistert hast, kannst du entweder zum CW-Generator oder zum Echo-Trainer innerhalb des Koch-Trainers wechseln, um das neu erlernte Zeichen in Verbindung mit allen bisher erlernten Zeichen zu üben. + +==== Koch: CW Generator und Echo Trainer + +Die Funktionalität ist die gleiche wie oben für diese beiden Funktionen beschrieben, mit den folgenden kleinen Unterschieden: + +- Es werden nur die Zeichen bis zur ausgewählten Koch-Lektion generiert (bzw die definierten benutzerspezifischen Zeichen, siehe weiter oben). +- Der Parameter 'Random Groups' wird ignoriert. +- Es gibt kein Untermenü "File Player". +- Es gibt Im Koch Echo Trainer auch das Untermenü "Adapt. Rand.", siehe unten. + +==== Koch Echo Trainer: Adaptive Random + +Der "Adaptive Random"-Modus modifiziert die zufällige Auswahl von Zeichen in Abhängigkeit von den eingegebenen Antworten. Ein falsches Zeichen erhöht die Wahrscheinlichkeit, ausgewählt zu werden. Ein korrekt eingegebenes Zeichen verringert seine Wahrscheinlichkeit. + +Um den adaptiven Modus zu starten, starte: Koch Trainer > Echo Trainer > Adapt. Rand. + +===== Bemerkungen: + +- Die Wahrscheinlichkeiten werden jedes Mal auf den Standardwert zurückgesetzt, wenn man den „Adaptive Random“-Modus startet. + +- Die letzten Kochlektionen / Zeichen haben eine höhere Wahrscheinlichkeit zu Beginn der Session. + +- Zu Beginn der Sitzung wird jedes Zeichen einmal (in zufälliger Reihenfolge) ausgewählt. + +- Nachdem jedes Zeichen einmal ausgewählt wurde, werden die nächsten Zeichen zufällig ausgewählt, falsch eingegebene Zeichen haben eine höhere Wahrscheinlichkeit, ausgewählt zu werden. + +- Ein falsch eingegebenes Zeichen erhöht auch die Wahrscheinlichkeit des Zeichens links und rechts. Z.B. "z/?" gefragt und du antwortest mit "g/?". Dann wird die Wahrscheinlichkeit von z erhöht und die Wahrscheinlichkeit von / wird ebenfalls etwas erhöht. + +- Nur das erste falsche Zeichen wird analysiert. Spätere Eingaben werden nicht ausgewertet. Z.B. "z/?" gefragt und du antwortest mit "gz/?". Die Wahrscheinlichkeiten werden auf die gleiche Weise wie im vorherigen Beispiel erhöht. + +- Erwarten nicht nur reinen Spaß in diesem Modus. Der adaptive Modus quält dich mit den Zeichen, die nicht jedes Mal 100% richtig eingegeben wurden. Wenn einmal ein Zeichen falsch eingegeben wurde, hast du oft die Möglichkeit, das Zeichen wieder falsch einzugeben, wodurch sich die Wahrscheinlichkeit erhöht, erneut ausgewählt zu werden. Wenn die totale Frustration erreicht ist, wechsle am besten zurück in den Koch-Random-Modus und entspann dich für einige Zeit, bevor du den „Adaptive Random“-Modus erneut verwendest. + + +=== Transceiver + +Es gibt drei Transceiver-Modi im Morserino-32. Der erste ist ein eigenständiger Sender-Empfänger für die Morse-Kommunikation unter Verwendung der LoRa Spread Spectrum Funktechnologie (in der Standardversion im 433-MHz-Band, aber es sind Versionen für die 868- und 920-MHz-Bänder erhältlich). Der zweite Transceiver Modus benutzt das Internet Protokoll (UDP auf Port 7373) zur Kommunikation über ein IP Netzwerk (über WLAN). Der dritte ist ein Transceiver-Modus, der entweder mit einem externen Transceiver (z.B. einem Kurzwellen-Amateurfunkgerät) oder mit einem Protokoll wie iCW (CW over Internet) verwendet werden kann. In allen drei Fällen sind der CW Keyer und der CW Decoder gleichzeitig aktiv. + + + +==== LoRa Trx + +Wie bereits erwähnt, handelt es sich hierbei um einen Morse-Code-Sender-Empfänger, der LoRa zur Übertragung von Morse-Code an andere Morserino-32s verwendet. +Zusätzlich zur Funktionalität des CW-Keyers sendet er alles, was über den Keyer eingegeben wird, über den LoRa-Transceiver aus +(mit einem speziellen Datenformat, das die Punkte, Striche und Pausen kodiert, unabhängig davon, ob es sich um legale Morsezeichen handelt oder nicht), +und die übrige Zeit auf der Frequenz empfängt; so kann man in der Tat ein interaktives QSO mit Morsezeichen zwischen zwei oder mehr Morserino-32-Geräten führen! +Bitte beachte, dass die Zeichen Wort für Wort übertragen werden, +daher gibt es eine kleine Verzögerung auf der Empfangsseite - QSK ist daher nicht möglich. Es wird notwendig sein, eine ordnungsgemäße Tastenübergabe durchzuführen! + +===== Mehr Informationen zum Modus "LoRa Trx". +Im Grunde genommen funktioniert dieser wie der CW Keyer. Aber sobald etwas empfangen wird, zeigt die Statuszeile neben der eigenen Geschwindigkeit auch die Geschwindigkeit der Gegenstation an - Das könnte so aussehen: **18r20sWpM**, was bedeutet, dass man eine Station mit einer Geschwindigkeit von 18 Wpm empfängt und selber mit 20 WpM sendet. +Darüber hinaus ändert der Lautstärkebalken rechts neben der Statuszeile seine Funktion: Anstatt den aktuellen Lautstärkepegel anzuzeigen, gibt er einen Hinweis auf die Signalstärke - eine Rohform eines S-Meters, sozusagen. +Der volle Balken zeigt einen RSSI-Pegel von etwa -20dB an, und der Balken beginnt, bei einem Pegel von etwa -150dB anzuzeigen. + +Durch Drücken der ROTEN Pwr/Vol/Scroll-Taste kann man aber die Lautstärke weiterhin einstellen. + +Vom Sender-Empfänger empfangene Morsezeichen +werden im (scrollbaren) Textfeld auf dem Display fett gedruckt, während alles, was man selber sendet, in regulären Zeichen dargestellt wird. + +Ein weiteres Merkmal ist hier erwähnenswert: Die Frequenz des Tons, den man beim Empfang der Gegenstation hört, wird wie in den anderen Modi auch über den Parameter `Pitch` eingestellt. +Beim Senden kann die Tonhöhe des Tons gleich sein, oder ein Halbton höher oder niedriger als der Empfangston -- +dies wird über den Parameter `Tone Shift` eingestellt, wie auch im Echo Trainer Modus. + +Eine weitere Sache, die Sie vielleicht wissen sollten: Der LoRa Morse-Transceiver funktioniert nicht wie ein CW-Transceiver auf Kurzwelle, bei dem ein unmodulierter Träger getastet wird, und die Verzögerung zwischen Sender und Empfänger nur durch die Verzögerung auf dem Weg der elektromagnetischen Wellen bestimmt wird, die die Signale übertragen. LoRa verwendet eine Spread-Spectrum-Technologie zum Senden von Datenpaketen - ähnlich wie WLAN auf dem Handy oder PC. +Daher wird alles, was eingegeben wird, zuerst in Daten kodiert -- im Wesentlichen die Geschwindigkeit und alle Punkte, Striche und Pausen zwischen den Zeichen. +Sobald die Pause lang genug ist, um als Pause zwischen den Wörtern (sozusagen als Leerzeichen) erkannt zu werden, +wird das gesamte bisher gesammelte Datenpaket übertragen und schließlich mit der ursprünglichen Geschwindigkeit vom empfangenden Morserino-32 wiedergegeben. + +Wenn Morsecode in ein LoRa-Datenpaket gepackt wird, werden Punkte, Striche und Pausen kodiert; es ist nicht so, dass der Klartext als ASCII-Zeichen gesendet würde. Daher ist es möglich, "illegale" Morsezeichen zu senden, oder Zeichen, die nur in bestimmten Sprachen üblich sind. Sie werden korrekt übertragen (aber auf dem Display als nicht dekodierbar angezeigt). + +Das wortweise Versenden bedeutet eine nicht unerhebliche Verzögerung zwischen Sender und Empfänger, und die Verzögerung hängt in hohem Maße von der Länge der zu versendenden Worte und der verwendeten Geschwindigkeit ab. Da die meisten Wörter in einem typischen CW-QSO eher kurz sind (7 Zeichen oder mehr sind da bereits ein sehr langes Wort), ist dies kein Grund zur Sorge (es sei denn, beide sitzen im selben Raum ohne Kopfhörer - dann wird es wirklich verwirrend werden). Aber versuche einmal, wirklich lange Wörter zu senden, sagen wir 10 oder mehr Zeichen lang, mit wirklich niedriger Geschwindigkeit (5 WpM), und du wirst sehen, wovon ich rede! + +===== Verwendung von zwei verschiedenen LoRa "Kanälen" +LoRa-Datenpakete werden mit einem so genannten "Sync Word" adressiert - Empfänger verwerfen Pakete, die nicht das erwartete Synchronwort anzeigen. + +Morserino-32 ab Version 2.0 kann zwei verschiedene Synchronworte verwenden und so effektiv auf zwei verschiedenen "Kanälen" +kommunizieren. Dies kann z.B. in einer Klassenraumsituation verwendet werden, +um zwei unabhängige Gruppen zu erstellen, die sich nicht gegenseitig stören sollen. + +Normalerweise arbeitet M32 LoRa mit dem Synchronwort 0x27 (wir nennen es den "Standard"-Kanal), aber durch die Einstellung `LoRa Channel` im Parametermenü +kann auf 0x66 (genannt "Sekundärkanal") umgeschaltet werden. + +===== Verwendung verschiedener LoRa-Frequenzbänder bzw. Frequenzen +Standardmäßig werden die Morserino-32-Bausätze mit einem LoRa-Modul ausgeliefert, das im 70-cm-Band arbeitet, +und als Standardfrequenz innerhalb dieses Bandes auf 434,150 MHz (innerhalb des 70cm Amateurbandes und innerhalb des Region 1 ISM-Bandes). + +Wenn man diese Frequenz aus irgendeinem Grund nicht nutzen kann (z.B. wegen Bandplänen, aus regulatorischen Gründen usw.), kann man die Frequenz am Standard-LoRa-Modul zwischen 433,65 und 434,55 MHz in Schritten von 100 kHz ändern. + +Sollte man eine LoRa-Frequenz entweder um 868 MHz oder um 920 MHz benötigen, muss ein Heltec-Modul beschafft werden, die diesen höheren Frequenzbereich unterstützen. In diesem Fall MUSS der Morserino-32 konfiguriert werden, damit er das richtige Band und die richtige Frequenz verwendet. + +**Siehe <> am Ende dieses Dokuments**, um zu erfahren, wie man LoRa für Module konfiguriert, die die Bänder 868 und 929 MHz unterstützen, und wie man die LoRa-Frequenzeinstellungen ändern kann. + + +===== Technische Details des LoRa Transceivers +* Frequenz: Der Standardwert ist 434,150 MHz (innerhalb des 70 cm Amateurbandes und innerhalb des Region 1 ISM-Bandes) - aber siehe die Hinweise oben für die Auswahl anderer Frequenzen. +* LoRa Spreizfaktor: 7 +* LoRa Bandbreite: 250 kHz +* LoRa CRC: kein CRC +* LoRa Synchronwort: 0x27 (= dezimal 39) für den Standardkanal und 0x66 (= dezimal 102) für den Sekundärkanal +* HF-Ausgangsleistung: 20 dBm (100 mW) + +==== Wifi Trx + +Du kannst diesen Transceiver-Modus verwenden, um mit deinem CW-Freund über das Internetprotokoll zu kommunizieren, entweder in deinem lokalen Netzwerk oder über das Internet. Da dazu das WLAN benutzt wird, musst du sicher stellen, dass du deinen Morserino mit deinem WLAN verbinden kannst - die Funktion "WiFi Config" muss als vorher einmal ausgeführt worden sein. In deinem lokalen Netzwerk ist die Benutzung dieses Transceiver Modus sehr einfach: Wähle ihn einfach aus dem Menü aus, und ihr könnt kommunizieren (ohne eine Peer IP Adresse zu konfigurieren, wird alles an die IP-Adresse 255.255.255.255 gesendet, das ist eine Broadcast-Adresse und kann von allen Geräten in diesem Netzwerk empfangen werden). Der Morserino-32 verwendet den UDP-Port 7373 für die asynchrone Kommunikation. + +Wenn du über das Internet mit einem bestimmten Morserino-32 kommunizieren möchtest, musst du die IP-Adresse deines Freundes konfigurieren. Dies erfolgt über den Menüpunkt 'Config WiFi' (WLAN konfigurieren), in dem nun ein drittes Feld neben SSID und Passwort angezeigt wird. In dieses Feld muss man die IP-Adresse deines Partners eingeben, oder, falls vorhanden, der DNS Hostname. Anschließend sendet der Wifi-Transceiver die Pakete an diese bestimmte IP-Adresse. + +Wenn sich diese IP-Adresse nicht in deinem lokalen Netzwerk befindet und du dich hinter einer Firewall oder einem Router befindest, der dein Netzwerk als privates Netzwerk behandelt, kann der Morserino zwar an das Internet senden (es sei denn, bestimmte Firewall-Regeln blockieren die meisten UDP-Ports), aber die von deinem Buddy kommenden Pakete werden am Router blockiert. In diesem Fall musst du "Port Forwarding" konfigurieren und den Router anweisen, alle UDP-Pakete an Port 7373 deiness Morserino zu senden. Gleichzeitig musst du deinem Kumpel deine vom Internet sichtbare IP-Adresse (dh die IP-Adresse deiner Router-Schnittstelle zu deinem Internetprovider) mitteilen, und dein Kumpel muss dasselbe tun (Portweiterleitung konfigurieren und dir seine IP-Adresse, die vom Internet sichtbar ist, mitteilen, die du in deinen Morserino eingeben musst). Klingt zunächst etwas kompliziert, ist aber nicht so schlimm. + +Eine andere, vielleicht etwas kompliziertere Option wäre das Einrichten eines VPN (Virtual Private Network), sodass sich beide Morserinos im selben "virtuellen Netzwerk" befinden und daher miteinander kommunizieren können, ohne dass Firewall-Regeln den Datenverkehr blockieren. Wie das geht, geht deutlich über den Rahmen dieses Handbuchs hinaus - frage dazu einen Internet-Guru nach weiteren Details! + +==== iCW/Ext Trx + +In diesem Modus wird ein mit dem Morserino-32 verbundener Transceiver getastet, oder man kann das Line-Out-Audiosignal verwenden, +um z.B. einen FM-Transceiver zu modulieren, oder um es für CW über das Internet (iCW - das verwendet Mumble als Audioaustauschprotokoll) zu betreiben. +Alle CW-Signale, die als Audio über den Audioeingang eingehen, werden dekodiert und auf dem Display angezeigt. +Ein externer Sender-Empfänger, der über den Anschluss 1 angeschlossen ist, wird vom Keyer getastet, oder man verwendet das Tonsignal am Audioausgang +(Anschluss 2), um es in einen Computer oder in einen FM-Transceiver einzuspeisen. + +=== CW Decoder + +In diesem Modus werden Morsezeichen dekodiert und auf dem Display angezeigt. Der Morsecode kann entweder über eine manuelle Morsetaste eingegeben werden("straight key" - verbunden mit der Buchse, an der normalerweise ein externes Paddel angeschlossen ist), man kann aber auch eines der beiden Touchpaddel verwenden, um sozusagen eine gewöhnliche Morsetaste zu simulieren. Wenn man die Dekodierung auf diese Weise verwendet, kann man seine Gebeweise verbessern, in dem man überprüft, ob korrekt dekodiert wurde, was man zu senden versucht hat. + +Man kann auch ein Tonsignal (am Audioeingang) dekodieren, das beispielsweise von einem Empfänger stammt. Der Ton sollte bei etwa 700 Hz liegen. Optional gibt es einen ziemlich scharfen Filter (in Software implementiert), der nur Töne in einem sehr engen Bereich um 700 Hz erkennt und alle anderen ignoriert. Dies wird durch Auswahl des Parameters `Narrow` aktiviert (siehe den Abschnitt <>). + +Die Statuszeile unterscheidet sich leicht von den anderen Modi. Zunächst einmal befindet sich der Drehgeber immer im Lautstärke-Einstellmodus - die Geschwindigkeit wird aus dem dekodierten Morsecode bestimmt und kann nicht manuell eingestellt werden. Durch Drücken des Drehgeber-Knopfes wird der Decoder-Modus beendet und man gelangt zurück zum Startmenü. + +Links neben der Statusanzeige oben sieht man bei jedem Tastendruck ein schwarzes Rechteck (oder wenn ein 700 Hz-Ton erkannt wurde) - dies ersetzt die Anzeige für den Keyer-Modus. + +Die vom Decoder erfasste aktuelle Geschwindigkeit wird als WpM in der Statuszeile angezeigt. + +Dieser Modus hat nicht viele Parameter (siehe den Abschnitt <>); am wichtigsten ist vielleicht die Möglichkeit, die Filterbandbreite des Audiodecoders zwischen schmal (ca 150 Hz) und breit (ca 600 Hz) umzuschalten. Für die Dekodierung von Signalen von einem Sender-Empfänger (wo sich andere Signale in der Nähe befinden können) ist es in der Regel am besten, die Bandbreite auf "Narrow" einzustellen und das Signal auf genau 700 Hz einzustellen. Für die Dekodierung von Signalen von einem FM-Transceiver, von iCW oder anderen Umgebungen mit geringer Interferenz ist es besser, die Einstellung "Wide" zu verwenden - in diesem Fall muss die Tonfrequenz nicht genau 700 Hz betragen. + +=== WLAN-Funktionen + +Man kann die WLAN-Möglichkeit des Heltec ESP32 Wifi LoRa Moduls im Morserino-32 für zwei Funktionen des Gerätes nutzen: + +* Hochladen einer Textdatei auf den Morserino-32, die dann im CW Generator Modus oder Echo Trainer Modus "abgespielt" werden kann. +* Hochladen der Binärdatei einer neuen Firmware-Version. + +Für beide Funktionalitäten muss sich die hochzuladende Datei (sei es eine Textdatei oder die kompilierte Binärdatei für das Software-Update) auf deinem Computer befinden (sogar ein Tablett oder Smartphone funktioniert, da man auf diesem Gerät nur die grundlegende Webbrowser-Funktionalität benötigt), und der Morserino muss mit dem gleichen WLAN-Netzwerk wie dein Computer (oder Smartphone etc.) verbunden sein. + +Um den Morserino-32 mit dem lokalen WLAN-Netzwerk zu verbinden, muss man die SSID (den "Namen") des Netzwerks und das Passwort für die Verbindung mit ihm kennen. Und du musst diese beiden Elemente in deinen Morserino-32 eingeben. Da es keine Tastatur für die bequeme Eingabe dieser Informationen gibt, verwenden wir eine andere Methode, und zu diesem Zweck wurde eine weitere WLAN-Funktion implementiert: die Netzwerkkonfiguration, die man verwenden muss, bevor man die Upload- oder Update-Funktionen nutzen kann. + +Für Heimnetzwerke, die (aus Sicherheitsgründen) eine Liste der zulässigen MAC-Adressen verwenden, muss man den Router konfigurieren und die MAC-Adresse des M32 eingeben, bevor man den M32 mit dem Netzwerk verbinden kann. Dazu ist auch eine Funktion zur Anzeige der MAC-Adresse auf dem Display implementiert. + +Alle netzwerkbezogenen Funktionen finden sich unter dem Menüpunkt "**WiFi Functions**". + +IMPORTANT: In Softwareversionen vor 2.0 waren die WLAN Funktionen nicht im Hauptmenü untergebracht. Für den Fall, dass du ein Update von Version 1.x auf Version 2.x über WLAN machen möchtest, lies bitte <> am Ende dieses Dokuments. + +==== Anzeige der MAC-Adresse (Displaying the MAC Address) +"**Disp MAC Addr**" ist der erste Eintrag unter dem Menü "Wifi Functions" und zeigt die MAC-Adresse des Morserino in der Statuszeile an. Jeder Morserino hat eine eindeutige MAC-Adresse. + +Man kann diese Informationen verwenden, um dem Morserino den Zugriff auf das WLAN-Netzwerk zu ermöglichen, wenn der WLAN-Router so konfiguriert ist, dass er nur bestimmte MAC-Adressen ans Netz lässt. + +Wenn man die ROTE Taste drückt, startet der Morserino-32 neu. Wenn man nichts tut, geht der Morserino wie gewohnt in den Tiefschlaf, je nachdem, welche Einstellungen man dafür vorgenommen hat. + + +==== Netzwerkkonfiguration + +Wähl das Untermenü **"WiFi Config"**, um die Netzwerkkonfiguration durchzuführen. + +Das Gerät startet WLAN als **Access Point** und erstellt so ein eigenes WLAN-Netzwerk (mit der SSID "morserino"). Wenn man die verfügbaren Netzwerke mit dem Computer oder Smartphone überprüft, kann man es leicht finden; bitte verwenden dieses Netzwerk auf deinem PC (oder Tablett oder Smartphone -- du benötigst kein Passwort zur Verbindung). + +Sobald du mit dem WLAN "morserino" "verbunden bist, gib "http://m32.local" im Browser auf deinem Computer ein. Wenn dein Computer oder Smartphone mDNS nicht unterstützt (Android z.B. unterstützt es nicht, auch Windows nur mangelhaft), musst du die IP-Adresse **192.168.4.1** im Browser anstelle von m32.local eingeben. Es erscheint dann dann ein kleines Formular mit nur 3 mal 3 leeren Feldern im Browser: "SSID of WiFi network?", "WiFi Password?" und "WiFi TRX Peer IP?". + +Du musst nur einen Satz von Angaben eingeben, aber man kann so bis zu drei unterschiedliche Netzwerkkonfigurationen angeben, falls man dies braucht (z.B. Verbindung mit unterschiedlichen Netzwerken). Es gibt einen eigen Menüpunkt im WiFi Menü, um auszuwählen, welche Netzwerkkonfiguration man verwenden will. + +Gib nun den Namen deines lokalen WLAN-Netzwerks und das entsprechende Passwort ein (das dritte Feld kann leer bleiben) und klicke auf die Schaltfläche "Submit". Der Morserino-32 speichert diese Netzwerk-Anmeldeinformationen und startet sich dann neu (das Netzwerk "morserino" verschwindet dann wieder). + +Das dritte Feld ("WiFi TRX Peer IP/Host?") wird benutzt, um die Wifi Transceiver Funktionalität zu konfigurieren, dh. um mit einem anderen Morserino über das Internet zu kommunizieren. Mann muss dann in diesem Feld die IP Adresse (oder, falls vorhanden, den DNS Hostnamen) des Gegenübers eintragen. Falls man nur mit Morserinos im eigenen lokalen Netzwerk kommunizieren möchte, braucht man hier keine IP Adresse einzugeben (es wird dann die Broadcast Adresse benutzt, so dass alle Morserinos empfangen können, was einer von ihnen sendet). + +IMPORTANT: Morserino kann kein WiFi-Netzwerk mit einem "Captive Portal" nutzen, wie sie oft in öffentlichen Netzwerken verwendet werden. Diese Netzwerke erfordern, dass auf dem Gerät, das sich mit dem Netzwerk verbinden möchte, ein Browser verfügbar ist, und der Morserino-32 hat keinen solchen ... + +IMPORTANT: Der Morserino-32 unterstützt nur 2.4 GHz WLANs, und keine im 5 GHz Bereich. Anscheinend gibt es auch fallweise Probleme mit Apple Airport Routern. + +TIP: Wenn man sein WLAN bereits konfiguriert hat und diesen Schritt erneut ausführt, wird der zuvor eingegebene SSID-Name im Formular vorab angezeigt, und man muss ihn nur bei Bedarf ändern. Das Passwortfeld ist leer, aber wenn man kein neues Passwort eingibt, bleibt das alte Passwort weiterhin gespeichert. Das Feld "TRX Peer IP-Address" wird ebenfalls mit einem Wert angezeigt, falls man zuvor einen eingegeben hat. Wenn man die Werte in diesem Feld löscht, wird diese IP-Adresse gelöscht. + +TIP: Man kann bis zu drei Netzwerkkonfigurationen eingeben; ab version 4.5.1 werden die Netzwerkkonfigurationen nicht mehr in den Snapshots gespeichert, so dass man diese nicht zum Speichern verschiedener Netzwerkkonfigurationen verwenden kann. + +==== Überprüfen der Netzwerkkonnektivität +Verwende den Untermenüpunkt "Check WiFi" unter "WiFi Functions", um die Netzwerkverbindung zu testen. + + +Dabei wird entweder eine Fehlermeldung ("No WiFi" und die verwendete SSID) angezeigt, oder eine Erfolgsmeldung ("Connected!"), die SSID und die IP-Adresse, die der Morserino vom WLAN-Router erhalten hat. + +TIP: Möglicherweise musst du deinen Morserino ziemlich nah an deinen WLAN-Router heranbringen (im selben Raum ist normalerweise OK)! Die WLAN-Antenne des Heltec-Moduls ist sehr klein und hat Probleme, schwache Signale zu empfangen. + + +TIP: Wenn du eine Fehlermeldung erhältst, obwohl du die korrekten Zugangsdaten eingegeben hast und sich der Morserino in unmittelbarer Nähe des WLAN-Routers befindet, solltest du es erneut versuchen - manchmal ist der erste Versuch, aus welchen Gründen auch immer, nicht erfolgreich... + +Wenn man die ROTE Taste drückt, kehrt diese Funktion zum Menü zurück. Wenn man nichts tut, geht der Morserino wie gewohnt in den Tiefschlaf, je nachdem, welche Einstellungen man dafür vorgenommen hat. + + +==== Hochladen einer Textdatei [[upload]] + +Sobald du den Morserino-32 mit deinen lokalen WLAN-Anmeldeinformationen konfiguriert hast, kannst du eine Textdatei hochladen, die du zum Üben verwenden kannst. Derzeit kann sich nur eine Datei auf dem Morserino-32 befinden, d.h. wenn man eine neue Datei hochlädt, wird die alte überschrieben. + +Die **Datei**, die man hochlädt, sollte eine reine ASCII-Textdatei ohne Formatierung sein (keine Word-Dateien, PDF-Dokumente usw.). Deutsche Zeichen (ÄÖÜäöüß), die als UTF-8 kodiert sind, sind erlaubt und werden in ae, oe, ue und ss umgewandelt. Die Datei kann Groß- und Kleinbuchstaben sowie alle Zeichen, die Teil der Koch-Methode sind, enthalten (insgesamt 50 Zeichen). Alle anderen Zeichen werden einfach ignoriert, wenn die Datei als Morsezeichen abgespielt wird. Die Datei zum Hochladen kann ziemlich groß sein - man hat fast 1 MB Speicherplatz dafür (genug, um eine Kopie von Mark Twains "Die Abenteuer des Huckleberry Finn" zu speichern). + +TIP: Android, Linux, iOS und OSX verwenden UTF-8 als Standardcodierung für Textdateien. Unter Windows ist das nicht so -- man kann aber z.B. Notepad benutzen und dort bei "Speichern unter" die Codierung UTF-8 angeben! + +Um die Datei hochzuladen, wählen man im Menü "WiFi Functions" "File Upload". Nach ein paar Sekunden (er muss sich ja zuerst mit dem WLAN-Netzwerk verbinden) zeigt der Morserino-32 an, dass er auf den Upload wartet. Nun geht man mit dem Browser des Computers zu "http://m32.local" (oder man ersetzt "m32.local" mit der auf dem Display angezeigten IP-Adresse). + +TIP: Für die Upload-Funktion muss der Morserino-32 (und natürlich der PC oder das Tablett etc.) wieder im lokalen WLAN-Netzwerk sein! + +Zuerst ist ein **Login**-Bildschirm im Browser zu sehen. Verwende "**m32**" als Benutzer-ID und "**upload**" als Passwort. Es erscheint dann im Browser ein Dateiauswahldialog - wähle die Datei, die du hochladen möchtest (Name oder Erweiterung spielt keine Rolle) und klicke dann auf die Schaltfläche "Begin". Sobald der Upload abgeschlossen ist (es dauert nicht lange), startet sich der Morserino-32 neu, und du kannst die hochgeladene Datei nun im *CW Generator* oder *Echo Trainer* Modus verwenden. + +IMPORTANT: Wenn du den Vorgang aus irgendeinem Grund abbrechen musst, musst du das Gerät neu starten, indem du es entweder vollständig von der Stromversorgung trennst (Akku aus und USB ausgesteckt) oder die Reset-Taste mit Hilfe eines kleinen Schraubendrehers oder eines Kugelschreibers drücken (die Reset-Taste ist durch das Loch neben dem USB-Anschluss in Richtung des externen Paddel-Anschlusses erreichbar). + +==== Aktualisierung der Morserino-32 Firmware + +Das Aktualisieren der Firmware des Morserino-32 über WLAN ist eine Möglichkeit, dies zu bewerkstelligen. Man kann dies auch tun, indem man die Arduino-IDE auf einem Computer verwendet (dazu müssen noch eine Reihe spezifischer Dateien und Bibliotheken installiert werden, um das Heltec-Modul und den ESP32-Prozessor zu unterstützen, dann kann die Binärdatei aus dem Quellcode kompiliert werden), oder indem man ein spezielles Update-Dienstprogramm (siehe <>). + +TIP: Du kannst jede beliebige Version aufspielen, man kann auch Versionen überspringen, ja, man kann auch wieder zu älteren Versionen zurück gehen. + +Das Aktualisieren der Firmware ist sehr ähnlich wie das Hochladen einer Textdatei. Zuerst muss die Binärdatei aus dem Morserino-32-Repository auf GitHub geholt werden (https://github.com/oe1wkl/Morserino-32 - suche nach einem Verzeichnis namens "Binaries" unter "Software" . Hol dir die neueste Version und lade sie auf deinen Computer herunter. Der Dateiname sieht so aus: + +`morse_3_vx.y.ino.wifi_lora_32.bin` mit x.y als Versionsnummer. + +Rufe nun wieder das Menü "**WiFi Functions**" auf und wähle den Punkt "**Update Firmw**". Ähnlich wie beim Datei-Upload gehe mit dem Browser zu "http://m32.local" (bzw. die angezeigte IP-Adresse anstelle von m32.local), um schließlich einen Anmeldebildschirm zu erhalten. Diesmal verwende den Benutzernamen "**m32**" und das Passwort "**update**". + +Als nächstes erscheint wieder ein Dateiauswahlbildschirm, wähle die heruntergeladene Binärdatei aus und klicke auf die Schaltfläche "Begin". Diesmal dauert das Ganze etwas länger - es kann einige Minuten dauern, also nur Geduld. Die Datei ist groß, muss hochgeladen und in den Speicher des Morserino-32 geschrieben und auch überprüft werden, um sicherzustellen, dass es sich um eine ausführbare Datei handelt. Schließlich startet sich das Gerät von selbst neu und man sollte die neue Versionsnummer beim Start auf dem Display sehen. + +[TIP] +==== +Im Folgenden sind die Schritte zum Aktualisieren der Firmware über WLAN zusammengefasst: + +1. Führe die Netzwerkkonfiguration wie oben beschrieben durch (dazu richtet der Morserino ein eigenes WiFi-Netzwerk ein, du verbindest deinen Browser mit dem Morserino und gibst im Browser den Namen und das Passwort deines WLAN-Netzwerks ein). Die ist nur einmal zu tun, da sich der Morserino diese Zugangsdaten für die zukünftige Verwendung merkt. Es empfiehlt sich die Funktion "Check WiFi" zu verwenden, um sicherzustellen, dass der Morserino eine Verbindung zu deinem Netzwerk herstellen kann. Denke daran, dass der Morserino ziemlich nah am WiFi-Router sein muss! + +2. Laden die neue Binärdatei auf deinen Computer herunter. + +3. Starte „Update Firmware“ auf dem Morserino. Nach einer Weile zeigt er die IP-Adresse (die sich in deinem WLAN befindet!) und eine Meldung, dass er auf ein Update wartet. + +4. Lass deinen Computer im Heimnetzwerk und richten den Browser entweder auf die IP-Adresse des Morserino oder auf "http://m32.local" (dies funktioniert auf Macs und iPhones, normalerweise funktioniert es nicht auf Windows-PCs oder Android-Geräten). + +5. Du siehst einen Anmeldebildschirm im Browser. Gib als Benutzernamen "m32" und als Passwort "update" ein. + +6. Es erscheint ein Dialog zur Dateiauswahl. Wähle die Binärdatei im Download-Ordner aus und klicke dann auf "Beginn". Es gibt einen Fortschrittsbalken, und nach einiger Zeit (kann einige Minuten dauern - auch wenn der Fortschrittsbalken bereits 100% anzeigt) startet sich der Morserino neu und zeigt die neue Versionsnummer auf dem Startbildschirm an. Dann weisst du, dass das Update erfolgreich war. +==== + + +==== WiFi Select +Hier kannst du auswählen, welche Netzwerkconfiguration verwendet werden soll, wenn mehr als ein Netzwerk konfiguriert ist. + + +=== Go To Sleep + +Dieser Menüpunkt versetzt den Morserino-32 bei Auswahl in einen Tiefschlafmodus, in dem er deutlich weniger Strom verbraucht als bei normalem Betrieb. Aber es wird die Batterie innerhalb weniger Tage immer noch entladen, so dass dies nur für kürzere Pausen zwischen den Trainingseinheiten gedacht ist. Siehe Abschnitt <> weiter oben in diesem Handbuch. + +== Einstellungen [[parameters]] + +Man erreicht das Parametermenü (Menü für die Einstellungen) immer durch **Doppelklick** auf den **SCHWARZEN Drehgeberknopf**. Man sieht ein`**>**` Zeichen vor dem aktuellen Parameter, und die Zeile darunter zeigt den aktuellen Wert. Verwende den Drehgeber, um durch die verfügbaren Einstellungen zu gehen. Wenn man das Parametermenü verlassen möchte, drückt man einfach den schwarzen Knopf des Drehgebers etwas länger und man befindet sich dann wieder in jenem Betriebsmodus, aus dem man das Parametereinstellungsmenü aufgerufen hat (oder auch wieder im Menü, wenn man mit Doppelklick aus dem Menü eingestiegen ist). + +Ist der zu ändernde Parameter erreicht, klickt man einmal. Nun steht das Zeichen `**>**` in der unteren Zeile vor dem Parameterwert und zeigt damit an, dass das Drehen des Drehgebers diesen Wert ändert. Ist man mit dem Wert zufrieden, klickt man wieder ** einmal**, um zur Auswahl der Parameter zurückzukehren, oder **man drückt den Knopf etwas länger**, um das Parametermenü gleich zu verlassen. + +Natürlich variieren die einstellbaren Parameter je nach Modus, in dem man sich befindet: **Wenn man in einem bestimmten Modus doppelt klickt, gelangt man nur zu den Parametern, die für den aktuellen Modus relevant sind.** Hat man den Doppelklick im Startmenü ausgeführt, wird die gesamte Palette der Parameter angezeigt. + + +=== Snapshots [[snapshots]] +Für verschiedene Trainingsarten benötigt man in der Regel unterschiedliche Einstellungen der Parameter -- z.B. für Abstände zwischen den Zeichen und für Wortabstände, für die Längen von Zeichengruppen oder Wörtern usw. Wenn man von einer Trainingsart zur nächsten wechselt, sind jedes Mal verschiedene Einstellungen zu ändern. + +Um dies zu erleichtern, kann man "Schnappschüsse" der Einstellungen verwenden: Nachdem alles für die ersten Trainingsart eingestellt wurde, speichert man alle aktuellen Parameter in einem von acht ""Snapshots"; dann macht man dasselbe mit den anderen Trainingsarten. Man kann die Einstellungen dann schnell abrufen, indem man einen bestimmten Snapshot zurück holt. + +TIP: Die eingestellte "Koch-Lektion" wird auch im nichtflüchtigen Speicher abgelegt und steht somit nach einem Neustart zur Verfügung, wird aber nicht in den Snapshots gespeichert oder von einem Snapshot überschrieben. Dies gilt auch für die WLAN Einstellungen, den "Serial Out" Parameter, oder die Einstellungen von Geschwindigkeit und Lautstärke. + +==== Speichern eines Snapshots + +Doppelklicke zunächst, um in das Parametermenü zu gelangen. Nun kannst du nach längerem Drücken der ROTEN Taste mit dem Drehgeber wählen, an welcher Stelle die aktuellen Einstellungen gespeichert werden sollen, von "Snapshot 1" bis "Snapshot 8"; eine weitere Option lautet "Cancel Store" und ermöglicht das Aussteigen ohne Speichern eines Snapshots. Snapshot-Speicherorte, die bereits in Gebrauch sind, werden in **fett** angezeigt, aber man kann auch diese überschreiben. Ein Klick auf den schwarzen Knopf speichert den Schnappschuss an der gewünschten Stelle und zeigt kurz an, dass gespeichert wurde. + +==== Abrufen eines Snapshots + +Doppelklicke zunächst, um in das Parametermenü zu gelangen. Nun kannst du nach längerem Drücken der ROTEN Taste mit dem Drehgeber wählen, an welcher Stelle die aktuellen Einstellungen gespeichert werden sollen, von "Snapshot 1" bis "Snapshot 8"; eine weitere Option lautet "Cancel Store" und ermöglicht das Aussteigen ohne Speichern eines Snapshots. Snapshot-Speicherorte, die bereits in Gebrauch sind, werden in **fett** angezeigt, aber man kann auch diese überschreiben. Ein Klick auf den schwarzen Knopf speichert den Schnappschuss an der gewünschten Stelle und zeigt kurz an, dass gespeichert wurde. +Auch hier steigt man mit einem Doppelklick auf den schwarzen Knopf zuerst in das Parametermenü ein. Nun kann man nach einem **kurzen** Klick auf die ROTE Taste mit dem Drehgeber auswählen, welche der gespeicherten Schnappschüsse man abrufen möchte, was mit Klicken auf die schwarze Drehgebertaste erfolgt; eine weitere Option lautet "Cancel Recall" und +ermöglicht das Aussteigen ohne einen Snapshot abzurufen. Wenn keine Schnappschüsse gespeichert sind, erhält man eine Meldung "NO SNAPSHOTS" und man steigt mit einem Druck auf eine beliebige Taste wieder aus. + +==== Löschen eines Snapshots + +Man kann Snapshots auch löschen, die nicht mehr benötigt werden oder versehentlich erstellt wurden. Gehe dabei so vor, als ob du einen Snapshot abrufen möchtest, wähle den zu löschenden Snapshot mit dem Drehgeber aus, und klicke dann auf die ROTE Taste zum Löschen. Wie beim Speichern und Abrufen von Snapshots zeigt eine kurze Meldung an, dass die Aktion erfolgreich war. + + +=== Liste aller Morserino-32 Parameter +Fettgedruckte Werte sind Standard- oder empfohlene Werte. Beim Aufruf aus dem Startmenü stehen alle Parameter zum Ändern zur Verfügung, beim Aufruf aus einem laufenden Modus nur jene, die für diesen Modus relevant sind. + +===== Allgemeine Parameter + +Manche Parameter sind von eher genereller Natur und darum für alle Modi des Morserino-32 relevant. + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Beschreibung +|Werte + + +| Encoder Click | Das Drehen des Drehgebers kann einen kurzen Tonimpuls erzeugen oder ohne Ton erfolgen | Off / On + +| Tone Pitch Hz | Die Frequenz des Mithörtons, in Hz | Eine Reihe von Tönen zwischen 233 und 932 Hz, die den musikalischen Noten der B-Dur-Skala von b zu b'' entsprechen (2 Oktaven). + +| Time Out | Wenn die in diesem Parameter angegebene Zeit ohne Aktualisierung der Anzeige vergeht, geht das Gerät in den Tiefschlafmodus. Man kann es durch Drücken der ROTEN Taste neu starten. | No timeout (kein Timeout)/ **5 min** / 10 min / 15 min + +| Quick Start | Ermöglicht es (gesetzt auf ON), die anfängliche Menüauswahl zu umgehen, d.h. das Gerät beginnt beim Start sofort mit der Ausführung des Modus, der vor dem letzten Ausschalten wirksam war. | ON / **OFF** + +| Serial Output | Damit wird gesteuert, was an die serielle Schnittstelle (USB-Anschluss) gesendet wird; es wird zwischen Zeichen vom Iambic-Keyer, decodierten Zeichen (vom CW-Decoder oder einer Handtaste) und "generierten" Zeichen (vom CW-Generator usw., auch von der Empfängerseite des LoRa- oder WiFi-Transceiver-Modus) unterschieden. "Nothing" sendet keines dieser Zeichen (aber bestimmte System- oder Fehlermeldungen können trotzdem erscheinen), während "All" alles sendet. Darüber hinaus können über das "M32 Serial Protocol" weitere Informationen über die serielle Schnittstelle gesendet und empfangen werden, sofern die angeschlossene Computersoftware dies unterstützt. +Siehe auch <>. | Nothing / Keyer / Decoded / Keyed+Decoded / Generated / **All** (default seit 4.3) + +|=== + + +===== Parameter bezüglich Taste, Paddles und Keyer + +Diese Parameter steuern das Verhalten der Paddles (eingebaut oder extern), insbesondere auch die Timing-Parameter, die für Iambic Keying oder eine externe Handtaste relevant sind (stellen Sie den *Keyer Mode* auf *Straight Key* ein, um eine Handtaste zu verwenden). + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + +| Paddle Polarity | Legt fest, welche Paddelseite für Dits ist und welche für Dahs. | ` _. dah-dit` / **`._ di-dah`** + +| External Pol. | Ermöglicht die Umkehrung der Polarität eines externen Paddels. Verwende dies, wenn das externe Paddel "falsch herum" verdrahtet ist, so dass Punkte und Striche des internen und externen Paddels alle auf der gleichen Seite liegen. | Normal / Reversed + + +| Keyer Mode | Wählt den Iambic Mode (A oder B), Ultimatic, Non-Squeeze oder Straight Key; siehe den Abschnitt <> | Curtis A / Curtis B / Ultimatic / Non-Squeeze / Straight Key + +| CurtisB DahT% | Timing im Curtis B Mode für dahs, in Prozent; siehe den Abschnitt <> | 0 -- 100, in Schritten of 5 [**35 -- 55**] + +| CurtisB DitT% | Timing im Curtis B mode für dits, in Prozent; siehe den Abschnitt <> | 0 -- 100, in Schritten von 5 [**55 -- 80**] + +| AutoChar Spce | Minimaler Abstand zwischen den Zeichen, in Längen eines Dits. | Off / min. 2 / **3** / 4 dots + +| Latency | Legt fest, wie lange nach dem Erzeugen des aktuellen Elements (Punkt oder Strich) die Paddel "taub" sind. Wenn der Wert 0 ist, muss man das Paddel loslassen, während das letzte Element noch "an" ist. Ist der Wert auf 7 eingestellt, reagieren die Paddel erst nach 7/8 einer Punktlänge auf eine Berührung. | Ein Wert zwischen 0 und 7, also 0/8 bis 7/8 einer Punktlänge (Standardwert ist **4**, d.h. eine halbe Punktlänge). + +|=== + +===== Parameter bezüglich Koch Zeichenfolge + +Wenn du an Kursen einer bestimmten Morseschule teilnimmst, wird dir diese die Morsezeichen in einer bestimmten Reihenfolge vorstellen. Hier kann man auswählen, welcher Reihenfolge man folgen möchte. + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + +| Koch Sequence | Dies bestimmt die Reihenfolge der Zeichen, wenn man die Koch-Methode zum Lernen und Trainieren verwendet. Man kann hier auch seinen spezifischen zeichensatz zum Üben definieren, mit der Option 'Custom Chars' - siehe das Kapitel <> (letzter Absatz).| **M32** (Native Ordnung des M32, dieselbe wie bei JLMC - Just Learn Morse Code) / LCWO / CW Academy / LICW Carousel / Custom Chars / + +| LICW Carousel | Damit wird der "Einstiegspunkt" ins Curriculum des LICW Clubs gewählt (dies ist nur relevant, wenn der Parameter "Koch Sequence" auf "LICW Carousel" gesetzt ist). Wenn du einen Kurs innerhalb des Carousel BC1 beginnst, sollst du diesen Parameter entsprechend setzen; und dann wieder, wenn du mit einem Kurs in BC2 einsteigst. | **BC1: r e a** / BC1: t i n / BB1: p g s / BC1: l c d / BC1: h o f / BC1: u w b / BC2: k m y / BC2: 5 9 , / BC2: q x v / BC2: 7 3 ? / BC2: ar sk = / BC2: 1 6 . / BC2: z j / / BC2: 2 8 bk / BC2: 4 0 + +|=== + + +===== Parameter bezüglich CW Generierung + +Die folgenden Parameter steuern, wie Zeichen zufällig generiert und abgespielt werden, oder wie Textdateien als Morsezeichen abgespielt werden. Ich möchte hier insbesondere auf „Interchar Spc“ und „Interword Spc“ verweisen, da man mit diesen Parametern das erreichen kann, was auch als „Farnsworth-Geschwindigkeit“ bzw. „Wordsworth-Geschwindigkeit“ bekannt ist. Diese Parameter sind natürlich auch für den Echo Trainer von Relevanz! + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + +| Interchar Spc | Der zeitliche Abstand (als Anzahl von Dits) der zwischen Zeichen eingefügt wird (siehe den Abschnitt <>) | 3 -- 15 [**3**] + +| Interword Spc | Der zeitliche Abstand (als Anzahl von Dits) der zwischen Wörtern eingefügt wird (siehe den Abschnitt <>) | 6 -- 45 [**7**] + +| Random Groups | Für die Ausgabe von Gruppen von zufälligen Zeichen legt man hier fest, welche Zeichenuntermengen inkludiert sein sollen. | Alpha (Buchstaben)/ Numerals (Ziffern) / Interpunct. (Satzzeichen) / Pro Signs (Betriebsabk.)/ Alpha + Num / Num+Interp. / Interp+ProSn / Alpha+Num+Int / Num+Int+ProS / All Chars (Alle) + +| Length Rnd Gr | Hier wählt man aus, wie viele Zeichen in jeder Gruppe von zufälligen Zeichen enthalten sein sollen; traditionell sind das 5, aber für das Training kann es sinnvoll sein, mit einer kleineren Zahl zu beginnen. | Fixe Längen 1 -- 6, und 2 to 3 -- 2 to 6 (Länge nach dem Zufallsprinzip innerhalb dieser Grenzen gewählt) [**5**] + +| Length Calls | Hier wählt man die maximale Länge der erzeugten Rufzeichen aus. | Unlimited (unbegrenzt) / max. 3 -- max. 6 + +| Length Abbrev | Hier wählt man die maximale Länge der zufällig erzeugten allgemein üblichen CW-Kürzel und Q-Gruppen aus. | Unlimited (unbegrenzt) / max. 2 -- max. 6 + +| Length Words | Hier wählt man die maximale Länge der zufällig generierten allgemeinen englischen Wörter aus. | Unlimited (unbegrenzt) / max. 2 -- max. 6 + +| Max # of Words | Wenn die angegebene Anzahl von Wörtern oder Buchstabengruppen erzeugt wurde, erzeugt der Morserino-32 ein abschließendes AR ("+"), um anzuzeigen, dass diese Sequenz beendet ist, und pausiert dann und wartet - nach Berühren eines Paddels (oder einem Klick auf den schwarzen Knopf) fährt er fort und erzeugt die nächste Folge von Wörtern. (Falls die Option "Auto Stop" aktiv ist, wird dieser Parameter im CW Generator ignoriert.) | **Unlimited** (unbegrenzt) / 5 bis 250 in Schritten von 5 + +| Stop/Next/Rep | Wenn auf ON gestellt, stoppt die Erzeugung von Morsezeichen nach jedem Wort im CW-Generator-Modus (oder Koch Generator Modus), um das Erlernen des Gehörlesens zu erleichtern. Nach Berühren des rechten Paddels wird das nächste Wort abgespielt, und durch Berühren des linken Paddles wird das Wort wiederholt. Diese Option und die Option 'Each Word 2x' sind nicht miteinander kompatibel; setzt man die eine auf ON, wird die andere automatisch auf OFF gesetzt.| ON / **OFF** + +| CW Gen Displ | Hier wählt man aus, wie der CW Trainer bzw. LoRa oder WiFi Transceiver anzeigen soll, was er erzeugt bzw. empfängt. | Display off (keine Anzeige)/ **Char by Char** (Zeichen für Zeichen) / Word by word (Wort für Wort) + +| Randomize File | Wenn auf "On" gesetzt, überspringt der File Player nach jedem gesendeten Wort n Wörter (n = Zufallszahl zwischen 0 und 255). | **Off** / On + +| Each Word 2x | Im CW Generator Modus wird bei Stellung "ON" jedes "Wort" (Zeichen zwischen den Leerzeichen) zweimal ausgegeben, um zu lernen, nach Gehör mitzulesen. Diese Option und die Option 'Stop/Next/Rep' sind nicht miteinander kompatibel; setzt man die eine auf ON, wird die andere automatisch auf OFF gesetzt. Es gibt drei ON-Möglichkeiten: normal (ein eventuell gesetzter vergrößerter Zwischenzeichenraum (inter-character space) wird bei der Wiederholung beibehalten); ON less ICS: der Zwischenraum wird verkleinert; ON true WpM: der vergrößerte Zwischenraum zwischen Zeichen wird bei der Wiederholung ignoriert.| **OFF** / ON / ON (less ICS) / ON (true WpM) + +|=== + +===== Parameter bezüglich Echo Trainer + +Die folgenden Parameter steuern die wesentlichen Eigenschaften des Echo Trainers (Tone Shift ist allerdings auch für die Transceiver Modi interessant). + + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + + +|Echo Repeats |Hier stellt man ein, wie oft ein Wort wiederholt wird, wenn die Antwort entweder zu spät oder fehlerhaft ist, bevor der Echo Trainer ein neues Wort generiert. Wenn der Wert 0 ist, dann ist das nächste Wort immer ein neues, unabhängig davon, ob die Reaktion richtig oder falsch war. | 0 -- 6 / Forever (unbegrenzte Wiederholung, bis die Antwort richtig ist) + +|Echo Prompt | Hiermit wird festgelegt, wie man im Echo-Trainer-Modus zur Eingabe aufgefordert wird. Die möglichen Einstellungen sind: "Sound Only" (nur Töne; Standardwert; am besten zum Gehörlesen Lernen), "Display only" (das Wort, das man eingeben soll, wird auf dem Display angezeigt, es wird kein hörbarer Code erzeugt; gut zum Geben Lernen mit dem Paddel) und "Sound & Display", d.h. man hört die Eingabeaufforderung UND man kann sie auf dem Display sehen. | **Sound only** / Display only / Sound&Displ + +| Confrm. Tone | Hiermit wird festgelegt, ob im Echo-Trainer-Modus ein akustischer Bestätigungston ausgegeben werden soll. Wenn man das ausschaltet (Off), wiederholt das Gerät nur die Eingabeaufforderung, wenn die Antwort falsch war, oder sendet eine neue Eingabeaufforderung. Die optische Anzeige von "OK" oder "ERR" ist aber auch nach dem Ausschalten des Tons sichtbar. | **On** / Off + +| Tone Shift | Die Tonhöhe des Tons, wenn man im Echo Trainer Modus oder oder einem Transceiver Modus sendet, kann entweder die gleiche sein wie die, die man von der Gegenstation (bzw. bei der Aufforderung im Echo Trainer Modus) hört, oder kann einen Halbton höher oder einen Halbton tiefer sein. |**No Tone Shift** / Up 1/2 Tone / Down 1/2 Tone + +| Adaptv. Speed | Wenn diese Option auf ON gesetzt ist, wird die Geschwindigkeit um 1 WpM erhöht, wenn man im Echo Trainer-Modus eine korrekte Antwort gegeben hat, und um 1 verringert, wenn die Antwort fehlerhaft war. | ON / **OFF** + +|=== + +===== Parameter bezüglich Senden und Decodieren + +Diese Parameter steuern einige Funktionen, die für die Übertragung (entweder direkt über LoRa oder WLAN oder durch Tasten eines externen Senders) oder für die Dekodierung von Morsezeichen verfügbar sind. + +[cols="2,6,3",options=header] +|=== +|Parameter Name +|Description +|Values + +|Key ext TX | Hier legt man fest, ob ein angeschlossener Sender bei der Verwendung des Gerätes getastet wird. Gen = Generator modi, RX = LoRa and Internet receiver | Never (niemals) / **CW Keyer only** (nur beim CW Keyer) / Keyer & Gen. / Keyer&Gen.&RX + +| Generator Tx (früher "Send via LoRa" genannt) | Damit kann der CW-Generator senden, was er generiert, entweder über LoRa oder über WLAN. Auf diese Weise kann ein M32 etwas generieren und mehrere andere die gleiche Sequenz empfangen. Dies kann in allen Modi CW Generator und Koch / CW Generator verwendet werden, einschließlich File Player. Könnte für Gruppen von Lernenden nützlich sein, da man z.B. den Inhalt einer Datei an eine Gruppe von Personen übermitteln kann. Dies sollte natürlich nur mit Vorsicht (und nicht über einen längeren Zeitraum) auf öffentlichen M32-Chat-Servern verwendet werden, kann jedoch für eine Gruppe im selben Netzwerksegment, die Broadcast als TrX-Peer verwendet, oder einen privat eingerichteten Chat-Server sehr nützlich sein, oder über LoRa, wenn alle Teilnehmer nahe genug beieinander sind. +Beachte, dass beim Senden über LoRa eine Antenne angeschlossen sein muss, da sonst der LoRa-Transceiver möglicherweise zerstört wird! | ** „Tx OFF“ ** (= generiertes CW nicht übertragen), „LoRa Tx ON“ (generiertes CW über LoRa übertragen) und „WiFi Tx ON“ (generiertes CQ über WiFi übertragen). + +| LoRa Channel | Wählt aus, welchen virtuellen Kanal LoRa verwendet. | **Standard Ch** / Secondary Ch + +| Bandwidth | Definiert die Bandbreite, die der CW-Decoder verwendet (dies ist in Software mit einem so genannten Goertzel-Filter implementiert). (Wide (breit) = ca. 600 Hz, Narrow (schmal) = ca. 150 Hz; Mittenfrequenz = ca. 700 Hz) | **Wide** / Narrow + +| Decoded on I/O | Normalerweise wird dekodiertes CW, das aus einer externen Quelle stammt (in einem der Transceiver Modi, oder beim Dekodieren von externem Audio) auf dem Lautsprecher (oder Kopfhörer) des M32 wiedergegeben, aber nicht an den externen I/O Ausgang gesendet. Wird dieser Parameter auf "On" gesetzt, wird der Ton des Dekoders auch an den externen I/O Ausgang geliefert. | On / **Off** + +|=== + +== Anhänge + +=== Anhang 1: Hardware Konfiguration (LoRa und Kalibrierung der Batterie-Messung) + +Es gibt ein Hardware-Konfigurationsmenü, welches man erreicht, indem man das Paddle (oder ein externes Paddle bzw. eine Handtaste) beim Einschalten des Morserino-32 gedrückt hält. Man kann dann die gewünschte Konfiguration durch Drehen des Drehreglers auswählen, und mit einem Druck auf den schwarzen Knopf aktivieren. + +Die Wahlmöglichkeiten sind "Calibr. Batt." (Kalibrierung der Batteriemessung), "LoRa Config." (Einstellen der LoRa Konfiguration entsprechend des benutzten Heltec-Moduls) und "Cancel" (Verlassen des Menüs ohne etwas zu ändern und Fortsetzung des normalen Hochfahrens des M32). + + +==== Anhang 1.1: LoRa-Bänder, Frequenzen und Senderleistung konfigurieren [[appendix1_1]] + +Hat man ein Standard 433 MHz Heltec Modul im Morserino-32, ist er bereits für das richtige Band und eine Standardfrequenz innerhalb dieses Bandes vorkonfiguriert. + +IMPORTANT: Wenn man entweder die Frequenz innerhalb des Standardbandes ändern muss, oder ein Heltec-Modul für die Bänder 868 und 920 MHz verwenden will, muss man den Morserino-32 konfigurieren, bevor man die LoRa-Funktionen nutzt. + +Die folgenden Bänder und Frequenzbereiche können im Morserino-32 für Heltec Module konfiguriert werden, die die oberen UHF LoRa Frequenzen unterstützen: + +* 868 MHz Band: + 866,25 bis 869,45 MHz in Schritten von 100 kHz (Standard: 869,15 MHz) +* 920 MHz Band: + 920,25 bis 923,15 MHz in Schritten von 100 kHz (Standard: 920,55 MHz) + +Die Standard Heltec-Module unterstützen nur das 433 MHz-Band, und der Morserino-32 kann so konfiguriert werden, dass er 433,65 bis 434,55 MHz in Schritten von 100 kHz (Standard: 434,15 MHz) verwendet. + +**Um den Morserino-32 für nicht standardisierte Frequenzen und Bänder, oder die Sendeleistung zu konfigurieren, gehe bitte wie folgt vor:**. + + +* Schalte den M32 ein, während du die Touch Paddles (oder externen Paddles, oder eine externe Handtaste) gedrückt hältst. +* Sobald eine Nachricht erscheint, lass den schwarzen Knopf los. +* Zuerst wird man aufgefordert, das gewünschte Band auszuwählen (wähle 433 für das Standard-LoRa-Modul und entweder 868 oder 920 für das obere UHF-LoRa-Modul); drehe den Drehgeber auf das gewünschte Band und klicke einmal kurz auf den schwarzen Knopf. **Die Bandauswahl muss für das verwendete Heltec-Modul passen!** +* Jetzt wird man aufgefordert, eine Frequenz innerhalb des ausgewählten Bandes auszuwählen. Die erste angezeigte Frequenz ist die Standardeinstellung für dieses Band - wenn das in Ordnung ist, klicke einfach einmal auf den schwarzen Knopf, andernfalls wähle eine Frequenz aus, indem du den Drehgeber drehst und auf den Knopf klickst, sobald Sie die richtige Frequenz gefunden wurde. +* In einem weiteren Schritt kann man die Ausgangsleistung des LoRa-Transceivers konfigurieren. Der Standardwert beträgt 14 dBm (= 25 mW) und kann in mehreren Schritten zwischen 10 dBm (= 10 mW) und 20 dBm (= 100 mW) eingestellt werden. **Beachte die geltenden Vorschriften in Ihrem Land. Möglicherweise gibt es eine gesetzliche Begrenzung der Ausgangsleistung!** Beachte außerdem, je höher die Ausgangsleistung ist, umso höher ist auch das Risiko der Zerstörung des LoRa-Transceivers, wenn er ohne ordnungsgemäße Terminierung (durch eine geeignete Antenne oder eine Dummy-Load) verwendet wird. +* Unmittelbar danach startet der Morserino-32 normal, mit den nun ausgewählten LoRa-Einstellungen. In der oberen Zeile des Startbildschirms sieht man die konfigurierte QRG für LoRa als 5-stellige Zahl (z.B. 43415 für die Voreinstellung im 433 MHz-Band). + + +==== Anhang 1.2: Kalibrierung der Batteriemessung [[appendix1_2]] + +Die in den Heltec-Modulen vorgesehene Messung der Batteriespannung ist leider recht unzuverlässig. Anscheinend spielen dabei mehrere Faktoren eine Rolle: ein Messfehler innerhalb des ESP32 Prozessors aufgrund kleiner Variationen der Referenzspannung des Chips (ein hier wohl vernachlässigbar kleiner Messfehler), und Probleme mit dem Spannungsteiler auf dem Heltec Modul, der zur Messung verwendet wird (dies resultiert in einem ziemlich großen Fehler und hoher Variabilität zwischen verschiedenen Modulen). Obwohl das Messen der Batteriespannung kein elementares Problem für den Betrieb des M32 darstellt, ist dies doch ein lästiger Fehler, der auch dazu führen kann, dass sich der M32 nicht mehr einschalten lässt, da die Firmware meint, die Batteriespannung sei zu niedrig, obwohl dies gar nicht der Fall ist. + +Um die Messung der Batteriespannung zu kalibrieren, musst du zuerst einmal die Spannung des Akkus mit einem Multimeter feststellen. Sobald dieser Wert bekannt ist, führe folgende Schritte aus: + +* Schalte den M32 ein, während du die Touch Paddles (oder externen Paddles, oder eine externe Handtaste) gedrückt hältst. +* Wähle die Option "Calibr. Batt." mit dem Drehregler. +* Man sieht nun eine Spannungsanzeige am Display (in Millivolt). Dreh nun den Drehregler in die eine oder andere Richtung, bis der angezeigte Wert möglichst nahe am gemessenen Wert der Batteriespannung liegt. +* Drück den Schwarzen Knopf um den Kalibrierungswert zu speichern, und um mit dem Hochfahren des M32 fortzufahren. + + +=== Anhang 2: Einstellen des Audioeingangspegels [[appendix2]] + +Man kann noch eine **andere Funktion** erreichen, während man sich innerhalb des Startmenüs befinden - nicht durch eine Menüauswahl, sondern durch **einen langen Druck auf die rote Taste**: + +Dies startet eine Funktion zur Einstellung des Audioeingangspegels: Stelle sicher, dass am Eingang ein Tonsignal anliegt, z.B. von einem Kurzwellenempfänger (siehe <> am Anfang dieses Dokuments, #2), und ein Balken zeigt die Spannung des Eingangssignals an. Stelle es mit dem blauen Trimmerpotentiometer so ein, dass sich das linke und rechte Ende des hellen Balkens innerhalb der beiden äußeren Rechtecke befinden. Gleichzeitig wird in dieser Betriebsart beim Line-Out Ausgang ein Sinussignal ausgegeben und der Transceiverausgang kurzgeschlossen (Tastet einen Senders, falls dieser an Anschluss #1 angeschlossen ist - trenne zuerst deinen Transceiver, wenn dies nicht das ist, was du willst!). Man kann nun z.B. den Pegel des Ausgangssignals an einem angeschlossenen Computer einstellen, oder überprüfen, ob ein Sender getastet wird. + +Ein einfacher Test oder eine Demo für die Audio-In-Einstellung besteht darin, den Line-Out mit dem Audio-In-Anschluss zu verbinden (verbinde dazu "Tip" (Spitze) mit "Sleeve"(Hülse)) und den Ausgangston in den Audio-Eingang einzuspeisen. Man kann sehen, wie sich der Balken ändert, wenn man das Potentiometer dreht, wobei an einem Ende des Potentiometerbereichs nur ein winziger Balken in der Mitte verbleibt und die beiden Rechtecke an beiden Enden des Diagramms freigelegt werden (im Wesentlichen misst man dann nur nur das Rauschen am Eingang der Operationsverstärkers). Am anderen Ende des Potentiometer-Drehbereichs geht hingegen der Balken über die äußeren Enden der Rechtecke hinaus. Nun kann man den Pegel mit dem Potentiometer so einstellen, dass der helle Balken die Außengrenzen der Rechtecke **fast** berührt. Dies ist die optimale Einstellung für den Audioeingang. Natürlich muss man dies für die Audioquelle tun, die man verwenden möchte, z.B. für einen Radioempfänger. + +TIP: Nur wenn Sie sich im Menü befinden, aktiviert **Langes Drücken der ROTEN Taste**die Pegeleinstellfunktion. Während man einen der Morserino-Modi (Keyer, Generator, Echo-Trainer, Transceiver usw.) ausführt, aktiviert ein langer Druck auf die ROTE Taste den Scroll-Modus der Anzeige, damit man Text lesen kann, der bereits weggescrollt ist! + +=== Anhang 3: Firmware Update Prozedur für Versionen < 2.0 [[appendix3]] + +Bei den Firmware Versionen 1.x waren die WLAN Funktionen nicht über das Hauptmenü erreichbar, sondern durch dreimaliges schnelles Drücken des ROTEN Knopfes. Damit ist die Update-Prozedur folgendermaßen auszuführen: + +Falls noch nicht geschehen, muss zuerst die WLAN Konfiguration durchgeführt werden. + +Während der Morserino-32 das Startmenü anzeigt, klicke ***drei mal*** schnell auf die ROTE Taste, um in das WLAN-Menü zu gelangen. Der oberste Eintrag lautet "WiFi Config". Wähle ihn aus, um fortzufahren. + +Das Gerät startet WiFi als Zugangspunkt (wie ein WLAN Router) und erstellt so ein eigenes WLAN-Netzwerk (mit der SSID "Morserino"). Wenn man die verfügbaren Netzwerke mit dem Computer oder Smartphone überprüft, wird man es leicht finden. Verbinde nun deinen Computer (oder dein Handy) mit diesem Netzwerk (man braucht kein Passwort, um eine Verbindung herzustellen). + +Wenn du verbunden bist, gib "m32.local" in deinen Browser ein. Falls dein Computer oder Smartphone mDNS nicht unterstützt (Android tut das zB. nicht), musst du die IP Adresse 192.168.4.1 in den Browser eingeben. Es erscheint nun ein kleines (leeres) Formular mit nur zwei Feldern im Browser: "SSID" und "Password". Gib den Namen deines lokalen WLAN-Netzwerks und das entsprechende Passwort ein und klicke auf die Schaltfläche "Submit". Der Morserino-32 speichert diese Netzwerk-Anmeldeinformationen und führt einen Neustart durch (das Netzwerk "Morserino" wird verschwinden). + +Rufe nun erneut das WLAN-Menü auf, indem du dreimal schnell auf die ROTE Taste klickst und die Option "**Update Firmw.**" auswählst. Ähnlich wie beim Hochladen von Dateien geh mit dem Browser zu "m32.local" (oder der am Display gezeigten IP Adresse), und schließlich wird wieder ein Anmeldebildschirm angezeigt. Dazu verwende nunmehr den Benutzernamen "**m32**" und das Passwort "**update**". + +Als Nächstes wird wieder ein Dateiauswahldialog angezeigt, man wählt die gespeicherte Binärdatei aus und klickt auf die Schaltfläche "Begin". Diesmal dauert das Laden länger - es kann einige Minuten dauern, man sollte also etwas Geduld haben. Die Datei ist groß, muss hochgeladen und in den Morserino-32 geschrieben werden und dann überprüft werden, um sicherzustellen, dass es sich um eine ausführbare Datei handelt. Schließlich startet das Gerät neu und man sollte während des Startvorgangs die neue Versionsnummer auf dem Display sehen können. + +Natürlich man die Firmware auch über USB aktualisieren, wenn derzeit noch eine ältere Softwareversion verwendet wird (siehe nächster Anhang). + +=== Anhang 4: Aktualisieren der Firmware über USB [[appendix4]] + +Dieses einfache Update-Verfahren, das derzeit für das Windows-Betriebssystem verfügbar ist, wurde durch Arbeiten von Matthias Jordan und Joe Wittmer ermöglicht. + +Stelle zunächst sicher, dass du einen Treiber für das USB-zu-Seriell CP210x von Silicon Labs installiert hast, der vom Heltec Modul für seine USB-Schnittstelle verwendet wird. Aktuelle Versionen von Windows 10 installieren dies automatisch. Wenn dies bei dir nicht der Fall ist, kannst du den Treiber von hier herunter laden: +    https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers + +Um zu überprüfen, ob du den passenden Treiber installiert hast, und um festzustellen, mit welchem ​​Port er verbunden ist, öffne den Gerätemanager auf deinem Computer (gib im Suchfeld unten links auf dem Bildschirm "Gerätemanager" ein und der Gerätemanager wird zur Auswahl angezeigt). + +Verbinden nun den Morserino mithilfe eines USB-Kabels mit dem Computer. Der Gerätemanager sollte seine Anzeige aktualisieren und einen Eintrag "Ports" anzeigen - öffnen diesen, und es sollte Folgendes angezeigt werden: Silicon Labs CP210x ... (COM3). Könnte in deinem Fall natürlich ein anderer COM-Port sein, notiere also den korrekten Portnamen. + +TIP: Stelle sicher, dass du ein ein "richtiges" USB-Kabel verwendest, kein Kabel das nur zum Aufladen geeignet ist! + +Lade jetzt das Update-Dienstprogramm aus Joes GitHub-Repository herunter: +https://github.com/joewittmer/Morserino-32-Firmware-Updater/releases + +Entpacke diese Datei. Darin ist ein Programm "update_m32.exe" - kopiere es in einen Ordner deiner Wahl (normalerweise verwende ich den Ordner ""Downloads"). Hole nun auch die binäre Morserino-Datei für die Version, die du installieren möchtest, vom Morserino GitHub, idealerweise in das selbe Verzeichnis. + +Öffne nun ein Befehlsfeld auf dem Computer (gib im Suchfeld unten links auf dem Bildschirm "cmd" ein). + +Zuerst führe "cd" (change directory) zu dem Verzeichnis aus, in das du das Programm und die Binärdatei gespeichert hast; z.B., falls dies das "Downloads" Verzeichnis war: + +`cd Downloads` + +Gib nun die folgende Befehlszeile ein: + +`update_m32 -p -f ` + +Ersetze durch den korrekten COM-Port-Namen und durch den korrekten Namen der Morserino-Binärdatei. In meinem Fall war dies: + +`update_m32 -p COM3 -f m32_v4.1.ino.wifi_lora_32_V2.bin` + +Nach kurzer Zeit sollte der Morserino-32 neu starten und die aktualisierte Versionsnummer anzeigen. + + +=== Anhang 5: Verwenden des seriellen Ausgangs des Morserino-32 [[appendix5]] + +Der Morserino-32 kann Daten über die serielle USB-Schnittstelle ausgeben. Hiermit kann man beispielsweise die Zeichen anzeigen, die auf dem Display gezeigt werden, auch in einem Terminalfenster eines Computers anzeigen. Auf diese Weise kann man die Morserino-Ausgabe mit einem großen Bildschirm oder einem Projektor sichtbar machen. Dies kann für Ausstellungen oder auch bei Schulungen nützlich sein. + +Man muss eine Baudrate von 115200 für das Terminal einstellen. + +Man kann diese Funktion auch in Verbindung mit Computersoftware verwenden, die speziell für den Morserino-32 entwickelt wurde, um dessen Trainingsfunktionen zu verbessern. + +Derzeit sind zwei Softwareprodukte für diesen Zweck verfügbar: + +* CW Trainer für Morserino von Enzo, IW7DMH (siehe https://iw7dmh.jimdofree.com/other-projects/cw-trainer-for-morserino-32/) und + +* Morserino Phrases Trainer von Tommy, OZ1THC (siehe https://github.com/Tommy-de-oz1thc/Morserino-32-Phrases-trainer). + +Siehe auch die Hinweise zum Parameter "Serial Output" im Kapitel <>. diff --git a/Documentation/User Manual/Version 5.x/m32_user-Manual_v5_de.pdf b/Documentation/User Manual/Version 5.x/m32_user-Manual_v5_de.pdf new file mode 100644 index 0000000..eaccbee Binary files /dev/null and b/Documentation/User Manual/Version 5.x/m32_user-Manual_v5_de.pdf differ diff --git a/Software/README.md b/Software/README.md index a100c6a..b81cae5 100644 --- a/Software/README.md +++ b/Software/README.md @@ -2,7 +2,9 @@ ## Build Instructions (for Arduino IDE) -It is now quite straightforward to set up an environment to build the Morserino-32 binary code from the source. As we are using now the latest ESP32 libraries from Heltec, and the source of the Clivkbutton library has been included into the source, there is no need to hint for libraries. +It is now quite straightforward to set up an environment to build the Morserino-32 binary code from the source. As we are using now the latest ESP32 libraries from Heltec, and the source of the Clivkbutton library has been included into the source, there is no need to hunt for libraries. + +**NOTICE: As Heltec changed their development environment libraries to accomodate newer products, you might need to set up an environment with their older library versions!** 1. Set up the Arduino IDE from . @@ -16,6 +18,29 @@ It is now quite straightforward to set up an environment to build the Morserino- ## Change History +### Changes V.5.0 + +#### Bug fixes: + +* `` was not recognized as pro sign in file player. Fixed. +* `` was only recognized once in echo Trainer mode, so you could only correct one error within a word. Fixed: it behaves now like a backspace key. +* Some inconsistencies in displaying snapshot functions and hardware calibration functions. Fixed. +* Paddle polarity was the opposite as shown in the preferences. Fixed. +* Touching touch paddles at power-on to get into hardware configuration menu was not very reliable. Hopefully fixed. + +#### Feature Modifications / Improvements: + +* `\c` or `` marks the begin of a comment in a file player text file - these characters or the word containing one of these and the rest of the line are ignored. +* The order of parameters in the parameter menu has been changed slightly to make the grouping a bit more logical. The user manual has been changed to make sure the order in the manual is the same as in the M32. + +#### New Feature(s): + +* Implementation of a serial protocol (see separate documentation on GitHub). + + This allows (through some software on a computer connected through USB) screen or voice output of menus and settings (e.g. making the M32 usable for blind or visually impaired people), and also allows remote control of all Morserino features from the computer (like setting parameters, managing snapshots, changing speed, exiting and entering menus, uploading text files and even automated keying). The protocol is described in a separate document. +* LoRa output power is now configurable through the Hardware Config Menu, and can be set between 10 and 100 mW (10 - 20 dBm); previously it always used the default of 25 mW (14 dBm). Be aware that some countries have restriction regarding maximal power of LoRa - so check your mileage before changing the default to something higher! + + ### Changes V.4.5.2 #### Bug Fix(es): diff --git a/Software/binary/m32_v5.ino.wifi_lora_32_V2.bin b/Software/binary/m32_v5.ino.wifi_lora_32_V2.bin new file mode 100644 index 0000000..3b19ae3 Binary files /dev/null and b/Software/binary/m32_v5.ino.wifi_lora_32_V2.bin differ diff --git a/Software/binary/m32_v5.ino.wifi_lora_32_V2.md5 b/Software/binary/m32_v5.ino.wifi_lora_32_V2.md5 new file mode 100644 index 0000000..7cc3c1e --- /dev/null +++ b/Software/binary/m32_v5.ino.wifi_lora_32_V2.md5 @@ -0,0 +1 @@ +48b51c6ddc5497f0d7f3b1e3619a8f95 diff --git a/Software/src/Version 5/ClickButton.cpp b/Software/src/Version 5/ClickButton.cpp new file mode 100644 index 0000000..640bbf4 --- /dev/null +++ b/Software/src/Version 5/ClickButton.cpp @@ -0,0 +1,129 @@ +/* ClickButton + Based on an Arduino librery by raronzen@gmail.com that decodes multiple clicks on one button. + Also copes with long clicks and click-and-hold. + + Usage: ClickButton buttonObject(pin [LOW/HIGH, [CLICKBTN_PULLUP]]); + + where LOW/HIGH denotes active LOW or HIGH button (default is LOW) + CLICKBTN_PULLUP is only possible with active low buttons. + + + Returned click counts: + + A positive number denotes the number of (short) clicks after a released button + A negative number denotes the number of "long" clicks + + Copyright (C) 2010,2012, 2013 raron + + GNU GPLv3 license + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Contact: raronzen@gmail.com + +*/ + +#include "ClickButton.h" + + +ClickButton::ClickButton(uint8_t buttonPin) +{ + pin = buttonPin; + activeHigh = LOW; // Assume active-low button + _btnState = !activeHigh; // initial button state in active-high logic + _lastState = _btnState; + _clickCount = 0; + clicks = 0; + depressed = false; + _lastBounceTime= 0; + debounceTime = 20; // Debounce timer in ms + multiclickTime = 250; // Time limit for multi clicks + longClickTime = 1000; // time until long clicks register + pinMode(pin, INPUT); +} + +/* +ClickButton::ClickButton(uint8_t buttonPin, boolean activeType) +{ + pin = buttonPin; + _activeHigh = activeType; + _btnState = !_activeHigh; // initial button state in active-high logic + _lastState = _btnState; + _clickCount = 0; + clicks = 0; + depressed = 0; + _lastBounceTime= 0; + debounceTime = 20; // Debounce timer in ms + multiclickTime = 250; // Time limit for multi clicks + longClickTime = 1000; // time until long clicks register + pinMode(pin, INPUT); +} + +ClickButton::ClickButton(uint8_t buttonPin, boolean activeType, boolean internalPullup) +{ + pin = buttonPin; + _activeHigh = activeType; + _btnState = !_activeHigh; // initial button state in active-high logic + _lastState = _btnState; + _clickCount = 0; + clicks = 0; + depressed = 0; + _lastBounceTime= 0; + debounceTime = 20; // Debounce timer in ms + multiclickTime = 250; // Time limit for multi clicks + longClickTime = 1000; // time until "long" click register + pinMode(pin, INPUT); + // Turn on internal pullup resistor if applicable + if (_activeHigh == LOW && internalPullup == CLICKBTN_PULLUP) digitalWrite(pin,HIGH); +} + +*/ + +void ClickButton::Update() +{ + long now = (long)millis(); // get current time + _btnState = digitalRead(pin); // current appearant button state + + // Make the button logic active-high in code + if (!activeHigh) _btnState = !_btnState; + + // If the switch changed, due to noise or a button press, reset the debounce timer + if (_btnState != _lastState) _lastBounceTime = now; + + + // debounce the button (Check if a stable, changed state has occured) + if (now - _lastBounceTime > debounceTime && _btnState != depressed) + { + depressed = _btnState; + if (depressed) _clickCount++; + } + + // If the button released state is stable, report nr of clicks and start new cycle + if (!depressed && (now - _lastBounceTime) > multiclickTime) + { + // positive count for released buttons + clicks = _clickCount; + _clickCount = 0; + } + + // Check for "long click" + if (depressed && (now - _lastBounceTime > longClickTime)) + { + // negative count for long clicks + clicks = 0 - _clickCount; + _clickCount = 0; + } + + _lastState = _btnState; +} diff --git a/Software/src/Version 5/ClickButton.h b/Software/src/Version 5/ClickButton.h new file mode 100644 index 0000000..08f170f --- /dev/null +++ b/Software/src/Version 5/ClickButton.h @@ -0,0 +1,36 @@ +#ifndef ClickButton_H +#define ClickButton_H + +#if (ARDUINO < 100) +#include +#else +#include +#endif + + +//#define CLICKBTN_PULLUP HIGH + + +class ClickButton +{ + public: + ClickButton(uint8_t buttonPin); +// ClickButton(uint8_t buttonPin, boolean active); +// ClickButton(uint8_t buttonPin, boolean active, boolean internalPullup); + void Update(); + int clicks; // button click counts to return + boolean depressed; // the currently debounced button (press) state (presumably it is not sad :) + long debounceTime; + long multiclickTime; + long longClickTime; + uint8_t pin; // Arduino pin connected to the button - now public to chaneg assignement at runtime + boolean activeHigh; // Type of button: Active-low = 0 or active-high = 1 + + private: + boolean _btnState; // Current appearant button state + boolean _lastState; // previous button reading + int _clickCount; // Number of button clicks within multiclickTime milliseconds + long _lastBounceTime; // the last time the button input pin was toggled, due to noise or a press +}; + +#endif diff --git a/Software/src/Version 5/MorseDecoder.cpp b/Software/src/Version 5/MorseDecoder.cpp new file mode 100644 index 0000000..8f5ff03 --- /dev/null +++ b/Software/src/Version 5/MorseDecoder.cpp @@ -0,0 +1,258 @@ +/****************************************************************************************************************************** + * morse_3 Software for the Morserino-32 multi-functional Morse code machine, based on the Heltec WiFi LORA (ESP32) module *** + * Copyright (C) 2018-2020 Willi Kraml, OE1WKL *** + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + *****************************************************************************************************************************/ +#include "morsedefs.h" +#include "goertzel.h" +#include "MorseDecoder.h" +#include "MorseOutput.h" + +extern unsigned long genTimer; /// needed for echo trainer +extern morserinoMode morseState; +extern int leftPin; + +//////// methods for class Decoder ///////// + +Decoder::Decoder(boolean key) { + fromKey = key; // this instance should be audio or key? + setup(); + MorseTable myTable; +} + +void Decoder::setup() { + /// set up variables for Morse Decoder + if (!fromKey) + Goertzel::setup(); + filteredState = filteredStateBefore = realstate = realstatebefore = false; + lastStartTime = 0; + decoderState = LOW_; + ditAvg = 60; + dahAvg = 180; + d_wpm = 15; + nbtime = 1; +} + + +#define straightPin leftPin + + +boolean Decoder::checkInput() { /// check if we have a tone signal at A6 with Gortzel's algorithm or from a key (fromKey == true), and apply some noise blanking as well + /// we return true when we detected a change in state, false otherwise! + +///// check straight key first before you check audio in.... (unless we are in transceiver mode) +///// straight key is connected to external paddle connector (tip), i.e. the same as the left pin (dit normally) + +if (fromKey) { +// we also check the paddles - you can use them like a cootie key / sideswiper +// if (MorsePreferences::pliste[posCurtisMode].value == STRAIGHTKEY) // we only check the "tip" of the external straight key jack +// realstate = !digitalRead(straightPin); +// else + realstate = ((!digitalRead(straightPin)) || leftKey || rightKey) ; // we also check the paddles (also the capacitive ones) +} +else + realstate = Goertzel::checkInput(); + + + ////////////////////////////////////////////////////////////////// + // here we clean up the state with a noise blanker (debouncing) // + ////////////////////////////////////////////////////////////////// + + if (realstate != realstatebefore) { + lastStartTime = millis(); +} + + if ((millis() - lastStartTime) > nbtime) { + if (realstate != filteredState) { + filteredState = realstate; + } + } + realstatebefore = realstate; + + if (filteredState == filteredStateBefore) { + //DEBUG("checkInput returns false"); + return false; + }// no change detected in filteredState + else { + filteredStateBefore = filteredState; + //DEBUG("checkInput returns true"); + return true; // change detected in filteredState + } +} /// end checkInput() + + +void Decoder::decode() { + float lacktime; + int wpm; + + switch(decoderState) { + case INTERELEMENT_: if (checkInput()) { + ON_(); + decoderState = HIGH_; + } else { + lowDuration = millis() - startTimeLow; // we record the length of the pause + lacktime = 2.2; /// when high speeds we have to have a little more pause before new letter + if (d_wpm > 35) lacktime = 2.4; + else if (d_wpm > 30) lacktime = 2.3; + if (lowDuration > (lacktime * ditAvg)) { + displayDecodedMorse(myTable.retrieveSymbol(), false); //displayDecodedMorse(!fromKey); /////////////////////////!!! decode the morse character and display it + wpm = (d_wpm + (int) (7200 / (dahAvg + 3*ditAvg))) / 2; //// recalculate speed in wpm + if (d_wpm != wpm) { + d_wpm = wpm; + //DEBUG("d_wpm1: " + String(d_wpm)); + lastWasKey = fromKey; + speedChanged = true; + } + if (morseState == loraTrx || morseState == wifiTrx) + cwForTx(0); + decoderState = INTERCHAR_; + } + } + break; + case INTERCHAR_: if (checkInput()) { + ON_(); + decoderState = HIGH_; + } else { + lowDuration = millis() - startTimeLow; // we record the length of the pause + lacktime = 5; /// when high speeds we have to have a little more pause before new word + if (d_wpm > 35) lacktime = 6; + else if (d_wpm > 30) lacktime = 5.5; + else if (morseState == echoTrainer) lacktime = MorsePreferences::pliste[posInterWordSpace].value + 1; + if (lowDuration > (lacktime * ditAvg)) { + displayDecodedMorse(myTable.retrieveSymbol(), false); ////////end of word////////// !!!!! + decoderState = LOW_; + if (morseState == echoTrainer && echoTrainerState == COMPLETE_ANSWER) { // change the state of the trainer at end of word in case of echo trainer + echoTrainerState = EVAL_ANSWER; + } + if (morseState == loraTrx || morseState == wifiTrx) { // when in Trx mode + cwForTx(3); + if (morseState == loraTrx) + sendWithLora(); // finalise the string and send it to LoRA + else + sendWithWifi(); + } + } + } + break; + case LOW_: if (checkInput()) { + ON_(); + decoderState = HIGH_; + if (morseState == echoTrainer) { // change the state of the trainer at end of word + echoTrainerState = COMPLETE_ANSWER; + } + } + else { + if (echoTrainerState == GET_ANSWER && millis() > genTimer) { + echoTrainerState = EVAL_ANSWER; + } + } + break; + case HIGH_: if (checkInput()) { + OFF_(); + decoderState = INTERELEMENT_; + } + break; + } +} + + +void Decoder::ON_() { /// what we do when we just detected a rising flank, from low to high + unsigned long timeNow = millis(); + lowDuration = timeNow - startTimeLow; // we record the length of the pause + startTimeHigh = timeNow; // prime the timer for the high state + + unsigned int pitch = MorseOutput::notes[MorsePreferences::pliste[posPitch].value]; + if ((morseState == echoTrainer || morseState == loraTrx || morseState == wifiTrx) && MorsePreferences::pliste[posEchoToneShift].value != 0) { + pitch = (MorsePreferences::pliste[posEchoToneShift].value == 1 ? pitch * 18 / 17 : pitch * 17 / 18); /// one half tone higher or lower, as set in parameters in echo trainer mode + } + keyOut(true, fromKey, pitch, MorsePreferences::sidetoneVolume); + MorseOutput::drawInputStatus(true); + + if (lowDuration < ditAvg * 2.4) // if we had an inter-element pause, + recalculateDit(lowDuration); // use it to adjust speed +} + +void Decoder::OFF_() { /// what we do when we just detected a falling flank, from high to low + unsigned long timeNow = millis(); + unsigned int threshold = (int) ( ditAvg * sqrt( dahAvg / ditAvg)); + + highDuration = timeNow - startTimeHigh; + startTimeLow = timeNow; + + if (highDuration > (ditAvg * 0.5) && highDuration < (dahAvg * 2.5)) { /// filter out VERY short and VERY long highs + if (highDuration < threshold) { /// we got a dit - + myTable.recordDit(); + //treeptr = CWtree[treeptr].dit; + recalculateDit(highDuration); + if (morseState == loraTrx || morseState == wifiTrx) + cwForTx(1); + } + else { /// we got a dah + myTable.recordDah(); + //treeptr = CWtree[treeptr].dah; + recalculateDah(highDuration); + if (morseState == loraTrx || morseState == wifiTrx) + cwForTx(2); + } + } + keyOut(false, fromKey, 0, 0); + MorseOutput::drawInputStatus(false); +} + + +void Decoder::recalculateDit(unsigned long duration) { /// recalculate the average dit length + ditAvg = (4*ditAvg + duration) / 5; + nbtime = constrain(ditAvg/5, 7, 20); +} + +void Decoder::recalculateDah(unsigned long duration) { /// recalculate the average dah length + if (duration > 2* dahAvg) { /// very rapid decrease in speed! + dahAvg = (dahAvg + 2* duration) / 3; /// we adjust faster, ditAvg as well! + ditAvg = ditAvg/2 + dahAvg/6; + } + else { + dahAvg = (3* ditAvg + dahAvg + duration) / 3; + } +} + +uint8_t Decoder::getWpm() { + return d_wpm; +} + + +//////// methods for class MorseTable ///////// + +MorseTable::MorseTable() +{ + treeptr = 0; +} + +void MorseTable::recordDit() { /// walk down the dit branch + treeptr = CWtree[treeptr].dit; +} + +void MorseTable::recordDah() { /// walk down the dah branch + treeptr = CWtree[treeptr].dah; +} + +void MorseTable::resetTable() { /// go back to the root + treeptr = 0; +} + +String MorseTable::retrieveSymbol() { /// deliver the character, and go backk to the root + String symbol; + symbol.reserve(6); + if (treeptr == 0 ) + return String(" "); /// we display a blank + symbol = CWtree[treeptr].symb; + treeptr = 0; + return symbol; +} diff --git a/Software/src/Version 5/MorseDecoder.h b/Software/src/Version 5/MorseDecoder.h new file mode 100644 index 0000000..9c380e0 --- /dev/null +++ b/Software/src/Version 5/MorseDecoder.h @@ -0,0 +1,168 @@ +#ifndef MORSEDECODER_H_ +#define MORSEDECODER_H_ + +#include +#include "morsedefs.h" +#include "goertzel.h" + +#define USE_KEY true +#define USE_AUDIO false + +extern boolean leftKey, rightKey; + +extern echoStates echoTrainerState; +//extern const struct linklist CWtree[]; +extern byte treeptr; +extern boolean lastWasKey; +extern boolean speedChanged; + +extern void displayDecodedMorse(String, boolean); +extern void cwForTx (int); +extern void sendWithLora(); +extern void sendWithWifi(); + +void keyOut(boolean, boolean, int, int); + +///////////////////////////////////////////// the tree for decoding CW ////////////////////////////////////// + +struct linklist { + const char* symb; + const uint8_t dit; + const uint8_t dah; +}; + + +const struct linklist CWtree[69] = { + {"",1,2}, // 0 + {"e", 3,4}, // 1 + {"t",5,6}, // 2 +// + {"i", 7, 8}, // 3 + {"a", 9,10}, // 4 + {"n", 11,12}, // 5 + {"m", 13,14}, // 6 +// + {"s", 15,16}, // 7 + {"u", 17,18}, // 8 + {"r", 19,20}, // 9 + {"w", 21,22}, //10 + {"d", 23,24}, //11 + {"k", 25, 26}, //12 + {"g", 27, 28}, //13 + {"o", 29,30}, //14 +//--------------------------------------------- + {"h", 31,32}, // 15 + {"v", 33, 34}, // 16 + {"f", 63, 63}, // 17 + {"ü", 35, 36}, // 18 german ue + {"l", 37, 38}, // 19 + {"ä", 39, 63}, // 20 german ae + {"p", 63, 40}, // 21 + {"j", 63, 41}, // 22 + {"b", 42, 43}, // 23 + {"x", 44, 63}, // 24 + {"c", 63, 45}, // 25 + {"y", 46, 63}, // 26 + {"z", 47, 48}, // 27 + {"q", 63, 63}, // 28 + {"ö", 49, 63}, // 29 german oe + {"", 50, 51}, // 30 !!! german "ch" +//--------------------------------------------- + {"5", 64, 63}, // 31 + {"4", 63, 63}, // 32 + {"", 63, 52}, // 33 or , sometimes "*" + {"3", 63,63}, // 34 + {"*", 53,63,}, // 35 * used for all unidentifiable characters ¬ + {"2", 63, 63}, // 36 + {"", 63,63}, // 37 !! + {"*", 54, 63}, // 38 + {"+", 63, 55}, // 39 + {"*", 56, 63}, // 40 + {"1", 57, 63}, // 41 + {"6", 63, 58}, // 42 + {"=", 67, 63}, // 43 + {"/", 63, 63}, // 44 + {"", 59, 60}, // 45 !! + {"", 63, 63}, // 46 !! + {"7", 63, 63}, // 47 + {"*", 63, 61}, // 48 + {"8", 62, 63}, // 49 + {"9", 63, 63}, // 50 + {"0", 63, 63}, // 51 +// + {"", 63, 63}, // 52 !! + {"?", 63, 63}, // 53 + {"\"", 63, 63}, // 54 + {".", 63, 63}, // 55 + {"@", 63, 63}, // 56 + {"\'",63, 63}, // 57 + {"-", 63, 63}, // 58 + {";", 63, 63}, // 59 + {"!", 63, 63}, // 60 + {",", 63, 63}, // 61 + {":", 63, 63}, // 62 +// + {"*", 63, 63}, // 63 Default for all unidentified characters + {"*", 65, 63}, // 64 + {"", 66, 63}, // 65 + {"", 66, 63}, // 66 !! Error - backspace + {"*", 63, 68}, // 67 + {"", 63, 63} // 68 +}; + +//// we define two classes: MorseTable (a pointer to the linked list defined above, and mthods to manipulate the pointer) +//// and Decoder (which decodes signals from a key or from audio - derived from a Goertzel filter) + +class MorseTable { + +private: + byte treeptr; + +public: + MorseTable(); + void recordDit(); + void recordDah(); + void resetTable(); + String retrieveSymbol(); +}; + + +class Decoder { + +private: + boolean fromKey; /// if true, decode from straight key, if false, from audio through Goertzel + boolean filteredState; + boolean filteredStateBefore; + DECODER_STATES decoderState; + + // Noise Blanker time (currently fixed) + int nbtime; /// ms noise blanker + + unsigned long startTimeHigh; + unsigned long highDuration; + unsigned long lastStartTime; + boolean realstate; + boolean realstatebefore; + long startTimeLow; + long lowDuration; + unsigned long ditAvg, dahAvg; /// average values of dit and dah lengths to decode as dit or dah and to adapt to speed change + uint8_t d_wpm; /// wpm as decoded + + boolean checkInput(); + void ON_(); + void OFF_(); + void recalculateDit(unsigned long ); + void recalculateDah(unsigned long ); + + //byte treeptr; // pointer used to navigate within the linked list representing the dichotomic tree + MorseTable myTable; + +public: + Decoder(boolean); + void setup(); + void decode(); + uint8_t getWpm(); + +}; /// end of class Decoder + +#endif /* MORSEDECODER_H_ */ diff --git a/Software/src/Version 5/MorseMenu.cpp b/Software/src/Version 5/MorseMenu.cpp new file mode 100644 index 0000000..24a41f0 --- /dev/null +++ b/Software/src/Version 5/MorseMenu.cpp @@ -0,0 +1,571 @@ +/****************************************************************************************************************************** + * morse_3 Software for the Morserino-32 multi-functional Morse code machine, based on the Heltec WiFi LORA (ESP32) module *** + * Copyright (C) 2018-2020 Willi Kraml, OE1WKL *** + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + *****************************************************************************************************************************/ + +#include "MorseMenu.h" +#include "MorseOutput.h" +#include "MorseDecoder.h" + +using namespace MorseMenu; + +//////// variables and constants for the modus menu + +//boolean MorseMenu::inMenuLoop = false; + + +const String menuText [menuN] = { + "", + "CW Keyer", // 1 + + "CW Generator", // 2 + "Random", + "CW Abbrevs", + "English Words", + "Call Signs", + "Mixed", + "File Player", + + "Echo Trainer", // 9 + "Random", + "CW Abbrevs", + "English Words", + "Call Signs", + "Mixed", + "File Player", + + "Koch Trainer", // 16 + "Select Lesson", + "Learn New Chr", + "CW Generator", // 19 + "Random", // 20 + "CW Abbrevs", + "English Words", + "Mixed", + "Echo Trainer", // 24 + "Random", + "CW Abbrevs", + "English Words", + "Mixed", + "Adapt. Rand.", + + "Transceiver", // 30 + "LoRa Trx", + "WiFi Trx", + "iCW/Ext Trx", + + "CW Decoder", // 34 + + "WiFi Functions", // 35 + "Disp MAC Addr", + "Config WiFi", + "Check WiFi", + "Upload File", + "Update Firmw", //40 + "Wifi Select", //41 + + + "Go To Sleep" } ; // 42 + +enum navi {naviLevel, naviLeft, naviRight, naviUp, naviDown }; + + +const uint8_t menuNav [menuN] [5] = { // { level, left, right, up, down} + { 0,0,0,0,0}, // 0 = dummy + {0,_goToSleep,_gen,_dummy,0}, // 1 keyer -e + {0,_keyer,_echo,_dummy,_genRand}, // 2 generator + {1,_genPlayer,_genAbb,_gen,0}, // 3 gen random -e + {1,_genRand,_genWords,_gen,0}, // 4 gen Abb -e + {1,_genAbb,_genCalls,_gen,0}, // 5 gen words -e + {1,_genWords,_genMixed,_gen,0}, // 6 gen calls -e + {1,_genCalls,_genPlayer,_gen,0}, // 7 gen mixed -e + {1,_genMixed,_genRand,_gen,0}, // 8 gen player -e + {0,_gen,_koch,_dummy,_echoRand}, // 9 echo tr + {1,_echoPlayer,_echoAbb,_echo,0}, // 10 echo random -e + {1,_echoRand,_echoWords,_echo,0}, // 11 echo abb -e + {1,_echoAbb,_echoCalls,_echo,0}, // 12 echo words -e + {1,_echoWords,_echoMixed,_echo,0}, // 13 echo calls -e + {1,_echoCalls,_echoPlayer,_echo,0}, // 14 echo mixed -e + {1,_echoMixed,_echoRand,_echo,0}, // 15 echo player -e + {0,_echo,_trx,_dummy,_kochSel}, // 16 koch + {1,_kochEcho,_kochLearn,_koch,0}, // 17 koch select -e!!? + {1,_kochSel,_kochGen,_koch,0}, // 18 koch learn new + {1,_kochLearn,_kochEcho,_koch,_kochGenRand}, // 19 koch gen + {2,_kochGenMixed,_kochGenAbb,_kochGen,0}, // 20 koch gen random -e + {2,_kochGenRand,_kochGenWords,_kochGen,0}, // 21 koch gen abb -e + {2,_kochGenAbb,_kochGenMixed,_kochGen,0}, // 22 koch gen words -e + {2,_kochGenWords,_kochGenRand,_kochGen,0}, // 23 koch gen mixed -e + {1,_kochGen,_kochSel,_koch,_kochEchoRand}, // 24 koch echo + {2,_kochEchoAdaptive,_kochEchoAbb,_kochEcho,0}, // 25 koch echo random -e + {2,_kochEchoRand,_kochEchoWords,_kochEcho,0}, // 26 koch echo abb -e + {2,_kochEchoAbb,_kochEchoMixed,_kochEcho,0}, // 27 koch echo words -e + {2,_kochEchoWords,_kochEchoAdaptive,_kochEcho,0}, // 28 koch echo mixed -e + {2,_kochEchoMixed,_kochEchoRand,_kochEcho,0}, // 29 koch echo adaptive -e + {0,_koch,_decode,_dummy,_trxLora}, // 30 transceiver + {1,_trxIcw,_trxWifi,_trx,0}, // 31 lora -e + {1,_trxLora,_trxIcw,_trx,0}, // 32 wifi -e + {1,_trxWifi,_trxLora,_trx,0}, // 33 icw -e + {0,_trx,_wifi,_dummy,0}, // 34 decoder -e + {0,_decode,_goToSleep,_dummy,_wifi_mac}, // 35 WiFi + {1,_wifi_select,_wifi_config,_wifi,0}, // 36 Disp Mac -e!! + {1,_wifi_mac,_wifi_check,_wifi,0}, // 37 Config Wifi --NE! + {1,_wifi_config,_wifi_upload,_wifi,0}, // 38 Check WiFi -e!! + {1,_wifi_check,_wifi_update,_wifi,0}, // 39 Upload File --NE! + {1,_wifi_upload,_wifi_select,_wifi,0}, // 40 Update Firmware --NE! + {1,_wifi_update,_wifi_mac,_wifi,0}, // 41 Select network --NE!! + {0,_wifi,_keyer,_dummy,0} // 42 goto sleep -e +}; + +//String MorseMenu::cmdPath; // used to create string for json + +////// The MENU + + +void MorseMenu::menu_() { + MorsePreferences::newMenuPtr = MorsePreferences::menuPtr; + uint8_t disp = 0; + int t, command; + m32state = menu_loop; + + LoRa.idle(); + WiFi.disconnect(true, false); + genIsActive = false; + cleanStartSettings(); + MorseOutput::clearScroll(); // clear the buffer + + keyOut(false, true, 0, 0); + keyOut(false, false, 0, 0); + encoderState = speedSettingMode; // always start with this encoderstate (decoder will change it anyway) + MorsePreferences::setCurrentOptions(MorsePreferences::allOptions, MorsePreferences::allOptionsSize); + MorsePreferences::writeWordPointer(); + file.close(); // just in case it is still open.... + MorseOutput::clearDisplay(); + + while (true) { // we wait for a click (= selection) or to get some serial input + serialEvent(); + + if (disp != MorsePreferences::newMenuPtr) { + disp = MorsePreferences::newMenuPtr; + MorseMenu::menuDisplay(disp); + } + if (quickStart) { + quickStart = false; + command = 1; + delay(250); + if (m32protocol) + jsonCreate("message", "Quick Start", ""); + MorseOutput::printOnScroll(2, REGULAR, 1, "QUICK START"); + Heltec.display -> display(); + delay(600); + MorseOutput::clearDisplay(); + } + else if (executeMenu) { + executeMenu = false; + command = 1; + } + else { + Buttons::modeButton.Update(); + command = Buttons::modeButton.clicks; + } + + switch (command) { // actions based on encoder button + case 2: if (MorsePreferences::setupPreferences(MorsePreferences::newMenuPtr)) // all available options when called from top menu + MorsePreferences::newMenuPtr = MorsePreferences::menuPtr; + MorseMenu::menuDisplay(MorsePreferences::newMenuPtr); + m32state = menu_loop; + break; + case 1: // check if we have a submenu or if we execute the selection + //DEBUG("newMP: " + String(MorsePreferences::newMenuPtr) + " navi: " + String(menuNav[MorsePreferences::newMenuPtr][naviDown])); + if (menuNav[MorsePreferences::newMenuPtr][naviDown] == 0) { + MorsePreferences::menuPtr = MorsePreferences::newMenuPtr; + disp = 0; + if (MorsePreferences::menuPtr < _wifi) { // remember last executed, unless it is a wifi function or shutdown + MorsePreferences::writeLastExecuted(MorsePreferences::newMenuPtr); + } + if (MorseMenu::menuExec()) + return; + } else { + MorsePreferences::newMenuPtr = menuNav[MorsePreferences::newMenuPtr][naviDown]; + } + break; + case -1: // we need to go one level up, if possible + if (menuNav[MorsePreferences::newMenuPtr][naviUp] != 0) + MorsePreferences::newMenuPtr = menuNav[MorsePreferences::newMenuPtr][naviUp]; + default: break; + } + + if ((t=checkEncoder())) { + MorseOutput::pwmClick(MorsePreferences::sidetoneVolume); /// click + MorsePreferences::newMenuPtr = menuNav [MorsePreferences::newMenuPtr][(t == -1) ? naviLeft : naviRight]; + } + + Buttons::volButton.Update(); + + switch (Buttons::volButton.clicks) { + case -1: audioLevelAdjust(); /// for adjusting line-in audio level (at the same time keying tx and sending oudio on line-out + MorseOutput::clearDisplay(); + MorseMenu::menuDisplay(disp); + break; + case 2: MorseOutput::decreaseBrightness(); + MorseMenu::menuDisplay(disp); + break; + } + checkShutDown(false); // check for time out + } // end while - we leave as soon as the button has been pressed + // MorseMenu::inMenuLoop = false; +} // end menu_() + + +void MorseMenu::menuDisplay(uint8_t ptr) { + //DEBUG("Level: " + (String) menuNav [ptr][naviLevel] + " " + menuText[ptr]); + uint8_t oneUp = menuNav[ptr][naviUp]; + uint8_t twoUp = menuNav[oneUp][naviUp]; + uint8_t oneDown = menuNav[ptr][naviDown]; + + + MorseOutput::printOnStatusLine( true, 0, "Select Modus: "); + + // delete previous content + MorseOutput::clearThreeLines(); + + /// level 0: top line, possibly ".." on line 1 + /// level 1: higher level on 0, item on 1, possibly ".." on 2 + /// level 2: higher level on 1, highest level on 0, item on 2 + switch (menuNav [ptr][naviLevel]) { + case 2: //cmdPath = menuText[twoUp] + "/" + menuText[oneUp] + "/" + menuText[ptr]; + MorseOutput::printOnScroll(2, BOLD, 0, menuText[ptr]); + MorseOutput::printOnScroll(1, REGULAR, 0, menuText[oneUp]); + MorseOutput::printOnScroll(0, REGULAR, 0, menuText[twoUp]); + break; + case 1: //cmdPath = menuText[oneUp] + "/" + menuText[ptr] + (oneDown ? ("/..") : ""); + if (oneDown) + MorseOutput::printOnScroll(2, REGULAR, 0, String("..")); + MorseOutput::printOnScroll(1, BOLD, 0, menuText[ptr]); + MorseOutput::printOnScroll(0, REGULAR, 0, menuText[oneUp]); + break; + case 0: //cmdPath = menuText[ptr] + (oneDown ? ("/..") : ""); + if (oneDown) + MorseOutput::printOnScroll(1, REGULAR, 0, String("..")); + MorseOutput::printOnScroll(0, BOLD, 0, menuText[ptr]); + break; + } + if (m32protocol) { + //cmdPath = MorseMenu::getMenuPath(ptr); + jsonMenu( MorseMenu::getMenuPath(ptr), (unsigned int) ptr, (m32state == menu_loop ? false : true), MorseMenu::isRemotelyExecutable(ptr)); + } + +} + +String MorseMenu::getMenuPath(uint8_t ptr) { + uint8_t oneUp = menuNav[ptr][naviUp]; + uint8_t twoUp = menuNav[oneUp][naviUp]; + uint8_t oneDown = menuNav[ptr][naviDown]; + String cmdPath; cmdPath.reserve(80); + + switch (menuNav [ptr][naviLevel]) { + case 2: cmdPath = menuText[twoUp] + "/" + menuText[oneUp] + "/" + menuText[ptr]; + break; + case 1: cmdPath = menuText[oneUp] + "/" + menuText[ptr] + (oneDown ? ("/..") : ""); + break; + case 0: cmdPath = menuText[ptr] + (oneDown ? ("/..") : ""); + break; + } + return cmdPath; +} + + +boolean MorseMenu::menuExec() { // return true if we should leave menu after execution, true if we should stay in menu + + uint32_t wcount = 0; +// String peer; +// const char* peerHost; + String s; + + if (m32protocol && (MorsePreferences::menuPtr != _kochSel)) + jsonActivate(ACT_ON); + + m32state = active_loop; + + kochActive = false; + keyerState = IDLE_STATE; + + switch (MorsePreferences::menuPtr) { + case _keyer: /// keyer + MorsePreferences::setCurrentOptions(MorsePreferences::keyerOptions, MorsePreferences::keyerOptionsSize); + morseState = morseKeyer; + showStartDisplay("CW Keyer Start", "", "", 400); + clearPaddleLatches(); + if(MorsePreferences::pliste[posCurtisMode].value == STRAIGHTKEY) + keyDecoder.setup(); + executeNow = false; + return true; + break; + case _genRand: + case _genAbb: + case _genWords: + case _genCalls: + case _genMixed: /// generator + generatorMode = (GEN_TYPE) (MorsePreferences::menuPtr - 3); /// 0 = RANDOMS ... 4 = MIXED, 5 = PLAYER + MorsePreferences::setCurrentOptions(MorsePreferences::generatorOptions, MorsePreferences::generatorOptionsSize); + goto startGenerator; + case _genPlayer: + generatorMode = (GEN_TYPE) (MorsePreferences::menuPtr - 3); /// 0 = RANDOMS ... 4 = MIXED, 5 = PLAYER + MorsePreferences::setCurrentOptions(MorsePreferences::playerOptions, MorsePreferences::playerOptionsSize); + file = SPIFFS.open("/player.txt"); // open file + //skip p_fileWordPointer words, as they have been played before + wcount = MorsePreferences::fileWordPointer; + MorsePreferences::fileWordPointer = 0; + skipWords(wcount); + + startGenerator: + startFirst = true; + firstTime = true; + morseState = morseGenerator; + showStartDisplay("Generator ", "Start / Stop ", "press Paddle ", 1250); + if (MorsePreferences::pliste[posLoraCwTransmit].value == 2) + if (!setupWifi()) + return false; + return true; + break; + case _echoRand: + case _echoAbb: + case _echoWords: + case _echoCalls: + case _echoMixed: + MorsePreferences::setCurrentOptions(MorsePreferences::echoTrainerOptions, MorsePreferences::echoTrainerOptionsSize); + + generatorMode = (GEN_TYPE) (MorsePreferences::menuPtr - 10); /// 0 = RANDOMS ... 4 = MIXED, 5 = PLAYER + goto startEcho; + case _echoPlayer: /// echo trainer + generatorMode = (GEN_TYPE) (MorsePreferences::menuPtr - 10); /// 0 = RANDOMS ... 4 = MIXED, 5 = PLAYER + MorsePreferences::setCurrentOptions(MorsePreferences::echoPlayerOptions, MorsePreferences::echoPlayerOptionsSize); + file = SPIFFS.open("/player.txt"); // open file + //skip p_fileWordPointer words, as they have been played before + wcount = MorsePreferences::fileWordPointer; + MorsePreferences::fileWordPointer = 0; + skipWords(wcount); + startEcho: + startFirst = true; + morseState = echoTrainer; + echoStop = false; + showStartDisplay(generatorMode == KOCH_LEARN ? "New Character:" : "Echo Trainer:", "Start: ", "Press paddle ", 1250); + if(MorsePreferences::pliste[posCurtisMode].value == STRAIGHTKEY) + keyDecoder.setup(); + executeNow = false; + return true; + break; + case _kochSel: // Koch Select + MorsePreferences::displayKeyerPreferencesMenu(posKochFilter); + MorsePreferences::adjustKeyerPreference(posKochFilter); + MorsePreferences::writePreferences("morserino"); + executeNow = false; + m32state = menu_loop; + return false; + break; + case _kochLearn: // Koch Learn New . /// just a new generatormode.... + generatorMode = KOCH_LEARN; + MorsePreferences::setCurrentOptions(MorsePreferences::kochEchoOptions, MorsePreferences::kochEchoOptionsSize); + executeNow = false; + goto startEcho; + case _kochGenRand: // RANDOMS + generatorMode = RANDOMS; + kochActive = true; + MorsePreferences::setCurrentOptions(MorsePreferences::kochGenOptions, MorsePreferences::kochGenOptionsSize); + goto startGenerator; + case _kochGenAbb: // ABBREVS - 2 + generatorMode = ABBREVS; + kochActive = true; + MorsePreferences::setCurrentOptions(MorsePreferences::kochGenOptions, MorsePreferences::kochGenOptionsSize); + goto startGenerator; + case _kochGenWords: // WORDS - 3 + generatorMode = WORDS; + kochActive = true; + MorsePreferences::setCurrentOptions(MorsePreferences::kochGenOptions, MorsePreferences::kochGenOptionsSize); + goto startGenerator; + case _kochGenMixed: // KOCH_MIXED - 5 + generatorMode = KOCH_MIXED; + kochActive = true; + MorsePreferences::setCurrentOptions(MorsePreferences::kochGenOptions, MorsePreferences::kochGenOptionsSize); + goto startGenerator; + case _kochEchoRand: // Koch Echo Random + generatorMode = RANDOMS; + kochActive = true; + MorsePreferences::setCurrentOptions(MorsePreferences::kochEchoOptions, MorsePreferences::kochEchoOptionsSize); + goto startEcho; + case _kochEchoAbb: // ABBREVS - 2 + generatorMode = ABBREVS; + kochActive = true; + MorsePreferences::setCurrentOptions(MorsePreferences::kochEchoOptions, MorsePreferences::kochEchoOptionsSize); + goto startEcho; + case _kochEchoWords: // WORDS - 3 + generatorMode = WORDS; + kochActive = true; + MorsePreferences::setCurrentOptions(MorsePreferences::kochEchoOptions, MorsePreferences::kochEchoOptionsSize); + goto startEcho; + case _kochEchoMixed: // KOCH_MIXED - 5 + generatorMode = KOCH_MIXED; + kochActive = true; + MorsePreferences::setCurrentOptions(MorsePreferences::kochEchoOptions, MorsePreferences::kochEchoOptionsSize); + goto startEcho; + case _kochEchoAdaptive: // Koch Echo Adaptive - 6 + generatorMode = KOCH_ADAPTIVE; + kochActive = true; + MorsePreferences::setCurrentOptions(MorsePreferences::kochEchoOptions, MorsePreferences::kochEchoOptionsSize); + + // re-run the setup, this will reset the character probabilities + koch.setup(); + goto startEcho; + case _trxLora: // LoRa Transceiver + generatorMode = RANDOMS; // to reset potential KOCH_LEARN + MorsePreferences::setCurrentOptions(MorsePreferences::loraTrxOptions, MorsePreferences::loraTrxOptionsSize); + morseState = loraTrx; + showStartDisplay("", "Start LoRa Trx", "", 500); + clearPaddleLatches(); + clearText = ""; + LoRa.receive(); + executeNow = false; + return true; + break; + case _trxWifi: // Wifi Transceiver + generatorMode = RANDOMS; // to reset potential KOCH_LEARN + MorsePreferences::setCurrentOptions(MorsePreferences::wifiTrxOptions, MorsePreferences::wifiTrxOptionsSize); + morseState = wifiTrx; + MorseOutput::clearDisplay(); + MorseOutput::printOnScroll(0, REGULAR, 0, "Connecting..."); + if (m32protocol) + jsonCreate("message", "Connecting...", ""); + + if (!setupWifi()) + return false; + //DEBUG("Peer IP: " + peerIP.toString()); + s = peerIP.toString(); + showStartDisplay("", "Start Wifi Trx", s == "255.255.255.255" ?"IP Broadcast" : s, 1500); + + MorseWiFi::audp.listen(MORSERINOPORT); // listen on port 7373 + MorseWiFi::audp.onPacket(onWifiReceive); + clearPaddleLatches(); + //keyTx = false; + clearText = ""; + executeNow = false; + return true; + break; + case _trxIcw: /// icw/ext TRX + MorsePreferences::setCurrentOptions(MorsePreferences::extTrxOptions, MorsePreferences::extTrxOptionsSize); + morseState = morseTrx; + MorseOutput::clearDisplay(); + MorseOutput::printOnScroll(1, REGULAR, 0, "Start CW Trx" ); + if (m32protocol) + jsonCreate("message", "Start CW Transceiver", ""); + clearPaddleLatches(); + goto setupDecoder; + + case _decode: /// decoder + MorsePreferences::setCurrentOptions(MorsePreferences::decoderOptions, MorsePreferences::decoderOptionsSize); + morseState = morseDecoder; + /// here we will do the init for decoder mode + encoderState = volumeSettingMode; + MorseOutput::clearDisplay(); + MorseOutput::printOnScroll(1, REGULAR, 0, "Start Decoder" ); + if (m32protocol) + jsonCreate("message", "Start Decoder", ""); + setupDecoder: + speedChanged = true; + delay(650); + cleanupScreen(); + MorseOutput::drawInputStatus(false); + + displayCWspeed(); + MorseOutput::displayVolume(encoderState == speedSettingMode, MorsePreferences::sidetoneVolume); // sidetone volume + keyDecoder.setup(); + audioDecoder.setup(); + executeNow = false; + return true; + break; + case _wifi_mac: + case _wifi_config: + case _wifi_check: + case _wifi_upload: + case _wifi_update: + MorseWiFi::menuExec((uint8_t) MorsePreferences::menuPtr); + break; + case _wifi_select: + MorseWiFi::menuNetSelect(); + break; + case _goToSleep: /// deep sleep + checkShutDown(true); + default: break; + } + m32state = menu_loop; + return false; +} /// end menuExec() + +boolean MorseMenu::setupWifi() { + String peer; + peer.reserve(24); + const char* peerHost; + +//// if not true WiFi has not been configured or is not available, hence return false! + if (! MorseWiFi::wifiConnect()) { + delay(1000); // wait a bit + return(false); + } + + if (MorsePreferences::wlanTRXPeer.length() == 0) + peer = "255.255.255.255"; // send to local broadcast IP if not set + else + peer = MorsePreferences::wlanTRXPeer; + peerHost = peer.c_str(); + if (!peerIP.fromString(peerHost)) { // try to interpret the peer as an ip address... + //DEBUG("hostname: " + String(peerHost)); + int err = WiFi.hostByName(peerHost, peerIP); // ...and resolve peer into ip address if that fails + //DEBUG("errcode: " + String(err)); + if (err != 1) // if that fails too, use broadcast + peerIP.fromString("255.255.255.255"); + } + return true; +} + + +void MorseMenu::cleanupScreen() { + MorseOutput::clearDisplay(); + updateTopLine(); + MorseOutput::clearScroll(); // clear the buffer} +} + +void MorseMenu::showStartDisplay(String l0, String l1, String l2, int pause) { + MorseOutput::clearDisplay(); + if (l0.length()) + MorseOutput::printOnScroll(0, REGULAR, 0, l0); + if (l1.length()) + MorseOutput::printOnScroll(1, REGULAR, 0, l1); + if (l2.length()) + MorseOutput::printOnScroll(2, REGULAR, 0, l2); + if (m32protocol) + jsonCreate("message", l0 + l1 + l2, ""); + delay(pause); + cleanupScreen(); +} + +boolean MorseMenu::isRemotelyExecutable(uint8_t ptr) { + if (menuNav[ptr][naviDown] == 0) { + switch (ptr) { + case 37: // these WIFi related functions cannot be executed remotely, but must be done directly on the device + case 39: + case 40: + case 41: + return false; + } + return true; + } + else + return false; +} diff --git a/Software/src/Version 5/MorseMenu.h b/Software/src/Version 5/MorseMenu.h new file mode 100644 index 0000000..d0dca11 --- /dev/null +++ b/Software/src/Version 5/MorseMenu.h @@ -0,0 +1,57 @@ +#ifndef MORSEMENU_H_ +#define MORSEMENU_H_ + +#include +#include "morsedefs.h" +#include "MorsePreferences.h" +#include "MorseWiFi.h" +#include "MorseDecoder.h" + +extern boolean m32protocol; +extern boolean genIsActive, echoStop, firstTime, startFirst; +extern boolean kochActive; +extern boolean filteredState, filteredStateBefore, speedChanged; +extern boolean quickStart; +extern unsigned long ditAvg, dahAvg; +extern String clearText; +extern File file; +extern IPAddress peerIP; +extern morserinoMode morseState; +extern loops m32state; +extern boolean executeMenu; +extern boolean executeNow; +extern encoderMode encoderState; +extern DECODER_STATES decoderState; +extern GEN_TYPE generatorMode; +extern KEYERSTATES keyerState; +extern Decoder keyDecoder; +extern Decoder audioDecoder; + + +extern void keyOut(boolean, boolean, int, int); +extern void cleanStartSettings(); +extern void checkShutDown(boolean ); +extern void updateTopLine(); +extern void clearPaddleLatches(); +extern void displayCWspeed(); +extern void skipWords(uint32_t ); +extern void audioLevelAdjust(); +extern void onWifiReceive(AsyncUDPPacket); +extern void serialEvent(); +extern void jsonCreate(String,String,String); +extern void jsonMenu(String,unsigned int,bool,bool); +extern void jsonActivate(actMessage); + +namespace MorseMenu +{ +void menu_(); +boolean menuExec(); +boolean setupWifi(); +String getMenuPath(uint8_t); +void menuDisplay(uint8_t); +void cleanupScreen(); +void showStartDisplay(String, String, String, int); +boolean isRemotelyExecutable(uint8_t); +} + +#endif /* MORSEMENU_H_ */ diff --git a/Software/src/Version 5/MorseOutput.cpp b/Software/src/Version 5/MorseOutput.cpp new file mode 100644 index 0000000..29b1c33 --- /dev/null +++ b/Software/src/Version 5/MorseOutput.cpp @@ -0,0 +1,622 @@ +/****************************************************************************************************************************** + morse_3 Software for the Morserino-32 multi-functional Morse code machine, based on the Heltec WiFi LORA (ESP32) module ** + Copyright (C) 2018 ff. Willi Kraml, OE1WKL ** + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. + If not, see . + *****************************************************************************************************************************/ + +/// This mpodule contrains functions for output on the display, on the USB serial output, on the speaker and on line out +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +////////////////////////////// New scrolling display + +/// circular buffer: 14 chars by NoOfLines lines (bottom 3 are visible) +#define NoOfLines 15 +#define NoOfCharsPerLine 14 +#define SCROLL_TOP 15 +#define LINE_HEIGHT 16 +#define C_WIDTH 9 + +#include +#include "heltec.h" +#include "MorseOutput.h" +#include "morsedefs.h" +#include "wklfonts.h" +#include "MorsePreferences.h" + +using namespace MorseOutput; + +char textBuffer[NoOfLines][2 * NoOfCharsPerLine + 1]; /// we need extra room for style markers (FONT_ATTRIB stored as characters to toggle on/off the style within a line) +/// and 0 terminator + + +uint8_t linePointer = 0; /// defines the current bottom line +uint8_t bottomLine = 0; + +const int8_t MorseOutput::maxPos = NoOfLines - 3; +int8_t MorseOutput::relPos = MorseOutput::maxPos; + +#define lora_width 6 /// a simple logo that shows when we operate with loRa, stored in XBM format +#define lora_height 11 +static unsigned char lora_bits[] = { + 0x0f, 0x18, 0x33, 0x24, 0x29, 0x2b, 0x29, 0x24, 0x33, 0x18, 0x0f +}; + +#define wifi_width 6 /// a simple logo that shows when we operate with WiFi, stored in XBM format +#define wifi_height 11 +static unsigned char wifi_bits[] = { + 0x00, 0x08, 0x10, 0x24, 0x29, 0x2b, 0x29, 0x24, 0x10, 0x08, 0x00 }; + +volatile uint64_t MorseOutput::TOTcounter; // holds millis for Time-Out Timer + +String printToScroll_buffer = ""; +FONT_ATTRIB printToScroll_lastStyle = REGULAR; + +/////////////////////// parameters for LF tone generation and HF (= vol ctrl) PWM +int toneFreq = 500 ; +int toneChannel = 2; // this PWM channel is used for LF generation, duty cycle is 0 (silent) or 50% (tone) +int lineOutChannel = 3; // this PWM channel is used for line-out LF generation, duty cycle is 0 (silent) or 50% (tone) +int volChannel = 8; // this PWM channel is used for HF generation, duty cycle between 1% (almost silent) and 100% (loud) +int pwmResolution = 10; +unsigned int volFreq = 32000; // this is the HF frequency we are using + +const int dutyCycleFiftyPercent = 511; ; +const int dutyCycleTwentyPercent = 250; +const int dutyCycleZero = 0; + + +////// Display functions + + +void MorseOutput::clearDisplay() +{ + Heltec.display -> clear(); + Heltec.display -> display(); +} + +void MorseOutput::sleep() +{ + Heltec.display -> sleep(); //OLED sleep +} + +void MorseOutput::decreaseBrightness() { + switch (MorsePreferences::oledBrightness) { + case 255: + MorsePreferences::oledBrightness = 127; + break; + case 127: + MorsePreferences::oledBrightness = 63; + break; + case 63: + MorsePreferences::oledBrightness = 28; + break; + case 28: + MorsePreferences::oledBrightness = 9; + break; + default: + MorsePreferences::oledBrightness = 255; + break; + } + Heltec.display -> setBrightness(MorsePreferences::oledBrightness); +} + + +void MorseOutput::printToScroll(FONT_ATTRIB style, String text, boolean autoflush, boolean scroll) { + boolean styleChanged = (style != printToScroll_lastStyle); + boolean lengthExceeded = printToScroll_buffer.length() + text.length() > 10; + // DEBUG("printToScroll String: >" + text + "<"); + if (styleChanged || lengthExceeded) { + // DEBUG("FLUSH!"); + MorseOutput::flushScroll(scroll); + } + + printToScroll_buffer += text; + printToScroll_lastStyle = style; + // DEBUG("printToScroll_buffer: >" + printToScroll_buffer + "<"); + if (autoflush || text.endsWith("\n")) { + MorseOutput::flushScroll(scroll); + } +} + + +void MorseOutput::clearBuffer() { + MorseOutput::printToScroll(REGULAR, "", false, false); // clear the buffer first +} + + +void MorseOutput::clearScrollBuffer() { + printToScroll_buffer = ""; + printToScroll_lastStyle = REGULAR; +} + + +void MorseOutput::flushScroll(boolean scroll) { + uint8_t len = printToScroll_buffer.length(); + if (len != 0) { + MorseOutput::printToScroll_internal(printToScroll_lastStyle, printToScroll_buffer, scroll); + //DEBUG("printToScroll_buffer: " + printToScroll_buffer); + clearScrollBuffer(); + } +} + + +/// store text in textBuffer, if it fits the screen line; otherwise scroll up, clear bottom buffer, store in new buffer, print on new line + +void MorseOutput::printToScroll_internal(FONT_ATTRIB style, String text, boolean scroll) { + + // for DEBUG + //char c; + //unsigned char ch; + // + static uint8_t pos = 0; + static uint8_t screenPos = 0; + static FONT_ATTRIB lastStyle = REGULAR; + uint8_t l = text.length(); + if (l == 0) { // an empty string signals we should clear the buffer + for (int i = 0; i < NoOfLines; ++i) { + textBuffer[i][0] = (char) 0; /// empty this line + } + refreshScrollArea((NoOfLines + bottomLine - 2) % NoOfLines); + pos = screenPos = 0; // reset the position pointers + return; + } + + int linebreak = text.endsWith("\n"); + if (linebreak) { + text.replace("\n", ""); + l = text.length(); + } + int textTooLong = (screenPos + l > NoOfCharsPerLine); + + if (textTooLong) { // we need to scroll up and start a new line + MorseOutput::newLine(scroll); + pos = 0; screenPos = 0; lastStyle = REGULAR; + } + + /// store text in buffer + if (style == REGULAR) { + memcpy(&textBuffer[bottomLine][pos], text.c_str(), l); // copy the string of characters + pos += l; + textBuffer[bottomLine][pos] = (char) 0; // add 0 character + } else { + if (style == lastStyle) { // not regular, but we have no change in style! + ///DEBUG("lastStyle :" + text); + //pos -= 1; // go one pos back to overwrite style marker + memcpy(&textBuffer[bottomLine][pos], text.c_str(), l); // copy the string of characters + pos += l; + textBuffer[bottomLine][pos++] = (char) style; // add the style marker + textBuffer[bottomLine][pos] = (char) 0; // add 0 character + } else { + //DEBUG("NOTlastStyle :" + text); + textBuffer[bottomLine][pos++] = (char) style; // add the style marker at the beginning + memcpy(&textBuffer[bottomLine][pos], text.c_str(), l); // copy the string of characters + pos += l; + textBuffer[bottomLine][pos++] = (char) style; // add the style marker at the end + textBuffer[bottomLine][pos] = (char) 0; // add 0 character + lastStyle = style; // remember new style flag + } + } +///// for debugging: show contents of text buffer +///DEBUG("Buffer:"); +///for (int i = 0; (c = textBuffer[bottomLine][i]); ++i) { +/// DEBUG(String( ch = c ) + " <"); +/// } +///// + + if (relPos == maxPos) { // we show the bottom lines on the screen, therefore we add the new stuff immediately + /// and send string to screen, avoiding refresh of complete line + //DEBUG("relPos: " + String(relPos)); + MorseOutput::printOnScroll(2, style, screenPos, text); // these characters are 9 pixels wide, + } + Heltec.display -> setFont(DialogInput_plain_15);; + screenPos += (Heltec.display -> getStringWidth(text) / C_WIDTH); + if (linebreak) { + MorseOutput::newLine(scroll); + pos = 0; screenPos = 0; lastStyle = REGULAR; + } +} + + +void MorseOutput::newLine(boolean scroll) { + //DEBUG("Newline!"); + linePointer = (linePointer + 1) % NoOfLines; + if (relPos && relPos != maxPos) + --relPos; + bottomLine = linePointer; + textBuffer[bottomLine][0] = (char) 0; /// and empty the bottom line + + if (relPos == 0 || relPos == maxPos) + refreshScrollArea(relPos); + if (scroll) + MorseOutput::displayScrollBar(true); + +} + +/// refresh all three lines from buffer in scroll area; + +void MorseOutput::refreshScrollArea(int relPos) { + //int pos = ((bottomLine + relPos +1) % NoOfLines); + refreshScrollLine(((bottomLine + relPos + 1) % NoOfLines), 0); /// refresh all three lines + refreshScrollLine(((bottomLine + relPos + 2) % NoOfLines), 1); + refreshScrollLine(((bottomLine + relPos + 3) % NoOfLines), 2); + Heltec.display -> display(); +} + +/// print a line to the screen + +void MorseOutput::refreshScrollLine(int bufferLine, int displayLine) { + String temp; + temp.reserve(16); + temp = ""; + char c; + unsigned char ch; + boolean irFlag = false; + FONT_ATTRIB style = REGULAR; + int pos = 0; + uint8_t charsPrinted; + + Heltec.display -> setColor(BLACK); + Heltec.display -> fillRect(0, SCROLL_TOP + displayLine * LINE_HEIGHT , 127, LINE_HEIGHT + 1); // black out the line on screen + for (int i = 0; (c = textBuffer[bufferLine][i]) ; ++i) { + // if (c == ' ') DEBUG("Blank!"); + if (c < 5) { /// a flag + if (irFlag) /// at the end of an emphasized string + { + //DEBUG("irFl>>" + temp + "<<"); + charsPrinted = MorseOutput::printOnScroll(displayLine, style, pos, temp) / C_WIDTH; + style = REGULAR; + pos += charsPrinted; + temp = ""; + irFlag = false; + } + else /// at the beginning of an emphasized string + { + if (temp.length()) { + //DEBUG("noFl>>" + temp + "<<"); + + charsPrinted = MorseOutput::printOnScroll(displayLine, style, pos, temp) / C_WIDTH; + style = REGULAR; + pos += charsPrinted; + temp = ""; + } + style = (FONT_ATTRIB) c; + irFlag = true; + } + } + else { /// normal character - add it to temp + temp += c; + // DEBUG("temp >>" + temp + "<<"); + } + } + + if (temp.length()) + MorseOutput::printOnScroll(displayLine, style, pos, temp); +} + + +/// place a string onto the scroll area; line = 0, 1 or 2 + +uint8_t MorseOutput::printOnScroll(uint8_t line, FONT_ATTRIB how, uint8_t xpos, String mystring) { + uint8_t w; +// DEBUG("pos: " + String(xpos) + " >" + mystring + "<"); + if (how > BOLD) + Heltec.display -> setColor(WHITE); + else + Heltec.display -> setColor(BLACK); + + if (how & BOLD) + Heltec.display -> setFont(DialogInput_bold_15); + else + Heltec.display -> setFont(DialogInput_plain_15); + + Heltec.display -> setTextAlignment(TEXT_ALIGN_LEFT); + + // convert the array characters into a String object + w = Heltec.display -> getStringWidth(mystring); + Heltec.display -> fillRect(xpos * C_WIDTH, SCROLL_TOP + line * LINE_HEIGHT , w, LINE_HEIGHT + 1); + + if (how > BOLD) + Heltec.display -> setColor(BLACK); + else + Heltec.display -> setColor(WHITE); + + Heltec.display -> drawString(xpos * C_WIDTH, SCROLL_TOP + line * LINE_HEIGHT, mystring); + Heltec.display -> display(); + resetTOT(); + return w; // we return the actual width of the output, in case of converted UTF8 characters +} + +/// clear the three lines of the display area + +void MorseOutput::clearThreeLines() { + for (int i = 0; i < 3; ++i) { + Heltec.display -> setColor(BLACK); + Heltec.display -> fillRect(0, SCROLL_TOP + i * LINE_HEIGHT , 127, LINE_HEIGHT + 1); + Heltec.display -> setColor(WHITE); + } +} + + +void MorseOutput::clearScroll() { + MorseOutput::printToScroll_internal(REGULAR, "", false); + clearScrollBuffer(); + clearThreeLines(); +} + + + +void MorseOutput::drawVolumeCtrl (boolean inverse, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t volume) { // volume = 0-19 + int i = (width - 4) * volume / 19; +//DEBUG(String(i)); + if (inverse) + Heltec.display -> setColor(BLACK); + else + Heltec.display -> setColor(WHITE); + + Heltec.display -> fillRect(x, y, width, height); + + if (!inverse) + Heltec.display -> setColor(BLACK); + else + Heltec.display -> setColor(WHITE); + + Heltec.display -> fillRect(x + 2, y + 4, (width - 4) * volume / 19, height - 8); + Heltec.display -> drawHorizontalLine(x + 2, y + height / 2, width - 4); + Heltec.display -> display(); + resetTOT(); +} + + +void MorseOutput::displayScrollBar(boolean visible) { /// display a scroll bar on the right edge of the display + const int l_bar = 3 * 49 / NoOfLines; + + if (visible) { + Heltec.display -> setColor(WHITE); + Heltec.display -> drawVerticalLine(127, 15, 49); + Heltec.display -> setColor(BLACK); + Heltec.display -> drawVerticalLine(127, 15 + (relPos * (49 - l_bar) / maxPos), l_bar); + } else { + Heltec.display -> setColor(BLACK); + Heltec.display -> drawVerticalLine(127, 15, 49); + } + Heltec.display -> display(); + resetTOT(); +} + + +///// display battery status as text and icon, parameter v: Voltage in mV + +void MorseOutput::displayBatteryStatus(int v) { /// v in millivolts! + + int a, b, c; String s; double d; + s.reserve(20); + d = v / 50; + c = round(d) * 50; + // DEBUG("v: " + String (v) + " c: " + String(c)); + a = c / 1000; + b = (c - 1000 * a) / 100; + if (v > 1000) + s = "U: " + String(a) + "." + String(b) + " V"; + else + s = "Unknown ?"; + printOnScroll(2, REGULAR, 0, s); + int w = constrain(v, 3100, 4100); + w = map(w, 3100, 4100, 0, 31); + Heltec.display -> drawRect(75, SCROLL_TOP + 2 * LINE_HEIGHT + 3, 35, LINE_HEIGHT - 4); + Heltec.display -> drawRect(110, SCROLL_TOP + 2 * LINE_HEIGHT + 5, 4, LINE_HEIGHT - 8); + if (v > 1000) + Heltec.display -> fillRect(77, SCROLL_TOP + 2 * LINE_HEIGHT + 5 , w, LINE_HEIGHT - 8); + Heltec.display -> display(); +} + +void MorseOutput::displayEmptyBattery(void (*f)()) { /// display a warning and go to (return to) deep sleep + Heltec.display -> clear(); + Heltec.display -> drawRect(10, 11, 95, 50); + Heltec.display -> drawRect(105, 26, 15, 20); + printOnScroll(1, INVERSE_BOLD, 4, "EMPTY"); + delay(4000); + (*f)(); +} + + +/// display volume as a progress bar: vol = 1-100 +void MorseOutput::displayVolume (boolean speedsetting, uint8_t volume) { + drawVolumeCtrl(speedsetting ? false : true, 93, 0, 28, 15, volume); + Heltec.display -> display(); +} + + +////// S Meter for Trx modus + +void MorseOutput::updateSMeter(int rssi) { + + static boolean wasZero = false; + + if (rssi == 0) + if (wasZero) + return; + else { + drawVolumeCtrl( false, 93, 0, 28, 15, 0); + wasZero = true; + } + else { + drawVolumeCtrl( false, 93, 0, 28, 15, constrain(map(rssi, -150, -20, 0, 100), 0, 100)); + wasZero = false; + } + Heltec.display -> display(); +} + +/// for morse decoder: show a suqare on status line when we detected a signal + +void MorseOutput::drawInputStatus( boolean on) { + if (on) + Heltec.display -> setColor(BLACK); + else + Heltec.display -> setColor(WHITE); + Heltec.display -> fillRect(1, 1, 13, 13); + Heltec.display -> display(); +} + + +void MorseOutput::dispLoraLogo() { /// display a small logo in the top right corner to indicate we operate with LoRa + Heltec.display -> setColor(BLACK); + Heltec.display -> drawXbm(121, 2, lora_width, lora_height, lora_bits); + Heltec.display -> setColor(WHITE); + Heltec.display -> display(); +} + +void MorseOutput::dispWifiLogo() { // display a small logo in the top right corner to indicate we operate with WiFi + Heltec.display -> setColor(BLACK); + Heltec.display -> drawXbm(121, 2, wifi_width, wifi_height, wifi_bits); + Heltec.display -> setColor(WHITE); + Heltec.display -> display(); +} + +//////// Display the status line in CW Keyer Mode +//////// Layout of top line: +//////// Tch ul 15 WpM +//////// 0 5 0 + + +void MorseOutput::printOnStatusLine(boolean strong, uint8_t xpos, String string) { // place a string onto the status line; chars are 7px wide = 18 chars per line + if (strong) + Heltec.display -> setFont(DialogInput_bold_12); + else + Heltec.display -> setFont(DialogInput_plain_12); + Heltec.display -> setTextAlignment(TEXT_ALIGN_LEFT); + uint8_t w = Heltec.display -> getStringWidth(string); + Heltec.display -> setColor(WHITE); + Heltec.display -> fillRect(xpos * 7, 0 , w, 15); + Heltec.display -> setColor(BLACK); + Heltec.display -> drawString(xpos * 7, 0, string); + Heltec.display -> setColor(WHITE); + Heltec.display -> display(); + resetTOT(); +} + +void MorseOutput::clearStatusLine() { // the status line is at the top, and inverted! + Heltec.display -> setColor(WHITE); + Heltec.display -> fillRect(0, 0, 128, 15); + Heltec.display -> setColor(BLACK); + + Heltec.display -> display(); +} + +void MorseOutput::clearLine(uint8_t line) { /// clear a line - display is done somewhere else! + Heltec.display -> setColor(BLACK); + Heltec.display -> fillRect(0, SCROLL_TOP + line * LINE_HEIGHT , 127, LINE_HEIGHT+1); + Heltec.display -> setColor(WHITE); +} + + +void MorseOutput::showVolumeScope(uint16_t mini, uint16_t maxi) +{ + uint16_t a, b, c; + a = map(mini, 0, 4096, 0, 125); + b = map(maxi, 0, 4000, 0, 125); + c = b - a; + MorseOutput::clearLine(2); + Heltec.display->drawRect(5, SCROLL_TOP + 2 * LINE_HEIGHT + 5, 102, LINE_HEIGHT - 8); + Heltec.display->drawRect(30, SCROLL_TOP + 2 * LINE_HEIGHT + 5, 52, LINE_HEIGHT - 8); + Heltec.display->fillRect(a, SCROLL_TOP + 2 * LINE_HEIGHT + 7, c, LINE_HEIGHT - 11); + Heltec.display->display(); +} + + +void MorseOutput::resetTOT() { //// reset the Time Out Timer - we do this whenever there is a screen update + MorseOutput::TOTcounter = millis(); +} + + +/////// functions for audio output + + +void MorseOutput::soundSetup() +{ + // set up PWMs for tone generation + ledcSetup(toneChannel, toneFreq, pwmResolution); + ledcAttachPin(LF_Pin, toneChannel); + + ledcSetup(lineOutChannel, toneFreq, pwmResolution); + ledcAttachPin(lineOutPin, lineOutChannel); ////// change this for real version - no line out currntly + + ledcSetup(volChannel, volFreq, pwmResolution); + ledcAttachPin(HF_Pin, volChannel); + + ledcWrite(toneChannel, 0); + ledcWrite(lineOutChannel, 0); +} + + +void MorseOutput::pwmTone(unsigned int frequency, unsigned int volume, boolean lineOut) { // frequency in Hertz, volume in range 0 - 19; we use 10 bit resolution + const uint16_t vol[] = {0, 1, 2, 4, 6, 9, 14, 21, 31, 45, 70, 100, 140, 200, 280, 390, 512, 680, 840, 1023}; // 20 values + unsigned int i = constrain(volume, 0, 19); + unsigned int j = vol[i] >> 5; // experimental: soften the inital click + + //DEBUG(String(vol[i])); + //DEBUG(String(frequency)); + if (lineOut) { + ledcWriteTone(lineOutChannel, (double) frequency); + ledcWrite(lineOutChannel, dutyCycleFiftyPercent); + } + + //ledcWrite(volChannel, volFreq); + ledcWrite(volChannel, j); // experimental: soften the inital click + delay(6); // experimental: soften the inital click + ledcWrite(volChannel, vol[i]); + ledcWriteTone(toneChannel, frequency); + + + if (i == 0 ) + ledcWrite(toneChannel, dutyCycleZero); + else + ledcWrite(toneChannel, dutyCycleFiftyPercent); +} + + +void MorseOutput::pwmNoTone() { // stop playing a tone by changing duty cycle of the tone to 0 + ledcWrite(toneChannel, dutyCycleTwentyPercent); + ledcWrite(lineOutChannel, dutyCycleTwentyPercent); + ledcWrite(volChannel, 2); // experimental: soften the click + delayMicroseconds(125); + ledcWrite(toneChannel, dutyCycleZero); + ledcWrite(lineOutChannel, dutyCycleZero); +} + + +void MorseOutput::pwmClick(unsigned int volume) { /// generate a click on the speaker + if (!MorsePreferences::pliste[posClicks].value) + return; + pwmTone(572,volume > 4 ? volume-4 : volume, false); + delay(3); + //pwmTone(286,volume, false); + pwmTone(1144,volume > 3 ? volume-3 : volume, false); + + delay(6); + //pwmTone(143,volume-4, false); + //delay(7); + pwmNoTone(); +} + +void MorseOutput::soundSignalOK() { + pwmTone(440, MorsePreferences::sidetoneVolume, false); + delay(97); + pwmNoTone(); + pwmTone(587, MorsePreferences::sidetoneVolume, false); + delay(193); + pwmNoTone(); +} + + +void MorseOutput::soundSignalERR() { + pwmTone(311, MorsePreferences::sidetoneVolume, false); + delay(193); + pwmNoTone(); +} diff --git a/Software/src/Version 5/MorseOutput.h b/Software/src/Version 5/MorseOutput.h new file mode 100644 index 0000000..a4f6a2a --- /dev/null +++ b/Software/src/Version 5/MorseOutput.h @@ -0,0 +1,65 @@ +#ifndef MORSEDISPLAY_H_ +#define MORSEDISPLAY_H_ + +#include "Arduino.h" +#include "heltec.h" + +#define NoOfLines 15 + +enum FONT_ATTRIB +{ + VOID, REGULAR, BOLD, INVERSE_REGULAR, INVERSE_BOLD +}; + +const FONT_ATTRIB FONT_INCOMING = REGULAR; +const FONT_ATTRIB FONT_OUTGOING = BOLD; + +namespace MorseOutput +{ + extern const int8_t maxPos; + extern int8_t relPos; + extern volatile uint64_t TOTcounter; + const int notes[] = + {0, 233, 262, 294, 311, 349, 392, 440, 466, 523, 587, 622, 698, 784, 880, 932}; + + + void clearDisplay(); + void decreaseBrightness(); + void sleep(); + void printOnStatusLine(boolean strong, uint8_t xpos, String string); + void clearBuffer(); + void refreshScrollArea(int relPos); + void refreshScrollLine(int bufferLine, int displayLine); + uint8_t printOnScroll(uint8_t line, FONT_ATTRIB how, uint8_t xpos, String mystring); + void printToScroll(FONT_ATTRIB style, String text, boolean autoflush, boolean scroll); + void printToScroll_internal(FONT_ATTRIB style, String text, boolean scroll); + void clearThreeLines(); + void clearLine(uint8_t line); + void clearScrollBuffer(); + void clearScroll(); + void flushScroll(boolean scroll); + void newLine(boolean scroll); + void displayScrollBar(boolean visible); + void displayBatteryStatus(int v); + void displayEmptyBattery(void (*f)()); + void displayVolume(boolean speedsetting, uint8_t volume); + void updateSMeter(int rssi); + void drawInputStatus(boolean on); + void clearStatusLine(); + void showVolumeBar(uint16_t mini, uint16_t maxi); + void drawVolumeCtrl(boolean inverse, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t volume); + void showVolumeScope(uint16_t mini, uint16_t maxi); + void dispLoraLogo(); + void dispWifiLogo(); + + void resetTOT(); + + void soundSetup(); + void pwmTone(unsigned int frequency, unsigned int volume, boolean lineOut); + void pwmNoTone(); + void pwmClick(unsigned int volume); + void soundSignalOK(); + void soundSignalERR(); +} + +#endif /* MORSEDISPLAY_H_ */ diff --git a/Software/src/Version 5/MorsePreferences.cpp b/Software/src/Version 5/MorsePreferences.cpp new file mode 100644 index 0000000..66037c3 --- /dev/null +++ b/Software/src/Version 5/MorsePreferences.cpp @@ -0,0 +1,1729 @@ +/****************************************************************************************************************************** + * morse_3 Software for the Morserino-32 multi-functional Morse code machine, based on the Heltec WiFi LORA (ESP32) module *** + * Copyright (C) 2018-2020 Willi Kraml, OE1WKL *** + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + *****************************************************************************************************************************/ + +#include // ESP 32 library for storing things in non-volatile storage +#include "MorseOutput.h" +#include "MorsePreferences.h" +#include "abbrev.h" +#include "english_words.h" +#include "ClickButton.h" // button control library +#include "goertzel.h" + +using namespace MorsePreferences; + +Preferences pref; // use the Preferences library for storing and retrieving objects + +///// the preferences variable and their defaults + +//// First, all those that can be changed in the parameters (preferences) menu + +#define SizeOfArray(x) (sizeof(x) / sizeof(x[0])) + + /* ////////////////// order of preferences //////////// make sure the next twom items are in sync with the following, up to posSerislOut: + enum prefPos : uint8_t + {posClicks, posPitch, posExtPddlPolarity, posPolarity, // 0 + posCurtisMode, posCurtisBDahTiming, posCurtisBDotTiming, posACS, // 4 + posEchoToneShift, posInterWordSpace, posInterCharSpace, posRandomOption, // 8 + posRandomLength, posCallLength, posAbbrevLength, posWordLength, // 12 + posGeneratorDisplay, posWordDoubler, posEchoDisplay, posEchoRepeats, posEchoConf, // 16 + posKeyExternalTx, posLoraCwTransmit, posGoertzelBandwidth, posSpeedAdapt, // 21 + posKochSeq, posCarouselStart, posLatency, posRandomFile, posExtAudioOnDecode, posTimeOut, // 25 + posQuickStart, posAutoStop, posMaxSequence, posLoraChannel, posSerialOut, // 31 + // to be treated differently: + posKochFilter, // 36 + posLoraBand, posLoraQRG, posSnapRecall, posSnapStore, posVAdjust, posHwConf // 37 + }; + */ + +const char * prefName[] = {"encoderClicks", "sidetoneFreq", "useExtPaddle", "didah", + "keyermode", "curtisBTiming", "curtisBDotT", "ACSlength", + "echoToneShift", "interWordSpace", "farnsworthMode", "randomOption", + "randomLength", "callLength", "abbrevLength", "wordLength", + "GeneratorDispl", "wordDoubler", "echoDisplay", "echoRepeats", "echoConf", + "KeyExternalTx", "LoraCwTransmit", "goertzelBW", "speedAdapt", + "KochSeq", "carouselStart", "latency", "randomFile", "extAudioOnDecod", "timeOut", + "quickStart", "autoStop", "maxSequence", "LoraChannel", "serialOut"}; + +parameter MorsePreferences::pliste[] = { + { + 1, 0, 1, 1, // should rotating the encoder generate a click? yes If 1 + "Encoder Click", + "Click when encoder is turned", + true, + {"Off", "On"} + }, + { + 10, 0, 14, 1, // side tone frequency 0-14 was 1 - 15 + "Tone Pitch", + "Pitch of CW tone", + true, + {"233 Hz bflat", "262 Hz c1", "294 Hz d1", "311 Hz e1", "349 Hz f1", "392 Hz g1", "440 Hz a1", "466 Hz b1flat", "523 Hz c2", "587 Hz d2", "622 Hz e2", "698 Hz f2", "784 Hz g2", "880 Hz a2", "932 Hz b2flat"} + }, + { + 0, 0, 1, 1, // true (1) when we need to reverse the polarity of the ext paddle + "External Pol.", + "Polarity of external paddle", + true, + {"Normal", "Reversed"} + }, + { + 1, 0, 1, 1, // paddle polarity + "Paddle Polar.", + "Where are dits and dahs", + true, + {"-. dah dit", ".- dit dah"} + }, + { + 2, 1, 5, 1, // keyer modes (Curtis modes etc.) + "Keyer Mode", + "Iambic Modes, Non-squeeze mode, Straight Key mode", + true, + {"", "Iambic A", "Iambic B", "Ultimatic", "Non-Squeeze", "Straight Key" } + }, + { + 45, 0, 100, 5, + "CurtisB DahT%", // keyer: timing for enhanced Curtis mode: dah 0 - 100 + "Timing for CurtisB Dahs in %", + false, + {} + }, + { + 75, 0, 100, 5, // keyer: timing for enhanced Curtis mode: dit 0 - 100 + "CurtisB DitT%", + "Timing for CurtisB Dits in %", + false, + {} + }, + { + 0, 0, 3, 1, // AutoChar Spacing: extend the pause between chars; 0=off, 1-3 ==> 2-4 + "AutoChar Spc", + "ACS: Minimum spacing between characters", + true, + {"Off", "2 dits", "3 dits", "4 dits"} + }, + { + 1, 0, 2, 1, // 0 = no shift, 1 = up, 2 = down (a half tone) 0 - 2 + "Tone Shift", + "Shift tones up or down (at receive, or echo input)", + true, + {"No Tone Shift", "Up 1 Half", "Down 1 Half"} + }, + { + 7, 6, 45, 1, // Generator: normal interword spacing in lengths of dit, 6 - 45 ; default = norm = 7 + "InterWord Spc", + "The time (in dits) that is inserted between generated words", + false, + {} + }, + { + 3, 3, 24, 1, // for generators: intercharacter space, in dit dit lengths + "Interchar Spc", + "Space between generated characters, in dits", + false, + {} + }, + { + 0, 0, 9, 1, // Generators: from which pool are we generating random characters? + "Random Groups", + "Which character subsets should be used for generating", + true, + {"All Chars","Alpha", "Numerals", "Interpunct.", "Pro Signs", "Alpha + Num", "Num+Interp.", "Interp+ProSn","Alph+Num+Int","Num+Int+ProS"} + }, + { + 3, 1, 10, 1, // generators: length of randomchar groups 2-6; 7-10 for rnd length 2 to 3-6 + "Length Rnd Gr", + "How many characters in each group of random characters?", + true, + {"", "1", "2", "3", "4", "5", "6", "2 to 3", "2 to 4", "2 to 5", "2 to 6"} + }, + { + 0, 0, 4, 1, // Generators: max length of call signs generated (0 = unlimited) 0, 3 - 6 + "Length Calls", + "Maximum length of generated call signs", + true, + {"Unlimited", "3", "4", "5", "6"} + }, + { + 0, 0, 5, 1, // Generators: max length of abbreviations generated (0 = unlimited) 0, 1-5 = 2-6 + "Length Abbrev", + "Maximum length of generated common CW abbreviations", + true, + {"Unlimited", "2", "3", "4", "5", "6"} + }, + { + 0, 0, 5, 1, // Generators: max length of english words generated (0 = unlimited) 0, 1-5 = 2-6 + "Length Words", + "Maximum length of generated common English words", + true, + {"Unlimited", "2", "3", "4", "5", "6"} + }, + { + 1, 0, 2, 1, // Generator: how we display what the trainer generates: nothing, by char, or by word 0-2 + "CW Gen Displ", + "No, char by char or word by word display", + true, + {"Display off", "Char by char", "Word by word"} + }, + { + 0, 0, 1, 1, // in CW trainer mode: repeat each word? + "Each Word 2x", + "Repeat each generated word", + true, + {"OFF", "ON"} + }, + { + 1, 1, 3, 1, // 1 = CODE_ONLY 2 = DISP_ONLY 3 = CODE_AND_DISP + "Echo Prompt", + "Echo Trainer prompt by sound, display, or both?", + true, + {"", "Sound only", "Display only", "Sound & Disp"} + }, + { + 3, 0, 7, 1, // how often will echo trainer repeat after an error? 0 - 7, 7=forever, default = 3 + "Echo Repeats", + "Maximum repetition of words in Echo Trainer after error", + true, + {"0", "1", "2", "3", "4", "5", "6", "Forever"} + }, + { + 1, 0, 1, 1, // true if echo trainer confirms audibly too, not just visually + "Confrm. Tone", + "Audible confirmation in Echo Trainer", + true, + {"OFF", "ON"} + }, + { + 1, 0, 3, 1, // key TX in generator/player/receiver mode? + "Key ext TX", + "When to key an external transmitter", + true, + {"Never", "CW Keyer only", "Keyer & Gen.", "Keyer&Gen.&RX"} + }, + { + 0, 0, 2, 1, // transmit generated/played things via LoRa or WiFi? + "Generator Tx", + "Generated CW to be sent by LoRa or Wifi?", + true, + {"Tx OFF", "LoRa Tx ON", "WiFi Tx ON"} + }, + { + 0, 0, 1, 1, // 0: "Wide" 1: "Narrow" + "Bandwidth", + "Audio bandwidth of the CW decoder", + true, + {"Wide", "Narrow"} + }, + { + 0, 0, 1, 1, // true: in echo modes, increase speed when OK, reduce when not ok + "Adaptv. Speed", + "Adaptive Speed of Echo Trainer?", + true, + {"OFF", "ON"} + }, + { + 0, 0, 4, 1, // select Koch sequence: 0 = native/JLMC, 1 = LCWO, 2 = CW Academy, 3 = LICW, 4 = Custom + "Koch Sequence", + "Sequence of characters for the Koch method", + true, + {"M32", "LCWO", "CW Academy", "LICW Carousel", "Custom Chars"} + }, + { + 0, 0, 13, 1, // Offset for LICW Carousel + "LICW Carousel", + "Entry Point into the LICW Carousel curriculum", + true, + {"BC1: r e a", "BC1: t i n", "BC1: p g s", "BC1: l c d", "BC1: h o f", "BC1: u w b", + "BC2: k m y", "BC2: 5 9 ,", "BC2: q x v", "BC2: ar sk =", "BC2: 1 6 .", + "BC2: z j /", "BC2: 2 8 bk", "BC2: 4 0"} + }, + { + 4, 0, 7, 1, // time span after currently sent element during which paddles are not checked; in 1/8th dits + "Latency", + "How long (in dit) after generating dit or dah the resp. paddle will be insensitive", + true, + {"0%", "12.5%", "25%", "37.5%", "50%", "62.5%", "75%", "87.5%", "100%"} + }, + { + 0, 0, 1, 1, // if 0, play file word by word; if 1, skip random number of words (0 - 255) between reads + "Randomize File", + "Should file player skip words randomly", + true, + {"OFF", "ON"} + }, + { + 0, 0, 1, 1, // send decoded audio also to external audio I/O port + "Decoded on IO", + "Decoded audio to be sent to the I/O port)", + true, + {"OFF", "ON"} + }, + { + 1, 0, 3, 1, // time-out value: 0 = no timeout, 1 = 5 min, 2 = 10 min, 3 = 15 min + "Time-out", + "Time of inactivity before the device will go to sleep mode", + true, + {"No time-out", "5 min", "10 min", "15 min"} + }, + { + 0, 0, 1, 1, // should we start the last executed command immediately? + "Quick Start", + "Bypass initial menu selection and start with mode used last", + true, + {"OFF", "ON"} + }, + { + 0, 0, 1, 1, // Stop after each word in CW generator modes? + "StopRep", + "Stop after each word; choose repeat or next word with paddle", + true, + {"OFF", "ON"} + }, + { + 0, 0, 250, 5, // max # of words generated before the Morserino pauses, 0 = no limit; allow step = 5 only + "Max # of Words", + "Stop after selected number of words", + false, + {} + }, + { + 0, 0, 1, 1, // allows to set different LoRa sync words, and so creating virtual "channels"; 0 = 0x27, 1 = 0x66 + "LoRa Channel", + "Which virtual channel is used by LoRa", + true, + {"Standard", "Secondary"} + }, + { + 5, 0, 5, 1, // output characters on USB serial? 0 = none (but DEBUG/ERR) 1= keyed, 2 = decode, 3=both, 4=generated, 5=all + "Serial Output", + "Select what is sent to the serial (USB) port", + true, + {"Nothing", "Keyed", "Decoded", "Keyed+Decoded", "Generated", "All"} + } +}; + +String extraItems[] = {"Koch Lesson", "LoRa Band", "LoRa Frequ", "LoRa Power", "RECALLSnapshot", "STORE Snapshot", "Calibrate Batt", "Hardware Conf" }; + +///// used like a parameter, but not in parameter menuPtr + + +uint8_t MorsePreferences::loraBand = 0; // 0 = 433, 1 = 868, 2 = 920 +uint32_t MorsePreferences::loraQRG = QRG433; // for 70 cm band +uint8_t MorsePreferences::loraPower = 14; // default 14 dBm = 25 mW + + + + ///// stored in preferences, but not adjustable through preferences menu: + + uint8_t MorsePreferences::version_major = VERSION_MAJOR; + uint8_t MorsePreferences::version_minor = VERSION_MINOR; + + uint8_t MorsePreferences::sidetoneVolume = 16; // side tone volume, as a value between 0 and 19 0 -19 + extern const uint8_t MorsePreferences::volumeMin = 0; + extern const uint8_t MorsePreferences::volumeMax = 19; + + uint8_t MorsePreferences::wpm = 15; // keyer speed in words per minute 5 - 60 + extern const uint8_t MorsePreferences::wpmMin = 5; + extern const uint8_t MorsePreferences::wpmMax = 60; + + uint8_t MorsePreferences::kochFilter = 5; // constrain output to characters learned according to Koch's method 2 + uint8_t MorsePreferences::kochCharsLength = 52; // # of chars for Koch training + uint8_t MorsePreferences::kochMinimum = 1; + uint8_t MorsePreferences::kochMaximum = 52; + String MorsePreferences::customCharSet = ""; // a place to store the custom character set + boolean MorsePreferences::useCustomChars = false; // flag if we should use custom characters + uint8_t MorsePreferences::responsePause = 5; // in echoTrainer mode, how long do we wait for response? in interWordSpaces; 2-12, default 5 + + uint8_t MorsePreferences::menuPtr = 1; // current position of menu + uint8_t MorsePreferences::newMenuPtr = 1; // current position of menu when changed + String MorsePreferences::wlanSSID = ""; // SSID for connecting to the Internet + String MorsePreferences::wlanPassword = ""; // password for connecting to WiFi router + String MorsePreferences::wlanTRXPeer = ""; // peer Morserino for WiFI TRX + String MorsePreferences::wlanSSID1 = ""; // SSID for connecting to the Internet + String MorsePreferences::wlanPassword1 = ""; // password for connecting to WiFi router + String MorsePreferences::wlanTRXPeer1 = ""; // peer Morserino for WiFI TRX + String MorsePreferences::wlanSSID2 = ""; // SSID for connecting to the Internet + String MorsePreferences::wlanPassword2 = ""; // password for connecting to WiFi router + String MorsePreferences::wlanTRXPeer2 = ""; // peer Morserino for WiFI TRX + String MorsePreferences::wlanSSID3 = ""; // SSID for connecting to the Internet + String MorsePreferences::wlanPassword3 = ""; // password for connecting to WiFi router + String MorsePreferences::wlanTRXPeer3 = ""; // peer Morserino for WiFI TRX + + uint32_t MorsePreferences::fileWordPointer = 0; // remember how far we have read the file in player mode / reset when loading new file + uint8_t MorsePreferences::promptPause = 2; // in echoTrainer mode, length of pause before we send next word; multiplied by interWordSpace + uint8_t MorsePreferences::tLeft = 20; // threshold for left paddle + uint8_t MorsePreferences::tRight = 20; // threshold for right paddle + + uint8_t MorsePreferences::vAdjust = 180; // correction value: 155 - 250 + + + uint8_t MorsePreferences::snapShots = 0; // keep track which snapshots are being used ( 0 .. 7, called 1 to 8) + + uint8_t MorsePreferences::boardVersion = 0; // which Morserino board version? v3 uses heltec Wifi Lora V2, V4 uses V2.1 + + uint8_t MorsePreferences::oledBrightness = 255; + + +//////// end of variables stored in preferences + +//// temporary buffer for conversions, local to this file, and some other items neeeded for preference menus +char numBuffer[16]; + +String topLine; //topLine.reserve(20); +String itemLine; //itemLine.reserve(20); +const int topMax = 17; const int elseMax = 13; +const String emptyLine = " "; + +/// variables for managing snapshots +uint8_t MorsePreferences::memories[8]; +uint8_t MorsePreferences::memCounter; +uint8_t MorsePreferences::memPtr = 0; + + + prefPos MorsePreferences::keyerOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, + posCurtisMode, posCurtisBDahTiming, posCurtisBDotTiming, posACS, posLatency + }; + prefPos MorsePreferences::generatorOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, posInterCharSpace, posInterWordSpace, + posRandomOption, posRandomLength, posCallLength, posAbbrevLength, posWordLength, + posMaxSequence, posAutoStop, posGeneratorDisplay, posWordDoubler, + posKeyExternalTx, posLoraCwTransmit, posLoraChannel + }; + prefPos MorsePreferences::playerOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, + posInterCharSpace, posInterWordSpace, + posMaxSequence, posAutoStop, posGeneratorDisplay, posRandomFile, posWordDoubler, + posKeyExternalTx, posLoraCwTransmit, posLoraChannel + }; + + prefPos MorsePreferences::echoPlayerOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, + posCurtisMode, posCurtisBDahTiming, posCurtisBDotTiming, posACS, posLatency, posInterCharSpace, posInterWordSpace, + posMaxSequence, posRandomFile, posEchoRepeats, posEchoDisplay, posEchoConf, posEchoToneShift, posSpeedAdapt, + }; + + prefPos MorsePreferences::echoTrainerOptions[]= { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, + posCurtisMode, posCurtisBDahTiming, posCurtisBDotTiming, posACS, posLatency, posInterCharSpace, posInterWordSpace, + posRandomOption, posRandomLength, posCallLength, posAbbrevLength, posWordLength, + posMaxSequence, posEchoRepeats, posEchoDisplay, posEchoConf, posEchoToneShift, posSpeedAdapt, + }; + + prefPos MorsePreferences::kochGenOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, + posKochSeq, posCarouselStart, posInterCharSpace, posInterWordSpace, posRandomLength, posAbbrevLength, posWordLength, + posMaxSequence, posAutoStop, posGeneratorDisplay, posWordDoubler, + posKeyExternalTx, posLoraCwTransmit, posLoraChannel + }; + + prefPos MorsePreferences::kochEchoOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, + posCurtisMode, posCurtisBDahTiming, posCurtisBDotTiming, posACS, posLatency, posKochSeq, posCarouselStart, + posInterCharSpace, posInterWordSpace, posRandomLength, posAbbrevLength, posWordLength, + posMaxSequence, posEchoRepeats, posEchoDisplay, posEchoConf, posEchoToneShift, posSpeedAdapt, + }; + + prefPos MorsePreferences::loraTrxOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, + posCurtisMode, posCurtisBDahTiming, posCurtisBDotTiming, posACS, posLatency, posGeneratorDisplay, + posEchoToneShift, posKeyExternalTx, posLoraChannel, posExtAudioOnDecode + }; + + prefPos MorsePreferences::wifiTrxOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, + posCurtisMode, posCurtisBDahTiming, posCurtisBDotTiming, posACS, posLatency, posGeneratorDisplay, posEchoToneShift, + posKeyExternalTx, posExtAudioOnDecode + }; + + prefPos MorsePreferences::extTrxOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, + posCurtisMode, posCurtisBDahTiming, posCurtisBDotTiming, posACS, posLatency, posEchoToneShift, + posGoertzelBandwidth, posExtAudioOnDecode + }; + + prefPos MorsePreferences::decoderOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, + posCurtisMode, posGoertzelBandwidth, posExtAudioOnDecode + }; + + prefPos MorsePreferences::allOptions[] = { posClicks, posPitch, posTimeOut, posQuickStart, posSerialOut, posPolarity, posExtPddlPolarity, + posCurtisMode, posCurtisBDahTiming, posCurtisBDotTiming, posACS, posLatency, posKochSeq, posCarouselStart, + posInterCharSpace, posInterWordSpace, posRandomOption, posRandomLength, posCallLength, posAbbrevLength, posWordLength, + posMaxSequence, posAutoStop, posGeneratorDisplay, posRandomFile, posWordDoubler, + posEchoRepeats, posEchoDisplay, posEchoConf, posEchoToneShift, posSpeedAdapt, + posKeyExternalTx, posLoraCwTransmit, posLoraChannel, posGoertzelBandwidth, posExtAudioOnDecode + }; + +prefPos *MorsePreferences::currentOptions = MorsePreferences::allOptions; + +int MorsePreferences::keyerOptionsSize = SizeOfArray(MorsePreferences::keyerOptions); +int MorsePreferences::generatorOptionsSize = SizeOfArray(MorsePreferences::generatorOptions); +int MorsePreferences::playerOptionsSize = SizeOfArray(MorsePreferences::playerOptions); +int MorsePreferences::echoPlayerOptionsSize = SizeOfArray(MorsePreferences::echoPlayerOptions); +int MorsePreferences::echoTrainerOptionsSize = SizeOfArray(MorsePreferences::echoTrainerOptions); +int MorsePreferences::kochGenOptionsSize = SizeOfArray(MorsePreferences::kochGenOptions); +int MorsePreferences::kochEchoOptionsSize = SizeOfArray(MorsePreferences::kochEchoOptions); +int MorsePreferences::loraTrxOptionsSize = SizeOfArray(MorsePreferences::loraTrxOptions); +int MorsePreferences::wifiTrxOptionsSize = SizeOfArray(MorsePreferences::wifiTrxOptions); +int MorsePreferences::extTrxOptionsSize = SizeOfArray(MorsePreferences::extTrxOptions); +int MorsePreferences::decoderOptionsSize = SizeOfArray(MorsePreferences::decoderOptions); +int MorsePreferences::allOptionsSize = SizeOfArray(MorsePreferences::allOptions); + + +int currentOptionSize; + +extern double voltage_raw; +extern int16_t volt; + +////// setup preferences /////// + + +boolean MorsePreferences::setupPreferences(uint8_t atMenu) { + // enum morserinoMode {morseKeyer, loraTrx, morseGenerator, echoTrainer, shutDown, morseDecoder, invalid }; + static int oldPos = 1; + int t; + + int ptrIndex, ptrMax; + prefPos posPtr; + + m32state = preferences_loop; + ptrMax = currentOptionSize; + + ///// we should check here if the old ptr (oldIndex) is contained in the current preferences collection (currentOptions) + ptrIndex = 1; + for (int i = 0; i < ptrMax; ++i) { + if (currentOptions[i] == oldPos) { + ptrIndex = i; + break; + } + } + posPtr = currentOptions[ptrIndex]; + keyOut(false, true, 0, 0); // turn the LED off, unkey transmitter, or whatever; just in case.... + keyOut(false,false, 0, 0); + displayKeyerPreferencesMenu(posPtr); + MorseOutput::printOnScroll(2, REGULAR, 0, " "); + + while (true) { // we wait for single click = selection or long click = exit - or single or long click or RED button, or for a serial event + serialEvent(); + if (goToMenu) { + jsonActivate(ACT_EXIT); + goToMenu = false; + goto exitFromHere; + } + Buttons::modeButton.Update(); + switch (Buttons::modeButton.clicks) { // button was clicked + case 1: if (adjustKeyerPreference(posPtr)) + goto exitFromHere; + break; + case -1: //////// long press indicates we are done with setting preferences - check if we need to store some of the preferences + + exitFromHere: if (MorsePreferences::useCustomChars) + koch.setCustomChars(getCustomChars()); //// get custom characters + if (m32protocol && posPtr < posKochFilter) + jsonActivate(ACT_EXIT); + writePreferences("morserino"); + //delay(200); + return false; + break; + } + + Buttons:: volButton.Update(); // RED button + switch (Buttons:: volButton.clicks) { // was clicked + case 1: // recall snapshot + if (MorsePreferences::recallSnapshot()) { + writePreferences("morserino"); + if (m32protocol) + jsonActivate(ACT_RECALLED); + } + else if(m32protocol) + jsonActivate(ACT_CANCELLED); + return true; + break; + case 2: MorseOutput::decreaseBrightness(); + displayKeyerPreferencesMenu(posPtr); + break; + case -1: //store snapshot + if (MorsePreferences::storeSnapshot(atMenu)) { + // writePreferences("morserino"); now in writePreferences() + if (m32protocol) + jsonActivate(ACT_SET); + } + else if (m32protocol) + jsonActivate(ACT_CANCELLED); + while(Buttons:: volButton.clicks) + Buttons:: volButton.Update(); + return false; + break; + } + + + //// display the value of the preference in question + + if ((t=checkEncoder())) { + MorseOutput::pwmClick(MorsePreferences::sidetoneVolume); /// click + ptrIndex = (ptrIndex +ptrMax + t) % ptrMax; + posPtr = currentOptions[ptrIndex]; + // remember menu position + oldPos = posPtr; + + displayKeyerPreferencesMenu(posPtr); + MorseOutput::printOnScroll(2, REGULAR, 0, " "); + + Heltec.display -> display(); // update the display + } // end if (encoderPos) + checkShutDown(false); // check for time out + } // end while - we leave as soon as the button has been pressed long +} // end function setupKeyerPreferences() + + +//////// Display the preferences menu - we display the following preferences + +//// new way of displaying it + +void MorsePreferences::displayKeyerPreferencesMenu(prefPos pos) { + const int maxLength = 14; + + MorseOutput::clearDisplay(); + + if (pos < posKochFilter) + topLine = "Set Preferences:"; + else if (pos < posLoraBand) + topLine = "Set Lesson #:"; + else if (pos < posSnapRecall) + topLine = "Config LoRa:"; + else if (pos < posVAdjust) + topLine = "Manage Snapshots:"; + else if (pos < posHwConf) + topLine = "Calibrate Voltage"; + else + topLine = "Hardware Config."; + + topLine += emptyLine.substring(0,topMax - topLine.length()); + MorseOutput::printOnStatusLine( true, 0, topLine); + + itemLine = (pos <= posSerialOut ? MorsePreferences::pliste[pos].parName : extraItems[pos-posKochFilter]); + itemLine += emptyLine.substring(0,maxLength - itemLine.length()); + MorseOutput::printOnScroll(1, BOLD, 0, itemLine); + displayValueLine(pos); +} + +/// posKochFilter, posLoraBand, posLoraQRG, posSnapRecall, posSnapStore, posVAdjust, posHwConf + + + +void MorsePreferences::displayValueLine(prefPos pos) { + String valueLine; valueLine.reserve(20); + const String emptyLine = " "; + const int maxLength = 14; + String jsonValueLine; jsonValueLine.reserve(48); + String item; item.reserve(18); + int value; + + value = pos <= posSerialOut ? (int) pliste[pos].value : getValue(pos); + valueLine = (pos <= posSerialOut ? (pliste[pos].isMapped ? pliste[pos].mapping[pliste[pos].value] : String(pliste[pos].value)) : getValueLine(pos)); + if (pos == posMaxSequence && pliste[pos].value == 0) /// we do a "mapping" for 0 here + valueLine = "Unlimited"; + valueLine += emptyLine.substring(0,maxLength - valueLine.length()); + + if (m32protocol) { + jsonValueLine = valueLine; + jsonValueLine.trim(); + switch (pos) { + case posKochFilter: + item = "Select Lesson"; + break; + case posSnapRecall: + item = "Recall Snapshot"; + break; + case posSnapStore: + item = "Store Snapshot"; + break; + default: + item = itemLine; + item.trim(); + break; + } + if (pos < posKochFilter || pos == posSnapRecall || pos == posSnapStore) + jsonConfigShort(item, value, jsonValueLine); + else if (pos == posKochFilter) + jsonMenu(MorseMenu::getMenuPath(MorsePreferences::menuPtr) + "/" + jsonValueLine, (unsigned int) MorsePreferences::menuPtr, + (m32state == menu_loop ? false : true), MorseMenu::isRemotelyExecutable(MorsePreferences::menuPtr)); + } + MorseOutput::printOnScroll(2, REGULAR, 1, valueLine); +} + +int MorsePreferences::getValue(prefPos pos) { /// a value to return for m32protocol + switch (pos) { + case posKochFilter: + return (int) MorsePreferences::kochFilter; + break; + case posSnapRecall: + if (MorsePreferences::memCounter && MorsePreferences::memPtr != MorsePreferences::memCounter) + return MorsePreferences::memories[MorsePreferences::memPtr] +1; + break; + case posSnapStore: + if (MorsePreferences::memPtr < 8) + return MorsePreferences::memPtr+1; + break; + } + return 0; +} + +String MorsePreferences::getValueLine(prefPos pos) { + String str; str.reserve(14); + uint8_t mask; + const int a = (int) QRG433; + const int b = (int) QRG866; + const int c = (int) QRG920; + String milliWatt[] = {"10", "12.5", "16", "20", "25", "32", "40", "50", "63", "80", "100"}; + + switch (pos) { + case posKochFilter: + str = koch.getNewChar(); + cleanUpProSigns(str); + sprintf(numBuffer, "%2i char %s", MorsePreferences::kochFilter, str.c_str()); + str = String(numBuffer); + break; + case posLoraBand: + switch (MorsePreferences::loraBand) { + case 0: str = "433 MHz"; + break; + case 1: str = "868 MHz"; + break; + case 2: str = "920 MHz"; + break; + } + break; + case posLoraQRG: + sprintf(numBuffer, "%6d kHz", MorsePreferences::loraQRG / 1000); + str = String(numBuffer); + switch (MorsePreferences::loraQRG) { + case a: + case b: + case c: str += " DEF"; + } + break; + case posSnapRecall: + if (MorsePreferences::memCounter) { + if (MorsePreferences::memPtr == MorsePreferences::memCounter) + str = "Cancel Recall"; + else { + sprintf(numBuffer, "Snapshot %d", MorsePreferences::memories[MorsePreferences::memPtr] +1); + str = String(numBuffer); + } + } + else + str = "NO SNAPSHOTS"; + break; + case posSnapStore: + mask = 1; mask = mask << MorsePreferences::memPtr; + if (MorsePreferences::memPtr == 8) + str = "Cancel Store"; + else { + sprintf(numBuffer, "Snapshot %d", MorsePreferences::memPtr+1); + str = String(numBuffer); + } + break; + case posVAdjust: + volt = (int16_t) (voltage_raw * (MorsePreferences::vAdjust * 12.9)); // recalculate millivolts for new adjustment + sprintf(numBuffer, "%4d mV", volt); + str = String(numBuffer); + break; + case posHwConf: + switch (hwConf) { + case 1: str = "Calibr. Batt."; + break; + case 2: str = "LoRa Config."; + break; + default: str = "Cancel"; + break; + } + break; + case posLoraPower: + str = milliWatt[MorsePreferences::loraPower - 10] + " mW"; + } + return str; +} + +//// function to adjust the selected preference + +boolean MorsePreferences::adjustKeyerPreference(prefPos pos) { /// rotating the encoder changes the value, click returns to preferences menu + /// returns true when a long button press ended it, and false when there was a short click + MorseOutput::printOnScroll(2, INVERSE_BOLD, 0, ">"); + uint8_t seq; + int8_t t; + + uint16_t val, maxi, mini, vstep, temp; + + while (true) { // we wait for single click = selection or long click = exit + serialEvent(); + if (goToMenu) { + jsonActivate(ACT_EXIT); + goToMenu = false; + return true; + } + pinMode(modeButtonPin, INPUT); + + Buttons::modeButton.Update(); + switch (Buttons::modeButton.clicks) { + case -1 : // long press = return and exit pref menu + return true; + break; + case 1 : // short press = set & return, but stay in pref menu + MorseOutput::printOnScroll(2, REGULAR, 0, " "); + return false; + } + if (pos == posSnapRecall) { // here we can delete a memory.... + Buttons:: volButton.Update(); + if (Buttons:: volButton.clicks) { + if (MorsePreferences::memCounter) { + clearMemory(MorsePreferences::memPtr); + if (m32protocol) + jsonActivate(ACT_CLEARED); + } + return true; + } + } + if ((t=checkEncoder())) { /// t == 1 or -1 + MorseOutput::pwmClick(MorsePreferences::sidetoneVolume); /// click; + + if (pos <= posSerialOut) { /// "normal" procedure through preferences menu + /// val = (((val + maxi - 2*mini + vstep + t*vstep) % (maxi - mini +vstep)) + mini); we calculate the new value, step up or down + val = pliste[pos].value; + mini = pliste[pos].minimum; + maxi = pliste[pos].maximum; + vstep = pliste[pos].stepValue; + if (mini == 0) { + temp = val + maxi + vstep + t*vstep; + pliste[pos].value = temp % (maxi + vstep); + } else { + temp = val + maxi - 2*mini + vstep + t*vstep; + pliste[pos].value = (temp % (maxi - mini +vstep)) + mini; + } + if (pos == posKochSeq) + MorsePreferences::handleKochSequence(); + else if (pos == posCarouselStart && pliste[posKochSeq].value == 3) + MorsePreferences::handleCarouselChange(); + } else { /// "strange" way of adjusting, outside preferences menu + switch (pos) { + case posKochFilter: + MorsePreferences::kochFilter = constrain(MorsePreferences::kochFilter +t, MorsePreferences::kochMinimum, MorsePreferences::kochMaximum); + break; + case posLoraBand: + MorsePreferences::loraBand += (t+1); // set the LoRa band + MorsePreferences::loraBand = constrain(MorsePreferences::loraBand-1, 0, 2); + switch (MorsePreferences::loraBand) { + case 0: MorsePreferences::loraQRG = QRG433; + break; + case 1: MorsePreferences::loraQRG = QRG866; + break; + case 2: MorsePreferences::loraQRG = QRG920; + break; + } + break; + case posLoraQRG: + MorsePreferences::loraQRG += (t*1E5); + switch (MorsePreferences::loraBand) { + case 0: MorsePreferences::loraQRG = constrain(MorsePreferences::loraQRG, 433.65E6, 434.55E6); + break; + case 1: MorsePreferences::loraQRG = constrain(MorsePreferences::loraQRG, 866.25E6, 869.45E6); + break; + case 2: MorsePreferences::loraQRG = constrain(MorsePreferences::loraQRG, 920.25E6, 923.15E6); + break; + } + break; + case posSnapRecall: + if (MorsePreferences::memCounter) + MorsePreferences::memPtr = (MorsePreferences::memPtr +t + MorsePreferences::memCounter + 1) % (MorsePreferences::memCounter+1); + break; + case posSnapStore: + MorsePreferences::memPtr = (MorsePreferences::memPtr + t + 9) % 9; + break; + case posVAdjust: + MorsePreferences::vAdjust += t; + MorsePreferences::vAdjust = constrain(MorsePreferences::vAdjust, 155, 254); + break; + case posHwConf: + hwConf += (t+3); + hwConf = hwConf % 3; + break; + case posLoraPower: + MorsePreferences::loraPower += t; // set the LoRa band + MorsePreferences::loraPower = constrain(MorsePreferences::loraPower, 10, 20); + break; + } + } + displayValueLine(pos); /// now display the value + Heltec.display -> display(); // update the display + } // end if (checkEncoder) + checkShutDown(false); // check for time out + } // end while(true) +} // end of function + + +void MorsePreferences::handleKochSequence() { + MorsePreferences::useCustomChars = false; + switch (MorsePreferences::pliste[posKochSeq].value) { + case 3: // LICW + handleCarouselChange(); + break; + case 4: // Custom Chars + MorsePreferences::useCustomChars = true; + default: + MorsePreferences::kochCharsLength = MorsePreferences::kochMaximum = 51; + MorsePreferences::kochMinimum = 1; + MorsePreferences::kochFilter = constrain(MorsePreferences::kochFilter, MorsePreferences::kochMinimum, MorsePreferences::kochMaximum); + } +} + +void MorsePreferences::handleCarouselChange() { + MorsePreferences::kochCharsLength = MorsePreferences::kochMaximum = koch.setupLICWkochChars(MorsePreferences::pliste[posCarouselStart].value); + MorsePreferences::kochMinimum = kochCharsLength > 18 ? 19 : 1; +//DEBUG("@ 842: kMin: " + String(MorsePreferences::kochMinimum) + " kMax: " + String(MorsePreferences::kochMaximum)); + MorsePreferences::kochFilter = constrain(MorsePreferences::kochFilter, MorsePreferences::kochMinimum, MorsePreferences::kochMaximum); +} + + +/////////////// READING and WRITING parameters from / into Non Volatile Storage, using ESP32 preferences + +void MorsePreferences::readPreferences(String repository) { + unsigned int l = 15; + char repName[l]; + uint8_t temp; + uint32_t tempInt; + + boolean morserino = false; + + if (repository == "morserino") + morserino = true; + + repository.toCharArray(repName, l); + // DEBUG("@851 Reading from repository: " + String(repName)); + // read preferences from non-volatile storage + // if version cannot be read, we have a new ESP32 and need to write the preferences first + + if (morserino) + pref.begin(repName, false); // open namespace in read/write mode + else + pref.begin(repName, true); // read only in all other cases + + // DEBUG("@ l.861 Free entries: " + String(pref.freeEntries())); + /// new code for reading preferences values - we check if we have a value, and if yes, we use it; if no, we use and write a default value + /// some things we get from permanent memory, only when NOT restoring from a snapshot + + if (morserino) { // == NOT from snapshot + + MorsePreferences::wlanSSID = pref.getString("wlanSSID"); + MorsePreferences::wlanPassword = pref.getString("wlanPassword"); + MorsePreferences::wlanTRXPeer = pref.getString("wlanTRXPeer", ""); + + MorsePreferences::wlanSSID1 = pref.getString("wlanSSID1"); + MorsePreferences::wlanPassword1 = pref.getString("wlanPassword1"); + MorsePreferences::wlanTRXPeer1 = pref.getString("wlanTRXPeer1", ""); + MorsePreferences::wlanSSID2 = pref.getString("wlanSSID2"); + MorsePreferences::wlanPassword2 = pref.getString("wlanPassword2"); + MorsePreferences::wlanTRXPeer2 = pref.getString("wlanTRXPeer2", ""); + MorsePreferences::wlanSSID3 = pref.getString("wlanSSID3"); + MorsePreferences::wlanPassword3 = pref.getString("wlanPassword3"); + MorsePreferences::wlanTRXPeer3 = pref.getString("wlanTRXPeer3", ""); + + if ((temp = pref.getUChar("brightness"))) + MorsePreferences::oledBrightness = temp; + + if ((temp = pref.getUChar("version_major")) != MorsePreferences::version_major) + pref.putUChar("version_major", MorsePreferences::version_major); + if ((temp = pref.getUChar("version_minor")) != MorsePreferences::version_minor) + pref.putUChar("version_minor", MorsePreferences::version_minor); + + if ((temp = pref.getUChar("kochFilter"))) + MorsePreferences::kochFilter = temp; + else + pref.putUChar("kochFilter", MorsePreferences::kochFilter); + + if ((temp = pref.getUChar("wpm"))) + MorsePreferences::wpm = temp; + else if (morserino) + pref.putUChar("wpm", MorsePreferences::wpm); + + if ((temp = pref.getUChar("sidetoneVolume",255)) != 255) + MorsePreferences::sidetoneVolume = temp; + else if (morserino) + pref.putUChar("sidetoneVolume", MorsePreferences::sidetoneVolume); + + if (temp = pref.getUChar("vAdjust")) + MorsePreferences::vAdjust = temp; + + if (temp = pref.getUChar("loraBand")) + MorsePreferences::loraBand = temp; + else + MorsePreferences::loraBand = 0; + + if (tempInt = pref.getUInt("loraQRG")) + MorsePreferences::loraQRG = tempInt; + else + MorsePreferences::loraQRG = QRG433; + + if (temp = pref.getUChar("loraPower")) + MorsePreferences::loraPower = temp; + else + MorsePreferences::loraPower = 14; + + MorsePreferences::snapShots = pref.getUChar("snapShots",0); + updateMemory(MorsePreferences::snapShots); + + MorsePreferences::fileWordPointer = pref.getUInt("fileWordPtr",0); // do not read fileWordPointer from other snapshots! we never write anything there! + } // endif morserino + + + MorsePreferences::useCustomChars = pref.getBool("useCustomChar"); + MorsePreferences::customCharSet = pref.getString("customCharSet", ""); + if ((temp = pref.getUChar("lastExecuted"))) { + MorsePreferences::menuPtr = temp; + // DEBUG("@942 read: temp = " + String(temp)); + } + + if ((temp = pref.getUChar("kochCharsLength"))) + MorsePreferences::kochCharsLength = temp; + else if (morserino) + pref.putUChar("kochCharsLength", MorsePreferences::kochCharsLength); + +//// now we read the preferences into memory that are also restored from snapshots (with two exception: posTimeOut and posSerialOut) + + for (uint8_t i = 0; i <= posSerialOut; ++i) { + if (!morserino) + if (i == posTimeOut || i == posSerialOut) + continue; + + if ((temp = pref.getUChar(prefName[i],255)) != 255) { // we have something in the repository +// DEBUG("@942 " + String(prefName[i]) + " : " + String(temp)); + if (i == posTimeOut && temp > 3) + temp = 0; + if (pliste[i].stepValue !=1) + if (uint8_t d = temp % pliste[i].stepValue) // we bring odd values in line with the step increment + temp -= d; + temp = constrain(temp, pliste[i].minimum, pliste[i].maximum); + MorsePreferences::pliste[i].value = temp; + } + else if (morserino) + pref.putUChar(prefName[i], MorsePreferences::pliste[i].value); + } + pref.end(); + handleKochSequence(); + updateTimings(); +} + +void MorsePreferences::writePreferences(String repository) { + unsigned int l = 15; + char repName[l]; + uint8_t temp; + uint32_t tempInt; + + boolean morserino = false; + + if (repository == "morserino") + morserino = true; +//DEBUG("Writing to repository: " + repository); + repository.toCharArray(repName, l); + + pref.begin(repName, false); // open namespace in read/write mode + //DEBUG("@ l.969 Free entries: " + String(pref.freeEntries())); + if (morserino) { // the following things are not stored in snapshots anymore, + //only in the ""Morserino" permanent memory + pref.putUChar("brightness", MorsePreferences::oledBrightness); // if not snapshots, store current screen brightness + + if (MorsePreferences::pliste[posSerialOut].value != pref.getUChar("serialOut")) { + pref.remove("serialOut"); + pref.putUChar("serialOut", MorsePreferences::pliste[posSerialOut].value); + } + + if (MorsePreferences::kochFilter != pref.getUChar("kochFilter")) { + pref.putUChar("kochFilter", MorsePreferences::kochFilter); + if (!MorsePreferences::useCustomChars) // we update these only if we do not use a custom character set! + koch.setup(); + } + + if (MorsePreferences::wlanSSID != pref.getString("wlanSSID")) { + pref.remove("wlanSSID"); + pref.putString("wlanSSID", MorsePreferences::wlanSSID); + } + if (MorsePreferences::wlanPassword != pref.getString("wlanPassword")) + pref.putString("wlanPassword", MorsePreferences::wlanPassword); + if (MorsePreferences::wlanTRXPeer != pref.getString("wlanTRXPeer")) + pref.putString("wlanTRXPeer", MorsePreferences::wlanTRXPeer); + + if (MorsePreferences::wlanSSID1 != pref.getString("wlanSSID1")) + pref.putString("wlanSSID1", MorsePreferences::wlanSSID1); + if (MorsePreferences::wlanPassword1 != pref.getString("wlanPassword1")) + pref.putString("wlanPassword1", MorsePreferences::wlanPassword1); + if (MorsePreferences::wlanTRXPeer1 != pref.getString("wlanTRXPeer1")) + pref.putString("wlanTRXPeer1", MorsePreferences::wlanTRXPeer1); + + if (MorsePreferences::wlanSSID2 != pref.getString("wlanSSID2")) + pref.putString("wlanSSID2", MorsePreferences::wlanSSID2); + if (MorsePreferences::wlanPassword2 != pref.getString("wlanPassword2")) + pref.putString("wlanPassword2", MorsePreferences::wlanPassword2); + if (MorsePreferences::wlanTRXPeer2 != pref.getString("wlanTRXPeer2")) + pref.putString("wlanTRXPeer2", MorsePreferences::wlanTRXPeer2); + + if (MorsePreferences::wlanSSID3 != pref.getString("wlanSSID3")) + pref.putString("wlanSSID3", MorsePreferences::wlanSSID3); + if (MorsePreferences::wlanPassword3 != pref.getString("wlanPassword3")) + pref.putString("wlanPassword3", MorsePreferences::wlanPassword3); + if (MorsePreferences::wlanTRXPeer3 != pref.getString("wlanTRXPeer3")) + pref.putString("wlanTRXPeer3", MorsePreferences::wlanTRXPeer3); + + } else { + pref.remove("wlanSSID"); + pref.remove("wlanPassword"); + pref.remove("wlanTRXPeer"); + pref.remove("wlanSSID1"); + pref.remove("wlanPassword1"); + pref.remove("wlanTRXPeer1"); + pref.remove("wlanSSID2"); + pref.remove("wlanPassword2"); + pref.remove("wlanTRXPeer2"); + pref.remove("wlanSSID3"); + pref.remove("wlanPassword3"); + pref.remove("wlanTRXPeer3"); + + pref.remove("lastExecuted"); // This is ONLY written to snapshots here (a separate function is used to store it in normal permanent memory) + pref.putUChar("lastExecuted", MorsePreferences::menuPtr); // store last executed command in snapshots + //DEBUG("@1051: lastExecuted: " + String(pref.getUChar("lastExecuted"))); + } + + + + // now we write all other preferences into the respective repository + + if (MorsePreferences::useCustomChars != pref.getBool("useCustomChar")) { + pref.remove("useCustomChar"); + pref.putBool("useCustomChar", MorsePreferences::useCustomChars); + } + if (MorsePreferences::customCharSet != pref.getString("customCharSet")) { + pref.remove("customCharSet"); + pref.putString("customCharSet", MorsePreferences::customCharSet); + koch.setup(); + } + if (MorsePreferences::kochCharsLength != pref.getUChar("kochCharsLength")) { + pref.remove("kochCharsLength"); + pref.putUChar("kochCharsLength", MorsePreferences::kochCharsLength); + koch.setup(); + } + + + for (uint8_t i = 0; i <= posSerialOut; ++i) { // for all these preferences + if (i == posTimeOut && !morserino) // ignore timeout when writing to snapshot + continue; + if (MorsePreferences::pliste[i].value != pref.getUChar(prefName[i],255) ) { // stored value is different, +//DEBUG("@1062 " + String(prefName[i]) + " old: " + String(pref.getUChar(prefName[i],255)) + " new: " + String(MorsePreferences::pliste[i].value)); + pref.putUChar(prefName[i], MorsePreferences::pliste[i].value); // so we need to store new value + switch (i) { // in certain cases we need to do something + case posLoraChannel: + if (morserino) + LoRa.setSyncWord(MorsePreferences::pliste[posLoraChannel].value == 0 ? 0x27 : 0x66); + break; + case posGoertzelBandwidth: + if (morserino) + Goertzel::setup(); + break; + case posKochSeq: + if (morserino && !MorsePreferences::useCustomChars) + koch.setup(); + break; + case posAbbrevLength: + case posWordLength: + case posCarouselStart: + if (morserino) + koch.setup(); + break; + } // end of "special cases" + } // end of "stored value is different" + } // end of "for all these preferences" + pref.end(); +// DEBUG("end l. 1087"); +} + + +boolean MorsePreferences::recallSnapshot() { // return true if we selected a real recall, false when it was cancelled + //String snapname; + String text; + + MorsePreferences::memPtr = 0; + displayKeyerPreferencesMenu(posSnapRecall); + if (!adjustKeyerPreference(posSnapRecall)) { + //DEBUG("recall memPtr: " + String(memPtr)); + text = "Snap " + String(MorsePreferences::memories[MorsePreferences::memPtr]+1) + " RECALLD"; + if(MorsePreferences::memCounter) { + if (MorsePreferences::memPtr != MorsePreferences::memCounter) { + doReadSnapshot(MorsePreferences::memPtr); + MorseOutput::printOnScroll(2, BOLD, 0, text); + if (m32protocol) + jsonCreate("message", text, ""); + delay(1000); + return true; + } + return false; + } + } return false; + +} + + +void MorsePreferences::doReadSnapshot(uint8_t storePos) { + String snapname; snapname.reserve(8); + snapname = "snap" + String(MorsePreferences::memories[storePos]); + readPreferences(snapname); +} + + + +boolean MorsePreferences::storeSnapshot(uint8_t menu) { // return true if we selected a real store, false when it was cancelled + String text; text.reserve(20); + + MorsePreferences::memPtr = 0; + displayKeyerPreferencesMenu(posSnapStore); + adjustKeyerPreference(posSnapStore); + Buttons:: volButton.Update(); + //DEBUG("store memPtr: " + String(memPtr)); + if (MorsePreferences::memPtr != 8) { + doWriteSnapshot(memPtr, menu); + text = "Snap " + String(MorsePreferences::memPtr+1) + " STORED "; + if (m32protocol) + jsonCreate("message", text, ""); + MorseOutput::printOnScroll(2, BOLD, 0, text); + delay(1000); + return true; + } + return false; +} + +void MorsePreferences::doWriteSnapshot(uint8_t storePos, uint8_t menuPos) { + uint8_t mask = 1; + String snapname; snapname.reserve(8); + + MorsePreferences::menuPtr = menuPos; // also store last menu selection + snapname = "snap" + String(storePos); + writePreferences(snapname); + /// insert the correct bit into p_snapShots & update memory variables + mask = mask << storePos; + MorsePreferences::snapShots = MorsePreferences::snapShots | mask; + updateMemory(MorsePreferences::snapShots); + pref.begin("morserino", false); // open the namespace as read/write + pref.remove("snapShots"); + pref.putUChar("snapShots", MorsePreferences::snapShots); + pref.end(); +} + + +void MorsePreferences::updateMemory(uint8_t temp) { // temp is a bitmap, 1 byte long, with a bit set for each existing snapshot + uint8_t t = temp; + MorsePreferences::memCounter = 0; // create / update an array indicating the snapshot numbers (memories) that are in use + for (uint8_t i = 0; i<8; ++i) { + if (t & 1) { // mask rightmost bit + MorsePreferences::memories[MorsePreferences::memCounter] = i; + ++MorsePreferences::memCounter; + } + t = t >> 1; // shift one position to the right + } + //DEBUG("update Memory: positions = " + String(MorsePreferences::memCounter)); +} + +void MorsePreferences::clearMemory(uint8_t ptr) { + String text; text.reserve(24); + + text = doClearMemory(ptr); + + if (m32protocol) // output to m32protocol and screen + jsonCreate("message", text, ""); + MorseOutput::printOnScroll(2, BOLD, 0, text); + delay(900); +} + +String MorsePreferences::doClearMemory(uint8_t ptr) { + String snapName; snapName.reserve(12); + String text ; text.reserve(24); + uint8_t snapNumber; + const unsigned int l = 12; + char repName[l]; + + snapNumber = MorsePreferences::memories[ptr]; + snapName = "snap" + String(snapNumber); + text = "Snap " + String(snapNumber+1) + " CLEARED"; + snapName.toCharArray(repName, l); + + pref.begin(repName, false); //// open namespace that will be cleared + pref.clear(); + pref.end(); + + MorsePreferences::snapShots &= ~(1 << MorsePreferences::memories[ptr]); // clear the bit in MorsePreferences::snapShots + updateMemory(MorsePreferences::snapShots); // and update the array of snapshot numbers + + pref.begin("morserino", false); // open the namespace as read/write + pref.remove("snapShots"); + pref.putUChar("snapShots", MorsePreferences::snapShots); // wite the new value of the bitmap snapshots into permanent storage + pref.end(); + + return text; +} + +///////// read & write board version into NVS memory + +void MorsePreferences::determineBoardVersion() { + pref.begin("morserino", false); // open the namespace as read/write + MorsePreferences::boardVersion = pref.getUChar("boardVersion"); + delay(1000); + if (MorsePreferences::boardVersion == 0) { // no board version had been set previously, so we determine and set it here + const int oldbatt = 13; // we measure voltage at pin 13; if V close to zero we have a 2.1 Heltec, so board 4 + + analogSetAttenuation(ADC_0db); + adcAttachPin(oldbatt); + analogSetClockDiv(128); // this value was found by experimenting - no clue what it really does :-( + analogSetPinAttenuation(oldbatt,ADC_11db); + if(analogRead(oldbatt) > 1023) { + MorsePreferences::boardVersion = 3; + // DEBUG("boardV: 3"); + } + else {MorsePreferences::boardVersion = 4; //DEBUG("boardV: 4"); + } + pref.putUChar("boardVersion", MorsePreferences::boardVersion); + } + pref.end(); +} + + +//////// System Setup / LoRa Setup ///// Called when BALCK knob is pressed @ startup + +void MorsePreferences::loraSystemSetup() { + MorsePreferences::displayKeyerPreferencesMenu(posLoraBand); + MorsePreferences::adjustKeyerPreference(posLoraBand); + MorsePreferences::displayKeyerPreferencesMenu(posLoraQRG); + MorsePreferences::adjustKeyerPreference(posLoraQRG); + MorsePreferences::displayKeyerPreferencesMenu(posLoraPower); + MorsePreferences::adjustKeyerPreference(posLoraPower); + /// now store chosen values in Preferences + pref.begin("morserino", false); // open the namespace as read/write + pref.putUChar("loraBand", MorsePreferences::loraBand); + pref.putUInt("loraQRG", MorsePreferences::loraQRG); + pref.putUChar("loraPower", MorsePreferences::loraPower); + pref.end(); +} + +/////// System Setup / Battery Measurement Calibration /////// called from system config (black knob at start-up) +void MorsePreferences::calibrateVoltageMeasurement() { + //voltage_raw = volt / 19.2 * MorsePreferences::vAdjust; + //DEBUG("v_raw: " + String(voltage_raw)); + MorsePreferences::displayKeyerPreferencesMenu(posVAdjust); + MorsePreferences::adjustKeyerPreference(posVAdjust); + pref.begin("morserino", false); // open the namespace as read/write + //DEBUG("Store " + String(MorsePreferences::vAdjust)); + pref.putUChar("vAdjust", MorsePreferences::vAdjust); + pref.end(); +} + + +void MorsePreferences::fireCharSeen(boolean wpmOnly) +{ + pref.begin("morserino", false); // open the namespace as read/write + pref.putUChar("wpm", MorsePreferences::wpm); + if (!wpmOnly) + { + pref.putUChar("tLeft", MorsePreferences::tLeft); + pref.putUChar("tRight", MorsePreferences::tRight); + } + pref.end(); +} + +void MorsePreferences::writeWordPointer() +{ + pref.begin("morserino", false); // open the namespace as read/write + if ((MorsePreferences::fileWordPointer != pref.getUInt("fileWordPtr"))) + { // update word pointer if necessary (if we ran player before) + pref.putUInt("fileWordPtr", MorsePreferences::fileWordPointer); + } + pref.end(); + +} + +void MorsePreferences::writeVolume() +{ + pref.begin("morserino", false); // open the namespace as read/write + if (pref.getUChar("sidetoneVolume") != MorsePreferences::sidetoneVolume) + pref.putUChar("sidetoneVolume", MorsePreferences::sidetoneVolume); // store the last volume, if it has changed + pref.end(); +} + +void MorsePreferences::writeLastExecuted(uint8_t menuPtr) +{ + pref.begin("morserino", false); // open the namespace as read/write + pref.putUChar("lastExecuted", menuPtr); // store last executed command + pref.end(); // close namespace +} + +void MorsePreferences::writeWifiInfoMultiple( + String ssid1, String passwd1, String trxpeer1, + String ssid2, String passwd2, String trxpeer2, + String ssid3, String passwd3, String trxpeer3 + ) +{ + pref.begin("morserino", false); // open the namespace as read/write + + if (ssid1 != "") + MorsePreferences::wlanSSID1 = ssid1; + if (passwd1 != "") + MorsePreferences::wlanPassword1 = passwd1; + //if (trxpeer != "") + MorsePreferences::wlanTRXPeer1 = trxpeer1; + + if (MorsePreferences::wlanSSID1 != pref.getString("wlanSSID1")) + pref.putString("wlanSSID1", MorsePreferences::wlanSSID1); + if (MorsePreferences::wlanPassword1 != pref.getString("wlanPassword1")) + pref.putString("wlanPassword1", MorsePreferences::wlanPassword1); + if (MorsePreferences::wlanTRXPeer1 != pref.getString("wlanTRXPeer1")) + pref.putString("wlanTRXPeer1", MorsePreferences::wlanTRXPeer1); + + if (ssid2 != "") + MorsePreferences::wlanSSID2 = ssid2; + if (passwd2 != "") + MorsePreferences::wlanPassword2 = passwd2; + //if (trxpeer != "") + MorsePreferences::wlanTRXPeer2 = trxpeer2; + + if (MorsePreferences::wlanSSID2 != pref.getString("wlanSSID2")) + pref.putString("wlanSSID2", MorsePreferences::wlanSSID2); + if (MorsePreferences::wlanPassword2 != pref.getString("wlanPassword2")) + pref.putString("wlanPassword2", MorsePreferences::wlanPassword2); + if (MorsePreferences::wlanTRXPeer2 != pref.getString("wlanTRXPeer2")) + pref.putString("wlanTRXPeer2", MorsePreferences::wlanTRXPeer2); + + if (ssid3 != "") + MorsePreferences::wlanSSID3 = ssid3; + if (passwd3 != "") + MorsePreferences::wlanPassword3 = passwd3; + //if (trxpeer != "") + MorsePreferences::wlanTRXPeer3 = trxpeer3; + + if (MorsePreferences::wlanSSID3 != pref.getString("wlanSSID3")) + pref.putString("wlanSSID3", MorsePreferences::wlanSSID3); + if (MorsePreferences::wlanPassword3 != pref.getString("wlanPassword3")) + pref.putString("wlanPassword3", MorsePreferences::wlanPassword3); + if (MorsePreferences::wlanTRXPeer3 != pref.getString("wlanTRXPeer3")) + pref.putString("wlanTRXPeer3", MorsePreferences::wlanTRXPeer3); + + pref.end(); + + writeWifiInfo(ssid1, passwd1, trxpeer1); +} + +void MorsePreferences::writeWifiInfo(String ssid, String passwd, String trxpeer) +{ + if (ssid != "") + MorsePreferences::wlanSSID = ssid; + if (passwd != "") + MorsePreferences::wlanPassword = passwd; + //if (trxpeer != "") + MorsePreferences::wlanTRXPeer = trxpeer; + pref.begin("morserino", false); // open the namespace as read/write + if (MorsePreferences::wlanSSID != pref.getString("wlanSSID")) + pref.putString("wlanSSID", MorsePreferences::wlanSSID); + if (MorsePreferences::wlanPassword != pref.getString("wlanPassword")) + pref.putString("wlanPassword", MorsePreferences::wlanPassword); + if (MorsePreferences::wlanTRXPeer != pref.getString("wlanTRXPeer")) + pref.putString("wlanTRXPeer", MorsePreferences::wlanTRXPeer); + + pref.end(); + +} + +void MorsePreferences::setCurrentOptions(prefPos *current, int size) { + MorsePreferences::currentOptions = current; + currentOptionSize = size; +} + +//////// methods for class Koch /////////////////////////////////////////////////////// + +Koch::Koch() { +} + +void Koch::createWords(uint8_t maxl, uint8_t koch) { // this function creates an array of words that are compliant to Koch filter and max word length + numberOfWords = 0; + //DEBUG("koch: " + String(koch)); + for (int i = EnglishWords::WORDS_POINTER[maxl]; i< EnglishWords::WORDS_NUMBER_OF_ELEMENTS; ++i) { // do this for all words with max length maxl + if (wordIsKoch(EnglishWords::words[i]) <= koch) { + wordIndices[numberOfWords++] = i; + } + } +} + +void Koch::createAbbr(uint8_t maxl, uint8_t koch) { // this function creates an array of abbrevs that are compliant to Koch filter and max word length + numberOfAbbr = 0; + + for (int i = Abbrev::ABBREV_POINTER[maxl]; i< Abbrev::ABBREV_NUMBER_OF_ELEMENTS; ++i) { // do this for all words with max length maxl + if (wordIsKoch(Abbrev::abbreviations[i]) <= koch) + abbrIndices[numberOfAbbr++] = i; + } +} + +uint8_t Koch::wordIsKoch(String thisWord) { /// returns the highest Koch sequence number of a word; if it is not in the charset, return a high number + uint8_t thisKoch = 0; + int index; + uint8_t l = thisWord.length(); + String charSet; + charSet.reserve(53); + + charSet = MorsePreferences::useCustomChars ? MorsePreferences::customCharSet : kochCharSet; + //DEBUG("set: " + charSet + " word: " + thisWord); + for ( int i = 0; i< l; ++i) { + index = charSet.indexOf(thisWord.charAt(i))+1; + if (index == 0) + index = kochCharsLength + 1; + thisKoch = _max(thisKoch, index); + //DEBUG("thisKoch: " + String(thisKoch)); + } + return thisKoch; +} + + +void Koch::setup() { // create the Koch tables according to custom char or Koch filter, and length + if (MorsePreferences::useCustomChars) + setCustomChars(MorsePreferences::customCharSet); + else + setKochChars(MorsePreferences::pliste[posKochSeq].value); + //// populate the array for abbreviations and words according to length and Koch filter + createWords(MorsePreferences::pliste[posWordLength].value, MorsePreferences::useCustomChars ? kochCharsLength+1 : MorsePreferences::kochFilter) ; // + createAbbr(MorsePreferences::pliste[posAbbrevLength].value, MorsePreferences::useCustomChars ? kochCharsLength+1 : MorsePreferences::kochFilter); + + String charSet = getCharSet(); + uint8_t probability = 0; + for (int i = 0; i < kochCharsLength; i++) + { + if (i >= charSet.length()) + probability = 0; + else if (i > charSet.length() - 6) + probability = 7 + i - charSet.length(); + else + probability = 1; + + adaptiveProbabilities[i] = probability; + } + + initSequence = getRandomCharSet(); + initSequenceIndex = 0; +} + +void Koch::setKochChars(uint8_t sequence) { // define the demanded Koch character set: Koch sequenze: 0 = native/JLMC, 1 = LCWO, 2 = CW Academy, 3 = LICW, 4 = Custom + switch (sequence) { + case 1: kochCharSet = lcwoKochChars; + break; + case 2: kochCharSet = cwacKochChars; + break; + case 3: setupLICWkochChars(MorsePreferences::pliste[posCarouselStart].value); + kochCharSet = licwKochChars; + break; + default:kochCharSet = morserinoKochChars; + break; + } +} + + + +uint8_t Koch::setupLICWkochChars(uint8_t start) { // set up the string of characters used for LICW carousel Koch training, return length + // depends on: carouselStart + //uint8_t start = MorsePreferences::pliste[posCarouselStart].value; + uint8_t l; + String temp; + temp.reserve(45); + if (start < 6) // BC 1 + { + temp = licwAllKochChars.substring(3*start,18) + (start > 0 ? licwAllKochChars.substring(0, 3*start) : ""); + l = 18; + } + else // BC 2 + { + temp = licwAllKochChars.substring(0,18) + licwAllKochChars.substring(3*start,44) + + (start > 6 ? licwAllKochChars.substring(18, 3*start) : ""); + l = 44; + } + // DEBUG("Start: " + String(start) + "licwKochChars: " + temp); + licwKochChars = temp; + return l; +} + +void Koch::setCustomChars(String chars) { // define the custom character set + MorsePreferences::customCharSet = chars; +} + +String Koch::getNewChar() { // for Koch Learn New Character + return String(kochCharSet.charAt(MorsePreferences::kochFilter - 1)); +} + +String Koch::getKochChar(uint8_t i) { // get a String consisting of a single character at pos i in kochCharSet (i starting with 0) + return String(kochCharSet.charAt(i)); +} + +String Koch::getRandomChar(int maxl) { // get a random character word for Koch, with max length maxl + String result = ""; + result.reserve(7); + + if (MorsePreferences::useCustomChars) { + for (int i = 0; i < maxl; ++i ) + result += MorsePreferences::customCharSet.charAt(random(MorsePreferences::customCharSet.length())); + } + else { + int endk = MorsePreferences::kochFilter; // kochChars = "mkrsuaptlowi.njef0yv,g5/q9zh38b?427c1d6x-=KA+SNE@:" + for (int i = 0; i < maxl; ++i) { // 1 5 1 5 2 5 3 5 4 5 5 + if (random(3)) // in Koch mode, we generate the last third of the chars learned a bit more often + result += kochCharSet.charAt(random(endk)); + else + result += kochCharSet.charAt(random(2*endk/3, endk)); + } + } + return result; +} + +String Koch::getRandomWord() { // get a random english word for Koch (preselected with max length and Koch filter (or custom character filter) + if (numberOfWords == 0) + return getRandomChar(1); + + uint16_t index = wordIndices[random(numberOfWords)]; + return EnglishWords::words[index]; +} + +String Koch::getRandomAbbrev() { + if (numberOfAbbr == 0) + return getRandomChar(1); + + uint16_t index = abbrIndices[random(numberOfAbbr)]; + return Abbrev::abbreviations[index]; +} + +String Koch::getAdaptiveChar(int maxl) { + String result = getInitChar(maxl); + String charSet = getCharSet(); + result.reserve(7); + int16_t probabilitySum = getProbabilitySum(); + + while (result.length() < maxl) + { + int16_t randomOffset = random(probabilitySum); + + for (int j = 0; j < charSet.length(); j++) + { + randomOffset -= adaptiveProbabilities[j]; + if (randomOffset < 0) + { + result += charSet.charAt(j); + break; + } + } + } + + return result; +} + +int16_t Koch::getProbabilitySum() +{ + int16_t sum = 0; + for (int i = 0; i < kochCharsLength; i++) + { + sum += adaptiveProbabilities[i]; + } + return sum; +} + +String Koch::getInitChar(int maxl) +{ + String result; + + if (initSequenceIndex >= initSequence.length()) + return result; + + result.reserve(maxl); + + while (result.length() < maxl && initSequenceIndex < initSequence.length()) + { + result += initSequence.charAt(initSequenceIndex); + initSequenceIndex++; + } + + return result; +} + +String Koch::getCharSet() +{ + if (MorsePreferences::useCustomChars) + return MorsePreferences::customCharSet; + else + return kochCharSet.substring(0, MorsePreferences::kochFilter); +} + +String Koch::getRandomCharSet() +{ + String charSet = getCharSet(); + String randomCharSet; + randomCharSet.reserve(charSet.length()); + + for (int i = 0; i < charSet.length(); i++) + { + int charIndex = random(charSet.length() - i); + randomCharSet += charSet.charAt(charIndex); + charSet.setCharAt(charIndex, charSet.charAt(charSet.length() - i - 1)); + } + + return randomCharSet; +} + +void Koch::increaseWordProbability(String& expected, String& received) +{ + int failedIndex = getFailedCharIndex(expected, received); + if (failedIndex == -1) + return; + + increaseCharProbability(expected.charAt(failedIndex), 4); + + if (failedIndex > 0 && expected.charAt(failedIndex - 1) != expected.charAt(failedIndex)) + increaseCharProbability(expected.charAt(failedIndex - 1), 2); + if ((failedIndex + 1) < expected.length() && expected.charAt(failedIndex + 1) != expected.charAt(failedIndex)) + increaseCharProbability(expected.charAt(failedIndex + 1), 2); +} + +int Koch::getFailedCharIndex(String& expected, String& received) +{ + for (int i = 0; i < expected.length(); i++) + { + if (i >= received.length()) + return i; + + if (expected.charAt(i) != received.charAt(i)) + return i; + } + + return -1; +} + +void Koch::increaseCharProbability(char c, uint8_t count) +{ + String charSet = getCharSet(); + int increaseIndex = charSet.indexOf(c); + + adaptiveProbabilities[increaseIndex] += count; + + if (adaptiveProbabilities[increaseIndex] > charSet.length()) + adaptiveProbabilities[increaseIndex] = charSet.length(); +} + +void Koch::decreaseWordProbability(String& word) +{ + for (int i = 0; i < word.length(); i++) + { + decreaseCharProbability(word.charAt(i)); + } +} + +void Koch::decreaseCharProbability(char c) +{ + String charSet = getCharSet(); + int decreaseIndex = charSet.indexOf(c); + + if (adaptiveProbabilities[decreaseIndex] > 1) + adaptiveProbabilities[decreaseIndex]--; +} diff --git a/Software/src/Version 5/MorsePreferences.h b/Software/src/Version 5/MorsePreferences.h new file mode 100644 index 0000000..a178d14 --- /dev/null +++ b/Software/src/Version 5/MorsePreferences.h @@ -0,0 +1,243 @@ +#ifndef PREFS_H +#define PREFS_H + +#include +#include // ESP 32 library for storing things in non-volatile storage + + +#include "ClickButton.h" // button control library +#include "morsedefs.h" +#include "MorseMenu.h" +#include "abbrev.h" +#include "english_words.h" +#include "goertzel.h" + + +extern boolean m32protocol; +extern int8_t hwConf; +extern loops m32state; +extern boolean goToMenu; + +extern String cleanUpProSigns( String &input ); +//extern int16_t batteryVoltage(); +//extern int16_t volt; +extern void updateTimings(); +extern String cleanUpProSigns( String &input ); + +extern int IRAM_ATTR checkEncoder(); + +extern void keyOut(boolean, boolean, int, int); +extern void checkShutDown(boolean); +extern void cleanStartSettings(); +extern void serialEvent(); +extern void jsonCreate(String, String,String); +extern void jsonConfigShort(String,int, String); + +extern void jsonError(String); +extern void jsonActivate(actMessage); +extern void jsonMenu(String,unsigned int,bool,bool); +extern String getCustomChars(); + + +namespace MorsePreferences +{ + //String extraItems[8]; + // the preferences variable and their defaults + + extern boolean useCustomChars; + extern String customCharSet; + + extern uint8_t version_major; + extern uint8_t version_minor; + extern uint8_t sidetoneVolume; + extern const uint8_t volumeMin; + extern const uint8_t volumeMax; + + extern uint8_t wpm; + extern const uint8_t wpmMin; + extern const uint8_t wpmMax; + + extern uint8_t kochFilter; + extern uint8_t kochCharsLength; + extern uint8_t kochMinimum; + extern uint8_t kochMaximum; + +/// variables for managing snapshots + extern uint8_t memories[8]; // contains snapshot numbers 0..7 for each stored snapshot + extern uint8_t memCounter; // contains number of stored snapshots + extern uint8_t memPtr; + + ///// stored in preferences, but not adjustable through preferences menu: + extern uint8_t responsePause; + extern uint8_t menuPtr; + extern uint8_t newMenuPtr; + + //current network config + extern String wlanSSID; + extern String wlanPassword; + extern String wlanTRXPeer; + + // config for up to three networks + extern String wlanSSID1; + extern String wlanPassword1; + extern String wlanTRXPeer1; + + extern String wlanSSID2; + extern String wlanPassword2; + extern String wlanTRXPeer2; + + extern String wlanSSID3; + extern String wlanPassword3; + extern String wlanTRXPeer3; + + extern uint32_t fileWordPointer; + extern uint8_t promptPause; + extern uint8_t tLeft; + extern uint8_t tRight; + extern uint8_t vAdjust; + extern uint8_t loraBand; + #define QRG433 434.15E6 + #define QRG866 869.15E6 + #define QRG920 920.55E6 + extern uint32_t loraQRG; + extern uint8_t loraPower; + extern uint8_t snapShots; + extern uint8_t boardVersion; + extern uint8_t oledBrightness; + + ////// end of variables stored in preferences + + //// for adjusting preferences + + + #define MAX_MAP_ELEMENTS 15 + + struct parameter { + uint8_t value; + const uint8_t minimum; + const uint8_t maximum; + const uint8_t stepValue; + const char *parName; + const char *parDescript; + const boolean isMapped; + const char *mapping[MAX_MAP_ELEMENTS]; + }; + + extern parameter pliste[]; + + extern const String prefOption[]; + extern const String prefDescript[]; + extern prefPos keyerOptions[]; + extern prefPos generatorOptions[]; + extern prefPos playerOptions[]; + extern prefPos echoPlayerOptions[]; + extern prefPos echoTrainerOptions[]; + extern prefPos kochGenOptions[]; + extern prefPos kochEchoOptions[]; + extern prefPos loraTrxOptions[]; + extern prefPos wifiTrxOptions[]; + extern prefPos extTrxOptions[]; + extern prefPos decoderOptions[]; + extern prefPos allOptions[]; + + extern prefPos *currentOptions; + + extern int keyerOptionsSize; + extern int generatorOptionsSize; + extern int playerOptionsSize; + extern int echoPlayerOptionsSize; + extern int echoTrainerOptionsSize; + extern int kochGenOptionsSize; + extern int kochEchoOptionsSize; + extern int loraTrxOptionsSize; + extern int wifiTrxOptionsSize; + extern int extTrxOptionsSize; + extern int decoderOptionsSize; + extern int allOptionsSize; + + + + void updateMemory(uint8_t); + void clearMemory(uint8_t); + String doClearMemory(uint8_t); + boolean recallSnapshot(); + boolean storeSnapshot(uint8_t); + boolean setupPreferences(uint8_t); + void displayKeyerPreferencesMenu(prefPos); + void displayValueLine(prefPos); + String getValueLine(prefPos); + int getValue(prefPos); + boolean adjustKeyerPreference(prefPos); + void readPreferences(String repository); + void writePreferences(String repository); + void doWriteSnapshot(uint8_t, uint8_t); + void doReadSnapshot(uint8_t); + void createKochWords(uint8_t maxl, uint8_t koch); + uint8_t wordIsKoch(String thisWord); + void createKochAbbr(uint8_t maxl, uint8_t koch); + void handleKochSequence(); + void handleCarouselChange(); + void setCustomChars(String); + void kochSetup(); + void loraSystemSetup(); + void determineBoardVersion(); + void calibrateVoltageMeasurement(); + void writeWordPointer(); + void writeVolume(); + void writeLastExecuted(uint8_t menuPtr); + void writeWifiInfo(String, String, String); + void writeWifiInfoMultiple(String, String, String, String, String, String, String, String, String); + void fireCharSeen(boolean wpmOnly); + void setCurrentOptions(prefPos *current, int size); +} + + +class Koch { + private: + uint16_t wordIndices[EnglishWords::WORDS_NUMBER_OF_ELEMENTS]; + uint16_t numberOfWords; + uint16_t abbrIndices[Abbrev::ABBREV_NUMBER_OF_ELEMENTS]; + uint16_t numberOfAbbr; + String kochCharSet; + String licwKochChars; + const String lcwoKochChars = "kmuresnaptlwi.jz=foy,vg5/q92h38b?47c1d60x-K+ASNEB@:"; + const String cwacKochChars = "teanois14rhdl25ucmw36?fypg79/bvkj80=xqz.,-K+ASNEB@:"; + //uint8_t kochCharsLength; + void createWords(uint8_t, uint8_t); + void createAbbr(uint8_t, uint8_t); + uint8_t wordIsKoch(String); + + uint8_t adaptiveProbabilities[51]; + String initSequence; + uint8_t initSequenceIndex; + + public: + const String morserinoKochChars = "mkrsuaptlowi.njef0yv,g5/q9zh38b?427c1d6x-=K+SNAEB@:"; + const String licwAllKochChars = "reatinpgslcdhofuwbkmy59,qxv73?+K=16.zj/28B40-ASNE@:"; + + Koch(); + void setup(); + String getNewChar(); + String getKochChar(uint8_t); + String getRandomChar(int); + String getRandomWord(); + String getRandomAbbrev(); + String getAdaptiveChar(int); + String getCharSet(); + String getRandomCharSet(); + String getInitChar(int); + void setKochChars(uint8_t); + uint8_t setupLICWkochChars(uint8_t); + void setCustomChars(String chars); + int16_t getProbabilitySum(); + void increaseWordProbability(String& expected, String& received); + int getFailedCharIndex(String& expected, String& received); + void increaseCharProbability(char c, uint8_t count); + void decreaseWordProbability(String& word); + void decreaseCharProbability(char c); + uint8_t adjustForCarousel(uint8_t offset); +}; + +extern Koch koch; + +#endif diff --git a/Software/src/Version 5/MorseWiFi.cpp b/Software/src/Version 5/MorseWiFi.cpp new file mode 100644 index 0000000..a2f6419 --- /dev/null +++ b/Software/src/Version 5/MorseWiFi.cpp @@ -0,0 +1,633 @@ +/****************************************************************************************************************************** + * morse_3 Software for the Morserino-32 multi-functional Morse code machine, based on the Heltec WiFi LORA (ESP32) module *** + * Copyright (C) 2018-2020 Willi Kraml, OE1WKL *** + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + *****************************************************************************************************************************/ + +#include "morsedefs.h" +#include "MorseWiFi.h" +#include "MorseOutput.h" +#include "MorsePreferences.h" + +////////////////// Variables for file handling and WiFi functions + +// File file; + +WebServer MorseWiFi::server(80); // Create a webserver object that listens for HTTP request on port 80 +//WiFiUDP MorseWiFi::udp; // Create udp socket for wifi tx +AsyncUDP MorseWiFi::audp; // Create async udp socket for wifi rx + +File MorseWiFi::fsUploadFile; // a File object to temporarily store the received file + +const char* MorseWiFi::host = "m32"; // hostname of the webserver + + +/// WiFi constants +const char* MorseWiFi::ssid = "morserino"; +const char* MorseWiFi::password = ""; + + +// HTML for the AP server - used to get SSID and Password for local WiFi network - needed for file upload and OTA SW updates +const char* MorseWiFi::myForm = "Get AP Info" + "" + "" + "

" + "
" // fields for network 1 + "
" + "" + "" + "
" + "
" + "" + "" + "
" + "
" + "" + "" + "
" + "
" + + "
" // fields for network 2 + "
" + "" + "" + "
" + "
" + "" + "" + "
" + "
" + "" + "" + "
" + "
" + + "
" // fields for network 3 + "
" + "" + "" + "
" + "
" + "" + "" + "
" + "
" + "" + "" + "
" + "
" + + "
" + "(255.255.255.255 = Local Broadcast IP will be used as Peer if empty)" + "
" + "
" + "" + "
" + "
" + "" + ""; + + +/* + * HTML for Upload Login page + */ + +const char* MorseWiFi::uploadLoginIndex = + "
" + "" + "" + "" + "
" + "
" + "" + "" + "" + "" + "
" + "
" + "" + "" + "" + "
" + "
" + "" + "" + "" + "" + "
" + "
M32 File Upload - Login Page
" + "
" + "
Username:
Password:
" +"
" +""; + + +const char* MorseWiFi::updateLoginIndex = + "
" + "" + "" + "" + "
" + "
" + "" + "" + "" + "" + "
" + "
" + "" + "" + "" + "
" + "
" + "" + "" + "" + "" + "
" + "
M32 Firmware Update Login Page
" + "
" + "
Username:
Password:
" +"
" +""; + + +const char* MorseWiFi::serverIndex = +"" +"
" + "" + "" + "
" + "
Progress: 0%
" + ""; + +namespace internal +{ + String getContentType(String filename); // convert the file extension to the MIME type + boolean handleFileRead(String path); // send the right file to the client (if it exists) + void handleFileUpload(); // upload a new file to the SPIFFS + void handleNotFound(); + void startMDNS(); + boolean errorConnect(String msg); +} + + + +void internal::handleNotFound() { + String message = "File Not Found\n\n"; + message += "URI: "; + message += MorseWiFi::server.uri(); + message += "\nMethod: "; + message += (MorseWiFi::server.method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += MorseWiFi::server.args(); + message += "\n"; + for (uint8_t i = 0; i < MorseWiFi::server.args(); i++) { + message += " " + MorseWiFi::server.argName(i) + ": " + MorseWiFi::server.arg(i) + "\n"; + } + MorseWiFi::server.send(404, "text/plain", message); +} + +void MorseWiFi::menuNetSelect() { + const int numNetworks = 3; + String names[numNetworks]; + names[0] = "1: " + MorsePreferences::wlanSSID1; + names[1] = "2: " + MorsePreferences::wlanSSID2; + names[2] = "3: " + MorsePreferences::wlanSSID3; + + MorseOutput::clearDisplay(); + MorseOutput::printOnStatusLine( true, 0, "Select Wifi"); + + int btnClicks; + int choice = 0; + int previousChoice = -1; + while (true) { + checkShutDown(false); // possibly time-out: go to sleep + if(choice!=previousChoice) { + MorseOutput::clearThreeLines(); + MorseOutput::printOnScroll(0, REGULAR, 0, names[choice] ); + if (m32protocol) + jsonCreate("message", names[choice], ""); + previousChoice = choice; + } + switch(checkEncoder()){ + case -1: + if(choice == 0) { + choice = numNetworks; + } + choice--; + break; + case 1: + choice++; + if(choice >= numNetworks){ + choice = 0; + } + break; + } + Buttons::modeButton.Update(); + btnClicks = Buttons::modeButton.clicks; + if(btnClicks == 1) + break; + if(btnClicks == -1){ + choice = -1; + break; + } + } + + switch(choice) { + case 0: MorsePreferences::writeWifiInfo(MorsePreferences::wlanSSID1, MorsePreferences::wlanPassword1, MorsePreferences::wlanTRXPeer1); + break; + case 1: MorsePreferences::writeWifiInfo(MorsePreferences::wlanSSID2, MorsePreferences::wlanPassword2, MorsePreferences::wlanTRXPeer2); + break; + case 2: MorsePreferences::writeWifiInfo(MorsePreferences::wlanSSID3, MorsePreferences::wlanPassword3, MorsePreferences::wlanTRXPeer3); + break; + case -1: // long (?) click pressed to exit menu + break; + } + +} + +void MorseWiFi::menuExec(uint8_t command) { + String msg; msg.reserve(120); + switch (command) { + case _wifi_mac: + WiFi.mode(WIFI_MODE_STA); // init WiFi as client + MorseOutput::clearDisplay(); + msg = "MAC Address is " + WiFi.macAddress(); + MorseOutput::printOnStatusLine( true, 0, WiFi.macAddress()); + + delay(1000); + if (m32protocol) { + jsonCreate("message", msg, ""); + WiFi.disconnect(true,false); + return; + } + else { + delay(2000); + MorseOutput::printOnScroll(0, REGULAR, 0, "RED: return" ); + while (true) { // wait + checkShutDown(false); // possibly time-out: go to sleep + if (digitalRead(volButtonPin) == LOW) { + WiFi.disconnect(true,false); + return; + } + } // end wait + } + break; + case _wifi_config: + MorseWiFi::startAP(); // run as AP to get WiFi credentials from user + break; + case _wifi_check: + MorseOutput::clearDisplay(); + msg = "Connecting... "; + MorseOutput::printOnStatusLine( true, 0, msg); + if (m32protocol) + jsonCreate("message", msg + "Please wait", ""); + if (! MorseWiFi::wifiConnect()) + msg = ""; //return false; + else { + msg = "Connected! " + MorsePreferences::wlanSSID + " - " + WiFi.localIP().toString(); + MorseOutput::printOnStatusLine( true, 0, "Connected! "); + MorseOutput::printOnScroll(0, REGULAR, 0, MorsePreferences::wlanSSID); + MorseOutput::printOnScroll(1, REGULAR, 0, WiFi.localIP().toString()); + } + //WiFi.mode( WIFI_MODE_NULL ); // switch off WiFi + delay(1000); + if (m32protocol) { + if (msg != "") + jsonCreate("message", msg, ""); + WiFi.disconnect(true,false); + return; + } + else { + delay(1000); + MorseOutput::printOnScroll(2, REGULAR, 0, "RED: return" ); + while (true) { + checkShutDown(false); // possibly time-out: go to sleep + if (digitalRead(volButtonPin) == LOW) { + WiFi.disconnect(true,false); + return; + } + } + } + break; + case _wifi_upload: + MorseWiFi::uploadFile(); // upload a text file + break; + case _wifi_update: + MorseWiFi::updateFirmware(); // run OTA update + break; + } +} + +void MorseWiFi::startAP() { + + WiFi.mode(WIFI_AP); + WiFi.setHostname(ssid); + WiFi.softAP(ssid); + MorseOutput::clearDisplay(); + MorseOutput::printOnStatusLine( true, 0, "Enter Wifi Info @"); + MorseOutput::printOnScroll(0, REGULAR, 0, "AP: morserino"); + MorseOutput::printOnScroll(1, REGULAR, 0, "URL: m32.local"); + MorseOutput::printOnScroll(2, REGULAR, 0, "RED to abort"); + internal::startMDNS(); + + server.on("/", HTTP_GET, []() { + String formular; + formular.reserve(1024); + formular = myForm; + + formular.replace("SSIDV1", MorsePreferences::wlanSSID1); + formular.replace("PEERIPV1", MorsePreferences::wlanTRXPeer1); + + formular.replace("SSIDV2", MorsePreferences::wlanSSID2); + formular.replace("PEERIPV2", MorsePreferences::wlanTRXPeer2); + + formular.replace("SSIDV3", MorsePreferences::wlanSSID3); + formular.replace("PEERIPV3", MorsePreferences::wlanTRXPeer3); + + server.sendHeader("Connection", "close"); + server.send(200, "text/html", formular); + }); + + server.on("/set", HTTP_GET, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", "Wifi Info updated - now restarting Morserino-32..."); + + MorsePreferences::writeWifiInfoMultiple( + String(server.arg("ssid1")), String(server.arg("pw1")), String(server.arg("trxpeer1")), + String(server.arg("ssid2")), String(server.arg("pw2")), String(server.arg("trxpeer2")), + String(server.arg("ssid3")), String(server.arg("pw3")), String(server.arg("trxpeer3")) + ); + + //DEBUG("SSID: " + String(MorsePreferences::wlanSSID) + " Password: " + String(MorsePreferences::wlanPassword) + " Peer. " + String(MorsePreferences::wlanTRXPeer)); + ESP.restart(); + }); + + server.onNotFound(internal::handleNotFound); + + server.begin(); + while (true) { + server.handleClient(); + delay(20); + Buttons::volButton.Update(); + if (Buttons::volButton.clicks) { + MorseOutput::clearDisplay(); + MorseOutput::printOnStatusLine( true, 0, "Resetting now..."); + delay(2000); + ESP.restart(); + } + } +} + + +void MorseWiFi::updateFirmware() { /// start wifi client, web server and upload new binary from a local computer + if (! wifiConnect()) + return; + + server.on("/", HTTP_GET, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", updateLoginIndex); + }); + + server.on("/serverIndex", HTTP_GET, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", serverIndex); + }); + + /*handling uploading firmware file */ + server.on("/update", HTTP_POST, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); + ESP.restart(); + }, []() { + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size + Update.printError(Serial); + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + /* flashing firmware to ESP*/ + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + Update.printError(Serial); + } + } else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { //true to set the size to the current progress + DEBUG("Update Success: Rebooting...\n"); + } else { + Update.printError(Serial); + } + } + }); + //DEBUG("Starting web server"); + server.begin(); + MorseOutput::clearDisplay(); + MorseOutput::printOnStatusLine( true, 0, "Waiting f. Update "); + MorseOutput::printOnScroll(0, REGULAR, 0, "URL: m32.local"); + MorseOutput::printOnScroll(1, REGULAR, 0, "IP:"); + MorseOutput::printOnScroll(2, REGULAR, 0, WiFi.localIP().toString()); + while(true) { + server.handleClient(); + delay(10); + } +} + + +boolean MorseWiFi::wifiConnect() { // connect to local WLAN + // Connect to WiFi network + if (MorsePreferences::wlanSSID == "") + return internal::errorConnect(String("WiFi Not Conf")); + + WiFi.begin(MorsePreferences::wlanSSID.c_str(), MorsePreferences::wlanPassword.c_str()); + // Wait for connection + long unsigned int wait = millis(); + while (WiFi.status() != WL_CONNECTED) { + delay(250); + if ((millis() - wait) > 11000) + return internal::errorConnect(String("No WiFi:")); + } + //DEBUG("Connected to " + String(p_wlanSSID)); + //DEBUG("IP address: " + String(WiFi.localIP()); + internal::startMDNS(); + return true; +} + + +boolean internal::errorConnect(String msg) { + MorseOutput::clearDisplay(); + MorseOutput::printOnStatusLine( true, 0, "Not connected"); + MorseOutput::printOnScroll(0, INVERSE_BOLD, 0, msg); + MorseOutput::printOnScroll(1, REGULAR, 0, MorsePreferences::wlanSSID); + delay(3500); + if (m32protocol) { + jsonCreate("message", "Not connected " + msg, ""); + } + return false; +} + +void internal::startMDNS() { + /*use mdns for host name resolution*/ + if (!MDNS.begin(MorseWiFi::host)) { //http://m32.local + DEBUG("Error setting up MDNS responder!"); + while (1) { + delay(1000); + if (MDNS.begin(MorseWiFi::host)) + break; + } + } + //DEBUG("mDNS responder started"); +} + + +void MorseWiFi::uploadFile() { + if (! wifiConnect()) + return; + server.on("/", HTTP_GET, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", uploadLoginIndex); + }); + server.on("/serverIndex", HTTP_GET, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", serverIndex); + }); + +server.on("/update", HTTP_POST, [](){ // if the client posts to the upload page + server.sendHeader("Connection", "close"); + server.send(200, "text/plain", "OK"); + ESP.restart();}, // Send status 200 (OK) to tell the client we are ready to receive; when done, restart the ESP32 + internal::handleFileUpload // Receive and save the file + ); + + server.onNotFound([]() { // If the client requests any URI + if (!internal::handleFileRead(server.uri())) // send it if it exists + server.send(404, "text/plain", "404: Not Found"); // otherwise, respond with a 404 (Not Found) error + }); + + server.begin(); // Actually start the server + //DEBUG("HTTP server started"); + MorseOutput::clearDisplay(); + MorseOutput::printOnStatusLine( true, 0, "Waiting f. Upload "); + MorseOutput::printOnScroll(0, REGULAR, 0, "URL: m32.local"); + MorseOutput::printOnScroll(1, REGULAR, 0, "IP:"); + MorseOutput::printOnScroll(2, REGULAR, 0, WiFi.localIP().toString()); + while(true) { + server.handleClient(); + //delay(5); + } +} + + +String internal::getContentType(String filename) { // convert the file extension to the MIME type + if (filename.endsWith(".html")) return "text/html"; + else if (filename.endsWith(".css")) return "text/css"; + else if (filename.endsWith(".js")) return "application/javascript"; + else if (filename.endsWith(".ico")) return "image/x-icon"; + else if (filename.endsWith(".gz")) return "application/x-gzip"; + return "text/plain"; +} + + +bool internal::handleFileRead(String path) { // send the right file to the client (if it exists) + //DEBUG("handleFileRead: " + path); + if (path.endsWith("/")) path += "index.html"; // If a folder is requested, send the index file + String contentType = internal::getContentType(path); // Get the MIME type + String pathWithGz = path + ".gz"; + if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { // If the file exists, either as a compressed archive, or normal + if (SPIFFS.exists(pathWithGz)) // If there's a compressed version available + path += ".gz"; // Use the compressed verion + File file = SPIFFS.open(path, "r"); // Open the file + MorseWiFi::server.streamFile(file, contentType); // Send it to the client + file.close(); // Close the file again + //DEBUG(String("\tSent file: ") + path); + return true; + } + //DEBUG(String("\tFile Not Found: ") + path); // If the file doesn't exist, return false + return false; +} + +void internal::handleFileUpload(){ // upload a new file to the SPIFFS + HTTPUpload& upload = MorseWiFi::server.upload(); + if(upload.status == UPLOAD_FILE_START){ + String filename = upload.filename; + if(!filename.startsWith("/")) filename = "/"+filename; + //DEBUG("handleFileUpload Name: " + filename); + MorseWiFi::fsUploadFile = SPIFFS.open("/player.txt", "w"); // Open the file for writing in SPIFFS (create if it doesn't exist) + filename = String(); + } else if(upload.status == UPLOAD_FILE_WRITE){ + if(MorseWiFi::fsUploadFile) + MorseWiFi::fsUploadFile.write(upload.buf, upload.currentSize); // Write the received bytes to the file + } else if(upload.status == UPLOAD_FILE_END){ + if(MorseWiFi::fsUploadFile) { // If the file was successfully created + MorseWiFi::fsUploadFile.close(); // Close the file again + MorsePreferences::fileWordPointer = 0; // reset word counter for file player +//DEBUG("fileWordPointer @ file upload: " + String(MorsePreferences::fileWordPointer)); + MorsePreferences::writeWordPointer(); + //DEBUG("handleFileUpload Size: " + String(upload.totalSize)); + //server.sendHeader("Location","/success.html"); // Redirect the client to the success page + //server.send(303); + } else { + MorseWiFi::server.send(500, "text/plain", "500: couldn't create file"); + } + } +} diff --git a/Software/src/Version 5/MorseWiFi.h b/Software/src/Version 5/MorseWiFi.h new file mode 100644 index 0000000..1958f6f --- /dev/null +++ b/Software/src/Version 5/MorseWiFi.h @@ -0,0 +1,48 @@ +#ifndef MORSEWIFI_H_ +#define MORSEWIFI_H_ + + +#include "morsedefs.h" + +#define MORSERINOPORT 7373 +extern void checkShutDown(boolean enforce); + +namespace MorseWiFi +{ + ////////////////// Variables for file handling and WiFi functions + + extern WebServer server; + //extern WiFiUDP udp; + extern AsyncUDP audp; + + extern File fsUploadFile; // a File object to temporarily store the received file + + extern const char* host; // hostname of the webserver + + /// WiFi constants + extern const char* ssid; + extern const char* password; + + // HTML for the AP server - ued to get SSID and Password for local WiFi network - needed for file upload and OTA SW updates + extern const char* myForm; + + /* + * HTML for Upload Login page + */ + + extern const char* uploadLoginIndex; + extern const char* updateLoginIndex; + extern const char* serverIndex; + + void menuExec(uint8_t command); // called from main menu, with argument from menu + void menuNetSelect(); // display menu of wifi networks to select from + void startAP(); + boolean wifiConnect(); + void uploadFile(); + void updateFirmware(); + String getContentType(String filename); // convert the file extension to the MIME type + bool handleFileRead(String path); // send the right file to the client (if it exists) + void handleFileUpload(); // upload a new file to the SPIFFS +} + +#endif /* MORSEWIFI_H_ */ diff --git a/Software/src/Version 5/abbrev.h b/Software/src/Version 5/abbrev.h new file mode 100644 index 0000000..15146d6 --- /dev/null +++ b/Software/src/Version 5/abbrev.h @@ -0,0 +1,260 @@ +/// ABBREVIATIONS in various lengths for CW Trainer +/////////////////////////////////////////////////// + +#ifndef ABBREV_H +#define ABBREV_H + +namespace Abbrev +{ + + const int ABBREV_NUMBER_OF_ELEMENTS = 243; // how many items all together? + const int ABBREV_MAX_SIZE = 9; // longest item +1 + const int ABBREV_POINTER[] = {0, 234, 149, 41, 11, 2, 1, 0};// array where items start with length = index + + const String abbreviations[ABBREV_NUMBER_OF_ELEMENTS] = { + { "congrats" }, /// l = 8 + { "output" }, /// l = 6 + { "award" }, /// l = 5 + { "conds" }, + { "condx" }, + { "cuagn" }, + { "elbug" }, + { "excus" }, + { "oscar" }, + { "unlis" }, + { "watts" }, + { "awdh" }, /// l = 4 pos = 11 + { "awds" }, + { "bcnu" }, + { "buro" }, + { "call" }, + { "diff" }, + { "fone" }, + { "freq" }, + { "iaru" }, + { "info" }, + { "inpt" }, + { "mesz" }, + { "mins" }, + { "psed" }, + { "rcvd" }, + { "rprt" }, + { "rtty" }, + { "sase" }, + { "sigs" }, + { "sked" }, + { "sstv" }, + { "sure" }, + { "temp" }, + { "test" }, + { "vert" }, + { "wtts" }, + { "xcus" }, + { "xcvr" }, + { "xmas" }, + { "xtal" }, + { "abt" }, /// l = 3 pos = 41 + { "adr" }, + { "agc" }, + { "agn" }, + { "alc" }, + { "ans" }, + { "ant" }, + { "atv" }, + { "avc" }, + { "bci" }, + { "bfo" }, + { "bpm" }, + { "btr" }, + { "btw" }, + { "bug" }, + { "cfm" }, + { "cul" }, + { "dwn" }, + { "fer" }, + { "frd" }, + { "fwd" }, + { "gnd" }, + { "gud" }, + { "ham" }, + { "hpe" }, + { "hrd" }, + { "hrs" }, + { "hvy" }, + { "irc" }, + { "itu" }, + { "khz" }, + { "lbr" }, + { "lid" }, + { "lis" }, + { "lng" }, + { "loc" }, + { "log" }, + { "lsb" }, + { "luf" }, + { "mez" }, + { "mgr" }, + { "mhz" }, + { "min" }, + { "mni" }, + { "mod" }, + { "msg" }, + { "mtr" }, + { "muf" }, + { "net" }, + { "nil" }, + { "osc" }, + { "pep" }, + { "pse" }, + { "pwr" }, + { "qra" }, + { "qrb" }, + { "qrg" }, + { "qrl" }, + { "qrm" }, + { "qrn" }, + { "qro" }, + { "qrp" }, + { "qrq" }, + { "qrs" }, + { "qrt" }, + { "qru" }, + { "qrv" }, + { "qrx" }, + { "qrz" }, + { "qsb" }, + { "qsk" }, + { "qsl" }, + { "qso" }, + { "qsp" }, + { "qst" }, + { "qsy" }, + { "qtc" }, + { "qth" }, + { "qtr" }, + { "ref" }, + { "rfi" }, + { "rig" }, + { "rpt" }, + { "rst" }, + { "shf" }, + { "sri" }, + { "ssb" }, + { "stn" }, + { "swl" }, + { "swr" }, + { "tia" }, + { "tks" }, + { "tnx" }, + { "trx" }, + { "tvi" }, + { "ufb" }, + { "uhf" }, + { "ukw" }, + { "usb" }, + { "utc" }, + { "vfo" }, + { "vhf" }, + { "vln" }, + { "wid" }, + { "wkd" }, + { "wkg" }, + { "wpm" }, + { "xyl" }, + { "33" }, //// l=2 pos = 149 + { "44" }, + { "55" }, + { "72" }, + { "73" }, + { "88" }, + { "99" }, + { "aa" }, + { "ab" }, + { "ac" }, + { "af" }, + { "am" }, + { "am" }, + { "bc" }, + { "bd" }, + { "bk" }, + { "b4" }, + { "cl" }, + { "cq" }, + { "cu" }, + { "cw" }, + { "db" }, + { "dc" }, + { "de" }, + { "dr" }, + { "dx" }, + { "ee" }, + { "el" }, + { "es" }, + { "fb" }, + { "fm" }, + { "fr" }, + { "ga" }, + { "gb" }, + { "gd" }, + { "gd" }, + { "ge" }, + { "gl" }, + { "gm" }, + { "gn" }, + { "gp" }, + { "gs" }, + { "gt" }, + { "hf" }, + { "hi" }, + { "hr" }, + { "hv" }, + { "hw" }, + { "if" }, + { "ii" }, + { "km" }, + { "kw" }, + { "ky" }, + { "lf" }, + { "lp" }, + { "lw" }, + { "ma" }, + { "mm" }, + { "my" }, + { "nf" }, + { "no" }, + { "nr" }, + { "nr" }, + { "nw" }, + { "ok" }, + { "om" }, + { "op" }, + { "ow" }, + { "pa" }, + { "pm" }, + { "px" }, + { "re" }, + { "rf" }, + { "rx" }, + { "sn" }, + { "sp" }, + { "tu" }, + { "tx" }, + { "up" }, + { "ur" }, + { "vl" }, + { "vy" }, + { "wl" }, + { "wx" }, + { "yl" }, + { "i" }, /// l = 1; pos = 234 + { "k" }, + { "n" }, + { "r" }, + { "t" }, + { "u" }, + { "v" }, + { "w" }, + { "z" } + }; +} +#endif diff --git a/Software/src/Version 5/english_words.h b/Software/src/Version 5/english_words.h new file mode 100644 index 0000000..45a492b --- /dev/null +++ b/Software/src/Version 5/english_words.h @@ -0,0 +1,389 @@ +#ifndef ENGLISH_WORDS_H +#define ENGLISH_WORDS_H + +/// The most common English Words in various lengths for CW Trainer +/////////////////////////////////////////////////////////////////// + +namespace EnglishWords + { + const int WORDS_NUMBER_OF_ELEMENTS = 373; // how many items all together? + const int WORDS_MAX_SIZE = 14; // longest item +1 + const int WORDS_POINTER[] = {0, 370, 345, 275, 144, 66, 34, 14}; // array where items start with length = index + + const String words[WORDS_NUMBER_OF_ELEMENTS] = { + { "international" }, + { "university" }, + { "government" }, + { "including" }, + { "following" }, + { "national" }, + { "american" }, + { "released" }, + { "although" }, + { "district" }, + { "sentence" }, /// new + { "together" }, /// new + { "children" }, /// new + { "mountain" }, /// new + { "between" }, /// l = 7, pos = 14 + { "however" }, + { "through" }, + { "several" }, + { "history" }, + { "against" }, + { "because" }, + { "located" }, + { "company" }, + { "general" }, + { "another" }, + { "century" }, + { "station" }, + { "british" }, + { "college" }, + { "members" }, + { "picture" }, /// new + { "country" }, /// new + { "thought" }, /// new + { "example" }, /// new + { "during" }, /// l = 6, pos = 34 + { "school" }, + { "united" }, + { "states" }, + { "became" }, + { "before" }, + { "people" }, + { "second" }, + { "called" }, + { "series" }, + { "number" }, + { "family" }, + { "county" }, + { "system" }, + { "season" }, + { "played" }, + { "around" }, + { "public" }, + { "former" }, + { "career" }, + { "little" }, /// new + { "differ" }, /// new + { "follow" }, /// new + { "change" }, /// new + { "animal" }, /// new + { "mother" }, /// new + { "father" }, /// new + { "should" }, /// new + { "answer" }, /// new + { "always" }, /// new + { "letter" }, /// new + { "friend" }, /// new + { "which" }, /// l = 5, pos = 66 + { "first" }, + { "their" }, + { "after" }, + { "other" }, + { "there" }, + { "years" }, + { "would" }, + { "where" }, + { "later" }, + { "these" }, + { "about" }, + { "under" }, + { "world" }, + { "known" }, + { "while" }, + { "state" }, + { "three" }, + { "being" }, + { "early" }, + { "since" }, + { "until" }, + { "south" }, + { "north" }, + { "music" }, + { "album" }, + { "group" }, + { "often" }, + { "those" }, + { "house" }, + { "began" }, + { "could" }, + { "found" }, + { "major" }, + { "river" }, + { "named" }, + { "still" }, + { "place" }, + { "local" }, + { "party" }, + { "large" }, + { "small" }, + { "along" }, + { "based" }, + { "write" }, /// new + { "thing" }, /// new + { "sound" }, /// new + { "water" }, /// new + { "round" }, /// new + { "every" }, /// new + { "great" }, /// new + { "think" }, /// new + { "cause" }, /// new + { "right" }, /// new + { "spell" }, /// new + { "light" }, /// new + { "again" }, /// new + { "point" }, /// new + { "build" }, /// new + { "earth" }, /// new + { "stand" }, /// new + { "study" }, /// new + { "learn" }, /// new + { "plant" }, /// new + { "cover" }, /// new + { "never" }, /// new + { "cross" }, /// new + { "start" }, /// new + { "might" }, /// new + { "story" }, /// new + { "press" }, /// new + { "close" }, /// new + { "night" }, /// new + { "white" }, /// new + { "begin" }, /// new + { "paper" }, /// new + { "carry" }, /// new + { "watch" }, /// new + { "with" }, /// l = 4, pos = 144 + { "that" }, + { "from" }, + { "were" }, + { "this" }, + { "also" }, + { "have" }, + { "they" }, + { "been" }, + { "when" }, + { "into" }, + { "more" }, + { "time" }, + { "most" }, + { "some" }, + { "only" }, + { "over" }, + { "many" }, + { "such" }, + { "used" }, + { "city" }, + { "then" }, + { "than" }, + { "made" }, + { "part" }, + { "year" }, + { "both" }, + { "them" }, + { "name" }, + { "area" }, + { "well" }, + { "will" }, + { "high" }, + { "born" }, + { "work" }, + { "town" }, + { "film" }, + { "team" }, + { "each" }, + { "life" }, + { "same" }, + { "game" }, + { "four" }, + { "west" }, + { "line" }, + { "like" }, + { "very" }, + { "john" }, + { "home" }, + { "back" }, + { "band" }, + { "show" }, + { "york" }, + { "even" }, + { "much" }, + { "east" }, + { "what" }, /// new + { "your" }, /// new + { "word" }, /// new + { "said" }, /// new + { "long" }, /// new + { "make" }, /// new + { "look" }, /// new + { "come" }, /// new + { "know" }, /// new + { "call" }, /// new + { "down" }, /// new + { "side" }, /// new + { "find" }, /// new + { "take" }, /// new + { "live" }, /// new + { "came" }, /// new + { "good" }, /// new + { "give" }, /// new + { "just" }, /// new + { "form" }, /// new + { "help" }, /// new + { "turn" }, /// new + { "mean" }, /// new + { "move" }, /// new + { "does" }, /// new + { "tell" }, /// new + { "want" }, /// new + { "play" }, /// new + { "read" }, /// new + { "hand" }, /// new + { "port" }, /// new + { "land" }, /// new + { "here" }, /// new + { "must" }, /// new + { "went" }, /// new + { "kind" }, /// new + { "need" }, /// new + { "near" }, /// new + { "self" }, /// new + { "head" }, /// new + { "page" }, /// new + { "grow" }, /// new + { "food" }, /// new + { "keep" }, /// new + { "last" }, /// new + { "door" }, /// new + { "tree" }, /// new + { "hard" }, /// new + { "draw" }, /// new + { "left" }, /// new + { "late" }, /// new + { "real" }, /// new + { "stop" }, /// new + { "open" }, /// new + { "seem" }, /// new + { "next" }, /// new + { "walk" }, /// new + { "ease" }, /// new + { "mark" }, /// new + { "book" }, /// new + { "mile" }, /// new + { "feet" }, /// new + { "care" }, /// new + { "took" }, /// new + { "rain" }, /// new + { "room" }, /// new + { "idea" }, /// new + { "fish" }, /// new + { "once" }, /// new + { "base" }, /// new + { "hear" }, /// new + { "sure" }, /// new + { "face" }, /// new + { "wood" }, /// new + { "main" }, /// new + { "the" }, /// l = 3, pos = 275 + { "and" }, + { "was" }, + { "for" }, + { "his" }, + { "are" }, + { "has" }, + { "had" }, + { "one" }, + { "not" }, + { "but" }, + { "its" }, + { "new" }, + { "who" }, + { "her" }, + { "two" }, + { "she" }, + { "all" }, + { "can" }, + { "may" }, + { "out" }, + { "him" }, + { "war" }, + { "age" }, + { "now" }, + { "use" }, + { "any" }, + { "end" }, + { "day" }, + { "did" }, + { "own" }, + { "due" }, + { "won" }, + { "sum" }, // + { "usa" }, // + { "you" }, /// new + { "hot" }, /// new + { "how" }, /// new + { "way" }, /// new + { "see" }, // new + { "man" }, // new + { "our" }, // new + { "say" }, // new + { "low" }, // new + { "boy" }, // new + { "old" }, // new + { "too" }, // new + { "set" }, // new + { "air" }, // new + { "put" }, // new + { "add" }, // new + { "big" }, // new + { "act" }, // new + { "why" }, // new + { "ask" }, // new + { "men" }, // new + { "off" }, // new + { "try" }, // new + { "sun" }, // new + { "let" }, // new + { "eye" }, // new + { "saw" }, // new + { "far" }, // new + { "sea" }, // new + { "run" }, // new + { "few" }, // new + { "got" }, // new + { "car" }, // new + { "eat" }, // new + { "cut" }, // new + { "of" }, /// l = 2, pos = 345 + { "km" }, // + { "mr" }, // + { "us" }, // + { "in" }, + { "to" }, + { "is" }, + { "as" }, + { "on" }, + { "by" }, + { "he" }, + { "at" }, + { "it" }, + { "an" }, + { "or" }, + { "be" }, + { "up" }, + { "no" }, + { "so" }, + { "if" }, + { "we" }, // new + { "do" }, // new + { "go" }, // new + { "my" }, // new + { "me" }, // new + { "a" }, // 370 + { "i" }, /// + { "m" } // pos = 372 + }; + } +#endif diff --git a/Software/src/Version 5/goertzel.cpp b/Software/src/Version 5/goertzel.cpp new file mode 100644 index 0000000..69e1e36 --- /dev/null +++ b/Software/src/Version 5/goertzel.cpp @@ -0,0 +1,106 @@ +/****************************************************************************************************************************** + * morse_3 Software for the Morserino-32 multi-functional Morse code machine, based on the Heltec WiFi LORA (ESP32) module *** + * Copyright (C) 2018-2020 Willi Kraml, OE1WKL *** + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + *****************************************************************************************************************************/ + + //// Checking audio signal with Goertzel filter - based on code by Hjalmar Skovholm Hansen OZ1JHM + //// - see http://skovholm.com/cwdecoder + +/////////////////////////////////////////////////////////// +// The sampling frq will be 106.000 on ESp32 // +// because we need the tone in the center of the bins // +// I set the tone to 698 Hz // +// then n the number of samples which give the bandwidth // +// can be (106000 / tone) * 1 or 2 or 3 or 4 etc // +// init is 106000/698 = 152 *4 = 608 samples // +// 152 will give you a bandwidth around 700 hz // +// 304 will give you a bandwidth around 350 hz // +// 608 will give you a bandwidth around 175 hz // +/////////////////////////////////////////////////////////// + +#include "goertzel.h" + +using namespace Goertzel; + + float coeff; + float sine; + float cosine; + const float sampling_freq = 106000.0; + const float target_freq = 698.0; /// adjust for your needs see above + int goertzel_n = 152; //// you can use: 152, 304, 456 or 608 - thats the max buffer reserved in checktone() + ///// resulting bandwidth: 700, 350, 233 or 175 Hz, respectively + float bw; + float Q1 = 0; + float Q2 = 0; + uint32_t magnitudelimit; // magnitudelimit_low = ( p_goertzelBandwidth? 80000 : 30000); + uint32_t magnitudelimit_low; // magnitudelimit = magnitudelimit_low; + +//const float target_freq = 698.0; /// adjust for your needs see above +//const int goertzel_n = 304; //// you can use: 152, 304, 456 or 608 - thats the max buffer reserved in checktone()// + + +void Goertzel::setup() { /// pre-compute some values (sine, cosine, coeff, magnitudelimit) that are compute-imntensive and won't change anyway + goertzel_n = ( MorsePreferences::pliste[posGoertzelBandwidth].value == 0 ? 152 : 608); // update Goertzel parameters depending on chosen bandwidth + magnitudelimit_low = ( MorsePreferences::pliste[posGoertzelBandwidth].value? 160000 : 40000); // values found by experimenting + magnitudelimit = magnitudelimit_low; + bw = (sampling_freq / goertzel_n ); //348 + int k; + float omega; + k = (int) (0.5 + ((goertzel_n * target_freq) / sampling_freq)); // 2 + omega = (2.0 * PI * k) / goertzel_n ; //0,041314579 + sine = sin(omega); // 0,007210753 + cosine = cos(omega); // 0,999999739 + coeff = 2.0 * cosine; // 1,999999479 + } + +boolean Goertzel::checkInput() { // check the audio input for a signal + float magnitude ; + uint16_t testData[1216]; /// buffer for freq analysis - max. 608 samples; you could increase this (and n) to a max of 1216, for sample time 10 ms, and bw 88 Hz + + + for (int index = 0; index < goertzel_n ; index++) + testData[index] = analogRead(audioInPin); + //DEBUG("Read and stored analog values!"); + for (int index = 0; index < goertzel_n ; index++) { + float Q0; + Q0 = coeff * Q1 - Q2 + (float) testData[index]; + Q2 = Q1; + Q1 = Q0; + } + //DEBUG("Calculated Q1 and Q2!"); + + float magnitudeSquared = (Q1 * Q1) + (Q2 * Q2) - (Q1 * Q2 * coeff); // we do only need the real part // + magnitude = sqrt(magnitudeSquared); + Q2 = 0; + Q1 = 0; + //DEBUG("Magnitude: " + String(magnitude) + " Limit: " + String(magnitudelimit)); //// here you can measure magnitude for setup.. + /////////////////////////////////////////////////////////// + // here we will try to set the magnitude limit automatic // + /////////////////////////////////////////////////////////// + + if (magnitude > magnitudelimit_low) { + magnitudelimit = (magnitudelimit + ((magnitude - magnitudelimit) / 6)); /// moving average filter + } + + if (magnitudelimit < magnitudelimit_low) + magnitudelimit = magnitudelimit_low; + + //////////////////////////////////// + // now we check for the magnitude // + // and return true or false // + //////////////////////////////////// + + if (magnitude > magnitudelimit * 0.6) // just to have some space up + return true; + else + return false; +} diff --git a/Software/src/Version 5/goertzel.h b/Software/src/Version 5/goertzel.h new file mode 100644 index 0000000..0f6687a --- /dev/null +++ b/Software/src/Version 5/goertzel.h @@ -0,0 +1,14 @@ +#ifndef GOERTZEL_H_ +#define GOERTZEL_H_ + +#include +#include "MorsePreferences.h" + +namespace Goertzel +{ + void setup(); + boolean checkInput(); +} + + +#endif /* GOERTZEL_H_ */ diff --git a/Software/src/Version 5/morsedefs.h b/Software/src/Version 5/morsedefs.h new file mode 100644 index 0000000..b9a9682 --- /dev/null +++ b/Software/src/Version 5/morsedefs.h @@ -0,0 +1,243 @@ +/****************************************************************************************************************************** + * Software for the Morserino-32 (M32) multi-functional Morse code machine, based on the Heltec WiFi LORA (ESP32) module *** + * Copyright (C) 2018-2021 Willi Kraml, OE1WKL *** + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + *****************************************************************************************************************************/ + +#ifndef MORSEDEFS_H +#define MORSEDEFS_H + +#include "Arduino.h" +#include "heltec.h" +#include +#include "ClickButton.h" // button control library +#include "WiFi.h" +#include // UDP lib for WiFi TRX +#include // UDP lib for WiFi TRX +#include // ESP 32 library for storing things in non-volatile storage +#include // simple web sever +#include // DNS functionality +#include // update "over the air" (OTA) functionality +#include "FS.h" +#include "SPIFFS.h" + +/////// Program Name & Version + +const String PROJECTNAME = "Morserino-32"; + +#define VERSION_MAJOR 5 +#define VERSION_MINOR 0 +#define VERSION_PATCH 0 + +#define BETA false + +// using the M32 serial protocol + +//define M32Protocol version +#define M32P_VERSION "1.0" + +/////// protocol version for Lora - for the time being this is B01 +/////// the first version of the CW over LoRA protocol; future versions will be B02, B03, B00 (reserved for future use) +#define CW_TRX_PROTO_VERSION B01 + + +#define IGNORE_SERIALOUT false +// if IGNORE_SERIALOUT is true, alle DEBUG messages are on serial out, even when Serial Out is active outputting characters from Keyer, Decoder etc + + +namespace Buttons +{ + + // define the buttons for the clickbutton library + extern ClickButton modeButton; + extern ClickButton volButton; + + //void setup(); + //void click(); + //void audioLevelAdjust(); + +} + +///// its is crucial to have the right board version - Boards 2 and 2a (prototypes only) set it to 2, Boards 3 set it to 3 +///// the Board Version 2 is for HEltec Modules V1 only, Board Version 3 for Heltec V2 only +///// Board version 1 not supported anymore! + + +///#define BOARDVERSION 4 + + +/////////////////////////////// H A R D W A R E /////////////////////////////////////////// +//// Here are the definitions for the various hardware-related I/O pins of the ESP32 +///////////////////////////////////////////////////////////////////////////////////////////////////// +///// Board versions: +///// 2(a): for Heltec ESP32 LORA Version 1 (Morserino-32 prototypes) +///// 3: for heltec ESP32 LORA Version 2 +///// Warning: Board version 1 not supported anymore!!!! (was an early prototype) +///// +///// the following Pins are dependent on the board version +///// 21: V2: an encoder port, using interrupts V3: Vext - used internally to switch Vext on and off +///// 39: V2: ADC input to measure battery voltage V3: encoder port (interrupt driven) instead of 21 +///// 13: V2: leftPin (external paddle, ext. pullup) V3: used internally to read battery voltage +///// 38: V2: not in use V3: encoder port (interrupt driven) instead of 35 +///// 32: V2: used for LoRa internally V3: leftPin +///// 33: V2: used for LoRa internally V3: rightPin +///// 34: rightPin V3: cannot be used any longer, used for LoRa internally +///// 35: PinDT V3: cannot be used any longer, used for LoRa internally + + +/////// here are the board dependent pins definitions + +//// BOARD 3 & 4 differences +//// batteryPin 13 in 3, 37 in 4 +//// these are NOT compile options in this version but will be determined at startup (in setup()), to achieve backwards compatibility + + +/// where is the encoder? +const int PinCLK=38; // Used for generating interrupts using CLK signal - needs external pullup resisitor! +const int PinDT=39; // Used for reading DT signal - needs external pullup resisitor! + +/// encoder switch (BLACK knob) +const int modeButtonPin = 37; + +/// 2nd switch button - toggles between Speed control and Volume control (RED button) +const int volButtonPin = 0; + + +//// with the following we define which pins are used as output for the two pwm channels +//// HF output (with varying duticycle and fixed frequency) and LF output (with varying frequency and fixed dutycycle of 50%) +/// are being added with a 2-transistor AND gate to create a tone frequency with variable frequency and volume + +const int LF_Pin = 23; // for the lower (= NF) frequency generation +const int HF_Pin = 22; // for the HF PWM generation + + +/// where are the touch paddles? +const int LEFT = T2; // = Pin 2 +const int RIGHT = T5; // = Pin 12 + +// Tx keyer +const int keyerPin = 25; // this keys the transmitter / through a MOSFET Optocoupler - at the same time lights up the LED + + +// audio in +const int audioInPin = 36; // audio in for Morse decoder // + + +// NF Line-out (for iCW etc.) +const int lineOutPin = 17; // for NF line out + + +// SENS_FACTOR is used for auto-calibrating sensitivity of touch paddles (somewhere between 2.0 and 2.5) +#define SENS_FACTOR 2.22 + +#define BAND 433E6 //you can set band here directly,e.g. 868E6,915E6 + + +///////////////////////////////////////// END OF HARDWARE DEFS //////////////////////////////////////////////////////////////////// + + +// defines for keyer modi +// + +#define IAMBICA 1 // Curtis Mode A +#define IAMBICB 2 // Curtis Mode B (with enhanced Curtis timing, set as parameter +#define ULTIMATIC 3 // Ultimatic mode +#define NONSQUEEZE 4 // Non-squeeze mode of dual-lever paddles - simulate a single-lever paddle +#define STRAIGHTKEY 5 // use of a straight key (for echo training etc) - not really a "keyer" mode + + +enum DISPLAY_TYPE // how we display in trainer mode + { + NO_DISPLAY, DISPLAY_BY_CHAR, DISPLAY_BY_WORD + }; + +enum random_OPTIONS // what we generate + { + OPT_ALL, OPT_ALPHA, OPT_NUM, OPT_PUNCT, OPT_PRO, OPT_ALNUM, OPT_NUMPUNCT, OPT_PUNCTPRO, OPT_ALNUMPUNCT, OPT_NUMPUNCTPRO, OPT_KOCH, OPT_KOCH_ADAPTIVE + }; +enum PROMPT_TYPE // how we prompt in echo trainer mode + { + NO_PROMPT, CODE_ONLY, DISP_ONLY, CODE_AND_DISP + }; + +enum GEN_TYPE // the things we can generate in generator mode + { + RANDOMS, ABBREVS, WORDS, CALLS, MIXED, PLAYER, KOCH_MIXED, KOCH_LEARN, KOCH_ADAPTIVE + }; + + +enum DECODER_STATES // state machine for decoding CW + { + LOW_, HIGH_, INTERELEMENT_, INTERCHAR_ + }; + +enum encoderMode // define modes for state machine of the various modi the encoder can be in + { + speedSettingMode, volumeSettingMode, scrollMode + }; + +enum morserinoMode // the states the morserino can be in - selected in top level menu + { + morseKeyer, loraTrx, wifiTrx, morseTrx, morseGenerator, echoTrainer, morseDecoder, shutDown, measureNF, invalid + }; + +const uint8_t menuN = 43; // no of menu items +1 + +enum menuNo + { _dummy, _keyer, _gen, _genRand, _genAbb, _genWords, _genCalls, _genMixed, _genPlayer, + _echo, _echoRand, _echoAbb, _echoWords, _echoCalls, _echoMixed, _echoPlayer, + _koch, _kochSel, _kochLearn, _kochGen, _kochGenRand, _kochGenAbb, _kochGenWords, + _kochGenMixed, _kochEcho, _kochEchoRand, _kochEchoAbb, _kochEchoWords, _kochEchoMixed, _kochEchoAdaptive, + _trx, _trxLora, _trxWifi, _trxIcw, _decode, _wifi, _wifi_mac, _wifi_config, _wifi_check, _wifi_upload, _wifi_update, _wifi_select, _goToSleep + }; + +enum loops + { active_loop, menu_loop, preferences_loop + }; + + +enum echoStates + { + START_ECHO, SEND_WORD, REPEAT_WORD, GET_ANSWER, COMPLETE_ANSWER, EVAL_ANSWER + }; + +enum KEYERSTATES + { + IDLE_STATE, DIT, DAH, KEY_START, KEYED, INTER_ELEMENT + }; + + +enum prefPos : uint8_t { + posClicks, posPitch, posExtPddlPolarity, posPolarity, // 0 + posCurtisMode, posCurtisBDahTiming, posCurtisBDotTiming, posACS, // 4 + posEchoToneShift, posInterWordSpace, posInterCharSpace, posRandomOption, // 8 + posRandomLength, posCallLength, posAbbrevLength, posWordLength, // 12 + posGeneratorDisplay, posWordDoubler, posEchoDisplay, posEchoRepeats, posEchoConf, // 16 + posKeyExternalTx, posLoraCwTransmit, posGoertzelBandwidth, posSpeedAdapt, // 21 + posKochSeq, posCarouselStart, posLatency, posRandomFile, posExtAudioOnDecode, posTimeOut, // 25 + posQuickStart, posAutoStop,posMaxSequence, posLoraChannel, posSerialOut, // 31 + // to be treated differently: + posKochFilter, // 36 + posLoraBand, posLoraQRG, posLoraPower, posSnapRecall, posSnapStore, posVAdjust, posHwConf // 37 +}; + +enum actMessage : int { + ACT_EXIT, ACT_ON, ACT_SET, ACT_CANCELLED, ACT_RECALLED, ACT_CLEARED +}; + +extern void DEBUG (String s); + + +// we need this for some strange reason: the min definition breaks with WiFi +#define _min(a,b) ((a)<(b)?(a):(b)) +#define _max(a,b) ((a)>(b)?(a):(b)) + +#endif diff --git a/Software/src/Version 5/wklfonts.cpp b/Software/src/Version 5/wklfonts.cpp new file mode 100644 index 0000000..2413f7a --- /dev/null +++ b/Software/src/Version 5/wklfonts.cpp @@ -0,0 +1,3078 @@ +/****************************************************************************************************************************** + * morse_3 Software for the Morserino-32 multi-functional Morse code machine, based on the Heltec WiFi LORA (ESP32) module *** + * Copyright (C) 2018 Willi Kraml, OE1WKL *** + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + *****************************************************************************************************************************/ + +#include "wklfonts.h" + +// Created by http://oleddisplay.squix.ch/ Consider a donation +// In case of problems make sure that you are using the font file with the correct version! +uint8_t DialogInput_plain_12[] PROGMEM = + {0x07, // Width: 7 + 0x0F, // Height: 15 + 0x20, // First Char: 32 + 0xE0, // Numbers of Chars: 224 + + // Jump Table: + 0xFF, 0xFF, 0x00, 0x07, // 32:65535 + 0x00, 0x00, 0x08, 0x07, // 33:0 + 0x00, 0x08, 0x09, 0x07, // 34:8 + 0x00, 0x11, 0x0D, 0x07, // 35:17 + 0x00, 0x1E, 0x0C, 0x07, // 36:30 + 0x00, 0x2A, 0x0E, 0x07, // 37:42 + 0x00, 0x38, 0x0E, 0x07, // 38:56 + 0x00, 0x46, 0x07, 0x07, // 39:70 + 0x00, 0x4D, 0x0C, 0x07, // 40:77 + 0x00, 0x59, 0x0A, 0x07, // 41:89 + 0x00, 0x63, 0x0B, 0x07, // 42:99 + 0x00, 0x6E, 0x0E, 0x07, // 43:110 + 0x00, 0x7C, 0x08, 0x07, // 44:124 + 0x00, 0x84, 0x0A, 0x07, // 45:132 + 0x00, 0x8E, 0x08, 0x07, // 46:142 + 0x00, 0x96, 0x0D, 0x07, // 47:150 + 0x00, 0xA3, 0x0E, 0x07, // 48:163 + 0x00, 0xB1, 0x0C, 0x07, // 49:177 + 0x00, 0xBD, 0x0E, 0x07, // 50:189 + 0x00, 0xCB, 0x0E, 0x07, // 51:203 + 0x00, 0xD9, 0x0E, 0x07, // 52:217 + 0x00, 0xE7, 0x0E, 0x07, // 53:231 + 0x00, 0xF5, 0x0E, 0x07, // 54:245 + 0x01, 0x03, 0x0D, 0x07, // 55:259 + 0x01, 0x10, 0x0E, 0x07, // 56:272 + 0x01, 0x1E, 0x0E, 0x07, // 57:286 + 0x01, 0x2C, 0x08, 0x07, // 58:300 + 0x01, 0x34, 0x08, 0x07, // 59:308 + 0x01, 0x3C, 0x0E, 0x07, // 60:316 + 0x01, 0x4A, 0x0E, 0x07, // 61:330 + 0x01, 0x58, 0x0E, 0x07, // 62:344 + 0x01, 0x66, 0x0D, 0x07, // 63:358 + 0x01, 0x73, 0x0E, 0x07, // 64:371 + 0x01, 0x81, 0x0E, 0x07, // 65:385 + 0x01, 0x8F, 0x0E, 0x07, // 66:399 + 0x01, 0x9D, 0x0E, 0x07, // 67:413 + 0x01, 0xAB, 0x0E, 0x07, // 68:427 + 0x01, 0xB9, 0x0E, 0x07, // 69:441 + 0x01, 0xC7, 0x0D, 0x07, // 70:455 + 0x01, 0xD4, 0x0E, 0x07, // 71:468 + 0x01, 0xE2, 0x0E, 0x07, // 72:482 + 0x01, 0xF0, 0x0C, 0x07, // 73:496 + 0x01, 0xFC, 0x0C, 0x07, // 74:508 + 0x02, 0x08, 0x0E, 0x07, // 75:520 + 0x02, 0x16, 0x0E, 0x07, // 76:534 + 0x02, 0x24, 0x0E, 0x07, // 77:548 + 0x02, 0x32, 0x0E, 0x07, // 78:562 + 0x02, 0x40, 0x0E, 0x07, // 79:576 + 0x02, 0x4E, 0x0D, 0x07, // 80:590 + 0x02, 0x5B, 0x0E, 0x07, // 81:603 + 0x02, 0x69, 0x0E, 0x07, // 82:617 + 0x02, 0x77, 0x0E, 0x07, // 83:631 + 0x02, 0x85, 0x0D, 0x07, // 84:645 + 0x02, 0x92, 0x0E, 0x07, // 85:658 + 0x02, 0xA0, 0x0D, 0x07, // 86:672 + 0x02, 0xAD, 0x0E, 0x07, // 87:685 + 0x02, 0xBB, 0x0E, 0x07, // 88:699 + 0x02, 0xC9, 0x0D, 0x07, // 89:713 + 0x02, 0xD6, 0x0E, 0x07, // 90:726 + 0x02, 0xE4, 0x0A, 0x07, // 91:740 + 0x02, 0xEE, 0x0E, 0x07, // 92:750 + 0x02, 0xFC, 0x08, 0x07, // 93:764 + 0x03, 0x04, 0x0B, 0x07, // 94:772 + 0x03, 0x0F, 0x0E, 0x07, // 95:783 + 0x03, 0x1D, 0x09, 0x07, // 96:797 + 0x03, 0x26, 0x0C, 0x07, // 97:806 + 0x03, 0x32, 0x0C, 0x07, // 98:818 + 0x03, 0x3E, 0x0C, 0x07, // 99:830 + 0x03, 0x4A, 0x0C, 0x07, // 100:842 + 0x03, 0x56, 0x0C, 0x07, // 101:854 + 0x03, 0x62, 0x0B, 0x07, // 102:866 + 0x03, 0x6D, 0x0C, 0x07, // 103:877 + 0x03, 0x79, 0x0C, 0x07, // 104:889 + 0x03, 0x85, 0x0C, 0x07, // 105:901 + 0x03, 0x91, 0x0A, 0x07, // 106:913 + 0x03, 0x9B, 0x0C, 0x07, // 107:923 + 0x03, 0xA7, 0x0C, 0x07, // 108:935 + 0x03, 0xB3, 0x0C, 0x07, // 109:947 + 0x03, 0xBF, 0x0C, 0x07, // 110:959 + 0x03, 0xCB, 0x0C, 0x07, // 111:971 + 0x03, 0xD7, 0x0C, 0x07, // 112:983 + 0x03, 0xE3, 0x0C, 0x07, // 113:995 + 0x03, 0xEF, 0x0D, 0x07, // 114:1007 + 0x03, 0xFC, 0x0C, 0x07, // 115:1020 + 0x04, 0x08, 0x0C, 0x07, // 116:1032 + 0x04, 0x14, 0x0C, 0x07, // 117:1044 + 0x04, 0x20, 0x0B, 0x07, // 118:1056 + 0x04, 0x2B, 0x0D, 0x07, // 119:1067 + 0x04, 0x38, 0x0C, 0x07, // 120:1080 + 0x04, 0x44, 0x0B, 0x07, // 121:1092 + 0x04, 0x4F, 0x0C, 0x07, // 122:1103 + 0x04, 0x5B, 0x0C, 0x07, // 123:1115 + 0x04, 0x67, 0x08, 0x07, // 124:1127 + 0x04, 0x6F, 0x0B, 0x07, // 125:1135 + 0x04, 0x7A, 0x0E, 0x07, // 126:1146 + 0x04, 0x88, 0x0E, 0x07, // 127:1160 + 0x04, 0x96, 0x0E, 0x07, // 128:1174 + 0x04, 0xA4, 0x0E, 0x07, // 129:1188 + 0x04, 0xB2, 0x0E, 0x07, // 130:1202 + 0x04, 0xC0, 0x0E, 0x07, // 131:1216 + 0x04, 0xCE, 0x0E, 0x07, // 132:1230 + 0x04, 0xDC, 0x0E, 0x07, // 133:1244 + 0x04, 0xEA, 0x0E, 0x07, // 134:1258 + 0x04, 0xF8, 0x0E, 0x07, // 135:1272 + 0x05, 0x06, 0x0E, 0x07, // 136:1286 + 0x05, 0x14, 0x0E, 0x07, // 137:1300 + 0x05, 0x22, 0x0E, 0x07, // 138:1314 + 0x05, 0x30, 0x0E, 0x07, // 139:1328 + 0x05, 0x3E, 0x0E, 0x07, // 140:1342 + 0x05, 0x4C, 0x0E, 0x07, // 141:1356 + 0x05, 0x5A, 0x0E, 0x07, // 142:1370 + 0x05, 0x68, 0x0E, 0x07, // 143:1384 + 0x05, 0x76, 0x0E, 0x07, // 144:1398 + 0x05, 0x84, 0x0E, 0x07, // 145:1412 + 0x05, 0x92, 0x0E, 0x07, // 146:1426 + 0x05, 0xA0, 0x0E, 0x07, // 147:1440 + 0x05, 0xAE, 0x0E, 0x07, // 148:1454 + 0x05, 0xBC, 0x0E, 0x07, // 149:1468 + 0x05, 0xCA, 0x0E, 0x07, // 150:1482 + 0x05, 0xD8, 0x0E, 0x07, // 151:1496 + 0x05, 0xE6, 0x0E, 0x07, // 152:1510 + 0x05, 0xF4, 0x0E, 0x07, // 153:1524 + 0x06, 0x02, 0x0E, 0x07, // 154:1538 + 0x06, 0x10, 0x0E, 0x07, // 155:1552 + 0x06, 0x1E, 0x0E, 0x07, // 156:1566 + 0x06, 0x2C, 0x0E, 0x07, // 157:1580 + 0x06, 0x3A, 0x0E, 0x07, // 158:1594 + 0x06, 0x48, 0x0E, 0x07, // 159:1608 + 0xFF, 0xFF, 0x00, 0x07, // 160:65535 + 0x06, 0x56, 0x08, 0x07, // 161:1622 + 0x06, 0x5E, 0x0C, 0x07, // 162:1630 + 0x06, 0x6A, 0x0C, 0x07, // 163:1642 + 0x06, 0x76, 0x0E, 0x07, // 164:1654 + 0x06, 0x84, 0x0D, 0x07, // 165:1668 + 0x06, 0x91, 0x08, 0x07, // 166:1681 + 0x06, 0x99, 0x0C, 0x07, // 167:1689 + 0x06, 0xA5, 0x09, 0x07, // 168:1701 + 0x06, 0xAE, 0x0E, 0x07, // 169:1710 + 0x06, 0xBC, 0x0A, 0x07, // 170:1724 + 0x06, 0xC6, 0x0E, 0x07, // 171:1734 + 0x06, 0xD4, 0x0E, 0x07, // 172:1748 + 0x06, 0xE2, 0x0A, 0x07, // 173:1762 + 0x06, 0xEC, 0x0E, 0x07, // 174:1772 + 0x06, 0xFA, 0x0B, 0x07, // 175:1786 + 0x07, 0x05, 0x0B, 0x07, // 176:1797 + 0x07, 0x10, 0x0E, 0x07, // 177:1808 + 0x07, 0x1E, 0x09, 0x07, // 178:1822 + 0x07, 0x27, 0x0B, 0x07, // 179:1831 + 0x07, 0x32, 0x09, 0x07, // 180:1842 + 0x07, 0x3B, 0x0E, 0x07, // 181:1851 + 0x07, 0x49, 0x0E, 0x07, // 182:1865 + 0x07, 0x57, 0x08, 0x07, // 183:1879 + 0x07, 0x5F, 0x0A, 0x07, // 184:1887 + 0x07, 0x69, 0x09, 0x07, // 185:1897 + 0x07, 0x72, 0x0A, 0x07, // 186:1906 + 0x07, 0x7C, 0x0E, 0x07, // 187:1916 + 0x07, 0x8A, 0x0E, 0x07, // 188:1930 + 0x07, 0x98, 0x0C, 0x07, // 189:1944 + 0x07, 0xA4, 0x0E, 0x07, // 190:1956 + 0x07, 0xB2, 0x0A, 0x07, // 191:1970 + 0x07, 0xBC, 0x0E, 0x07, // 192:1980 + 0x07, 0xCA, 0x0E, 0x07, // 193:1994 + 0x07, 0xD8, 0x0E, 0x07, // 194:2008 + 0x07, 0xE6, 0x0E, 0x07, // 195:2022 + 0x07, 0xF4, 0x0E, 0x07, // 196:2036 + 0x08, 0x02, 0x0E, 0x07, // 197:2050 + 0x08, 0x10, 0x0E, 0x07, // 198:2064 + 0x08, 0x1E, 0x0E, 0x07, // 199:2078 + 0x08, 0x2C, 0x0E, 0x07, // 200:2092 + 0x08, 0x3A, 0x0E, 0x07, // 201:2106 + 0x08, 0x48, 0x0E, 0x07, // 202:2120 + 0x08, 0x56, 0x0E, 0x07, // 203:2134 + 0x08, 0x64, 0x0C, 0x07, // 204:2148 + 0x08, 0x70, 0x0C, 0x07, // 205:2160 + 0x08, 0x7C, 0x0C, 0x07, // 206:2172 + 0x08, 0x88, 0x0C, 0x07, // 207:2184 + 0x08, 0x94, 0x0E, 0x07, // 208:2196 + 0x08, 0xA2, 0x0E, 0x07, // 209:2210 + 0x08, 0xB0, 0x0E, 0x07, // 210:2224 + 0x08, 0xBE, 0x0E, 0x07, // 211:2238 + 0x08, 0xCC, 0x0E, 0x07, // 212:2252 + 0x08, 0xDA, 0x0E, 0x07, // 213:2266 + 0x08, 0xE8, 0x0E, 0x07, // 214:2280 + 0x08, 0xF6, 0x0C, 0x07, // 215:2294 + 0x09, 0x02, 0x0E, 0x07, // 216:2306 + 0x09, 0x10, 0x0E, 0x07, // 217:2320 + 0x09, 0x1E, 0x0E, 0x07, // 218:2334 + 0x09, 0x2C, 0x0E, 0x07, // 219:2348 + 0x09, 0x3A, 0x0E, 0x07, // 220:2362 + 0x09, 0x48, 0x0D, 0x07, // 221:2376 + 0x09, 0x55, 0x0D, 0x07, // 222:2389 + 0x09, 0x62, 0x0C, 0x07, // 223:2402 + 0x09, 0x6E, 0x0C, 0x07, // 224:2414 + 0x09, 0x7A, 0x0C, 0x07, // 225:2426 + 0x09, 0x86, 0x0C, 0x07, // 226:2438 + 0x09, 0x92, 0x0C, 0x07, // 227:2450 + 0x09, 0x9E, 0x0C, 0x07, // 228:2462 + 0x09, 0xAA, 0x0C, 0x07, // 229:2474 + 0x09, 0xB6, 0x0C, 0x07, // 230:2486 + 0x09, 0xC2, 0x0C, 0x07, // 231:2498 + 0x09, 0xCE, 0x0C, 0x07, // 232:2510 + 0x09, 0xDA, 0x0C, 0x07, // 233:2522 + 0x09, 0xE6, 0x0C, 0x07, // 234:2534 + 0x09, 0xF2, 0x0C, 0x07, // 235:2546 + 0x09, 0xFE, 0x0C, 0x07, // 236:2558 + 0x0A, 0x0A, 0x0C, 0x07, // 237:2570 + 0x0A, 0x16, 0x0C, 0x07, // 238:2582 + 0x0A, 0x22, 0x0C, 0x07, // 239:2594 + 0x0A, 0x2E, 0x0C, 0x07, // 240:2606 + 0x0A, 0x3A, 0x0C, 0x07, // 241:2618 + 0x0A, 0x46, 0x0C, 0x07, // 242:2630 + 0x0A, 0x52, 0x0C, 0x07, // 243:2642 + 0x0A, 0x5E, 0x0C, 0x07, // 244:2654 + 0x0A, 0x6A, 0x0C, 0x07, // 245:2666 + 0x0A, 0x76, 0x0C, 0x07, // 246:2678 + 0x0A, 0x82, 0x0C, 0x07, // 247:2690 + 0x0A, 0x8E, 0x0C, 0x07, // 248:2702 + 0x0A, 0x9A, 0x0C, 0x07, // 249:2714 + 0x0A, 0xA6, 0x0C, 0x07, // 250:2726 + 0x0A, 0xB2, 0x0C, 0x07, // 251:2738 + 0x0A, 0xBE, 0x0C, 0x07, // 252:2750 + 0x0A, 0xCA, 0x0B, 0x07, // 253:2762 + 0x0A, 0xD5, 0x0C, 0x07, // 254:2773 + 0x0A, 0xE1, 0x0B, 0x07, // 255:2785 + + // Font Data: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x0D, // 33 + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, // 34 + 0x00, 0x02, 0x40, 0x0E, 0xE0, 0x03, 0x50, 0x0A, 0xC0, 0x07, 0x70, 0x02, 0x40, // 35 + 0x00, 0x00, 0xE0, 0x04, 0x90, 0x08, 0xF8, 0x3F, 0x10, 0x09, 0x20, 0x07, // 36 + 0x30, 0x00, 0x48, 0x01, 0x48, 0x01, 0xB0, 0x06, 0x80, 0x09, 0x40, 0x09, 0x00, 0x06, // 37 + 0x00, 0x00, 0x00, 0x07, 0xF0, 0x0C, 0xC8, 0x08, 0x08, 0x0B, 0x08, 0x06, 0x00, 0x0B, // 38 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, // 39 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x1C, 0x1C, 0x04, 0x10, // 40 + 0x00, 0x00, 0x00, 0x00, 0x04, 0x10, 0x1C, 0x1C, 0xE0, 0x03, // 41 + 0x00, 0x00, 0x90, 0x00, 0x60, 0x00, 0xF8, 0x01, 0x60, 0x00, 0x90, // 42 + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0xE0, 0x0F, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, // 43 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0C, // 44 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, // 45 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, // 46 + 0x00, 0x00, 0x00, 0x10, 0x00, 0x0C, 0x00, 0x03, 0xC0, 0x00, 0x30, 0x00, 0x08, // 47 + 0x00, 0x00, 0xE0, 0x03, 0x18, 0x0C, 0x08, 0x08, 0x88, 0x08, 0x18, 0x0C, 0xE0, 0x03, // 48 + 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0xF8, 0x0F, 0x00, 0x08, 0x00, 0x08, // 49 + 0x00, 0x00, 0x10, 0x08, 0x08, 0x0C, 0x08, 0x0A, 0x08, 0x09, 0x88, 0x08, 0x70, 0x08, // 50 + 0x00, 0x00, 0x10, 0x04, 0x08, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x70, 0x07, // 51 + 0x00, 0x00, 0x00, 0x03, 0xC0, 0x02, 0x60, 0x02, 0x18, 0x02, 0xF8, 0x0F, 0x00, 0x02, // 52 + 0x00, 0x00, 0x78, 0x04, 0x48, 0x08, 0x48, 0x08, 0x48, 0x08, 0xC8, 0x0C, 0x80, 0x07, // 53 + 0x00, 0x00, 0xE0, 0x03, 0x90, 0x0C, 0x48, 0x08, 0x48, 0x08, 0xC8, 0x0C, 0x90, 0x07, // 54 + 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x08, 0x06, 0x88, 0x01, 0x78, 0x00, 0x18, // 55 + 0x00, 0x00, 0x70, 0x07, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x70, 0x07, // 56 + 0x00, 0x00, 0xF0, 0x04, 0x18, 0x09, 0x08, 0x09, 0x08, 0x09, 0x98, 0x04, 0xE0, 0x03, // 57 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0C, // 58 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xC0, 0x0C, // 59 + 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x20, 0x04, // 60 + 0x00, 0x00, 0x80, 0x02, 0x80, 0x02, 0x80, 0x02, 0x80, 0x02, 0x80, 0x02, 0x80, 0x02, // 61 + 0x00, 0x00, 0x20, 0x04, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x80, 0x01, 0x80, 0x01, // 62 + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x88, 0x0D, 0xC8, 0x00, 0x48, 0x00, 0x30, // 63 + 0x00, 0x00, 0xC0, 0x0F, 0x20, 0x18, 0x10, 0x23, 0x90, 0x24, 0xB0, 0x24, 0xE0, 0x07, // 64 + 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x03, 0x38, 0x02, 0x38, 0x02, 0xC0, 0x03, 0x00, 0x0C, // 65 + 0x00, 0x00, 0xF8, 0x0F, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x70, 0x07, // 66 + 0x00, 0x00, 0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0x04, // 67 + 0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 68 + 0x00, 0x00, 0xF8, 0x0F, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, // 69 + 0x00, 0x00, 0xF8, 0x0F, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, // 70 + 0x00, 0x00, 0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, 0x08, 0x08, 0x88, 0x08, 0x90, 0x07, // 71 + 0x00, 0x00, 0xF8, 0x0F, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xF8, 0x0F, // 72 + 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0xF8, 0x0F, 0x08, 0x08, 0x08, 0x08, // 73 + 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0xF8, 0x07, // 74 + 0x00, 0x00, 0xF8, 0x0F, 0x80, 0x00, 0xC0, 0x00, 0x20, 0x03, 0x10, 0x06, 0x08, 0x08, // 75 + 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 76 + 0x00, 0x00, 0xF8, 0x0F, 0x30, 0x00, 0xC0, 0x01, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x0F, // 77 + 0x00, 0x00, 0xF8, 0x0F, 0x18, 0x00, 0xE0, 0x00, 0x80, 0x03, 0x00, 0x0C, 0xF8, 0x0F, // 78 + 0x00, 0x00, 0xE0, 0x03, 0x18, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x18, 0x0C, 0xE0, 0x03, // 79 + 0x00, 0x00, 0xF8, 0x0F, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, // 80 + 0x00, 0x00, 0xE0, 0x03, 0x18, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x18, 0x3C, 0xE0, 0x07, // 81 + 0x00, 0x00, 0xF8, 0x0F, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x01, 0x70, 0x06, // 82 + 0x00, 0x00, 0x70, 0x04, 0xC8, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x10, 0x07, // 83 + 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x0F, 0x08, 0x00, 0x08, 0x00, 0x08, // 84 + 0x00, 0x00, 0xF8, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0xF8, 0x07, // 85 + 0x00, 0x00, 0x18, 0x00, 0xE0, 0x01, 0x00, 0x0E, 0x00, 0x0E, 0xE0, 0x01, 0x18, // 86 + 0xF8, 0x01, 0x00, 0x0E, 0xC0, 0x03, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0E, 0xF8, 0x01, // 87 + 0x00, 0x00, 0x08, 0x08, 0x30, 0x06, 0xC0, 0x01, 0xC0, 0x01, 0x30, 0x06, 0x08, 0x08, // 88 + 0x08, 0x00, 0x10, 0x00, 0x60, 0x00, 0x80, 0x0F, 0x60, 0x00, 0x10, 0x00, 0x08, // 89 + 0x00, 0x00, 0x08, 0x0C, 0x08, 0x0E, 0x88, 0x09, 0xC8, 0x08, 0x38, 0x08, 0x18, 0x08, // 90 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x1F, 0x04, 0x10, // 91 + 0x00, 0x00, 0x08, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x00, 0x0C, 0x00, 0x10, // 92 + 0x00, 0x00, 0x00, 0x00, 0x04, 0x10, 0xFC, 0x1F, // 93 + 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, // 94 + 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, // 95 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, // 96 + 0x00, 0x00, 0x40, 0x06, 0x20, 0x09, 0x20, 0x09, 0x20, 0x09, 0xC0, 0x0F, // 97 + 0x00, 0x00, 0xFC, 0x0F, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xC0, 0x07, // 98 + 0x00, 0x00, 0xC0, 0x07, 0x60, 0x0C, 0x20, 0x08, 0x20, 0x08, 0x40, 0x08, // 99 + 0x00, 0x00, 0xC0, 0x07, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xFC, 0x0F, // 100 + 0x00, 0x00, 0xC0, 0x07, 0x60, 0x09, 0x20, 0x09, 0x20, 0x09, 0xC0, 0x05, // 101 + 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0xF8, 0x0F, 0x24, 0x00, 0x24, // 102 + 0x00, 0x00, 0xC0, 0x07, 0x20, 0x28, 0x20, 0x48, 0x20, 0x48, 0xE0, 0x3F, // 103 + 0x00, 0x00, 0xFC, 0x0F, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x0F, // 104 + 0x00, 0x00, 0x20, 0x08, 0x20, 0x08, 0xE4, 0x0F, 0x00, 0x08, 0x00, 0x08, // 105 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0x20, 0x40, 0xE4, 0x3F, // 106 + 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x01, 0x80, 0x02, 0x40, 0x04, 0x20, 0x08, // 107 + 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0xFC, 0x07, 0x00, 0x08, 0x00, 0x08, // 108 + 0x00, 0x00, 0xE0, 0x0F, 0x20, 0x00, 0xE0, 0x0F, 0x20, 0x00, 0xE0, 0x0F, // 109 + 0x00, 0x00, 0xE0, 0x0F, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x0F, // 110 + 0x00, 0x00, 0xC0, 0x07, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xC0, 0x07, // 111 + 0x00, 0x00, 0xE0, 0x7F, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xC0, 0x07, // 112 + 0x00, 0x00, 0xC0, 0x07, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xE0, 0x7F, // 113 + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, // 114 + 0x00, 0x00, 0xC0, 0x04, 0x20, 0x09, 0x20, 0x09, 0x20, 0x09, 0x40, 0x06, // 115 + 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0xF8, 0x0F, 0x20, 0x08, 0x20, 0x08, // 116 + 0x00, 0x00, 0xE0, 0x07, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0xE0, 0x0F, // 117 + 0x00, 0x00, 0x60, 0x00, 0x80, 0x03, 0x00, 0x0C, 0x80, 0x03, 0x60, // 118 + 0x60, 0x00, 0x80, 0x03, 0x00, 0x0E, 0x80, 0x01, 0x00, 0x0E, 0x80, 0x03, 0x60, // 119 + 0x00, 0x00, 0x20, 0x08, 0xC0, 0x06, 0x00, 0x01, 0xC0, 0x06, 0x20, 0x08, // 120 + 0x00, 0x00, 0x60, 0x40, 0x80, 0x67, 0x00, 0x1C, 0x80, 0x03, 0x60, // 121 + 0x00, 0x00, 0x20, 0x0C, 0x20, 0x0A, 0x20, 0x09, 0xA0, 0x08, 0x60, 0x08, // 122 + 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x7C, 0x1F, 0x04, 0x10, 0x04, 0x10, // 123 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, // 124 + 0x00, 0x00, 0x04, 0x10, 0x04, 0x10, 0x7C, 0x1F, 0x80, 0x00, 0x80, // 125 + 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, // 126 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 127 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 128 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 129 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 130 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 131 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 132 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 133 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 134 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 135 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 136 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 137 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 138 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 139 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 140 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 141 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 142 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 143 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 144 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 145 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 146 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 147 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 148 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 149 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 150 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 151 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 152 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 153 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 154 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 155 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 156 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 157 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 158 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 159 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x3F, // 161 + 0x00, 0x00, 0xC0, 0x07, 0x20, 0x08, 0xF8, 0x3F, 0x20, 0x08, 0x40, 0x04, // 162 + 0x00, 0x00, 0x80, 0x08, 0xF0, 0x0F, 0x88, 0x08, 0x88, 0x08, 0x08, 0x08, // 163 + 0x00, 0x00, 0x20, 0x04, 0xC0, 0x03, 0x40, 0x02, 0x40, 0x02, 0xC0, 0x03, 0x20, 0x04, // 164 + 0x08, 0x00, 0x50, 0x01, 0x60, 0x01, 0x80, 0x0F, 0x60, 0x01, 0x50, 0x01, 0x08, // 165 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3C, // 166 + 0x00, 0x00, 0xF0, 0x11, 0x28, 0x13, 0x48, 0x12, 0xC8, 0x14, 0x88, 0x0F, // 167 + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, // 168 + 0xC0, 0x01, 0x20, 0x02, 0xD0, 0x05, 0x50, 0x05, 0x50, 0x05, 0x20, 0x02, 0xC0, 0x01, // 169 + 0x00, 0x00, 0xE8, 0x02, 0xA8, 0x02, 0xA8, 0x02, 0xF0, 0x02, // 170 + 0x00, 0x00, 0x00, 0x01, 0x80, 0x02, 0x40, 0x04, 0x00, 0x01, 0x80, 0x02, 0x40, 0x04, // 171 + 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x03, // 172 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, // 173 + 0xC0, 0x01, 0x20, 0x02, 0xD0, 0x05, 0xD0, 0x05, 0xD0, 0x05, 0x20, 0x02, 0xC0, 0x01, // 174 + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 175 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 176 + 0x80, 0x08, 0x80, 0x08, 0x80, 0x08, 0xE0, 0x0B, 0x80, 0x08, 0x80, 0x08, 0x80, 0x08, // 177 + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0xE8, 0x00, 0xB8, // 178 + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0xA8, 0x00, 0xA8, 0x00, 0xD8, // 179 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, // 180 + 0x00, 0x00, 0xE0, 0x7F, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0xE0, 0x0F, 0x00, 0x08, // 181 + 0x00, 0x00, 0x70, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0xF8, 0x1F, 0x08, 0x00, 0xF8, 0x1F, // 182 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, // 183 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x30, // 184 + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0xF8, 0x00, 0x80, // 185 + 0x00, 0x00, 0x70, 0x02, 0x88, 0x02, 0x88, 0x02, 0x70, 0x02, // 186 + 0x00, 0x00, 0x40, 0x04, 0x80, 0x02, 0x00, 0x01, 0x40, 0x04, 0x80, 0x02, 0x00, 0x01, // 187 + 0x44, 0x02, 0x7C, 0x03, 0x40, 0x01, 0x00, 0x0D, 0x80, 0x0B, 0x80, 0x1F, 0x00, 0x08, // 188 + 0x44, 0x02, 0x7C, 0x03, 0x40, 0x01, 0x00, 0x11, 0x80, 0x1D, 0x80, 0x17, // 189 + 0x00, 0x02, 0x44, 0x03, 0x54, 0x01, 0x54, 0x0D, 0xEC, 0x0B, 0x80, 0x1F, 0x00, 0x08, // 190 + 0x00, 0x38, 0x00, 0x4C, 0x00, 0x44, 0x60, 0x43, 0x00, 0x20, // 191 + 0x00, 0x00, 0x00, 0x0C, 0xC1, 0x03, 0x3A, 0x02, 0x38, 0x02, 0xC0, 0x03, 0x00, 0x0C, // 192 + 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x03, 0x3A, 0x02, 0x39, 0x02, 0xC0, 0x03, 0x00, 0x0C, // 193 + 0x00, 0x00, 0x00, 0x0C, 0xC2, 0x03, 0x39, 0x02, 0x39, 0x02, 0xC2, 0x03, 0x00, 0x0C, // 194 + 0x00, 0x00, 0x00, 0x0C, 0xC3, 0x03, 0x39, 0x02, 0x3A, 0x02, 0xC3, 0x03, 0x00, 0x0C, // 195 + 0x00, 0x00, 0x00, 0x0C, 0xC2, 0x03, 0x38, 0x02, 0x38, 0x02, 0xC2, 0x03, 0x00, 0x0C, // 196 + 0x00, 0x00, 0x00, 0x08, 0x00, 0x07, 0xFE, 0x02, 0xE6, 0x02, 0x00, 0x07, 0x00, 0x08, // 197 + 0x00, 0x0C, 0x80, 0x03, 0x78, 0x02, 0x08, 0x02, 0xF8, 0x0F, 0x88, 0x08, 0x88, 0x08, // 198 + 0x00, 0x00, 0xE0, 0x03, 0x10, 0x04, 0x08, 0x28, 0x08, 0x28, 0x08, 0x38, 0x10, 0x04, // 199 + 0x00, 0x00, 0xF8, 0x0F, 0x89, 0x08, 0x8A, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, // 200 + 0x00, 0x00, 0xF8, 0x0F, 0x88, 0x08, 0x8A, 0x08, 0x89, 0x08, 0x88, 0x08, 0x88, 0x08, // 201 + 0x00, 0x00, 0xF8, 0x0F, 0x8A, 0x08, 0x89, 0x08, 0x89, 0x08, 0x8A, 0x08, 0x88, 0x08, // 202 + 0x00, 0x00, 0xF8, 0x0F, 0x8A, 0x08, 0x88, 0x08, 0x8A, 0x08, 0x88, 0x08, 0x88, 0x08, // 203 + 0x00, 0x00, 0x08, 0x08, 0x09, 0x08, 0xFA, 0x0F, 0x08, 0x08, 0x08, 0x08, // 204 + 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0xFA, 0x0F, 0x09, 0x08, 0x08, 0x08, // 205 + 0x00, 0x00, 0x08, 0x08, 0x0A, 0x08, 0xF9, 0x0F, 0x0A, 0x08, 0x08, 0x08, // 206 + 0x00, 0x00, 0x08, 0x08, 0x0A, 0x08, 0xF8, 0x0F, 0x0A, 0x08, 0x08, 0x08, // 207 + 0x80, 0x00, 0xF8, 0x0F, 0x88, 0x08, 0x88, 0x08, 0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 208 + 0x00, 0x00, 0xF8, 0x0F, 0x1B, 0x00, 0xE1, 0x00, 0x82, 0x03, 0x03, 0x0C, 0xF8, 0x0F, // 209 + 0x00, 0x00, 0xE0, 0x03, 0x19, 0x0C, 0x0A, 0x08, 0x08, 0x08, 0x18, 0x0C, 0xE0, 0x03, // 210 + 0x00, 0x00, 0xE0, 0x03, 0x18, 0x0C, 0x0A, 0x08, 0x09, 0x08, 0x18, 0x0C, 0xE0, 0x03, // 211 + 0x00, 0x00, 0xE0, 0x03, 0x1A, 0x0C, 0x09, 0x08, 0x09, 0x08, 0x1A, 0x0C, 0xE0, 0x03, // 212 + 0x00, 0x00, 0xE0, 0x03, 0x1B, 0x0C, 0x09, 0x08, 0x0A, 0x08, 0x1B, 0x0C, 0xE0, 0x03, // 213 + 0x00, 0x00, 0xE0, 0x03, 0x1A, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x1A, 0x0C, 0xE0, 0x03, // 214 + 0x00, 0x00, 0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 215 + 0x00, 0x08, 0xE0, 0x07, 0x18, 0x0E, 0x88, 0x09, 0x48, 0x08, 0x38, 0x0C, 0xF8, 0x03, // 216 + 0x00, 0x00, 0xF8, 0x07, 0x01, 0x08, 0x02, 0x08, 0x00, 0x08, 0x00, 0x08, 0xF8, 0x07, // 217 + 0x00, 0x00, 0xF8, 0x07, 0x00, 0x08, 0x02, 0x08, 0x01, 0x08, 0x00, 0x08, 0xF8, 0x07, // 218 + 0x00, 0x00, 0xF8, 0x07, 0x02, 0x08, 0x01, 0x08, 0x01, 0x08, 0x02, 0x08, 0xF8, 0x07, // 219 + 0x00, 0x00, 0xF8, 0x07, 0x02, 0x08, 0x00, 0x08, 0x00, 0x08, 0x02, 0x08, 0xF8, 0x07, // 220 + 0x08, 0x00, 0x10, 0x00, 0x60, 0x00, 0x82, 0x0F, 0x61, 0x00, 0x10, 0x00, 0x08, // 221 + 0x00, 0x00, 0xF8, 0x0F, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xE0, // 222 + 0x00, 0x00, 0xF8, 0x0F, 0xE4, 0x00, 0xA4, 0x09, 0x18, 0x09, 0x00, 0x06, // 223 + 0x00, 0x00, 0x40, 0x06, 0x20, 0x09, 0x24, 0x09, 0x28, 0x09, 0xC0, 0x0F, // 224 + 0x00, 0x00, 0x40, 0x06, 0x20, 0x09, 0x28, 0x09, 0x24, 0x09, 0xC0, 0x0F, // 225 + 0x00, 0x00, 0x40, 0x06, 0x28, 0x09, 0x24, 0x09, 0x24, 0x09, 0xC8, 0x0F, // 226 + 0x00, 0x00, 0x40, 0x06, 0x2C, 0x09, 0x24, 0x09, 0x28, 0x09, 0xCC, 0x0F, // 227 + 0x00, 0x00, 0x40, 0x06, 0x28, 0x09, 0x20, 0x09, 0x28, 0x09, 0xC0, 0x0F, // 228 + 0x00, 0x00, 0x40, 0x06, 0x26, 0x09, 0x29, 0x09, 0x29, 0x09, 0xC6, 0x0F, // 229 + 0x00, 0x00, 0x40, 0x0F, 0x20, 0x09, 0xC0, 0x07, 0x20, 0x09, 0xE0, 0x09, // 230 + 0x00, 0x00, 0xC0, 0x07, 0x60, 0x0C, 0x20, 0x28, 0x20, 0x28, 0x40, 0x38, // 231 + 0x00, 0x00, 0xC0, 0x07, 0x60, 0x09, 0x24, 0x09, 0x28, 0x09, 0xC0, 0x05, // 232 + 0x00, 0x00, 0xC0, 0x07, 0x60, 0x09, 0x28, 0x09, 0x24, 0x09, 0xC0, 0x05, // 233 + 0x00, 0x00, 0xC0, 0x07, 0x68, 0x09, 0x24, 0x09, 0x24, 0x09, 0xC8, 0x05, // 234 + 0x00, 0x00, 0xC0, 0x07, 0x68, 0x09, 0x20, 0x09, 0x28, 0x09, 0xC0, 0x05, // 235 + 0x00, 0x00, 0x20, 0x08, 0x20, 0x08, 0xE4, 0x0F, 0x08, 0x08, 0x00, 0x08, // 236 + 0x00, 0x00, 0x20, 0x08, 0x20, 0x08, 0xE8, 0x0F, 0x04, 0x08, 0x00, 0x08, // 237 + 0x00, 0x00, 0x28, 0x08, 0x24, 0x08, 0xE4, 0x0F, 0x08, 0x08, 0x00, 0x08, // 238 + 0x00, 0x00, 0x20, 0x08, 0x28, 0x08, 0xE0, 0x0F, 0x08, 0x08, 0x00, 0x08, // 239 + 0x00, 0x00, 0x80, 0x07, 0x54, 0x08, 0x58, 0x08, 0x68, 0x08, 0xC0, 0x07, // 240 + 0x00, 0x00, 0xE0, 0x0F, 0x4C, 0x00, 0x24, 0x00, 0x28, 0x00, 0xCC, 0x0F, // 241 + 0x00, 0x00, 0xC0, 0x07, 0x20, 0x08, 0x24, 0x08, 0x28, 0x08, 0xC0, 0x07, // 242 + 0x00, 0x00, 0xC0, 0x07, 0x20, 0x08, 0x28, 0x08, 0x24, 0x08, 0xC0, 0x07, // 243 + 0x00, 0x00, 0xC0, 0x07, 0x28, 0x08, 0x24, 0x08, 0x28, 0x08, 0xC0, 0x07, // 244 + 0x00, 0x00, 0xCC, 0x07, 0x24, 0x08, 0x2C, 0x08, 0x28, 0x08, 0xCC, 0x07, // 245 + 0x00, 0x00, 0xC0, 0x07, 0x28, 0x08, 0x20, 0x08, 0x28, 0x08, 0xC0, 0x07, // 246 + 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x40, 0x05, 0x00, 0x01, 0x00, 0x01, // 247 + 0x00, 0x00, 0xC0, 0x0F, 0x20, 0x0A, 0x20, 0x09, 0xA0, 0x08, 0xE0, 0x07, // 248 + 0x00, 0x00, 0xE0, 0x07, 0x00, 0x08, 0x04, 0x08, 0x08, 0x08, 0xE0, 0x0F, // 249 + 0x00, 0x00, 0xE0, 0x07, 0x00, 0x08, 0x08, 0x08, 0x04, 0x08, 0xE0, 0x0F, // 250 + 0x00, 0x00, 0xE0, 0x07, 0x08, 0x08, 0x04, 0x08, 0x08, 0x08, 0xE0, 0x0F, // 251 + 0x00, 0x00, 0xE0, 0x07, 0x08, 0x08, 0x00, 0x08, 0x08, 0x08, 0xE0, 0x0F, // 252 + 0x00, 0x00, 0x60, 0x40, 0x80, 0x67, 0x08, 0x1C, 0x84, 0x03, 0x60, // 253 + 0x00, 0x00, 0xFC, 0x7F, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xC0, 0x07, // 254 + 0x00, 0x00, 0x60, 0x40, 0x88, 0x67, 0x00, 0x1C, 0x88, 0x03, 0x60 // 255 + }; +// Created by http://oleddisplay.squix.ch/ Consider a donation +// In case of problems make sure that you are using the font file with the correct version! +uint8_t DialogInput_plain_15[] PROGMEM = + { + 0x09, // Width: 9 + 0x12, // Height: 18 + 0x20, // First Char: 32 + 0xE0, // Numbers of Chars: 224 + + // Jump Table: + 0xFF, 0xFF, 0x00, + 0x09, // 32:65535 + 0x00, 0x00, 0x0E, + 0x09, // 33:0 + 0x00, 0x0E, 0x13, + 0x09, // 34:14 + 0x00, 0x21, 0x19, + 0x09, // 35:33 + 0x00, 0x3A, 0x17, + 0x09, // 36:58 + 0x00, 0x51, 0x1A, + 0x09, // 37:81 + 0x00, 0x6B, 0x17, + 0x09, // 38:107 + 0x00, 0x82, 0x0D, + 0x09, // 39:130 + 0x00, 0x8F, 0x11, + 0x09, // 40:143 + 0x00, 0xA0, 0x0E, + 0x09, // 41:160 + 0x00, 0xAE, 0x16, + 0x09, // 42:174 + 0x00, 0xC4, 0x17, + 0x09, // 43:196 + 0x00, 0xDB, 0x11, + 0x09, // 44:219 + 0x00, 0xEC, 0x14, + 0x09, // 45:236 + 0x01, 0x00, 0x11, + 0x09, // 46:256 + 0x01, 0x11, 0x16, + 0x09, // 47:273 + 0x01, 0x27, 0x17, + 0x09, // 48:295 + 0x01, 0x3E, 0x14, + 0x09, // 49:318 + 0x01, 0x52, 0x17, + 0x09, // 50:338 + 0x01, 0x69, 0x17, + 0x09, // 51:361 + 0x01, 0x80, 0x17, + 0x09, // 52:384 + 0x01, 0x97, 0x17, + 0x09, // 53:407 + 0x01, 0xAE, 0x17, + 0x09, // 54:430 + 0x01, 0xC5, 0x16, + 0x09, // 55:453 + 0x01, 0xDB, 0x17, + 0x09, // 56:475 + 0x01, 0xF2, 0x17, + 0x09, // 57:498 + 0x02, 0x09, 0x11, + 0x09, // 58:521 + 0x02, 0x1A, 0x11, + 0x09, // 59:538 + 0x02, 0x2B, 0x1A, + 0x09, // 60:555 + 0x02, 0x45, 0x1A, + 0x09, // 61:581 + 0x02, 0x5F, 0x1A, + 0x09, // 62:607 + 0x02, 0x79, 0x16, + 0x09, // 63:633 + 0x02, 0x8F, 0x17, + 0x09, // 64:655 + 0x02, 0xA6, 0x17, + 0x09, // 65:678 + 0x02, 0xBD, 0x17, + 0x09, // 66:701 + 0x02, 0xD4, 0x17, + 0x09, // 67:724 + 0x02, 0xEB, 0x17, + 0x09, // 68:747 + 0x03, 0x02, 0x17, + 0x09, // 69:770 + 0x03, 0x19, 0x17, + 0x09, // 70:793 + 0x03, 0x30, 0x17, + 0x09, // 71:816 + 0x03, 0x47, 0x17, + 0x09, // 72:839 + 0x03, 0x5E, 0x14, + 0x09, // 73:862 + 0x03, 0x72, 0x14, + 0x09, // 74:882 + 0x03, 0x86, 0x17, + 0x09, // 75:902 + 0x03, 0x9D, 0x17, + 0x09, // 76:925 + 0x03, 0xB4, 0x17, + 0x09, // 77:948 + 0x03, 0xCB, 0x17, + 0x09, // 78:971 + 0x03, 0xE2, 0x17, + 0x09, // 79:994 + 0x03, 0xF9, 0x17, + 0x09, // 80:1017 + 0x04, 0x10, 0x17, + 0x09, // 81:1040 + 0x04, 0x27, 0x1A, + 0x09, // 82:1063 + 0x04, 0x41, 0x17, + 0x09, // 83:1089 + 0x04, 0x58, 0x16, + 0x09, // 84:1112 + 0x04, 0x6E, 0x17, + 0x09, // 85:1134 + 0x04, 0x85, 0x16, + 0x09, // 86:1157 + 0x04, 0x9B, 0x19, + 0x09, // 87:1179 + 0x04, 0xB4, 0x17, + 0x09, // 88:1204 + 0x04, 0xCB, 0x19, + 0x09, // 89:1227 + 0x04, 0xE4, 0x17, + 0x09, // 90:1252 + 0x04, 0xFB, 0x11, + 0x09, // 91:1275 + 0x05, 0x0C, 0x17, + 0x09, // 92:1292 + 0x05, 0x23, 0x0E, + 0x09, // 93:1315 + 0x05, 0x31, 0x19, + 0x09, // 94:1329 + 0x05, 0x4A, 0x1B, + 0x09, // 95:1354 + 0x05, 0x65, 0x10, + 0x09, // 96:1381 + 0x05, 0x75, 0x17, + 0x09, // 97:1397 + 0x05, 0x8C, 0x17, + 0x09, // 98:1420 + 0x05, 0xA3, 0x14, + 0x09, // 99:1443 + 0x05, 0xB7, 0x17, + 0x09, // 100:1463 + 0x05, 0xCE, 0x17, + 0x09, // 101:1486 + 0x05, 0xE5, 0x16, + 0x09, // 102:1509 + 0x05, 0xFB, 0x17, + 0x09, // 103:1531 + 0x06, 0x12, 0x17, + 0x09, // 104:1554 + 0x06, 0x29, 0x17, + 0x09, // 105:1577 + 0x06, 0x40, 0x0E, + 0x09, // 106:1600 + 0x06, 0x4E, 0x17, + 0x09, // 107:1614 + 0x06, 0x65, 0x14, + 0x09, // 108:1637 + 0x06, 0x79, 0x17, + 0x09, // 109:1657 + 0x06, 0x90, 0x17, + 0x09, // 110:1680 + 0x06, 0xA7, 0x17, + 0x09, // 111:1703 + 0x06, 0xBE, 0x17, + 0x09, // 112:1726 + 0x06, 0xD5, 0x18, + 0x09, // 113:1749 + 0x06, 0xED, 0x19, + 0x09, // 114:1773 + 0x07, 0x06, 0x17, + 0x09, // 115:1798 + 0x07, 0x1D, 0x14, + 0x09, // 116:1821 + 0x07, 0x31, 0x17, + 0x09, // 117:1841 + 0x07, 0x48, 0x16, + 0x09, // 118:1864 + 0x07, 0x5E, 0x19, + 0x09, // 119:1886 + 0x07, 0x77, 0x17, + 0x09, // 120:1911 + 0x07, 0x8E, 0x16, + 0x09, // 121:1934 + 0x07, 0xA4, 0x17, + 0x09, // 122:1956 + 0x07, 0xBB, 0x15, + 0x09, // 123:1979 + 0x07, 0xD0, 0x0F, + 0x09, // 124:2000 + 0x07, 0xDF, 0x14, + 0x09, // 125:2015 + 0x07, 0xF3, 0x1A, + 0x09, // 126:2035 + 0x08, 0x0D, 0x1A, + 0x09, // 127:2061 + 0x08, 0x27, 0x1A, + 0x09, // 128:2087 + 0x08, 0x41, 0x1A, + 0x09, // 129:2113 + 0x08, 0x5B, 0x1A, + 0x09, // 130:2139 + 0x08, 0x75, 0x1A, + 0x09, // 131:2165 + 0x08, 0x8F, 0x1A, + 0x09, // 132:2191 + 0x08, 0xA9, 0x1A, + 0x09, // 133:2217 + 0x08, 0xC3, 0x1A, + 0x09, // 134:2243 + 0x08, 0xDD, 0x1A, + 0x09, // 135:2269 + 0x08, 0xF7, 0x1A, + 0x09, // 136:2295 + 0x09, 0x11, 0x1A, + 0x09, // 137:2321 + 0x09, 0x2B, 0x1A, + 0x09, // 138:2347 + 0x09, 0x45, 0x1A, + 0x09, // 139:2373 + 0x09, 0x5F, 0x1A, + 0x09, // 140:2399 + 0x09, 0x79, 0x1A, + 0x09, // 141:2425 + 0x09, 0x93, 0x1A, + 0x09, // 142:2451 + 0x09, 0xAD, 0x1A, + 0x09, // 143:2477 + 0x09, 0xC7, 0x1A, + 0x09, // 144:2503 + 0x09, 0xE1, 0x1A, + 0x09, // 145:2529 + 0x09, 0xFB, 0x1A, + 0x09, // 146:2555 + 0x0A, 0x15, 0x1A, + 0x09, // 147:2581 + 0x0A, 0x2F, 0x1A, + 0x09, // 148:2607 + 0x0A, 0x49, 0x1A, + 0x09, // 149:2633 + 0x0A, 0x63, 0x1A, + 0x09, // 150:2659 + 0x0A, 0x7D, 0x1A, + 0x09, // 151:2685 + 0x0A, 0x97, 0x1A, + 0x09, // 152:2711 + 0x0A, 0xB1, 0x1A, + 0x09, // 153:2737 + 0x0A, 0xCB, 0x1A, + 0x09, // 154:2763 + 0x0A, 0xE5, 0x1A, + 0x09, // 155:2789 + 0x0A, 0xFF, 0x1A, + 0x09, // 156:2815 + 0x0B, 0x19, 0x1A, + 0x09, // 157:2841 + 0x0B, 0x33, 0x1A, + 0x09, // 158:2867 + 0x0B, 0x4D, 0x1A, + 0x09, // 159:2893 + 0xFF, 0xFF, 0x00, + 0x09, // 160:65535 + 0x0B, 0x67, 0x0F, + 0x09, // 161:2919 + 0x0B, 0x76, 0x14, + 0x09, // 162:2934 + 0x0B, 0x8A, 0x17, + 0x09, // 163:2954 + 0x0B, 0xA1, 0x17, + 0x09, // 164:2977 + 0x0B, 0xB8, 0x19, + 0x09, // 165:3000 + 0x0B, 0xD1, 0x0F, + 0x09, // 166:3025 + 0x0B, 0xE0, 0x14, + 0x09, // 167:3040 + 0x0B, 0xF4, 0x13, + 0x09, // 168:3060 + 0x0C, 0x07, 0x1A, + 0x09, // 169:3079 + 0x0C, 0x21, 0x14, + 0x09, // 170:3105 + 0x0C, 0x35, 0x14, + 0x09, // 171:3125 + 0x0C, 0x49, 0x1A, + 0x09, // 172:3145 + 0x0C, 0x63, 0x14, + 0x09, // 173:3171 + 0x0C, 0x77, 0x1A, + 0x09, // 174:3191 + 0x0C, 0x91, 0x10, + 0x09, // 175:3217 + 0x0C, 0xA1, 0x13, + 0x09, // 176:3233 + 0x0C, 0xB4, 0x17, + 0x09, // 177:3252 + 0x0C, 0xCB, 0x11, + 0x09, // 178:3275 + 0x0C, 0xDC, 0x10, + 0x09, // 179:3292 + 0x0C, 0xEC, 0x13, + 0x09, // 180:3308 + 0x0C, 0xFF, 0x1A, + 0x09, // 181:3327 + 0x0D, 0x19, 0x17, + 0x09, // 182:3353 + 0x0D, 0x30, 0x11, + 0x09, // 183:3376 + 0x0D, 0x41, 0x12, + 0x09, // 184:3393 + 0x0D, 0x53, 0x14, + 0x09, // 185:3411 + 0x0D, 0x67, 0x14, + 0x09, // 186:3431 + 0x0D, 0x7B, 0x14, + 0x09, // 187:3451 + 0x0D, 0x8F, 0x17, + 0x09, // 188:3471 + 0x0D, 0xA6, 0x17, + 0x09, // 189:3494 + 0x0D, 0xBD, 0x17, + 0x09, // 190:3517 + 0x0D, 0xD4, 0x14, + 0x09, // 191:3540 + 0x0D, 0xE8, 0x17, + 0x09, // 192:3560 + 0x0D, 0xFF, 0x17, + 0x09, // 193:3583 + 0x0E, 0x16, 0x17, + 0x09, // 194:3606 + 0x0E, 0x2D, 0x17, + 0x09, // 195:3629 + 0x0E, 0x44, 0x17, + 0x09, // 196:3652 + 0x0E, 0x5B, 0x17, + 0x09, // 197:3675 + 0x0E, 0x72, 0x1A, + 0x09, // 198:3698 + 0x0E, 0x8C, 0x17, + 0x09, // 199:3724 + 0x0E, 0xA3, 0x17, + 0x09, // 200:3747 + 0x0E, 0xBA, 0x17, + 0x09, // 201:3770 + 0x0E, 0xD1, 0x17, + 0x09, // 202:3793 + 0x0E, 0xE8, 0x17, + 0x09, // 203:3816 + 0x0E, 0xFF, 0x14, + 0x09, // 204:3839 + 0x0F, 0x13, 0x14, + 0x09, // 205:3859 + 0x0F, 0x27, 0x14, + 0x09, // 206:3879 + 0x0F, 0x3B, 0x14, + 0x09, // 207:3899 + 0x0F, 0x4F, 0x17, + 0x09, // 208:3919 + 0x0F, 0x66, 0x17, + 0x09, // 209:3942 + 0x0F, 0x7D, 0x17, + 0x09, // 210:3965 + 0x0F, 0x94, 0x17, + 0x09, // 211:3988 + 0x0F, 0xAB, 0x17, + 0x09, // 212:4011 + 0x0F, 0xC2, 0x17, + 0x09, // 213:4034 + 0x0F, 0xD9, 0x17, + 0x09, // 214:4057 + 0x0F, 0xF0, 0x17, + 0x09, // 215:4080 + 0x10, 0x07, 0x17, + 0x09, // 216:4103 + 0x10, 0x1E, 0x17, + 0x09, // 217:4126 + 0x10, 0x35, 0x17, + 0x09, // 218:4149 + 0x10, 0x4C, 0x17, + 0x09, // 219:4172 + 0x10, 0x63, 0x17, + 0x09, // 220:4195 + 0x10, 0x7A, 0x19, + 0x09, // 221:4218 + 0x10, 0x93, 0x17, + 0x09, // 222:4243 + 0x10, 0xAA, 0x17, + 0x09, // 223:4266 + 0x10, 0xC1, 0x17, + 0x09, // 224:4289 + 0x10, 0xD8, 0x17, + 0x09, // 225:4312 + 0x10, 0xEF, 0x17, + 0x09, // 226:4335 + 0x11, 0x06, 0x17, + 0x09, // 227:4358 + 0x11, 0x1D, 0x17, + 0x09, // 228:4381 + 0x11, 0x34, 0x17, + 0x09, // 229:4404 + 0x11, 0x4B, 0x17, + 0x09, // 230:4427 + 0x11, 0x62, 0x15, + 0x09, // 231:4450 + 0x11, 0x77, 0x17, + 0x09, // 232:4471 + 0x11, 0x8E, 0x17, + 0x09, // 233:4494 + 0x11, 0xA5, 0x17, + 0x09, // 234:4517 + 0x11, 0xBC, 0x17, + 0x09, // 235:4540 + 0x11, 0xD3, 0x17, + 0x09, // 236:4563 + 0x11, 0xEA, 0x17, + 0x09, // 237:4586 + 0x12, 0x01, 0x17, + 0x09, // 238:4609 + 0x12, 0x18, 0x17, + 0x09, // 239:4632 + 0x12, 0x2F, 0x17, + 0x09, // 240:4655 + 0x12, 0x46, 0x17, + 0x09, // 241:4678 + 0x12, 0x5D, 0x17, + 0x09, // 242:4701 + 0x12, 0x74, 0x17, + 0x09, // 243:4724 + 0x12, 0x8B, 0x17, + 0x09, // 244:4747 + 0x12, 0xA2, 0x17, + 0x09, // 245:4770 + 0x12, 0xB9, 0x17, + 0x09, // 246:4793 + 0x12, 0xD0, 0x1A, + 0x09, // 247:4816 + 0x12, 0xEA, 0x17, + 0x09, // 248:4842 + 0x13, 0x01, 0x17, + 0x09, // 249:4865 + 0x13, 0x18, 0x17, + 0x09, // 250:4888 + 0x13, 0x2F, 0x17, + 0x09, // 251:4911 + 0x13, 0x46, 0x17, + 0x09, // 252:4934 + 0x13, 0x5D, 0x16, + 0x09, // 253:4957 + 0x13, 0x73, 0x17, + 0x09, // 254:4979 + 0x13, 0x8A, 0x16, + 0x09, // 255:5002 + + // Font Data: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, + 0x33, // 33 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x78, // 34 + 0x00, 0x04, 0x00, 0x40, 0x24, 0x00, 0x40, 0x1F, 0x00, 0xF0, 0x05, 0x00, 0x58, 0x34, 0x00, 0x40, 0x1F, 0x00, 0xF0, 0x05, 0x00, + 0x58, 0x04, 0x00, + 0x40, // 35 + 0x00, 0x00, 0x00, 0xE0, 0x10, 0x00, 0x30, 0x21, 0x00, 0x10, 0x21, 0x00, 0xFC, 0xFF, 0x00, 0x10, 0x22, 0x00, 0x10, 0x22, 0x00, + 0x20, + 0x1C, // 36 + 0x70, 0x00, 0x00, 0x88, 0x02, 0x00, 0x88, 0x02, 0x00, 0x88, 0x01, 0x00, 0x70, 0x1D, 0x00, 0x00, 0x23, 0x00, 0x80, 0x22, 0x00, + 0x80, 0x22, 0x00, 0x00, + 0x1C, // 37 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xF0, 0x11, 0x00, 0xC8, 0x20, 0x00, 0x88, 0x21, 0x00, 0x08, 0x26, 0x00, 0x08, 0x1C, 0x00, + 0x00, + 0x27, // 38 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x78, // 39 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x30, 0x60, 0x00, 0x08, + 0x80, // 40 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x30, 0x60, 0x00, 0xC0, + 0x1F, // 41 + 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x60, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x60, 0x00, 0x00, 0xA0, 0x00, 0x00, + 0x90, // 42 + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, + 0x02, // 43 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x30, // 44 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x02, // 45 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, + 0x30, // 46 + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x00, 0x00, + 0x08, // 47 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x10, 0x10, 0x00, 0x08, 0x20, 0x00, 0x88, 0x21, 0x00, 0x88, 0x21, 0x00, 0x10, 0x10, 0x00, + 0xE0, + 0x0F, // 48 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, 0x08, 0x20, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x20, // 49 + 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x18, 0x30, 0x00, 0x08, 0x28, 0x00, 0x08, 0x24, 0x00, 0x08, 0x22, 0x00, 0x10, 0x21, 0x00, + 0xF0, + 0x20, // 50 + 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x08, 0x20, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x90, 0x32, 0x00, + 0xF0, + 0x1E, // 51 + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x80, 0x05, 0x00, 0xC0, 0x04, 0x00, 0x30, 0x04, 0x00, 0x18, 0x04, 0x00, 0xF8, 0x3F, 0x00, + 0x00, + 0x04, // 52 + 0x00, 0x00, 0x00, 0xF8, 0x11, 0x00, 0x88, 0x20, 0x00, 0x88, 0x20, 0x00, 0x88, 0x20, 0x00, 0x88, 0x20, 0x00, 0x08, 0x11, 0x00, + 0x00, + 0x0E, // 53 + 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x30, 0x11, 0x00, 0x98, 0x20, 0x00, 0x88, 0x20, 0x00, 0x88, 0x20, 0x00, 0x88, 0x31, 0x00, + 0x10, + 0x1F, // 54 + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x20, 0x00, 0x08, 0x18, 0x00, 0x08, 0x06, 0x00, 0x88, 0x01, 0x00, 0x78, 0x00, 0x00, + 0x18, // 55 + 0x00, 0x00, 0x00, 0xF0, 0x1E, 0x00, 0x98, 0x32, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x98, 0x32, 0x00, + 0xF0, + 0x1E, // 56 + 0x00, 0x00, 0x00, 0xF0, 0x11, 0x00, 0x18, 0x23, 0x00, 0x08, 0x22, 0x00, 0x08, 0x22, 0x00, 0x08, 0x32, 0x00, 0x10, 0x19, 0x00, + 0xE0, + 0x07, // 57 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x30, 0x00, 0xC0, + 0x30, // 58 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x70, 0x00, 0xC0, + 0x30, // 59 + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x07, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, + 0x80, 0x08, 0x00, 0x40, + 0x10, // 60 + 0x00, 0x00, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, + 0x80, 0x04, 0x00, 0x80, + 0x04, // 61 + 0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x00, + 0x00, 0x07, 0x00, 0x00, + 0x02, // 62 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x37, 0x00, 0x08, 0x01, 0x00, 0x88, 0x00, 0x00, + 0x70, // 63 + 0x00, 0x00, 0x00, 0x80, 0x1F, 0x00, 0x60, 0x60, 0x00, 0x30, 0x40, 0x00, 0x10, 0x8F, 0x00, 0x90, 0x90, 0x00, 0xB0, 0x90, 0x00, + 0xE0, + 0x1F, // 64 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0F, 0x00, 0xF0, 0x04, 0x00, 0x08, 0x04, 0x00, 0xF0, 0x04, 0x00, 0x00, 0x0F, 0x00, + 0x00, + 0x30, // 65 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x98, 0x33, 0x00, + 0xF0, + 0x1E, // 66 + 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x30, 0x18, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, + 0x10, + 0x10, // 67 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x18, 0x30, 0x00, 0x30, 0x18, 0x00, + 0xC0, + 0x07, // 68 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, + 0x08, + 0x21, // 69 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, + 0x08, + 0x01, // 70 + 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x30, 0x18, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, + 0x10, + 0x1F, // 71 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, + 0xF8, + 0x3F, // 72 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x20, 0x00, 0x08, + 0x20, // 73 + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x30, 0x00, 0xF8, + 0x1F, // 74 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x40, 0x03, 0x00, 0x20, 0x04, 0x00, 0x10, 0x18, 0x00, + 0x08, + 0x20, // 75 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, + 0x00, + 0x20, // 76 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x38, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x02, 0x00, 0xC0, 0x01, 0x00, 0x38, 0x00, 0x00, + 0xF8, + 0x3F, // 77 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x18, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30, 0x00, + 0xF8, + 0x3F, // 78 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x10, 0x10, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x10, 0x10, 0x00, + 0xE0, + 0x0F, // 79 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x10, 0x03, 0x00, + 0xF0, + 0x01, // 80 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x10, 0x10, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x60, 0x00, 0x10, 0xF0, 0x00, + 0xE0, + 0x0F, // 81 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x98, 0x02, 0x00, + 0xF0, 0x1C, 0x00, 0x00, + 0x20, // 82 + 0x00, 0x00, 0x00, 0xF0, 0x10, 0x00, 0x90, 0x30, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x18, 0x32, 0x00, + 0x10, + 0x1E, // 83 + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x08, // 84 + 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x30, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x30, 0x00, + 0xF8, + 0x1F, // 85 + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x20, 0x00, 0x00, 0x1E, 0x00, 0xE0, 0x01, 0x00, + 0x18, // 86 + 0x38, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x30, 0x00, 0x80, 0x0F, 0x00, 0x40, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x30, 0x00, + 0xC0, 0x0F, 0x00, + 0x38, // 87 + 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x30, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x00, 0x01, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x18, 0x00, + 0x08, + 0x20, // 88 + 0x08, 0x00, 0x00, 0x18, 0x00, 0x00, 0x60, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x80, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x18, 0x00, 0x00, + 0x08, // 89 + 0x00, 0x00, 0x00, 0x08, 0x30, 0x00, 0x08, 0x28, 0x00, 0x08, 0x26, 0x00, 0x08, 0x21, 0x00, 0xC8, 0x20, 0x00, 0x28, 0x20, 0x00, + 0x18, + 0x20, // 90 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, + 0x80, // 91 + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x30, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30, 0x00, + 0x00, + 0x40, // 92 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 93 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x00, + 0x60, 0x00, 0x00, + 0x40, // 94 + 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x02, 0x00, 0x00, + 0x02, // 95 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x18, 0x00, 0x00, + 0x10, // 96 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x26, 0x00, 0x40, 0x22, 0x00, 0x40, 0x22, 0x00, 0x40, 0x22, 0x00, 0x40, 0x12, 0x00, + 0x80, + 0x3F, // 97 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x80, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x80, 0x10, 0x00, + 0x00, + 0x0F, // 98 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x80, + 0x10, // 99 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x80, 0x10, 0x00, + 0xF8, + 0x3F, // 100 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x14, 0x00, 0x40, 0x24, 0x00, 0x40, 0x24, 0x00, 0x40, 0x24, 0x00, 0x80, 0x24, 0x00, + 0x00, + 0x17, // 101 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x48, 0x00, 0x00, 0x48, 0x00, 0x00, + 0x48, // 102 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x90, 0x00, 0x40, 0x20, 0x01, 0x40, 0x20, 0x01, 0x40, 0x20, 0x01, 0x80, 0x90, 0x01, + 0xC0, + 0xFF, // 103 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x00, 0x00, + 0x80, + 0x3F, // 104 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0xD8, 0x3F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, + 0x00, + 0x20, // 105 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x01, 0x40, 0x00, 0x01, 0xD8, + 0xFF, // 106 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x80, 0x04, 0x00, 0x80, 0x08, 0x00, 0x40, 0x10, 0x00, + 0x00, + 0x20, // 107 + 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x20, // 108 + 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, + 0x80, + 0x3F, // 109 + 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x00, 0x00, + 0x80, + 0x3F, // 110 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x80, 0x10, 0x00, + 0x00, + 0x0F, // 111 + 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x01, 0x80, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x80, 0x10, 0x00, + 0x00, + 0x0F, // 112 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x80, 0x10, 0x00, + 0xC0, 0xFF, + 0x01, // 113 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, + 0x40, 0x00, 0x00, + 0x80, // 114 + 0x00, 0x00, 0x00, 0x80, 0x13, 0x00, 0x40, 0x22, 0x00, 0x40, 0x22, 0x00, 0x40, 0x22, 0x00, 0x40, 0x24, 0x00, 0x40, 0x24, 0x00, + 0x80, + 0x1C, // 115 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xF0, 0x1F, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, + 0x20, // 116 + 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x30, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, + 0xC0, + 0x3F, // 117 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x20, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x03, 0x00, + 0x40, // 118 + 0xC0, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x03, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x0F, 0x00, + 0xC0, // 119 + 0x00, 0x00, 0x00, 0x40, 0x20, 0x00, 0xC0, 0x30, 0x00, 0x00, 0x09, 0x00, 0x00, 0x06, 0x00, 0x00, 0x09, 0x00, 0xC0, 0x30, 0x00, + 0x40, + 0x20, // 120 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x03, 0x01, 0x00, 0x0C, 0x01, 0x00, 0xF0, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x03, 0x00, + 0x40, // 121 + 0x00, 0x00, 0x00, 0x40, 0x30, 0x00, 0x40, 0x28, 0x00, 0x40, 0x24, 0x00, 0x40, 0x24, 0x00, 0x40, 0x22, 0x00, 0x40, 0x21, 0x00, + 0xC0, + 0x20, // 122 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0xF0, 0xFD, 0x00, 0x08, 0x00, 0x01, 0x08, 0x00, + 0x01, // 123 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, + 0x03, // 124 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x08, 0x00, 0x01, 0xF0, 0xFD, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x02, // 125 + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x01, // 126 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 127 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 128 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 129 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 130 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 131 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 132 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 133 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 134 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 135 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 136 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 137 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 138 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 139 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 140 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 141 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 142 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 143 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 144 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 145 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 146 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 147 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 148 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 149 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 150 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 151 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 152 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 153 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 154 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 155 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 156 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 157 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 158 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 159 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFC, + 0x01, // 161 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x10, 0x00, 0x40, 0x20, 0x00, 0xF0, 0xFF, 0x00, 0x40, 0x20, 0x00, 0x80, + 0x10, // 162 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x22, 0x00, 0xF0, 0x3F, 0x00, 0x18, 0x22, 0x00, 0x08, 0x22, 0x00, 0x08, 0x22, 0x00, + 0x10, + 0x20, // 163 + 0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80, 0x0F, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x0F, 0x00, + 0x40, + 0x10, // 164 + 0x08, 0x00, 0x00, 0x98, 0x02, 0x00, 0xE0, 0x02, 0x00, 0xC0, 0x02, 0x00, 0x00, 0x3F, 0x00, 0xC0, 0x02, 0x00, 0xA0, 0x02, 0x00, + 0x98, 0x02, 0x00, + 0x08, // 165 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF9, + 0x01, // 166 + 0x00, 0x00, 0x00, 0xB0, 0x43, 0x00, 0x68, 0x46, 0x00, 0x48, 0x4C, 0x00, 0xC8, 0x4C, 0x00, 0x88, 0x49, 0x00, 0x08, + 0x37, // 167 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, + 0x18, // 168 + 0xC0, 0x07, 0x00, 0x60, 0x0C, 0x00, 0xB0, 0x1B, 0x00, 0x50, 0x14, 0x00, 0x50, 0x14, 0x00, 0x50, 0x14, 0x00, 0x30, 0x18, 0x00, + 0x60, 0x0C, 0x00, 0xC0, + 0x07, // 169 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC8, 0x04, 0x00, 0x28, 0x05, 0x00, 0x28, 0x05, 0x00, 0xA8, 0x04, 0x00, 0xF0, + 0x05, // 170 + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x05, 0x00, 0x80, 0x08, 0x00, 0x00, 0x02, 0x00, 0x00, 0x05, 0x00, 0x80, + 0x08, // 171 + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x0F, // 172 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x02, // 173 + 0xC0, 0x07, 0x00, 0x60, 0x0C, 0x00, 0x30, 0x18, 0x00, 0xD0, 0x17, 0x00, 0x50, 0x13, 0x00, 0xD0, 0x14, 0x00, 0x30, 0x18, 0x00, + 0x60, 0x0C, 0x00, 0xC0, + 0x07, // 174 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x08, // 175 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x88, 0x00, 0x00, 0x88, 0x00, 0x00, 0x88, 0x00, 0x00, + 0x70, // 176 + 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x21, 0x00, 0x00, 0x21, 0x00, 0xC0, 0x27, 0x00, 0x00, 0x21, 0x00, 0x00, 0x21, 0x00, + 0x00, + 0x21, // 177 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x88, 0x01, 0x00, 0x48, 0x01, 0x00, 0x30, + 0x01, // 178 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x28, 0x01, 0x00, 0x28, 0x01, 0x00, + 0xD8, // 179 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00, 0x0C, 0x00, 0x00, + 0x04, // 180 + 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x01, 0x00, 0x30, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x30, 0x00, + 0xC0, 0x3F, 0x00, 0x00, + 0x20, // 181 + 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF8, 0x01, 0x00, 0xF8, 0x01, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x00, 0x00, + 0xF8, + 0x7F, // 182 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, // 183 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0xC0, + 0x01, // 184 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x01, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x01, // 185 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x04, 0x00, 0x08, 0x05, 0x00, 0x08, 0x05, 0x00, 0x08, 0x05, 0x00, 0xF0, + 0x04, // 186 + 0x00, 0x00, 0x00, 0x80, 0x08, 0x00, 0x00, 0x05, 0x00, 0x00, 0x02, 0x00, 0x80, 0x08, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x02, // 187 + 0x80, 0x04, 0x00, 0x84, 0x04, 0x00, 0xFC, 0x02, 0x00, 0x80, 0x62, 0x00, 0x80, 0x52, 0x00, 0x00, 0x4E, 0x00, 0x00, 0xFD, 0x00, + 0x00, + 0x41, // 188 + 0x80, 0x04, 0x00, 0x84, 0x04, 0x00, 0xFC, 0x02, 0x00, 0x80, 0x86, 0x00, 0x80, 0xC6, 0x00, 0x00, 0xA6, 0x00, 0x00, 0x99, 0x00, + 0x00, + 0x01, // 189 + 0x84, 0x04, 0x00, 0x94, 0x04, 0x00, 0x94, 0x02, 0x00, 0x6C, 0x62, 0x00, 0x00, 0x52, 0x00, 0x00, 0x4E, 0x00, 0x00, 0xFD, 0x00, + 0x00, + 0x41, // 190 + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x10, 0x01, 0x00, 0x08, 0x01, 0xC0, 0x0E, 0x01, 0x00, 0x00, 0x01, 0x00, + 0x80, // 191 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0F, 0x00, 0xF1, 0x04, 0x00, 0x0B, 0x04, 0x00, 0xF2, 0x04, 0x00, 0x00, 0x0F, 0x00, + 0x00, + 0x30, // 192 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0F, 0x00, 0xF2, 0x04, 0x00, 0x0B, 0x04, 0x00, 0xF1, 0x04, 0x00, 0x00, 0x0F, 0x00, + 0x00, + 0x30, // 193 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0F, 0x00, 0xF2, 0x04, 0x00, 0x09, 0x04, 0x00, 0xF2, 0x04, 0x00, 0x00, 0x0F, 0x00, + 0x00, + 0x30, // 194 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x03, 0x0F, 0x00, 0xF1, 0x04, 0x00, 0x0B, 0x04, 0x00, 0xF2, 0x04, 0x00, 0x03, 0x0F, 0x00, + 0x00, + 0x30, // 195 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x03, 0x0F, 0x00, 0xF3, 0x04, 0x00, 0x08, 0x04, 0x00, 0xF3, 0x04, 0x00, 0x03, 0x0F, 0x00, + 0x00, + 0x30, // 196 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0E, 0x00, 0xE7, 0x05, 0x00, 0x19, 0x04, 0x00, 0xE7, 0x05, 0x00, 0x00, 0x0E, 0x00, + 0x00, + 0x30, // 197 + 0x00, 0x30, 0x00, 0x00, 0x0E, 0x00, 0xE0, 0x05, 0x00, 0x38, 0x04, 0x00, 0x08, 0x04, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x21, 0x00, + 0x08, 0x21, 0x00, 0x08, + 0x21, // 198 + 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x30, 0x18, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x01, 0x08, 0x20, 0x01, 0x08, 0xE0, 0x01, + 0x10, + 0x10, // 199 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x21, 0x00, 0x09, 0x21, 0x00, 0x0B, 0x21, 0x00, 0x0A, 0x21, 0x00, 0x08, 0x21, 0x00, + 0x08, + 0x21, // 200 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x21, 0x00, 0x0A, 0x21, 0x00, 0x0B, 0x21, 0x00, 0x09, 0x21, 0x00, 0x08, 0x21, 0x00, + 0x08, + 0x21, // 201 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x0A, 0x21, 0x00, 0x09, 0x21, 0x00, 0x09, 0x21, 0x00, 0x0A, 0x21, 0x00, 0x08, 0x21, 0x00, + 0x08, + 0x21, // 202 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x0B, 0x21, 0x00, 0x0B, 0x21, 0x00, 0x08, 0x21, 0x00, 0x0B, 0x21, 0x00, 0x0B, 0x21, 0x00, + 0x08, + 0x21, // 203 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x09, 0x20, 0x00, 0xFB, 0x3F, 0x00, 0x0A, 0x20, 0x00, 0x08, + 0x20, // 204 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x0A, 0x20, 0x00, 0xFB, 0x3F, 0x00, 0x09, 0x20, 0x00, 0x08, + 0x20, // 205 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x0A, 0x20, 0x00, 0xF9, 0x3F, 0x00, 0x0A, 0x20, 0x00, 0x08, + 0x20, // 206 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x20, 0x00, 0x0B, 0x20, 0x00, 0xF8, 0x3F, 0x00, 0x0B, 0x20, 0x00, 0x0B, + 0x20, // 207 + 0x00, 0x01, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x20, 0x00, 0x18, 0x30, 0x00, 0x30, 0x18, 0x00, + 0xC0, + 0x07, // 208 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x1B, 0x00, 0x00, 0xE1, 0x00, 0x00, 0x83, 0x03, 0x00, 0x02, 0x0C, 0x00, 0x03, 0x30, 0x00, + 0xF8, + 0x3F, // 209 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x10, 0x10, 0x00, 0x09, 0x20, 0x00, 0x0B, 0x20, 0x00, 0x0A, 0x20, 0x00, 0x10, 0x10, 0x00, + 0xE0, + 0x0F, // 210 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x10, 0x10, 0x00, 0x0A, 0x20, 0x00, 0x0B, 0x20, 0x00, 0x09, 0x20, 0x00, 0x10, 0x10, 0x00, + 0xE0, + 0x0F, // 211 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x10, 0x10, 0x00, 0x0A, 0x20, 0x00, 0x09, 0x20, 0x00, 0x0A, 0x20, 0x00, 0x10, 0x10, 0x00, + 0xE0, + 0x0F, // 212 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x13, 0x10, 0x00, 0x09, 0x20, 0x00, 0x0B, 0x20, 0x00, 0x0A, 0x20, 0x00, 0x13, 0x10, 0x00, + 0xE0, + 0x0F, // 213 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x13, 0x10, 0x00, 0x0B, 0x20, 0x00, 0x08, 0x20, 0x00, 0x0B, 0x20, 0x00, 0x13, 0x10, 0x00, + 0xE0, + 0x0F, // 214 + 0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80, 0x08, 0x00, 0x00, 0x05, 0x00, 0x00, 0x02, 0x00, 0x00, 0x05, 0x00, 0x80, 0x08, 0x00, + 0x40, + 0x10, // 215 + 0x00, 0x20, 0x00, 0xE0, 0x1F, 0x00, 0x10, 0x3C, 0x00, 0x08, 0x22, 0x00, 0x08, 0x21, 0x00, 0xC8, 0x20, 0x00, 0x78, 0x10, 0x00, + 0xF8, + 0x0F, // 216 + 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x30, 0x00, 0x01, 0x20, 0x00, 0x03, 0x20, 0x00, 0x02, 0x20, 0x00, 0x00, 0x30, 0x00, + 0xF8, + 0x1F, // 217 + 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x30, 0x00, 0x02, 0x20, 0x00, 0x03, 0x20, 0x00, 0x01, 0x20, 0x00, 0x00, 0x30, 0x00, + 0xF8, + 0x1F, // 218 + 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x30, 0x00, 0x02, 0x20, 0x00, 0x01, 0x20, 0x00, 0x02, 0x20, 0x00, 0x00, 0x30, 0x00, + 0xF8, + 0x1F, // 219 + 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x03, 0x30, 0x00, 0x03, 0x20, 0x00, 0x00, 0x20, 0x00, 0x03, 0x20, 0x00, 0x03, 0x30, 0x00, + 0xF8, + 0x1F, // 220 + 0x08, 0x00, 0x00, 0x18, 0x00, 0x00, 0x60, 0x00, 0x00, 0xC2, 0x00, 0x00, 0x03, 0x3F, 0x00, 0x81, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x18, 0x00, 0x00, + 0x08, // 221 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x20, 0x04, 0x00, 0x60, 0x06, 0x00, + 0xC0, + 0x03, // 222 + 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x08, 0x00, 0x00, 0x88, 0x23, 0x00, 0x48, 0x22, 0x00, 0x48, 0x26, 0x00, 0x30, 0x26, 0x00, + 0x00, + 0x1C, // 223 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x84, 0x26, 0x00, 0x4C, 0x22, 0x00, 0x58, 0x22, 0x00, 0x50, 0x22, 0x00, 0x40, 0x12, 0x00, + 0x80, + 0x3F, // 224 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x26, 0x00, 0x50, 0x22, 0x00, 0x58, 0x22, 0x00, 0x4C, 0x22, 0x00, 0x44, 0x12, 0x00, + 0x80, + 0x3F, // 225 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x90, 0x26, 0x00, 0x48, 0x22, 0x00, 0x44, 0x22, 0x00, 0x48, 0x22, 0x00, 0x50, 0x12, 0x00, + 0x80, + 0x3F, // 226 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x98, 0x26, 0x00, 0x48, 0x22, 0x00, 0x58, 0x22, 0x00, 0x50, 0x22, 0x00, 0x58, 0x12, 0x00, + 0x80, + 0x3F, // 227 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x98, 0x26, 0x00, 0x58, 0x22, 0x00, 0x40, 0x22, 0x00, 0x58, 0x22, 0x00, 0x58, 0x12, 0x00, + 0x80, + 0x3F, // 228 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x26, 0x00, 0x4C, 0x22, 0x00, 0x52, 0x22, 0x00, 0x52, 0x22, 0x00, 0x4C, 0x12, 0x00, + 0x80, + 0x3F, // 229 + 0x00, 0x00, 0x00, 0x80, 0x38, 0x00, 0x40, 0x24, 0x00, 0x40, 0x24, 0x00, 0x80, 0x1F, 0x00, 0x40, 0x24, 0x00, 0x40, 0x24, 0x00, + 0x80, + 0x27, // 230 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x01, 0x40, 0x20, 0x01, 0x80, 0xD0, + 0x01, // 231 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x84, 0x14, 0x00, 0x4C, 0x24, 0x00, 0x58, 0x24, 0x00, 0x50, 0x24, 0x00, 0x80, 0x24, 0x00, + 0x00, + 0x17, // 232 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x14, 0x00, 0x50, 0x24, 0x00, 0x58, 0x24, 0x00, 0x4C, 0x24, 0x00, 0x84, 0x24, 0x00, + 0x00, + 0x17, // 233 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x90, 0x14, 0x00, 0x48, 0x24, 0x00, 0x44, 0x24, 0x00, 0x48, 0x24, 0x00, 0x90, 0x24, 0x00, + 0x00, + 0x17, // 234 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x98, 0x14, 0x00, 0x58, 0x24, 0x00, 0x40, 0x24, 0x00, 0x58, 0x24, 0x00, 0x98, 0x24, 0x00, + 0x00, + 0x17, // 235 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x44, 0x20, 0x00, 0x4C, 0x20, 0x00, 0xD8, 0x3F, 0x00, 0x10, 0x20, 0x00, 0x00, 0x20, 0x00, + 0x00, + 0x20, // 236 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x20, 0x00, 0x50, 0x20, 0x00, 0xD8, 0x3F, 0x00, 0x0C, 0x20, 0x00, 0x04, 0x20, 0x00, + 0x00, + 0x20, // 237 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x20, 0x00, 0x48, 0x20, 0x00, 0xC4, 0x3F, 0x00, 0x08, 0x20, 0x00, 0x10, 0x20, 0x00, + 0x00, + 0x20, // 238 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x58, 0x20, 0x00, 0x58, 0x20, 0x00, 0xC0, 0x3F, 0x00, 0x18, 0x20, 0x00, 0x18, 0x20, 0x00, + 0x00, + 0x20, // 239 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xA0, 0x10, 0x00, 0x58, 0x20, 0x00, 0x50, 0x20, 0x00, 0x70, 0x20, 0x00, 0xD0, 0x10, 0x00, + 0x00, + 0x0F, // 240 + 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0x98, 0x00, 0x00, 0x48, 0x00, 0x00, 0x58, 0x00, 0x00, 0x50, 0x00, 0x00, 0xD8, 0x00, 0x00, + 0x80, + 0x3F, // 241 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x84, 0x10, 0x00, 0x4C, 0x20, 0x00, 0x58, 0x20, 0x00, 0x50, 0x20, 0x00, 0x80, 0x10, 0x00, + 0x00, + 0x0F, // 242 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x10, 0x00, 0x50, 0x20, 0x00, 0x58, 0x20, 0x00, 0x4C, 0x20, 0x00, 0x84, 0x10, 0x00, + 0x00, + 0x0F, // 243 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x90, 0x10, 0x00, 0x48, 0x20, 0x00, 0x44, 0x20, 0x00, 0x48, 0x20, 0x00, 0x90, 0x10, 0x00, + 0x00, + 0x0F, // 244 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x98, 0x10, 0x00, 0x48, 0x20, 0x00, 0x58, 0x20, 0x00, 0x50, 0x20, 0x00, 0x98, 0x10, 0x00, + 0x00, + 0x0F, // 245 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x98, 0x10, 0x00, 0x58, 0x20, 0x00, 0x40, 0x20, 0x00, 0x58, 0x20, 0x00, 0x98, 0x10, 0x00, + 0x00, + 0x0F, // 246 + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0xC0, 0x1A, 0x00, 0xC0, 0x1A, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x00, 0x00, + 0x02, // 247 + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x80, 0x38, 0x00, 0x40, 0x2C, 0x00, 0x40, 0x26, 0x00, 0x40, 0x23, 0x00, 0xC0, 0x11, 0x00, + 0xC0, + 0x0F, // 248 + 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x04, 0x30, 0x00, 0x0C, 0x20, 0x00, 0x18, 0x20, 0x00, 0x10, 0x20, 0x00, 0x00, 0x10, 0x00, + 0xC0, + 0x3F, // 249 + 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x30, 0x00, 0x10, 0x20, 0x00, 0x18, 0x20, 0x00, 0x0C, 0x20, 0x00, 0x04, 0x10, 0x00, + 0xC0, + 0x3F, // 250 + 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x10, 0x30, 0x00, 0x08, 0x20, 0x00, 0x04, 0x20, 0x00, 0x08, 0x20, 0x00, 0x10, 0x10, 0x00, + 0xC0, + 0x3F, // 251 + 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x18, 0x30, 0x00, 0x18, 0x20, 0x00, 0x00, 0x20, 0x00, 0x18, 0x20, 0x00, 0x18, 0x10, 0x00, + 0xC0, + 0x3F, // 252 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x03, 0x01, 0x10, 0x0C, 0x01, 0x18, 0xF0, 0x00, 0x0C, 0x1C, 0x00, 0x84, 0x03, 0x00, + 0x40, // 253 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x01, 0x80, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x80, 0x10, 0x00, + 0x00, + 0x0F, // 254 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x98, 0x03, 0x01, 0x18, 0x0C, 0x01, 0x00, 0xF0, 0x00, 0x18, 0x1C, 0x00, 0x98, 0x03, 0x00, + 0x40 // 255 + }; +// Created by http://oleddisplay.squix.ch/ Consider a donation +// In case of problems make sure that you are using the font file with the correct version! +uint8_t DialogInput_bold_12[] PROGMEM = + {0x07, // Width: 7 + 0x0F, // Height: 15 + 0x20, // First Char: 32 + 0xE0, // Numbers of Chars: 224 + + // Jump Table: + 0xFF, 0xFF, 0x00, 0x07, // 32:65535 + 0x00, 0x00, 0x0A, 0x07, // 33:0 + 0x00, 0x0A, 0x0D, 0x07, // 34:10 + 0x00, 0x17, 0x0D, 0x07, // 35:23 + 0x00, 0x24, 0x0E, 0x07, // 36:36 + 0x00, 0x32, 0x0E, 0x07, // 37:50 + 0x00, 0x40, 0x0E, 0x07, // 38:64 + 0x00, 0x4E, 0x09, 0x07, // 39:78 + 0x00, 0x57, 0x0A, 0x07, // 40:87 + 0x00, 0x61, 0x0A, 0x07, // 41:97 + 0x00, 0x6B, 0x0B, 0x07, // 42:107 + 0x00, 0x76, 0x0E, 0x07, // 43:118 + 0x00, 0x84, 0x0A, 0x07, // 44:132 + 0x00, 0x8E, 0x0C, 0x07, // 45:142 + 0x00, 0x9A, 0x0A, 0x07, // 46:154 + 0x00, 0xA4, 0x0D, 0x07, // 47:164 + 0x00, 0xB1, 0x0E, 0x07, // 48:177 + 0x00, 0xBF, 0x0E, 0x07, // 49:191 + 0x00, 0xCD, 0x0E, 0x07, // 50:205 + 0x00, 0xDB, 0x0E, 0x07, // 51:219 + 0x00, 0xE9, 0x0E, 0x07, // 52:233 + 0x00, 0xF7, 0x0E, 0x07, // 53:247 + 0x01, 0x05, 0x0E, 0x07, // 54:261 + 0x01, 0x13, 0x0D, 0x07, // 55:275 + 0x01, 0x20, 0x0E, 0x07, // 56:288 + 0x01, 0x2E, 0x0E, 0x07, // 57:302 + 0x01, 0x3C, 0x0A, 0x07, // 58:316 + 0x01, 0x46, 0x0A, 0x07, // 59:326 + 0x01, 0x50, 0x0E, 0x07, // 60:336 + 0x01, 0x5E, 0x0E, 0x07, // 61:350 + 0x01, 0x6C, 0x0E, 0x07, // 62:364 + 0x01, 0x7A, 0x0D, 0x07, // 63:378 + 0x01, 0x87, 0x0E, 0x07, // 64:391 + 0x01, 0x95, 0x0E, 0x07, // 65:405 + 0x01, 0xA3, 0x0E, 0x07, // 66:419 + 0x01, 0xB1, 0x0E, 0x07, // 67:433 + 0x01, 0xBF, 0x0E, 0x07, // 68:447 + 0x01, 0xCD, 0x0E, 0x07, // 69:461 + 0x01, 0xDB, 0x0D, 0x07, // 70:475 + 0x01, 0xE8, 0x0E, 0x07, // 71:488 + 0x01, 0xF6, 0x0E, 0x07, // 72:502 + 0x02, 0x04, 0x0E, 0x07, // 73:516 + 0x02, 0x12, 0x0E, 0x07, // 74:530 + 0x02, 0x20, 0x0E, 0x07, // 75:544 + 0x02, 0x2E, 0x0E, 0x07, // 76:558 + 0x02, 0x3C, 0x0E, 0x07, // 77:572 + 0x02, 0x4A, 0x0E, 0x07, // 78:586 + 0x02, 0x58, 0x0E, 0x07, // 79:600 + 0x02, 0x66, 0x0D, 0x07, // 80:614 + 0x02, 0x73, 0x0E, 0x07, // 81:627 + 0x02, 0x81, 0x0E, 0x07, // 82:641 + 0x02, 0x8F, 0x0E, 0x07, // 83:655 + 0x02, 0x9D, 0x0D, 0x07, // 84:669 + 0x02, 0xAA, 0x0E, 0x07, // 85:682 + 0x02, 0xB8, 0x0D, 0x07, // 86:696 + 0x02, 0xC5, 0x0D, 0x07, // 87:709 + 0x02, 0xD2, 0x0E, 0x07, // 88:722 + 0x02, 0xE0, 0x0D, 0x07, // 89:736 + 0x02, 0xED, 0x0E, 0x07, // 90:749 + 0x02, 0xFB, 0x0A, 0x07, // 91:763 + 0x03, 0x05, 0x0E, 0x07, // 92:773 + 0x03, 0x13, 0x0A, 0x07, // 93:787 + 0x03, 0x1D, 0x0D, 0x07, // 94:797 + 0x03, 0x2A, 0x0E, 0x07, // 95:810 + 0x03, 0x38, 0x07, 0x07, // 96:824 + 0x03, 0x3F, 0x0E, 0x07, // 97:831 + 0x03, 0x4D, 0x0E, 0x07, // 98:845 + 0x03, 0x5B, 0x0E, 0x07, // 99:859 + 0x03, 0x69, 0x0E, 0x07, // 100:873 + 0x03, 0x77, 0x0E, 0x07, // 101:887 + 0x03, 0x85, 0x0D, 0x07, // 102:901 + 0x03, 0x92, 0x0E, 0x07, // 103:914 + 0x03, 0xA0, 0x0E, 0x07, // 104:928 + 0x03, 0xAE, 0x0E, 0x07, // 105:942 + 0x03, 0xBC, 0x0A, 0x07, // 106:956 + 0x03, 0xC6, 0x0E, 0x07, // 107:966 + 0x03, 0xD4, 0x0C, 0x07, // 108:980 + 0x03, 0xE0, 0x0E, 0x07, // 109:992 + 0x03, 0xEE, 0x0E, 0x07, // 110:1006 + 0x03, 0xFC, 0x0E, 0x07, // 111:1020 + 0x04, 0x0A, 0x0E, 0x07, // 112:1034 + 0x04, 0x18, 0x0E, 0x07, // 113:1048 + 0x04, 0x26, 0x0D, 0x07, // 114:1062 + 0x04, 0x33, 0x0E, 0x07, // 115:1075 + 0x04, 0x41, 0x0C, 0x07, // 116:1089 + 0x04, 0x4D, 0x0E, 0x07, // 117:1101 + 0x04, 0x5B, 0x0D, 0x07, // 118:1115 + 0x04, 0x68, 0x0D, 0x07, // 119:1128 + 0x04, 0x75, 0x0E, 0x07, // 120:1141 + 0x04, 0x83, 0x0D, 0x07, // 121:1155 + 0x04, 0x90, 0x0E, 0x07, // 122:1168 + 0x04, 0x9E, 0x0E, 0x07, // 123:1182 + 0x04, 0xAC, 0x08, 0x07, // 124:1196 + 0x04, 0xB4, 0x0D, 0x07, // 125:1204 + 0x04, 0xC1, 0x0E, 0x07, // 126:1217 + 0x04, 0xCF, 0x0E, 0x07, // 127:1231 + 0x04, 0xDD, 0x0E, 0x07, // 128:1245 + 0x04, 0xEB, 0x0E, 0x07, // 129:1259 + 0x04, 0xF9, 0x0E, 0x07, // 130:1273 + 0x05, 0x07, 0x0E, 0x07, // 131:1287 + 0x05, 0x15, 0x0E, 0x07, // 132:1301 + 0x05, 0x23, 0x0E, 0x07, // 133:1315 + 0x05, 0x31, 0x0E, 0x07, // 134:1329 + 0x05, 0x3F, 0x0E, 0x07, // 135:1343 + 0x05, 0x4D, 0x0E, 0x07, // 136:1357 + 0x05, 0x5B, 0x0E, 0x07, // 137:1371 + 0x05, 0x69, 0x0E, 0x07, // 138:1385 + 0x05, 0x77, 0x0E, 0x07, // 139:1399 + 0x05, 0x85, 0x0E, 0x07, // 140:1413 + 0x05, 0x93, 0x0E, 0x07, // 141:1427 + 0x05, 0xA1, 0x0E, 0x07, // 142:1441 + 0x05, 0xAF, 0x0E, 0x07, // 143:1455 + 0x05, 0xBD, 0x0E, 0x07, // 144:1469 + 0x05, 0xCB, 0x0E, 0x07, // 145:1483 + 0x05, 0xD9, 0x0E, 0x07, // 146:1497 + 0x05, 0xE7, 0x0E, 0x07, // 147:1511 + 0x05, 0xF5, 0x0E, 0x07, // 148:1525 + 0x06, 0x03, 0x0E, 0x07, // 149:1539 + 0x06, 0x11, 0x0E, 0x07, // 150:1553 + 0x06, 0x1F, 0x0E, 0x07, // 151:1567 + 0x06, 0x2D, 0x0E, 0x07, // 152:1581 + 0x06, 0x3B, 0x0E, 0x07, // 153:1595 + 0x06, 0x49, 0x0E, 0x07, // 154:1609 + 0x06, 0x57, 0x0E, 0x07, // 155:1623 + 0x06, 0x65, 0x0E, 0x07, // 156:1637 + 0x06, 0x73, 0x0E, 0x07, // 157:1651 + 0x06, 0x81, 0x0E, 0x07, // 158:1665 + 0x06, 0x8F, 0x0E, 0x07, // 159:1679 + 0xFF, 0xFF, 0x00, 0x07, // 160:65535 + 0x06, 0x9D, 0x0A, 0x07, // 161:1693 + 0x06, 0xA7, 0x0E, 0x07, // 162:1703 + 0x06, 0xB5, 0x0E, 0x07, // 163:1717 + 0x06, 0xC3, 0x0E, 0x07, // 164:1731 + 0x06, 0xD1, 0x0E, 0x07, // 165:1745 + 0x06, 0xDF, 0x08, 0x07, // 166:1759 + 0x06, 0xE7, 0x0C, 0x07, // 167:1767 + 0x06, 0xF3, 0x09, 0x07, // 168:1779 + 0x06, 0xFC, 0x0E, 0x07, // 169:1788 + 0x07, 0x0A, 0x0C, 0x07, // 170:1802 + 0x07, 0x16, 0x0E, 0x07, // 171:1814 + 0x07, 0x24, 0x0E, 0x07, // 172:1828 + 0x07, 0x32, 0x0C, 0x07, // 173:1842 + 0x07, 0x3E, 0x0E, 0x07, // 174:1854 + 0x07, 0x4C, 0x0B, 0x07, // 175:1868 + 0x07, 0x57, 0x0B, 0x07, // 176:1879 + 0x07, 0x62, 0x0C, 0x07, // 177:1890 + 0x07, 0x6E, 0x0B, 0x07, // 178:1902 + 0x07, 0x79, 0x0B, 0x07, // 179:1913 + 0x07, 0x84, 0x0B, 0x07, // 180:1924 + 0x07, 0x8F, 0x0E, 0x07, // 181:1935 + 0x07, 0x9D, 0x0C, 0x07, // 182:1949 + 0x07, 0xA9, 0x0A, 0x07, // 183:1961 + 0x07, 0xB3, 0x0A, 0x07, // 184:1971 + 0x07, 0xBD, 0x09, 0x07, // 185:1981 + 0x07, 0xC6, 0x0C, 0x07, // 186:1990 + 0x07, 0xD2, 0x0E, 0x07, // 187:2002 + 0x07, 0xE0, 0x0E, 0x07, // 188:2016 + 0x07, 0xEE, 0x0E, 0x07, // 189:2030 + 0x07, 0xFC, 0x0E, 0x07, // 190:2044 + 0x08, 0x0A, 0x0C, 0x07, // 191:2058 + 0x08, 0x16, 0x0E, 0x07, // 192:2070 + 0x08, 0x24, 0x0E, 0x07, // 193:2084 + 0x08, 0x32, 0x0E, 0x07, // 194:2098 + 0x08, 0x40, 0x0E, 0x07, // 195:2112 + 0x08, 0x4E, 0x0E, 0x07, // 196:2126 + 0x08, 0x5C, 0x0E, 0x07, // 197:2140 + 0x08, 0x6A, 0x0E, 0x07, // 198:2154 + 0x08, 0x78, 0x0E, 0x07, // 199:2168 + 0x08, 0x86, 0x0E, 0x07, // 200:2182 + 0x08, 0x94, 0x0E, 0x07, // 201:2196 + 0x08, 0xA2, 0x0E, 0x07, // 202:2210 + 0x08, 0xB0, 0x0E, 0x07, // 203:2224 + 0x08, 0xBE, 0x0E, 0x07, // 204:2238 + 0x08, 0xCC, 0x0E, 0x07, // 205:2252 + 0x08, 0xDA, 0x0E, 0x07, // 206:2266 + 0x08, 0xE8, 0x0E, 0x07, // 207:2280 + 0x08, 0xF6, 0x0E, 0x07, // 208:2294 + 0x09, 0x04, 0x0E, 0x07, // 209:2308 + 0x09, 0x12, 0x0E, 0x07, // 210:2322 + 0x09, 0x20, 0x0E, 0x07, // 211:2336 + 0x09, 0x2E, 0x0E, 0x07, // 212:2350 + 0x09, 0x3C, 0x0E, 0x07, // 213:2364 + 0x09, 0x4A, 0x0E, 0x07, // 214:2378 + 0x09, 0x58, 0x0E, 0x07, // 215:2392 + 0x09, 0x66, 0x0E, 0x07, // 216:2406 + 0x09, 0x74, 0x0E, 0x07, // 217:2420 + 0x09, 0x82, 0x0E, 0x07, // 218:2434 + 0x09, 0x90, 0x0E, 0x07, // 219:2448 + 0x09, 0x9E, 0x0E, 0x07, // 220:2462 + 0x09, 0xAC, 0x0D, 0x07, // 221:2476 + 0x09, 0xB9, 0x0D, 0x07, // 222:2489 + 0x09, 0xC6, 0x0E, 0x07, // 223:2502 + 0x09, 0xD4, 0x0E, 0x07, // 224:2516 + 0x09, 0xE2, 0x0E, 0x07, // 225:2530 + 0x09, 0xF0, 0x0E, 0x07, // 226:2544 + 0x09, 0xFE, 0x0E, 0x07, // 227:2558 + 0x0A, 0x0C, 0x0E, 0x07, // 228:2572 + 0x0A, 0x1A, 0x0E, 0x07, // 229:2586 + 0x0A, 0x28, 0x0C, 0x07, // 230:2600 + 0x0A, 0x34, 0x0E, 0x07, // 231:2612 + 0x0A, 0x42, 0x0E, 0x07, // 232:2626 + 0x0A, 0x50, 0x0E, 0x07, // 233:2640 + 0x0A, 0x5E, 0x0E, 0x07, // 234:2654 + 0x0A, 0x6C, 0x0E, 0x07, // 235:2668 + 0x0A, 0x7A, 0x0E, 0x07, // 236:2682 + 0x0A, 0x88, 0x0E, 0x07, // 237:2696 + 0x0A, 0x96, 0x0E, 0x07, // 238:2710 + 0x0A, 0xA4, 0x0E, 0x07, // 239:2724 + 0x0A, 0xB2, 0x0E, 0x07, // 240:2738 + 0x0A, 0xC0, 0x0E, 0x07, // 241:2752 + 0x0A, 0xCE, 0x0E, 0x07, // 242:2766 + 0x0A, 0xDC, 0x0E, 0x07, // 243:2780 + 0x0A, 0xEA, 0x0E, 0x07, // 244:2794 + 0x0A, 0xF8, 0x0E, 0x07, // 245:2808 + 0x0B, 0x06, 0x0E, 0x07, // 246:2822 + 0x0B, 0x14, 0x0E, 0x07, // 247:2836 + 0x0B, 0x22, 0x0E, 0x07, // 248:2850 + 0x0B, 0x30, 0x0E, 0x07, // 249:2864 + 0x0B, 0x3E, 0x0E, 0x07, // 250:2878 + 0x0B, 0x4C, 0x0E, 0x07, // 251:2892 + 0x0B, 0x5A, 0x0E, 0x07, // 252:2906 + 0x0B, 0x68, 0x0D, 0x07, // 253:2920 + 0x0B, 0x75, 0x0E, 0x07, // 254:2933 + 0x0B, 0x83, 0x0D, 0x07, // 255:2947 + + // Font Data: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x0D, 0xF8, 0x0D, // 33 + 0x00, 0x00, 0x38, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x38, // 34 + 0x00, 0x02, 0x40, 0x0E, 0xE0, 0x03, 0x70, 0x0E, 0xC0, 0x07, 0x70, 0x02, 0x40, // 35 + 0x60, 0x04, 0xF0, 0x0C, 0x90, 0x08, 0xF8, 0x3F, 0x90, 0x08, 0xB0, 0x0F, 0x00, 0x07, // 36 + 0x30, 0x01, 0x48, 0x01, 0x48, 0x01, 0xB0, 0x06, 0x80, 0x09, 0x40, 0x09, 0x40, 0x06, // 37 + 0x00, 0x00, 0x00, 0x07, 0xB8, 0x0F, 0xF8, 0x08, 0x88, 0x0F, 0x00, 0x0E, 0x80, 0x0B, // 38 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x38, // 39 + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x03, 0xF8, 0x0F, 0x0C, 0x18, // 40 + 0x00, 0x00, 0x00, 0x00, 0x0C, 0x18, 0xF8, 0x0F, 0xE0, 0x03, // 41 + 0x00, 0x00, 0x90, 0x00, 0x60, 0x00, 0xF8, 0x01, 0x60, 0x00, 0x90, // 42 + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0xE0, 0x0F, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, // 43 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x1C, 0x00, 0x0C, // 44 + 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, // 45 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x0C, // 46 + 0x00, 0x00, 0x00, 0x10, 0x00, 0x0C, 0x00, 0x03, 0xC0, 0x00, 0x30, 0x00, 0x08, // 47 + 0x00, 0x00, 0xE0, 0x03, 0xF8, 0x0F, 0x08, 0x08, 0x88, 0x08, 0xF8, 0x0F, 0xE0, 0x03, // 48 + 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0xF8, 0x0F, 0xF8, 0x0F, 0x00, 0x08, 0x00, 0x08, // 49 + 0x00, 0x00, 0x10, 0x0C, 0x08, 0x0E, 0x08, 0x0B, 0x88, 0x09, 0xF8, 0x08, 0x70, 0x08, // 50 + 0x00, 0x00, 0x10, 0x04, 0x08, 0x08, 0x88, 0x08, 0x88, 0x08, 0x78, 0x0F, 0x70, 0x07, // 51 + 0x00, 0x00, 0x80, 0x03, 0xC0, 0x02, 0x30, 0x02, 0xF8, 0x0F, 0xF8, 0x0F, 0x00, 0x02, // 52 + 0x00, 0x00, 0x78, 0x04, 0x78, 0x08, 0x48, 0x08, 0xC8, 0x08, 0xC8, 0x0F, 0x80, 0x07, // 53 + 0x00, 0x00, 0xE0, 0x07, 0xF8, 0x0F, 0x58, 0x08, 0x48, 0x08, 0xC8, 0x0F, 0x80, 0x07, // 54 + 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x08, 0x0F, 0xE8, 0x07, 0xF8, 0x00, 0x38, // 55 + 0x00, 0x00, 0x70, 0x07, 0x78, 0x0F, 0x88, 0x08, 0x88, 0x08, 0x78, 0x0F, 0x70, 0x07, // 56 + 0x00, 0x00, 0xF0, 0x00, 0xF8, 0x09, 0x08, 0x09, 0x08, 0x0D, 0xF8, 0x0F, 0xF0, 0x03, // 57 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0C, 0xC0, 0x0C, // 58 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xC0, 0x1C, 0xC0, 0x0C, // 59 + 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0xC0, 0x03, 0x40, 0x02, 0x40, 0x02, 0x60, 0x06, // 60 + 0x00, 0x00, 0x80, 0x02, 0x80, 0x02, 0x80, 0x02, 0x80, 0x02, 0x80, 0x02, 0x80, 0x02, // 61 + 0x00, 0x00, 0x60, 0x06, 0x40, 0x02, 0x40, 0x02, 0xC0, 0x03, 0x80, 0x01, 0x80, 0x01, // 62 + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x88, 0x0D, 0xC8, 0x0D, 0x78, 0x00, 0x30, // 63 + 0xC0, 0x07, 0x60, 0x18, 0x90, 0x37, 0x50, 0x28, 0x50, 0x28, 0x50, 0x28, 0xE0, 0x3F, // 64 + 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x0F, 0xF8, 0x02, 0xF8, 0x02, 0xC0, 0x0F, 0x00, 0x0C, // 65 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x88, 0x08, 0x88, 0x08, 0x78, 0x0F, 0x70, 0x07, // 66 + 0x00, 0x00, 0xE0, 0x03, 0xF0, 0x07, 0x18, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x10, 0x04, // 67 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x08, 0x08, 0x18, 0x0C, 0xF0, 0x07, 0xE0, 0x03, // 68 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x08, 0x08, // 69 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x08, // 70 + 0x00, 0x00, 0xE0, 0x03, 0xF0, 0x07, 0x18, 0x0C, 0x08, 0x09, 0x08, 0x0F, 0x10, 0x0F, // 71 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x80, 0x00, 0x80, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, // 72 + 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0xF8, 0x0F, 0xF8, 0x0F, 0x08, 0x08, 0x08, 0x08, // 73 + 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0xF8, 0x0F, 0xF8, 0x07, // 74 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0xC0, 0x00, 0xE0, 0x01, 0x38, 0x07, 0x18, 0x0C, // 75 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 76 + 0x00, 0x00, 0xF8, 0x0F, 0xF0, 0x0F, 0xE0, 0x00, 0xE0, 0x00, 0xF0, 0x0F, 0xF8, 0x0F, // 77 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0xF0, 0x00, 0x80, 0x07, 0xF8, 0x0F, 0xF8, 0x0F, // 78 + 0x00, 0x00, 0xE0, 0x03, 0xF8, 0x0F, 0x08, 0x08, 0x08, 0x08, 0xF8, 0x0F, 0xE0, 0x03, // 79 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x88, 0x00, 0x88, 0x00, 0xF8, 0x00, 0x70, // 80 + 0x00, 0x00, 0xE0, 0x03, 0xF8, 0x0F, 0x08, 0x08, 0x08, 0x08, 0xF8, 0x1F, 0xE0, 0x03, // 81 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x88, 0x00, 0x88, 0x01, 0x78, 0x07, 0x70, 0x0E, // 82 + 0x00, 0x00, 0x70, 0x04, 0xF8, 0x08, 0xC8, 0x08, 0x88, 0x09, 0x88, 0x0F, 0x10, 0x07, // 83 + 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x08, 0x00, 0x08, // 84 + 0x00, 0x00, 0xF8, 0x07, 0xF8, 0x0F, 0x00, 0x08, 0x00, 0x08, 0xF8, 0x0F, 0xF8, 0x07, // 85 + 0x00, 0x00, 0x18, 0x00, 0xF8, 0x03, 0x80, 0x0F, 0x80, 0x0F, 0xF8, 0x03, 0x18, // 86 + 0x78, 0x00, 0xF8, 0x0F, 0x80, 0x0F, 0x60, 0x00, 0x80, 0x0F, 0xF8, 0x0F, 0xF8, // 87 + 0x00, 0x00, 0x08, 0x08, 0x38, 0x0E, 0xE0, 0x03, 0xE0, 0x03, 0x38, 0x0E, 0x08, 0x08, // 88 + 0x08, 0x00, 0x38, 0x00, 0xF0, 0x00, 0xC0, 0x0F, 0xC0, 0x0F, 0xF0, 0x00, 0x38, // 89 + 0x00, 0x00, 0x08, 0x0C, 0x08, 0x0F, 0x88, 0x0B, 0xE8, 0x08, 0x78, 0x08, 0x18, 0x08, // 90 + 0x00, 0x00, 0x00, 0x00, 0xFC, 0x1F, 0xFC, 0x1F, 0x04, 0x10, // 91 + 0x00, 0x00, 0x08, 0x00, 0x38, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x00, 0x1C, 0x00, 0x10, // 92 + 0x00, 0x00, 0x00, 0x00, 0x04, 0x10, 0xFC, 0x1F, 0xFC, 0x1F, // 93 + 0x20, 0x00, 0x30, 0x00, 0x18, 0x00, 0x08, 0x00, 0x18, 0x00, 0x30, 0x00, 0x20, // 94 + 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, // 95 + 0x00, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x08, // 96 + 0x00, 0x00, 0x00, 0x06, 0x40, 0x0F, 0x20, 0x09, 0x20, 0x09, 0xE0, 0x0F, 0xC0, 0x0F, // 97 + 0x00, 0x00, 0xFC, 0x0F, 0xFC, 0x0F, 0x20, 0x08, 0x20, 0x08, 0xE0, 0x0F, 0xC0, 0x07, // 98 + 0x00, 0x00, 0x80, 0x03, 0xC0, 0x07, 0x60, 0x0C, 0x20, 0x08, 0x20, 0x08, 0x40, 0x04, // 99 + 0x00, 0x00, 0xC0, 0x07, 0xE0, 0x0F, 0x20, 0x08, 0x20, 0x08, 0xFC, 0x0F, 0xFC, 0x0F, // 100 + 0x00, 0x00, 0xC0, 0x07, 0xE0, 0x0F, 0x20, 0x09, 0x20, 0x09, 0xE0, 0x09, 0xC0, 0x05, // 101 + 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0xF8, 0x0F, 0xFC, 0x0F, 0x24, 0x00, 0x24, // 102 + 0x00, 0x00, 0xC0, 0x07, 0xE0, 0x4F, 0x20, 0x48, 0x20, 0x48, 0xE0, 0x7F, 0xE0, 0x3F, // 103 + 0x00, 0x00, 0xFC, 0x0F, 0xFC, 0x0F, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x0F, 0xC0, 0x0F, // 104 + 0x00, 0x00, 0x20, 0x08, 0x20, 0x08, 0xEC, 0x0F, 0xEC, 0x0F, 0x00, 0x08, 0x00, 0x08, // 105 + 0x00, 0x00, 0x20, 0x40, 0x20, 0x40, 0xEC, 0x7F, 0xEC, 0x3F, // 106 + 0x00, 0x00, 0xFC, 0x0F, 0xFC, 0x0F, 0x80, 0x01, 0xC0, 0x07, 0x60, 0x0E, 0x00, 0x08, // 107 + 0x04, 0x00, 0x04, 0x00, 0xFC, 0x07, 0xFC, 0x0F, 0x00, 0x08, 0x00, 0x08, // 108 + 0x00, 0x00, 0xE0, 0x0F, 0xE0, 0x0F, 0x20, 0x00, 0xE0, 0x0F, 0x20, 0x00, 0xE0, 0x0F, // 109 + 0x00, 0x00, 0xE0, 0x0F, 0xE0, 0x0F, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x0F, 0xC0, 0x0F, // 110 + 0x00, 0x00, 0xC0, 0x07, 0xE0, 0x0F, 0x20, 0x08, 0x20, 0x08, 0xE0, 0x0F, 0xC0, 0x07, // 111 + 0x00, 0x00, 0xE0, 0x7F, 0xE0, 0x7F, 0x20, 0x08, 0x20, 0x08, 0xE0, 0x0F, 0xC0, 0x07, // 112 + 0x00, 0x00, 0xC0, 0x07, 0xE0, 0x0F, 0x20, 0x08, 0x20, 0x08, 0xE0, 0x7F, 0xE0, 0x7F, // 113 + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x0F, 0xE0, 0x0F, 0x20, 0x00, 0x20, 0x00, 0x20, // 114 + 0x00, 0x00, 0xC0, 0x04, 0xE0, 0x09, 0xA0, 0x09, 0x20, 0x09, 0x20, 0x0F, 0x40, 0x06, // 115 + 0x20, 0x00, 0x20, 0x00, 0xF8, 0x07, 0xF8, 0x0F, 0x20, 0x08, 0x20, 0x08, // 116 + 0x00, 0x00, 0xE0, 0x07, 0xE0, 0x0F, 0x00, 0x08, 0x00, 0x08, 0xE0, 0x0F, 0xE0, 0x0F, // 117 + 0x00, 0x00, 0x60, 0x00, 0xE0, 0x03, 0x00, 0x0F, 0x00, 0x0F, 0xE0, 0x03, 0x60, // 118 + 0xE0, 0x00, 0xE0, 0x0F, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x0F, 0xE0, 0x0F, 0xE0, // 119 + 0x00, 0x00, 0x20, 0x08, 0xE0, 0x0E, 0xC0, 0x07, 0xC0, 0x07, 0xE0, 0x0E, 0x20, 0x08, // 120 + 0x00, 0x00, 0x60, 0x40, 0xE0, 0x43, 0x00, 0x7F, 0x00, 0x1F, 0xE0, 0x03, 0x60, // 121 + 0x00, 0x00, 0x20, 0x0C, 0x20, 0x0E, 0x20, 0x0B, 0xA0, 0x09, 0xE0, 0x08, 0x60, 0x08, // 122 + 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x7C, 0x1F, 0x7C, 0x1F, 0x04, 0x10, 0x04, 0x10, // 123 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, // 124 + 0x00, 0x00, 0x04, 0x10, 0x04, 0x10, 0x7C, 0x1F, 0x7C, 0x1F, 0x80, 0x00, 0x80, // 125 + 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 126 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 127 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 128 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 129 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 130 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 131 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 132 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 133 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 134 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 135 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 136 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 137 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 138 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 139 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 140 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 141 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 142 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 143 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 144 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 145 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 146 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 147 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 148 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 149 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 150 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 151 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 152 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 153 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 154 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 155 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 156 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 157 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 158 + 0x00, 0x00, 0xF0, 0x7F, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0xF0, 0x7F, // 159 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x3F, 0x60, 0x3F, // 161 + 0x00, 0x00, 0x80, 0x03, 0xC0, 0x07, 0x60, 0x0C, 0x20, 0x08, 0xF8, 0x3F, 0x20, 0x08, // 162 + 0x00, 0x00, 0x80, 0x08, 0xF0, 0x0F, 0xF8, 0x0F, 0x88, 0x08, 0x88, 0x08, 0x10, 0x08, // 163 + 0x00, 0x00, 0x20, 0x04, 0xC0, 0x03, 0x40, 0x02, 0x40, 0x02, 0xE0, 0x07, 0x40, 0x04, // 164 + 0x08, 0x00, 0x58, 0x01, 0x70, 0x01, 0xC0, 0x0F, 0xC0, 0x0F, 0x70, 0x01, 0x58, 0x01, // 165 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3C, // 166 + 0x00, 0x00, 0x90, 0x01, 0xF8, 0x13, 0x68, 0x16, 0xC8, 0x1F, 0x80, 0x19, // 167 + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, // 168 + 0xC0, 0x01, 0x20, 0x02, 0xD0, 0x05, 0x50, 0x05, 0x50, 0x05, 0x20, 0x02, 0xC0, 0x01, // 169 + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0xA8, 0x02, 0xA8, 0x02, 0xF8, 0x02, // 170 + 0x00, 0x00, 0x00, 0x01, 0x80, 0x02, 0xC0, 0x06, 0x00, 0x01, 0x80, 0x02, 0xC0, 0x06, // 171 + 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x03, // 172 + 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, // 173 + 0xC0, 0x01, 0x20, 0x02, 0xD0, 0x05, 0xD0, 0x04, 0x50, 0x05, 0x20, 0x02, 0xC0, 0x01, // 174 + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 175 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 176 + 0x00, 0x00, 0x80, 0x08, 0x80, 0x08, 0xE0, 0x0B, 0x80, 0x08, 0x80, 0x08, // 177 + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0xC8, 0x00, 0xA8, 0x00, 0x98, // 178 + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0xA8, 0x00, 0xA8, 0x00, 0xD8, // 179 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0C, 0x00, 0x04, // 180 + 0x00, 0x00, 0xE0, 0x7F, 0xE0, 0x7F, 0x00, 0x08, 0x00, 0x08, 0xE0, 0x07, 0xE0, 0x0F, // 181 + 0x70, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0xF8, 0x1F, 0x08, 0x00, 0xF8, 0x1F, // 182 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, // 183 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x30, // 184 + 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0xF8, 0x00, 0x80, // 185 + 0x00, 0x00, 0x00, 0x00, 0x70, 0x02, 0x88, 0x02, 0x88, 0x02, 0x70, 0x02, // 186 + 0x00, 0x00, 0xC0, 0x06, 0x80, 0x02, 0x00, 0x01, 0xC0, 0x06, 0x80, 0x02, 0x00, 0x01, // 187 + 0x44, 0x01, 0x7C, 0x01, 0xC0, 0x00, 0x80, 0x0C, 0x40, 0x0E, 0x40, 0x1F, 0x00, 0x08, // 188 + 0x44, 0x01, 0x7C, 0x01, 0xC0, 0x00, 0x80, 0x11, 0x40, 0x19, 0x40, 0x15, 0x00, 0x13, // 189 + 0x00, 0x01, 0x44, 0x01, 0xD4, 0x00, 0xD4, 0x0C, 0x6C, 0x0E, 0x40, 0x1F, 0x00, 0x08, // 190 + 0x00, 0x00, 0x00, 0x30, 0x00, 0x78, 0x60, 0x4F, 0x60, 0x47, 0x00, 0x20, // 191 + 0x00, 0x00, 0x00, 0x0C, 0xC1, 0x0F, 0xFB, 0x02, 0xFA, 0x02, 0xC0, 0x0F, 0x00, 0x0C, // 192 + 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x0F, 0xFA, 0x02, 0xFB, 0x02, 0xC1, 0x0F, 0x00, 0x0C, // 193 + 0x00, 0x00, 0x00, 0x0C, 0xC2, 0x0F, 0xF9, 0x02, 0xF9, 0x02, 0xC2, 0x0F, 0x00, 0x0C, // 194 + 0x00, 0x00, 0x00, 0x0C, 0xC3, 0x0F, 0xF9, 0x02, 0xFA, 0x02, 0xC3, 0x0F, 0x00, 0x0C, // 195 + 0x00, 0x00, 0x00, 0x0C, 0xC2, 0x0F, 0xF8, 0x02, 0xF8, 0x02, 0xC2, 0x0F, 0x00, 0x0C, // 196 + 0x00, 0x00, 0x00, 0x0C, 0x80, 0x0F, 0xFE, 0x02, 0xF6, 0x02, 0x80, 0x0F, 0x00, 0x0C, // 197 + 0x00, 0x0C, 0xC0, 0x0F, 0xF8, 0x01, 0x08, 0x01, 0xF8, 0x0F, 0xF8, 0x0F, 0x88, 0x08, // 198 + 0x00, 0x00, 0xE0, 0x03, 0xF0, 0x07, 0x18, 0x2C, 0x08, 0x28, 0x08, 0x38, 0x10, 0x04, // 199 + 0x00, 0x00, 0xF8, 0x0F, 0xF9, 0x0F, 0x8B, 0x08, 0x8A, 0x08, 0x88, 0x08, 0x08, 0x08, // 200 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x8A, 0x08, 0x8B, 0x08, 0x89, 0x08, 0x08, 0x08, // 201 + 0x00, 0x00, 0xF8, 0x0F, 0xFA, 0x0F, 0x89, 0x08, 0x89, 0x08, 0x8A, 0x08, 0x08, 0x08, // 202 + 0x00, 0x00, 0xF8, 0x0F, 0xFA, 0x0F, 0x88, 0x08, 0x8A, 0x08, 0x88, 0x08, 0x08, 0x08, // 203 + 0x00, 0x00, 0x08, 0x08, 0x09, 0x08, 0xFB, 0x0F, 0xFA, 0x0F, 0x08, 0x08, 0x08, 0x08, // 204 + 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0xFA, 0x0F, 0xFB, 0x0F, 0x09, 0x08, 0x08, 0x08, // 205 + 0x00, 0x00, 0x08, 0x08, 0x0A, 0x08, 0xF9, 0x0F, 0xF9, 0x0F, 0x0A, 0x08, 0x08, 0x08, // 206 + 0x00, 0x00, 0x08, 0x08, 0x0A, 0x08, 0xF8, 0x0F, 0xF8, 0x0F, 0x0A, 0x08, 0x08, 0x08, // 207 + 0x80, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x88, 0x08, 0x18, 0x0C, 0xF0, 0x07, 0xE0, 0x03, // 208 + 0x00, 0x00, 0xF8, 0x0F, 0xFB, 0x0F, 0xF1, 0x00, 0x82, 0x07, 0xFB, 0x0F, 0xF8, 0x0F, // 209 + 0x00, 0x00, 0xE0, 0x03, 0xF9, 0x0F, 0x0B, 0x08, 0x0A, 0x08, 0xF8, 0x0F, 0xE0, 0x03, // 210 + 0x00, 0x00, 0xE0, 0x03, 0xF8, 0x0F, 0x0A, 0x08, 0x0B, 0x08, 0xF9, 0x0F, 0xE0, 0x03, // 211 + 0x00, 0x00, 0xE0, 0x03, 0xFA, 0x0F, 0x09, 0x08, 0x09, 0x08, 0xFA, 0x0F, 0xE0, 0x03, // 212 + 0x00, 0x00, 0xE0, 0x03, 0xFB, 0x0F, 0x09, 0x08, 0x0A, 0x08, 0xFB, 0x0F, 0xE0, 0x03, // 213 + 0x00, 0x00, 0xE0, 0x03, 0xFA, 0x0F, 0x08, 0x08, 0x08, 0x08, 0xFA, 0x0F, 0xE0, 0x03, // 214 + 0x00, 0x00, 0x40, 0x02, 0xE0, 0x07, 0xC0, 0x03, 0xC0, 0x03, 0xE0, 0x07, 0x40, 0x02, // 215 + 0x00, 0x08, 0xE0, 0x0F, 0xF8, 0x0F, 0x88, 0x0B, 0xE8, 0x08, 0xF8, 0x0F, 0xF8, 0x03, // 216 + 0x00, 0x00, 0xF8, 0x07, 0xF9, 0x0F, 0x03, 0x08, 0x02, 0x08, 0xF8, 0x0F, 0xF8, 0x07, // 217 + 0x00, 0x00, 0xF8, 0x07, 0xF8, 0x0F, 0x02, 0x08, 0x03, 0x08, 0xF9, 0x0F, 0xF8, 0x07, // 218 + 0x00, 0x00, 0xF8, 0x07, 0xFA, 0x0F, 0x01, 0x08, 0x01, 0x08, 0xFA, 0x0F, 0xF8, 0x07, // 219 + 0x00, 0x00, 0xF8, 0x07, 0xFA, 0x0F, 0x00, 0x08, 0x00, 0x08, 0xFA, 0x0F, 0xF8, 0x07, // 220 + 0x08, 0x00, 0x38, 0x00, 0xF0, 0x00, 0xC2, 0x0F, 0xC3, 0x0F, 0xF1, 0x00, 0x38, // 221 + 0x00, 0x00, 0xF8, 0x0F, 0xF8, 0x0F, 0x10, 0x01, 0x10, 0x01, 0xF0, 0x01, 0xE0, // 222 + 0x00, 0x00, 0xF8, 0x0F, 0xFC, 0x0F, 0x04, 0x00, 0xE4, 0x08, 0x74, 0x0F, 0x18, 0x07, // 223 + 0x00, 0x00, 0x04, 0x06, 0x4C, 0x0F, 0x28, 0x09, 0x20, 0x09, 0xE0, 0x0F, 0xC0, 0x0F, // 224 + 0x00, 0x00, 0x00, 0x06, 0x40, 0x0F, 0x28, 0x09, 0x2C, 0x09, 0xE4, 0x0F, 0xC0, 0x0F, // 225 + 0x00, 0x00, 0x00, 0x06, 0x48, 0x0F, 0x24, 0x09, 0x24, 0x09, 0xE8, 0x0F, 0xC0, 0x0F, // 226 + 0x00, 0x00, 0x00, 0x06, 0x4C, 0x0F, 0x24, 0x09, 0x28, 0x09, 0xEC, 0x0F, 0xC0, 0x0F, // 227 + 0x00, 0x00, 0x00, 0x06, 0x48, 0x0F, 0x20, 0x09, 0x28, 0x09, 0xE0, 0x0F, 0xC0, 0x0F, // 228 + 0x00, 0x00, 0x00, 0x06, 0x40, 0x0F, 0x2E, 0x09, 0x2A, 0x09, 0xEE, 0x0F, 0xC0, 0x0F, // 229 + 0x40, 0x0F, 0x20, 0x09, 0xE0, 0x0F, 0xE0, 0x0F, 0x20, 0x09, 0xC0, 0x09, // 230 + 0x00, 0x00, 0x80, 0x03, 0xC0, 0x27, 0x60, 0x2C, 0x20, 0x38, 0x20, 0x08, 0x40, 0x04, // 231 + 0x00, 0x00, 0xC4, 0x07, 0xEC, 0x0F, 0x28, 0x09, 0x20, 0x09, 0xE0, 0x09, 0xC0, 0x05, // 232 + 0x00, 0x00, 0xC0, 0x07, 0xE0, 0x0F, 0x28, 0x09, 0x2C, 0x09, 0xE4, 0x09, 0xC0, 0x05, // 233 + 0x00, 0x00, 0xC0, 0x07, 0xE8, 0x0F, 0x24, 0x09, 0x24, 0x09, 0xE8, 0x09, 0xC0, 0x05, // 234 + 0x00, 0x00, 0xC0, 0x07, 0xE8, 0x0F, 0x20, 0x09, 0x28, 0x09, 0xE0, 0x09, 0xC0, 0x05, // 235 + 0x00, 0x00, 0x24, 0x08, 0x2C, 0x08, 0xE8, 0x0F, 0xE0, 0x0F, 0x00, 0x08, 0x00, 0x08, // 236 + 0x00, 0x00, 0x20, 0x08, 0x20, 0x08, 0xE8, 0x0F, 0xEC, 0x0F, 0x04, 0x08, 0x00, 0x08, // 237 + 0x00, 0x00, 0x28, 0x08, 0x24, 0x08, 0xE4, 0x0F, 0xE8, 0x0F, 0x00, 0x08, 0x00, 0x08, // 238 + 0x00, 0x00, 0x20, 0x08, 0x28, 0x08, 0xE0, 0x0F, 0xE8, 0x0F, 0x00, 0x08, 0x00, 0x08, // 239 + 0x00, 0x00, 0x80, 0x07, 0xD4, 0x0F, 0x5C, 0x08, 0x78, 0x08, 0xE4, 0x0F, 0x80, 0x07, // 240 + 0x00, 0x00, 0xE0, 0x0F, 0xEC, 0x0F, 0x24, 0x00, 0x28, 0x00, 0xEC, 0x0F, 0xC0, 0x0F, // 241 + 0x00, 0x00, 0xC4, 0x07, 0xEC, 0x0F, 0x28, 0x08, 0x20, 0x08, 0xE0, 0x0F, 0xC0, 0x07, // 242 + 0x00, 0x00, 0xC0, 0x07, 0xE0, 0x0F, 0x28, 0x08, 0x2C, 0x08, 0xE4, 0x0F, 0xC0, 0x07, // 243 + 0x00, 0x00, 0xC0, 0x07, 0xE8, 0x0F, 0x24, 0x08, 0x24, 0x08, 0xE8, 0x0F, 0xC0, 0x07, // 244 + 0x00, 0x00, 0xC0, 0x07, 0xEC, 0x0F, 0x24, 0x08, 0x28, 0x08, 0xEC, 0x0F, 0xC0, 0x07, // 245 + 0x00, 0x00, 0xC0, 0x07, 0xE8, 0x0F, 0x20, 0x08, 0x20, 0x08, 0xE8, 0x0F, 0xC0, 0x07, // 246 + 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x60, 0x0D, 0x60, 0x0D, 0x00, 0x01, 0x00, 0x01, // 247 + 0x00, 0x00, 0xC0, 0x0F, 0xE0, 0x0F, 0x20, 0x0B, 0xE0, 0x09, 0xE0, 0x0F, 0xF0, 0x07, // 248 + 0x00, 0x00, 0xE4, 0x07, 0xEC, 0x0F, 0x08, 0x08, 0x00, 0x08, 0xE0, 0x0F, 0xE0, 0x0F, // 249 + 0x00, 0x00, 0xE0, 0x07, 0xE0, 0x0F, 0x08, 0x08, 0x0C, 0x08, 0xE4, 0x0F, 0xE0, 0x0F, // 250 + 0x00, 0x00, 0xE0, 0x07, 0xE8, 0x0F, 0x04, 0x08, 0x04, 0x08, 0xE8, 0x0F, 0xE0, 0x0F, // 251 + 0x00, 0x00, 0xE0, 0x07, 0xE8, 0x0F, 0x00, 0x08, 0x00, 0x08, 0xE8, 0x0F, 0xE0, 0x0F, // 252 + 0x00, 0x00, 0x60, 0x40, 0xE0, 0x43, 0x08, 0x7F, 0x0C, 0x1F, 0xE4, 0x03, 0x60, // 253 + 0x00, 0x00, 0xFC, 0x7F, 0xFC, 0x7F, 0x20, 0x08, 0x20, 0x08, 0xE0, 0x0F, 0xC0, 0x07, // 254 + 0x00, 0x00, 0x60, 0x40, 0xE8, 0x43, 0x00, 0x7F, 0x00, 0x1F, 0xE8, 0x03, 0x60 // 255 + }; + +// Created by http://oleddisplay.squix.ch/ Consider a donation +// In case of problems make sure that you are using the font file with the correct version! +uint8_t DialogInput_bold_15[] PROGMEM = + { + 0x09, // Width: 9 + 0x12, // Height: 18 + 0x20, // First Char: 32 + 0xE0, // Numbers of Chars: 224 + + // Jump Table: + 0xFF, 0xFF, 0x00, + 0x09, // 32:65535 + 0x00, 0x00, 0x11, + 0x09, // 33:0 + 0x00, 0x11, 0x16, + 0x09, // 34:17 + 0x00, 0x27, 0x19, + 0x09, // 35:39 + 0x00, 0x40, 0x17, + 0x09, // 36:64 + 0x00, 0x57, 0x1A, + 0x09, // 37:87 + 0x00, 0x71, 0x17, + 0x09, // 38:113 + 0x00, 0x88, 0x10, + 0x09, // 39:136 + 0x00, 0x98, 0x14, + 0x09, // 40:152 + 0x00, 0xAC, 0x11, + 0x09, // 41:172 + 0x00, 0xBD, 0x16, + 0x09, // 42:189 + 0x00, 0xD3, 0x1A, + 0x09, // 43:211 + 0x00, 0xED, 0x0E, + 0x09, // 44:237 + 0x00, 0xFB, 0x14, + 0x09, // 45:251 + 0x01, 0x0F, 0x0E, + 0x09, // 46:271 + 0x01, 0x1D, 0x19, + 0x09, // 47:285 + 0x01, 0x36, 0x17, + 0x09, // 48:310 + 0x01, 0x4D, 0x17, + 0x09, // 49:333 + 0x01, 0x64, 0x17, + 0x09, // 50:356 + 0x01, 0x7B, 0x17, + 0x09, // 51:379 + 0x01, 0x92, 0x17, + 0x09, // 52:402 + 0x01, 0xA9, 0x17, + 0x09, // 53:425 + 0x01, 0xC0, 0x17, + 0x09, // 54:448 + 0x01, 0xD7, 0x16, + 0x09, // 55:471 + 0x01, 0xED, 0x17, + 0x09, // 56:493 + 0x02, 0x04, 0x17, + 0x09, // 57:516 + 0x02, 0x1B, 0x0E, + 0x09, // 58:539 + 0x02, 0x29, 0x0E, + 0x09, // 59:553 + 0x02, 0x37, 0x1A, + 0x09, // 60:567 + 0x02, 0x51, 0x1A, + 0x09, // 61:593 + 0x02, 0x6B, 0x1A, + 0x09, // 62:619 + 0x02, 0x85, 0x13, + 0x09, // 63:645 + 0x02, 0x98, 0x18, + 0x09, // 64:664 + 0x02, 0xB0, 0x17, + 0x09, // 65:688 + 0x02, 0xC7, 0x17, + 0x09, // 66:711 + 0x02, 0xDE, 0x17, + 0x09, // 67:734 + 0x02, 0xF5, 0x17, + 0x09, // 68:757 + 0x03, 0x0C, 0x17, + 0x09, // 69:780 + 0x03, 0x23, 0x16, + 0x09, // 70:803 + 0x03, 0x39, 0x17, + 0x09, // 71:825 + 0x03, 0x50, 0x17, + 0x09, // 72:848 + 0x03, 0x67, 0x14, + 0x09, // 73:871 + 0x03, 0x7B, 0x17, + 0x09, // 74:891 + 0x03, 0x92, 0x1A, + 0x09, // 75:914 + 0x03, 0xAC, 0x17, + 0x09, // 76:940 + 0x03, 0xC3, 0x17, + 0x09, // 77:963 + 0x03, 0xDA, 0x17, + 0x09, // 78:986 + 0x03, 0xF1, 0x17, + 0x09, // 79:1009 + 0x04, 0x08, 0x17, + 0x09, // 80:1032 + 0x04, 0x1F, 0x17, + 0x09, // 81:1055 + 0x04, 0x36, 0x1A, + 0x09, // 82:1078 + 0x04, 0x50, 0x17, + 0x09, // 83:1104 + 0x04, 0x67, 0x16, + 0x09, // 84:1127 + 0x04, 0x7D, 0x17, + 0x09, // 85:1149 + 0x04, 0x94, 0x16, + 0x09, // 86:1172 + 0x04, 0xAA, 0x19, + 0x09, // 87:1194 + 0x04, 0xC3, 0x17, + 0x09, // 88:1219 + 0x04, 0xDA, 0x16, + 0x09, // 89:1242 + 0x04, 0xF0, 0x17, + 0x09, // 90:1264 + 0x05, 0x07, 0x14, + 0x09, // 91:1287 + 0x05, 0x1B, 0x17, + 0x09, // 92:1307 + 0x05, 0x32, 0x11, + 0x09, // 93:1330 + 0x05, 0x43, 0x16, + 0x09, // 94:1347 + 0x05, 0x59, 0x1B, + 0x09, // 95:1369 + 0x05, 0x74, 0x0D, + 0x09, // 96:1396 + 0x05, 0x81, 0x17, + 0x09, // 97:1409 + 0x05, 0x98, 0x17, + 0x09, // 98:1432 + 0x05, 0xAF, 0x17, + 0x09, // 99:1455 + 0x05, 0xC6, 0x17, + 0x09, // 100:1478 + 0x05, 0xDD, 0x17, + 0x09, // 101:1501 + 0x05, 0xF4, 0x16, + 0x09, // 102:1524 + 0x06, 0x0A, 0x17, + 0x09, // 103:1546 + 0x06, 0x21, 0x17, + 0x09, // 104:1569 + 0x06, 0x38, 0x1A, + 0x09, // 105:1592 + 0x06, 0x52, 0x11, + 0x09, // 106:1618 + 0x06, 0x63, 0x17, + 0x09, // 107:1635 + 0x06, 0x7A, 0x17, + 0x09, // 108:1658 + 0x06, 0x91, 0x1A, + 0x09, // 109:1681 + 0x06, 0xAB, 0x17, + 0x09, // 110:1707 + 0x06, 0xC2, 0x17, + 0x09, // 111:1730 + 0x06, 0xD9, 0x17, + 0x09, // 112:1753 + 0x06, 0xF0, 0x18, + 0x09, // 113:1776 + 0x07, 0x08, 0x16, + 0x09, // 114:1800 + 0x07, 0x1E, 0x17, + 0x09, // 115:1822 + 0x07, 0x35, 0x17, + 0x09, // 116:1845 + 0x07, 0x4C, 0x17, + 0x09, // 117:1868 + 0x07, 0x63, 0x16, + 0x09, // 118:1891 + 0x07, 0x79, 0x1A, + 0x09, // 119:1913 + 0x07, 0x93, 0x17, + 0x09, // 120:1939 + 0x07, 0xAA, 0x16, + 0x09, // 121:1962 + 0x07, 0xC0, 0x17, + 0x09, // 122:1984 + 0x07, 0xD7, 0x18, + 0x09, // 123:2007 + 0x07, 0xEF, 0x12, + 0x09, // 124:2031 + 0x08, 0x01, 0x17, + 0x09, // 125:2049 + 0x08, 0x18, 0x1A, + 0x09, // 126:2072 + 0x08, 0x32, 0x1A, + 0x09, // 127:2098 + 0x08, 0x4C, 0x1A, + 0x09, // 128:2124 + 0x08, 0x66, 0x1A, + 0x09, // 129:2150 + 0x08, 0x80, 0x1A, + 0x09, // 130:2176 + 0x08, 0x9A, 0x1A, + 0x09, // 131:2202 + 0x08, 0xB4, 0x1A, + 0x09, // 132:2228 + 0x08, 0xCE, 0x1A, + 0x09, // 133:2254 + 0x08, 0xE8, 0x1A, + 0x09, // 134:2280 + 0x09, 0x02, 0x1A, + 0x09, // 135:2306 + 0x09, 0x1C, 0x1A, + 0x09, // 136:2332 + 0x09, 0x36, 0x1A, + 0x09, // 137:2358 + 0x09, 0x50, 0x1A, + 0x09, // 138:2384 + 0x09, 0x6A, 0x1A, + 0x09, // 139:2410 + 0x09, 0x84, 0x1A, + 0x09, // 140:2436 + 0x09, 0x9E, 0x1A, + 0x09, // 141:2462 + 0x09, 0xB8, 0x1A, + 0x09, // 142:2488 + 0x09, 0xD2, 0x1A, + 0x09, // 143:2514 + 0x09, 0xEC, 0x1A, + 0x09, // 144:2540 + 0x0A, 0x06, 0x1A, + 0x09, // 145:2566 + 0x0A, 0x20, 0x1A, + 0x09, // 146:2592 + 0x0A, 0x3A, 0x1A, + 0x09, // 147:2618 + 0x0A, 0x54, 0x1A, + 0x09, // 148:2644 + 0x0A, 0x6E, 0x1A, + 0x09, // 149:2670 + 0x0A, 0x88, 0x1A, + 0x09, // 150:2696 + 0x0A, 0xA2, 0x1A, + 0x09, // 151:2722 + 0x0A, 0xBC, 0x1A, + 0x09, // 152:2748 + 0x0A, 0xD6, 0x1A, + 0x09, // 153:2774 + 0x0A, 0xF0, 0x1A, + 0x09, // 154:2800 + 0x0B, 0x0A, 0x1A, + 0x09, // 155:2826 + 0x0B, 0x24, 0x1A, + 0x09, // 156:2852 + 0x0B, 0x3E, 0x1A, + 0x09, // 157:2878 + 0x0B, 0x58, 0x1A, + 0x09, // 158:2904 + 0x0B, 0x72, 0x1A, + 0x09, // 159:2930 + 0xFF, 0xFF, 0x00, + 0x09, // 160:65535 + 0x0B, 0x8C, 0x12, + 0x09, // 161:2956 + 0x0B, 0x9E, 0x17, + 0x09, // 162:2974 + 0x0B, 0xB5, 0x1A, + 0x09, // 163:2997 + 0x0B, 0xCF, 0x17, + 0x09, // 164:3023 + 0x0B, 0xE6, 0x17, + 0x09, // 165:3046 + 0x0B, 0xFD, 0x12, + 0x09, // 166:3069 + 0x0C, 0x0F, 0x17, + 0x09, // 167:3087 + 0x0C, 0x26, 0x13, + 0x09, // 168:3110 + 0x0C, 0x39, 0x1A, + 0x09, // 169:3129 + 0x0C, 0x53, 0x14, + 0x09, // 170:3155 + 0x0C, 0x67, 0x14, + 0x09, // 171:3175 + 0x0C, 0x7B, 0x1A, + 0x09, // 172:3195 + 0x0C, 0x95, 0x14, + 0x09, // 173:3221 + 0x0C, 0xA9, 0x1A, + 0x09, // 174:3241 + 0x0C, 0xC3, 0x13, + 0x09, // 175:3267 + 0x0C, 0xD6, 0x13, + 0x09, // 176:3286 + 0x0C, 0xE9, 0x1A, + 0x09, // 177:3305 + 0x0D, 0x03, 0x14, + 0x09, // 178:3331 + 0x0D, 0x17, 0x13, + 0x09, // 179:3351 + 0x0D, 0x2A, 0x13, + 0x09, // 180:3370 + 0x0D, 0x3D, 0x1A, + 0x09, // 181:3389 + 0x0D, 0x57, 0x17, + 0x09, // 182:3415 + 0x0D, 0x6E, 0x0E, + 0x09, // 183:3438 + 0x0D, 0x7C, 0x12, + 0x09, // 184:3452 + 0x0D, 0x8E, 0x14, + 0x09, // 185:3470 + 0x0D, 0xA2, 0x14, + 0x09, // 186:3490 + 0x0D, 0xB6, 0x14, + 0x09, // 187:3510 + 0x0D, 0xCA, 0x17, + 0x09, // 188:3530 + 0x0D, 0xE1, 0x17, + 0x09, // 189:3553 + 0x0D, 0xF8, 0x17, + 0x09, // 190:3576 + 0x0E, 0x0F, 0x17, + 0x09, // 191:3599 + 0x0E, 0x26, 0x17, + 0x09, // 192:3622 + 0x0E, 0x3D, 0x17, + 0x09, // 193:3645 + 0x0E, 0x54, 0x17, + 0x09, // 194:3668 + 0x0E, 0x6B, 0x17, + 0x09, // 195:3691 + 0x0E, 0x82, 0x17, + 0x09, // 196:3714 + 0x0E, 0x99, 0x17, + 0x09, // 197:3737 + 0x0E, 0xB0, 0x1A, + 0x09, // 198:3760 + 0x0E, 0xCA, 0x17, + 0x09, // 199:3786 + 0x0E, 0xE1, 0x17, + 0x09, // 200:3809 + 0x0E, 0xF8, 0x17, + 0x09, // 201:3832 + 0x0F, 0x0F, 0x17, + 0x09, // 202:3855 + 0x0F, 0x26, 0x17, + 0x09, // 203:3878 + 0x0F, 0x3D, 0x14, + 0x09, // 204:3901 + 0x0F, 0x51, 0x14, + 0x09, // 205:3921 + 0x0F, 0x65, 0x14, + 0x09, // 206:3941 + 0x0F, 0x79, 0x14, + 0x09, // 207:3961 + 0x0F, 0x8D, 0x17, + 0x09, // 208:3981 + 0x0F, 0xA4, 0x17, + 0x09, // 209:4004 + 0x0F, 0xBB, 0x17, + 0x09, // 210:4027 + 0x0F, 0xD2, 0x17, + 0x09, // 211:4050 + 0x0F, 0xE9, 0x17, + 0x09, // 212:4073 + 0x10, 0x00, 0x17, + 0x09, // 213:4096 + 0x10, 0x17, 0x17, + 0x09, // 214:4119 + 0x10, 0x2E, 0x17, + 0x09, // 215:4142 + 0x10, 0x45, 0x17, + 0x09, // 216:4165 + 0x10, 0x5C, 0x17, + 0x09, // 217:4188 + 0x10, 0x73, 0x17, + 0x09, // 218:4211 + 0x10, 0x8A, 0x17, + 0x09, // 219:4234 + 0x10, 0xA1, 0x17, + 0x09, // 220:4257 + 0x10, 0xB8, 0x16, + 0x09, // 221:4280 + 0x10, 0xCE, 0x17, + 0x09, // 222:4302 + 0x10, 0xE5, 0x17, + 0x09, // 223:4325 + 0x10, 0xFC, 0x17, + 0x09, // 224:4348 + 0x11, 0x13, 0x17, + 0x09, // 225:4371 + 0x11, 0x2A, 0x17, + 0x09, // 226:4394 + 0x11, 0x41, 0x17, + 0x09, // 227:4417 + 0x11, 0x58, 0x17, + 0x09, // 228:4440 + 0x11, 0x6F, 0x17, + 0x09, // 229:4463 + 0x11, 0x86, 0x17, + 0x09, // 230:4486 + 0x11, 0x9D, 0x17, + 0x09, // 231:4509 + 0x11, 0xB4, 0x17, + 0x09, // 232:4532 + 0x11, 0xCB, 0x17, + 0x09, // 233:4555 + 0x11, 0xE2, 0x17, + 0x09, // 234:4578 + 0x11, 0xF9, 0x17, + 0x09, // 235:4601 + 0x12, 0x10, 0x1A, + 0x09, // 236:4624 + 0x12, 0x2A, 0x1A, + 0x09, // 237:4650 + 0x12, 0x44, 0x1A, + 0x09, // 238:4676 + 0x12, 0x5E, 0x1A, + 0x09, // 239:4702 + 0x12, 0x78, 0x17, + 0x09, // 240:4728 + 0x12, 0x8F, 0x17, + 0x09, // 241:4751 + 0x12, 0xA6, 0x17, + 0x09, // 242:4774 + 0x12, 0xBD, 0x17, + 0x09, // 243:4797 + 0x12, 0xD4, 0x17, + 0x09, // 244:4820 + 0x12, 0xEB, 0x17, + 0x09, // 245:4843 + 0x13, 0x02, 0x17, + 0x09, // 246:4866 + 0x13, 0x19, 0x17, + 0x09, // 247:4889 + 0x13, 0x30, 0x17, + 0x09, // 248:4912 + 0x13, 0x47, 0x17, + 0x09, // 249:4935 + 0x13, 0x5E, 0x17, + 0x09, // 250:4958 + 0x13, 0x75, 0x17, + 0x09, // 251:4981 + 0x13, 0x8C, 0x17, + 0x09, // 252:5004 + 0x13, 0xA3, 0x16, + 0x09, // 253:5027 + 0x13, 0xB9, 0x17, + 0x09, // 254:5049 + 0x13, 0xD0, 0x16, + 0x09, // 255:5072 + + // Font Data: + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x37, 0x00, 0xF8, + 0x37, // 33 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, + 0x78, // 34 + 0x00, 0x04, 0x00, 0x40, 0x3C, 0x00, 0xC0, 0x3F, 0x00, 0xF8, 0x07, 0x00, 0x78, 0x34, 0x00, 0xC0, 0x3F, 0x00, 0xF0, 0x07, 0x00, + 0x78, 0x04, 0x00, + 0x40, // 35 + 0x00, 0x00, 0x00, 0xE0, 0x10, 0x00, 0xF0, 0x31, 0x00, 0x90, 0x21, 0x00, 0xFC, 0xFF, 0x00, 0x10, 0x23, 0x00, 0x30, 0x3F, 0x00, + 0x00, + 0x1E, // 36 + 0x70, 0x02, 0x00, 0x88, 0x02, 0x00, 0x88, 0x02, 0x00, 0x88, 0x01, 0x00, 0x70, 0x1D, 0x00, 0x00, 0x23, 0x00, 0x00, 0x23, 0x00, + 0x80, 0x22, 0x00, 0x80, + 0x1C, // 37 + 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0xB0, 0x3F, 0x00, 0xF8, 0x31, 0x00, 0x88, 0x2F, 0x00, 0x08, 0x3E, 0x00, 0x00, 0x3F, 0x00, + 0x00, + 0x27, // 38 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, + 0x78, // 39 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0xF0, 0x7F, 0x00, 0x38, 0xE0, 0x00, 0x08, + 0x80, // 40 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x38, 0xE0, 0x00, 0xF0, 0x7F, 0x00, 0xC0, + 0x1F, // 41 + 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x60, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x60, 0x00, 0x00, 0xF0, 0x00, 0x00, + 0x90, // 42 + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0xE0, 0x1F, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x03, 0x00, 0x00, + 0x03, // 43 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0xF8, 0x00, 0x00, + 0x78, // 44 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x06, // 45 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, + 0x38, // 46 + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x70, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x0F, 0x00, 0xC0, 0x03, 0x00, 0xF0, 0x00, 0x00, + 0x38, 0x00, 0x00, + 0x08, // 47 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0xF0, 0x1F, 0x00, 0x18, 0x30, 0x00, 0x88, 0x21, 0x00, 0x18, 0x30, 0x00, 0xF0, 0x1F, 0x00, + 0xE0, + 0x0F, // 48 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, 0x08, 0x20, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x20, 0x00, + 0x00, + 0x20, // 49 + 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x08, 0x38, 0x00, 0x08, 0x3C, 0x00, 0x08, 0x2E, 0x00, 0x88, 0x27, 0x00, 0xF0, 0x23, 0x00, + 0xF0, + 0x20, // 50 + 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x08, 0x20, 0x00, 0x88, 0x20, 0x00, 0x88, 0x20, 0x00, 0x88, 0x31, 0x00, 0x78, 0x1F, 0x00, + 0x70, + 0x1E, // 51 + 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x80, 0x05, 0x00, 0xE0, 0x04, 0x00, 0x30, 0x04, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, + 0x00, + 0x04, // 52 + 0x00, 0x00, 0x00, 0xF8, 0x11, 0x00, 0xF8, 0x20, 0x00, 0x88, 0x20, 0x00, 0x88, 0x20, 0x00, 0x88, 0x31, 0x00, 0x08, 0x1F, 0x00, + 0x00, + 0x0E, // 53 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0xF0, 0x1F, 0x00, 0x98, 0x20, 0x00, 0x88, 0x20, 0x00, 0x88, 0x20, 0x00, 0x90, 0x3F, 0x00, + 0x00, + 0x1F, // 54 + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x20, 0x00, 0x08, 0x3C, 0x00, 0x08, 0x1F, 0x00, 0xE8, 0x07, 0x00, 0xF8, 0x00, 0x00, + 0x38, // 55 + 0x00, 0x00, 0x00, 0x70, 0x1E, 0x00, 0x78, 0x3F, 0x00, 0x88, 0x31, 0x00, 0x88, 0x20, 0x00, 0x88, 0x31, 0x00, 0x78, 0x3F, 0x00, + 0x70, + 0x1E, // 56 + 0x00, 0x00, 0x00, 0xF0, 0x01, 0x00, 0xF8, 0x13, 0x00, 0x08, 0x22, 0x00, 0x08, 0x22, 0x00, 0x08, 0x32, 0x00, 0xF0, 0x1F, 0x00, + 0xE0, + 0x0F, // 57 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x39, 0x00, 0xC0, + 0x39, // 58 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0xF9, 0x00, 0xC0, + 0x79, // 59 + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x80, 0x07, 0x00, 0x80, 0x04, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, + 0x40, 0x08, 0x00, 0x60, + 0x18, // 60 + 0x00, 0x00, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, + 0x0C, // 61 + 0x00, 0x00, 0x00, 0x60, 0x18, 0x00, 0x40, 0x08, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0x80, 0x04, 0x00, 0x80, 0x07, 0x00, + 0x00, 0x03, 0x00, 0x00, + 0x03, // 62 + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x88, 0x37, 0x00, 0xC8, 0x37, 0x00, 0xF8, 0x00, 0x00, + 0x70, // 63 + 0x80, 0x3F, 0x00, 0xC0, 0x7F, 0x00, 0x60, 0xC0, 0x00, 0x10, 0x9F, 0x01, 0x90, 0x3F, 0x01, 0x90, 0x20, 0x01, 0xF0, 0xBF, 0x01, + 0xE0, 0x3F, + 0x01, // 64 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x80, 0x3F, 0x00, 0xF8, 0x1F, 0x00, 0x78, 0x04, 0x00, 0xF8, 0x1F, 0x00, 0x80, 0x3F, 0x00, + 0x00, + 0x30, // 65 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0xF8, 0x3E, 0x00, + 0xF0, + 0x1E, // 66 + 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0xF0, 0x1F, 0x00, 0x18, 0x30, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, + 0x10, + 0x10, // 67 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x18, 0x30, 0x00, 0xF0, 0x1F, 0x00, + 0xE0, + 0x0F, // 68 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, + 0x08, + 0x20, // 69 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, + 0x08, // 70 + 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x00, 0xF0, 0x1F, 0x00, 0x18, 0x30, 0x00, 0x08, 0x20, 0x00, 0x08, 0x22, 0x00, 0x08, 0x3E, 0x00, + 0x10, + 0x3E, // 71 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF8, 0x3F, 0x00, + 0xF8, + 0x3F, // 72 + 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x20, 0x00, 0x08, + 0x20, // 73 + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0xF8, 0x3F, 0x00, + 0xF8, + 0x1F, // 74 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x80, 0x03, 0x00, 0xE0, 0x03, 0x00, 0x70, 0x0F, 0x00, 0x38, 0x3E, 0x00, + 0x18, 0x38, 0x00, 0x00, + 0x20, // 75 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, + 0x00, + 0x20, // 76 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x03, 0x00, 0xF8, 0x01, 0x00, 0xF8, 0x3F, 0x00, + 0xF8, + 0x3F, // 77 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0x3E, 0x00, 0xF8, 0x3F, 0x00, + 0xF8, + 0x3F, // 78 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0xF0, 0x1F, 0x00, 0x18, 0x30, 0x00, 0x08, 0x20, 0x00, 0x18, 0x30, 0x00, 0xF0, 0x1F, 0x00, + 0xE0, + 0x0F, // 79 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x18, 0x03, 0x00, 0xF8, 0x03, 0x00, + 0xF0, + 0x01, // 80 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0xF0, 0x1F, 0x00, 0x18, 0x30, 0x00, 0x08, 0x20, 0x00, 0x18, 0x70, 0x00, 0xF0, 0xFF, 0x00, + 0xE0, + 0x0F, // 81 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x02, 0x00, 0x08, 0x02, 0x00, 0x08, 0x0E, 0x00, 0xF8, 0x3D, 0x00, + 0xF0, 0x39, 0x00, 0x00, + 0x20, // 82 + 0x00, 0x00, 0x00, 0xF0, 0x10, 0x00, 0xF0, 0x21, 0x00, 0x88, 0x21, 0x00, 0x88, 0x23, 0x00, 0x08, 0x23, 0x00, 0x08, 0x3F, 0x00, + 0x10, + 0x1E, // 83 + 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x08, // 84 + 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0xF8, 0x3F, 0x00, + 0xF8, + 0x1F, // 85 + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0xF8, 0x03, 0x00, 0xE0, 0x3F, 0x00, 0x00, 0x38, 0x00, 0xE0, 0x3F, 0x00, 0xF8, 0x03, 0x00, + 0x18, // 86 + 0xF8, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x38, 0x00, 0xC0, 0x0F, 0x00, 0xC0, 0x01, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x38, 0x00, + 0xF8, 0x3F, 0x00, + 0xF8, // 87 + 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x38, 0x38, 0x00, 0xF0, 0x1E, 0x00, 0xC0, 0x07, 0x00, 0xF0, 0x1E, 0x00, 0x38, 0x38, 0x00, + 0x08, + 0x20, // 88 + 0x08, 0x00, 0x00, 0x38, 0x00, 0x00, 0xF8, 0x01, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x3F, 0x00, 0xF8, 0x01, 0x00, 0x38, 0x00, 0x00, + 0x08, // 89 + 0x00, 0x00, 0x00, 0x08, 0x38, 0x00, 0x08, 0x3C, 0x00, 0x08, 0x2F, 0x00, 0xC8, 0x23, 0x00, 0xE8, 0x21, 0x00, 0x78, 0x20, 0x00, + 0x38, + 0x20, // 90 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, + 0x80, // 91 + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x38, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x80, 0x07, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x70, 0x00, + 0x00, + 0x40, // 92 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0xF8, 0xFF, 0x00, 0xF8, + 0xFF, // 93 + 0x40, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x40, // 94 + 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x02, 0x00, 0x00, + 0x02, // 95 + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x18, 0x00, 0x00, + 0x10, // 96 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x3E, 0x00, 0x40, 0x22, 0x00, 0x40, 0x22, 0x00, 0x40, 0x32, 0x00, 0xC0, 0x3F, 0x00, + 0x80, + 0x3F, // 97 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0xC0, 0x30, 0x00, 0x40, 0x20, 0x00, 0xC0, 0x30, 0x00, 0xC0, 0x3F, 0x00, + 0x80, + 0x1F, // 98 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x1F, 0x00, 0xC0, 0x30, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, + 0x80, + 0x10, // 99 + 0x00, 0x00, 0x00, 0x80, 0x1F, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x30, 0x00, 0x40, 0x20, 0x00, 0xC0, 0x30, 0x00, 0xF8, 0x3F, 0x00, + 0xF8, + 0x3F, // 100 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x1F, 0x00, 0x40, 0x32, 0x00, 0x40, 0x22, 0x00, 0x40, 0x22, 0x00, 0xC0, 0x23, 0x00, + 0x80, + 0x13, // 101 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x48, 0x00, 0x00, 0x48, 0x00, 0x00, + 0x48, // 102 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xC0, 0xBF, 0x00, 0xC0, 0x30, 0x01, 0x40, 0x20, 0x01, 0xC0, 0x30, 0x01, 0xC0, 0xFF, 0x01, + 0xC0, + 0xFF, // 103 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x3F, 0x00, + 0x80, + 0x3F, // 104 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, 0xDC, 0x3F, 0x00, 0xDC, 0x3F, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x20, 0x00, 0x00, + 0x20, // 105 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x01, 0x40, 0x00, 0x01, 0xDC, 0xFF, 0x01, 0xDC, + 0xFF, // 106 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x03, 0x00, 0x80, 0x0F, 0x00, 0xC0, 0x3C, 0x00, 0x40, 0x30, 0x00, + 0x00, + 0x20, // 107 + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0xF8, 0x3F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, + 0x00, + 0x20, // 108 + 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x3F, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x3F, 0x00, 0x40, 0x00, 0x00, + 0xC0, 0x3F, 0x00, 0xC0, + 0x3F, // 109 + 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x3F, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x3F, 0x00, + 0x80, + 0x3F, // 110 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x1F, 0x00, 0xC0, 0x30, 0x00, 0x40, 0x20, 0x00, 0xC0, 0x30, 0x00, 0x80, 0x1F, 0x00, + 0x00, + 0x0F, // 111 + 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x01, 0xC0, 0xFF, 0x01, 0xC0, 0x30, 0x00, 0x40, 0x20, 0x00, 0xC0, 0x30, 0x00, 0xC0, 0x3F, 0x00, + 0x80, + 0x1F, // 112 + 0x00, 0x00, 0x00, 0x80, 0x1F, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x30, 0x00, 0x40, 0x20, 0x00, 0xC0, 0x30, 0x00, 0xC0, 0xFF, 0x01, + 0xC0, 0xFF, + 0x01, // 113 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, + 0x40, // 114 + 0x00, 0x00, 0x00, 0x80, 0x13, 0x00, 0xC0, 0x23, 0x00, 0x40, 0x26, 0x00, 0x40, 0x26, 0x00, 0x40, 0x26, 0x00, 0x40, 0x3C, 0x00, + 0x80, + 0x1C, // 115 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0xF0, 0x1F, 0x00, 0xF0, 0x3F, 0x00, 0x40, 0x20, 0x00, 0x40, 0x20, 0x00, + 0x40, + 0x20, // 116 + 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0xC0, 0x3F, 0x00, + 0xC0, + 0x3F, // 117 + 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x30, 0x00, 0x80, 0x3F, 0x00, 0xC0, 0x07, 0x00, + 0xC0, // 118 + 0xC0, 0x01, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x03, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x3C, 0x00, + 0xC0, 0x3F, 0x00, 0xC0, + 0x01, // 119 + 0x00, 0x00, 0x00, 0x40, 0x20, 0x00, 0xC0, 0x30, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x0F, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x38, 0x00, + 0x40, + 0x20, // 120 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x03, 0x01, 0x80, 0x8F, 0x01, 0x00, 0xFC, 0x01, 0x80, 0x7F, 0x00, 0xC0, 0x07, 0x00, + 0xC0, // 121 + 0x00, 0x00, 0x00, 0x40, 0x30, 0x00, 0x40, 0x38, 0x00, 0x40, 0x2C, 0x00, 0x40, 0x26, 0x00, 0x40, 0x23, 0x00, 0xC0, 0x21, 0x00, + 0xC0, + 0x20, // 122 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0xF0, 0xFB, 0x00, 0xF8, 0xFB, 0x01, 0x08, 0x00, 0x01, + 0x08, 0x00, + 0x01, // 123 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x03, 0xF8, 0xFF, + 0x03, // 124 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x08, 0x00, 0x01, 0xF8, 0xFB, 0x01, 0xF0, 0xFB, 0x00, 0x00, 0x04, 0x00, + 0x00, + 0x04, // 125 + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x06, 0x00, 0x00, + 0x03, // 126 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 127 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 128 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 129 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 130 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 131 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 132 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 133 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 134 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 135 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 136 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 137 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 138 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 139 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 140 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 141 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 142 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 143 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 144 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 145 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 146 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 147 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 148 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 149 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 150 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 151 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 152 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 153 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 154 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 155 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 156 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 157 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 158 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, + 0x08, 0x80, 0x00, 0xF8, + 0xFF, // 159 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFE, 0x01, 0xC0, 0xFE, + 0x01, // 161 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x1F, 0x00, 0xC0, 0x30, 0x00, 0x40, 0x20, 0x00, 0xF0, 0xFF, 0x00, 0x40, 0x20, 0x00, + 0x80, + 0x10, // 162 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x21, 0x00, 0xF0, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x21, 0x00, 0x08, 0x21, 0x00, + 0x10, 0x20, 0x00, 0x00, + 0x20, // 163 + 0x00, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80, 0x0F, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0xC0, 0x1F, 0x00, + 0x80, + 0x10, // 164 + 0x88, 0x02, 0x00, 0xF8, 0x02, 0x00, 0xF8, 0x02, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x3F, 0x00, 0xF8, 0x02, 0x00, 0xF8, 0x02, 0x00, + 0x88, + 0x02, // 165 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF9, 0x01, 0xF8, 0xF9, + 0x01, // 166 + 0x00, 0x00, 0x00, 0xB0, 0x03, 0x00, 0xB8, 0x47, 0x00, 0x68, 0x4E, 0x00, 0xC8, 0x4C, 0x00, 0xC8, 0x59, 0x00, 0x88, 0x7F, 0x00, + 0x00, + 0x37, // 167 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, + 0x0C, // 168 + 0xC0, 0x07, 0x00, 0x60, 0x0C, 0x00, 0xB0, 0x1B, 0x00, 0x50, 0x14, 0x00, 0x50, 0x14, 0x00, 0x50, 0x14, 0x00, 0x30, 0x18, 0x00, + 0x60, 0x0C, 0x00, 0xC0, + 0x07, // 169 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0xE8, 0x05, 0x00, 0x28, 0x05, 0x00, 0xF8, 0x05, 0x00, 0xF0, + 0x05, // 170 + 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x80, 0x0D, 0x00, 0xC0, 0x18, 0x00, 0x00, 0x07, 0x00, 0x80, 0x0D, 0x00, 0xC0, + 0x18, // 171 + 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, 0x80, 0x01, 0x00, + 0x80, 0x07, 0x00, 0x80, + 0x07, // 172 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x06, // 173 + 0xC0, 0x07, 0x00, 0x60, 0x0C, 0x00, 0xF0, 0x1F, 0x00, 0x50, 0x11, 0x00, 0x50, 0x11, 0x00, 0x50, 0x13, 0x00, 0xF0, 0x1E, 0x00, + 0x60, 0x0C, 0x00, 0xC0, + 0x07, // 174 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x08, // 175 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x88, 0x00, 0x00, 0x88, 0x00, 0x00, 0x88, 0x00, 0x00, + 0x70, // 176 + 0x00, 0x00, 0x00, 0x80, 0x31, 0x00, 0x80, 0x31, 0x00, 0x80, 0x31, 0x00, 0xF0, 0x3F, 0x00, 0xF0, 0x3F, 0x00, 0x80, 0x31, 0x00, + 0x80, 0x31, 0x00, 0x80, + 0x31, // 177 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x88, 0x01, 0x00, 0xC8, 0x01, 0x00, 0x68, 0x01, 0x00, 0x30, + 0x01, // 178 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x28, 0x01, 0x00, 0x28, 0x01, 0x00, 0x28, 0x01, 0x00, + 0xD8, // 179 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00, 0x0C, 0x00, 0x00, + 0x04, // 180 + 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x01, 0xC0, 0xFF, 0x01, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0xC0, 0x1F, 0x00, + 0xC0, 0x3F, 0x00, 0x00, + 0x20, // 181 + 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF8, 0x01, 0x00, 0xF8, 0x01, 0x00, 0xF8, 0x7F, 0x00, 0x08, 0x00, 0x00, + 0xF8, + 0x7F, // 182 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x80, + 0x03, // 183 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x01, 0x00, 0x80, + 0x01, // 184 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x08, 0x01, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x01, // 185 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x04, 0x00, 0xF8, 0x05, 0x00, 0x08, 0x05, 0x00, 0xF8, 0x05, 0x00, 0xF0, + 0x04, // 186 + 0x00, 0x00, 0x00, 0xC0, 0x18, 0x00, 0x80, 0x0D, 0x00, 0x00, 0x07, 0x00, 0xC0, 0x18, 0x00, 0x80, 0x0D, 0x00, 0x00, + 0x07, // 187 + 0x84, 0x04, 0x00, 0x84, 0x04, 0x00, 0xFC, 0x02, 0x00, 0x80, 0x62, 0x00, 0x80, 0x52, 0x00, 0x00, 0x4E, 0x00, 0x00, 0xFD, 0x00, + 0x00, + 0x41, // 188 + 0x84, 0x04, 0x00, 0x84, 0x04, 0x00, 0xFC, 0x02, 0x00, 0x80, 0x86, 0x00, 0x80, 0xC6, 0x00, 0x00, 0xE6, 0x00, 0x00, 0xB5, 0x00, + 0x00, + 0x99, // 189 + 0x84, 0x04, 0x00, 0x94, 0x04, 0x00, 0x94, 0x02, 0x00, 0x94, 0x62, 0x00, 0x6C, 0x52, 0x00, 0x00, 0x4E, 0x00, 0x00, 0xFD, 0x00, + 0x00, + 0x41, // 190 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xF0, 0x01, 0xC0, 0x3E, 0x01, 0xC0, 0x1E, 0x01, 0x00, 0x00, 0x01, + 0x00, + 0x80, // 191 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x80, 0x3F, 0x00, 0xF9, 0x1F, 0x00, 0x7B, 0x04, 0x00, 0xFA, 0x1F, 0x00, 0x80, 0x3F, 0x00, + 0x00, + 0x30, // 192 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x80, 0x3F, 0x00, 0xFA, 0x1F, 0x00, 0x7B, 0x04, 0x00, 0xF9, 0x1F, 0x00, 0x80, 0x3F, 0x00, + 0x00, + 0x30, // 193 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x82, 0x3F, 0x00, 0xFB, 0x1F, 0x00, 0x79, 0x04, 0x00, 0xFB, 0x1F, 0x00, 0x82, 0x3F, 0x00, + 0x00, + 0x30, // 194 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x83, 0x3F, 0x00, 0xF9, 0x1F, 0x00, 0x7B, 0x04, 0x00, 0xFA, 0x1F, 0x00, 0x83, 0x3F, 0x00, + 0x00, + 0x30, // 195 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x83, 0x3F, 0x00, 0xFB, 0x1F, 0x00, 0x78, 0x04, 0x00, 0xFB, 0x1F, 0x00, 0x83, 0x3F, 0x00, + 0x00, + 0x30, // 196 + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x3F, 0x00, 0xF6, 0x1F, 0x00, 0x79, 0x04, 0x00, 0xF6, 0x1F, 0x00, 0x00, 0x3F, 0x00, + 0x00, + 0x30, // 197 + 0x00, 0x30, 0x00, 0x00, 0x3F, 0x00, 0xE0, 0x07, 0x00, 0x78, 0x04, 0x00, 0x08, 0x04, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, + 0x08, 0x21, 0x00, 0x08, + 0x21, // 198 + 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0xF0, 0x1F, 0x00, 0x18, 0x30, 0x00, 0x08, 0x20, 0x01, 0x08, 0x60, 0x01, 0x08, 0xA0, 0x01, + 0x10, + 0x10, // 199 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x09, 0x21, 0x00, 0x0B, 0x21, 0x00, 0x0A, 0x21, 0x00, 0x08, 0x21, 0x00, + 0x08, + 0x20, // 200 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x0A, 0x21, 0x00, 0x0B, 0x21, 0x00, 0x09, 0x21, 0x00, 0x08, 0x21, 0x00, + 0x08, + 0x20, // 201 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xFA, 0x3F, 0x00, 0x0B, 0x21, 0x00, 0x09, 0x21, 0x00, 0x09, 0x21, 0x00, 0x0B, 0x21, 0x00, + 0x0A, + 0x20, // 202 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xFB, 0x3F, 0x00, 0x0B, 0x21, 0x00, 0x08, 0x21, 0x00, 0x0B, 0x21, 0x00, 0x0B, 0x21, 0x00, + 0x08, + 0x20, // 203 + 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0xF9, 0x3F, 0x00, 0xFB, 0x3F, 0x00, 0x0A, 0x20, 0x00, 0x08, + 0x20, // 204 + 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x08, 0x20, 0x00, 0xFA, 0x3F, 0x00, 0xFB, 0x3F, 0x00, 0x09, 0x20, 0x00, 0x08, + 0x20, // 205 + 0x00, 0x00, 0x00, 0x0A, 0x20, 0x00, 0x0B, 0x20, 0x00, 0xF9, 0x3F, 0x00, 0xF9, 0x3F, 0x00, 0x0B, 0x20, 0x00, 0x0A, + 0x20, // 206 + 0x00, 0x00, 0x00, 0x0B, 0x20, 0x00, 0x0B, 0x20, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x0B, 0x20, 0x00, 0x0B, + 0x20, // 207 + 0x80, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x88, 0x20, 0x00, 0x88, 0x20, 0x00, 0x18, 0x30, 0x00, 0xF0, 0x1F, 0x00, + 0xE0, + 0x0F, // 208 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xFB, 0x3F, 0x00, 0xF9, 0x00, 0x00, 0xC3, 0x07, 0x00, 0x02, 0x3E, 0x00, 0xFB, 0x3F, 0x00, + 0xF8, + 0x3F, // 209 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0xF0, 0x1F, 0x00, 0x19, 0x30, 0x00, 0x0B, 0x20, 0x00, 0x1A, 0x30, 0x00, 0xF0, 0x1F, 0x00, + 0xE0, + 0x0F, // 210 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0xF0, 0x1F, 0x00, 0x1A, 0x30, 0x00, 0x0B, 0x20, 0x00, 0x19, 0x30, 0x00, 0xF0, 0x1F, 0x00, + 0xE0, + 0x0F, // 211 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0xF2, 0x1F, 0x00, 0x1B, 0x30, 0x00, 0x09, 0x20, 0x00, 0x1B, 0x30, 0x00, 0xF2, 0x1F, 0x00, + 0xE0, + 0x0F, // 212 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0xF3, 0x1F, 0x00, 0x19, 0x30, 0x00, 0x0B, 0x20, 0x00, 0x1A, 0x30, 0x00, 0xF3, 0x1F, 0x00, + 0xE0, + 0x0F, // 213 + 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0xF3, 0x1F, 0x00, 0x1B, 0x30, 0x00, 0x08, 0x20, 0x00, 0x1B, 0x30, 0x00, 0xF3, 0x1F, 0x00, + 0xE0, + 0x0F, // 214 + 0x00, 0x00, 0x00, 0x80, 0x08, 0x00, 0xC0, 0x1D, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x07, 0x00, 0x80, 0x0F, 0x00, 0xC0, 0x1D, 0x00, + 0x80, + 0x08, // 215 + 0x00, 0x20, 0x00, 0xE0, 0x3F, 0x00, 0xF0, 0x3F, 0x00, 0x18, 0x3E, 0x00, 0x88, 0x23, 0x00, 0xF8, 0x30, 0x00, 0xF8, 0x1F, 0x00, + 0xF8, + 0x0F, // 216 + 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0xF8, 0x3F, 0x00, 0x01, 0x20, 0x00, 0x03, 0x20, 0x00, 0x02, 0x20, 0x00, 0xF8, 0x3F, 0x00, + 0xF8, + 0x1F, // 217 + 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0xF8, 0x3F, 0x00, 0x02, 0x20, 0x00, 0x03, 0x20, 0x00, 0x01, 0x20, 0x00, 0xF8, 0x3F, 0x00, + 0xF8, + 0x1F, // 218 + 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0xFA, 0x3F, 0x00, 0x03, 0x20, 0x00, 0x01, 0x20, 0x00, 0x03, 0x20, 0x00, 0xFA, 0x3F, 0x00, + 0xF8, + 0x1F, // 219 + 0x00, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0xFB, 0x3F, 0x00, 0x03, 0x20, 0x00, 0x00, 0x20, 0x00, 0x03, 0x20, 0x00, 0xFB, 0x3F, 0x00, + 0xF8, + 0x1F, // 220 + 0x08, 0x00, 0x00, 0x38, 0x00, 0x00, 0xF8, 0x01, 0x00, 0xC2, 0x3F, 0x00, 0xC3, 0x3F, 0x00, 0xF9, 0x01, 0x00, 0x38, 0x00, 0x00, + 0x08, // 221 + 0x00, 0x00, 0x00, 0xF8, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x20, 0x08, 0x00, 0x20, 0x08, 0x00, 0x60, 0x0C, 0x00, 0xE0, 0x0F, 0x00, + 0xC0, + 0x07, // 222 + 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0xF8, 0x3F, 0x00, 0x08, 0x00, 0x00, 0xC8, 0x23, 0x00, 0xF8, 0x27, 0x00, 0x30, 0x3C, 0x00, + 0x00, + 0x1C, // 223 + 0x00, 0x00, 0x00, 0x04, 0x1C, 0x00, 0x8C, 0x3E, 0x00, 0x58, 0x22, 0x00, 0x50, 0x22, 0x00, 0x40, 0x32, 0x00, 0xC0, 0x3F, 0x00, + 0x80, + 0x3F, // 224 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x80, 0x3E, 0x00, 0x50, 0x22, 0x00, 0x58, 0x22, 0x00, 0x4C, 0x32, 0x00, 0xC4, 0x3F, 0x00, + 0x80, + 0x3F, // 225 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x90, 0x3E, 0x00, 0x58, 0x22, 0x00, 0x4C, 0x22, 0x00, 0x4C, 0x32, 0x00, 0xD8, 0x3F, 0x00, + 0x90, + 0x3F, // 226 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x8C, 0x3E, 0x00, 0x44, 0x22, 0x00, 0x4C, 0x22, 0x00, 0x48, 0x32, 0x00, 0xCC, 0x3F, 0x00, + 0x80, + 0x3F, // 227 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x8C, 0x3E, 0x00, 0x4C, 0x22, 0x00, 0x40, 0x22, 0x00, 0x4C, 0x32, 0x00, 0xCC, 0x3F, 0x00, + 0x80, + 0x3F, // 228 + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x8C, 0x3E, 0x00, 0x52, 0x22, 0x00, 0x52, 0x22, 0x00, 0x4C, 0x32, 0x00, 0xC0, 0x3F, 0x00, + 0x80, + 0x3F, // 229 + 0x00, 0x1C, 0x00, 0x40, 0x3E, 0x00, 0x40, 0x22, 0x00, 0xC0, 0x3F, 0x00, 0xC0, 0x3F, 0x00, 0x40, 0x22, 0x00, 0xC0, 0x23, 0x00, + 0x80, + 0x23, // 230 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x1F, 0x00, 0xC0, 0x30, 0x00, 0x40, 0x20, 0x01, 0x40, 0x60, 0x01, 0x40, 0xA0, 0x01, + 0x80, + 0x10, // 231 + 0x00, 0x00, 0x00, 0x04, 0x0F, 0x00, 0x8C, 0x1F, 0x00, 0x58, 0x32, 0x00, 0x50, 0x22, 0x00, 0x40, 0x22, 0x00, 0xC0, 0x23, 0x00, + 0x80, + 0x13, // 232 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x1F, 0x00, 0x50, 0x32, 0x00, 0x58, 0x22, 0x00, 0x4C, 0x22, 0x00, 0xC4, 0x23, 0x00, + 0x80, + 0x13, // 233 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x90, 0x1F, 0x00, 0x58, 0x32, 0x00, 0x4C, 0x22, 0x00, 0x4C, 0x22, 0x00, 0xD8, 0x23, 0x00, + 0x90, + 0x13, // 234 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x8C, 0x1F, 0x00, 0x4C, 0x32, 0x00, 0x40, 0x22, 0x00, 0x4C, 0x22, 0x00, 0xCC, 0x23, 0x00, + 0x80, + 0x13, // 235 + 0x00, 0x00, 0x00, 0x04, 0x20, 0x00, 0x4C, 0x20, 0x00, 0x58, 0x20, 0x00, 0xD0, 0x3F, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x20, 0x00, 0x00, + 0x20, // 236 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x20, 0x00, 0x50, 0x20, 0x00, 0xD8, 0x3F, 0x00, 0xCC, 0x3F, 0x00, 0x04, 0x20, 0x00, + 0x00, 0x20, 0x00, 0x00, + 0x20, // 237 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x20, 0x00, 0x58, 0x20, 0x00, 0xCC, 0x3F, 0x00, 0xCC, 0x3F, 0x00, 0x18, 0x20, 0x00, + 0x10, 0x20, 0x00, 0x00, + 0x20, // 238 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x4C, 0x20, 0x00, 0x4C, 0x20, 0x00, 0xC0, 0x3F, 0x00, 0xCC, 0x3F, 0x00, 0x0C, 0x20, 0x00, + 0x00, 0x20, 0x00, 0x00, + 0x20, // 239 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xE0, 0x1F, 0x00, 0xD8, 0x30, 0x00, 0x78, 0x20, 0x00, 0x78, 0x30, 0x00, 0xE8, 0x1F, 0x00, + 0x80, + 0x0F, // 240 + 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x00, 0xCC, 0x3F, 0x00, 0x44, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x48, 0x00, 0x00, 0xCC, 0x3F, 0x00, + 0x80, + 0x3F, // 241 + 0x00, 0x00, 0x00, 0x04, 0x0F, 0x00, 0x8C, 0x1F, 0x00, 0xD8, 0x30, 0x00, 0x50, 0x20, 0x00, 0xC0, 0x30, 0x00, 0x80, 0x1F, 0x00, + 0x00, + 0x0F, // 242 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x80, 0x1F, 0x00, 0xD0, 0x30, 0x00, 0x58, 0x20, 0x00, 0xCC, 0x30, 0x00, 0x84, 0x1F, 0x00, + 0x00, + 0x0F, // 243 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x90, 0x1F, 0x00, 0xDC, 0x30, 0x00, 0x44, 0x20, 0x00, 0xDC, 0x30, 0x00, 0x90, 0x1F, 0x00, + 0x00, + 0x0F, // 244 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x8C, 0x1F, 0x00, 0xC4, 0x30, 0x00, 0x4C, 0x20, 0x00, 0xC8, 0x30, 0x00, 0x8C, 0x1F, 0x00, + 0x00, + 0x0F, // 245 + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x8C, 0x1F, 0x00, 0xCC, 0x30, 0x00, 0x40, 0x20, 0x00, 0xCC, 0x30, 0x00, 0x8C, 0x1F, 0x00, + 0x00, + 0x0F, // 246 + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x60, 0x1B, 0x00, 0x60, 0x1B, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x00, + 0x03, // 247 + 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x80, 0x3F, 0x00, 0xC0, 0x3C, 0x00, 0x40, 0x26, 0x00, 0xC0, 0x33, 0x00, 0xC0, 0x1F, 0x00, + 0xE0, + 0x0F, // 248 + 0x00, 0x00, 0x00, 0xC4, 0x1F, 0x00, 0xCC, 0x3F, 0x00, 0x18, 0x20, 0x00, 0x10, 0x20, 0x00, 0x00, 0x20, 0x00, 0xC0, 0x3F, 0x00, + 0xC0, + 0x3F, // 249 + 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0xC0, 0x3F, 0x00, 0x10, 0x20, 0x00, 0x18, 0x20, 0x00, 0x0C, 0x20, 0x00, 0xC4, 0x3F, 0x00, + 0xC0, + 0x3F, // 250 + 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0xD0, 0x3F, 0x00, 0x1C, 0x20, 0x00, 0x04, 0x20, 0x00, 0x1C, 0x20, 0x00, 0xD0, 0x3F, 0x00, + 0xC0, + 0x3F, // 251 + 0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0xCC, 0x3F, 0x00, 0x0C, 0x20, 0x00, 0x00, 0x20, 0x00, 0x0C, 0x20, 0x00, 0xCC, 0x3F, 0x00, + 0xC0, + 0x3F, // 252 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC0, 0x03, 0x01, 0x90, 0x8F, 0x01, 0x18, 0xFC, 0x01, 0x8C, 0x7F, 0x00, 0xC4, 0x07, 0x00, + 0xC0, // 253 + 0x00, 0x00, 0x00, 0xF8, 0xFF, 0x01, 0xF8, 0xFF, 0x01, 0xC0, 0x30, 0x00, 0x40, 0x20, 0x00, 0xC0, 0x30, 0x00, 0xC0, 0x3F, 0x00, + 0x80, + 0x1F, // 254 + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xCC, 0x03, 0x01, 0x8C, 0x8F, 0x01, 0x00, 0xFC, 0x01, 0x8C, 0x7F, 0x00, 0xCC, 0x07, 0x00, + 0xC0 // 255 + }; diff --git a/Software/src/Version 5/wklfonts.h b/Software/src/Version 5/wklfonts.h new file mode 100644 index 0000000..0ed31b6 --- /dev/null +++ b/Software/src/Version 5/wklfonts.h @@ -0,0 +1,22 @@ +#ifndef WKLFONTS_H +#define WKLFONTS_H + +#include + +// Created by http://oleddisplay.squix.ch/ Consider a donation +// In case of problems make sure that you are using the font file with the correct version! +extern uint8_t DialogInput_plain_12[] PROGMEM; + +// Created by http://oleddisplay.squix.ch/ Consider a donation +// In case of problems make sure that you are using the font file with the correct version! +extern uint8_t DialogInput_plain_15[] PROGMEM; + +// Created by http://oleddisplay.squix.ch/ Consider a donation +// In case of problems make sure that you are using the font file with the correct version! +extern uint8_t DialogInput_bold_12[] PROGMEM; + +// Created by http://oleddisplay.squix.ch/ Consider a donation +// In case of problems make sure that you are using the font file with the correct version! +extern uint8_t DialogInput_bold_15[] PROGMEM; + +#endif