aboutsummaryrefslogtreecommitdiffstats
path: root/sw
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2021-04-15 13:07:56 +0200
committerMatthias P. Braendli <matthias.braendli@mpb.li>2021-04-15 13:07:56 +0200
commit03ad81d7afaea119df607b6e65f19bd5362fab39 (patch)
tree38835bd11e550aa267acaf82dad2c63f5a566522 /sw
parent1a65830bbfc9ea4cfb999f43beb67e15947ef358 (diff)
downloadpicardy-03ad81d7afaea119df607b6e65f19bd5362fab39.tar.gz
picardy-03ad81d7afaea119df607b6e65f19bd5362fab39.tar.bz2
picardy-03ad81d7afaea119df607b6e65f19bd5362fab39.zip
Get FELDHELL to work
Diffstat (limited to 'sw')
-rw-r--r--sw/eval-clock-cw-tx/README.md9
-rwxr-xr-xsw/eval-clock-cw-tx/gui.py222
-rw-r--r--sw/eval-clock-cw-tx/src/feldhell_font.rs277
-rw-r--r--sw/eval-clock-cw-tx/src/main.rs82
-rw-r--r--sw/eval-clock-cw-tx/src/state.rs25
-rw-r--r--sw/eval-clock-cw-tx/src/ui.rs5
-rw-r--r--sw/eval-clock-cw-tx/src/usb.rs318
-rw-r--r--sw/picardy/src/main.rs1
8 files changed, 882 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);
+ }
+ }
+}
diff --git a/sw/picardy/src/main.rs b/sw/picardy/src/main.rs
index efdc59d..3a2e500 100644
--- a/sw/picardy/src/main.rs
+++ b/sw/picardy/src/main.rs
@@ -97,6 +97,7 @@ fn main() -> ! {
let mut rcc = dp.RCC.constrain();
let mut afio = dp.AFIO.constrain(&mut rcc.apb2);
let clocks = rcc.cfgr
+ // TODO .use_hse(8.mhz())
.adcclk(2.mhz())
.freeze(&mut flash.acr);
let mut delay = Delay::new(cp.SYST, clocks);