diff --git a/keyboard/twinax/Makefile.pjrc b/keyboard/twinax/Makefile.pjrc new file mode 100644 index 0000000000..80d0bb88c4 --- /dev/null +++ b/keyboard/twinax/Makefile.pjrc @@ -0,0 +1,120 @@ +#---------------------------------------------------------------------------- +# 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 = twinax_pjrc + +# Directory common source filess exist +TMK_DIR = ../../tmk_core + +# Directory keyboard dependent files exist +TARGET_DIR = . + +# keyboard dependent files +SRC = keymap.c \ + matrix.c \ + led.c \ + oled/font.c \ + oled/i2c.c \ + oled/lcd.c \ + oled/ibm_logo.c \ + shootduino/game_state.cpp \ + shootduino/game_objects.cpp \ + shootduino/highscores.cpp \ + shootduino/textutils.cpp \ + shootduino/gfx.cpp \ + shootduino/starfield.cpp \ + shootduino/shootduino.cpp + +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 = at90usb162 # Teensy 1.0 +#MCU = atmega32u4 # Teensy 2.0 +#MCU = at90usb646 # Teensy++ 1.0 +MCU = at90usb1286 # Teensy++ 2.0 + + +# 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 + + +# 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 +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) +#PS2_MOUSE_ENABLE = yes # PS/2 mouse(TrackPoint) support + + +# Search Path +VPATH += $(TARGET_DIR) +VPATH += $(TMK_DIR) +VPATH += $(TARGET_DIR)/oled + +include $(TMK_DIR)/protocol/pjrc.mk +include $(TMK_DIR)/common.mk +include $(TMK_DIR)/rules.mk + +ansi: OPT_DEFS += -DLAYOUT_ANSI +ansi: all + +ansi_150: OPT_DEFS += -DLAYOUT_ANSI_150 +ansi_150: all + +iso: OPT_DEFS += -DLAYOUT_ISO +iso: all + +iso_150: OPT_DEFS += -DLAYOUT_ISO_150 +iso_150: all + +7bit: OPT_DEFS += -DLAYOUT_7BIT +7bit: all diff --git a/keyboard/twinax/config.h b/keyboard/twinax/config.h new file mode 100644 index 0000000000..f27a049c5d --- /dev/null +++ b/keyboard/twinax/config.h @@ -0,0 +1,50 @@ +/* + * Configuration for IBM Twinax keyboard(1394312, german version) + * (C) Copyright 2019 + * Dirk Eibach + * + * Based on Model M support by Les Orchard. + * + * 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 . + */ + +#ifndef CONFIG_H +#define CONFIG_H + +/* USB Device descriptor parameter */ +#define VENDOR_ID 0xFEED +#define PRODUCT_ID 0x6057 +#define DEVICE_VER 0x0001 +#define MANUFACTURER IBM +#define PRODUCT Twinax + +/* message strings */ +#define DESCRIPTION t.m.k. keyboard firmware for IBM Twinax + +/* matrix size */ +#define MATRIX_ROWS 20 +#define MATRIX_COLS 8 + +/* define if matrix has ghost */ +#define MATRIX_HAS_GHOST + +/* Set 0 if need no debouncing */ +#define DEBOUNCE 3 + +/* key combination for command */ +#define IS_COMMAND() ( \ + keyboard_report->mods == (MOD_BIT(KC_LALT) | MOD_BIT(KC_RALT)) \ +) + +#endif diff --git a/keyboard/twinax/keymap.c b/keyboard/twinax/keymap.c new file mode 100644 index 0000000000..26a985ca3b --- /dev/null +++ b/keyboard/twinax/keymap.c @@ -0,0 +1,86 @@ +/* + * Keymap for IBM Twinax keyboard(1394312, german version) + * (C) Copyright 2019 + * Dirk Eibach + * + * Based on Model M support by Les Orchard. + * + * 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 . + */ + +#include +#include +#include "keycode.h" +#include "action.h" +#include "action_macro.h" +#include "report.h" +#include "host.h" +#include "debug.h" +#include "keymap.h" + +#define KEYMAP( \ + K0J,K1J,K1K,K2J,K3J,K3K,K4J,K5J,K5K,K6J,K7J,K7K, \ + K0K,K0L,K1L,K2K,K2L,K3L,K4K,K4L,K5L,K6K,K6L,K7L, \ + \ + K3B,K3C, K3E, K2E,K2F,K2G,K2H,K3H,K3I,K2I,K2M,K2N,K2O,K3O,K3M, K3P, K3Q,K3R,K2Q, K2P,K2R,K2S,K2A, \ + K2B,K4B, K4C, K4E,K4F,K4G,K4H,K5H,K5I,K4I,K4M,K4N,K4O,K5O,K5M, K1Q,K5Q,K4Q, K4P,K4R,K4S,K4A, \ + K5B,K5C, K6C, K1E,K1F,K1G,K1H,K0H,K0I,K1I,K1M,K1N,K1O,K0O,K6O, K6P, K0Q, K1P,K1R,K1S,K1A, \ + K1B,K1C, K7D, K7E,K6E,K6F,K6G,K6H,K7H,K7I,K6I,K6M,K6N,K7O, K6D, K6A,K0T,K1T, K6B,K6R,K6S,K7T, \ + K0C,K0B, K0A, K7A, K7B, K0D, K7C, K0P, K7R,K7S \ +) { \ + { KC_##K0A, KC_##K1A, KC_##K2A, KC_NO, KC_##K4A, KC_NO, KC_##K6A, KC_##K7A }, \ + { KC_##K0B, KC_##K1B, KC_##K2B, KC_##K3B, KC_##K4B, KC_##K5B, KC_##K6B, KC_##K7B }, \ + { KC_##K0C, KC_##K1C, KC_NO, KC_##K3C, KC_##K4C, KC_##K5C, KC_##K6C, KC_##K7C }, \ + { KC_##K0D, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_##K6D, KC_##K7D }, \ + { KC_NO, KC_##K1E, KC_##K2E, KC_##K3E, KC_##K4E, KC_NO, KC_##K6E, KC_##K7E }, \ + { KC_NO, KC_##K1F, KC_##K2F, KC_NO, KC_##K4F, KC_NO, KC_##K6F, KC_NO }, \ + { KC_NO, KC_##K1G, KC_##K2G, KC_NO, KC_##K4G, KC_NO, KC_##K6G, KC_NO }, \ + { KC_##K0H, KC_##K1H, KC_##K2H, KC_##K3H, KC_##K4H, KC_##K5H, KC_##K6H, KC_##K7H }, \ + { KC_##K0I, KC_##K1I, KC_##K2I, KC_##K3I, KC_##K4I, KC_##K5I, KC_##K6I, KC_##K7I }, \ + { KC_##K0J, KC_##K1J, KC_##K2J, KC_##K3J, KC_##K4J, KC_##K5J, KC_##K6J, KC_##K7J }, \ + { KC_##K0K, KC_##K1K, KC_##K2K, KC_##K3K, KC_##K4K, KC_##K5K, KC_##K6K, KC_##K7K }, \ + { KC_##K0L, KC_##K1L, KC_##K2L, KC_##K3L, KC_##K4L, KC_##K5L, KC_##K6L, KC_##K7L }, \ + { KC_NO, KC_##K1M, KC_##K2M, KC_##K3M, KC_##K4M, KC_##K5M, KC_##K6M, KC_NO }, \ + { KC_NO, KC_##K1N, KC_##K2N, KC_NO, KC_##K4N, KC_NO, KC_##K6N, KC_NO }, \ + { KC_##K0O, KC_##K1O, KC_##K2O, KC_##K3O, KC_##K4O, KC_##K5O, KC_##K6O, KC_##K7O }, \ + { KC_##K0P, KC_##K1P, KC_##K2P, KC_##K3P, KC_##K4P, KC_NO, KC_##K6P, KC_NO }, \ + { KC_##K0Q, KC_##K1Q, KC_##K2Q, KC_##K3Q, KC_##K4Q, KC_##K5Q, KC_NO, KC_NO }, \ + { KC_NO, KC_##K1R, KC_##K2R, KC_##K3R, KC_##K4R, KC_NO, KC_##K6R, KC_##K7R }, \ + { KC_NO, KC_##K1S, KC_##K2S, KC_NO, KC_##K4S, KC_NO, KC_##K6S, KC_##K7S }, \ + { KC_##K0T, KC_##K1T, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_##K7T }, \ +} + +const uint8_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + KEYMAP( + F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + + PSCR,ESC, GRV, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, MINS,EQL, BSPC, INS, HOME,PGUP, NLCK,PSLS,PAST,PMNS, + SLCK,INT4, TAB, Q, W, E, R, T, Y, U, I, O, P, LBRC,RBRC, DEL, END, PGDN, P7, P8, P9, PPLS, + PAUS,INT5, CAPS,A, S, D, F, G, H, J, K, L, SCLN,QUOT,BSLS, ENT, UP, P4, P5, P6, PCMM, + APP, INT6, LSFT,NUBS,Z, X, C, V, B, N, M, COMM,DOT, SLSH, RSFT, LEFT,INT2,RGHT, P1, P2, P3, PENT, + RGUI,LGUI, LCTL, LALT, SPC, RALT, RCTL, DOWN, P0, PDOT + ), +}; + +#define KEYCODE(layer, row, col) (pgm_read_byte(&keymaps[(layer)][(row)][(col)])) + +const action_t PROGMEM fn_actions[] = {}; + +#define KEYMAPS_SIZE (sizeof(keymaps) / sizeof(keymaps[0])) + +uint8_t keymap_get_keycode(uint8_t layer, uint8_t row, uint8_t col) +{ + return KEYCODE(layer, row, col); +} diff --git a/keyboard/twinax/led.c b/keyboard/twinax/led.c new file mode 100644 index 0000000000..4a58ace40c --- /dev/null +++ b/keyboard/twinax/led.c @@ -0,0 +1,150 @@ +/* + * LED support for IBM Twinax keyboard(1394312, german version) + * (C) Copyright 2019 + * Dirk Eibach + * + * 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 . + */ + +#include "led.h" +#include "lcd.h" + +#include "debug.h" +#include "timer.h" +#include "led.h" +#include "keyboard.h" +#include "ibm_logo.h" +#include "keycode.h" +#include "keymap.h" +#include "shootduino/joystick.h" +#include "shootduino/shootduino.h" + +#define SLEEP_TIMEOUT_MS (1000L * 60 * 10) +bool sleep_status = 0; +bool must_init = 1; +bool game_active = 0; +bool f24_lock = 0; +uint32_t sleep_timer; +uint8_t last_usb_led = 0; + +void led_set(uint8_t usb_led) +{ + /* this keyboard has no leds, but I added an OLED */ + lcd_gotoxy(0,0); + if (usb_led & (1<<0)) + lcd_puts("NumLock"); + else + lcd_puts(" "); + + lcd_gotoxy(0,1); + if (usb_led & (1<<1)) + lcd_puts("CapsLock"); + else + lcd_puts(" "); + + lcd_gotoxy(0,2); + if (usb_led & (1<<2)) + lcd_puts("ScrollLock"); + else + lcd_puts(" "); + +#ifdef GRAPHICMODE + lcd_display(); +#endif + + last_usb_led = usb_led; +} + +void start_lcd(void) +{ + lcd_init(LCD_DISP_ON); + lcd_gotoxy(0,6); + lcd_puts("IBM Twinax TMK port"); + lcd_gotoxy(0,7); + lcd_puts("(C)2019 Dirk Eibach"); + lcd_drawBitmap(64, 5, ibm_logo, 64, 26, WHITE); +} + +void hook_matrix_change(keyevent_t event) +{ + if (sleep_status) { + sleep_status = 0; + start_lcd(); + led_set(last_usb_led); + } + sleep_timer = timer_read32(); + + switch (keymap_key_to_keycode(0, event.key)) { + case KC_F24: + if (!f24_lock && event.pressed) { + f24_lock = 1; + if (!game_active) { + shootduino_setup(); + game_active = 1; + } else { + game_active = 0; + lcd_init(LCD_DISP_OFF); + start_lcd(); + led_set(last_usb_led); + } + } else { + f24_lock = 0; + } + break; + } +} + +void hook_keyboard_loop(void) +{ + if (must_init) { + start_lcd(); + sleep_timer = timer_read32(); +#ifdef GRAPHICMODE + lcd_display(); +#endif + must_init = 0; + } + + if (!sleep_status && (timer_elapsed32(sleep_timer) > SLEEP_TIMEOUT_MS)) { + lcd_init(LCD_DISP_OFF); + sleep_status = 1; + game_active = 0; + } + + if (game_active) { + uint32_t loop_timer = timer_read32(); + shootduino_loop(); + xprintf("Loop %u ms\n", timer_elapsed32(loop_timer)); + } +} + +void hook_usb_wakeup(void) +{ + sleep_status = 0; + start_lcd(); + led_set(last_usb_led); +} + +void hook_usb_suspend_entry(void) +{ + lcd_init(LCD_DISP_OFF); + sleep_status = 1; + game_active = 0; +} + +void init_joystick(void) { +} + +void update_joystick(void) { +} diff --git a/keyboard/twinax/matrix.c b/keyboard/twinax/matrix.c new file mode 100644 index 0000000000..2334bb9761 --- /dev/null +++ b/keyboard/twinax/matrix.c @@ -0,0 +1,217 @@ +/* + * Matrix for IBM Twinax keyboard(1394312, german version) + * (C) Copyright 2019 + * Dirk Eibach + * + * Based on Model M support by Les Orchard. + * + * 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 . + */ +#include +#include +#include +#include +#include "print.h" +#include "debug.h" +#include "util.h" +#include "matrix.h" +#include "shootduino/joystick.h" + +#ifndef DEBOUNCE +# define DEBOUNCE 10 +#endif +static uint8_t debouncing = DEBOUNCE; + +// bit array of key state(1:on, 0:off) +static matrix_row_t matrix[MATRIX_ROWS]; +static matrix_row_t matrix_debouncing[MATRIX_ROWS]; + +static uint32_t read_rows(void); +static void init_rows(void); +static void unselect_cols(void); +static void select_col(uint8_t col); + +uint8_t matrix_rows(void) +{ + return MATRIX_ROWS; +} + +uint8_t matrix_cols(void) +{ + return MATRIX_COLS; +} + +void matrix_init(void) +{ + unselect_cols(); + init_rows(); + + // initialize matrix state: all keys off + for (uint8_t i = 0; i < MATRIX_ROWS; i++) { + matrix[i] = 0; + matrix_debouncing[i] = 0; + } + + debug_enable=true; +} + +struct JoystickState joystick; + +uint8_t matrix_scan(void) +{ + for (uint8_t col = 0; col < MATRIX_COLS; col++) { + select_col(col); + _delay_us(30); // without this wait it won't read stable value. + uint32_t rows = read_rows(); + for (uint8_t row = 0; row < MATRIX_ROWS; row++) { + bool prev_bit = matrix_debouncing[row] & ((matrix_row_t)1< +{0x00, 0x02, 0x01, 0x51, 0x09, 0x06}, // ? +{0x00, 0x32, 0x49, 0x59, 0x51, 0x3E}, // @ +{0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C}, // A +{0x00, 0x7F, 0x49, 0x49, 0x49, 0x36}, // B +{0x00, 0x3E, 0x41, 0x41, 0x41, 0x22}, // C +{0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C}, // D +{0x00, 0x7F, 0x49, 0x49, 0x49, 0x41}, // E +{0x00, 0x7F, 0x09, 0x09, 0x09, 0x01}, // F +{0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A}, // G +{0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F}, // H +{0x00, 0x00, 0x41, 0x7F, 0x41, 0x00}, // I +{0x00, 0x20, 0x40, 0x41, 0x3F, 0x01}, // J +{0x00, 0x7F, 0x08, 0x14, 0x22, 0x41}, // K +{0x00, 0x7F, 0x40, 0x40, 0x40, 0x40}, // L +{0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F}, // M +{0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F}, // N +{0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E}, // O +{0x00, 0x7F, 0x09, 0x09, 0x09, 0x06}, // P +{0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E}, // Q +{0x00, 0x7F, 0x09, 0x19, 0x29, 0x46}, // R +{0x00, 0x46, 0x49, 0x49, 0x49, 0x31}, // S +{0x00, 0x01, 0x01, 0x7F, 0x01, 0x01}, // T +{0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F}, // U +{0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F}, // V +{0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F}, // W +{0x00, 0x63, 0x14, 0x08, 0x14, 0x63}, // X +{0x00, 0x07, 0x08, 0x70, 0x08, 0x07}, // Y +{0x00, 0x61, 0x51, 0x49, 0x45, 0x43}, // Z +{0x00, 0x00, 0x7F, 0x41, 0x41, 0x00}, // [ +{0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55}, // backslash +{0x00, 0x00, 0x41, 0x41, 0x7F, 0x00}, // ] +{0x00, 0x04, 0x02, 0x01, 0x02, 0x04}, // ^ +{0x00, 0x40, 0x40, 0x40, 0x40, 0x40}, // _ +{0x00, 0x00, 0x01, 0x02, 0x04, 0x00}, // ' +{0x00, 0x20, 0x54, 0x54, 0x54, 0x78}, // a +{0x00, 0x7F, 0x48, 0x44, 0x44, 0x38}, // b +{0x00, 0x38, 0x44, 0x44, 0x44, 0x20}, // c +{0x00, 0x38, 0x44, 0x44, 0x48, 0x7F}, // d +{0x00, 0x38, 0x54, 0x54, 0x54, 0x18}, // e +{0x00, 0x08, 0x7E, 0x09, 0x01, 0x02}, // f +{0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C}, // g +{0x00, 0x7F, 0x08, 0x04, 0x04, 0x78}, // h +{0x00, 0x00, 0x44, 0x7D, 0x40, 0x00}, // i +{0x00, 0x40, 0x80, 0x84, 0x7D, 0x00}, // j +{0x00, 0x7F, 0x10, 0x28, 0x44, 0x00}, // k +{0x00, 0x00, 0x41, 0x7F, 0x40, 0x00}, // l +{0x00, 0x7C, 0x04, 0x18, 0x04, 0x78}, // m +{0x00, 0x7C, 0x08, 0x04, 0x04, 0x78}, // n +{0x00, 0x38, 0x44, 0x44, 0x44, 0x38}, // o +{0x00, 0xFC, 0x24, 0x24, 0x24, 0x18}, // p +{0x00, 0x18, 0x24, 0x24, 0x18, 0xFC}, // q +{0x00, 0x7C, 0x08, 0x04, 0x04, 0x08}, // r +{0x00, 0x48, 0x54, 0x54, 0x54, 0x20}, // s +{0x00, 0x04, 0x3F, 0x44, 0x40, 0x20}, // t +{0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C}, // u +{0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C}, // v +{0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C}, // w +{0x00, 0x44, 0x28, 0x10, 0x28, 0x44}, // x +{0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C}, // y +{0x00, 0x44, 0x64, 0x54, 0x4C, 0x44}, // z +{0x00, 0x00, 0x08, 0x77, 0x41, 0x00}, // { +{0x00, 0x00, 0x00, 0x63, 0x00, 0x00}, // ¦ +{0x00, 0x00, 0x41, 0x77, 0x08, 0x00}, // } +{0x00, 0x08, 0x04, 0x08, 0x08, 0x04}, // ~ +/* end of normal char-set */ +/* put your own signs/chars here, edit special_char too */ +/* be sure that your first special char stand here */ +{0x00, 0x3A, 0x40, 0x40, 0x20, 0x7A}, // ü, !!! Important: this must be special_char[0] !!! +{0x00, 0x3D, 0x40, 0x40, 0x40, 0x3D}, // Ü +{0x00, 0x21, 0x54, 0x54, 0x54, 0x79}, // ä +{0x00, 0x7D, 0x12, 0x11, 0x12, 0x7D}, // Ä +{0x00, 0x39, 0x44, 0x44, 0x44, 0x39}, // ö +{0x00, 0x3D, 0x42, 0x42, 0x42, 0x3D}, // Ö +{0x00, 0x02, 0x05, 0x02, 0x00, 0x00}, // ° +{0x00, 0x7E, 0x01, 0x49, 0x55, 0x73}, // ß +{0x00, 0x7C, 0x10, 0x10, 0x08, 0x1C} // µ +}; +const char special_char[][2] PROGMEM = { + // define position of special char in font + // {special char, position in font} + // be sure that last element of this + // array are {0xff, 0xff} and first element + // are {first special char, first element after normal char-set in font} + {'ü', 95}, // special_char[0] + {'Ü', 96}, + {'ä', 97}, + {'Ä', 98}, + {'ö', 99}, + {'Ö', 100}, + {'°', 101}, + {'ß', 102}, + {'µ', 103}, + {0xff, 0xff} // end of table special_char +}; diff --git a/keyboard/twinax/oled/font.h b/keyboard/twinax/oled/font.h new file mode 100644 index 0000000000..da8d44c91c --- /dev/null +++ b/keyboard/twinax/oled/font.h @@ -0,0 +1,16 @@ +/* + * font.h + * i2c + * + * Created by Michael Köhler on 13.09.18. + * Copyright 2018 Skie-Systems. All rights reserved. + * + */ +#ifndef _font_h_ +#define _font_h_ +#include + +extern const char ssd1306oled_font[][6] PROGMEM; +extern const char special_char[][2] PROGMEM; + +#endif diff --git a/keyboard/twinax/oled/i2c.c b/keyboard/twinax/oled/i2c.c new file mode 100644 index 0000000000..997051bb93 --- /dev/null +++ b/keyboard/twinax/oled/i2c.c @@ -0,0 +1,179 @@ +// +// i2c.c +// i2c +// +// Created by Michael Köhler on 09.10.17. +// +// + +#include "i2c.h" + +#if defined (__AVR_ATmega328__) || defined(__AVR_ATmega328P__) || \ +defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168PA__) || \ +defined(__AVR_ATmega88P__) || \ +defined(__AVR_ATmega48P__) || \ +defined(__AVR_ATmega1284P__) || \ +defined (__AVR_ATmega324A__) || defined (__AVR_ATmega324P__) || defined (__AVR_ATmega324PA__) || \ +defined (__AVR_ATmega644__) || defined (__AVR_ATmega644A__) || defined (__AVR_ATmega644P__) || defined (__AVR_ATmega644PA__) || \ +defined (__AVR_ATmega1284P__) || \ +defined (__AVR_ATmega2560__) || \ +defined (__AVR_AT90USB1286__) +#if PSC_I2C != 1 && PSC_I2C != 4 && PSC_I2C != 16 && PSC_I2C != 64 +#error "Wrong prescaler for TWI !" +#elif SET_TWBR < 0 || SET_TWBR > 255 +#error "TWBR out of range, change PSC_I2C or F_I2C !" +#endif + +uint8_t I2C_ErrorCode; +/********************************************** + Public Function: i2c_init + + Purpose: Initialise TWI/I2C interface + + Input Parameter: none + + Return Value: none + **********************************************/ +void i2c_init(void){ + // set clock + switch (PSC_I2C) { + case 4: + TWSR = 0x1; + break; + case 16: + TWSR = 0x2; + break; + case 64: + TWSR = 0x3; + break; + default: + TWSR = 0x00; + break; + } + TWBR = (uint8_t)SET_TWBR; + // enable + TWCR = (1 << TWEN); +} +/********************************************** + Public Function: i2c_start + + Purpose: Start TWI/I2C interface + + Input Parameter: + - uint8_t i2c_addr: Adress of reciever + + Return Value: none + **********************************************/ +void i2c_start(uint8_t i2c_addr){ + // i2c start + TWCR = (1 << TWINT)|(1 << TWSTA)|(1 << TWEN); + uint16_t timeout = F_CPU/F_I2C*2.0; + while((TWCR & (1 << TWINT)) == 0 && + timeout !=0){ + timeout--; + if(timeout == 0){ + I2C_ErrorCode |= (1 << I2C_START); + return; + } + }; + // send adress + TWDR = i2c_addr; + TWCR = (1 << TWINT)|( 1 << TWEN); + timeout = F_CPU/F_I2C*2.0; + while((TWCR & (1 << TWINT)) == 0 && + timeout !=0){ + timeout--; + if(timeout == 0){ + I2C_ErrorCode |= (1 << I2C_SENDADRESS); + return; + } + }; +} +/********************************************** + Public Function: i2c_stop + + Purpose: Stop TWI/I2C interface + + Input Parameter: none + + Return Value: none + **********************************************/ +void i2c_stop(void){ + // i2c stop + TWCR = (1 << TWINT)|(1 << TWSTO)|(1 << TWEN); +} +/********************************************** + Public Function: i2c_byte + + Purpose: Send byte at TWI/I2C interface + + Input Parameter: + - uint8_t byte: Byte to send to reciever + + Return Value: none + **********************************************/ +void i2c_byte(uint8_t byte){ + TWDR = byte; + TWCR = (1 << TWINT)|( 1 << TWEN); + uint16_t timeout = F_CPU/F_I2C*2.0; + while((TWCR & (1 << TWINT)) == 0 && + timeout !=0){ + timeout--; + if(timeout == 0){ + I2C_ErrorCode |= (1 << I2C_BYTE); + return; + } + }; +} +/********************************************** + Public Function: i2c_readAck + + Purpose: read acknowledge from TWI/I2C Interface + + Input Parameter: none + + Return Value: uint8_t + - TWDR: recieved value at TWI/I2C-Interface, 0 at timeout + - 0: Error at read + **********************************************/ +uint8_t i2c_readAck(void){ + TWCR = (1< +#include + +extern uint8_t I2C_ErrorCode; // variable for communication error at twi + // ckeck it in your code + // 0 means no error + // define bits in I2C-ErrorCode: +#define I2C_START 0 // bit 0: timeout start-condition +#define I2C_SENDADRESS 1 // bit 0: timeout device-adress +#define I2C_BYTE 2 // bit 0: timeout byte-transmission +#define I2C_READACK 3 // bit 0: timeout read acknowledge +#define I2C_READNACK 4 // bit 0: timeout read nacknowledge + +void i2c_init(void); // init hw-i2c +void i2c_start(uint8_t i2c_addr); // send i2c_start_condition +void i2c_stop(void); // send i2c_stop_condition +void i2c_byte(uint8_t byte); // send data_byte + +uint8_t i2c_readAck(void); // read byte with ACK +uint8_t i2c_readNAck(void); // read byte with NACK + +#ifdef __cplusplus +} +#endif + +#endif /* i2c_h */ diff --git a/keyboard/twinax/oled/ibm_logo.c b/keyboard/twinax/oled/ibm_logo.c new file mode 100644 index 0000000000..7d8ddffb78 --- /dev/null +++ b/keyboard/twinax/oled/ibm_logo.c @@ -0,0 +1,22 @@ +#include "ibm_logo.h" + +const uint8_t ibm_logo[] PROGMEM = { + 0xff, 0xf3, 0xff, 0xfc, 0x1f, + 0xfc, 0x03, 0xff, 0xff, 0xf3, 0xff, 0xfe, 0x1f, 0xfc, 0x03, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf3, 0xff, 0xff, 0x9f, + 0xfc, 0x07, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xdf, 0xfe, 0x0f, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0f, 0x80, 0x3e, 0x0f, 0xc1, 0xff, 0x1f, 0xf0, 0x0f, + 0x80, 0x3e, 0x07, 0xc1, 0xff, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0f, 0x80, 0x3f, 0xff, 0x81, 0xff, 0xbf, 0xf0, 0x0f, + 0x80, 0x3f, 0xff, 0x01, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, + 0x80, 0x3f, 0xff, 0x01, 0xf3, 0xfd, 0xf0, 0x0f, 0x80, 0x3f, 0xff, 0x81, + 0xf3, 0xf9, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, + 0x80, 0x3e, 0x07, 0xc1, 0xf1, 0xf9, 0xf0, 0x0f, 0x80, 0x3e, 0x0f, 0xc1, + 0xf1, 0xf1, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfb, 0xff, 0xff, 0xcf, + 0xf0, 0xe1, 0xff, 0xff, 0xfb, 0xff, 0xff, 0x8f, 0xf0, 0x61, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfb, 0xff, 0xfe, 0x0f, + 0xf0, 0x41, 0xff, 0xff, 0xfb, 0xff, 0xfc, 0x0f, 0xf0, 0x01, 0xff +}; diff --git a/keyboard/twinax/oled/ibm_logo.h b/keyboard/twinax/oled/ibm_logo.h new file mode 100644 index 0000000000..4a3ddef919 --- /dev/null +++ b/keyboard/twinax/oled/ibm_logo.h @@ -0,0 +1,7 @@ +#ifndef _ibm_logo_h_ +#define _ibm_logo_h_ +#include + +extern const uint8_t ibm_logo[] PROGMEM; + +#endif diff --git a/keyboard/twinax/oled/lcd.c b/keyboard/twinax/oled/lcd.c new file mode 100644 index 0000000000..f940936606 --- /dev/null +++ b/keyboard/twinax/oled/lcd.c @@ -0,0 +1,466 @@ +/* + * This file is part of lcd library for ssd1306/sh1106 oled-display. + * + * lcd library for ssd1306/sh1106 oled-display 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 any later version. + * + * lcd library for ssd1306/sh1106 oled-display 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 Foobar. If not, see . + * + * Diese Datei ist Teil von lcd library for ssd1306/sh1106 oled-display. + * + * lcd library for ssd1306/sh1106 oled-display ist Freie Software: Sie können es unter den Bedingungen + * der GNU General Public License, wie von der Free Software Foundation, + * Version 3 der Lizenz oder jeder späteren + * veröffentlichten Version, weiterverbreiten und/oder modifizieren. + * + * lcd library for ssd1306/sh1106 oled-display wird in der Hoffnung, dass es nützlich sein wird, aber + * OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite + * Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. + * Siehe die GNU General Public License für weitere Details. + * + * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem + * Programm erhalten haben. Wenn nicht, siehe . + * + * lcd.h + * + * Created by Michael Köhler on 22.12.16. + * Copyright 2016 Skie-Systems. All rights reserved. + * + * lib for OLED-Display with ssd1306/sh1106-Controller + * first dev-version only for I2C-Connection + * at ATMega328P like Arduino Uno + * + * at GRAPHICMODE lib needs static SRAM for display: + * DISPLAY-WIDTH * DISPLAY-HEIGHT + 2 bytes + * + * at TEXTMODE lib need static SRAM for display: + * 2 bytes (cursorPosition) + */ + +#include "lcd.h" +#include "font.h" +#include + +static struct { + uint8_t x; + uint8_t y; +} cursorPosition; +static uint8_t charMode = NORMALSIZE; +#if defined GRAPHICMODE +#include +static uint8_t displayBuffer[DISPLAY_HEIGHT/8][DISPLAY_WIDTH]; +#elif defined TEXTMODE +#else +#error "No valid displaymode! Refer lcd.h" +#endif + + +const uint8_t init_sequence [] PROGMEM = { // Initialization Sequence + LCD_DISP_OFF, // Display OFF (sleep mode) + 0x20, 0b00, // Set Memory Addressing Mode + // 00=Horizontal Addressing Mode; 01=Vertical Addressing Mode; + // 10=Page Addressing Mode (RESET); 11=Invalid + 0xB0, // Set Page Start Address for Page Addressing Mode, 0-7 + 0xC8, // Set COM Output Scan Direction + 0x00, // --set low column address + 0x10, // --set high column address + 0x40, // --set start line address + 0x81, 0x3F, // Set contrast control register + 0xA1, // Set Segment Re-map. A0=address mapped; A1=address 127 mapped. + 0xA6, // Set display mode. A6=Normal; A7=Inverse + 0xA8, 0x3F, // Set multiplex ratio(1 to 64) + 0xA4, // Output RAM to Display + // 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content + 0xD3, 0x00, // Set display offset. 00 = no offset + 0xD5, // --set display clock divide ratio/oscillator frequency + 0xF0, // --set divide ratio + 0xD9, 0x22, // Set pre-charge period + 0xDA, 0x12, // Set com pins hardware configuration + 0xDB, // --set vcomh + 0x20, // 0x20,0.77xVcc + 0x8D, 0x14, // Set DC-DC enable + + +}; +#pragma mark LCD COMMUNICATION +void lcd_command(uint8_t cmd[], uint8_t size) { + i2c_start((LCD_I2C_ADR << 1) | 0); + i2c_byte(0x00); // 0x00 for command, 0x40 for data + for (uint8_t i=0; i (DISPLAY_WIDTH/sizeof(FONT[0])) || y > (DISPLAY_HEIGHT/8-1)) return;// out of display + x = x * sizeof(FONT[0]); + cursorPosition.x=x; + cursorPosition.y=y; +#if defined SSD1306 + uint8_t commandSequence[] = {0xb0+y, 0x21, x, 0x7f}; +#elif defined SH1106 + uint8_t commandSequence[] = {0xb0+y, 0x21, 0x00+((2+x) & (0x0f)), 0x10+( ((2+x) & (0xf0)) >> 4 ), 0x7f}; +#endif + lcd_command(commandSequence, sizeof(commandSequence)); +} +void lcd_clrmem(void){ +#ifdef GRAPHICMODE + for (uint8_t i = 0; i < DISPLAY_HEIGHT/8; i++){ + memset(displayBuffer[i], 0x00, sizeof(displayBuffer[i])); + } +#endif +} +void lcd_clrscr(void){ +#ifdef GRAPHICMODE + for (uint8_t i = 0; i < DISPLAY_HEIGHT/8; i++){ + memset(displayBuffer[i], 0x00, sizeof(displayBuffer[i])); + lcd_gotoxy(0,i); + lcd_data(displayBuffer[i], sizeof(displayBuffer[i])); + } +#elif defined TEXTMODE + uint8_t displayBuffer[DISPLAY_WIDTH]; + memset(displayBuffer, 0x00, sizeof(displayBuffer)); + for (uint8_t i = 0; i < DISPLAY_HEIGHT/8; i++){ + lcd_gotoxy(0,i); + lcd_data(displayBuffer, sizeof(displayBuffer)); + } +#endif + lcd_home(); +} +void lcd_home(void){ + lcd_gotoxy(0, 0); +} +void lcd_invert(uint8_t invert){ + i2c_start((LCD_I2C_ADR << 1) | 0); + uint8_t commandSequence[1]; + if (invert != YES) { + commandSequence[0] = 0xA6; + } else { + commandSequence[0] = 0xA7; + } + lcd_command(commandSequence, 1); +} +void lcd_sleep(uint8_t sleep){ + i2c_start((LCD_I2C_ADR << 1) | 0); + uint8_t commandSequence[1]; + if (sleep != YES) { + commandSequence[0] = 0xAF; + } else { + commandSequence[0] = 0xAE; + } + lcd_command(commandSequence, 1); +} +void lcd_set_contrast(uint8_t contrast){ + uint8_t commandSequence[2] = {0x81, contrast}; + lcd_command(commandSequence, sizeof(commandSequence)); +} +void lcd_putc(char c){ + switch (c) { + case '\b': + // backspace + lcd_gotoxy(cursorPosition.x-charMode, cursorPosition.y); + lcd_putc(' '); + lcd_gotoxy(cursorPosition.x-charMode, cursorPosition.y); + break; + case '\t': + // tab + if( (cursorPosition.x+charMode*4) < (DISPLAY_WIDTH/ sizeof(FONT[0])-charMode*4) ){ + lcd_gotoxy(cursorPosition.x+charMode*4, cursorPosition.y); + }else{ + lcd_gotoxy(DISPLAY_WIDTH/ sizeof(FONT[0]), cursorPosition.y); + } + break; + case '\n': + // linefeed + if(cursorPosition.y < (DISPLAY_HEIGHT/8-1)){ + lcd_gotoxy(cursorPosition.x, cursorPosition.y+charMode); + } + break; + case '\r': + // carrige return + lcd_gotoxy(0, cursorPosition.y); + break; + default: + // char doesn't fit in line + if( (cursorPosition.x >= DISPLAY_WIDTH-sizeof(FONT[0])) || (c < ' ') ) break; + // mapping char + c -= ' '; + if (c >= pgm_read_byte(&special_char[0][1]) ) { + char temp = c; + c = 0xff; + for (uint8_t i=0; pgm_read_byte(&special_char[i][1]) != 0xff; i++) { + if ( pgm_read_byte(&special_char[i][0])-' ' == temp ) { + c = pgm_read_byte(&special_char[i][1]); + break; + } + } + if ( c == 0xff ) break; + } + // print char at display +#ifdef GRAPHICMODE + if (charMode == DOUBLESIZE) { + uint16_t doubleChar[sizeof(FONT[0])]; + uint8_t dChar; + + for (uint8_t i=0; i < sizeof(FONT[0]); i++) { + doubleChar[i] = 0; + dChar = pgm_read_byte(&(FONT[(uint8_t)c][i])); + for (uint8_t j=0; j<8; j++) { + if ((dChar & (1 << j))) { + doubleChar[i] |= (1 << (j*2)); + doubleChar[i] |= (1 << ((j*2)+1)); + } + } + } + for (uint8_t i = 0; i < sizeof(FONT[0]); i++) + { + // load bit-pattern from flash + displayBuffer[cursorPosition.y+1][cursorPosition.x+(2*i)] = doubleChar[i] >> 8; + displayBuffer[cursorPosition.y+1][cursorPosition.x+(2*i)+1] = doubleChar[i] >> 8; + displayBuffer[cursorPosition.y][cursorPosition.x+(2*i)] = doubleChar[i] & 0xff; + displayBuffer[cursorPosition.y][cursorPosition.x+(2*i)+1] = doubleChar[i] & 0xff; + } + cursorPosition.x += sizeof(FONT[0])*2; + } else { + for (uint8_t i = 0; i < sizeof(FONT[0]); i++) + { + // load bit-pattern from flash + displayBuffer[cursorPosition.y][cursorPosition.x+i] =pgm_read_byte(&(FONT[(uint8_t)c][i])); + } + cursorPosition.x += sizeof(FONT[0]); + } +#elif defined TEXTMODE + if (charMode == DOUBLESIZE) { + uint16_t doubleChar[sizeof(FONT[0])]; + uint8_t dChar; + + for (uint8_t i=0; i < sizeof(FONT[0]); i++) { + doubleChar[i] = 0; + dChar = pgm_read_byte(&(FONT[(uint8_t)c][i])); + for (uint8_t j=0; j<8; j++) { + if ((dChar & (1 << j))) { + doubleChar[i] |= (1 << (j*2)); + doubleChar[i] |= (1 << ((j*2)+1)); + } + } + } + i2c_start(LCD_I2C_ADR << 1); + i2c_byte(0x40); + for (uint8_t i = 0; i < sizeof(FONT[0]); i++) + { + // print font to ram, print 6 columns + i2c_byte(doubleChar[i] & 0xff); + i2c_byte(doubleChar[i] & 0xff); + } + i2c_stop(); + +#if defined SSD1306 + uint8_t commandSequence[] = {0xb0+cursorPosition.y+1, + 0x21, + cursorPosition.x, + 0x7f}; +#elif defined SH1106 + uint8_t commandSequence[] = {0xb0+cursorPosition.y+1, + 0x21, + 0x00+((2+cursorPosition.x) & (0x0f)), + 0x10+( ((2+cursorPosition.x) & (0xf0)) >> 4 ), + 0x7f}; +#endif + lcd_command(commandSequence, sizeof(commandSequence)); + + i2c_start(LCD_I2C_ADR << 1); + i2c_byte(0x40); + for (uint8_t j = 0; j < sizeof(FONT[0]); j++) + { + // print font to ram, print 6 columns + i2c_byte(doubleChar[j] >> 8); + i2c_byte(doubleChar[j] >> 8); + } + i2c_stop(); + + commandSequence[0] = 0xb0+cursorPosition.y; +#if defined SSD1306 + commandSequence[2] = cursorPosition.x+(2*sizeof(FONT[0])); +#elif defined SH1106 + commandSequence[2] = 0x00+((2+cursorPosition.x+(2*sizeof(FONT[0]))) & (0x0f)); + commandSequence[3] = 0x10+( ((2+cursorPosition.x+(2*sizeof(FONT[0]))) & (0xf0)) >> 4 ); +#endif + lcd_command(commandSequence, sizeof(commandSequence)); + cursorPosition.x += sizeof(FONT[0])*2; + } else { + i2c_start(LCD_I2C_ADR << 1); + i2c_byte(0x40); + for (uint8_t i = 0; i < sizeof(FONT[0]); i++) + { + // print font to ram, print 6 columns + i2c_byte(pgm_read_byte(&(FONT[(uint8_t)c][i]))); + } + i2c_stop(); + cursorPosition.x += sizeof(FONT[0]); + } +#endif + break; + } + +} +void lcd_charMode(uint8_t mode){ + charMode = mode; +} +void lcd_puts(const char* s){ + while (*s) { + lcd_putc(*s++); + } +} +void lcd_puts_p(const char* progmem_s){ + register uint8_t c; + while ((c = pgm_read_byte(progmem_s++))) { + lcd_putc(c); + } +} +#ifdef GRAPHICMODE +#pragma mark - +#pragma mark GRAPHIC FUNCTIONS +void lcd_drawPixel(uint8_t x, uint8_t y, uint8_t color){ + if( x > DISPLAY_WIDTH-1 || y > (DISPLAY_HEIGHT-1)) return; // out of Display + if( color == WHITE){ + displayBuffer[(y / (DISPLAY_HEIGHT/8))][x] |= (1 << (y % (DISPLAY_HEIGHT/8))); + } else { + displayBuffer[(y / (DISPLAY_HEIGHT/8))][x] &= ~(1 << (y % (DISPLAY_HEIGHT/8))); + } +} +void lcd_drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color){ + if( x1 > DISPLAY_WIDTH-1 || + x2 > DISPLAY_WIDTH-1 || + y1 > DISPLAY_HEIGHT-1 || + y2 > DISPLAY_HEIGHT-1) return; + int dx = abs(x2-x1), sx = x1 dy) { err += dy; x1 += sx; } /* e_xy+e_x > 0 */ + if (e2 < dx) { err += dx; y1 += sy; } /* e_xy+e_y < 0 */ + } +} +void lcd_drawFastHLine(uint8_t x1, uint8_t y1, uint8_t width, uint8_t color){ + lcd_drawLine(x1, y1, x1 + width, y1, color); +} + +void lcd_drawRect(uint8_t px1, uint8_t py1, uint8_t px2, uint8_t py2, uint8_t color){ + if( px1 > DISPLAY_WIDTH-1 || + px2 > DISPLAY_WIDTH-1 || + py1 > DISPLAY_HEIGHT-1 || + py2 > DISPLAY_HEIGHT-1) return; + lcd_drawLine(px1, py1, px2, py1, color); + lcd_drawLine(px2, py1, px2, py2, color); + lcd_drawLine(px2, py2, px1, py2, color); + lcd_drawLine(px1, py2, px1, py1, color); +} +void lcd_fillRect(uint8_t px1, uint8_t py1, uint8_t px2, uint8_t py2, uint8_t color){ + if( px1 > px2){ + uint8_t temp = px1; + px1 = px2; + px2 = temp; + temp = py1; + py1 = py2; + py2 = temp; + } + for (uint8_t i=0; i<=(py2-py1); i++){ + lcd_drawLine(px1, py1+i, px2, py1+i, color); + } +} +void lcd_drawCircle(uint8_t center_x, uint8_t center_y, uint8_t radius, uint8_t color){ + if( ((center_x + radius) > DISPLAY_WIDTH-1) || + ((center_y + radius) > DISPLAY_HEIGHT-1) || + center_x < radius || + center_y < radius) return; + int16_t f = 1 - radius; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * radius; + int16_t x = 0; + int16_t y = radius; + + lcd_drawPixel(center_x , center_y+radius, color); + lcd_drawPixel(center_x , center_y-radius, color); + lcd_drawPixel(center_x+radius, center_y , color); + lcd_drawPixel(center_x-radius, center_y , color); + + while (x= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + + lcd_drawPixel(center_x + x, center_y + y, color); + lcd_drawPixel(center_x - x, center_y + y, color); + lcd_drawPixel(center_x + x, center_y - y, color); + lcd_drawPixel(center_x - x, center_y - y, color); + lcd_drawPixel(center_x + y, center_y + x, color); + lcd_drawPixel(center_x - y, center_y + x, color); + lcd_drawPixel(center_x + y, center_y - x, color); + lcd_drawPixel(center_x - y, center_y - x, color); + } +} +void lcd_fillCircle(uint8_t center_x, uint8_t center_y, uint8_t radius, uint8_t color) { + for(uint8_t i=0; i<= radius;i++){ + lcd_drawCircle(center_x, center_y, i, color); + } +} +void lcd_drawBitmap(uint8_t x, uint8_t y, const uint8_t *picture, uint8_t width, uint8_t height, uint8_t color){ + uint8_t i,j, byteWidth = (width+7)/8; + for (j = 0; j < height; j++) { + for(i=0; i < width;i++){ + if(pgm_read_byte(picture + j * byteWidth + i / 8) & (128 >> (i & 7))){ + lcd_drawPixel(x+i, y+j, color); + } else { + lcd_drawPixel(x+i, y+j, !color); + } + } + } +} +void lcd_display() { +#if defined SSD1306 + lcd_gotoxy(0,0); + lcd_data(&displayBuffer[0][0], DISPLAY_WIDTH*DISPLAY_HEIGHT/8); +#elif defined SH1106 + for (uint8_t i = 0; i < DISPLAY_HEIGHT/8; i++){ + lcd_gotoxy(0,i); + lcd_data(displayBuffer[i], sizeof(displayBuffer[i])); + } +#endif +} +#endif diff --git a/keyboard/twinax/oled/lcd.h b/keyboard/twinax/oled/lcd.h new file mode 100644 index 0000000000..8e4d789698 --- /dev/null +++ b/keyboard/twinax/oled/lcd.h @@ -0,0 +1,130 @@ +/* + * This file is part of lcd library for ssd1306/sh1106 oled-display. + * + * lcd library for ssd1306/sh1106 oled-display 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 any later version. + * + * lcd library for ssd1306/sh1106 oled-display 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 Foobar. If not, see . + * + * Diese Datei ist Teil von lcd library for ssd1306/sh1106 oled-display. + * + * lcd library for ssd1306/sh1106 oled-display ist Freie Software: Sie können es unter den Bedingungen + * der GNU General Public License, wie von der Free Software Foundation, + * Version 3 der Lizenz oder jeder späteren + * veröffentlichten Version, weiterverbreiten und/oder modifizieren. + * + * lcd library for ssd1306/sh1106 oled-display wird in der Hoffnung, dass es nützlich sein wird, aber + * OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite + * Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. + * Siehe die GNU General Public License für weitere Details. + * + * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem + * Programm erhalten haben. Wenn nicht, siehe . + * + * lcd.h + * + * Created by Michael Köhler on 22.12.16. + * Copyright 2016 Skie-Systems. All rights reserved. + * + * lib for OLED-Display with ssd1306/sh1106-Controller + * first dev-version only for I2C-Connection + * at ATMega328P like Arduino Uno + * + * at GRAPHICMODE lib needs SRAM for display + * DISPLAY-WIDTH * DISPLAY-HEIGHT + 2 bytes + */ + +#ifndef LCD_H +#define LCD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if (__GNUC__ * 100 + __GNUC_MINOR__) < 303 +#error "This library requires AVR-GCC 3.3 or later, update to newer AVR-GCC compiler !" +#endif + +#include +#include +#include "i2c.h" // library for I2C-communication + // if you want to use other lib for I2C + // edit i2c_xxx commands in this library + // i2c_start(), i2c_byte(), i2c_stop() + + /* TODO: define displaycontroller */ +#define SSD1306 // or SSD1306, check datasheet of your display + /* TODO: define displaymode */ +#define GRAPHICMODE // TEXTMODE for only text to display, + // GRAPHICMODE for text and graphic + /* TODO: define font */ +#define FONT ssd1306oled_font// set font here, refer font-name at font.h/font.c + + /* TODO: define I2C-adress for display */ + + // using 7-bit-adress for lcd-library + // if you use your own library for twi check I2C-adress-handle +#define LCD_I2C_ADR (0x78 >> 1) // 7 bit slave-adress without r/w-bit + // r/w-bit are set/unset by library + // e.g. 8 bit slave-adress: + // 0x78 = adress 0x3C with cleared r/w-bit (write-mode) + + +#ifndef YES +#define YES 1 +#endif + +#define NORMALSIZE 1 +#define DOUBLESIZE 2 + +#define LCD_DISP_OFF 0xAE +#define LCD_DISP_ON 0xAF + +#define WHITE 0x01 +#define BLACK 0x00 + +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 + + void lcd_command(uint8_t cmd[], uint8_t size); // transmit command to display + void lcd_data(uint8_t data[], uint16_t size); // transmit data to display + void lcd_init(uint8_t dispAttr); + void lcd_home(void); // set cursor to 0,0 + void lcd_invert(uint8_t invert); // invert display + void lcd_sleep(uint8_t sleep); // display goto sleep (power off) + void lcd_set_contrast(uint8_t contrast); // set contrast for display + void lcd_puts(const char* s); // print string, \n-terminated, from ram on screen (TEXTMODE) + // or buffer (GRAPHICMODE) + void lcd_puts_p(const char* progmem_s); // print string from flash on screen (TEXTMODE) + // or buffer (GRAPHICMODE) + + void lcd_clrmem(void); // clear buffer at GRFAICMODE + void lcd_clrscr(void); // clear screen (and buffer at GRFAICMODE) + void lcd_gotoxy(uint8_t x, uint8_t y); // set curser at pos x, y. x means character, + // y means line (page, refer lcd manual) + void lcd_putc(char c); // print character on screen at TEXTMODE + // at GRAPHICMODE print character to buffer + void lcd_charMode(uint8_t mode); // set size of chars +#if defined GRAPHICMODE + void lcd_drawPixel(uint8_t x, uint8_t y, uint8_t color); + void lcd_drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color); + void lcd_drawFastHLine(uint8_t x1, uint8_t y1, uint8_t width, uint8_t color); + void lcd_drawRect(uint8_t px1, uint8_t py1, uint8_t px2, uint8_t py2, uint8_t color); + void lcd_fillRect(uint8_t px1, uint8_t py1, uint8_t px2, uint8_t py2, uint8_t color); + void lcd_drawCircle(uint8_t center_x, uint8_t center_y, uint8_t radius, uint8_t color); + void lcd_fillCircle(uint8_t center_x, uint8_t center_y, uint8_t radius, uint8_t color); + void lcd_drawBitmap(uint8_t x, uint8_t y, const uint8_t picture[], uint8_t width, uint8_t height, uint8_t color); + void lcd_display(void); // copy buffer to display RAM +#endif + +#ifdef __cplusplus +} +#endif +#endif /* LCD_H */ diff --git a/keyboard/twinax/shootduino/game.h b/keyboard/twinax/shootduino/game.h new file mode 100644 index 0000000000..2b7440dc58 --- /dev/null +++ b/keyboard/twinax/shootduino/game.h @@ -0,0 +1,33 @@ +#ifndef __GAME_H_ +#define __GAME_H_ + +#include + + +#include "game_state.h" +#include "highscores.h" + +const uint8_t MAX_LIVES = 3; +const uint8_t MAX_MISSES = 5; +const uint16_t ASTEROID_DELAY = 800; +const uint16_t BULLET_DELAY = 600; +const uint16_t MIN_DELAY_AFTER_STATE_CHANGE = 300; +const uint16_t DEBOUNCE_DELAY = 200; +const uint16_t SHOW_HIGHSCORES_DELAY = 7000; +const uint8_t MAX_SCORE_LEN = 7; + +struct Game { + GameState state; + uint16_t score; + uint32_t ticks; + uint32_t state_changed; + uint32_t bullet_fired; // Timestamp of the last bullet fired by player. + uint32_t asteroid_started; // Timestamp of the last asteroid. + bool player_hit; + uint8_t asteroids_missed; + uint8_t lives; + HighScoreEntry highscore_entry; + bool keys_were_idle; +}; +#endif + diff --git a/keyboard/twinax/shootduino/game_objects.cpp b/keyboard/twinax/shootduino/game_objects.cpp new file mode 100644 index 0000000000..2cd8d539b8 --- /dev/null +++ b/keyboard/twinax/shootduino/game_objects.cpp @@ -0,0 +1,77 @@ +#include "game_objects.h" +#include "game_state.h" +#include "gfx.h" +#include "game.h" +#include + +extern Game shootduino; + +GameObject bullets[MAX_BULLETS]; +GameObject asteroids[MAX_ASTEROIDS]; +GameObject player; + +void init_objects(GameObject* objects, uint8_t max_objects) { + for (uint8_t i = 0; i < max_objects; i++) { + objects[i].is_active = false; + } +} + +void draw_objects(GameObject* objects, uint8_t max_objects) { + const uint8_t* bmp = NULL; + for (uint8_t i = 0; i < max_objects; i++) { + if (objects[i].is_active) { + switch (objects[i].type) { + case ASTEROID: + bmp = asteroid_anim + (2 * ASTEROID_H) * objects[i].anim_frame; + lcd_drawBitmap(asteroids[i].x, asteroids[i].y, bmp, ASTEROID_W, ASTEROID_H, WHITE); + if (shootduino.state == RUNNING) + objects[i].frame_count++; + if (objects[i].frame_count == ANIM_FRAME_DELAY) { + objects[i].anim_frame++; + objects[i].anim_frame %= NUM_ASTEROID_FRAMES; + objects[i].frame_count = 0; + } + break; + + case EXPLOSION: + bmp = explosion_anim + (2 * EXPLOSION_H) * objects[i].anim_frame; + lcd_drawBitmap(asteroids[i].x, asteroids[i].y, bmp, EXPLOSION_W, EXPLOSION_H, WHITE); + objects[i].frame_count++; + if (objects[i].frame_count == ANIM_FRAME_DELAY) { + objects[i].frame_count = 0; + objects[i].anim_frame++; + if (objects[i].anim_frame == NUM_EXPLOSION_FRAMES) + objects[i].is_active = false; + } + break; + + case BULLET: + lcd_drawFastHLine(objects[i].x, objects[i].y, BULLET_W, WHITE); + break; + } + } + } +} + +void move_objects(GameObject* objects, uint8_t max_objects) { + for (uint8_t i = 0; i < max_objects; i++) { + if (objects[i].is_active) { + objects[i].x += objects[i].vx; + switch (objects[i].type) { + case ASTEROID: + if (objects[i].x + (int)ASTEROID_W <= 0) { + objects[i].is_active = false; + shootduino.asteroids_missed++; + } + break; + + case BULLET: + if (objects[i].x >= DISPLAY_WIDTH) { + objects[i].is_active = false; + } + break; + } + } + } +} + diff --git a/keyboard/twinax/shootduino/game_objects.h b/keyboard/twinax/shootduino/game_objects.h new file mode 100644 index 0000000000..deeb378336 --- /dev/null +++ b/keyboard/twinax/shootduino/game_objects.h @@ -0,0 +1,30 @@ +#ifndef __GAME_OBJECTS_H_ +#define __GAME_OBJECTS_H_ + +#include + +const uint8_t MAX_ASTEROIDS = 8; +const uint8_t MAX_BULLETS = 3; + +enum GameObjectType : uint8_t { + PLAYER, ASTEROID, BULLET, EXPLOSION +}; + +struct GameObject { + int16_t x; + int8_t y, vx, vy; + bool is_active; + GameObjectType type; + uint8_t anim_frame; + uint8_t frame_count; +}; + +extern GameObject bullets[MAX_BULLETS]; +extern GameObject asteroids[MAX_ASTEROIDS]; +extern GameObject player; + +void init_objects(GameObject* objects, uint8_t max_objects); +void draw_objects(GameObject* objects, uint8_t max_objects); +void move_objects(GameObject* objects, uint8_t max_objects); +#endif + diff --git a/keyboard/twinax/shootduino/game_state.cpp b/keyboard/twinax/shootduino/game_state.cpp new file mode 100644 index 0000000000..07ca683a11 --- /dev/null +++ b/keyboard/twinax/shootduino/game_state.cpp @@ -0,0 +1,9 @@ +#include "game.h" + +extern Game shootduino; + +void change_state(GameState new_state) { + shootduino.state = new_state; + shootduino.state_changed = shootduino.ticks; +} + diff --git a/keyboard/twinax/shootduino/game_state.h b/keyboard/twinax/shootduino/game_state.h new file mode 100644 index 0000000000..d5aef3afd9 --- /dev/null +++ b/keyboard/twinax/shootduino/game_state.h @@ -0,0 +1,13 @@ +#ifndef __GAME_STATE_H_ +#define __GAME_STATE_H_ + +#include + +enum GameState : uint8_t { + INTRO, RUNNING, PAUSED, LOST_LIVE, DONE, + ENTER_HS, SHOW_HS +}; + +void change_state(GameState new_state); +#endif + diff --git a/keyboard/twinax/shootduino/gfx.cpp b/keyboard/twinax/shootduino/gfx.cpp new file mode 100644 index 0000000000..0d117d811d --- /dev/null +++ b/keyboard/twinax/shootduino/gfx.cpp @@ -0,0 +1,105 @@ +#include "gfx.h" + +const uint8_t spaceship_bmp[] PROGMEM = { + 0b00110000, 0b00000000, + 0b00111000, 0b11100000, + 0b01111100, 0b10000000, + 0b11111111, 0b10000000, + 0b01111111, 0b11000000, + 0b00111111, 0b11100000, + 0b00111111, 0b11111100, + 0b00111111, 0b11100000, + 0b01111111, 0b11000000, + 0b11111111, 0b10000000, + 0b01111100, 0b10000000, + 0b00111000, 0b11100000, + 0b00110000, 0b00000000, + 0b00110000, 0b00000000, +}; + +const uint8_t asteroid_anim[] PROGMEM = { + 0b00011110, 0b00000000, + 0b01111111, 0b00000000, + 0b01111111, 0b11000000, + 0b11111111, 0b11000000, + 0b11111111, 0b11110000, + 0b11111111, 0b11110000, + 0b11111111, 0b11110000, + 0b11111111, 0b11110000, + 0b01111111, 0b11110000, + 0b00011111, 0b11000000, + 0b00000111, 0b10000000, + 0b00000111, 0b10000000, + + 0b00001111, 0b10000000, + 0b00111111, 0b11000000, + 0b00111111, 0b11110000, + 0b01111111, 0b11110000, + 0b11111111, 0b11110000, + 0b11111111, 0b11110000, + 0b11111111, 0b11000000, + 0b11111111, 0b11000000, + 0b01111111, 0b10000000, + 0b01111111, 0b10000000, + 0b00011111, 0b00000000, + 0b00011111, 0b00000000, + + 0b00111100, 0b00000000, + 0b01111111, 0b00000000, + 0b11111111, 0b11000000, + 0b11111111, 0b11110000, + 0b11111111, 0b11110000, + 0b11111111, 0b11110000, + 0b11111111, 0b11110000, + 0b01111111, 0b11110000, + 0b01111111, 0b11000000, + 0b00011111, 0b11000000, + 0b00001111, 0b00000000, + 0b00001111, 0b00000000, +}; + +const unsigned char explosion_anim[] PROGMEM = { + 0b00000000, 0b00000000, + 0b01000000, 0b10000000, + 0b00100001, 0b00000000, + 0b00001100, 0b00000000, + 0b00011110, 0b00000000, + 0b00011110, 0b00000000, + 0b00001100, 0b00000000, + 0b00100001, 0b00000000, + 0b01000000, 0b10000000, + 0b00000000, 0b00000000, + + 0b10000000, 0b01000000, + 0b01000000, 0b10000000, + 0b00001100, 0b00000000, + 0b00010010, 0b00000000, + 0b00100001, 0b00000000, + 0b00100001, 0b00000000, + 0b00010010, 0b00000000, + 0b00001100, 0b00000000, + 0b01000000, 0b10000000, + 0b10000000, 0b01000000, + + 0b10000000, 0b01000000, + 0b00011110, 0b00000000, + 0b00100001, 0b00000000, + 0b01000000, 0b10000000, + 0b01000000, 0b10000000, + 0b01000000, 0b10000000, + 0b01000000, 0b10000000, + 0b00100001, 0b00000000, + 0b00011110, 0b00000000, + 0b10000000, 0b01000000, + + 0b00011110, 0b00000000, + 0b00100001, 0b00000000, + 0b01000000, 0b10000000, + 0b10000000, 0b01000000, + 0b10000000, 0b01000000, + 0b10000000, 0b01000000, + 0b10000000, 0b01000000, + 0b01000000, 0b10000000, + 0b00100001, 0b00000000, + 0b00011110, 0b00000000, +}; diff --git a/keyboard/twinax/shootduino/gfx.h b/keyboard/twinax/shootduino/gfx.h new file mode 100644 index 0000000000..cb916764d6 --- /dev/null +++ b/keyboard/twinax/shootduino/gfx.h @@ -0,0 +1,25 @@ +#ifndef __GFX_H_ +#define __GFX_H_ + +#include + +const uint8_t PLAYER_W = 13; +const uint8_t PLAYER_H = 13; +extern const uint8_t spaceship_bmp[]; + +const uint8_t NUM_ASTEROID_FRAMES = 3; +const uint8_t ASTEROID_W = 11; +const uint8_t ASTEROID_H = 12; +extern const uint8_t asteroid_anim[]; + +const uint8_t NUM_EXPLOSION_FRAMES = 4; +const uint8_t EXPLOSION_W = 10; +const uint8_t EXPLOSION_H = 10; +extern const uint8_t explosion_anim[]; + +const uint8_t BULLET_W = 5; +const uint8_t BULLET_H = 1; + +const uint8_t ANIM_FRAME_DELAY = 4; +#endif + diff --git a/keyboard/twinax/shootduino/highscores.cpp b/keyboard/twinax/shootduino/highscores.cpp new file mode 100644 index 0000000000..e4629afd5d --- /dev/null +++ b/keyboard/twinax/shootduino/highscores.cpp @@ -0,0 +1,133 @@ +#include + +#include "highscores.h" +#include "joystick.h" +#include "textutils.h" +#include "game.h" + +const char INITIALS_LETTERS[] PROGMEM = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .,-?!"; + +extern JoystickState joystick; +extern HighScoreEntry highscore_entry; +extern Game shootduino; + +uint32_t initials_control_hit = 0; +uint8_t letter_index[LEN_INITIALS] = { 0 }; +uint8_t initials_index = 0; + +void init_highscores() { + uint16_t address = HIGHSCORE_ADDR; + if (eeprom_read_byte((uint8_t*)address) != HIGHSCORE_MARKER) { + eeprom_write_byte((uint8_t*)address, HIGHSCORE_MARKER); + HighScoreEntry entry; + address += 1; + char initial = 'A'; + uint16_t score = 0; + for (uint8_t i = 0; i < MAX_HIGHSCORES; i++) { + for (uint8_t j = 0; j < LEN_INITIALS; j++) { + entry.initials[j] = initial; + } + entry.score = score; + eeprom_write_block(&entry, (uint8_t*)address, sizeof(entry)); + address += sizeof(entry); + initial += 1; + } + } +} + +static uint16_t entry_address(uint8_t index, const HighScoreEntry& entry) { + return HIGHSCORE_ADDR + 1 + sizeof(entry) * index; +} + +void get_entry(uint8_t index, HighScoreEntry& entry) { + eeprom_read_block(&entry, (uint8_t*)entry_address(index, entry), sizeof(entry)); +} + +void set_entry(uint8_t index, const HighScoreEntry& entry) { + eeprom_write_block(&entry, (uint8_t*)entry_address(index, entry), sizeof(entry)); +} + +void insert_entry(const HighScoreEntry& entry) { + uint8_t index = get_highscore_index(entry.score); + uint8_t current_index = MAX_HIGHSCORES - 1; + while (current_index != index) { + HighScoreEntry prev_entry; + get_entry(current_index - 1, prev_entry); + set_entry(current_index, prev_entry); + current_index--; + } + set_entry(current_index, entry); +} + +int8_t get_highscore_index(uint16_t score) { + uint8_t index = -1; + HighScoreEntry entry; + for (uint8_t i = 0; i < MAX_HIGHSCORES; i++) { + get_entry(i, entry); + if (entry.score < score) { + index = i; + break; + } + } + return index; +} + +void show_highscore_entry(uint8_t y, HighScoreEntry entry) { + char buf[LEN_HIGHSCORE_ENTRY + 1]; + for (uint8_t j = 0; j < LEN_INITIALS; j++) { + buf[j] = entry.initials[j]; + } + buf[LEN_INITIALS] = ' '; + sprintf(buf + 4, "%05d", entry.score); + buf[LEN_HIGHSCORE_ENTRY] = 0; + set_cursor(score_entry_xpos(), y); + lcd_puts(buf); +} + +void init_highscore_entry(uint16_t score) { + shootduino.highscore_entry.score = score; + for (uint8_t i = 0; i < LEN_INITIALS; i++) { + shootduino.highscore_entry.initials[i] = 'A'; + } + for (uint8_t i = 0; i < LEN_INITIALS; i++) + letter_index[i] = 0; + initials_index = 0; +} + +void handle_highscore_controls() { + if (shootduino.ticks - initials_control_hit >= HS_CONTROL_DELAY) { + initials_control_hit = shootduino.ticks; + if (joystick.left_button) { + initials_index++; + initials_index %= LEN_INITIALS; + } else if (joystick.up) { + letter_index[initials_index]++; + if (letter_index[initials_index] >= strlen_P(INITIALS_LETTERS) - 1) + letter_index[initials_index] = 0; + } else if (joystick.down) { + if (letter_index[initials_index] == 0) + letter_index[initials_index] = strlen_P(INITIALS_LETTERS) - 1; + else + letter_index[initials_index]--; + } + } +} + +void copy_initials_letters() { + for (uint8_t i = 0; i < LEN_INITIALS; i++) { + shootduino.highscore_entry.initials[i] = pgm_read_byte(INITIALS_LETTERS + letter_index[i]); + } + shootduino.highscore_entry.score = shootduino.score; +} + +uint8_t score_entry_xpos() { + return (DISPLAY_WIDTH - LEN_HIGHSCORE_ENTRY * BASE_FONT_W) / 2; +} + +void show_highscore_display() { + pmem_print_center(10, 1, PSTR("Enter Initials")); + show_highscore_entry(30, shootduino.highscore_entry); + set_cursor(score_entry_xpos() + initials_index * BASE_FONT_W, 36); + lcd_putc(24); // Shows an arrow +} + diff --git a/keyboard/twinax/shootduino/highscores.h b/keyboard/twinax/shootduino/highscores.h new file mode 100644 index 0000000000..b2b8cb3e3e --- /dev/null +++ b/keyboard/twinax/shootduino/highscores.h @@ -0,0 +1,33 @@ +#ifndef __HIGHSCORES_H_ +#define __HIGHSCORES_H_ + +#include + +extern const char INITIALS_LETTERS[] PROGMEM; + +const uint16_t HIGHSCORE_ADDR = 0; +const uint8_t HIGHSCORE_MARKER = 42; +const uint8_t LEN_INITIALS = 3; +const uint8_t MAX_HIGHSCORES = 3; + +const uint16_t HS_CONTROL_DELAY = 70; +const uint8_t LEN_HIGHSCORE_ENTRY = 9; + +struct HighScoreEntry { + char initials[LEN_INITIALS]; + uint16_t score; +}; + +void init_highscores(); +void get_entry(uint8_t index, HighScoreEntry& entry); +void set_entry(uint8_t index, const HighScoreEntry& entry); +void insert_entry(const HighScoreEntry& entry); +int8_t get_highscore_index(uint16_t score); +void show_highscore_entry(uint8_t y, HighScoreEntry entry); +void init_highscore_entry(uint16_t score); +void handle_highscore_controls(); +void copy_initials_letters(); +uint8_t score_entry_xpos(); +void show_highscore_display(); +#endif + diff --git a/keyboard/twinax/shootduino/joystick.h b/keyboard/twinax/shootduino/joystick.h new file mode 100644 index 0000000000..380f798937 --- /dev/null +++ b/keyboard/twinax/shootduino/joystick.h @@ -0,0 +1,31 @@ +#ifndef __JOYSTICK_H_ +#define __JOYSTICK_H_ +#include + +struct JoystickState { + bool left : 1; + bool right : 1; + bool up : 1; + bool down : 1; + bool joy_button : 1; +#ifdef JOYSTICK_SHIELD + bool top_button : 1; + bool bottom_button : 1; +#endif + bool left_button : 1; + bool right_button : 1; +}; + +extern struct JoystickState joystick; + +#ifdef __cplusplus + extern "C" { +#endif +void update_joystick(void); +void init_joystick(void); +#ifdef __cplusplus + } +#endif + +#endif + diff --git a/keyboard/twinax/shootduino/shootduino.cpp b/keyboard/twinax/shootduino/shootduino.cpp new file mode 100644 index 0000000000..c5276d545e --- /dev/null +++ b/keyboard/twinax/shootduino/shootduino.cpp @@ -0,0 +1,370 @@ +#include "gfx.h" +#include "joystick.h" +#include "highscores.h" +#include "textutils.h" +#include "starfield.h" +#include "game_objects.h" +#include "game_state.h" +#include "game.h" +#include "timer.h" +#include "shootduino.h" +#include "debug.h" + +#include + +#include + +Game shootduino; + +void fire_bullet() { + if (shootduino.ticks - shootduino.bullet_fired < BULLET_DELAY) { + return; + } + shootduino.bullet_fired = shootduino.ticks; + + for (uint8_t i = 0; i < MAX_BULLETS; i++) { + if (!bullets[i].is_active) { + bullets[i].type = BULLET; + bullets[i].is_active = true; + bullets[i].x = player.x + PLAYER_W; + bullets[i].y = player.y + (PLAYER_H / 2); + bullets[i].vx = 3; + break; + } + } +} + +void start_asteroid() { + if (shootduino.ticks - shootduino.asteroid_started < ASTEROID_DELAY) { + return; + } + shootduino.asteroid_started = shootduino.ticks; + + for (uint8_t i = 0; i < MAX_ASTEROIDS; i++) { + if (!asteroids[i].is_active) { + asteroids[i].type = ASTEROID; + asteroids[i].is_active = true; + asteroids[i].x = DISPLAY_WIDTH; + asteroids[i].y = random_range(DISPLAY_HEIGHT - ASTEROID_H) + 1; + asteroids[i].vx = -1 * random_range(2) - 2; + asteroids[i].anim_frame = random_range(NUM_ASTEROID_FRAMES); + asteroids[i].frame_count = 0; + break; + } + } +} + +void move_player() { + if (joystick.left) { + player.x -= player.vx; + if (player.x < 0) { + player.x = 0; + } + } + if (joystick.right) { + player.x += player.vx; + if (player.x + PLAYER_W > DISPLAY_WIDTH) { + player.x = DISPLAY_WIDTH - PLAYER_W; + } + } + if (joystick.up) { + player.y -= player.vy; + if (player.y < 0) { + player.y = 0; + } + } + if (joystick.down) { + player.y += player.vy; + if (player.y + PLAYER_W > DISPLAY_HEIGHT) { + player.y = DISPLAY_HEIGHT - PLAYER_H; + } + } +} + +void draw_player() { + if (!player.is_active) + return; + if (player.type == PLAYER) { + lcd_drawBitmap(player.x, player.y, spaceship_bmp, PLAYER_W, PLAYER_H, WHITE); + } else { + const uint8_t* bmp = explosion_anim + (2 * EXPLOSION_H) * player.anim_frame; + lcd_drawBitmap(player.x, player.y, bmp, EXPLOSION_W, EXPLOSION_H, WHITE); + player.frame_count++; + if (player.frame_count == ANIM_FRAME_DELAY) { + player.frame_count = 0; + player.anim_frame++; + if (player.anim_frame == NUM_EXPLOSION_FRAMES) + player.is_active = false; + } + } +} + +void check_collisions() { + for (uint8_t i = 0; i < MAX_BULLETS; i++) { + if (bullets[i].is_active) { + for (uint8_t j = 0; j < MAX_ASTEROIDS; j++) { + if (asteroids[j].is_active && asteroids[j].type == ASTEROID) { + if (bullets[i].x < asteroids[j].x + ASTEROID_W && bullets[i].x + BULLET_W > asteroids[j].x && + bullets[i].y < asteroids[j].y + ASTEROID_H && bullets[i].y + BULLET_H > asteroids[j].y) + { + asteroids[j].type = EXPLOSION; + asteroids[j].vx = 0; + bullets[i].is_active = false; + shootduino.score += 100; + break; + } + } + } + } + } + + for (uint8_t i = 0; i < MAX_ASTEROIDS; i++) { + if (asteroids[i].is_active && asteroids[i].type == ASTEROID) { + if (player.x < asteroids[i].x + ASTEROID_W && player.x + PLAYER_W > asteroids[i].x && + player.y < asteroids[i].y + ASTEROID_H && player.y + PLAYER_H > asteroids[i].y) + { + shootduino.player_hit = true; + player.type = EXPLOSION; + player.vx = player.vy = 0; + asteroids[i].type = EXPLOSION; + asteroids[i].vx = 0; + break; + } + } + } +} + +void init_game() { + init_starfield(); + init_objects(bullets, MAX_BULLETS); + init_objects(asteroids, MAX_ASTEROIDS); + change_state(INTRO); + player.x = 0; + player.y = DISPLAY_HEIGHT / 2; + player.vx = player.vy = 2; + player.type = PLAYER; + player.is_active = true; + player.anim_frame = 0; + shootduino.player_hit = false; + shootduino.lives = MAX_LIVES; + shootduino.score = 0; + shootduino.asteroids_missed = 0; + init_highscore_entry(0); +} + +void pause_game() { + char buf[8]; + move_stars(); + draw_stars(); + draw_player(); + draw_objects(bullets, MAX_BULLETS); + draw_objects(asteroids, MAX_ASTEROIDS); + pmem_print_center(5, 2, PSTR("Pause")); + pmem_print(0, 48, 1, PSTR("Lives: ")); + pmem_print(0, 56, 1, PSTR("Score: ")); + set_cursor(42, 48); + sprintf(buf, "%u", shootduino.lives); + lcd_puts(buf); + set_cursor(42, 56); + sprintf(buf, "%u", shootduino.score); + lcd_puts(buf); + if (joystick.left_button && (shootduino.ticks - shootduino.state_changed > MIN_DELAY_AFTER_STATE_CHANGE)) { + change_state(RUNNING); + joystick.left_button = false; +// delay(DEBOUNCE_DELAY); + } +} + +void lost_live() { + move_stars(); + draw_stars(); + draw_objects(bullets, MAX_BULLETS); + draw_objects(asteroids, MAX_ASTEROIDS); + draw_player(); + pmem_print_center(5, 2, PSTR("Don't")); + pmem_print_center(25, 2, PSTR("Give")); + pmem_print_center(45, 2, PSTR("Up!")); + if (joystick.right_button && (shootduino.ticks - shootduino.state_changed > MIN_DELAY_AFTER_STATE_CHANGE)) { + change_state(RUNNING); + player.vx = player.vy = 2; + player.type = PLAYER; + player.is_active = true; + player.anim_frame = 0; + joystick.right_button = false; +// delay(DEBOUNCE_DELAY); + } +} + +void print_score(const uint8_t y) { + char tmp[MAX_SCORE_LEN]; + snprintf(tmp, MAX_SCORE_LEN, "%d", shootduino.score); + const uint8_t score_txt_len = 7; // Length of "Score: ". + const uint8_t score_len = strlen(tmp); + const uint8_t x = (DISPLAY_WIDTH - (score_txt_len + score_len) * BASE_FONT_W) / 2; + pmem_print(x, y, 1, PSTR("Score: ")); + set_cursor(x + score_txt_len * BASE_FONT_W, y); + lcd_puts(tmp); +} + +void game_over() { + move_stars(); + draw_stars(); + pmem_print_center(10, 2, PSTR("Game Over")); + + if (shootduino.lives == 0) { + pmem_print_center(30, 1, PSTR("You have lost")); + pmem_print_center(40, 1, PSTR("all your lives.")); + } else { + pmem_print_center(30, 1, PSTR("You have missed.")); + pmem_print_center(40, 1, PSTR("too many asteroids.")); + } + + print_score(56); + + if (!joystick.right_button) + shootduino.keys_were_idle = true; + + if (shootduino.keys_were_idle && joystick.right_button && (shootduino.ticks - shootduino.state_changed > MIN_DELAY_AFTER_STATE_CHANGE)) { + shootduino.keys_were_idle = false; + if (get_highscore_index(shootduino.score) != -1) { + change_state(ENTER_HS); + } else { + init_game(); + } + joystick.right_button = false; +// delay(DEBOUNCE_DELAY); + } +} + +void update_game() { + if (joystick.left_button) { + change_state(PAUSED); + return; + } + if (shootduino.player_hit) { + shootduino.player_hit = false; + if (--shootduino.lives == 0) { + change_state(DONE); + } else { + change_state(LOST_LIVE); + } + return; + } + if (shootduino.asteroids_missed >= MAX_MISSES) { + change_state(DONE); + return; + } + if (joystick.right_button) { + fire_bullet(); + } + if (shootduino.ticks - shootduino.asteroid_started > ASTEROID_DELAY) { + start_asteroid(); + } + check_collisions(); + move_player(); + move_objects(bullets, MAX_BULLETS); + move_objects(asteroids, MAX_ASTEROIDS); + move_stars(); + draw_stars(); + draw_player(); + draw_objects(bullets, MAX_BULLETS); + draw_objects(asteroids, MAX_ASTEROIDS); +} + +void intro() { + move_stars(); + draw_stars(); + pmem_print_center(10, 2, PSTR("Shootduino")); + pmem_print_center(35, 1, PSTR("Press right button")); + pmem_print_center(45, 1, PSTR("to start!")); + + if (shootduino.ticks - shootduino.state_changed > SHOW_HIGHSCORES_DELAY) { + change_state(SHOW_HS); + } + + if (joystick.right_button) { + change_state(RUNNING); + joystick.right_button = false; +// delay(DEBOUNCE_DELAY); + } +} + +void show_highscores() { + move_stars(); + draw_stars(); + pmem_print_center(10, 1, PSTR("High Scores")); + HighScoreEntry entry; + uint8_t y = 25; + for (uint8_t i = 0; i < MAX_HIGHSCORES; i++) { + get_entry(i, entry); + show_highscore_entry(y, entry); + y += 10; + } + + if (shootduino.ticks - shootduino.state_changed > SHOW_HIGHSCORES_DELAY) { + change_state(INTRO); + } + + if (joystick.right_button) { + change_state(INTRO); + joystick.right_button = false; +// delay(DEBOUNCE_DELAY); + } +} + +void enter_highscore() { + move_stars(); + draw_stars(); + handle_highscore_controls(); + copy_initials_letters(); + show_highscore_display(); + + if (joystick.right_button) { + insert_entry(shootduino.highscore_entry); + init_game(); + change_state(INTRO); + joystick.right_button = false; +// delay(DEBOUNCE_DELAY); + } +} + +void init_display() { +// lcd_init(LCD_DISP_ON); +} + +void shootduino_setup() { +// random_rangeSeed(analogRead(A0)); + init_joystick(); + init_highscores(); + init_game(); + init_display(); +} + +void shootduino_loop() { + shootduino.ticks = timer_read32(); + update_joystick(); + lcd_clrmem(); +#if 1 + switch (shootduino.state) { + case INTRO: intro(); break; + case PAUSED: pause_game(); break; + case RUNNING: update_game(); break; + case LOST_LIVE: lost_live(); break; + case SHOW_HS: show_highscores(); break; + case ENTER_HS: enter_highscore(); break; + case DONE: game_over(); break; + } +#else + { + char buf[16]; + lcd_gotoxy(0,0); + sprintf(buf, "%08u", shootduino.ticks); + lcd_puts(buf); + } +#endif + { + uint32_t loop_timer = timer_read32(); + lcd_display(); + xprintf("Display %u ms\n", timer_elapsed32(loop_timer)); + } +} diff --git a/keyboard/twinax/shootduino/shootduino.h b/keyboard/twinax/shootduino/shootduino.h new file mode 100644 index 0000000000..5424070557 --- /dev/null +++ b/keyboard/twinax/shootduino/shootduino.h @@ -0,0 +1,14 @@ +#ifndef __SHOOTDUINO_H_ +#define __SHOOTDUINO_H_ + +#ifdef __cplusplus +extern "C" { +#endif +void shootduino_setup(void); +void shootduino_loop(void); +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/keyboard/twinax/shootduino/starfield.cpp b/keyboard/twinax/shootduino/starfield.cpp new file mode 100644 index 0000000000..d83cf7d532 --- /dev/null +++ b/keyboard/twinax/shootduino/starfield.cpp @@ -0,0 +1,38 @@ +#include "starfield.h" + +#include + +#include + +Star starfield[MAX_STARS]; + +long random_range(uint8_t range) +{ + return random() % range; +} + +void init_starfield() { + for (uint8_t i = 0; i < MAX_STARS; i++) { + starfield[i].x = random_range(DISPLAY_WIDTH); + starfield[i].y = random_range(DISPLAY_HEIGHT); + starfield[i].vx = random_range(3) + 1; + } +} + +void move_stars() { + for (uint8_t i = 0; i < MAX_STARS; i++) { + starfield[i].x -= starfield[i].vx; + if (starfield[i].x < 0) { + starfield[i].x = DISPLAY_WIDTH; + starfield[i].y = random_range(DISPLAY_HEIGHT); + starfield[i].vx = random_range(3) + 1; + } + } +} + +void draw_stars() { + for (uint8_t i = 0; i < MAX_STARS; i++) { + lcd_drawPixel(starfield[i].x, starfield[i].y, WHITE); + } +} + diff --git a/keyboard/twinax/shootduino/starfield.h b/keyboard/twinax/shootduino/starfield.h new file mode 100644 index 0000000000..498b36a80e --- /dev/null +++ b/keyboard/twinax/shootduino/starfield.h @@ -0,0 +1,21 @@ +#ifndef __STARFIELD_H_ +#define __STARFIELD_H_ + +#include + +const uint8_t MAX_STARS = 10; + +struct Star { + int8_t x, y, vx; +}; + +extern Star starfield[MAX_STARS]; + +void init_starfield(); +void move_stars(); +void draw_stars(); + +long random_range(uint8_t range); + +#endif + diff --git a/keyboard/twinax/shootduino/textutils.cpp b/keyboard/twinax/shootduino/textutils.cpp new file mode 100644 index 0000000000..e25e0f68d7 --- /dev/null +++ b/keyboard/twinax/shootduino/textutils.cpp @@ -0,0 +1,28 @@ +#include "textutils.h" + +uint8_t center_pos(const char* str, const uint8_t text_size) { + return (DISPLAY_WIDTH - (strlen_P(str) * text_size * BASE_FONT_W)) / 2; +} + +void pmem_print(uint8_t x, uint8_t y, uint8_t size, const char* str, uint16_t color) { + uint8_t font_width = size * BASE_FONT_W; + char c; + uint8_t i = 0; +// display.setTextSize(size); +// display.setTextColor(color); + while (c = pgm_read_byte(str++)) { + set_cursor(x + i++ * font_width, y); + lcd_putc(c); + } +} + +void pmem_print_center(uint8_t y, uint8_t size, const char* str, uint16_t color) { + pmem_print(center_pos(str, size), y, size, str, color); +} + +void set_cursor(uint8_t x, uint8_t y) { + uint8_t xt = (x + BASE_FONT_W / 2) / BASE_FONT_W; + uint8_t yt = (y + BASE_FONT_H / 2) / BASE_FONT_H; + + lcd_gotoxy(xt, yt); +} diff --git a/keyboard/twinax/shootduino/textutils.h b/keyboard/twinax/shootduino/textutils.h new file mode 100644 index 0000000000..e45537a2e7 --- /dev/null +++ b/keyboard/twinax/shootduino/textutils.h @@ -0,0 +1,14 @@ +#ifndef __TEXTUTILS_H_ +#define __TEXTUTILS_H_ + +#include + +const uint8_t BASE_FONT_W = 6; +const uint8_t BASE_FONT_H = 8; + +uint8_t center_pos(const char* str, const uint8_t text_size); +void pmem_print(uint8_t x, uint8_t y, uint8_t size, const char* str, uint16_t color = WHITE); +void pmem_print_center(uint8_t y, uint8_t size, const char* str, uint16_t color = WHITE); +void set_cursor(uint8_t x, uint8_t y); +#endif +