diff options
Diffstat (limited to 'sw/eval-clock-cw-tx')
| -rw-r--r-- | sw/eval-clock-cw-tx/README.md | 9 | ||||
| -rwxr-xr-x | sw/eval-clock-cw-tx/gui.py | 222 | ||||
| -rw-r--r-- | sw/eval-clock-cw-tx/src/feldhell_font.rs | 277 | ||||
| -rw-r--r-- | sw/eval-clock-cw-tx/src/main.rs | 82 | ||||
| -rw-r--r-- | sw/eval-clock-cw-tx/src/state.rs | 25 | ||||
| -rw-r--r-- | sw/eval-clock-cw-tx/src/ui.rs | 5 | ||||
| -rw-r--r-- | sw/eval-clock-cw-tx/src/usb.rs | 318 | 
7 files changed, 881 insertions, 57 deletions
diff --git a/sw/eval-clock-cw-tx/README.md b/sw/eval-clock-cw-tx/README.md index de12a37..5a22503 100644 --- a/sw/eval-clock-cw-tx/README.md +++ b/sw/eval-clock-cw-tx/README.md @@ -1,11 +1,14 @@  CW TX firmware for eval-clock  ============================= -Mostly the same as ../picardy except where different +Sends CW and FELDHELL https://www.qsl.net/zl1bpu/HELL/Feld.htm -pios are different: both Si5351 and display are on same i2c +Taken from ../picardy and then adapted. Includes two FELDHELL fonts from fldigi, therefore the project is also +GPL-licensed, and my own code is MIT licensed. -PB3 is PPTOUTn +pios are different than on picardy: both Si5351 and display are on same i2c + +PB3 is PTTOUTn  PB15 is CWOUTn diff --git a/sw/eval-clock-cw-tx/gui.py b/sw/eval-clock-cw-tx/gui.py new file mode 100755 index 0000000..4efde84 --- /dev/null +++ b/sw/eval-clock-cw-tx/gui.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +# +#   The MIT License (MIT) +# +#   Copyright (c) 2021 Matthias P. Braendli +# +#   Permission is hereby granted, free of charge, to any person obtaining a copy +#   of this software and associated documentation files (the "Software"), to deal +#   in the Software without restriction, including without limitation the rights +#   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#   copies of the Software, and to permit persons to whom the Software is +#   furnished to do so, subject to the following conditions: +# +#   The above copyright notice and this permission notice shall be included in all +#   copies or substantial portions of the Software. +# +#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +#   SOFTWARE. + +import socket +import serial +from tkinter import * +from tkinter import ttk +from functools import partial +import sys + +NUM_MACROS=5 + +DEFAULT_MACROS=["CQ WCA de HB9HI HB9HI HB9HI pse K", "REF WCA HB-xxxxx", "12345667890123455678901234567890", "", ""] + +class GUI(Frame): +    def __init__(self, root): +        super().__init__(root) +        self.root = root +        self.create_widgets() +        self.last_tx_freq = None +        self.last_serial_message = bytes() +        try: +            self.serial = serial.Serial("/dev/ttyACM0", 115200, timeout=0) +            print(f"Opened serial port {self.serial.name}") +        except Exception as e: +            self.serial = None +            self.set_status(f"Serial port failed {e}") + +        self.root.after(1000, self.fetch_gqrx_freq) +        self.root.after(1000, self.read_serial) + +    def read_serial(self): +        if self.serial is not None: +            try: +                dat = self.serial.read(1) +                if dat: +                    self.last_serial_message += dat +                    if self.last_serial_message.endswith(b"\n"): +                        print(f"< {self.last_serial_message.decode().strip()}") +                        self.set_status(f"Received: {self.last_serial_message.decode().strip()}") +                        self.last_serial_message = bytes() +            except serial.SerialException as e: +                self.set_status(f"Serial read failed: {e:!r}") +            except Exception as e: +                self.set_status(f"Serial read failed: {e:!r}") +            self.root.after(10, self.read_serial) +        else: +            self.root.after(1000, self.read_serial) + +    def fetch_gqrx_freq(self): +        gqrx_sock = socket.socket() +        gqrx_sock.settimeout(100) +        try: +            gqrx_sock.connect(("127.0.0.1", 7356)) +            gqrx_sock.send(b"f\n") +            buf = gqrx_sock.recv(1024) + +            freq = int(buf.decode()) +            if self.enable_freq_sync.get(): +                self.gqrx_freq_var.set(freq) + +                tx_freq = freq + self.gqrx_offset_var.get() + +                if self.last_tx_freq != tx_freq: +                    self.last_tx_freq = tx_freq +                    self.send_serial('f{}\n'.format(tx_freq).encode("ascii")) + +            self.root.after(100, self.fetch_gqrx_freq) +        except Exception as e: +            self.set_status(f"GQRX socket error {e}") +            self.root.after(1000, self.fetch_gqrx_freq) + +        gqrx_sock.close() + +    def terminate(self): +        print("Terminate: send RX") +        self.send_serial(b'rx\n') + +    def load_macro(self, i): +        self.tx_message.set(self.macro_widgets[i]["var"].get()) + +    def send_serial(self, message): +        if self.serial is not None: +            self.serial.write(message) + +    def set_font_narrow(self): +        print("Set font narrow") +        self.send_serial(b'fontn\n') + +    def set_font_wide(self): +        print("Set font wide") +        self.send_serial(b'fontw\n') + +    def send_tx_message(self): +        msg = self.tx_message.get() +        print(f"Send '{msg}'") +        self.send_serial('m{}\n'.format(msg).encode("ascii")) + +    def toggle_tx_enabled(self): +        transmit = self.tx_enabled_var.get() +        if transmit: +            print("Enable TX") +            self.send_serial(b'tx\n') +        else: +            print("Disable TX") +            self.send_serial(b'rx\n') + +    def create_widgets(self): +        self.root.columnconfigure(0, weight=1) +        self.root.rowconfigure(0, weight=1) +        self.top_frame = Frame(self.root, borderwidth=5, relief="ridge", width=800, height=600) +        self.top_frame.grid(column=0, row=0, sticky=(N, S, E, W)) +        self.top_frame.columnconfigure(0, weight=1) +        self.top_frame.columnconfigure(1, weight=1) +        self.top_frame.rowconfigure(0, weight=0) +        self.top_frame.rowconfigure(1, weight=1) + +        self.macros_frame = Frame(self.top_frame, borderwidth=1, relief="ridge") +        self.macros_frame.grid(column=0, row=0, sticky=(N, S, E, W)) + +        self.macro_widgets = [] +        for i in range(NUM_MACROS): +            tv = StringVar() +            tv.set(DEFAULT_MACROS[i]) +            e = Entry(self.macros_frame, textvariable=tv, width=80) +            e.grid(column=0, row=i) +            b = Button(self.macros_frame, text=f"Load {i}", command=partial(self.load_macro, i)) +            b.grid(column=1, row=i) + +            self.macro_widgets.append({'entry': e, 'button': b, 'var': tv}) + + +        self.control_frame = Frame(self.top_frame, borderwidth=1, relief="ridge") +        self.control_frame.grid(column=1, row=0, sticky=(N, S, E, W)) + +        self.font_frame = Frame(self.control_frame, borderwidth=1, relief="ridge") +        self.font_frame.grid(column=0, row=0) +        self.font_label = Label(self.font_frame, text="Font selection") +        self.font_label.grid(column=0, row=0) +        self.button_font_narrow = Button(self.font_frame, text="Narrow", command=self.set_font_narrow) +        self.button_font_narrow.grid(column=0, row=1) +        self.button_font_wide = Button(self.font_frame, text="Wide", command=self.set_font_wide) +        self.button_font_wide.grid(column=1, row=1) + +        self.gqrx_frame = Frame(self.control_frame, borderwidth=1, relief="ridge") +        self.gqrx_frame.grid(column=0, row=1, sticky=(N, S, E, W)) +        self.enable_freq_sync = IntVar() +        self.gqrx_checkbutton = Checkbutton(self.gqrx_frame, text="Sync", variable=self.enable_freq_sync) +        self.gqrx_checkbutton.grid(column=0, row=0, columnspan=1) + +        self.gqrx_freq_label = Label(self.gqrx_frame, text="Frequency") +        self.gqrx_freq_label.grid(column=0, row=1) +        self.gqrx_freq_var = IntVar() +        self.gqrx_freq = Label(self.gqrx_frame, width=10, textvariable=self.gqrx_freq_var) +        self.gqrx_freq.grid(column=1, row=1, columnspan=1, sticky=(N, S, E, W)) + +        self.gqrx_offset_label = Label(self.gqrx_frame, text="Offset") +        self.gqrx_offset_label.grid(column=0, row=2) +        self.gqrx_offset_var = IntVar() +        self.gqrx_offset_var.set(800) +        self.gqrx_offset = Entry(self.gqrx_frame, width=10, textvariable=self.gqrx_offset_var) +        self.gqrx_offset.grid(column=1, row=2, columnspan=1, sticky=(N, S, E, W)) + + +        self.tx_frame = Frame(self.control_frame, borderwidth=1, relief="ridge") +        self.tx_frame.grid(column=0, row=2, sticky=(N, S, E, W)) +        self.tx_enabled_var = IntVar() +        self.tx_checkbutton = Checkbutton(self.tx_frame, text="Transmit", variable=self.tx_enabled_var, command=self.toggle_tx_enabled) +        self.tx_checkbutton.grid(column=0, row=0, columnspan=2) + +        self.transmit_frame = Frame(self.top_frame, borderwidth=1, relief="ridge") +        self.transmit_frame.grid(column=0, row=1, columnspan=2) +        self.transmit_frame.columnconfigure(0, weight=1) +        self.transmit_frame.columnconfigure(1, weight=1) + +        self.tx_message = StringVar() +        self.tx_message_entry = Entry(self.transmit_frame, width=90, textvariable=self.tx_message) +        self.tx_message_entry.grid(column=0, row=0, sticky=(N, S, E, W)) +        self.tx_message_entry.columnconfigure(0, weight=1) +        self.tx_message_entry.columnconfigure(1, weight=1) +        self.tx_message_button = Button(self.transmit_frame, text="Send", command=self.send_tx_message) +        self.tx_message_button.grid(column=0, row=1, sticky=(N, S, E, W)) + +        self.status_var = StringVar() +        self.status_label = Label(self.top_frame, bd=1, relief=SUNKEN, anchor=W, textvariable=self.status_var) +        self.status_label.grid(column=0, row=2, columnspan=2, sticky=(N, S, E, W)) +        self.set_status("") + +    def set_status(self, message): +        self.status_var.set(f"Status: {message}") + + +root = Tk() + +app = GUI(root) + +try: +    root.mainloop() +finally: +    app.terminate() + diff --git a/sw/eval-clock-cw-tx/src/feldhell_font.rs b/sw/eval-clock-cw-tx/src/feldhell_font.rs new file mode 100644 index 0000000..e4f65d2 --- /dev/null +++ b/sw/eval-clock-cw-tx/src/feldhell_font.rs @@ -0,0 +1,277 @@ +// ---------------------------------------------------------------------------- +// Copyright (C) 2014 +//              David Freese, W1HKJ +// +// This file is part of fldigi +// +// fldigi 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. +// +// fldigi 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 <http://www.gnu.org/licenses/>. +// ---------------------------------------------------------------------------- + +/* Format of the arrays: + * Each line is a character, first line is space (0x20), last one is the tilda 0x7E. + * 14 lines, 8 columns: Each u8 is one line + * + * FELDHELL letters are scanned bottom-to-top, left-to-right */ + +// feld-fat font +static FONT_FELDHELL_FAT : [ [u8; 14]; 95] = [ +    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0xC0, 0xC0 ], +    [ 0x00, 0x00, 0xD8, 0xD8, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x6C, 0x6C, 0xFE, 0xFE, 0x6C, 0x6C, 0xFE, 0xFE, 0x6C, 0x6C, 0x00, 0x00 ], +    [ 0x00, 0x10, 0x7E, 0xFE, 0xD0, 0xD0, 0xFC, 0x7E, 0x16, 0x16, 0xFE, 0xFC, 0x10, 0x00 ], +    [ 0x00, 0xE6, 0xE6, 0xE6, 0x06, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xCE, 0xCE, 0xCE, 0x00 ], +    [ 0x00, 0x18, 0x3E, 0x66, 0x60, 0x30, 0x7B, 0x7F, 0xCE, 0xC6, 0xC7, 0xFF, 0x7C, 0x00 ], +    [ 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x18, 0x38, 0x70, 0xE0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE0, 0x70, 0x38, 0x18, 0x00 ], +    [ 0x00, 0xC0, 0xE0, 0x70, 0x38, 0x18, 0x18, 0x18, 0x18, 0x38, 0x70, 0xE0, 0xC0, 0x00 ], +    [ 0x00, 0x00, 0x10, 0x10, 0x10, 0xFE, 0xFE, 0x38, 0x38, 0x6C, 0xC6, 0x82, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE0, 0x60, 0x60, 0xE0, 0xC0, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE0, 0xE0, 0x00, 0x00 ], +    [ 0x00, 0x0C, 0x0C, 0x1C, 0x18, 0x38, 0x30, 0x70, 0x60, 0xE0, 0xC0, 0xC0, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x7C, 0xFE, 0xCE, 0xCE, 0xD6, 0xD6, 0xE6, 0xE6, 0xFE, 0x7C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x30, 0x70, 0xF0, 0xF0, 0x30, 0x30, 0x30, 0x30, 0xFC, 0xFC, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x7C, 0xFE, 0xC6, 0x06, 0x3E, 0x7C, 0xC0, 0xC0, 0xFE, 0xFE, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x7C, 0xFE, 0xC6, 0x06, 0x1C, 0x1C, 0x06, 0xC6, 0xFE, 0x7C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x3C, 0x7C, 0xEC, 0xCC, 0xCC, 0xFE, 0xFE, 0x0C, 0x0C, 0x0C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xFE, 0xFE, 0xC0, 0xC0, 0xFC, 0xFE, 0x06, 0x06, 0xFE, 0xFC, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x7C, 0xFC, 0xC0, 0xC0, 0xFC, 0xFE, 0xC6, 0xC6, 0xFE, 0x7C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xFE, 0xFE, 0x0C, 0x0C, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x7C, 0xFE, 0xC6, 0xC6, 0x7C, 0x7C, 0xC6, 0xC6, 0xFE, 0x7C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x7C, 0xFE, 0xC6, 0xC6, 0xFE, 0x7E, 0x06, 0x06, 0x7E, 0x7C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x60, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x20, 0x20, 0xE0, 0xC0, 0x00 ], +    [ 0x00, 0x00, 0x0C, 0x1C, 0x38, 0x70, 0xE0, 0xE0, 0x70, 0x38, 0x1C, 0x0C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x1C, 0x38, 0x70, 0xE0, 0xC0, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x78, 0xFC, 0xCC, 0x0C, 0x38, 0x70, 0x60, 0x00, 0x00, 0x60, 0x60, 0x00 ], +    [ 0x00, 0x7C, 0xFE, 0xC6, 0xC6, 0xDE, 0xDE, 0xDC, 0xC0, 0xC0, 0x7C, 0x3C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x38, 0x7C, 0xEE, 0xC6, 0xC6, 0xFE, 0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xFC, 0xFE, 0xC6, 0xC6, 0xFC, 0xFC, 0xC6, 0xC6, 0xFE, 0xFC, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x3C, 0x7E, 0xE6, 0xC0, 0xC0, 0xC0, 0xC0, 0xE6, 0x7E, 0x3C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xF8, 0xFC, 0xCE, 0xC6, 0xC6, 0xC6, 0xC6, 0xCE, 0xFC, 0xF8, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xFE, 0xFE, 0xC0, 0xC0, 0xF8, 0xF8, 0xC0, 0xC0, 0xFE, 0xFE, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xFE, 0xFE, 0xC0, 0xC0, 0xF8, 0xF8, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x3E, 0x7E, 0xE0, 0xC0, 0xCE, 0xCE, 0xC6, 0xE6, 0x7E, 0x3C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xF0, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0xF0, 0xF0, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x0E, 0x0E, 0x06, 0x06, 0x06, 0x06, 0xC6, 0xC6, 0xFE, 0x7C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC6, 0xC6, 0xCE, 0xDC, 0xF8, 0xF8, 0xDC, 0xCE, 0xC6, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0xFE, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x38, 0x7C, 0xEE, 0xC6, 0xC6, 0xC6, 0xC6, 0xEE, 0x7C, 0x38, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xFC, 0xFE, 0xC6, 0xC6, 0xFE, 0xFC, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x7C, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0xDE, 0xDE, 0xFE, 0x7C, 0x0C, 0x0C ], +    [ 0x00, 0x00, 0xFC, 0xFE, 0xC6, 0xC6, 0xFE, 0xFC, 0xCC, 0xCE, 0xC6, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x7E, 0xFE, 0xC0, 0xC0, 0xFC, 0x7E, 0x06, 0x06, 0xFE, 0xFC, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xFC, 0xFC, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0x7C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x6C, 0x38, 0x38, 0x10, 0x10, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6, 0xD6, 0xFE, 0xFE, 0x6C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC6, 0xC6, 0xEE, 0x7C, 0x38, 0x38, 0x7C, 0xEE, 0xC6, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xFC, 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xFE, 0xFE, 0x0E, 0x0C, 0x18, 0x30, 0x60, 0xE0, 0xFE, 0xFE, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xF0, 0xF0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xF0, 0xF0, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC0, 0xC0, 0xE0, 0x60, 0x70, 0x30, 0x38, 0x18, 0x1C, 0x0C, 0x0C, 0x00 ], +    [ 0x00, 0x00, 0xF0, 0xF0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xF0, 0xF0, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x20, 0x20, 0x70, 0x70, 0xF8, 0xD8, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x7C, 0x7E, 0x06, 0x7E, 0xFE, 0xC6, 0xFE, 0x7E, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC0, 0xC0, 0xDC, 0xFE, 0xE6, 0xC6, 0xC6, 0xC6, 0xFE, 0xFC, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x7C, 0xFC, 0xC0, 0xC0, 0xC0, 0xC0, 0xFC, 0x7C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x06, 0x06, 0x76, 0xFE, 0xCE, 0xC6, 0xC6, 0xC6, 0xFE, 0x7E, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x7C, 0xFE, 0xC6, 0xFE, 0xFE, 0xC0, 0xFC, 0x7C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x3C, 0x7C, 0x60, 0x60, 0xF8, 0xF8, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x7E, 0xFE, 0xC6, 0xC6, 0xC6, 0xFE, 0x7E, 0x06, 0x7E, 0x7C ], +    [ 0x00, 0x00, 0xC0, 0xC0, 0xDC, 0xFE, 0xEE, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x60, 0x60, 0x00, 0xE0, 0xE0, 0x60, 0x60, 0x60, 0x60, 0xF0, 0xF0, 0x00, 0x00 ], +    [ 0x18, 0x18, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0xD8, 0xF8, 0x70, 0x00 ], +    [ 0x00, 0x00, 0xC0, 0xCC, 0xDC, 0xF8, 0xF0, 0xF0, 0xF8, 0xDC, 0xCC, 0xCC, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xE0, 0xE0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0xF0, 0xF0, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFE, 0xFE, 0xD6, 0xD6, 0xC6, 0xC6, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xDC, 0xFE, 0xEE, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x7C, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0x7C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFE, 0xC6, 0xC6, 0xE6, 0xFE, 0xDC, 0xC0, 0xC0, 0xC0 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x7E, 0xFE, 0xC6, 0xC6, 0xCE, 0xFE, 0x76, 0x06, 0x06, 0x06 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xDC, 0xFE, 0xE6, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x7E, 0xFE, 0xC0, 0xFC, 0x7E, 0x06, 0xFE, 0xFC, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x60, 0x60, 0xF0, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x78, 0x38, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xCE, 0xFE, 0x76, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xEE, 0x7C, 0x38, 0x10, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xD6, 0xD6, 0xFE, 0xFE, 0x6C, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xC6, 0xEE, 0x7C, 0x38, 0x38, 0x7C, 0xEE, 0xC6, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xEE, 0x7C, 0x38, 0x30, 0x70, 0xE0, 0xC0 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0x0C, 0x18, 0x30, 0x60, 0xFE, 0xFE, 0x00, 0x00 ], +    [ 0x00, 0x30, 0x70, 0x60, 0x60, 0x60, 0xE0, 0xE0, 0x60, 0x60, 0x70, 0x70, 0x30, 0x00 ], +    [ 0x00, 0xC0, 0xC0, 0xE0, 0x60, 0x70, 0x30, 0x38, 0x18, 0x1C, 0x0C, 0x0C, 0x00, 0x00 ], +    [ 0x00, 0xC0, 0xE0, 0x60, 0x60, 0x60, 0x70, 0x70, 0x60, 0x60, 0x60, 0xE0, 0xC0, 0x00 ], +    [ 0x00, 0x00, 0x20, 0x70, 0xF8, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    ]; + +static FONT_FELDHELL : [ [u8; 14]; 95] = [ +    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0xC0, 0xC0, 0x00 ], +    [ 0x00, 0xA0, 0xA0, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x50, 0x50, 0xF8, 0xF8, 0x50, 0x50, 0xF8, 0xF8, 0x50, 0x50, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x20, 0x70, 0xF8, 0xA8, 0xA0, 0xF0, 0x78, 0x28, 0xA8, 0xF8, 0x70, 0x20, 0x00 ], +    [ 0x00, 0xC8, 0xC8, 0xC8, 0x10, 0x10, 0x20, 0x20, 0x40, 0x40, 0x98, 0x98, 0x98, 0x00 ], +    [ 0x00, 0x40, 0xE0, 0xA0, 0xA0, 0x40, 0x40, 0xA0, 0xA0, 0x90, 0x90, 0xF8, 0x78, 0x00 ], +    [ 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x20, 0x60, 0xC0, 0xC0, 0x80, 0x80, 0xC0, 0xC0, 0x60, 0x20, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x80, 0xC0, 0x60, 0x60, 0x20, 0x20, 0x60, 0x60, 0xC0, 0x80, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x88, 0x88, 0x50, 0x70, 0xF8, 0xF8, 0x70, 0x50, 0x88, 0x88, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0xF8, 0xF8, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x20, 0x20, 0xE0, 0xC0 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0x00, 0x00 ], +    [ 0x00, 0x08, 0x08, 0x18, 0x10, 0x30, 0x20, 0x60, 0x40, 0xC0, 0x80, 0x80, 0x00, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0x98, 0x98, 0xA8, 0xA8, 0xC8, 0xC8, 0xF8, 0x70, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x20, 0x60, 0xE0, 0xA0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x60, 0xF0, 0x90, 0x10, 0x30, 0x20, 0x40, 0xC0, 0xF8, 0xF8, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF0, 0xF8, 0x08, 0x08, 0x38, 0x38, 0x08, 0x08, 0xF8, 0xF0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x80, 0x90, 0x90, 0x90, 0x90, 0x90, 0xF8, 0xF8, 0x10, 0x10, 0x10, 0x00, 0x00 ], +    [ 0x00, 0x78, 0x78, 0x40, 0x40, 0x70, 0x78, 0x08, 0x08, 0x18, 0xF0, 0xE0, 0x00, 0x00 ], +    [ 0x00, 0x20, 0x60, 0xC0, 0x80, 0x80, 0xB0, 0xF8, 0xC8, 0x88, 0x88, 0xF8, 0x70, 0x00 ], +    [ 0x00, 0xF8, 0xF8, 0x18, 0x10, 0x30, 0x20, 0x60, 0x40, 0xC0, 0x80, 0x80, 0x00, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0x88, 0x88, 0xD8, 0x70, 0x70, 0xD8, 0x88, 0x88, 0xF8, 0x70, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0x88, 0x88, 0xF8, 0x78, 0x08, 0x08, 0x18, 0x70, 0x60, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0x00, 0xC0, 0xC0, 0xC0, 0x00, 0x00 ], +    [ 0x00, 0x00, 0xC0, 0x00, 0x60, 0x60, 0x60, 0x00, 0x00, 0x60, 0x60, 0x20, 0x20, 0xE0 ], +    [ 0x00, 0x00, 0x08, 0x18, 0x30, 0x60, 0xC0, 0xC0, 0x60, 0x30, 0x18, 0x08, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x00, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0x88, 0x08, 0x18, 0x30, 0x20, 0x20, 0x00, 0x00, 0x20, 0x20, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0x88, 0x88, 0xB8, 0xB8, 0xB0, 0x80, 0xC0, 0x78, 0x38, 0x00, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0xD8, 0x88, 0x88, 0xF8, 0xF8, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF0, 0xF8, 0x48, 0x48, 0x70, 0x70, 0x48, 0x48, 0xF8, 0xF0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0xC8, 0x80, 0x80, 0x80, 0x80, 0xC8, 0xF8, 0x70, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF0, 0xF8, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0xF8, 0xF0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF8, 0xF8, 0x80, 0x80, 0xE0, 0xE0, 0x80, 0x80, 0xF8, 0xF8, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF8, 0xF8, 0x80, 0x80, 0xE0, 0xE0, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x78, 0xF8, 0xC0, 0x80, 0x98, 0x98, 0x88, 0xC8, 0xF8, 0x70, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0x88, 0x88, 0xF8, 0xF8, 0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xE0, 0xE0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0xE0, 0xE0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x88, 0x88, 0xF8, 0xF8, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0x98, 0xB0, 0xE0, 0xE0, 0xB0, 0x98, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8, 0xF8, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x82, 0xC6, 0xEE, 0xBA, 0x92, 0x82, 0x82, 0x82, 0x82, 0x82, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0xC8, 0xC8, 0xE8, 0xA8, 0xB8, 0x98, 0x98, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF8, 0x70, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF0, 0xF8, 0x48, 0x48, 0x78, 0x70, 0x40, 0x40, 0xE0, 0xE0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0xD8, 0x88, 0x88, 0xA8, 0xA8, 0xB8, 0xF0, 0x70, 0x18, 0x08, 0x00 ], +    [ 0x00, 0xF0, 0xF8, 0x88, 0x88, 0xF8, 0xF0, 0x90, 0x98, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x78, 0xF8, 0x80, 0x80, 0xF0, 0x78, 0x08, 0x08, 0xF8, 0xF0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF8, 0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF8, 0x70, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0x98, 0x90, 0xB0, 0xA0, 0xE0, 0xC0, 0xC0, 0x80, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0xBA, 0xEE, 0x44, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0xD8, 0x70, 0x20, 0x20, 0x70, 0xD8, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0xD8, 0x50, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF8, 0xF8, 0x18, 0x10, 0x30, 0x60, 0x48, 0xC8, 0xF8, 0xF8, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xE0, 0xE0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xE0, 0xE0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x80, 0x80, 0xC0, 0x40, 0x60, 0x20, 0x30, 0x10, 0x18, 0x08, 0x08, 0x00, 0x00 ], +    [ 0x00, 0xE0, 0xE0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xE0, 0xE0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x20, 0x20, 0x70, 0x50, 0xD8, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x20, 0x70, 0xD8, 0x88, 0x88, 0xF8, 0xF8, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF0, 0xF8, 0x48, 0x48, 0x78, 0x78, 0x48, 0x48, 0xF8, 0xF0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0xC8, 0x80, 0x80, 0x80, 0x80, 0xC8, 0xF8, 0x70, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF0, 0xF8, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0xF8, 0xF0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF8, 0xF8, 0x80, 0x80, 0xF8, 0xF8, 0x80, 0x80, 0xF8, 0xF8, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF8, 0xF8, 0x80, 0x80, 0xE0, 0xE0, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x78, 0xF8, 0xC0, 0x80, 0x98, 0x98, 0x88, 0xC8, 0xF8, 0x70, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0x88, 0x88, 0xF8, 0xF8, 0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xE0, 0xE0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0xE0, 0xE0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x88, 0x88, 0xF8, 0x70, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x98, 0x98, 0xB0, 0xE0, 0xE0, 0xB0, 0x98, 0x98, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8, 0xF8, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x82, 0xC6, 0xEE, 0xBA, 0x92, 0x82, 0x82, 0x82, 0x82, 0x82, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0xC8, 0xC8, 0xE8, 0xA8, 0xB8, 0x98, 0x98, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF8, 0x70, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF0, 0xF8, 0x48, 0x48, 0x78, 0x70, 0x40, 0x40, 0xE0, 0xE0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x70, 0xF8, 0xD8, 0x88, 0x88, 0xA8, 0xA8, 0xB8, 0xF0, 0x70, 0x18, 0x08, 0x00 ], +    [ 0x00, 0xF0, 0xF8, 0x88, 0x88, 0xF8, 0xF0, 0x90, 0x98, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x78, 0xF8, 0x80, 0x80, 0xF0, 0x78, 0x08, 0x08, 0xF8, 0xF0, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF8, 0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF8, 0x70, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0x98, 0x90, 0xB0, 0xA0, 0xE0, 0xC0, 0xC0, 0x80, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0xBA, 0xEE, 0x44, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0xD8, 0x70, 0x20, 0x20, 0x70, 0xD8, 0x88, 0x88, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x88, 0x88, 0xD8, 0x50, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00 ], +    [ 0x00, 0xF8, 0xF8, 0x18, 0x10, 0x30, 0x60, 0x48, 0xC8, 0xF8, 0xF8, 0x00, 0x00, 0x00 ], +    [ 0x00, 0x20, 0x60, 0x40, 0x40, 0x40, 0xC0, 0xC0, 0x40, 0x40, 0x40, 0x60, 0x20, 0x00 ], +    [ 0x00, 0x80, 0x80, 0xC0, 0x40, 0x60, 0x20, 0x30, 0x10, 0x18, 0x08, 0x08, 0x00, 0x00 ], +    [ 0x00, 0x80, 0xC0, 0x40, 0x40, 0x40, 0x60, 0x60, 0x40, 0x40, 0x40, 0xC0, 0x80, 0x00 ], +    [ 0x00, 0x20, 0x70, 0xD8, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], +    ]; + +fn get_letter(letter : u8, fat : bool) -> Option<&'static [u8; 14]> { +    if letter >= b' ' && letter <= 0x7e { +        let offset = letter - b' '; + +        Some(if fat { +            &FONT_FELDHELL_FAT[offset as usize] +        } +        else { +            &FONT_FELDHELL[offset as usize] +        }) +    } +    else { +        None +    } +} + +/* Returns the columns for the given letter. + * Bit 14 of each column says if the column needs to be transmitted or not. + * The rows are in bits 13 down to 0. + */ +pub fn get_letter_by_columns(letter : u8, fat : bool) -> Option<[u16; 8]> { +    if let Some(lines) = get_letter(letter, fat) { +        let mut pattern = [0u16; 8]; + +        // Count number of effective columns +        let effective_columns = if letter == b' ' { +            5 +        } +        else { +            9 - lines.iter() +                .fold(0x00, |acc, x| acc | x ) +                .trailing_zeros() +        } as usize; + +        assert!(effective_columns <= 8); + +        // lines is 14x u8, each u8 is a line +        // pattern is 8x u16, each u16 is a column, where bits 13..0 correspond to the 14 columns, +        // and bit 14 is always 1 to mark the end +        for j in 0..effective_columns { +            for i in 0..14 { +                if (lines[13-i] >> (7-j)) & 0x1 != 0 { +                    pattern[j] |= 1 << i; +                } +            } +            pattern[j] |= 1 << 14; +        } + +        Some(pattern) +    } +    else { +        None +    } +} diff --git a/sw/eval-clock-cw-tx/src/main.rs b/sw/eval-clock-cw-tx/src/main.rs index a548f81..0d70630 100644 --- a/sw/eval-clock-cw-tx/src/main.rs +++ b/sw/eval-clock-cw-tx/src/main.rs @@ -47,10 +47,10 @@ use hd44780_driver::{Cursor, CursorBlink, Display, DisplayMode, HD44780};  pub mod ui;  pub mod usb; +pub mod feldhell_font;  pub mod cw;  pub mod state;  pub mod si_clock; -pub mod log10f;  use state::*; @@ -59,6 +59,7 @@ const TICKS_PER_SECOND : u32 = 100;  struct SharedWithISR {      state : State,      last_sequence_state_change : u32, +    feldhell_ptt : bool,      cw_ptt_timestamp : u32,      cw_key_out_n : gpio::gpioa::PA15<gpio::Output<gpio::PushPull>>,      ui : ui::UI, @@ -78,12 +79,6 @@ fn _ticks_now() -> u32 {      cortex_m::interrupt::free(|_cs| unsafe { *TICK_COUNTER.as_ptr() })  } -fn get_state_copy() -> State { -    cortex_m::interrupt::free(|_cs| unsafe { -        (*SHARED.as_ptr()).state.clone() -    }) -} -  #[cortex_m_rt::entry]  fn main() -> ! {      let cp = cortex_m::Peripherals::take().unwrap(); @@ -109,9 +104,14 @@ fn main() -> ! {      let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);      let mut gpioc = dp.GPIOC.split(&mut rcc.apb2); +    let mut timer4 = Timer::tim4(dp.TIM4, &clocks, &mut rcc.apb1) +        .start_count_down(usb::TIMER_FREQ_HZ.hz()); +    timer4.listen(Event::Update); +      let usb_dm = gpioa.pa11;      let usb_dp = gpioa.pa12.into_floating_input(&mut gpioa.crh); -    let usb = usb::USB::new(dp.USB, usb_dm, usb_dp); +    let mut usb = usb::USBData::new(timer4, dp.USB, usb_dm, usb_dp); +    usb::enable_interrupts();      // Buttons as analog inputs (multi-level)      let pb0 = gpiob.pb0.into_floating_input(&mut gpiob.crl); // BTN1 Button B, has external pullup @@ -184,13 +184,12 @@ fn main() -> ! {      lcd.write_str(" 30m CW TX 2021 ", &mut delay).unwrap();      delay.delay_ms(1_500u16); -    usb.enable_interrupts(); -      let mut siclock = {          let shared = unsafe { &mut *SHARED.as_mut_ptr() };          *shared = SharedWithISR {              state : State::new(),              last_sequence_state_change : 0, +            feldhell_ptt : false,              cw_ptt_timestamp : 0,              cw_key_out_n,              ui, @@ -202,7 +201,7 @@ fn main() -> ! {          si_clock::SiClock::new(i2c_busmanager.acquire_i2c(), 0, shared.state.vfo_display())      }; -    ui::update_disp(&mut lcd, &get_state_copy(), &mut delay); +    ui::update_disp(&mut lcd, unsafe { &(*SHARED.as_ptr()).state }, &mut delay);      let mut last_encoder_count = qei.count(); @@ -221,13 +220,31 @@ fn main() -> ! {      unsafe { pac::NVIC::unmask(pac::Interrupt::TIM2); }      let mut last_disp_update_counter = 1; +    let mut previous_usb_freq = usb.frequency;      let mut previous_vfo = 0;      let mut previous_state = SequenceState::Rx;      loop {          let mut update_disp_required = false; -        let state = get_state_copy(); +        usb.handle(); + +        if previous_usb_freq != usb.frequency { +            previous_usb_freq = usb.frequency; + +            cortex_m::interrupt::free(|_cs| { +                let shared = unsafe { &mut *SHARED.as_mut_ptr() }; +                shared.state.set_vfo(usb.frequency); +            }); + +            update_disp_required = true; +        } + +        let state = cortex_m::interrupt::free(|_cs| unsafe { +            let shared = SHARED.as_mut_ptr(); +            (*shared).feldhell_ptt = usb.is_transmit(); +            (*shared).state.clone() +        });          let encoder_count : u16 = qei.count();          if encoder_count != last_encoder_count { @@ -267,6 +284,7 @@ fn main() -> ! {      }  } +#[allow(non_snake_case)]  #[interrupt]  fn TIM2() {      let timer = unsafe { &mut *CLOCK_TIMER.as_mut_ptr() }; @@ -286,7 +304,7 @@ fn TIM2() {      let cw_paddle_ring_low = shared.cw_paddle_ring.is_low().unwrap();      let cw_ptt_delay : u32 = TICKS_PER_SECOND * 800 / 1000; -    let cw_ptt = match shared.state.mode { +    let ptt = match shared.state.mode {          Mode::CW(_) => {              if cw_paddle_tip_low || cw_paddle_ring_low {                  shared.cw_ptt_timestamp = *ticks; @@ -295,49 +313,58 @@ fn TIM2() {              else {                  shared.cw_ptt_timestamp + cw_ptt_delay > *ticks              } +        }, +        Mode::FeldHell => { +            shared.feldhell_ptt          }      };      let cw_beep = match shared.state.mode {          Mode::CW(CWMode::StraightKey) => cw_paddle_tip_low,          Mode::CW(CWMode::Iambic) => shared.cw_keyer.tick(*ticks, cw_paddle_tip_low, cw_paddle_ring_low), +        Mode::FeldHell => false, // Done in usb.c      };      let next_state = match shared.state.sequence_state {          SequenceState::Rx => {              shared.ptt_out.set_low().unwrap();              shared.led.set_high().unwrap(); -            if cw_ptt { -                SequenceState::SwitchingCW +            if ptt { +                if shared.state.mode == Mode::FeldHell { +                    SequenceState::Switching(SequenceMode::FeldHell) +                } +                else { +                    SequenceState::Switching(SequenceMode::CW) +                }              }              else {                  SequenceState::Rx              }          }, -        SequenceState::SwitchingCW => { +        SequenceState::Switching(m) => {              shared.ptt_out.set_high().unwrap();              shared.led.set_low().unwrap(); -            if cw_ptt { -                SequenceState::TxCW +            if ptt { +                SequenceState::Tx(m)              }              else {                  SequenceState::Rx              }          }, -        SequenceState::TxCW => { +        SequenceState::Tx(m) => {              shared.ptt_out.set_high().unwrap();              shared.led.set_low().unwrap(); -            if cw_ptt { -                SequenceState::TxCW +            if ptt { +                SequenceState::Tx(m)              }              else { -                SequenceState::SwitchingCW +                SequenceState::Switching(m)              }          },      };      match shared.state.sequence_state { -        SequenceState::TxCW => { +        SequenceState::Tx(SequenceMode::CW) => {              if cw_beep {                  shared.cw_pwm.on();                  shared.cw_key_out_n.set_low().unwrap(); @@ -347,6 +374,15 @@ fn TIM2() {                  shared.cw_key_out_n.set_high().unwrap();              }          }, +        SequenceState::Tx(SequenceMode::FeldHell) => { +            // cw_key_out_n is handled by TIM4 ISR in usb.rs +            if cw_beep { +                shared.cw_pwm.on(); +            } +            else { +                shared.cw_pwm.off(); +            } +        },          _ => {              shared.cw_pwm.off();              shared.cw_key_out_n.set_high().unwrap(); diff --git a/sw/eval-clock-cw-tx/src/state.rs b/sw/eval-clock-cw-tx/src/state.rs index a5891c5..cd89ede 100644 --- a/sw/eval-clock-cw-tx/src/state.rs +++ b/sw/eval-clock-cw-tx/src/state.rs @@ -29,13 +29,20 @@ pub enum CWMode {  #[derive(PartialEq, Eq, Clone, Copy)]  pub enum Mode {      CW(CWMode), +    FeldHell, +} + +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum SequenceMode { +    CW, +    FeldHell,  }  #[derive(Clone, PartialEq, Eq)]  pub enum SequenceState {      Rx, -    SwitchingCW, -    TxCW, +    Switching(SequenceMode), +    Tx(SequenceMode),  }  #[derive(Clone)] @@ -55,7 +62,8 @@ impl State {      pub fn new() -> Self {          State {              ui_sel : UISelection::VFO, -            mode : Mode::CW(CWMode::StraightKey), +            //mode : Mode::CW(CWMode::StraightKey), +            mode : Mode::FeldHell,              vfo_sel : VFOSelection::A,              vfo_a : INITIAL_VFO,              vfo_b : INITIAL_VFO, @@ -66,6 +74,13 @@ impl State {          }      } +    pub fn set_vfo(&mut self, freq : u32) { +        match self.vfo_sel { +            VFOSelection::A => self.vfo_a = freq, +            VFOSelection::B => self.vfo_b = freq, +        } +    } +      pub fn vfo_display(&self) -> u32 {          match self.vfo_sel {              VFOSelection::A => self.vfo_a, @@ -90,4 +105,8 @@ impl State {              TuneSpeed::Fast => 1000,          }      } + +    pub fn allow_feldhell_keying(&self) -> bool { +        self.sequence_state == SequenceState::Tx(SequenceMode::FeldHell) +    }  } diff --git a/sw/eval-clock-cw-tx/src/ui.rs b/sw/eval-clock-cw-tx/src/ui.rs index 3cc8c90..38355e5 100644 --- a/sw/eval-clock-cw-tx/src/ui.rs +++ b/sw/eval-clock-cw-tx/src/ui.rs @@ -170,7 +170,8 @@ impl UI {          if button_updates.c {              let (new_ui_sel, new_filter_shift) = match (state.ui_sel, state.mode) {                  (UISelection::Mode, Mode::CW(CWMode::StraightKey)) => (UISelection::Mode, Mode::CW(CWMode::Iambic)), -                (UISelection::Mode, Mode::CW(CWMode::Iambic)) => (UISelection::Mode, Mode::CW(CWMode::StraightKey)), +                (UISelection::Mode, Mode::CW(CWMode::Iambic)) => (UISelection::Mode, Mode::FeldHell), +                (UISelection::Mode, Mode::FeldHell) => (UISelection::Mode, Mode::CW(CWMode::StraightKey)),                  (_, f) => (UISelection::Mode, f),              }; @@ -203,7 +204,6 @@ impl UI {          result      } -    // Returns true if bfo must be reprogrammed      pub fn update_encoder(&mut self, state: &mut State, delta : i32) {          match state.ui_sel {              UISelection::VFO => { @@ -259,6 +259,7 @@ pub fn update_disp<T: hd44780_driver::bus::DataBus>(lcd: &mut HD44780<T>, state:      let mode = match state.mode {          Mode::CW(CWMode::StraightKey) => "CWs",          Mode::CW(CWMode::Iambic) => "CWp", +        Mode::FeldHell => "HEL",      };      let speed = match state.tune_speed { diff --git a/sw/eval-clock-cw-tx/src/usb.rs b/sw/eval-clock-cw-tx/src/usb.rs index e803c91..467c148 100644 --- a/sw/eval-clock-cw-tx/src/usb.rs +++ b/sw/eval-clock-cw-tx/src/usb.rs @@ -1,23 +1,96 @@ +use core::mem::MaybeUninit;  use core::fmt::Write; -use stm32f1xx_hal::gpio; -use stm32f1xx_hal::pac; -use stm32f1xx_hal::pac::{interrupt, Interrupt}; +use stm32f1xx_hal::{ +    pac, +    pac::{interrupt, Interrupt}, +    gpio, +    timer::CountDownTimer, +};  use stm32f1xx_hal::usb::{Peripheral, UsbBus, UsbBusType};  use usb_device::{bus::UsbBusAllocator, prelude::*};  use usbd_serial::{SerialPort, USB_CLASS_CDC}; +use arrayvec::{ArrayVec, ArrayString}; + +/* Transmit 17.5 columns per second + * With 14 lines per column, this gives 245 lines per second + */ +pub const TIMER_FREQ_HZ : u32 = 245; + +const FELDHELL_PATTERN_LEN : usize = 16; +const MESSAGE_LEN : usize = 64; + +static mut FELDHELL_TIMER: MaybeUninit<CountDownTimer<pac::TIM4>> = MaybeUninit::uninit(); + +static mut SHARED_USB: MaybeUninit<USBSharedData> = MaybeUninit::uninit();  static mut USB_BUS: Option<UsbBusAllocator<UsbBusType>> = None;  static mut USB_SERIAL: Option<usbd_serial::SerialPort<UsbBusType>> = None;  static mut USB_DEVICE: Option<UsbDevice<UsbBusType>> = None; -pub struct USB { + +/* Commands + * ======== + * Commands end with \n + * + * Switch to transmit: + * tx + * + * Switch to receive: + * rx + * + * Set frequency: + * f10121000 + * + * Append to transmit message + * mThe message to transmit + * + * Clear transmit message + * clear + * + * Set FELDHELL wide font + * fontw + * + * Set FELDHELL narrow font + * fontn +*/ + + +#[derive(Clone, Copy)] +enum Font { +    Narrow, +    Wide,  } -impl USB { +// Shared between application and interrupt context +struct USBSharedData { +    incoming_buf: ArrayVec::<[u8; MESSAGE_LEN]>, +    message : ArrayString::<[u8; MESSAGE_LEN]>, +    feldhell_pattern: ArrayVec::<[u16; FELDHELL_PATTERN_LEN]>, // FELDHELL columns to transmit +} + +pub fn enable_interrupts() { +    unsafe { +        pac::NVIC::unmask(Interrupt::USB_HP_CAN_TX); +        pac::NVIC::unmask(Interrupt::USB_LP_CAN_RX0); +        pac::NVIC::unmask(Interrupt::TIM4); +    } +} + +pub struct USBData { +    font : Font, +    pub frequency : u32, +    pub transmit : bool, + +    current_str : ArrayVec::<[u8; 96]>, +    send_ix : usize, +} + +impl USBData {      pub fn new( -        usb: stm32f1xx_hal::pac::USB, +        timer4: CountDownTimer<pac::TIM4>, +        usb: pac::USB,          pin_dm: gpio::gpioa::PA11<gpio::Input<gpio::Floating>>, -        pin_dp: gpio::gpioa::PA12<gpio::Input<gpio::Floating>>) -> Self { +        pin_dp: gpio::gpioa::PA12<gpio::Input<gpio::Floating>>) -> USBData {          let peripheral = Peripheral {              usb, @@ -25,32 +98,161 @@ impl USB {              pin_dp,          }; -        unsafe { -            let bus = UsbBus::new(peripheral); - -            USB_BUS = Some(bus); +        { +            let timer = unsafe { &mut *FELDHELL_TIMER.as_mut_ptr() }; +            *timer = timer4; +        } +        unsafe { +            USB_BUS = Some(UsbBus::new(peripheral));              USB_SERIAL = Some(SerialPort::new(USB_BUS.as_ref().unwrap())); - -            let usb_dev = UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x1d50, 0x5120)) // Openmoko Neo1973 serial +            let usb_device = UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x1d50, 0x5120)) // Openmoko Neo1973 serial                  .manufacturer("HB9EGM")                  .product("Beep Machine")                  .serial_number("1")                  .device_class(USB_CLASS_CDC)                  .build(); +            USB_DEVICE = Some(usb_device); -            USB_DEVICE = Some(usb_dev); +            let shared_usb = &mut *SHARED_USB.as_mut_ptr(); +            *shared_usb = USBSharedData { +                incoming_buf : ArrayVec::new(), +                message : ArrayString::new(), +                feldhell_pattern : ArrayVec::new(), +            }          } -        USB{} +        USBData { +            font : Font::Narrow, +            frequency : 10121000, +            transmit : false, +            current_str : ArrayVec::new(), +            send_ix : 0, +        }      } -    pub fn enable_interrupts(&self) { -        unsafe { -            pac::NVIC::unmask(Interrupt::USB_HP_CAN_TX); -            pac::NVIC::unmask(Interrupt::USB_LP_CAN_RX0); +    pub fn handle(&mut self) { +        let in_message = cortex_m::interrupt::free(|_cs| { +            let shared_usb = unsafe { &mut *SHARED_USB.as_mut_ptr() }; + +            if shared_usb.message.len() > 0 { +                let mut outgoing = ArrayString::<[u8; MESSAGE_LEN]>::new(); + +                if let Err(_) = write!(outgoing, "{}", &shared_usb.message) { +                    outgoing.clear(); +                    write!(outgoing, "EXT FAIL\n").ok(); +                } +                let serial = unsafe { USB_SERIAL.as_mut().unwrap() }; +                serial.write(outgoing.as_bytes()).ok(); + +                let in_message = shared_usb.message.clone(); +                shared_usb.message.clear(); +                Some(in_message) +            } +            else { +                None +            } +        }); + +        let mut outgoing = ArrayVec::<[u8; MESSAGE_LEN]>::new(); +        if let Some(m) = in_message { +            if m.as_str() == "tx\n" { +                outgoing.try_extend_from_slice(b"TX ok\n").ok(); +                self.transmit = true; +            } +            else if m.as_str() == "rx\n" { +                outgoing.try_extend_from_slice(b"RX ok\n").ok(); +                self.transmit = false; +            } +            else if m.chars().nth(0).unwrap() == 'm' { +                self.current_str.clear(); +                for c in m.bytes().skip(1) { +                    self.current_str.push(c); +                } +                outgoing.try_extend_from_slice(b"m ok\n").ok(); +            } +            else if m.as_str() == "clear\n" { +                self.current_str.clear(); +                outgoing.try_extend_from_slice(b"clear ok\n").ok(); +            } +            else if m.as_str() == "fontw\n" { +                self.font = Font::Wide; +                outgoing.try_extend_from_slice(b"fontw ok\n").ok(); +            } +            else if m.as_str() == "fontn\n" { +                self.font = Font::Narrow; +                outgoing.try_extend_from_slice(b"fontn ok\n").ok(); +            } +            else if m.chars().nth(0).unwrap() == 'f' { +                let mut as_str = ArrayString::<[u8; MESSAGE_LEN]>::new(); + +                for c in m.chars().skip(1) { +                    as_str.push(c); +                } + +                match u32::from_str_radix(as_str.as_str().trim(), 10) { +                    Ok(val) => { +                        self.frequency = val; +                        outgoing.try_extend_from_slice(b"f ok\n").ok(); +                    } +                    _ => { +                        outgoing.try_extend_from_slice(b"F").ok(); +                        outgoing.try_extend_from_slice(as_str.as_bytes()).ok(); +                        outgoing.try_extend_from_slice(b" err\n").ok(); +                    } +                } +            } +            else { +                outgoing.try_extend_from_slice(b"no\n").ok(); +            } +        } + +        let feldhell_pattern_length = cortex_m::interrupt::free(|_cs| { +            let serial = unsafe { USB_SERIAL.as_mut().unwrap() }; +            serial.write(&outgoing).ok(); + +            let shared_usb = unsafe { &mut *SHARED_USB.as_mut_ptr() }; +            shared_usb.feldhell_pattern.len() +        }); + +        if self.current_str.len() > 0 && feldhell_pattern_length < FELDHELL_PATTERN_LEN-8 { +            match self.current_str.get(self.send_ix) { +                Some(letter) => { +                    let fat = match self.font { +                        Font::Wide => true, +                        Font::Narrow => false, +                    }; + +                    match crate::feldhell_font::get_letter_by_columns(*letter, fat) { +                        Some(columns) => { +                            cortex_m::interrupt::free(|_cs| { +                                let shared_usb = unsafe { &mut *SHARED_USB.as_mut_ptr() }; +                                shared_usb.feldhell_pattern.try_extend_from_slice(&columns).ok(); +                            }); + +                        } +                        None => {} +                    } +                } +                None => {} +            } + +            self.send_ix += 1; +            if self.send_ix >= self.current_str.len() { +                cortex_m::interrupt::free(|_cs| { +                    let serial = unsafe { USB_SERIAL.as_mut().unwrap() }; +                    let mut outgoing = ArrayString::<[u8; 16]>::new(); +                    write!(&mut outgoing, "FH sent {}\n", self.send_ix).ok(); +                    serial.write(outgoing.as_bytes()).ok(); +                }); + +                self.send_ix = 0; +                self.current_str.clear(); +            }          }      } + +    pub fn is_transmit(&self) -> bool { self.transmit }  }  #[allow(non_snake_case)] @@ -73,18 +275,82 @@ fn usb_interrupt() {          return;      } -    let mut buf = [0u8; 8]; -    let mut string = arrayvec::ArrayString::<[_; 16]>::new(); +    let mut buf = [0u8; 64]; + +    let shared_usb = unsafe { &mut *SHARED_USB.as_mut_ptr() };      match serial.read(&mut buf) {          Ok(count) if count > 0 => { -            // Echo back in upper case -            for c in buf[0..count].iter_mut() { -                write!(string, "{:02x}", *c).ok(); +            if count > 64 { +                panic!("illegal count"); +            } +            shared_usb.incoming_buf.try_extend_from_slice(&buf[0..count]).ok(); + +            let mut split_at = None; +            for (i, c) in shared_usb.incoming_buf.iter().enumerate() { +                if *c == b'\n' { +                    split_at = Some(i); +                    break; +                }              } -            serial.write(string.as_bytes()).ok(); -            string.clear(); + +            match split_at { +                Some(i) if i+1 == shared_usb.incoming_buf.len() => { +                    write!(&mut shared_usb.message, "{}", core::str::from_utf8(&shared_usb.incoming_buf).unwrap()).ok(); +                    shared_usb.incoming_buf.clear(); +                } +                Some(i) => { +                    let (front, back) = shared_usb.incoming_buf.split_at(i+1); + +                    write!(&mut shared_usb.message, "{}", core::str::from_utf8(front).unwrap()).ok(); + +                    let mut keep = ArrayVec::<[u8; MESSAGE_LEN]>::new(); +                    keep.try_extend_from_slice(back).ok(); + +                    shared_usb.incoming_buf.clear(); +                    shared_usb.incoming_buf.try_extend_from_slice(&keep).ok(); +                } +                _ => { } +            }; +          }          _ => {}      }  } + +#[allow(non_snake_case)] +#[interrupt] +fn TIM4() { +    let timer = unsafe { &mut *FELDHELL_TIMER.as_mut_ptr() }; +    timer.clear_update_interrupt_flag(); + +    let shared_usb = unsafe { &mut *SHARED_USB.as_mut_ptr() }; +    while shared_usb.feldhell_pattern.len() > 0 && shared_usb.feldhell_pattern.as_slice()[0] == 0 { +        shared_usb.feldhell_pattern.pop_at(0); +    } + +    if shared_usb.feldhell_pattern.len() > 0 { +        let remove_first_element = { +            let pat = shared_usb.feldhell_pattern.as_mut_slice().first_mut().unwrap(); + +            let shared = unsafe { &mut *crate::SHARED.as_mut_ptr() }; + +            use embedded_hal::digital::v2::OutputPin; + +            if shared.state.allow_feldhell_keying() && (*pat & 0b1) == 0b1 { +                shared.cw_key_out_n.set_low().unwrap(); +            } +            else { +                shared.cw_key_out_n.set_high().unwrap(); +            } + +            *pat >>= 1; +            assert_ne!(*pat, 0); +            *pat == 0b1 +        }; + +        if remove_first_element { +            shared_usb.feldhell_pattern.pop_at(0); +        } +    } +}  | 
