aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2021-02-11 08:10:12 +0100
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2021-02-13 05:53:45 +0100
commitaa6c480f40241471d12c317c72acd94c2148a991 (patch)
tree1a6ed6298f3fef109a1989f313f7f1d75449f2ba
parentdff9b75c6fb900830e478ad5c71ca42efe34ffc6 (diff)
downloadtmk7637-aa6c480f40241471d12c317c72acd94c2148a991.tar.gz
added the K7637 firmware
-rw-r--r--COPYING339
-rw-r--r--Makefile107
-rw-r--r--command.c60
-rw-r--r--config.h75
-rwxr-xr-xk7637-beep.sh19
-rw-r--r--k7637.rules3
-rw-r--r--keyclick.h47
-rw-r--r--led.c82
-rw-r--r--matrix.c337
-rw-r--r--pwm.c347
-rw-r--r--pwm.h34
-rw-r--r--song.c456
-rw-r--r--song.h24
-rw-r--r--unimap_00.c34
-rw-r--r--unimap_trans.h99
-rw-r--r--usbconfig.h381
16 files changed, 2444 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7ed57ed
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,107 @@
+#----------------------------------------------------------------------------
+# On command line:
+#
+# make all = Make software.
+#
+# make clean = Clean out built project files.
+#
+# make coff = Convert ELF to AVR COFF.
+#
+# make extcoff = Convert ELF to AVR Extended COFF.
+#
+# make program = Download the hex file to the device.
+# Please customize your programmer settings(PROGRAM_CMD)
+#
+# make teensy = Download the hex file to the device, using teensy_loader_cli.
+# (must have teensy_loader_cli installed).
+#
+# make dfu = Download the hex file to the device, using dfu-programmer (must
+# have dfu-programmer installed).
+#
+# make flip = Download the hex file to the device, using Atmel FLIP (must
+# have Atmel FLIP installed).
+#
+# make dfu-ee = Download the eeprom file to the device, using dfu-programmer
+# (must have dfu-programmer installed).
+#
+# make flip-ee = Download the eeprom file to the device, using Atmel FLIP
+# (must have Atmel FLIP installed).
+#
+# make debug = Start either simulavr or avarice as specified for debugging,
+# with avr-gdb or avr-insight as the front end for debugging.
+#
+# make filename.s = Just compile filename.c into the assembler code only.
+#
+# make filename.i = Create a preprocessed source file for use in submitting
+# bug reports to the GCC project.
+#
+# To rebuild project do "make clean" then "make all".
+#----------------------------------------------------------------------------
+
+# Target file name (without extension).
+TARGET = k7637
+
+# Directory common source filess exist
+TMK_DIR = tmk_core
+
+# Directory keyboard dependent files exist
+TARGET_DIR = .
+
+# project specific files
+SRC = unimap_00.c \
+ matrix.c \
+ led.c \
+ command.c \
+ pwm.c \
+ song.c
+
+CONFIG_H = config.h
+
+
+# MCU name, you MUST set this to match the board you are using
+# type "make clean" after changing this, so all files will be rebuilt
+MCU = at90usb1286
+
+
+# Processor frequency.
+# Normally the first thing your program should do is set the clock prescaler,
+# so your program will run at the correct speed. You should also set this
+# variable to same clock speed. The _delay_ms() macro uses this, and many
+# examples use this variable to calculate timings. Do not add a "UL" here.
+F_CPU = 16000000
+
+
+# Boot Section Size in *bytes*
+# Teensy halfKay 512
+# Atmel DFU loader 4096
+# LUFA bootloader 4096
+OPT_DEFS += -DBOOTLOADER_SIZE=2048
+
+
+# Build Options
+# comment out to disable the options.
+#
+#BOOTMAGIC_ENABLE = yes # Virtual DIP switch configuration(+1000)
+#MOUSEKEY_ENABLE = yes # Mouse keys(+5000)
+EXTRAKEY_ENABLE = yes # Audio control and System control(+600)
+CONSOLE_ENABLE = yes # Console for debug (hid_listen)
+COMMAND_ENABLE = yes # Commands for debug and configuration
+#SLEEP_LED_ENABLE = yes # Breathing sleep LED during USB suspend
+NKRO_ENABLE = yes # USB Nkey Rollover(+500)
+UNIMAP_ENABLE = yes
+KEYMAP_SECTION_ENABLE = yes
+
+#PS2_MOUSE_ENABLE = yes # PS/2 mouse(TrackPoint) support
+#PS2_USE_BUSYWAIT = yes # uses primitive reference code
+#PS2_USE_INT = yes # uses external interrupt for falling edge of PS/2 clock pin
+#PS2_USE_USART = yes # uses hardware USART engine for PS/2 signal receive(recomened)
+
+
+# Search Path
+VPATH += $(TARGET_DIR)
+VPATH += $(TMK_DIR)
+
+include $(TMK_DIR)/common.mk
+include $(TMK_DIR)/protocol.mk
+include $(TMK_DIR)/protocol/pjrc.mk
+include $(TMK_DIR)/rules.mk
diff --git a/command.c b/command.c
new file mode 100644
index 0000000..6827706
--- /dev/null
+++ b/command.c
@@ -0,0 +1,60 @@
+/*
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "debug.h"
+#include "led.h"
+#include "host.h"
+#include "keycode.h"
+#include "keyclick.h"
+#include "pwm.h"
+#include "song.h"
+#include "command.h"
+
+enum keyclick_mode keyclick_mode = KEYCLICK_OFF;
+
+bool command_extra(uint8_t code)
+{
+ switch (code) {
+ //case KC_AUDIO_MUTE:
+ case KC_SPACE:
+ keyclick_mode = (keyclick_mode+1) % KEYCLICK_MAX;
+ dprintf("new keyclick mode: %u\n", keyclick_mode);
+ /* FIXME: Perhaps do this in matrix_scan() */
+ keyclick_solenoid_set(false);
+ pwm_pb5_set_tone(0);
+ /* update the keyclick mode LED */
+ led_set(host_keyboard_leds());
+ return true;
+
+ /*
+ * NOTE: The Fx keys are also used in command_common()
+ * but do the same as the number keys (switch layers).
+ * It should therefore be safe to repurpose them.
+ */
+ case KC_F1:
+ song_play_ruinen();
+ return true;
+ case KC_F2:
+ song_play_kitt();
+ return true;
+ }
+
+ return false;
+}
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..9821031
--- /dev/null
+++ b/config.h
@@ -0,0 +1,75 @@
+/*
+Copyright 2012 Jun Wako <wakojun@gmail.com>
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+
+/* USB Device descriptor parameter */
+#define VENDOR_ID 0xFEED
+#define PRODUCT_ID 0x1111
+#define DEVICE_VER 0x0001
+#define MANUFACTURER VEB Kombinat Robotron
+#define PRODUCT K7637
+#define DESCRIPTION t.m.k. keyboard firmware for Robotron K7637
+
+/* key matrix size */
+#define MATRIX_ROWS 8
+#define MATRIX_COLS 16
+
+/* define if matrix has ghost */
+//#define MATRIX_HAS_GHOST
+
+/* Set 0 if debouncing isn't needed */
+#define DEBOUNCE 2
+
+/* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */
+#define LOCKING_SUPPORT_ENABLE
+/* Locking resynchronize hack */
+#define LOCKING_RESYNC_ENABLE
+
+/*
+ * Key combination for "magic" commands (LSHIFT+ET2+ET1).
+ *
+ * NOTE: This is LSHIFT+RSHIFT default.
+ * We had to pick something else since we cannot current discern these keys.
+ * This would be possible to add support for RSHIFT with a simple hardware hack, though.
+ */
+#define IS_COMMAND() ( \
+ keyboard_report->mods == (MOD_BIT(KC_LSHIFT) | MOD_BIT(KC_LCTRL) | MOD_BIT(KC_RALT)) \
+)
+
+/*
+ * Feature disable options
+ * These options are also useful to firmware size reduction.
+ */
+
+/* disable debug print */
+//#define NO_DEBUG
+
+/* disable print */
+//#define NO_PRINT
+
+/* disable action features */
+//#define NO_ACTION_LAYER
+//#define NO_ACTION_TAPPING
+//#define NO_ACTION_ONESHOT
+//#define NO_ACTION_MACRO
+//#define NO_ACTION_FUNCTION
+
+#endif
diff --git a/k7637-beep.sh b/k7637-beep.sh
new file mode 100755
index 0000000..03ba3a7
--- /dev/null
+++ b/k7637-beep.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+#./k7637-beep.sh [duration]
+DURATION=${1:-200}
+
+# `xset led` does not work for me at all,
+# so we use sysfs instead.
+# This way we can also avoid sending the request to all attached keyboard.
+for led in /sys/class/leds/*\:\:kana; do
+ if [ "`cat $led/device/name`" = "VEB Kombinat Robotron K7637" ]; then
+ # NOTE: This will usually require root
+ echo 1 >$led/brightness || break
+ sleep `printf '%.3f' ${DURATION}e-3`
+ echo 0 >$led/brightness
+ exit $?
+ fi
+done
+
+# Fall back to regular PC speaker beep
+exec beep -l $DURATION
diff --git a/k7637.rules b/k7637.rules
new file mode 100644
index 0000000..27c213f
--- /dev/null
+++ b/k7637.rules
@@ -0,0 +1,3 @@
+# Make sure that all users can write the K7637's LEDs.
+# This is important, so that k7637-beep and consequently xkbevd can run without root privilege.
+SUBSYSTEM=="leds", ATTRS{name}=="VEB Kombinat Robotron K7637", ACTION=="add", RUN+="/bin/chmod a+rw /sys%p/brightness"
diff --git a/keyclick.h b/keyclick.h
new file mode 100644
index 0000000..f964230
--- /dev/null
+++ b/keyclick.h
@@ -0,0 +1,47 @@
+/*
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef KEYCLICK_H
+#define KEYCLICK_H
+
+#include <stdbool.h>
+
+#include <avr/io.h>
+
+enum keyclick_mode {
+ KEYCLICK_OFF = 0,
+ KEYCLICK_SOLENOID,
+ KEYCLICK_BUZZER,
+ /** not a real keyclick mode */
+ KEYCLICK_MAX
+};
+
+extern enum keyclick_mode keyclick_mode;
+
+static inline void
+keyclick_solenoid_set(bool state)
+{
+ /*
+ * NOTE: The solenoid/relay is LOW-active.
+ */
+ if (state)
+ PORTB &= ~(1 << PB3);
+ else
+ PORTB |= (1 << PB3);
+}
+
+#endif
diff --git a/led.c b/led.c
new file mode 100644
index 0000000..c7e5933
--- /dev/null
+++ b/led.c
@@ -0,0 +1,82 @@
+/*
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdint.h>
+
+#include <avr/io.h>
+
+#include "debug.h"
+#include "keyclick.h"
+#include "led.h"
+#include "pwm.h"
+
+void led_set(uint8_t usb_led)
+{
+ dprintf("Set keyboard LEDs: 0x%02X\n", usb_led);
+
+ /*
+ * All LEDs and the buzzer are output pins obviously, even for PWM operation
+ */
+ DDRD |= 0b00001111;
+ DDRB |= 0b11110000;
+
+ /* LED right next to the Capslock key (C99) */
+ if (usb_led & (1 << USB_LED_CAPS_LOCK))
+ PORTD &= ~(1 << PD3);
+ else
+ PORTD |= (1 << PD3);
+
+ /*
+ * 1st LED on first row (G00).
+ *
+ * NOTE: This will automatically disable PWM mode, so it will not
+ * interfere with timer 0 and tmk's timer module.
+ */
+ pwm_pd0_set_led(usb_led & (1 << USB_LED_NUM_LOCK) ? 255 : 0);
+
+ /* 2nd LED on first row (G01): Highlight keyclick mode. */
+ pwm_pb7_set_led(keyclick_mode*255/(KEYCLICK_MAX-1));
+
+ /* 3rd LED on the first row (G02) */
+ pwm_pd1_set_led(usb_led & (1 << USB_LED_COMPOSE) ? 255 : 0);
+
+ /*
+ * 4th LED (G03) are currently not triggerable via USB.
+ * Could be triggered as the "backlight".
+ */
+ pwm_pb6_set_led(0);
+
+ /* 5th LED on the first row (G04) */
+ pwm_pb4_set_led(usb_led & (1 << USB_LED_SCROLL_LOCK) ? 255 : 0);
+
+ /*
+ * 6th LED on the first row (G53).
+ *
+ * Triggering this LED (PD2) will also enable the buzzer (PB5),
+ * so we have got a way to beep from userspace (see ./k7637-beep.sh).
+ *
+ * The original firmware also had the error display on G53
+ * (cf. Betriebsdokumentation).
+ */
+ if (usb_led & (1 << USB_LED_KANA)) {
+ PORTD &= ~(1 << PD2);
+ pwm_pb5_set_tone(2200);
+ } else {
+ PORTD |= (1 << PD2);
+ pwm_pb5_set_tone(0);
+ }
+}
diff --git a/matrix.c b/matrix.c
new file mode 100644
index 0000000..8f98afd
--- /dev/null
+++ b/matrix.c
@@ -0,0 +1,337 @@
+/*
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <avr/io.h>
+#include <util/delay.h>
+
+#include "print.h"
+#include "debug.h"
+#include "timer.h"
+#include "led.h"
+#include "host.h"
+#include "pwm.h"
+#include "keyclick.h"
+#include "matrix.h"
+
+/*
+ * NOTE: The "Betriebsdokumentation" mentions that the keyboard matrix
+ * must not change for two scan cycles.
+ * The setting below actually means, it must not change for 2ms.
+ * This has been shown to be sufficient.
+ * It is still possible for keypresses to be instable but this has only
+ * been observed with a poor power source.
+ */
+#ifndef DEBOUNCE
+# define DEBOUNCE 2
+#endif
+
+/* matrix state(1:on, 0:off) */
+static matrix_row_t matrix[MATRIX_ROWS];
+static matrix_row_t matrix_debouncing[MATRIX_ROWS];
+
+static void init_pins(void);
+static void select_col(uint8_t col);
+static void unselect_cols(void);
+static bool read_row(uint8_t row);
+
+void matrix_init(void)
+{
+ debug_enable = true;
+ debug_matrix = true;
+
+ /* this also configures all LED pins and brings them into defined states */
+ led_set(host_keyboard_leds());
+
+ init_pins();
+
+ /* initialize matrix state: all keys off */
+ memset(matrix, sizeof(matrix), 0);
+ memset(matrix_debouncing, sizeof(matrix_debouncing), 0);
+}
+
+/*
+ * FIXME: If we rotated the matrix (8 columns and 16 rows)
+ * we could simplify and speed up the code below.
+ * Unfortunately, we'd also have to rotate the Unimap translation table and
+ * it would no longer correspond with the "Serviceschaltplan".
+ */
+uint8_t matrix_scan(void)
+{
+ static uint16_t debouncing_time = 0, keyclick_time = 0;
+ /** Number of pressed keys in `matrix_debouncing` */
+ uint8_t matrix_debouncing_pressed_keys = 0;
+
+ for (uint8_t col = 0; col < MATRIX_COLS; col++) {
+ select_col(col);
+ /*
+ * Give the signals some time to settle.
+ * This is not mentioned in the "Betriebsdokumentation"
+ * but has been tested experimentally.
+ * 30us is used by all the other controller firmwares as well.
+ */
+ _delay_us(30);
+
+ uint8_t row = 0;
+
+ /*
+ * The first 4 bits in the 15th column
+ * are sensed like ordinary keypresses but
+ * in reality represent a 3-bit "security key"
+ * (cf. Betriebsdokumentation, p.13f) with
+ * an actual resolution of 6 encoded into a special
+ * keylike device that's plugged into the keyboard.
+ * It makes no sense to map these original bits into
+ * the keyboard matrix.
+ * Instead we translate it into one of six key presses
+ * via unused fields of the keyboard matrix whenever
+ * the "security" key is removed.
+ * These pseudo-keypress are mapped to F19-F24 in unimap_trans
+ * and could be mapped at the OS level, eg. to lock up the screen.
+ */
+ if (col+1 == MATRIX_COLS) {
+ static uint8_t last_security_key = 0;
+ uint8_t security_key = (!read_row(0) << 0) |
+ (!read_row(1) << 1) |
+ (!read_row(2) << 2);
+
+ for (uint8_t i = 0; i < 6; i++)
+ matrix_debouncing[i] &= ~(1 << col);
+ if (security_key != last_security_key && security_key == 0 &&
+ 1 <= last_security_key && last_security_key <= 6)
+ matrix_debouncing[last_security_key-1] |= (1 << col);
+
+ last_security_key = security_key;
+ row = 6;
+ }
+
+ while (row < MATRIX_ROWS) {
+ matrix_row_t prev_row = matrix_debouncing[row];
+
+ if (read_row(row)) {
+ matrix_debouncing[row] |= (1 << col);
+ matrix_debouncing_pressed_keys++;
+ } else {
+ matrix_debouncing[row] &= ~(1 << col);
+ }
+
+ if (matrix_debouncing[row] != prev_row)
+ debouncing_time = timer_read();
+
+ row++;
+ }
+
+ unselect_cols();
+ }
+
+ if (debouncing_time && timer_elapsed(debouncing_time) >= DEBOUNCE) {
+ /** Number of pressed keys in `matrix` */
+ static uint8_t matrix_pressed_keys = 0;
+
+ /*
+ * Trigger keyclick whenever the keyboard matrix has actually
+ * changed (ie. `debouncing_time` was set).
+ * When using the solenoid, the keyclick is supposed to immitate a
+ * mechanical click, so there should be one sound when pressing down
+ * the key and another when releasing it.
+ * If a key is already pressed (ie. you press an additional key),
+ * we release the solenoid for 50ms.
+ * We consciously do not _delay_ms(50) here since that would delay
+ * key event delivery.
+ * When using the buzzer, a short 50ms beep is played every time
+ * a new key is pressed.
+ */
+ switch (keyclick_mode) {
+ case KEYCLICK_SOLENOID:
+ if (matrix_debouncing_pressed_keys > 1 ||
+ (matrix_debouncing_pressed_keys == 1 && matrix_pressed_keys > 1)) {
+ keyclick_solenoid_set(false);
+ keyclick_time = timer_read();
+ } else {
+ keyclick_solenoid_set(matrix_debouncing_pressed_keys);
+ }
+ break;
+
+ case KEYCLICK_BUZZER:
+ if (matrix_debouncing_pressed_keys > matrix_pressed_keys) {
+ pwm_pb5_set_tone(550);
+ keyclick_time = timer_read();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ memcpy(matrix, matrix_debouncing, sizeof(matrix));
+ matrix_pressed_keys = matrix_debouncing_pressed_keys;
+
+ debouncing_time = 0;
+ }
+
+ /*
+ * This is responsible for performing a delayed action depending
+ * on the keyclick mode (see abovc).
+ * For solenoids, it reactivates them since they should be on and
+ * are released when the last key is released.
+ * When using the buzzer, this is responsible for turning it off
+ * after a short while.
+ * Polling here makes sure we do not delay any key delivery.
+ */
+ if (keyclick_time && timer_elapsed(keyclick_time) >= 50) {
+ switch (keyclick_mode) {
+ case KEYCLICK_SOLENOID:
+ keyclick_solenoid_set(true);
+ break;
+
+ case KEYCLICK_BUZZER:
+ if (!(host_keyboard_leds() & (1 << USB_LED_KANA)))
+ pwm_pb5_set_tone(0);
+ break;
+
+ default:
+ break;
+ }
+
+ keyclick_time = 0;
+ }
+
+ return 1;
+}
+
+inline
+matrix_row_t matrix_get_row(uint8_t row)
+{
+ return matrix[row];
+}
+
+static void init_pins(void)
+{
+ /*
+ * All rows are input pins (DDR:0) and need pull-ups.
+ * There is a pull-up attached to the EPROM, but I acceidentally
+ * destroyed it.
+ * Therefore we enable internal pull-ups (PORT:1).
+ */
+ /* Row 0: PB0 == D7 */
+ DDRB &= ~0b1;
+ PORTB |= 0b1;
+ /* Row 1-2: PE4-5 == D5-6 (Connected by cable) */
+ DDRE &= ~0b110000;
+ PORTE |= 0b110000;
+ /* Row 3-4: PF0-1 == D4-3 (NOTE: PF0 == Row 4, PF1 == Row 3) */
+ DDRF &= ~0b11;
+ PORTF |= 0b11;
+ /* Row 5: PE7 == D2 */
+ DDRE &= ~0b10000000;
+ PORTE |= 0b10000000;
+ /* Row 6-7: PB1-2 == D0-1 */
+ DDRB &= ~0b110;
+ PORTB |= 0b110;
+
+ /*
+ * We've got a strobe-sense matrix and we strobe via the column pins.
+ * Therefore they must be outputs (DDR:1).
+ */
+ /* Column 0-3: PF4-7 == A11-14 */
+ DDRF |= 0b11110000;
+ /* Column 4-11: PC0-7 == A3-10 */
+ DDRC |= 0b11111111;
+ /* Column 12-13: PE0-1 == A1-2 */
+ DDRE |= 0b11;
+ /* Column 14: PD7 == A0 */
+ DDRD |= 0b10000000;
+
+ /*
+ * PF3 == A15 must be LOW since otherwise some NAND gates (IC13) will interfere with D0-3
+ * (half of the matrix rows).
+ * We might also draw this to GND by cable or destroy IC13.
+ * The latter might even save some power.
+ *
+ * FIXME: A15 must actually be triggered for some of the modifiers!
+ */
+ DDRF |= 0b00001000;
+ PORTF &= ~0b00001000;
+
+ /*
+ * Solenoid (ie. relay). It's LOW-active.
+ */
+ DDRB |= (1 << PB3);
+ PORTB |= (1 << PB3);
+}
+
+static void select_col(uint8_t col)
+{
+ /*
+ * FIXME: Simplify? Will go at the expense of readability.
+ */
+ switch (col) {
+ case 0: PORTD |= 0b10000000; break;
+ case 1: PORTE |= 0b00000001; break;
+ case 2: PORTE |= 0b00000010; break;
+ case 3: PORTC |= 0b00000001; break;
+ case 4: PORTC |= 0b00000010; break;
+ case 5: PORTC |= 0b00000100; break;
+ case 6: PORTC |= 0b00001000; break;
+ case 7: PORTC |= 0b00010000; break;
+ case 8: PORTC |= 0b00100000; break;
+ case 9: PORTC |= 0b01000000; break;
+ case 10: PORTC |= 0b10000000; break;
+ case 11: PORTF |= 0b10000000; break;
+ case 12: PORTF |= 0b01000000; break;
+ case 13: PORTF |= 0b00100000; break;
+ case 14: PORTF |= 0b00010000; break;
+ case 15: PORTF |= 0b00001000; break;
+ }
+}
+
+static void unselect_cols(void)
+{
+ PORTD &= ~0b10000000;
+ PORTE &= ~0b11;
+ PORTC &= ~0b11111111;
+ PORTF &= ~0b11111000;
+}
+
+static bool read_row(uint8_t row)
+{
+ /*
+ * NOTE: The key mechanism pulls the row pin to GND when the key is
+ * pressed.
+ * However, the rows are not directly connected to the keyboard matrix
+ * but through NAND gates. The signal is therefore inverted.
+ * The NAND gates no longer serve any purpose and could be skipped
+ * altogether by desoldering them and directly connecting the inputs with
+ * the outputs. The PIN registers should then be inverted.
+ */
+ switch (row) {
+ case 0: return PINB & 0b00000010;
+ case 1: return PINB & 0b00000100;
+ case 2: return PINE & 0b10000000;
+ case 3: return PINF & 0b00000001;
+ case 4: return PINF & 0b00000010;
+ case 5: return PINE & 0b00100000;
+ case 6: return PINE & 0b00010000;
+ case 7: return PINB & 0b00000001;
+ }
+
+ /* not reached */
+ return false;
+}
diff --git a/pwm.c b/pwm.c
new file mode 100644
index 0000000..97c3dac
--- /dev/null
+++ b/pwm.c
@@ -0,0 +1,347 @@
+/*
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdint.h>
+#include <math.h>
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/pgmspace.h>
+
+#include "debug.h"
+#include "pwm.h"
+
+/**
+ * Translation table from brightness to PWM setting to allow smooth
+ * fadings (8-bit timers).
+ *
+ * This is calculated by the formula:
+ * ```
+ * uint16_t resolution = 0xFF;
+ * pwm_table8[x] = x ? round(pow((double)x / 255.0, 2.5) * resolution) : 0;
+ * ```
+ */
+static const uint8_t pwm_table8[256] PROGMEM = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x07,
+ 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A,
+ 0x0B, 0x0B, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F,
+ 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14, 0x14, 0x15, 0x16,
+ 0x16, 0x17, 0x17, 0x18, 0x19, 0x19, 0x1A, 0x1A, 0x1B, 0x1C, 0x1C, 0x1D,
+ 0x1E, 0x1E, 0x1F, 0x20, 0x21, 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x26,
+ 0x27, 0x28, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2E, 0x2F, 0x30,
+ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
+ 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
+ 0x4B, 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x53, 0x55, 0x56, 0x57, 0x59,
+ 0x5A, 0x5B, 0x5D, 0x5E, 0x5F, 0x61, 0x62, 0x63, 0x65, 0x66, 0x68, 0x69,
+ 0x6B, 0x6C, 0x6E, 0x6F, 0x71, 0x72, 0x74, 0x75, 0x77, 0x79, 0x7A, 0x7C,
+ 0x7D, 0x7F, 0x81, 0x82, 0x84, 0x86, 0x87, 0x89, 0x8B, 0x8D, 0x8E, 0x90,
+ 0x92, 0x94, 0x96, 0x97, 0x99, 0x9B, 0x9D, 0x9F, 0xA1, 0xA3, 0xA5, 0xA6,
+ 0xA8, 0xAA, 0xAC, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBD, 0xBF,
+ 0xC1, 0xC3, 0xC5, 0xC7, 0xC9, 0xCC, 0xCE, 0xD0, 0xD2, 0xD4, 0xD7, 0xD9,
+ 0xDB, 0xDD, 0xE0, 0xE2, 0xE4, 0xE7, 0xE9, 0xEB, 0xEE, 0xF0, 0xF3, 0xF5,
+ 0xF8, 0xFA, 0xFD, 0xFF
+};
+
+/**
+ * Translation table from brightness to PWM setting to allow smooth
+ * fadings (16-bit timers).
+ */
+static const uint16_t pwm_table16[256] PROGMEM = {
+ 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0006, 0x0008, 0x000B,
+ 0x000F, 0x0014, 0x0019, 0x001F, 0x0026, 0x002E, 0x0037, 0x0041, 0x004B,
+ 0x0057, 0x0063, 0x0071, 0x0080, 0x008F, 0x00A0, 0x00B2, 0x00C5, 0x00DA,
+ 0x00EF, 0x0106, 0x011E, 0x0137, 0x0152, 0x016E, 0x018B, 0x01A9, 0x01C9,
+ 0x01EB, 0x020E, 0x0232, 0x0257, 0x027F, 0x02A7, 0x02D2, 0x02FD, 0x032B,
+ 0x0359, 0x038A, 0x03BC, 0x03EF, 0x0425, 0x045C, 0x0494, 0x04CF, 0x050B,
+ 0x0548, 0x0588, 0x05C9, 0x060C, 0x0651, 0x0698, 0x06E0, 0x072A, 0x0776,
+ 0x07C4, 0x0814, 0x0866, 0x08B9, 0x090F, 0x0967, 0x09C0, 0x0A1B, 0x0A79,
+ 0x0AD8, 0x0B3A, 0x0B9D, 0x0C03, 0x0C6A, 0x0CD4, 0x0D3F, 0x0DAD, 0x0E1D,
+ 0x0E8F, 0x0F03, 0x0F79, 0x0FF2, 0x106C, 0x10E9, 0x1168, 0x11E9, 0x126C,
+ 0x12F2, 0x137A, 0x1404, 0x1490, 0x151F, 0x15B0, 0x1643, 0x16D9, 0x1771,
+ 0x180B, 0x18A7, 0x1946, 0x19E8, 0x1A8B, 0x1B32, 0x1BDA, 0x1C85, 0x1D33,
+ 0x1DE2, 0x1E95, 0x1F49, 0x2001, 0x20BB, 0x2177, 0x2236, 0x22F7, 0x23BB,
+ 0x2481, 0x254A, 0x2616, 0x26E4, 0x27B5, 0x2888, 0x295E, 0x2A36, 0x2B11,
+ 0x2BEF, 0x2CD0, 0x2DB3, 0x2E99, 0x2F81, 0x306D, 0x315A, 0x324B, 0x333F,
+ 0x3435, 0x352E, 0x3629, 0x3728, 0x3829, 0x392D, 0x3A33, 0x3B3D, 0x3C49,
+ 0x3D59, 0x3E6B, 0x3F80, 0x4097, 0x41B2, 0x42D0, 0x43F0, 0x4513, 0x463A,
+ 0x4763, 0x488F, 0x49BE, 0x4AF0, 0x4C25, 0x4D5D, 0x4E97, 0x4FD5, 0x5116,
+ 0x525A, 0x53A1, 0x54EB, 0x5638, 0x5787, 0x58DA, 0x5A31, 0x5B8A, 0x5CE6,
+ 0x5E45, 0x5FA7, 0x610D, 0x6276, 0x63E1, 0x6550, 0x66C2, 0x6837, 0x69AF,
+ 0x6B2B, 0x6CAA, 0x6E2B, 0x6FB0, 0x7139, 0x72C4, 0x7453, 0x75E5, 0x777A,
+ 0x7912, 0x7AAE, 0x7C4C, 0x7DEF, 0x7F94, 0x813D, 0x82E9, 0x8498, 0x864B,
+ 0x8801, 0x89BA, 0x8B76, 0x8D36, 0x8EFA, 0x90C0, 0x928A, 0x9458, 0x9629,
+ 0x97FD, 0x99D4, 0x9BB0, 0x9D8E, 0x9F70, 0xA155, 0xA33E, 0xA52A, 0xA71A,
+ 0xA90D, 0xAB04, 0xACFE, 0xAEFB, 0xB0FC, 0xB301, 0xB509, 0xB715, 0xB924,
+ 0xBB37, 0xBD4D, 0xBF67, 0xC184, 0xC3A5, 0xC5CA, 0xC7F2, 0xCA1E, 0xCC4D,
+ 0xCE80, 0xD0B7, 0xD2F1, 0xD52F, 0xD771, 0xD9B6, 0xDBFE, 0xDE4B, 0xE09B,
+ 0xE2EF, 0xE547, 0xE7A2, 0xEA01, 0xEC63, 0xEECA, 0xF134, 0xF3A2, 0xF613,
+ 0xF888, 0xFB02, 0xFD7E, 0xFFFF
+};
+
+/**
+ * Configure a PWM pin of Timer 0 (8-bit resolution).
+ *
+ * @note Timer 0 is used by tmk's timer module as well.
+ * You can therefore only temporarilly PWM-modulate using timer 0
+ * (ie. NOT during matrix scanning), cannot use any timer module functions
+ * and should call timer_init() afterwards.
+ * This can be easily resolved by using pwm_pb5_set_led() for one of the LEDs
+ * and redistribute all the LED.
+ * The buzzer can then use the no longer used PD0 since it's manually
+ * IRQ-triggered anyway.
+ *
+ * @param channel Channel to initialize.
+ * 0 (OC0A/PB7), 1 (OC0B/PD0)
+ */
+static void pwm_timer0_init(uint8_t channel)
+{
+ /* Fast PWM on OC0x, inverted duty cycle, TOP = 0xFF */
+ TCCR0A |= (0b11 << (3-channel)*2) | 0b11;
+ /* no prescaling */
+ TCCR0B = 0b00000001;
+}
+
+void pwm_pd0_set_led(uint8_t brightness)
+{
+ switch (brightness) {
+ case 0:
+ TCCR0A &= ~0b00110000;
+ PORTD |= (1 << PD0);
+ break;
+ case 255:
+ TCCR0A &= ~0b00110000;
+ PORTD &= ~(1 << PD0);
+ break;
+ default:
+ pwm_timer0_init(1);
+ OCR0B = pgm_read_byte(&pwm_table8[brightness]);
+ break;
+ }
+}
+
+/**
+ * Configure a PWM pin of Timer 1 (16-bit resolution).
+ *
+ * @param channel Channel to initialize.
+ * 0 (OC1A/PB5), 1 (OC1B/PB6), 2 (OC1C/PB7)
+ */
+static void pwm_timer1_init(uint8_t channel)
+{
+ /* Fast PWM on OC1x, inverted duty cycle, TOP = ICR1 */
+ TCCR1A |= (0b11 << (3-channel)*2) | 0b10;
+ /* stop timer */
+ TCCR1B = 0;
+ /* TOP for PWM - full 16 Bit */
+ ICR1 = 0xFFFF;
+ /* no prescaling */
+ TCCR1B = 0b00011001;
+}
+
+void pwm_pb5_set_led(uint8_t brightness)
+{
+ switch (brightness) {
+ case 0:
+ TCCR1A &= ~0b11000000;
+ PORTB |= (1 << PB5);
+ break;
+ case 255:
+ TCCR1A &= ~0b11000000;
+ PORTB &= ~(1 << PB5);
+ break;
+ default:
+ pwm_timer1_init(0);
+ OCR1A = pgm_read_word(&pwm_table16[brightness]);
+ break;
+ }
+}
+
+void pwm_pb6_set_led(uint8_t brightness)
+{
+ switch (brightness) {
+ case 0:
+ TCCR1A &= ~0b00110000;
+ PORTB |= (1 << PB6);
+ break;
+ case 255:
+ TCCR1A &= ~0b00110000;
+ PORTB &= ~(1 << PB6);
+ break;
+ default:
+ pwm_timer1_init(1);
+ OCR1B = pgm_read_word(&pwm_table16[brightness]);
+ break;
+ }
+}
+
+void pwm_pb7_set_led(uint8_t brightness)
+{
+ switch (brightness) {
+ case 0:
+ TCCR1A &= ~0b00001100;
+ PORTB |= (1 << PB7);
+ break;
+ case 255:
+ TCCR1A &= ~0b00001100;
+ PORTB &= ~(1 << PB7);
+ break;
+ default:
+ pwm_timer1_init(2);
+ OCR1C = pgm_read_word(&pwm_table16[brightness]);
+ break;
+ }
+}
+
+/**
+ * Configure a PWM pin of Timer 2 (8-bit resolution).
+ *
+ * @param channel Channel to initialize.
+ * 0 (OC2A/PB4), 1 (OC2B/PD1)
+ */
+static void pwm_timer2_init(uint8_t channel)
+{
+ /* Fast PWM on OC2x, inverted duty cycle, TOP = 0xFF */
+ TCCR2A |= (0b11 << (3-channel)*2) | 0b11;
+ /* no prescaling */
+ TCCR2B = 0b00000001;
+}
+
+void pwm_pb4_set_led(uint8_t brightness)
+{
+ switch (brightness) {
+ case 0:
+ TCCR2A &= ~0b11000000;
+ PORTB |= (1 << PB4);
+ break;
+ case 255:
+ TCCR2A &= ~0b11000000;
+ PORTB &= ~(1 << PB4);
+ break;
+ default:
+ pwm_timer2_init(0);
+ OCR2A = pgm_read_byte(&pwm_table8[brightness]);
+ break;
+ }
+}
+
+void pwm_pd1_set_led(uint8_t brightness)
+{
+ switch (brightness) {
+ case 0:
+ TCCR2A &= ~0b00110000;
+ PORTD |= (1 << PD1);
+ break;
+ case 255:
+ TCCR2A &= ~0b00110000;
+ PORTD &= ~(1 << PD1);
+ break;
+ default:
+ pwm_timer2_init(1);
+ OCR2B = pgm_read_byte(&pwm_table8[brightness]);
+ break;
+ }
+}
+
+/**
+ * Set a LED by position in the first row.
+ *
+ * @bug The pins used should be rescrambled after we freed PD0.
+ *
+ * @param led LED to set (0-4).
+ * @param brightness Brightness level to set.
+ */
+void pwm_set_led(uint8_t led, uint8_t brightness)
+{
+ switch (led) {
+ case 0: pwm_pd0_set_led(brightness); break;
+ case 1: pwm_pb7_set_led(brightness); break;
+ case 2: pwm_pd1_set_led(brightness); break;
+ case 3: pwm_pb6_set_led(brightness); break;
+ case 4: pwm_pb4_set_led(brightness); break;
+ }
+}
+
+/**
+ * Play a tone on PB5 (the buzzer).
+ *
+ * @note This uses timer 3 with an IRQ, even though we cannot
+ * use one of the Timer 3 PWM pins -- they are required for matrix
+ * strobing.
+ * It was problematic to use one of the accessible Timer 0-2 pins as
+ * a frequency generator for the buzzer, though:
+ * - Timer 0 (PD0) does not support toggle mode and regular PWM mode would
+ * leave only a 7-bit frequency resolution.
+ * - Timer 1 is shared with two other LED pins.
+ * It is therefore not possible to use toggle mode, but the remaining 15
+ * bit are sufficient anyway. Unfortunately, changing the frequency changes
+ * the timer's TOP value, which requires recalculating the duty cycles of the
+ * remaining two pins. This calculation is slow and cannot be cached.
+ * Also, changing the frequency would always introduce some flickering.
+ * - The same is true for timer 2, just with even less resolution.
+ * Using timer 3 exclusively for sound has the advantage that we can also
+ * some day use an IRQ for playing back (bit banging) audio samples eg.
+ * for a keyclick sounds.
+ *
+ * @todo It would be nice, if we could specify the tone's volume.
+ * This will reduce the effective resolution to 15 bit, but that should be
+ * sufficient anyway.
+ *
+ * @param freq Frequency to play. If 0, disables the buzzer.
+ */
+void pwm_pb5_set_tone(uint16_t freq)
+{
+ if (!freq) {
+ /*
+ * There should be a way to turn off the buzzer as we would otherwise
+ * always have some kind of tone.
+ */
+ TIMSK3 = 0;
+ return;
+ }
+
+ /*
+ * CTC mode: Effectively allows us to toggle the pin after OCR3A counts.
+ * This allows us to use the full 16-bit resolution.
+ * On the other hand, if we'd like to control volume, we'd have to
+ * control the cycle length independently, reducing resolution to 15 bit.
+ */
+ TCCR3A = 0b00;
+ /*
+ * Prescaling: 8 (0b010).
+ * This allows for frequencies between 15 Hz and 1 MHz.
+ *
+ * FIXME: It may be even better to use 1 if we have frequencies below 122 Hz.
+ */
+ TCCR3B = 0b00001010;
+
+ /*
+ * The frequency of the resulting signal is calculated as follows:
+ * F_CPU / PRESCALER / (CYCLE_LENGTH+1) / 2
+ */
+ OCR3A = (F_CPU/2/8) / freq - 1;
+
+ /* enable TIMER3_COMPA_vect() interrupt */
+ TIMSK3 = (1 << OCIE3A);
+}
+
+ISR(TIMER3_COMPA_vect, ISR_NOBLOCK)
+{
+ PORTB ^= (1 << PB5);
+}
diff --git a/pwm.h b/pwm.h
new file mode 100644
index 0000000..f1e0d3f
--- /dev/null
+++ b/pwm.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef PWM_H
+#define PWM_H
+
+#include <stdint.h>
+
+void pwm_pd0_set_led(uint8_t brightness);
+void pwm_pd1_set_led(uint8_t brightness);
+void pwm_pb4_set_led(uint8_t brightness);
+void pwm_pb5_set_led(uint8_t brightness);
+void pwm_pb6_set_led(uint8_t brightness);
+void pwm_pb7_set_led(uint8_t brightness);
+
+void pwm_set_led(uint8_t led, uint8_t brightness);
+
+void pwm_pb5_set_tone(uint16_t freq);
+
+#endif
diff --git a/song.c b/song.c
new file mode 100644
index 0000000..cd3b000
--- /dev/null
+++ b/song.c
@@ -0,0 +1,456 @@
+/*
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdint.h>
+
+#include <avr/pgmspace.h>
+#include <util/delay.h>
+
+#include "debug.h"
+#include "timer.h"
+#include "led.h"
+#include "host.h"
+#include "pwm.h"
+#include "keyclick.h"
+#include "song.h"
+
+static void delay_long(uint16_t ms)
+{
+ while (ms >= 10) {
+ _delay_ms(10);
+ ms -= 10;
+ }
+ switch (ms) {
+ case 9: _delay_ms(9); break;
+ case 8: _delay_ms(8); break;
+ case 7: _delay_ms(7); break;
+ case 6: _delay_ms(6); break;
+ case 5: _delay_ms(5); break;
+ case 4: _delay_ms(4); break;
+ case 3: _delay_ms(3); break;
+ case 2: _delay_ms(2); break;
+ case 1: _delay_ms(1); break;
+ }
+}
+
+struct song_note {
+ uint16_t freq;
+ uint16_t dur;
+};
+
+// midicsv anthem_ddr.mid | perl midicsv2frequency.pl
+static const struct song_note song_ruinen[] PROGMEM = {
+ {440,585},
+ {440,459},
+ {0,585},
+ {0,156},
+ {391,500},
+ {0,100},
+ {349,500},
+ {0,100},
+ {466,1044},
+ {0,156},
+ {440,500},
+ {0,100},
+ {391,500},
+ {0,100},
+ {523,500},
+ {0,100},
+ {440,500},
+ {0,100},
+ {349,500},
+ {0,100},
+ {523,500},
+ {0,100},
+ {523,800},
+ {0,100},
+ {587,294},
+ {0,6},
+ {466,1044},
+ {0,156},
+ {440,1044},
+ {0,156},
+ {391,500},
+ {0,100},
+ {349,500},
+ {0,100},
+ {466,1044},
+ {0,156},
+ {440,500},
+ {0,100},
+ {391,500},
+ {0,100},
+ {523,500},
+ {0,100},
+ {440,500},
+ {0,100},
+ {587,500},
+ {0,100},
+ {466,500},
+ {0,100},
+ {391,800},
+ {0,100},
+ {329,294},
+ {0,6},
+ {349,1700},
+ {0,100},
+ {349,444},
+ {0,6},
+ {329,144},
+ {0,6},
+ {293,800},
+ {0,100},
+ {329,294},
+ {0,6},
+ {349,800},
+ {0,100},
+ {440,294},
+ {0,6},
+ {391,500},
+ {0,100},
+ {261,1100},
+ {0,100},
+ {349,444},
+ {0,6},
+ {329,144},
+ {0,6},
+ {293,800},
+ {0,100},
+ {329,294},
+ {0,6},
+ {349,800},
+ {0,100},
+ {440,294},
+ {0,6},
+ {391,1044},
+ {0,156},
+ {440,500},
+ {0,100},
+ {440,500},
+ {0,100},
+ {391,500},
+ {0,100},
+ {349,500},
+ {0,100},
+ {466,500},
+ {0,100},
+ {466,500},
+ {0,100},
+ {440,500},
+ {0,100},
+ {391,500},
+ {0,100},
+ {523,500},
+ {0,100},
+ {440,500},
+ {0,100},
+ {349,500},
+ {0,100},
+ {523,500},
+ {0,100},
+ {523,800},
+ {0,100},
+ {587,294},
+ {0,6},
+ {466,500},
+ {0,100},
+ {349,294},
+ {0,6},
+ {391,294},
+ {0,6},
+ {440,1044},
+ {0,156},
+ {391,1044},
+ {0,156},
+ {523,1044},
+ {0,156},
+ {349,500},
+ {0,100},
+ {391,500},
+ {0,100},
+ {440,1044},
+ {0,156},
+ {391,1044},
+ {0,156},
+ {349,1044}
+};
+
+void song_play_ruinen(void)
+{
+ keyclick_solenoid_set(false);
+
+ uint8_t i = 0;
+ for (long unsigned int cur_note = 0; cur_note < sizeof(song_ruinen)/sizeof(song_ruinen[0]); cur_note++) {
+ pwm_pb5_set_tone(pgm_read_word(&song_ruinen[cur_note].freq));
+
+ uint8_t max_brightness = ((uint32_t)pgm_read_word(&song_ruinen[cur_note].freq)*255)/600;
+ uint16_t fade_dur = pgm_read_word(&song_ruinen[cur_note].dur)/2/(max_brightness+1);
+ for (int16_t brightness = 0; brightness <= max_brightness; brightness++) {
+ pwm_set_led(i % 5, brightness);
+ delay_long(fade_dur);
+ }
+
+ /*
+ * The fade duration is probably not very precise, so compensate for it.
+ *
+ * FIXME: Once timer 0 is freed, we should use timer_read() and timer_elapsed()
+ * which will be more precise.
+ */
+ delay_long(pgm_read_word(&song_ruinen[cur_note].dur) - fade_dur*(max_brightness+1)*2);
+
+ for (int16_t brightness = max_brightness; brightness >= 0; brightness--) {
+ pwm_set_led(i % 5, brightness);
+ delay_long(fade_dur);
+ }
+
+ if (pgm_read_word(&song_ruinen[cur_note].freq))
+ i++;
+ }
+
+ pwm_pb5_set_tone(0);
+
+ /* we screwed up timer 0 settings and they are also used by the timer module */
+ timer_init();
+
+ /* restore the previous lock lights */
+ led_set(host_keyboard_leds());
+}
+
+static const struct song_note song_knight_rider[] PROGMEM = {
+ // KnightRider:d=4, o=5, b=125:16e, 16p, 16f, 16e, 16e, 16p, 16e, 16e, 16f, 16e, 16e, 16e,
+ // 16d#, 16e, 16e, 16e, 16e, 16p, 16f, 16e, 16e, 16p, 16f, 16e, 16f, 16e, 16e, 16e, 16d#,
+ // 16e, 16e, 16e, 16d, 16p, 16e, 16d, 16d, 16p, 16e, 16d, 16e, 16d, 16d, 16d, 16c, 16d, 16d,
+ // 16d, 16d, 16p, 16e, 16d, 16d, 16p, 16e, 16d, 16e, 16d, 16d, 16d, 16c, 16d, 16d, 16d
+ {0, 480},
+ {330, 480},
+ {0, 120},
+ {349, 120},
+ {330, 120},
+ {330, 120},
+ {0, 120},
+ {330, 120},
+ {330, 120},
+ {349, 120},
+ {330, 120},
+ {330, 120},
+ {330, 120},
+ {311, 120},
+ {330, 120},
+ {330, 120},
+ {330, 120},
+ {330, 120},
+ {0, 120},
+ {349, 120},
+ {330, 120},
+ {330, 120},
+ {0, 120},
+ {349, 120},
+ {330, 120},
+ {349, 120},
+ {330, 120},
+ {330, 120},
+ {330, 120},
+ {311, 120},
+ {330, 120},
+ {330, 120},
+ {330, 120},
+ {294, 120},
+ {0, 120},
+ {330, 120},
+ {294, 120},
+ {294, 120},
+ {0, 120},
+ {330, 120},
+ {294, 120},
+ {330, 120},
+ {294, 120},
+ {294, 120},
+ {294, 120},
+ {262, 120},
+ {294, 120},
+ {294, 120},
+ {294, 120},
+ {294, 120},
+ {0, 120},
+ {330, 120},
+ {294, 120},
+ {294, 120},
+ {0, 120},
+ {330, 120},
+ {294, 120},
+ {330, 120},
+ {294, 120},
+ {294, 120},
+ {294, 120},
+ {262, 120},
+ {294, 120},
+ {294, 120},
+
+ // KnightRider:d=4, o=5, b=63:16e, 32f, 32e, 8b, 16e6, 32f6, 32e6, 8b, 16e, 32f, 32e, 16b,
+ // 16e6, d6, 8p, p, 16e, 32f, 32e, 8b, 16e6, 32f6, 32e6, 8b, 16e, 32f, 32e, 16b, 16e6, f6, p
+ {0, 952},
+ {330, 952},
+ {349, 119},
+ {330, 119},
+ {494, 476},
+ {659, 238},
+ {698, 119},
+ {659, 119},
+ {494, 476},
+ {330, 238},
+ {349, 119},
+ {330, 119},
+ {494, 238},
+ {659, 238},
+ {587, 952},
+ {0, 476},
+ {0, 952},
+ {330, 238},
+ {349, 119},
+ {330, 119},
+ {494, 476},
+ {659, 238},
+ {698, 119},
+ {659, 119},
+ {494, 476},
+ {330, 238},
+ {349, 119},
+ {330, 119},
+ {494, 238},
+ {659, 238},
+ {698, 952}
+
+#if 0
+ {0, 952},
+ {659, 952},
+ {698, 119},
+ {659, 119},
+ {988, 476},
+ {1319, 238},
+ {1397, 119},
+ {1319, 119},
+ {988, 476},
+ {659, 238},
+ {698, 119},
+ {659, 119},
+ {988, 238},
+ {1319, 238},
+ {1175, 952},
+ {0, 476},
+ {0, 952},
+ {659, 238},
+ {698, 119},
+ {659, 119},
+ {988, 476},
+ {1319, 238},
+ {1397, 119},
+ {1319, 119},
+ {988, 476},
+ {659, 238},
+ {698, 119},
+ {659, 119},
+ {988, 238},
+ {1319, 238},
+ {1397, 952}
+#endif
+};
+
+/**
+ * Light curve of the Larsen light.
+ * This is half a period of a sine curve now.
+ */
+static const uint8_t song_larsen_curve[] PROGMEM = {
+ 3, 6, 9, 12, 15, 18, 21, 24, 28, 31, 34, 37, 40, 43, 46, 49, 52, 55, 58, 61, 64, 68,
+ 71, 74, 77, 79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 111, 114, 117, 120, 122,
+ 125, 128, 131, 133, 136, 139, 141, 144, 146, 149, 151, 154, 156, 159, 161, 164, 166,
+ 168, 171, 173, 175, 178, 180, 182, 184, 186, 188, 191, 193, 195, 197, 199, 201, 202,
+ 204, 206, 208, 210, 212, 213, 215, 217, 218, 220, 221, 223, 224, 226, 227, 229, 230,
+ 231, 233, 234, 235, 236, 237, 239, 240, 241, 242, 243, 244, 244, 245, 246, 247, 248,
+ 248, 249, 250, 250, 251, 251, 252, 252, 253, 253, 253, 254, 254, 254, 254, 254, 254,
+ 254, 255, 254, 254, 254, 254, 254, 254, 254, 253, 253, 253, 252, 252, 251, 251, 250,
+ 250, 249, 248, 248, 247, 246, 245, 244, 244, 243, 242, 241, 240, 239, 237, 236, 235,
+ 234, 233, 231, 230, 229, 227, 226, 224, 223, 221, 220, 218, 217, 215, 213, 212, 210,
+ 208, 206, 204, 202, 201, 199, 197, 195, 193, 191, 188, 186, 184, 182, 180, 178, 175,
+ 173, 171, 168, 166, 164, 161, 159, 156, 154, 151, 149, 146, 144, 141, 139, 136, 133,
+ 131, 128, 125, 122, 120, 117, 114, 111, 109, 106, 103, 100, 97, 94, 91, 88, 85, 82,
+ 79, 77, 74, 71, 68, 64, 61, 58, 55, 52, 49, 46, 43, 40, 37, 34, 31, 28, 24, 21, 18,
+ 15, 12, 9, 6, 3, 0
+};
+
+/**
+ * Step length for the Larsen curve animation.
+ * This will result in 1024ms per sweep.
+ */
+#define LARSEN_CURVE_STEP 4 /* ms */
+
+static void song_larsen_light(int16_t pos)
+{
+ for (uint8_t led = 0; led < 5; led++) {
+ int16_t offset = sizeof(song_larsen_curve)*(2+led)/4 - pos;
+ pwm_set_led(led, 0 <= offset && offset < sizeof(song_larsen_curve)
+ ? pgm_read_byte(&song_larsen_curve[offset]) : 0);
+ }
+
+ _delay_ms(LARSEN_CURVE_STEP);
+}
+
+void song_play_kitt(void)
+{
+ keyclick_solenoid_set(false);
+
+ int16_t i;
+
+ /* fade in larsen light */
+ for (i = (int16_t)sizeof(song_larsen_curve)/-2; i < 0; i++)
+ song_larsen_light(i);
+
+ long unsigned int cur_note = 0;
+ uint16_t cur_note_dur = 0;
+
+ int8_t dir = 1;
+ for (i = 0; cur_note < sizeof(song_knight_rider)/sizeof(song_knight_rider[0]); i += dir) {
+ /*
+ * FIXME: Once we freed up timer 0 by rearranging the LED pins,
+ * it might be more precise to use timer_read()/timer_elapsed() to wait for the next note.
+ */
+ if (cur_note_dur < LARSEN_CURVE_STEP) {
+ cur_note_dur = pgm_read_word(&song_knight_rider[cur_note].dur);
+ pwm_pb5_set_tone(pgm_read_word(&song_knight_rider[cur_note].freq));
+ }
+
+ song_larsen_light(i);
+
+ cur_note_dur -= LARSEN_CURVE_STEP;
+ if (cur_note_dur < LARSEN_CURVE_STEP)
+ cur_note++;
+
+ if (i == sizeof(song_larsen_curve)-1 || (dir < 0 && i == 0))
+ dir *= -1;
+ }
+
+ pwm_pb5_set_tone(0);
+
+ /* fade out larsen light */
+ while (i < sizeof(song_larsen_curve)*3/2)
+ song_larsen_light(i++);
+
+ /* we screwed up timer 0 settings and they are also used by the timer module */
+ timer_init();
+
+ /* restore the previous lock lights */
+ led_set(host_keyboard_leds());
+}
diff --git a/song.h b/song.h
new file mode 100644
index 0000000..53f667f
--- /dev/null
+++ b/song.h
@@ -0,0 +1,24 @@
+/*
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SONG_H
+#define SONG_H
+
+void song_play_ruinen(void);
+void song_play_kitt(void);
+
+#endif
diff --git a/unimap_00.c b/unimap_00.c
new file mode 100644
index 0000000..cd266a7
--- /dev/null
+++ b/unimap_00.c
@@ -0,0 +1,34 @@
+/*
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <avr/pgmspace.h>
+
+#include "action_code.h"
+#include "unimap_trans.h"
+
+#ifdef KEYMAP_SECTION_ENABLE
+const action_t actionmaps[][UNIMAP_ROWS][UNIMAP_COLS] __attribute__ ((section (".keymap.keymaps"))) = {
+#else
+const action_t actionmaps[][UNIMAP_ROWS][UNIMAP_COLS] PROGMEM = {
+#endif
+ [0] = UNIMAP_K7637(
+ ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, PSCR,SLCK,PAUS, VOLD,VOLU,MUTE, F19,
+ GRV, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, MINS,EQL, BSPC, END, DEL, NLCK,PSLS, F20,
+ TAB, Q, W, E, R, T, Y, U, I, O, P, LBRC,RBRC, HOME,PGUP, P7, P8, P9, PPLS, F21,
+ CAPS,A, S, D, F, G, H, J, K, L, SCLN,QUOT,NUHS, ENT, PGDN, P4, P5, P6, NO, F22,
+ LSFT,NUBS,Z, X, C, V, B, N, M, COMM,DOT, SLSH, DOWN,UP, P1, P2, P3, PENT, F23,
+ NO, LCTL, SPC, RALT, NO, LEFT,RGHT, P0, P00, PCMM,NO, F24),
+};
diff --git a/unimap_trans.h b/unimap_trans.h
new file mode 100644
index 0000000..a616fd1
--- /dev/null
+++ b/unimap_trans.h
@@ -0,0 +1,99 @@
+/*
+Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef UNIMAP_TRANS_H
+#define UNIMAP_TRANS_H
+
+#include <stdint.h>
+#include <avr/pgmspace.h>
+
+#include "unimap.h"
+
+#define UNIMAP_K7637( \
+ K29,K3A,K3B,K3C,K3D,K3E,K3F,K40,K41,K42,K43,K44,K45,K68, K46,K47,K48, K01,K02,K03, K6E, \
+ K35,K1E,K1F,K20,K21,K22,K23,K24,K25,K26,K27,K2D,K2E,K2A, K4A,K4C, K53,K54, K6F, \
+ K2B,K14,K1A,K08,K15,K17,K1C,K18,K0C,K12,K13,K2F,K30, K4D,K4B, K5F,K60,K61,K57, K70, \
+ K39,K04,K16,K07,K09,K0A,K0B,K0D,K0E,K0F,K33,K34,K32, K28,K4E, K5C,K5D,K5E,K66, K71, \
+ K79,K64,K1D,K1B,K06,K19,K05,K11,K10,K36,K37,K38, K51,K52, K59,K5A,K5B,K58, K72, \
+ K78, K7A, K2C, K7E, K7C, K50,K4F, K62,K55,K63,K67, K73 \
+) UNIMAP( \
+ K68, NO, NO, NO, NO, NO,K6E,K6F,K70,K71,K72,K73, \
+ K29, K3A,K3B,K3C,K3D,K3E,K3F,K40,K41,K42,K43,K44,K45, K46,K47,K48, K01,K02,K03, \
+ K35,K1E,K1F,K20,K21,K22,K23,K24,K25,K26,K27,K2D,K2E, NO,K2A, NO,K4A,K4B, K53,K54,K55, NO, \
+ K2B,K14,K1A,K08,K15,K17,K1C,K18,K0C,K12,K13,K2F,K30, NO, K4C,K4D,K4E, K5F,K60,K61,K57, \
+ K39,K04,K16,K07,K09,K0A,K0B,K0D,K0E,K0F,K33,K34, K32,K28, K5C,K5D,K5E,K66, \
+ K79,K64,K1D,K1B,K06,K19,K05,K11,K10,K36,K37,K38, NO, NO, K52, K59,K5A,K5B,K58, \
+ K78, NO,K7A, NO, K2C, NO, NO,K7E, NO, NO,K7C, K50,K51,K4F, K62,K63,K67 \
+)
+
+/* Mapping to Universal keyboard layout
+ * ,-----------------------------------------------.
+ * |F13|F14|F15|F16|F17|F18|F19|F20|F21|F22|F23|F24|
+ * ,---. |-----------------------------------------------| ,-----------. ,-----------.
+ * |Esc| |F1 |F2 |F3 |F4 |F5 |F6 |F7 |F8 |F9 |F10|F11|F12| |PrS|ScL|Pau| |VDn|VUp|Mut|
+ * `---' `-----------------------------------------------' `-----------' `-----------'
+ * ,-----------------------------------------------------------. ,-----------. ,---------------.
+ * | `| 1| 2| 3| 4| 5| 6| 7| 8| 9| 0| -| =|JPY|Bsp| |Ins|Hom|PgU| |NmL| /| *| -|
+ * |-----------------------------------------------------------| |-----------| |---------------|
+ * |Tab | Q| W| E| R| T| Y| U| I| O| P| [| ]| \ | |Del|End|PgD| | 7| 8| 9| +|
+ * |-----------------------------------------------------------| `-----------' |---------------|
+ * |CapsL | A| S| D| F| G| H| J| K| L| ;| '| #|Retn| | 4| 5| 6|KP,|
+ * |-----------------------------------------------------------| ,---. |---------------|
+ * |Shft| <| Z| X| C| V| B| N| M| ,| ,| /| RO|Shift | |Up | | 1| 2| 3|KP=|
+ * |-----------------------------------------------------------| ,-----------. |---------------|
+ * |Ctl|Gui|Alt|MHEN| Space |HENK|KANA|Alt|Gui|App|Ctl| |Lef|Dow|Rig| | 0 | .|Ent|
+ * `-----------------------------------------------------------' `-----------' `---------------'
+ *
+ * ,-----------------------------------------------.
+ * | 68| 69| 6A| 6B| 6C| 6D| 6E| 6F| 70| 71| 72| 73|
+ * ,---. |-----------------------------------------------| ,-----------. ,-----------.
+ * | 29| | 3A| 3B| 3C| 3D| 3E| 3F| 40| 41| 42| 43| 44| 45| | 46| 47| 48| | 01| 02| 03|
+ * `---' `-----------------------------------------------' `-----------' `-----------'
+ * ,-----------------------------------------------------------. ,-----------. ,---------------.
+ * | 35| 1E| 1F| 20| 21| 22| 23| 24| 25| 26| 27| 2D| 2E| 74| 2A| | 49| 4A| 4B| | 53| 54| 55| 56|
+ * |-----------------------------------------------------------| |-----------| |---------------|
+ * | 2B| 14| 1A| 08| 15| 17| 1C| 18| 0C| 12| 13| 2F| 30| 31| | 4C| 4D| 4E| | 5F| 60| 61| 57|
+ * |-----------------------------------------------------------| `-----------' |---------------|
+ * | 39| 04| 16| 07| 09| 0A| 0B| 0D| 0E| 0F| 33| 34| 32| 28| | 5C| 5D| 5E| 66|
+ * |-----------------------------------------------------------| ,---. |---------------|
+ * | 79| 64| 1D| 1B| 06| 19| 05| 11| 10| 36| 37| 38| 75| 7D| | 52| | 59| 5A| 5B| 58|
+ * |-----------------------------------------------------------| ,-----------. |---------------|
+ * | 78| 7B| 7A| 77| 2C| 76| 00| 7E| 7F| 65| 7C| | 50| 51| 4F| | 62| 63| 67|
+ * `-----------------------------------------------------------' `-----------' `---------------'
+ *
+ * This is a direct adaption of the table in "Serviceschaltplaene", p.3.
+ * I tried to keep the geometries as close as possible and support keys that my particular model
+ * does not feature.
+ * This may have resulted in countrintuitive keycaps, but makes sure that other models will also
+ * be supported and makes it easier to work with the keymap editor.
+ *
+ * NOTE: The first 6 rows in the last column aren't in the original keyboard matrix - they are the
+ * result of mapping the "security" key.
+ */
+#define NO UNIMAP_NO
+const uint8_t PROGMEM unimap_trans[MATRIX_ROWS][MATRIX_COLS] = {
+ {0x02, 0x01, 0x48, 0x45, 0x46, 0x44, 0x40, 0x43, 0x3C, 0x29, 0x3D, 0x42, 0x3E, NO, 0x3A, 0x6E},
+ { NO, NO, NO, 0x51, 0x47, 0x34, 0x10, 0x38, 0x06, NO, 0x05, 0x37, 0x11, NO, 0x1B, 0x6F},
+ {0x61, 0x60, 0x5F, 0x2E, 0x4B, 0x2D, 0x23, 0x12, 0x1F, 0x2B, 0x17, 0x25, 0x22, 0x57, 0x1E, 0x70},
+ {0x5B, 0x5A, 0x59, 0x4D, 0x52, 0x30, 0x0D, 0x33, 0x07, 0x64, 0x09, 0x0E, 0x0B, 0x58, 0x14, 0x71},
+ {0x5E, 0x5D, 0x5C, 0x2A, 0x4E, 0x2F, 0x18, 0x13, 0x08, 0x04, 0x15, 0x0C, 0x1C, 0x66, 0x1A, 0x72},
+ {0x63, 0x55, 0x62, 0x50, 0x4F, 0x7C, NO, 0x7E, 0x2C, 0x7A, NO, NO, NO, 0x67, 0x78, 0x73},
+ {0x03, 0x54, 0x53, 0x68, 0x4C, 0x27, 0x24, 0x26, 0x20, NO, 0x21, 0x41, 0x3F, NO, 0x3B, 0x35},
+ { NO, NO, NO, 0x28, 0x4A, 0x32, 0x36, 0x39, 0x19, 0x1D, 0x0A, 0x0F, NO, NO, 0x16, 0x79}
+};
+#undef NO
+
+#endif
diff --git a/usbconfig.h b/usbconfig.h
new file mode 100644
index 0000000..01e5aa6
--- /dev/null
+++ b/usbconfig.h
@@ -0,0 +1,381 @@
+/* Name: usbconfig.h
+ * Project: V-USB, virtual USB port for Atmel's(r) AVR(r) microcontrollers
+ * Author: Christian Starkjohann
+ * Creation Date: 2005-04-01
+ * Tabsize: 4
+ * Copyright: (c) 2005 by OBJECTIVE DEVELOPMENT Software GmbH
+ * License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
+ * This Revision: $Id: usbconfig-prototype.h 785 2010-05-30 17:57:07Z cs $
+ */
+
+#ifndef __usbconfig_h_included__
+#define __usbconfig_h_included__
+
+
+/*
+General Description:
+This file is an example configuration (with inline documentation) for the USB
+driver. It configures V-USB for USB D+ connected to Port D bit 2 (which is
+also hardware interrupt 0 on many devices) and USB D- to Port D bit 4. You may
+wire the lines to any other port, as long as D+ is also wired to INT0 (or any
+other hardware interrupt, as long as it is the highest level interrupt, see
+section at the end of this file).
+*/
+
+/* ---------------------------- Hardware Config ---------------------------- */
+
+#define USB_CFG_IOPORTNAME B
+/* This is the port where the USB bus is connected. When you configure it to
+ * "B", the registers PORTB, PINB and DDRB will be used.
+ */
+#define USB_CFG_DMINUS_BIT 1
+/* This is the bit number in USB_CFG_IOPORT where the USB D- line is connected.
+ * This may be any bit in the port.
+ */
+#define USB_CFG_DPLUS_BIT 2
+/* This is the bit number in USB_CFG_IOPORT where the USB D+ line is connected.
+ * This may be any bit in the port. Please note that D+ must also be connected
+ * to interrupt pin INT0! [You can also use other interrupts, see section
+ * "Optional MCU Description" below, or you can connect D- to the interrupt, as
+ * it is required if you use the USB_COUNT_SOF feature. If you use D- for the
+ * interrupt, the USB interrupt will also be triggered at Start-Of-Frame
+ * markers every millisecond.]
+ */
+#define USB_CFG_CLOCK_KHZ (F_CPU/1000)
+/* Clock rate of the AVR in kHz. Legal values are 12000, 12800, 15000, 16000,
+ * 16500, 18000 and 20000. The 12.8 MHz and 16.5 MHz versions of the code
+ * require no crystal, they tolerate +/- 1% deviation from the nominal
+ * frequency. All other rates require a precision of 2000 ppm and thus a
+ * crystal!
+ * Since F_CPU should be defined to your actual clock rate anyway, you should
+ * not need to modify this setting.
+ */
+#define USB_CFG_CHECK_CRC 0
+/* Define this to 1 if you want that the driver checks integrity of incoming
+ * data packets (CRC checks). CRC checks cost quite a bit of code size and are
+ * currently only available for 18 MHz crystal clock. You must choose
+ * USB_CFG_CLOCK_KHZ = 18000 if you enable this option.
+ */
+
+/* ----------------------- Optional Hardware Config ------------------------ */
+
+/* #define USB_CFG_PULLUP_IOPORTNAME D */
+/* If you connect the 1.5k pullup resistor from D- to a port pin instead of
+ * V+, you can connect and disconnect the device from firmware by calling
+ * the macros usbDeviceConnect() and usbDeviceDisconnect() (see usbdrv.h).
+ * This constant defines the port on which the pullup resistor is connected.
+ */
+/* #define USB_CFG_PULLUP_BIT 4 */
+/* This constant defines the bit number in USB_CFG_PULLUP_IOPORT (defined
+ * above) where the 1.5k pullup resistor is connected. See description
+ * above for details.
+ */
+
+/* --------------------------- Functional Range ---------------------------- */
+
+#define USB_CFG_HAVE_INTRIN_ENDPOINT 1
+/* Define this to 1 if you want to compile a version with two endpoints: The
+ * default control endpoint 0 and an interrupt-in endpoint (any other endpoint
+ * number).
+ */
+#define USB_CFG_HAVE_INTRIN_ENDPOINT3 1
+/* Define this to 1 if you want to compile a version with three endpoints: The
+ * default control endpoint 0, an interrupt-in endpoint 3 (or the number
+ * configured below) and a catch-all default interrupt-in endpoint as above.
+ * You must also define USB_CFG_HAVE_INTRIN_ENDPOINT to 1 for this feature.
+ */
+#define USB_CFG_EP3_NUMBER 3
+/* If the so-called endpoint 3 is used, it can now be configured to any other
+ * endpoint number (except 0) with this macro. Default if undefined is 3.
+ */
+/* #define USB_INITIAL_DATATOKEN USBPID_DATA1 */
+/* The above macro defines the startup condition for data toggling on the
+ * interrupt/bulk endpoints 1 and 3. Defaults to USBPID_DATA1.
+ * Since the token is toggled BEFORE sending any data, the first packet is
+ * sent with the oposite value of this configuration!
+ */
+#define USB_CFG_IMPLEMENT_HALT 0
+/* Define this to 1 if you also want to implement the ENDPOINT_HALT feature
+ * for endpoint 1 (interrupt endpoint). Although you may not need this feature,
+ * it is required by the standard. We have made it a config option because it
+ * bloats the code considerably.
+ */
+#define USB_CFG_SUPPRESS_INTR_CODE 0
+/* Define this to 1 if you want to declare interrupt-in endpoints, but don't
+ * want to send any data over them. If this macro is defined to 1, functions
+ * usbSetInterrupt() and usbSetInterrupt3() are omitted. This is useful if
+ * you need the interrupt-in endpoints in order to comply to an interface
+ * (e.g. HID), but never want to send any data. This option saves a couple
+ * of bytes in flash memory and the transmit buffers in RAM.
+ */
+#define USB_CFG_INTR_POLL_INTERVAL 10
+/* If you compile a version with endpoint 1 (interrupt-in), this is the poll
+ * interval. The value is in milliseconds and must not be less than 10 ms for
+ * low speed devices.
+ */
+#define USB_CFG_IS_SELF_POWERED 0
+/* Define this to 1 if the device has its own power supply. Set it to 0 if the
+ * device is powered from the USB bus.
+ */
+#define USB_CFG_MAX_BUS_POWER 100
+/* Set this variable to the maximum USB bus power consumption of your device.
+ * The value is in milliamperes. [It will be divided by two since USB
+ * communicates power requirements in units of 2 mA.]
+ */
+#define USB_CFG_IMPLEMENT_FN_WRITE 1
+/* Set this to 1 if you want usbFunctionWrite() to be called for control-out
+ * transfers. Set it to 0 if you don't need it and want to save a couple of
+ * bytes.
+ */
+#define USB_CFG_IMPLEMENT_FN_READ 0
+/* Set this to 1 if you need to send control replies which are generated
+ * "on the fly" when usbFunctionRead() is called. If you only want to send
+ * data from a static buffer, set it to 0 and return the data from
+ * usbFunctionSetup(). This saves a couple of bytes.
+ */
+#define USB_CFG_IMPLEMENT_FN_WRITEOUT 0
+/* Define this to 1 if you want to use interrupt-out (or bulk out) endpoints.
+ * You must implement the function usbFunctionWriteOut() which receives all
+ * interrupt/bulk data sent to any endpoint other than 0. The endpoint number
+ * can be found in 'usbRxToken'.
+ */
+#define USB_CFG_HAVE_FLOWCONTROL 0
+/* Define this to 1 if you want flowcontrol over USB data. See the definition
+ * of the macros usbDisableAllRequests() and usbEnableAllRequests() in
+ * usbdrv.h.
+ */
+#define USB_CFG_DRIVER_FLASH_PAGE 0
+/* If the device has more than 64 kBytes of flash, define this to the 64 k page
+ * where the driver's constants (descriptors) are located. Or in other words:
+ * Define this to 1 for boot loaders on the ATMega128.
+ */
+#define USB_CFG_LONG_TRANSFERS 0
+/* Define this to 1 if you want to send/receive blocks of more than 254 bytes
+ * in a single control-in or control-out transfer. Note that the capability
+ * for long transfers increases the driver size.
+ */
+/* #define USB_RX_USER_HOOK(data, len) if(usbRxToken == (uchar)USBPID_SETUP) blinkLED(); */
+/* This macro is a hook if you want to do unconventional things. If it is
+ * defined, it's inserted at the beginning of received message processing.
+ * If you eat the received message and don't want default processing to
+ * proceed, do a return after doing your things. One possible application
+ * (besides debugging) is to flash a status LED on each packet.
+ */
+#define USB_RESET_HOOK(resetStarts) if(!resetStarts){hadUsbReset();}
+/* http://codeandlife.com/2012/02/22/v-usb-with-attiny45-attiny85-without-a-crystal/ */
+#ifndef __ASSEMBLER__
+extern void hadUsbReset(void); // define the function for usbdrv.c
+#endif
+/* This macro is a hook if you need to know when an USB RESET occurs. It has
+ * one parameter which distinguishes between the start of RESET state and its
+ * end.
+ */
+/* #define USB_SET_ADDRESS_HOOK() hadAddressAssigned(); */
+/* This macro (if defined) is executed when a USB SET_ADDRESS request was
+ * received.
+ */
+#define USB_COUNT_SOF 0
+/* define this macro to 1 if you need the global variable "usbSofCount" which
+ * counts SOF packets. This feature requires that the hardware interrupt is
+ * connected to D- instead of D+.
+ */
+/* #ifdef __ASSEMBLER__
+ * macro myAssemblerMacro
+ * in YL, TCNT0
+ * sts timer0Snapshot, YL
+ * endm
+ * #endif
+ * #define USB_SOF_HOOK myAssemblerMacro
+ * This macro (if defined) is executed in the assembler module when a
+ * Start Of Frame condition is detected. It is recommended to define it to
+ * the name of an assembler macro which is defined here as well so that more
+ * than one assembler instruction can be used. The macro may use the register
+ * YL and modify SREG. If it lasts longer than a couple of cycles, USB messages
+ * immediately after an SOF pulse may be lost and must be retried by the host.
+ * What can you do with this hook? Since the SOF signal occurs exactly every
+ * 1 ms (unless the host is in sleep mode), you can use it to tune OSCCAL in
+ * designs running on the internal RC oscillator.
+ * Please note that Start Of Frame detection works only if D- is wired to the
+ * interrupt, not D+. THIS IS DIFFERENT THAN MOST EXAMPLES!
+ */
+#define USB_CFG_CHECK_DATA_TOGGLING 0
+/* define this macro to 1 if you want to filter out duplicate data packets
+ * sent by the host. Duplicates occur only as a consequence of communication
+ * errors, when the host does not receive an ACK. Please note that you need to
+ * implement the filtering yourself in usbFunctionWriteOut() and
+ * usbFunctionWrite(). Use the global usbCurrentDataToken and a static variable
+ * for each control- and out-endpoint to check for duplicate packets.
+ */
+#define USB_CFG_HAVE_MEASURE_FRAME_LENGTH 1
+/* define this macro to 1 if you want the function usbMeasureFrameLength()
+ * compiled in. This function can be used to calibrate the AVR's RC oscillator.
+ */
+#define USB_USE_FAST_CRC 0
+/* The assembler module has two implementations for the CRC algorithm. One is
+ * faster, the other is smaller. This CRC routine is only used for transmitted
+ * messages where timing is not critical. The faster routine needs 31 cycles
+ * per byte while the smaller one needs 61 to 69 cycles. The faster routine
+ * may be worth the 32 bytes bigger code size if you transmit lots of data and
+ * run the AVR close to its limit.
+ */
+
+/* -------------------------- Device Description --------------------------- */
+
+#define USB_CFG_VENDOR_ID (VENDOR_ID & 0xFF), ((VENDOR_ID >> 8) & 0xFF)
+/* USB vendor ID for the device, low byte first. If you have registered your
+ * own Vendor ID, define it here. Otherwise you may use one of obdev's free
+ * shared VID/PID pairs. Be sure to read USB-IDs-for-free.txt for rules!
+ * *** IMPORTANT NOTE ***
+ * This template uses obdev's shared VID/PID pair for Vendor Class devices
+ * with libusb: 0x16c0/0x5dc. Use this VID/PID pair ONLY if you understand
+ * the implications!
+ */
+#define USB_CFG_DEVICE_ID (PRODUCT_ID & 0xFF), ((PRODUCT_ID >> 8) & 0xFF)
+/* This is the ID of the product, low byte first. It is interpreted in the
+ * scope of the vendor ID. If you have registered your own VID with usb.org
+ * or if you have licensed a PID from somebody else, define it here. Otherwise
+ * you may use one of obdev's free shared VID/PID pairs. See the file
+ * USB-IDs-for-free.txt for details!
+ * *** IMPORTANT NOTE ***
+ * This template uses obdev's shared VID/PID pair for Vendor Class devices
+ * with libusb: 0x16c0/0x5dc. Use this VID/PID pair ONLY if you understand
+ * the implications!
+ */
+#define USB_CFG_DEVICE_VERSION 0x00, 0x01
+/* Version number of the device: Minor number first, then major number.
+ */
+#define USB_CFG_VENDOR_NAME 't', '.', 'm', '.', 'k', '.'
+#define USB_CFG_VENDOR_NAME_LEN 6
+/* These two values define the vendor name returned by the USB device. The name
+ * must be given as a list of characters under single quotes. The characters
+ * are interpreted as Unicode (UTF-16) entities.
+ * If you don't want a vendor name string, undefine these macros.
+ * ALWAYS define a vendor name containing your Internet domain name if you use
+ * obdev's free shared VID/PID pair. See the file USB-IDs-for-free.txt for
+ * details.
+ */
+#define USB_CFG_DEVICE_NAME 'O', 'n', 'e', 'k', 'e', 'y'
+#define USB_CFG_DEVICE_NAME_LEN 6
+/* Same as above for the device name. If you don't want a device name, undefine
+ * the macros. See the file USB-IDs-for-free.txt before you assign a name if
+ * you use a shared VID/PID.
+ */
+/*#define USB_CFG_SERIAL_NUMBER 'N', 'o', 'n', 'e' */
+/*#define USB_CFG_SERIAL_NUMBER_LEN 0 */
+/* Same as above for the serial number. If you don't want a serial number,
+ * undefine the macros.
+ * It may be useful to provide the serial number through other means than at
+ * compile time. See the section about descriptor properties below for how
+ * to fine tune control over USB descriptors such as the string descriptor
+ * for the serial number.
+ */
+#define USB_CFG_DEVICE_CLASS 0
+#define USB_CFG_DEVICE_SUBCLASS 0
+/* See USB specification if you want to conform to an existing device class.
+ * Class 0xff is "vendor specific".
+ */
+#define USB_CFG_INTERFACE_CLASS 3 /* HID */
+#define USB_CFG_INTERFACE_SUBCLASS 1 /* Boot */
+#define USB_CFG_INTERFACE_PROTOCOL 1 /* Keyboard */
+/* See USB specification if you want to conform to an existing device class or
+ * protocol. The following classes must be set at interface level:
+ * HID class is 3, no subclass and protocol required (but may be useful!)
+ * CDC class is 2, use subclass 2 and protocol 1 for ACM
+ */
+#define USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH 0
+/* Define this to the length of the HID report descriptor, if you implement
+ * an HID device. Otherwise don't define it or define it to 0.
+ * If you use this define, you must add a PROGMEM character array named
+ * "usbHidReportDescriptor" to your code which contains the report descriptor.
+ * Don't forget to keep the array and this define in sync!
+ */
+
+/* #define USB_PUBLIC static */
+/* Use the define above if you #include usbdrv.c instead of linking against it.
+ * This technique saves a couple of bytes in flash memory.
+ */
+
+/* ------------------- Fine Control over USB Descriptors ------------------- */
+/* If you don't want to use the driver's default USB descriptors, you can
+ * provide our own. These can be provided as (1) fixed length static data in
+ * flash memory, (2) fixed length static data in RAM or (3) dynamically at
+ * runtime in the function usbFunctionDescriptor(). See usbdrv.h for more
+ * information about this function.
+ * Descriptor handling is configured through the descriptor's properties. If
+ * no properties are defined or if they are 0, the default descriptor is used.
+ * Possible properties are:
+ * + USB_PROP_IS_DYNAMIC: The data for the descriptor should be fetched
+ * at runtime via usbFunctionDescriptor(). If the usbMsgPtr mechanism is
+ * used, the data is in FLASH by default. Add property USB_PROP_IS_RAM if
+ * you want RAM pointers.
+ * + USB_PROP_IS_RAM: The data returned by usbFunctionDescriptor() or found
+ * in static memory is in RAM, not in flash memory.
+ * + USB_PROP_LENGTH(len): If the data is in static memory (RAM or flash),
+ * the driver must know the descriptor's length. The descriptor itself is
+ * found at the address of a well known identifier (see below).
+ * List of static descriptor names (must be declared PROGMEM if in flash):
+ * char usbDescriptorDevice[];
+ * char usbDescriptorConfiguration[];
+ * char usbDescriptorHidReport[];
+ * char usbDescriptorString0[];
+ * int usbDescriptorStringVendor[];
+ * int usbDescriptorStringDevice[];
+ * int usbDescriptorStringSerialNumber[];
+ * Other descriptors can't be provided statically, they must be provided
+ * dynamically at runtime.
+ *
+ * Descriptor properties are or-ed or added together, e.g.:
+ * #define USB_CFG_DESCR_PROPS_DEVICE (USB_PROP_IS_RAM | USB_PROP_LENGTH(18))
+ *
+ * The following descriptors are defined:
+ * USB_CFG_DESCR_PROPS_DEVICE
+ * USB_CFG_DESCR_PROPS_CONFIGURATION
+ * USB_CFG_DESCR_PROPS_STRINGS
+ * USB_CFG_DESCR_PROPS_STRING_0
+ * USB_CFG_DESCR_PROPS_STRING_VENDOR
+ * USB_CFG_DESCR_PROPS_STRING_PRODUCT
+ * USB_CFG_DESCR_PROPS_STRING_SERIAL_NUMBER
+ * USB_CFG_DESCR_PROPS_HID
+ * USB_CFG_DESCR_PROPS_HID_REPORT
+ * USB_CFG_DESCR_PROPS_UNKNOWN (for all descriptors not handled by the driver)
+ *
+ * Note about string descriptors: String descriptors are not just strings, they
+ * are Unicode strings prefixed with a 2 byte header. Example:
+ * int serialNumberDescriptor[] = {
+ * USB_STRING_DESCRIPTOR_HEADER(6),
+ * 'S', 'e', 'r', 'i', 'a', 'l'
+ * };
+ */
+
+#define USB_CFG_DESCR_PROPS_DEVICE 0
+#define USB_CFG_DESCR_PROPS_CONFIGURATION USB_PROP_IS_DYNAMIC
+//#define USB_CFG_DESCR_PROPS_CONFIGURATION 0
+#define USB_CFG_DESCR_PROPS_STRINGS 0
+#define USB_CFG_DESCR_PROPS_STRING_0 0
+#define USB_CFG_DESCR_PROPS_STRING_VENDOR 0
+#define USB_CFG_DESCR_PROPS_STRING_PRODUCT 0
+#define USB_CFG_DESCR_PROPS_STRING_SERIAL_NUMBER 0
+//#define USB_CFG_DESCR_PROPS_HID USB_PROP_IS_DYNAMIC
+#define USB_CFG_DESCR_PROPS_HID 0
+#define USB_CFG_DESCR_PROPS_HID_REPORT USB_PROP_IS_DYNAMIC
+//#define USB_CFG_DESCR_PROPS_HID_REPORT 0
+#define USB_CFG_DESCR_PROPS_UNKNOWN 0
+
+/* ----------------------- Optional MCU Description ------------------------ */
+
+/* The following configurations have working defaults in usbdrv.h. You
+ * usually don't need to set them explicitly. Only if you want to run
+ * the driver on a device which is not yet supported or with a compiler
+ * which is not fully supported (such as IAR C) or if you use a differnt
+ * interrupt than INT0, you may have to define some of these.
+ */
+/* #define USB_INTR_CFG MCUCR */
+/* #define USB_INTR_CFG_SET ((1 << ISC00) | (1 << ISC01)) */
+/* #define USB_INTR_CFG_CLR 0 */
+/* #define USB_INTR_ENABLE GIMSK */
+/* #define USB_INTR_ENABLE_BIT INT0 */
+/* #define USB_INTR_PENDING GIFR */
+/* #define USB_INTR_PENDING_BIT INTF0 */
+/* #define USB_INTR_VECTOR INT0_vect */
+
+#endif /* __usbconfig_h_included__ */