From fa5324d30d8191a60c1e8ffd0089fd20c499110b Mon Sep 17 00:00:00 2001 From: eddyem Date: Fri, 24 Jul 2015 12:16:05 +0300 Subject: [PATCH] added matrix keyboard support --- matrix_keyboard/Makefile | 133 ++++++++++++++ matrix_keyboard/Readme.md | 6 + matrix_keyboard/keycodes.c | 74 ++++++++ matrix_keyboard/keycodes.h | 138 +++++++++++++++ matrix_keyboard/ld/devices.data | 9 + matrix_keyboard/ld/stm32f103x4.ld | 31 ++++ matrix_keyboard/ld/stm32f103x6.ld | 31 ++++ matrix_keyboard/ld/stm32f103x8.ld | 31 ++++ matrix_keyboard/ld/stm32f103xB.ld | 31 ++++ matrix_keyboard/ld/stm32f103xC.ld | 31 ++++ matrix_keyboard/ld/stm32f103xD.ld | 31 ++++ matrix_keyboard/ld/stm32f103xE.ld | 31 ++++ matrix_keyboard/ld/stm32f103xF.ld | 31 ++++ matrix_keyboard/ld/stm32f103xG.ld | 31 ++++ matrix_keyboard/main.c | 277 ++++++++++++++++++++++++++++++ matrix_keyboard/matrixkbd.c | 119 +++++++++++++ matrix_keyboard/matrixkbd.h | 52 ++++++ matrix_keyboard/matrkeyb.bin | Bin 0 -> 6084 bytes 18 files changed, 1087 insertions(+) create mode 100644 matrix_keyboard/Makefile create mode 100644 matrix_keyboard/Readme.md create mode 100644 matrix_keyboard/keycodes.c create mode 100644 matrix_keyboard/keycodes.h create mode 100644 matrix_keyboard/ld/devices.data create mode 100644 matrix_keyboard/ld/stm32f103x4.ld create mode 100644 matrix_keyboard/ld/stm32f103x6.ld create mode 100644 matrix_keyboard/ld/stm32f103x8.ld create mode 100644 matrix_keyboard/ld/stm32f103xB.ld create mode 100644 matrix_keyboard/ld/stm32f103xC.ld create mode 100644 matrix_keyboard/ld/stm32f103xD.ld create mode 100644 matrix_keyboard/ld/stm32f103xE.ld create mode 100644 matrix_keyboard/ld/stm32f103xF.ld create mode 100644 matrix_keyboard/ld/stm32f103xG.ld create mode 100644 matrix_keyboard/main.c create mode 100644 matrix_keyboard/matrixkbd.c create mode 100644 matrix_keyboard/matrixkbd.h create mode 100755 matrix_keyboard/matrkeyb.bin diff --git a/matrix_keyboard/Makefile b/matrix_keyboard/Makefile new file mode 100644 index 0000000..54a6dbd --- /dev/null +++ b/matrix_keyboard/Makefile @@ -0,0 +1,133 @@ +BINARY = matrkeyb +BOOTPORT ?= /dev/ttyUSB0 +BOOTSPEED ?= 115200 +# change this linking script depending on particular MCU model, +# for example, if you have STM32F103VBT6, you should write: +LDSCRIPT = ld/stm32f103xB.ld +LIBNAME = opencm3_stm32f1 +DEFS = -DSTM32F1 -DKBD_3BY4 -DEBUG + +OBJDIR = mk +INDEPENDENT_HEADERS= + +FP_FLAGS ?= -msoft-float +ARCH_FLAGS = -mthumb -mcpu=cortex-m3 $(FP_FLAGS) -mfix-cortex-m3-ldrd + +############################################################################### +# Executables +PREFIX ?= arm-none-eabi + +RM := rm -f +RMDIR := rmdir +CC := $(PREFIX)-gcc +LD := $(PREFIX)-gcc +AR := $(PREFIX)-ar +AS := $(PREFIX)-as +OBJCOPY := $(PREFIX)-objcopy +OBJDUMP := $(PREFIX)-objdump +GDB := $(PREFIX)-gdb +STFLASH = $(shell which st-flash) +STBOOT = $(shell which stm32flash) + +############################################################################### +# Source files +LDSCRIPT ?= $(BINARY).ld +SRC = $(wildcard *.c) +OBJS = $(addprefix $(OBJDIR)/, $(SRC:%.c=%.o)) + +ifeq ($(strip $(OPENCM3_DIR)),) +OPENCM3_DIR := /usr/local/arm-none-eabi +$(info Using $(OPENCM3_DIR) path to library) +endif + +INCLUDE_DIR = $(OPENCM3_DIR)/include +LIB_DIR = $(OPENCM3_DIR)/lib +SCRIPT_DIR = $(OPENCM3_DIR)/scripts + +############################################################################### +# C flags +CFLAGS += -Os -g +CFLAGS += -Wall -Wextra -Wshadow -Wimplicit-function-declaration +CFLAGS += -Wredundant-decls +# -Wmissing-prototypes -Wstrict-prototypes +CFLAGS += -fno-common -ffunction-sections -fdata-sections + +############################################################################### +# C & C++ preprocessor common flags +CPPFLAGS += -MD +CPPFLAGS += -Wall -Werror +CPPFLAGS += -I$(INCLUDE_DIR) $(DEFS) + +############################################################################### +# Linker flags +LDFLAGS += --static -nostartfiles +LDFLAGS += -L$(LIB_DIR) +LDFLAGS += -T$(LDSCRIPT) +LDFLAGS += -Wl,-Map=$(*).map +LDFLAGS += -Wl,--gc-sections + +############################################################################### +# Used libraries +LDLIBS += -l$(LIBNAME) +LDLIBS += -Wl,--start-group -lc -lgcc -Wl,--end-group + +.SUFFIXES: .elf .bin .hex .srec .list .map .images +.SECONDEXPANSION: +.SECONDARY: + +ELF := $(OBJDIR)/$(BINARY).elf +LIST := $(OBJDIR)/$(BINARY).list +BIN := $(BINARY).bin +HEX := $(BINARY).hex + +all: bin + +elf: $(ELF) +bin: $(BIN) +hex: $(HEX) +list: $(LIST) + +$(OBJDIR): + mkdir $(OBJDIR) + +$(OBJDIR)/%.o: %.c + @printf " CC $<\n" + $(CC) $(CFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -o $@ -c $< + +$(SRC) : %.c : %.h $(INDEPENDENT_HEADERS) + @touch $@ + +%.h: ; + +$(BIN): $(ELF) + @printf " OBJCOPY $(BIN)\n" + $(OBJCOPY) -Obinary $(ELF) $(BIN) + +$(HEX): $(ELF) + @printf " OBJCOPY $(HEX)\n" + $(OBJCOPY) -Oihex $(ELF) $(HEX) + +$(LIST): $(ELF) + @printf " OBJDUMP $(LIST)\n" + $(OBJDUMP) -S $(ELF) > $(LIST) + +$(ELF): $(OBJDIR) $(OBJS) $(LDSCRIPT) $(LIB_DIR)/lib$(LIBNAME).a + @printf " LD $(ELF)\n" + $(LD) $(LDFLAGS) $(ARCH_FLAGS) $(OBJS) $(LDLIBS) -o $(ELF) + +clean: + @printf " CLEAN\n" + $(RM) $(OBJS) $(OBJDIR)/*.d $(ELF) $(HEX) $(LIST) $(OBJDIR)/*.map + $(RMDIR) $(OBJDIR) + +flash: $(BIN) + @printf " FLASH $(BIN)\n" + $(STFLASH) write $(BIN) 0x8000000 + +boot: $(BIN) + @printf " LOAD $(BIN) through bootloader\n" + $(STBOOT) -b$(BOOTSPEED) $(BOOTPORT) -w $(BIN) + +.PHONY: clean elf hex list flash boot + +#-include $(OBJS:.o=.d) diff --git a/matrix_keyboard/Readme.md b/matrix_keyboard/Readme.md new file mode 100644 index 0000000..1bb611c --- /dev/null +++ b/matrix_keyboard/Readme.md @@ -0,0 +1,6 @@ +### USB HID keyboard + +This is an emulator allowing to connect simple matrix +keyboard 3x3 or 4x4 to computer as regular USB keyboard + +To choose type of keyboard define KBD_3BY4 or KBD_4BY4 in Makefile diff --git a/matrix_keyboard/keycodes.c b/matrix_keyboard/keycodes.c new file mode 100644 index 0000000..7d9d2f5 --- /dev/null +++ b/matrix_keyboard/keycodes.c @@ -0,0 +1,74 @@ +/* + * keycodes.c + * + * Copyright 2015 Edward V. Emelianov + * + * 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. + */ + +#include "keycodes.h" +/* + * Keyboard buffer: + * buf[0]: MOD + * buf[1]: reserved + * buf[2]..buf[7] - keycodes 1..6 + */ +static uint8_t buf[8] = {0,0,0,0,0,0,0,0}; + +#define _(x) (x|0x80) +// array for keycodes according to ASCII table; MSB is MOD_SHIFT flag +static const uint8_t keycodes[] = { + // space !"#$%&' + KEY_SPACE, _(KEY_1), _(KEY_QUOTE), _(KEY_3), _(KEY_4), _(KEY_5), _(KEY_7), KEY_QUOTE, + // ()*+,-./ + _(KEY_9), _(KEY_0), _(KEY_8), _(KEY_EQUAL), KEY_COMMA, KEY_MINUS, KEY_PERIOD, KEY_SLASH, + // 0..9 + 39, 30, 31, 32, 33, 34, 35, 36, 37, 38, + // :;<=>?@ + _(KEY_SEMICOLON), KEY_SEMICOLON, _(KEY_COMMA), KEY_EQUAL, _(KEY_PERIOD), _(KEY_SLASH), _(KEY_2), + // A..Z: for a in $(seq 0 25); do printf "$((a+132)),"; done + 132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157, + // [\]^_` + KEY_LEFT_BRACE, KEY_BACKSLASH, KEY_RIGHT_BRACE, _(KEY_6), _(KEY_MINUS), KEY_TILDE, + // a..z: for a in $(seq 0 25); do printf "$((a+4)),"; done + 4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29, + // {|}~ + _(KEY_LEFT_BRACE), _(KEY_BACKSLASH), _(KEY_RIGHT_BRACE), _(KEY_TILDE) +}; + +uint8_t *set_key_buf(uint8_t MOD, uint8_t KEY){ + buf[0] = MOD; + buf[2] = KEY; + return buf; +} + +/** + * return buffer for sending symbol "ltr" with addition modificator mod + */ +uint8_t *press_key_mod(char ltr, uint8_t mod){ + uint8_t MOD = 0; + uint8_t KEY = 0; + if(ltr > 31){ + KEY = keycodes[ltr - 32]; + if(KEY & 0x80){ + MOD = MOD_SHIFT; + KEY &= 0x7f; + } + }else if (ltr == '\n') KEY = KEY_ENTER; + buf[0] = MOD | mod; + buf[2] = KEY; + return buf; +} diff --git a/matrix_keyboard/keycodes.h b/matrix_keyboard/keycodes.h new file mode 100644 index 0000000..98a2fa1 --- /dev/null +++ b/matrix_keyboard/keycodes.h @@ -0,0 +1,138 @@ +/* + * keycodes.h + * + * Copyright 2015 Edward V. Emelianov + * + * 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. + */ + +#pragma once +#ifndef __KEYKODES_H__ +#define __KEYKODES_H__ + +#include + +uint8_t *set_key_buf(uint8_t MOD, uint8_t KEY); +#define release_key() set_key_buf(0,0) +uint8_t *press_key_mod(char key, uint8_t mod); +#define press_key(k) press_key_mod(k, 0) + +#define MOD_CTRL 0x01 +#define MOD_SHIFT 0x02 +#define MOD_ALT 0x04 +#define MOD_GUI 0x08 + +#define LEFT(mod) (mod) +#define RIGHT(mod) ((mod << 4)) + +#define KEY_A 4 +#define KEY_B 5 +#define KEY_C 6 +#define KEY_D 7 +#define KEY_E 8 +#define KEY_F 9 +#define KEY_G 10 +#define KEY_H 11 +#define KEY_I 12 +#define KEY_J 13 +#define KEY_K 14 +#define KEY_L 15 +#define KEY_M 16 +#define KEY_N 17 +#define KEY_O 18 +#define KEY_P 19 +#define KEY_Q 20 +#define KEY_R 21 +#define KEY_S 22 +#define KEY_T 23 +#define KEY_U 24 +#define KEY_V 25 +#define KEY_W 26 +#define KEY_X 27 +#define KEY_Y 28 +#define KEY_Z 29 +#define KEY_1 30 +#define KEY_2 31 +#define KEY_3 32 +#define KEY_4 33 +#define KEY_5 34 +#define KEY_6 35 +#define KEY_7 36 +#define KEY_8 37 +#define KEY_9 38 +#define KEY_0 39 +#define KEY_ENTER 40 +#define KEY_ESC 41 +#define KEY_BACKSPACE 42 +#define KEY_TAB 43 +#define KEY_SPACE 44 +#define KEY_MINUS 45 +#define KEY_EQUAL 46 +#define KEY_LEFT_BRACE 47 +#define KEY_RIGHT_BRACE 48 +#define KEY_BACKSLASH 49 +#define KEY_NUMBER 50 +#define KEY_SEMICOLON 51 +#define KEY_QUOTE 52 +#define KEY_TILDE 53 +#define KEY_COMMA 54 +#define KEY_PERIOD 55 +#define KEY_SLASH 56 +#define KEY_CAPS_LOCK 57 +#define KEY_F1 58 +#define KEY_F2 59 +#define KEY_F3 60 +#define KEY_F4 61 +#define KEY_F5 62 +#define KEY_F6 63 +#define KEY_F7 64 +#define KEY_F8 65 +#define KEY_F9 66 +#define KEY_F10 67 +#define KEY_F11 68 +#define KEY_F12 69 +#define KEY_PRINTSCREEN 70 +#define KEY_SCROLL_LOCK 71 +#define KEY_PAUSE 72 +#define KEY_INSERT 73 +#define KEY_HOME 74 +#define KEY_PAGE_UP 75 +#define KEY_DELETE 76 +#define KEY_END 77 +#define KEY_PAGE_DOWN 78 +#define KEY_RIGHT 79 +#define KEY_LEFT 80 +#define KEY_DOWN 81 +#define KEY_UP 82 +#define KEY_NUM_LOCK 83 +#define KEYPAD_SLASH 84 +#define KEYPAD_ASTERIX 85 +#define KEYPAD_MINUS 86 +#define KEYPAD_PLUS 87 +#define KEYPAD_ENTER 88 +#define KEYPAD_1 89 +#define KEYPAD_2 90 +#define KEYPAD_3 91 +#define KEYPAD_4 92 +#define KEYPAD_5 93 +#define KEYPAD_6 94 +#define KEYPAD_7 95 +#define KEYPAD_8 96 +#define KEYPAD_9 97 +#define KEYPAD_0 98 +#define KEYPAD_PERIOD 99 + +#endif // __KEYKODES_H__ diff --git a/matrix_keyboard/ld/devices.data b/matrix_keyboard/ld/devices.data new file mode 100644 index 0000000..7f29538 --- /dev/null +++ b/matrix_keyboard/ld/devices.data @@ -0,0 +1,9 @@ +stm32f103?4* stm32f1 ROM=16K RAM=6K +stm32f103?6* stm32f1 ROM=32K RAM=10K +stm32f103?8* stm32f1 ROM=64K RAM=20K +stm32f103?b* stm32f1 ROM=128K RAM=20K +stm32f103?c* stm32f1 ROM=256K RAM=48K +stm32f103?d* stm32f1 ROM=384K RAM=64K +stm32f103?e* stm32f1 ROM=512K RAM=64K +stm32f103?f* stm32f1 ROM=768K RAM=96K +stm32f103?g* stm32f1 ROM=1024K RAM=96K diff --git a/matrix_keyboard/ld/stm32f103x4.ld b/matrix_keyboard/ld/stm32f103x4.ld new file mode 100644 index 0000000..efed65e --- /dev/null +++ b/matrix_keyboard/ld/stm32f103x4.ld @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2012 Karl Palsson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* Linker script for STM32F100x4, 16K flash, 4K RAM. */ + +/* Define memory regions. */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 16K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 6K +} + +/* Include the common ld script. */ +INCLUDE libopencm3_stm32f1.ld + diff --git a/matrix_keyboard/ld/stm32f103x6.ld b/matrix_keyboard/ld/stm32f103x6.ld new file mode 100644 index 0000000..13f05f9 --- /dev/null +++ b/matrix_keyboard/ld/stm32f103x6.ld @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2012 Karl Palsson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* Linker script for STM32F100x4, 16K flash, 4K RAM. */ + +/* Define memory regions. */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 32K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 10K +} + +/* Include the common ld script. */ +INCLUDE libopencm3_stm32f1.ld + diff --git a/matrix_keyboard/ld/stm32f103x8.ld b/matrix_keyboard/ld/stm32f103x8.ld new file mode 100644 index 0000000..2c4640f --- /dev/null +++ b/matrix_keyboard/ld/stm32f103x8.ld @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2012 Karl Palsson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* Linker script for STM32F100x4, 16K flash, 4K RAM. */ + +/* Define memory regions. */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 64K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K +} + +/* Include the common ld script. */ +INCLUDE libopencm3_stm32f1.ld + diff --git a/matrix_keyboard/ld/stm32f103xB.ld b/matrix_keyboard/ld/stm32f103xB.ld new file mode 100644 index 0000000..138444d --- /dev/null +++ b/matrix_keyboard/ld/stm32f103xB.ld @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2012 Karl Palsson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* Linker script for STM32F100x4, 16K flash, 4K RAM. */ + +/* Define memory regions. */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 128K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K +} + +/* Include the common ld script. */ +INCLUDE libopencm3_stm32f1.ld + diff --git a/matrix_keyboard/ld/stm32f103xC.ld b/matrix_keyboard/ld/stm32f103xC.ld new file mode 100644 index 0000000..fda76bf --- /dev/null +++ b/matrix_keyboard/ld/stm32f103xC.ld @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2012 Karl Palsson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* Linker script for STM32F100x4, 16K flash, 4K RAM. */ + +/* Define memory regions. */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 256K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 48K +} + +/* Include the common ld script. */ +INCLUDE libopencm3_stm32f1.ld + diff --git a/matrix_keyboard/ld/stm32f103xD.ld b/matrix_keyboard/ld/stm32f103xD.ld new file mode 100644 index 0000000..0f996c2 --- /dev/null +++ b/matrix_keyboard/ld/stm32f103xD.ld @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2012 Karl Palsson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* Linker script for STM32F100x4, 16K flash, 4K RAM. */ + +/* Define memory regions. */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 384K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K +} + +/* Include the common ld script. */ +INCLUDE libopencm3_stm32f1.ld + diff --git a/matrix_keyboard/ld/stm32f103xE.ld b/matrix_keyboard/ld/stm32f103xE.ld new file mode 100644 index 0000000..b0fcb69 --- /dev/null +++ b/matrix_keyboard/ld/stm32f103xE.ld @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2012 Karl Palsson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* Linker script for STM32F100x4, 16K flash, 4K RAM. */ + +/* Define memory regions. */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 512K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K +} + +/* Include the common ld script. */ +INCLUDE libopencm3_stm32f1.ld + diff --git a/matrix_keyboard/ld/stm32f103xF.ld b/matrix_keyboard/ld/stm32f103xF.ld new file mode 100644 index 0000000..62d47db --- /dev/null +++ b/matrix_keyboard/ld/stm32f103xF.ld @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2012 Karl Palsson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* Linker script for STM32F100x4, 16K flash, 4K RAM. */ + +/* Define memory regions. */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 768K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 96K +} + +/* Include the common ld script. */ +INCLUDE libopencm3_stm32f1.ld + diff --git a/matrix_keyboard/ld/stm32f103xG.ld b/matrix_keyboard/ld/stm32f103xG.ld new file mode 100644 index 0000000..0c0c968 --- /dev/null +++ b/matrix_keyboard/ld/stm32f103xG.ld @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2012 Karl Palsson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* Linker script for STM32F100x4, 16K flash, 4K RAM. */ + +/* Define memory regions. */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 96K +} + +/* Include the common ld script. */ +INCLUDE libopencm3_stm32f1.ld + diff --git a/matrix_keyboard/main.c b/matrix_keyboard/main.c new file mode 100644 index 0000000..ac2a932 --- /dev/null +++ b/matrix_keyboard/main.c @@ -0,0 +1,277 @@ +/* + * main.c + * + * Copyright 2015 Edward V. Emelianov + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "keycodes.h" +#include "matrixkbd.h" + +const struct usb_device_descriptor dev = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, +// 0x03EB 0x2042 - Atmel Keyboard Demo Application + .idVendor = 0x03EB, + .idProduct = 0x2042, + .bcdDevice = 0x0200, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1, +}; + +static const uint8_t hid_report_descriptor[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x06, /* Usage (Keyboard) */ + 0xA1, 0x01, /* Collection (Application) */ +// 0x85, 0x02, /* Report ID */ + 0x05, 0x07, /* Usage (Key codes) */ + 0x19, 0xE0, /* Usage Minimum (224) */ + 0x29, 0xE7, /* Usage Maximum (231) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x75, 0x01, /* Report Size (1) */ + 0x95, 0x08, /* Report Count (8) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x08, /* Report Size (8) */ + 0x81, 0x01, /* Input (Constant) ;5 bit padding */ + 0x95, 0x05, /* Report Count (5) */ + 0x75, 0x01, /* Report Size (1) */ + 0x05, 0x08, /* Usage Page (Page# for LEDs) */ + 0x19, 0x01, /* Usage Minimum (01) */ + 0x29, 0x05, /* Usage Maximum (05) */ + 0x91, 0x02, /* Output (Data, Variable, Absolute) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x03, /* Report Size (3) */ + 0x91, 0x01, /* Output (Constant) */ + 0x95, 0x06, /* Report Count (1) */ + 0x75, 0x08, /* Report Size (3) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x65, /* Logical Maximum (101) */ + 0x05, 0x07, /* Usage (Key codes) */ + 0x19, 0x00, /* Usage Minimum (00) */ + 0x29, 0x65, /* Usage Maximum (101) */ + 0x81, 0x00, /* Input (Data, Array) */ + 0x09, 0x05, /* Usage (Vendor Defined) */ + 0x15, 0x00, /* Logical Minimum (0)) */ + 0x26, 0xFF, 0x00, /* Logical Maximum (255)) */ + 0x75, 0x08, /* Report Count (2)) */ + 0x95, 0x02, /* Report Size (8 bit)) */ + 0xB1, 0x02, /* Feature (Data, Variable, Absolute) */ + 0xC0 /* End Collection,End Collection */ +}; +#if 0 + 0x05, 0x01, // Usage Page (Generic Desktop), + 0x09, 0x06, // Usage (Keyboard), + 0xA1, 0x01, // Collection (Application), + 0x75, 0x01, // Report Size (1), + 0x95, 0x08, // Report Count (8), + 0x05, 0x07, // Usage Page (Key Codes), + 0x19, 0xE0, // Usage Minimum (224), + 0x29, 0xE7, // Usage Maximum (231), + 0x15, 0x00, // Logical Minimum (0), + 0x25, 0x01, // Logical Maximum (1), + 0x81, 0x02, // Input (Data, Variable, Absolute), ;Modifier byte + 0x95, 0x01, // Report Count (1), + 0x75, 0x08, // Report Size (8), + 0x81, 0x03, // Input (Constant), ;Reserved byte + 0x95, 0x05, // Report Count (5), + 0x75, 0x01, // Report Size (1), + 0x05, 0x08, // Usage Page (LEDs), + 0x19, 0x01, // Usage Minimum (1), + 0x29, 0x05, // Usage Maximum (5), + 0x91, 0x02, // Output (Data, Variable, Absolute), ;LED report + 0x95, 0x01, // Report Count (1), + 0x75, 0x03, // Report Size (3), + 0x91, 0x03, // Output (Constant), ;LED report padding + 0x95, 0x06, // Report Count (6), + 0x75, 0x08, // Report Size (8), + 0x15, 0x00, // Logical Minimum (0), + 0x25, 0x68, // Logical Maximum(104), + 0x05, 0x07, // Usage Page (Key Codes), + 0x19, 0x00, // Usage Minimum (0), + 0x29, 0x68, // Usage Maximum (104), + 0x81, 0x00, // Input (Data, Array), + 0xc0 // End Collection +}; +#endif + +static const struct { + struct usb_hid_descriptor hid_descriptor; + struct { + uint8_t bReportDescriptorType; + uint16_t wDescriptorLength; + } __attribute__((packed)) hid_report; +} __attribute__((packed)) hid_function = { + .hid_descriptor = { + .bLength = sizeof(hid_function), + .bDescriptorType = USB_DT_HID, + .bcdHID = 0x0100, + .bCountryCode = 0, + .bNumDescriptors = 1, + }, + .hid_report = { + .bReportDescriptorType = USB_DT_REPORT, + .wDescriptorLength = sizeof(hid_report_descriptor), + }, +}; + +const struct usb_endpoint_descriptor hid_endpoint = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x81, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 8, + .bInterval = 0x10, +}; + +const struct usb_interface_descriptor hid_iface = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 1, // boot + .bInterfaceProtocol = 1, // keyboard + .iInterface = 0, + + .endpoint = &hid_endpoint, + + .extra = &hid_function, + .extralen = sizeof(hid_function), +}; + +const struct usb_interface ifaces[] = {{ + .num_altsetting = 1, + .altsetting = &hid_iface, +}}; + +const struct usb_config_descriptor config = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0, + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0xC0, + .bMaxPower = 0x32, + + .interface = ifaces, +}; + +static const char *usb_strings[] = { + "Simple matrix keyboard 3x4", + "EEV", + "v01", +}; + +/* Buffer to be used for control requests. */ +uint8_t usbd_control_buffer[128]; + +static int hid_control_request(usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, + void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req)){ + (void)complete; + (void)usbd_dev; + + if ((req->bmRequestType != 0x81) || + (req->bRequest != USB_REQ_GET_DESCRIPTOR) || + (req->wValue != 0x2200)) + return 0; + + *buf = (uint8_t *)hid_report_descriptor; + *len = sizeof(hid_report_descriptor); + + return 1; +} + +static void hid_set_config(usbd_device *usbd_dev, uint16_t wValue){ + (void)wValue; + (void)usbd_dev; + usbd_ep_setup(usbd_dev, 0x81, USB_ENDPOINT_ATTR_INTERRUPT, 4, NULL); + usbd_register_control_callback( + usbd_dev, + USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_INTERFACE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, + hid_control_request); +} + +/* + * SysTick used for system timer with period of 1ms + */ +void SysTick_init(){ + systick_set_clocksource(STK_CSR_CLKSOURCE_AHB_DIV8); // Systyck: 72/8=9MHz + systick_set_reload(8999); // 9000 pulses: 1kHz + systick_interrupt_enable(); + systick_counter_enable(); +} + +int main(void){ + usbd_device *usbd_dev; + + rcc_clock_setup_in_hsi_out_48mhz(); + SysTick_init(); +/* + // if PC11 connected to usb 1.5kOhm pull-up through transistor + rcc_periph_clock_enable(RCC_GPIOC); + gpio_set(GPIOC, GPIO11); + gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_2_MHZ, + GPIO_CNF_OUTPUT_PUSHPULL, GPIO11); +*/ + usbd_dev = usbd_init(&stm32f103_usb_driver, &dev, &config, usb_strings, 3, usbd_control_buffer, sizeof(usbd_control_buffer)); + usbd_register_set_config_callback(usbd_dev, hid_set_config); + matrixkbd_init(); +/* + int i; + for (i = 0; i < 0x80000; i++) + __asm__("nop"); + gpio_clear(GPIOC, GPIO11); +*/ + + while (1){ + char prsd, rlsd; + usbd_poll(usbd_dev); + get_matrixkbd(&prsd, &rlsd); + if(rlsd) + while(8 != usbd_ep_write_packet(usbd_dev, 0x81, release_key(), 8)); + if(prsd) + while(8 != usbd_ep_write_packet(usbd_dev, 0x81, press_key(prsd), 8)); + } +} + +volatile uint32_t Timer = 0; // global timer (milliseconds) +/** + * SysTick interrupt: increment global time & send data buffer through USB + */ +void sys_tick_handler(){ + Timer++; +} diff --git a/matrix_keyboard/matrixkbd.c b/matrix_keyboard/matrixkbd.c new file mode 100644 index 0000000..8e17654 --- /dev/null +++ b/matrix_keyboard/matrixkbd.c @@ -0,0 +1,119 @@ +/* + * matrixkbd.c + * Simple utilite for working with matrix keyboard 3columns x 4rows + * Don't understand multiple keys!!! + * + * Copyright 2015 Edward V. Emelianov + * + * 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. + */ + +#include "matrixkbd.h" + +extern volatile uint32_t Timer; + +#if !defined(KBD_3BY4) && !defined(KBD_4BY4) + #error You should define keyboard type: KBD_3BY4 or KBD_4BY4 +#endif + +/* + * Rows are inputs, columns are outputs + */ +#define ROWS 4 +const uint32_t row_ports[ROWS] = {ROW1_PORT, ROW2_PORT, ROW3_PORT, ROW4_PORT}; +const uint16_t row_pins[ROWS] = {ROW1_PIN, ROW2_PIN, ROW3_PIN, ROW4_PIN}; +// symbols[Rows][Columns] +#if defined(KBD_3BY4) +#define COLS 3 +const uint32_t col_ports[COLS] = {COL1_PORT, COL2_PORT, COL3_PORT}; +const uint16_t col_pins[COLS] = {COL1_PIN, COL2_PIN, COL3_PIN}; +char kbd[ROWS][COLS] = { + {'1', '2', '3'}, + {'4', '5', '6'}, + {'7', '8', '9'}, + {'*', '0', '#'} +}; +#elif defined(KBD_4BY4) +#define COLS 4 +const uint32_t col_ports[COLS] = {COL1_PORT, COL2_PORT, COL3_PORT, COL4_PORT}; +const uint16_t col_pins[COLS] = {COL1_PIN, COL2_PIN, COL3_PIN, COL4_PIN}; +char kbd[ROWS][COLS] = { + {'1', '2', '3', 'A'}, + {'4', '5', '6', 'B'}, + {'7', '8', '9', 'C'}, + {'*', '0', '#', 'D'} +}; +#endif +uint8_t oldstate[ROWS][COLS]; + +/** + * init keyboard pins: all columns are opendrain outputs + * all rows are pullup inputs + */ +void matrixkbd_init(){ + int i, j; + rcc_peripheral_enable_clock(&RCC_APB2ENR, KBD_RCC_PORT_CLOCK); + for(i = 0; i < COLS; ++i){ + gpio_set(col_ports[i], col_pins[i]); + gpio_set_mode(col_ports[i], GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, + col_pins[i]); + } + for(i = 0; i < ROWS; ++i){ + gpio_set(row_ports[i], row_pins[i]); + gpio_set_mode(row_ports[i], GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, + row_pins[i]); + } + for(j = 0; j < ROWS; ++j) + for(i = 0; i < COLS; ++i) + oldstate[j][i] = 1; +} + + +/** + * Check keyboard + * if some key pressed, set prsd to its value (symbol) + * if released - set rlsd to 1 + * works onse per 60ms + * DON't work with multiple key pressing! + * In case of simultaneous pressing it will return at first call code of + * first key met, at second - second and so on + */ +void get_matrixkbd(char *prsd, char *rlsd){ + *prsd = 0; + *rlsd = 0; + static uint32_t oldTimer = 0; + if(Timer > oldTimer && Timer - oldTimer < KBD_TIMEOUT) return; + int r, c; + for(c = 0; c < COLS; ++c){ + gpio_clear(col_ports[c], col_pins[c]); + oldTimer = Timer; + while(oldTimer == Timer); // wait at least one second + for(r = 0; r < ROWS; ++r){ + uint8_t st = (gpio_get(row_ports[r], row_pins[r])) ? 1 : 0; + if(oldstate[r][c] != st){ // button state changed + oldstate[r][c] = st; + gpio_set(col_ports[c], col_pins[c]); + if(st == 1){ // released + *rlsd = 1; + }else{ + *prsd = kbd[r][c]; + } + return; + } + } + gpio_set(col_ports[c], col_pins[c]); + } +} diff --git a/matrix_keyboard/matrixkbd.h b/matrix_keyboard/matrixkbd.h new file mode 100644 index 0000000..408c5c2 --- /dev/null +++ b/matrix_keyboard/matrixkbd.h @@ -0,0 +1,52 @@ +/* + * matrixkbd.h + * + * Copyright 2015 Edward V. Emelianov + * + * 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. + */ + +#include +#include +#include +#include + +void matrixkbd_init(); +void get_matrixkbd(char *prsd, char *rlsd); + +// timeout for keyboard checkout +#define KBD_TIMEOUT 50 + +// kbd ports for clock_enable +#define KBD_RCC_PORT_CLOCK (RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN) +// Rows pins & ports: PB15, 13, 11 & 10 +#define ROW1_PORT (GPIOB) +#define ROW1_PIN (GPIO15) +#define ROW2_PORT (GPIOB) +#define ROW2_PIN (GPIO13) +#define ROW3_PORT (GPIOB) +#define ROW3_PIN (GPIO11) +#define ROW4_PORT (GPIOB) +#define ROW4_PIN (GPIO10) +// Columns pins & ports: PB1, PA7 & PA5 +#define COL1_PORT (GPIOB) +#define COL1_PIN (GPIO1) +#define COL2_PORT (GPIOA) +#define COL2_PIN (GPIO7) +#define COL3_PORT (GPIOA) +#define COL3_PIN (GPIO5) +//#define COL4_PORT () +//#define COL4_PIN () diff --git a/matrix_keyboard/matrkeyb.bin b/matrix_keyboard/matrkeyb.bin new file mode 100755 index 0000000000000000000000000000000000000000..29ce3c430074d72006cdadc8d948f2c3d8e7b44e GIT binary patch literal 6084 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 OF94naK!wjB0{;uua2mk? literal 0 HcmV?d00001