├── .gitignore ├── .gitmodules ├── README.md ├── lib ├── libopencm3.rules.mk ├── libopencm3.stm32f1xx.mk ├── librfm3.rules.mk ├── librfm3 │ ├── include │ │ ├── librfm3.h │ │ └── librfm3 │ │ │ └── i2c_ctx.h │ └── src │ │ └── i2c_ctx.c ├── librfn.rules.mk └── stm32.ld ├── pics ├── 17-11-23 17-50-54 1132.jpg ├── 17-11-23 18-09-33 1133.jpg ├── 17-11-23 18-09-50 1134.jpg └── hardware.png ├── src ├── Makefile ├── controllers │ ├── snescontroller.c │ └── wiiclassic.c ├── i2c.c ├── include │ ├── bitset.h │ ├── controller.h │ ├── i2c.h │ └── myconsole.h ├── myconsole.c ├── swiitch-controller.c └── usb │ ├── include │ ├── usb.h │ ├── usb_cdc.h │ ├── usb_dfu.h │ └── usb_hid.h │ ├── usb.c │ ├── usb_cdc.c │ ├── usb_dfu.c │ └── usb_hid.c └── stuff └── bootloader ├── README.md └── stm32f103c8t6.bin /.gitignore: -------------------------------------------------------------------------------- 1 | *.d 2 | *.elf 3 | *.bin 4 | *.hex 5 | *.map 6 | *.o 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libopencm3"] 2 | path = lib/libopencm3 3 | url = https://github.com/libopencm3/libopencm3 4 | [submodule "lib/librfn"] 5 | path = lib/librfn 6 | url = https://github.com/daniel-thompson/librfn/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sWiitch controller 2 | 3 | Code to use some classic (or generally alternative) controllers on the Nintendo Switch. 4 | 5 | Build using libopencm3 for the stm32f103 series microprocessor. 6 | 7 | ## Hardware 8 | 9 | I'm using a stm32f108c8t6 on a so called "blue pill" board, which basically is just the bare minimum 10 | to get the chip running. 11 | I had to solder a 1.8kOhm resistor on the board between A12 and 3.3V to get USB working (see [stm32duino wiki](http://wiki.stm32duino.com/index.php?title=Blue_Pill#Hardware_installation)). 12 | 13 | ### Wii Classic Controller 14 | 15 | The Wii Classic Controller should be connected to I2C1 (GPIO B6/SCL and B7/SDA, with around 4.7kOhm pull-up resistors). 16 | 17 | You can use an extension cord to get a nice socket, or you can get one of the many cheap breakout boards, or even just splice into the wire of your controller, 18 | or go haywire and build the stm32 board directly into the controller, the possibilities are endless! 19 | 20 | Mine has the connector from a broken Wiimote :) 21 | 22 | In theory you should be able to connect the 8Bitdo Retro Receiver to this setup, 23 | making PS3/PS4/Wii Mote and Wii U Pro controllers compatible with the Switch (untested). 24 | 25 | ### NES/SNES Controller 26 | 27 | The NES/SNES controller should be connected to GPIO A0/DATA, A1/LATCH and A2/CLOCK. Power should just be connected between 3.3V and GND, don't use the 5V output! 28 | 29 | I actually put a board into one of my SNES controllers ;) 30 | 31 | -------------------------------------------------------------------------------- /lib/libopencm3.rules.mk: -------------------------------------------------------------------------------- 1 | ## 2 | ## This file is part of the libopencm3 project. 3 | ## 4 | ## Copyright (C) 2009 Uwe Hermann 5 | ## Copyright (C) 2010 Piotr Esden-Tempski 6 | ## Copyright (C) 2013 Frantisek Burian 7 | ## 8 | ## This library is free software: you can redistribute it and/or modify 9 | ## it under the terms of the GNU Lesser General Public License as published by 10 | ## the Free Software Foundation, either version 3 of the License, or 11 | ## (at your option) any later version. 12 | ## 13 | ## This library is distributed in the hope that it will be useful, 14 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ## GNU Lesser General Public License for more details. 17 | ## 18 | ## You should have received a copy of the GNU Lesser General Public License 19 | ## along with this library. If not, see . 20 | ## 21 | 22 | # Be silent per default, but 'make V=1' will show all compiler calls. 23 | ifneq ($(V),1) 24 | Q := @ 25 | NULL := 2>/dev/null 26 | endif 27 | 28 | ############################################################################### 29 | # Executables 30 | 31 | PREFIX ?= arm-none-eabi 32 | 33 | CC := $(PREFIX)-gcc 34 | CXX := $(PREFIX)-g++ 35 | LD := $(PREFIX)-gcc 36 | AR := $(PREFIX)-ar 37 | AS := $(PREFIX)-as 38 | OBJCOPY := $(PREFIX)-objcopy 39 | OBJDUMP := $(PREFIX)-objdump 40 | GDB := $(PREFIX)-gdb 41 | STFLASH = $(shell which st-flash) 42 | STYLECHECK := /checkpatch.pl 43 | STYLECHECKFLAGS := --no-tree -f --terse --mailback 44 | STYLECHECKFILES := $(shell find . -name '*.[ch]') 45 | 46 | 47 | ############################################################################### 48 | # Source files 49 | 50 | LDSCRIPT ?= $(BINARY).ld 51 | 52 | OBJS += $(BINARY).o 53 | 54 | 55 | ifeq ($(strip $(OPENCM3_DIR)),) 56 | # user has not specified the library path, so we try to detect it 57 | 58 | # where we search for the library 59 | LIBPATHS := ../lib/libopencm3 60 | 61 | OPENCM3_DIR := $(wildcard $(LIBPATHS:=/locm3.sublime-project)) 62 | OPENCM3_DIR := $(firstword $(dir $(OPENCM3_DIR))) 63 | 64 | ifeq ($(strip $(OPENCM3_DIR)),) 65 | $(warning Cannot find libopencm3 library in the standard search paths.) 66 | $(error Please specify it through OPENCM3_DIR variable!) 67 | endif 68 | endif 69 | 70 | ifeq ($(V),1) 71 | $(info Using $(OPENCM3_DIR) path to library) 72 | endif 73 | 74 | INCLUDE_DIR = $(OPENCM3_DIR)/include 75 | LIB_DIR = $(OPENCM3_DIR)/lib 76 | SCRIPT_DIR = $(OPENCM3_DIR)/scripts 77 | 78 | ############################################################################### 79 | # C flags 80 | 81 | CFLAGS += -Os -g 82 | CFLAGS += -std=gnu99 83 | CFLAGS += -Wextra -Wshadow -Wimplicit-function-declaration 84 | CFLAGS += -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes 85 | CFLAGS += -fno-common -ffunction-sections -fdata-sections 86 | 87 | ############################################################################### 88 | # C++ flags 89 | 90 | CXXFLAGS += -Os -g 91 | CXXFLAGS += -Wextra -Wshadow -Wredundant-decls -Weffc++ 92 | CXXFLAGS += -fno-common -ffunction-sections -fdata-sections 93 | 94 | ############################################################################### 95 | # C & C++ preprocessor common flags 96 | 97 | CPPFLAGS += -MD 98 | CPPFLAGS += -Wall -Wundef 99 | CPPFLAGS += -I$(INCLUDE_DIR) $(DEFS) 100 | 101 | ############################################################################### 102 | # Linker flags 103 | 104 | LDFLAGS += --static -nostartfiles 105 | LDFLAGS += -L$(LIB_DIR) 106 | LDFLAGS += -T$(LDSCRIPT) 107 | LDFLAGS += -Wl,-Map=$(*).map 108 | LDFLAGS += -Wl,--gc-sections 109 | ifeq ($(V),99) 110 | LDFLAGS += -Wl,--print-gc-sections 111 | endif 112 | 113 | ############################################################################### 114 | # Used libraries 115 | 116 | LDLIBS += -l$(LIBNAME) 117 | LDLIBS += -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group 118 | 119 | ############################################################################### 120 | ############################################################################### 121 | ############################################################################### 122 | 123 | .SUFFIXES: .elf .bin .hex .srec .list .map .images 124 | .SECONDEXPANSION: 125 | .SECONDARY: 126 | 127 | all: elf 128 | 129 | elf: $(BINARY).elf 130 | bin: $(BINARY).bin 131 | hex: $(BINARY).hex 132 | srec: $(BINARY).srec 133 | list: $(BINARY).list 134 | 135 | images: $(BINARY).images 136 | flash: $(BINARY).flash 137 | 138 | %.images: %.bin %.hex %.srec %.list %.map 139 | @#printf "*** $* images generated ***\n" 140 | 141 | %.bin: %.elf 142 | @#printf " OBJCOPY $(*).bin\n" 143 | $(Q)$(OBJCOPY) -Obinary $(*).elf $(*).bin 144 | 145 | %.hex: %.elf 146 | @#printf " OBJCOPY $(*).hex\n" 147 | $(Q)$(OBJCOPY) -Oihex $(*).elf $(*).hex 148 | 149 | %.srec: %.elf 150 | @#printf " OBJCOPY $(*).srec\n" 151 | $(Q)$(OBJCOPY) -Osrec $(*).elf $(*).srec 152 | 153 | %.list: %.elf 154 | @#printf " OBJDUMP $(*).list\n" 155 | $(Q)$(OBJDUMP) -S $(*).elf > $(*).list 156 | 157 | $(BINARY).elf $(BINARY).map: $(OBJS) $(LDSCRIPT) $(LIB_DIR)/lib$(LIBNAME).a 158 | @printf " LD $(*).elf\n" 159 | $(Q)$(LD) $(LDFLAGS) $(ARCH_FLAGS) $(OBJS) $(LDLIBS) -o $(*).elf 160 | 161 | %.o: %.c 162 | @printf " CC $(*).c\n" 163 | $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -o $@ -c $< 164 | 165 | %.o: %.cxx 166 | @printf " CXX $(*).cxx\n" 167 | $(Q)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -o $(*).o -c $(*).cxx 168 | 169 | %.o: %.cpp 170 | @printf " CXX $(*).cpp\n" 171 | $(Q)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -o $(*).o -c $(*).cpp 172 | 173 | clean: 174 | @#printf " CLEAN\n" 175 | $(Q)$(RM) *.o *.d *.elf *.bin *.hex *.srec *.list *.map 176 | 177 | stylecheck: $(STYLECHECKFILES:=.stylecheck) 178 | styleclean: $(STYLECHECKFILES:=.styleclean) 179 | 180 | # the cat is due to multithreaded nature - we like to have consistent chunks of text on the output 181 | %.stylecheck: % 182 | $(Q)$(SCRIPT_DIR)$(STYLECHECK) $(STYLECHECKFLAGS) $* > $*.stylecheck; \ 183 | if [ -s $*.stylecheck ]; then \ 184 | cat $*.stylecheck; \ 185 | else \ 186 | rm -f $*.stylecheck; \ 187 | fi; 188 | 189 | %.styleclean: 190 | $(Q)rm -f $*.stylecheck; 191 | 192 | 193 | %.stlink-flash: %.bin 194 | @printf " FLASH $<\n" 195 | $(Q)$(STFLASH) write $(*).bin 0x8000000 196 | 197 | ifeq ($(STLINK_PORT),) 198 | ifeq ($(BMP_PORT),) 199 | ifeq ($(OOCD_SERIAL),) 200 | ifeq ($(DFU_ADDRESS),) 201 | %.flash: %.hex 202 | @printf " FLASH $<\n" 203 | @# IMPORTANT: Don't use "resume", only "reset" will work correctly! 204 | $(Q)$(OOCD) -f interface/$(OOCD_INTERFACE).cfg \ 205 | -f $(OOCD_BOARD).cfg \ 206 | -c "init" -c "reset init" \ 207 | -c "flash write_image erase $(*).hex" \ 208 | -c "reset" \ 209 | -c "shutdown" $(NULL) 210 | else 211 | %.flash: %.bin 212 | @printf " FLASH $<\n" 213 | -$(Q)amidi -p hw:multistomp,0,0 --send-hex "f0 60 07 10 4d f7" 2> /dev/null && sleep 1 214 | #$(Q)dfu-util -a 0 -d 0483:df11 -s $(DFU_ADDRESS):leave -D $(*).bin 215 | $(Q)dfu-util -a 0 -d 0f0d:00c1 -s $(DFU_ADDRESS):leave -E 1 -D $(*).bin 216 | endif 217 | else 218 | %.flash: %.hex 219 | @printf " FLASH $<\n" 220 | @# IMPORTANT: Don't use "resume", only "reset" will work correctly! 221 | $(Q)$(OOCD) -f interface/$(OOCD_INTERFACE).cfg \ 222 | -f board/$(OOCD_BOARD).cfg \ 223 | -c "ft2232_serial $(OOCD_SERIAL)" \ 224 | -c "init" -c "reset init" \ 225 | -c "flash write_image erase $(*).hex" \ 226 | -c "reset" \ 227 | -c "shutdown" $(NULL) 228 | endif 229 | else 230 | %.flash: %.elf 231 | @printf " GDB $(*).elf (flash)\n" 232 | $(Q)$(GDB) --batch \ 233 | -ex 'target extended-remote $(BMP_PORT)' \ 234 | -x $(SCRIPT_DIR)/black_magic_probe_flash.scr \ 235 | $(*).elf 236 | endif 237 | else 238 | %.flash: %.elf 239 | @printf " GDB $(*).elf (flash)\n" 240 | $(Q)$(GDB) --batch \ 241 | -ex 'target extended-remote $(STLINK_PORT)' \ 242 | -x $(SCRIPT_DIR)/stlink_flash.scr \ 243 | $(*).elf 244 | endif 245 | 246 | .PHONY: images clean stylecheck styleclean elf bin hex srec list 247 | 248 | -include $(OBJS:.o=.d) 249 | -------------------------------------------------------------------------------- /lib/libopencm3.stm32f1xx.mk: -------------------------------------------------------------------------------- 1 | DEFS += -DF_CPU=72000000 2 | DEFS += -DF_APB1=3600000 3 | LDSCRIPT = ../lib/stm32.ld 4 | DFU_ADDRESS = 0x08002000 5 | LIBNAME = opencm3_stm32f1 6 | DEFS += -DSTM32F1 7 | 8 | FP_FLAGS ?= -msoft-float 9 | ARCH_FLAGS = -mthumb -mcpu=cortex-m3 $(FP_FLAGS) -mfix-cortex-m3-ldrd 10 | 11 | include ../lib/librfn.rules.mk 12 | include ../lib/librfm3.rules.mk 13 | include ../lib/libopencm3.rules.mk 14 | -------------------------------------------------------------------------------- /lib/librfm3.rules.mk: -------------------------------------------------------------------------------- 1 | # 2 | # librfm3.rules.mk 3 | # 4 | # This file is part of the i2c-star project. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | 12 | LIBRFM3_DIR = ../lib/librfm3 13 | 14 | OBJS += \ 15 | i2c_ctx.o 16 | 17 | vpath %.c $(LIBRFM3_DIR)/src 18 | 19 | CPPFLAGS += -I$(LIBRFM3_DIR)/include 20 | -------------------------------------------------------------------------------- /lib/librfm3/include/librfm3.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of librfm3 (a utility library built on librfn and libopencm3) 3 | * 4 | * Copyright (C) 2014 Daniel Thompson 5 | * 6 | * This library is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library. If not, see . 18 | */ 19 | 20 | #ifndef RF_LIBRFM3_H_ 21 | 22 | /*! 23 | \mainpage 24 | 25 | librfm3 is a collection of utility libraries that fit together nicely 26 | with librfn and libopencm3. It is a collection of tools and toys for 27 | ARM Cortex-M microcontrollers, especially the STM32 family. 28 | 29 | librfm3 is free software licensed under the GNU Lesser General Public License, 30 | v3 or later. 31 | 32 | Source code can be downloaded from https://github.com/daniel-thompson/i2c-star 33 | 34 | */ 35 | 36 | #include 37 | 38 | #endif // RF_LIBRFM3_H_ 39 | -------------------------------------------------------------------------------- /lib/librfm3/include/librfm3/i2c_ctx.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of librfm3 (a utility library built on librfn and libopencm3) 3 | * 4 | * Copyright (C) 2014 Daniel Thompson 5 | * 6 | * This library is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library. If not, see . 18 | */ 19 | 20 | #ifndef RF_I2C_CTX_H_ 21 | #define RF_I2C_CTX_H_ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | /*! 28 | * \defgroup librfm3_i2c_ctx I2C context manager 29 | * 30 | * \brief Protothreaded I2C driver for libopencm3. 31 | * 32 | * @{ 33 | */ 34 | 35 | typedef struct i2c_ctx { 36 | pt_t pt; //!< Protothread state for high-level functions 37 | pt_t leaf; //!< Protothread state for low-level functions 38 | 39 | int err; //!< Error reporting (becomes non-zero on error) 40 | bool verbose; //!< Automatically print error reports 41 | 42 | uint32_t i2c; //!< \private 43 | uint32_t timeout; //!< \private 44 | uint8_t bytes_remaining; //!< \private 45 | int i; //!< \private 46 | } i2c_ctx_t; 47 | 48 | typedef struct i2c_device_map { 49 | uint16_t devices[8]; //!< Bitmap recording detected devices 50 | } i2c_device_map_t; 51 | 52 | /*! 53 | * \brief Initialize the context structure ready for a single I2C transaction. 54 | * 55 | * The context must be reinitialized before each I2C transaction otherwise 56 | * the transaction may timeout immediately. 57 | */ 58 | void i2c_ctx_init(i2c_ctx_t *c, uint32_t pi2c); 59 | 60 | /*! 61 | * \brief Reset and reinitialize the I2C bus. 62 | */ 63 | void i2c_ctx_reset(i2c_ctx_t *c); 64 | 65 | /*! 66 | * \brief Send a start condition. 67 | * 68 | * \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN(). 69 | */ 70 | pt_state_t i2c_ctx_start(i2c_ctx_t *c); 71 | 72 | /*! 73 | * \brief Send the target bus address. 74 | * 75 | * \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN(). 76 | * 77 | * If bytes_to_read is zero then launch a write transaction, otherwise 78 | * launch a read and manage the ACK/NACK and STOP generation based on 79 | * the number of bytes to transfer. 80 | */ 81 | pt_state_t i2c_ctx_sendaddr(i2c_ctx_t *c, uint16_t addr, uint8_t bytes_to_read); 82 | 83 | /*! 84 | * \brief Write a single byte. 85 | * 86 | * \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN(). 87 | */ 88 | pt_state_t i2c_ctx_senddata(i2c_ctx_t *c, uint8_t data); 89 | 90 | /*! 91 | * \brief Read a single byte. 92 | * 93 | * \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN(). 94 | */ 95 | pt_state_t i2c_ctx_getdata(i2c_ctx_t *c, uint8_t *data); 96 | 97 | /*! 98 | * \brief Send a stop condition. 99 | * 100 | * \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN(). 101 | * 102 | * Complete a low-level I2C write transaction. This is not required for 103 | * reads because the drivers have to request the stop prior to reading 104 | * the final bytes (so the stop is issued automatically). 105 | * 106 | */ 107 | pt_state_t i2c_ctx_stop(i2c_ctx_t *c); 108 | 109 | /*! 110 | * \brief Detect attached I2C devices. 111 | * 112 | * \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN(). 113 | */ 114 | pt_state_t i2c_ctx_detect(i2c_ctx_t *c, i2c_device_map_t *map); 115 | 116 | /*! 117 | * \brief Write to an I2C register. 118 | * 119 | * \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN(). 120 | * 121 | * \todo API leaves space for 16-bit addresses and registers but this 122 | * is not implemented. 123 | */ 124 | pt_state_t i2c_ctx_setreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg, 125 | uint8_t val); 126 | 127 | /*! 128 | * \brief Read from an I2C register. 129 | * 130 | * \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN(). 131 | * 132 | * \todo API leaves space for 16-bit addresses and registers but this 133 | * is not implemented. 134 | */ 135 | pt_state_t i2c_ctx_getreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg, 136 | uint8_t *val); 137 | 138 | /*! 139 | * \brief Burst write to an I2C device. 140 | * 141 | * Primarily used for writes to memory devices or to initialize devices 142 | * whose registers can be written to continuously (without a stop/restart). 143 | * 144 | * \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN(). 145 | */ 146 | pt_state_t i2c_ctx_write(i2c_ctx_t *c, uint16_t addr, uint8_t *data, 147 | uint8_t len); 148 | 149 | /*! @} */ 150 | 151 | #endif // RF_I2C_CTX_H_ 152 | -------------------------------------------------------------------------------- /lib/librfm3/src/i2c_ctx.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of librfm3 (a utility library built on librfn and libopencm3) 3 | * 4 | * Copyright (C) 2014 Daniel Thompson 5 | * 6 | * This library is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library. If not, see . 18 | */ 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define D(x) { #x, x } 33 | static const regdump_desc_t i2c_sr1_desc[] = { { "I2C_SR1", 0 }, 34 | D(I2C_SR1_SMBALERT), 35 | D(I2C_SR1_TIMEOUT), 36 | D(I2C_SR1_PECERR), 37 | D(I2C_SR1_OVR), 38 | D(I2C_SR1_AF), 39 | D(I2C_SR1_ARLO), 40 | D(I2C_SR1_BERR), 41 | D(I2C_SR1_TxE), 42 | D(I2C_SR1_RxNE), 43 | D(I2C_SR1_STOPF), 44 | D(I2C_SR1_ADD10), 45 | D(I2C_SR1_BTF), 46 | D(I2C_SR1_ADDR), 47 | D(I2C_SR1_SB), 48 | { NULL, 0 } }; 49 | #undef D 50 | 51 | static bool i2c_ctx_is_timed_out(i2c_ctx_t *c) 52 | { 53 | if (cyclecmp32(time_now(), c->timeout) > 0) { 54 | if (c->verbose) { 55 | printf("I2C TRANSACTION TIMED OUT\n"); 56 | regdump(I2C_SR1(c->i2c), i2c_sr1_desc); 57 | } 58 | 59 | return true; 60 | } 61 | 62 | return false; 63 | } 64 | 65 | void i2c_ctx_init(i2c_ctx_t *c, uint32_t pi2c) 66 | { 67 | memset(c, 0, sizeof(*c)); 68 | 69 | c->i2c = pi2c; 70 | c->timeout = time_now() + 100000; 71 | } 72 | 73 | void i2c_ctx_reset(i2c_ctx_t *c) 74 | { 75 | /* TODO: Latest opencm3 has this code built in */ 76 | switch (c->i2c) { 77 | case I2C1: 78 | rcc_periph_reset_pulse(RST_I2C1); 79 | rcc_periph_reset_pulse(RST_I2C1); 80 | break; 81 | case I2C2: 82 | rcc_periph_reset_pulse(RST_I2C2); 83 | rcc_periph_reset_pulse(RST_I2C2); 84 | break; 85 | #ifdef STM32F4 86 | case I2C3: 87 | rcc_periph_reset_pulse(RST_I2C3); 88 | rcc_periph_reset_pulse(RST_I2C3); 89 | break; 90 | #endif 91 | } 92 | 93 | /* freq's numeric value ends up in MHz (i.e. in this case, 30) */ 94 | #ifdef STM32F4 95 | uint16_t freq = I2C_CR2_FREQ_30MHZ; 96 | #else 97 | uint16_t freq = 36; 98 | #endif 99 | 100 | /* CCR is the number of APB bus cycles in *half* an I2C bus 101 | * cycle. For Sm (100Khz) this ends up as: 102 | * freq * 1MHz / 2 * 100KHz 103 | * freq * 1000000 / 200000 104 | * freq * 5 105 | * 106 | * Similar trise is the number of APB bus cycles in the rise 107 | * time (plus 1). For Sm (1us) this ends up as: 108 | * freq * 1Mhz / (1/1us) + 1 109 | * freq * 1MHz / 1MHz + 1 110 | * freq + 1 111 | */ 112 | 113 | /* peripheral configuration */ 114 | i2c_peripheral_disable(c->i2c); 115 | i2c_set_clock_frequency(c->i2c, freq); 116 | i2c_set_ccr(c->i2c, freq * 5); 117 | i2c_set_trise(c->i2c, freq + 1); 118 | i2c_set_own_7bit_slave_address(c->i2c, 0x32); 119 | i2c_peripheral_enable(c->i2c); 120 | } 121 | 122 | pt_state_t i2c_ctx_start(i2c_ctx_t *c) 123 | { 124 | PT_BEGIN(&c->leaf); 125 | 126 | i2c_send_start(c->i2c); 127 | 128 | while (!i2c_ctx_is_timed_out(c) && 129 | !((I2C_SR1(c->i2c) & I2C_SR1_SB) & 130 | (I2C_SR2(c->i2c) & (I2C_SR2_MSL | I2C_SR2_BUSY)))) 131 | PT_YIELD(); 132 | 133 | if (!I2C_SR1(c->i2c) & I2C_SR1_SB) { 134 | i2c_ctx_reset(c); 135 | c->err = EIO; 136 | } 137 | 138 | PT_END(); 139 | } 140 | 141 | pt_state_t i2c_ctx_sendaddr(i2c_ctx_t *c, uint16_t addr, 142 | uint8_t bytes_to_read) 143 | { 144 | PT_BEGIN(&c->leaf); 145 | 146 | c->bytes_remaining = bytes_to_read; 147 | 148 | i2c_send_7bit_address(c->i2c, addr, !!bytes_to_read); 149 | 150 | while (!i2c_ctx_is_timed_out(c) && // bad 151 | !(I2C_SR1(c->i2c) & I2C_SR1_AF) && // bad 152 | !(I2C_SR1(c->i2c) & I2C_SR1_ADDR)) // good 153 | PT_YIELD(); 154 | 155 | if (!(I2C_SR1(c->i2c) & I2C_SR1_ADDR)) { 156 | i2c_ctx_reset(c); 157 | c->err = EIO; 158 | } 159 | 160 | /* If we are only reading one byte we must get ready to NACK the 161 | * final byte. 162 | */ 163 | if (c->bytes_remaining == 1) 164 | I2C_CR1(c->i2c) &= ~I2C_CR1_ACK; 165 | else if (c->bytes_remaining >= 2) 166 | I2C_CR1(c->i2c) |= I2C_CR1_ACK; 167 | 168 | /* Read sequence has side effect or clearing I2C_SR1_ADDR */ 169 | uint32_t reg32 __attribute__((unused)); 170 | reg32 = I2C_SR2(c->i2c); 171 | 172 | if (c->bytes_remaining == 1) 173 | i2c_send_stop(c->i2c); 174 | 175 | PT_END(); 176 | } 177 | 178 | pt_state_t i2c_ctx_senddata(i2c_ctx_t *c, uint8_t data) 179 | { 180 | PT_BEGIN(&c->leaf); 181 | 182 | i2c_send_data(c->i2c, data); 183 | 184 | while (!i2c_ctx_is_timed_out(c) && !(I2C_SR1(c->i2c) & I2C_SR1_BTF)) 185 | PT_YIELD(); 186 | 187 | if (!(I2C_SR1(c->i2c) & I2C_SR1_BTF)) { 188 | i2c_ctx_reset(c); 189 | c->err = EIO; 190 | } 191 | 192 | PT_END(); 193 | } 194 | 195 | pt_state_t i2c_ctx_getdata(i2c_ctx_t *c, uint8_t *data) 196 | { 197 | PT_BEGIN(&c->leaf); 198 | 199 | while (!i2c_ctx_is_timed_out(c) && !(I2C_SR1(c->i2c) & I2C_SR1_RxNE)) 200 | PT_YIELD(); 201 | 202 | if (!(I2C_SR1(c->i2c) & I2C_SR1_RxNE)) { 203 | i2c_ctx_reset(c); 204 | c->err = EIO; 205 | PT_EXIT(); 206 | } 207 | 208 | /* Need to NACK the final byte and STOP the transaction */ 209 | if (--c->bytes_remaining == 1) { 210 | I2C_CR1(c->i2c) &= ~I2C_CR1_ACK; 211 | i2c_send_stop(c->i2c); 212 | } 213 | 214 | *data = i2c_get_data(c->i2c); 215 | 216 | PT_END(); 217 | } 218 | 219 | pt_state_t i2c_ctx_stop(i2c_ctx_t *c) 220 | { 221 | PT_BEGIN(&c->leaf); 222 | 223 | while (!i2c_ctx_is_timed_out(c) && 224 | !(I2C_SR1(c->i2c) & (I2C_SR1_BTF | I2C_SR1_TxE))) 225 | PT_YIELD(); 226 | 227 | if (!(I2C_SR1(c->i2c) & (I2C_SR1_BTF | I2C_SR1_TxE))) { 228 | i2c_ctx_reset(c); 229 | c->err = EIO; 230 | PT_EXIT(); 231 | } 232 | 233 | i2c_send_stop(c->i2c); 234 | 235 | /* TODO: is it safe to just drop out of this */ 236 | 237 | PT_END(); 238 | } 239 | 240 | pt_state_t i2c_ctx_detect(i2c_ctx_t *c, i2c_device_map_t *map) 241 | { 242 | PT_BEGIN(&c->pt); 243 | 244 | memset(map, 0, sizeof(*map)); 245 | 246 | for (c->i = 0; c->i < 0x80; c->i++) { 247 | c->timeout = time_now() + 10000; 248 | c->err = 0; 249 | 250 | PT_SPAWN(&c->leaf, i2c_ctx_start(c)); 251 | if (c->err) 252 | continue; 253 | 254 | PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, c->i, I2C_WRITE)); 255 | if (c->err) 256 | continue; 257 | 258 | PT_SPAWN(&c->leaf, i2c_ctx_stop(c)); 259 | if (c->err) 260 | continue; 261 | 262 | map->devices[c->i / 16] |= 1 << (c->i % 16); 263 | } 264 | 265 | PT_END(); 266 | } 267 | 268 | pt_state_t i2c_ctx_setreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg, 269 | uint8_t val) 270 | { 271 | PT_BEGIN(&c->pt); 272 | 273 | PT_SPAWN(&c->leaf, i2c_ctx_start(c)); 274 | PT_EXIT_ON(c->err); 275 | 276 | PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_WRITE)); 277 | PT_EXIT_ON(c->err); 278 | 279 | PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, reg)); 280 | PT_EXIT_ON(c->err); 281 | 282 | PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, val)); 283 | PT_EXIT_ON(c->err); 284 | 285 | PT_SPAWN(&c->leaf, i2c_ctx_stop(c)); 286 | 287 | PT_END(); 288 | } 289 | 290 | pt_state_t i2c_ctx_getreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg, 291 | uint8_t *val) 292 | { 293 | PT_BEGIN(&c->pt); 294 | 295 | PT_SPAWN(&c->leaf, i2c_ctx_start(c)); 296 | PT_EXIT_ON(c->err); 297 | 298 | PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_WRITE)); 299 | PT_EXIT_ON(c->err); 300 | 301 | PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, reg)); 302 | PT_EXIT_ON(c->err); 303 | 304 | PT_SPAWN(&c->leaf, i2c_ctx_start(c)); 305 | PT_EXIT_ON(c->err); 306 | 307 | PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_READ)); 308 | PT_EXIT_ON(c->err); 309 | 310 | PT_SPAWN(&c->leaf, i2c_ctx_getdata(c, val)); 311 | 312 | /* For reads STOP is generated automatically by sendaddr and/or 313 | * getdata 314 | */ 315 | 316 | PT_END(); 317 | } 318 | 319 | pt_state_t i2c_ctx_write(i2c_ctx_t *c, uint16_t addr, uint8_t *data, 320 | uint8_t len) 321 | { 322 | PT_BEGIN(&c->pt); 323 | 324 | PT_SPAWN(&c->leaf, i2c_ctx_start(c)); 325 | PT_EXIT_ON(c->err); 326 | 327 | PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_WRITE)); 328 | PT_EXIT_ON(c->err); 329 | 330 | for (c->i = 0; c->i < len; c->i++) { 331 | PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, data[c->i])); 332 | PT_EXIT_ON(c->err); 333 | } 334 | 335 | PT_SPAWN(&c->leaf, i2c_ctx_stop(c)); 336 | 337 | PT_END(); 338 | } 339 | -------------------------------------------------------------------------------- /lib/librfn.rules.mk: -------------------------------------------------------------------------------- 1 | # 2 | # librfn.rules.mk 3 | # 4 | # This file is part of the i2c-star project. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | 12 | LIBRFN_DIR = ../lib/librfn 13 | 14 | OBJS += \ 15 | bitops.o \ 16 | fibre.o \ 17 | fibre_default.o \ 18 | list.o \ 19 | messageq.o \ 20 | regdump.o \ 21 | ringbuf.o \ 22 | time_libopencm3.o \ 23 | util.o \ 24 | console.o 25 | 26 | vpath %.c $(LIBRFN_DIR)/librfn 27 | vpath %.c $(LIBRFN_DIR)/librfn/libopencm3 28 | 29 | CPPFLAGS += -DNDEBUG 30 | CPPFLAGS += -I$(LIBRFN_DIR)/include 31 | -------------------------------------------------------------------------------- /lib/stm32.ld: -------------------------------------------------------------------------------- 1 | /* Define memory regions. */ 2 | MEMORY 3 | { 4 | rom (rx) : ORIGIN = 0x08002000, LENGTH = 120K 5 | ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K 6 | } 7 | 8 | /* Include the common ld script. */ 9 | INCLUDE libopencm3_stm32f1.ld 10 | -------------------------------------------------------------------------------- /pics/17-11-23 17-50-54 1132.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukas2511/swiitch-controller/10d88c1d742bea528fcd7fc5f73ee4378f5b9163/pics/17-11-23 17-50-54 1132.jpg -------------------------------------------------------------------------------- /pics/17-11-23 18-09-33 1133.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukas2511/swiitch-controller/10d88c1d742bea528fcd7fc5f73ee4378f5b9163/pics/17-11-23 18-09-33 1133.jpg -------------------------------------------------------------------------------- /pics/17-11-23 18-09-50 1134.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukas2511/swiitch-controller/10d88c1d742bea528fcd7fc5f73ee4378f5b9163/pics/17-11-23 18-09-50 1134.jpg -------------------------------------------------------------------------------- /pics/hardware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukas2511/swiitch-controller/10d88c1d742bea528fcd7fc5f73ee4378f5b9163/pics/hardware.png -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | BINARY = swiitch-controller 2 | 3 | CONTROLLER ?= wiiclassic 4 | 5 | OBJS += myconsole.o i2c.o 6 | OBJS += $(CONTROLLER).o 7 | OBJS += usb.o usb_cdc.o usb_dfu.o usb_hid.o 8 | CPPFLAGS += -I include -I usb/include 9 | 10 | vpath %.c usb 11 | vpath %.c controllers 12 | 13 | include ../lib/libopencm3.stm32f1xx.mk 14 | -------------------------------------------------------------------------------- /src/controllers/snescontroller.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "controller.h" 9 | #include 10 | #include 11 | #include "bitset.h" 12 | 13 | #define SNES_CLOCK GPIO2 14 | #define SNES_LATCH GPIO1 15 | #define SNES_DATA GPIO0 16 | 17 | // the snes controller is missing some important features 18 | // on the first run of snescontroller_poller it detects if some buttons 19 | // are pressed, and switches some features around 20 | // see the code further down for more details 21 | static bool first_run = 1; 22 | static bool fake_analog = 0; 23 | static bool use_zl_for_l = 0; 24 | static bool use_zr_for_r = 0; 25 | 26 | uint8_t controller_state[20] = {0, 0}; 27 | uint8_t controller_state_bytes = 2; 28 | 29 | static int snescontroller_poller(fibre_t *fibre) { 30 | PT_BEGIN_FIBRE(fibre); 31 | static uint32_t t; 32 | static uint32_t i; 33 | 34 | for(i=0; i 2 | #include 3 | 4 | #include "i2c.h" 5 | #include "controller.h" 6 | #include "bitset.h" 7 | #include "librfn/time.h" 8 | 9 | uint8_t controller_state[20] = {0, 0, 0, 0, 0, 0}; 10 | uint8_t controller_state_bytes = 6; 11 | 12 | int poll_controller(void) { 13 | if(i2c_read(0x52, controller_state, 6) == 6) { 14 | // RX is split over multiple bytes (why?!?) 15 | // 3 least significant bits always 1 (for now, easiest way to zero-out the virtual analog stick) 16 | switch_controller.data.RX = ((_GETBITS(controller_state[0], 6, 2) << 3 | _GETBITS(controller_state[1], 6, 2) << 1 | _GETBITS(controller_state[2], 7, 1)) << 3); 17 | switch_controller.data.RY = 263 - ((_GETBITS(controller_state[2], 0, 5) << 3) | 0x07); 18 | 19 | // 2 least significant bits always 1 (for now, easiest way to zero-out the virtual analog stick) 20 | switch_controller.data.LX = ((_GETBITS(controller_state[0], 0, 6) << 2) | 0x03) +1; 21 | switch_controller.data.LY = 255 - ((_GETBITS(controller_state[1], 0, 6) << 2) | 0x03); 22 | 23 | // byte 4 24 | switch_controller.data.R = !_GETBIT(controller_state[4], 1); 25 | switch_controller.data.plus = !_GETBIT(controller_state[4], 2); 26 | switch_controller.data.home = !_GETBIT(controller_state[4], 3); 27 | switch_controller.data.minus = !_GETBIT(controller_state[4], 4); 28 | switch_controller.data.L = !_GETBIT(controller_state[4], 5); 29 | switch_controller.data.dpad_down = !_GETBIT(controller_state[4], 6); 30 | switch_controller.data.dpad_right = !_GETBIT(controller_state[4], 7); 31 | 32 | // byte 5 33 | switch_controller.data.dpad_up = !_GETBIT(controller_state[5], 0); 34 | switch_controller.data.dpad_left = !_GETBIT(controller_state[5], 1); 35 | switch_controller.data.ZR = !_GETBIT(controller_state[5], 2); 36 | switch_controller.data.X = !_GETBIT(controller_state[5], 3); 37 | switch_controller.data.A = !_GETBIT(controller_state[5], 4); 38 | switch_controller.data.Y = !_GETBIT(controller_state[5], 5); 39 | switch_controller.data.B = !_GETBIT(controller_state[5], 6); 40 | switch_controller.data.ZL = !_GETBIT(controller_state[5], 7); 41 | 42 | uint32_t t = time_now() + 100; 43 | while(time_now() < t); 44 | 45 | i2c_write(0x52, (uint8_t[]){0x00}, 1); 46 | 47 | // the nintendo switch has no analog triggers, if it had those probably would be the values 48 | // TODO: maybe use slightly pressed trigger in combination with home button as the missing capture button? 49 | // LT = (_GETBITS(controller_state[2], 1, 2) << 3 | _GETBITS(controller_state[3], 0, 3)) << 3 50 | // RT = _GETBITS(controller_state[3], 3, 5) << 3 51 | return 1; 52 | } 53 | 54 | return 0; 55 | } 56 | 57 | void init_controller(void) { 58 | init_i2c(); 59 | 60 | uint32_t t = time_now(); 61 | 62 | // try to disable encryption 63 | i2c_write(0x52, (uint8_t[]){0xF0, 0x55}, 2); 64 | 65 | t += 100; 66 | while(time_now() < t); 67 | 68 | i2c_write(0x52, (uint8_t[]){0xFB, 0x00}, 2); 69 | 70 | t += 100; 71 | while(time_now() < t); 72 | } 73 | -------------------------------------------------------------------------------- /src/i2c.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include "include/myconsole.h" 10 | #include "include/i2c.h" 11 | 12 | void i2c_write(uint8_t addr, uint8_t* buf, int len) { 13 | i2c_ctx_t ctx; 14 | i2c_ctx_init(&ctx, I2C1); 15 | PT_CALL(&ctx.leaf, i2c_ctx_start(&ctx)); 16 | if(ctx.err) goto err; 17 | PT_CALL(&ctx.leaf, i2c_ctx_sendaddr(&ctx, addr, 0)); 18 | if(ctx.err) goto err; 19 | 20 | for(int i=0;iargc != 3) { 75 | fprintf(c->out, "Usage: i2c_read \n"); 76 | } else { 77 | uint8_t addr = parse_integer(c->argv[1]); 78 | int len = parse_integer(c->argv[2]); 79 | uint8_t *buf = malloc(len); 80 | fprintf(c->out, "Reading %d bytes from 0x%02x...\n", len, addr); 81 | 82 | i2c_read(addr, buf, len); 83 | for(int i=0;iout, "%02x ", buf[i]); 85 | } 86 | fprintf(c->out, "\r\n"); 87 | } 88 | 89 | return PT_EXITED; 90 | } 91 | static const console_cmd_t cmd_i2c_read = CONSOLE_CMD_VAR_INIT("i2c_read", console_i2c_read); 92 | 93 | static pt_state_t console_i2c_write(console_t *c) 94 | { 95 | if (c->argc < 3) { 96 | fprintf(c->out, "Usage: i2c_write \n"); 97 | } else { 98 | uint8_t addr = parse_integer(c->argv[1]); 99 | int len = c->argc - 2; 100 | uint8_t buf[len]; 101 | for(int i=0;iargv[i + 2]); 103 | } 104 | fprintf(c->out, "Sending %d bytes to 0x%02x...\n", len, addr); 105 | i2c_write(addr, buf, len); 106 | fprintf(c->out, "Done!\n"); 107 | } 108 | 109 | return PT_EXITED; 110 | } 111 | static const console_cmd_t cmd_i2c_write = CONSOLE_CMD_VAR_INIT("i2c_write", console_i2c_write); 112 | 113 | void init_i2c(void) { 114 | /* Enable GPIOB clock. */ 115 | rcc_periph_clock_enable(RCC_GPIOB); 116 | 117 | /* Enable clocks for I2C1 and AFIO. */ 118 | rcc_periph_clock_enable(RCC_I2C1); 119 | rcc_periph_clock_enable(RCC_AFIO); 120 | 121 | /* initialize the peripheral */ 122 | i2c_ctx_t ctx; 123 | i2c_ctx_init(&ctx, I2C1); 124 | i2c_ctx_reset(&ctx); 125 | 126 | /* GPIO for I2C1 */ 127 | gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO6 | GPIO7); 128 | 129 | /* Debug GPIO */ 130 | gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO5); 131 | 132 | console_register(&cmd_i2c_read); 133 | console_register(&cmd_i2c_write); 134 | } 135 | -------------------------------------------------------------------------------- /src/include/bitset.h: -------------------------------------------------------------------------------- 1 | #ifndef __BITSET_H__ 2 | #define __BITSET_H__ 3 | 4 | #ifndef _UV 5 | #define _UV(x) (1 << (x)) 6 | #endif 7 | 8 | #ifndef _SETBIT 9 | #define _SETBIT(x, y) ((x) |= _UV(y)) 10 | #endif 11 | 12 | #ifndef _CLRBIT 13 | #define _CLRBIT(x, y) ((x) &= ~_UV(y)) 14 | #endif 15 | 16 | #ifndef _GETBITS 17 | #define _GETBITS(c, start, len) (((c >> start << (8-len)) & 0xFF) >> (8-len)) 18 | #endif 19 | 20 | #ifndef _GETBIT 21 | #define _GETBIT(c, n) _GETBITS(c, n, 1) 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/include/controller.h: -------------------------------------------------------------------------------- 1 | union SwitchController { 2 | struct { 3 | // byte1 4 | unsigned int Y : 1; 5 | unsigned int B : 1; 6 | unsigned int A : 1; 7 | unsigned int X : 1; 8 | unsigned int L : 1; 9 | unsigned int R : 1; 10 | unsigned int ZL : 1; 11 | unsigned int ZR : 1; 12 | // byte2 13 | unsigned int minus : 1; 14 | unsigned int plus : 1; 15 | unsigned int lclick : 1; 16 | unsigned int rclick : 1; 17 | unsigned int home : 1; 18 | unsigned int capture : 1; 19 | unsigned int : 2; // unused bits 20 | // byte3 21 | unsigned int HAT : 4; 22 | unsigned int : 4; // unused bits 23 | // byte4 24 | uint8_t LX : 8; 25 | // byte5 26 | uint8_t LY : 8; 27 | // byte6 28 | uint8_t RX : 8; 29 | // byte7 30 | uint8_t RY : 8; 31 | // byte8 32 | unsigned int : 8; // vendor specific 33 | 34 | // custom/temp data 35 | unsigned int : 4; 36 | unsigned int dpad_left : 1; 37 | unsigned int dpad_right : 1; 38 | unsigned int dpad_down : 1; 39 | unsigned int dpad_up : 1; 40 | } data; 41 | uint8_t bytes[9]; 42 | }; 43 | extern union SwitchController switch_controller; 44 | 45 | extern uint8_t controller_state[20]; 46 | extern uint8_t controller_state_bytes; 47 | void init_controller(void); 48 | int poll_controller(void); 49 | -------------------------------------------------------------------------------- /src/include/i2c.h: -------------------------------------------------------------------------------- 1 | void i2c_write(uint8_t addr, uint8_t* buf, int len); 2 | int i2c_read(uint8_t addr, uint8_t* buf, int len); 3 | 4 | void init_i2c(void); 5 | -------------------------------------------------------------------------------- /src/include/myconsole.h: -------------------------------------------------------------------------------- 1 | extern console_t console; 2 | void init_myconsole(void); 3 | -------------------------------------------------------------------------------- /src/myconsole.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "myconsole.h" 5 | #include "controller.h" 6 | #include 7 | #include 8 | #include "usb_cdc.h" 9 | 10 | #define CLEAR_SCREEN fprintf(console.out, "\e[1;1H\e[2J"); 11 | #define BOLD fprintf(console.out, "\033[1m"); 12 | #define RESET fprintf(console.out, "\033[0m"); 13 | #define RED fprintf(console.out, "\033[31;1m"); 14 | #define MOVE_CURSOR(l, c) fprintf(console.out, "\033[%d;%dH", l, c); 15 | #define GREEN fprintf(console.out, "\033[32;1m"); 16 | 17 | console_t console; 18 | 19 | void console_hwinit(console_t *c) { 20 | console_silent(c); 21 | } 22 | 23 | // uptime 24 | static pt_state_t console_uptime(console_t *c) { 25 | if (c->argc != 1) 26 | fprintf(c->out, "Usage: uptime\n"); 27 | else 28 | fprintf(c->out, "I've been running for %ld ms\n", time_now() / 1000l); 29 | 30 | return PT_EXITED; 31 | } 32 | static const console_cmd_t cmd_uptime = CONSOLE_CMD_VAR_INIT("uptime", console_uptime); 33 | 34 | // reset 35 | static pt_state_t console_reset(console_t *c) { 36 | (void)c; 37 | scb_reset_system(); 38 | return PT_EXITED; 39 | } 40 | static const console_cmd_t cmd_reset = CONSOLE_CMD_VAR_INIT("reset", console_reset); 41 | 42 | // bootloader 43 | static pt_state_t console_bootloader(console_t *c) { 44 | (void)c; 45 | char *const marker = (char *)0x20004800; 46 | const char key[] = "remain-in-loader"; 47 | memcpy(marker, key, sizeof(key)); 48 | scb_reset_system(); 49 | return PT_EXITED; 50 | } 51 | static const console_cmd_t cmd_bootloader = CONSOLE_CMD_VAR_INIT("bootloader", console_bootloader); 52 | 53 | // watcher 54 | static int print_loop_countdown = 0; 55 | static void print_controller_state(void) { 56 | MOVE_CURSOR(2, 2) 57 | fprintf(console.out, "Uptime: %ldms", time_now() / 1000); 58 | 59 | MOVE_CURSOR(4, 2) 60 | BOLD 61 | fprintf(console.out, "Simulated Output"); 62 | 63 | // left analog stick 64 | MOVE_CURSOR(2+7, 4) 65 | fprintf(console.out, "LX: %d ", switch_controller.data.LX); 66 | MOVE_CURSOR(2+8, 4) 67 | fprintf(console.out, "LY: %d ", switch_controller.data.LY); 68 | 69 | // buttons dpad 70 | MOVE_CURSOR(7+7, 6) 71 | if(switch_controller.data.dpad_up) GREEN else RED 72 | fprintf(console.out, "u"); 73 | MOVE_CURSOR(7+8, 4) 74 | if(switch_controller.data.dpad_left) GREEN else RED 75 | fprintf(console.out, "l"); 76 | MOVE_CURSOR(7+8, 8) 77 | if(switch_controller.data.dpad_right) GREEN else RED 78 | fprintf(console.out, "r"); 79 | MOVE_CURSOR(7+9, 6) 80 | if(switch_controller.data.dpad_down) GREEN else RED 81 | fprintf(console.out, "d"); 82 | 83 | // buttons Z/ZL/R/ZR 84 | MOVE_CURSOR(6, 3) 85 | if(switch_controller.data.ZL) GREEN else RED 86 | fprintf(console.out, "ZL"); 87 | MOVE_CURSOR(6, 6) 88 | if(switch_controller.data.L) GREEN else RED 89 | fprintf(console.out, "L"); 90 | MOVE_CURSOR(6, 36) 91 | if(switch_controller.data.R) GREEN else RED 92 | fprintf(console.out, "R"); 93 | MOVE_CURSOR(6, 39) 94 | if(switch_controller.data.ZR) GREEN else RED 95 | fprintf(console.out, "ZR"); 96 | 97 | // buttons +,H,-,C 98 | MOVE_CURSOR(11, 19) 99 | if(switch_controller.data.minus) GREEN else RED 100 | fprintf(console.out, "-"); 101 | MOVE_CURSOR(11, 21) 102 | if(switch_controller.data.home) GREEN else RED 103 | fprintf(console.out, "H"); 104 | MOVE_CURSOR(12, 21) 105 | if(switch_controller.data.capture) GREEN else RED 106 | fprintf(console.out, "C"); 107 | MOVE_CURSOR(11, 23) 108 | if(switch_controller.data.plus) GREEN else RED 109 | fprintf(console.out, "+"); 110 | 111 | // buttons A,B,X,Y 112 | MOVE_CURSOR(2+7, 30+6) 113 | if(switch_controller.data.X) GREEN else RED 114 | fprintf(console.out, "X"); 115 | MOVE_CURSOR(2+8, 30+4) 116 | if(switch_controller.data.Y) GREEN else RED 117 | fprintf(console.out, "Y"); 118 | MOVE_CURSOR(2+8, 30+8) 119 | if(switch_controller.data.A) GREEN else RED 120 | fprintf(console.out, "A"); 121 | MOVE_CURSOR(2+9, 30+6) 122 | if(switch_controller.data.B) GREEN else RED 123 | fprintf(console.out, "B"); 124 | RESET 125 | 126 | BOLD 127 | // right analog stick 128 | MOVE_CURSOR(15, 30+3) 129 | fprintf(console.out, "RX: %d ", switch_controller.data.RX); 130 | MOVE_CURSOR(16, 30+3) 131 | fprintf(console.out, "RY: %d ", switch_controller.data.RY); 132 | 133 | MOVE_CURSOR(18, 2) 134 | fprintf(console.out, "Raw Input: "); 135 | for(uint8_t i=0; iargc==2 && strncmp(c->argv[1], "watch", 5)==0) { 180 | watch_controller_state = 1; 181 | } else { 182 | watch_controller_state = 0; 183 | CLEAR_SCREEN 184 | print_controller_state(); 185 | } 186 | return PT_EXITED; 187 | } 188 | static const console_cmd_t cmd_state = CONSOLE_CMD_VAR_INIT("state", console_state); 189 | 190 | 191 | void init_myconsole(void) { 192 | console_init(&console, stdout); 193 | console_register(&cmd_uptime); 194 | console_register(&cmd_bootloader); 195 | console_register(&cmd_reset); 196 | console_register(&cmd_state); 197 | fibre_run(&watcher_loop_task); 198 | } 199 | -------------------------------------------------------------------------------- /src/swiitch-controller.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include "include/myconsole.h" 10 | #include 11 | 12 | #include "include/controller.h" 13 | #include "usb.h" 14 | #include "usb_cdc.h" 15 | #include "usb_hid.h" 16 | #include "usb_dfu.h" 17 | 18 | union SwitchController switch_controller; 19 | 20 | static int main_loop(fibre_t *fibre) { 21 | PT_BEGIN_FIBRE(fibre); 22 | static uint32_t t; 23 | 24 | for(uint32_t i=0; i 2 | 3 | #define MAX_USB_IFACES 10 4 | #define MAX_USB_ENDPOINTS 10 5 | 6 | extern usbd_device *usbd_dev; 7 | extern int usb_running; 8 | void init_usb(void); 9 | void usb_write(uint8_t *bytes, uint32_t len); 10 | 11 | uint8_t register_usb_interface_descriptor(struct usb_interface_descriptor *iface_descr); 12 | void register_usb_endpoint(uint8_t addr, uint8_t type, uint16_t max_size, usbd_endpoint_callback callback); 13 | void register_usb_callback(uint8_t type, uint8_t type_mask, usbd_control_callback callback); 14 | -------------------------------------------------------------------------------- /src/usb/include/usb_cdc.h: -------------------------------------------------------------------------------- 1 | void init_usb_cdc(void); 2 | -------------------------------------------------------------------------------- /src/usb/include/usb_dfu.h: -------------------------------------------------------------------------------- 1 | void init_usb_dfu(void); 2 | -------------------------------------------------------------------------------- /src/usb/include/usb_hid.h: -------------------------------------------------------------------------------- 1 | void init_usb_hid(void); 2 | void usb_hid_write(uint8_t *bytes, uint32_t len); 3 | -------------------------------------------------------------------------------- /src/usb/usb.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "usb.h" 11 | #include "usb_cdc.h" 12 | 13 | #include 14 | #include 15 | 16 | usbd_device *usbd_dev; 17 | int usb_running = 0; 18 | 19 | struct registered_endpoint { 20 | uint8_t addr; 21 | uint8_t type; 22 | uint16_t max_size; 23 | usbd_endpoint_callback callback; 24 | }; 25 | static struct registered_endpoint registered_endpoints[MAX_USB_ENDPOINTS]; 26 | static int num_registered_endpoints = 0; 27 | 28 | struct registered_callback { 29 | uint8_t type; 30 | uint8_t type_mask; 31 | usbd_control_callback callback; 32 | }; 33 | static struct registered_callback registered_callbacks[MAX_USB_ENDPOINTS]; 34 | static int num_registered_callbacks; 35 | 36 | const struct usb_device_descriptor dev_descr = { 37 | .bLength = USB_DT_DEVICE_SIZE, 38 | .bDescriptorType = USB_DT_DEVICE, 39 | .bcdUSB = 0x0200, 40 | .bDeviceClass = 0, 41 | .bDeviceSubClass = 0, 42 | .bDeviceProtocol = 0, 43 | .bMaxPacketSize0 = 64, 44 | .idVendor = 0x0f0d, 45 | .idProduct = 0x00c1, 46 | .bcdDevice = 0x0572, 47 | .iManufacturer = 1, 48 | .iProduct = 2, 49 | .iSerialNumber = 0, 50 | .bNumConfigurations = 1, 51 | }; 52 | 53 | struct usb_interface usb_ifaces[MAX_USB_IFACES] = {{}, {}, {}, {}}; 54 | 55 | struct usb_config_descriptor usb_config = { 56 | .bLength = USB_DT_CONFIGURATION_SIZE, 57 | .bDescriptorType = USB_DT_CONFIGURATION, 58 | .wTotalLength = 0, 59 | .bNumInterfaces = 0, 60 | .bConfigurationValue = 1, 61 | .iConfiguration = 0, 62 | .bmAttributes = 0x80, 63 | .bMaxPower = 0xFA, 64 | 65 | .interface = usb_ifaces, 66 | }; 67 | 68 | static const char *usb_strings[] = { 69 | "LUKAS2511", 70 | "FRICKELPAD S", 71 | "", 72 | }; 73 | 74 | /* Buffer to be used for control requests. */ 75 | uint8_t usbd_control_buffer[128]; 76 | 77 | static void set_usb_config(usbd_device *dev, uint16_t wValue) 78 | { 79 | (void)wValue; 80 | (void)dev; 81 | 82 | for(int i=0; ibInterfaceNumber = usb_config.bNumInterfaces; 106 | usb_ifaces[usb_config.bNumInterfaces].num_altsetting = 1; 107 | usb_ifaces[usb_config.bNumInterfaces].altsetting = iface_descr; 108 | return usb_config.bNumInterfaces++; 109 | } 110 | return -1; 111 | } 112 | 113 | void register_usb_endpoint(uint8_t addr, uint8_t type, uint16_t max_size, usbd_endpoint_callback callback) { 114 | const struct registered_endpoint new_endpoint = { 115 | .addr = addr, 116 | .type = type, 117 | .max_size = max_size, 118 | .callback = callback, 119 | }; 120 | registered_endpoints[num_registered_endpoints++] = new_endpoint; 121 | } 122 | 123 | void register_usb_callback(uint8_t type, uint8_t type_mask, usbd_control_callback callback) { 124 | const struct registered_callback new_callback = { 125 | .type = type, 126 | .type_mask = type_mask, 127 | .callback = callback, 128 | }; 129 | registered_callbacks[num_registered_callbacks++] = new_callback; 130 | } 131 | 132 | void init_usb(void) { 133 | rcc_periph_clock_enable(RCC_GPIOA); 134 | /* 135 | * This is a somewhat common cheap hack to trigger device re-enumeration 136 | * on startup. Assuming a fixed external pullup on D+, (For USB-FS) 137 | * setting the pin to output, and driving it explicitly low effectively 138 | * "removes" the pullup. The subsequent USB init will "take over" the 139 | * pin, and it will appear as a proper pullup to the host. 140 | * The magic delay is somewhat arbitrary, no guarantees on USBIF 141 | * compliance here, but "it works" in most places. 142 | */ 143 | gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ, 144 | GPIO_CNF_OUTPUT_PUSHPULL, GPIO12); 145 | gpio_clear(GPIOA, GPIO12); 146 | for (unsigned i = 0; i < 800000; i++) { 147 | __asm__("nop"); 148 | } 149 | 150 | usbd_dev = usbd_init(&st_usbfs_v1_usb_driver, &dev_descr, &usb_config, usb_strings, 3, usbd_control_buffer, sizeof(usbd_control_buffer)); 151 | usbd_register_set_config_callback(usbd_dev, set_usb_config); 152 | 153 | fibre_run(&usb_task); 154 | } 155 | -------------------------------------------------------------------------------- /src/usb/usb_cdc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "usb.h" 10 | #include "usb_cdc.h" 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "myconsole.h" 20 | int _write(int file, char *ptr, int len); 21 | 22 | static uint8_t outbuf[1024]; 23 | static ringbuf_t outring = RINGBUF_VAR_INIT(outbuf, sizeof(outbuf)); 24 | 25 | static const struct usb_endpoint_descriptor cdc_comm_endp[] = {{ 26 | .bLength = USB_DT_ENDPOINT_SIZE, 27 | .bDescriptorType = USB_DT_ENDPOINT, 28 | .bEndpointAddress = 0x83, 29 | .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, 30 | .wMaxPacketSize = 16, 31 | .bInterval = 255, 32 | }}; 33 | 34 | static const struct usb_endpoint_descriptor cdc_data_endp[] = {{ 35 | .bLength = USB_DT_ENDPOINT_SIZE, 36 | .bDescriptorType = USB_DT_ENDPOINT, 37 | .bEndpointAddress = 0x01, 38 | .bmAttributes = USB_ENDPOINT_ATTR_BULK, 39 | .wMaxPacketSize = 64, 40 | .bInterval = 1, 41 | }, { 42 | .bLength = USB_DT_ENDPOINT_SIZE, 43 | .bDescriptorType = USB_DT_ENDPOINT, 44 | .bEndpointAddress = 0x82, 45 | .bmAttributes = USB_ENDPOINT_ATTR_BULK, 46 | .wMaxPacketSize = 64, 47 | .bInterval = 1, 48 | }}; 49 | 50 | static struct { 51 | struct usb_cdc_header_descriptor header; 52 | struct usb_cdc_call_management_descriptor call_mgmt; 53 | struct usb_cdc_acm_descriptor acm; 54 | struct usb_cdc_union_descriptor cdc_union; 55 | } __attribute__((packed)) cdcacm_functional_descriptors = { 56 | .header = { 57 | .bFunctionLength = sizeof(struct usb_cdc_header_descriptor), 58 | .bDescriptorType = CS_INTERFACE, 59 | .bDescriptorSubtype = USB_CDC_TYPE_HEADER, 60 | .bcdCDC = 0x0110, 61 | }, 62 | .call_mgmt = { 63 | .bFunctionLength = 64 | sizeof(struct usb_cdc_call_management_descriptor), 65 | .bDescriptorType = CS_INTERFACE, 66 | .bDescriptorSubtype = USB_CDC_TYPE_CALL_MANAGEMENT, 67 | .bmCapabilities = 0, 68 | .bDataInterface = 0, 69 | }, 70 | .acm = { 71 | .bFunctionLength = sizeof(struct usb_cdc_acm_descriptor), 72 | .bDescriptorType = CS_INTERFACE, 73 | .bDescriptorSubtype = USB_CDC_TYPE_ACM, 74 | .bmCapabilities = 0, 75 | }, 76 | .cdc_union = { 77 | .bFunctionLength = sizeof(struct usb_cdc_union_descriptor), 78 | .bDescriptorType = CS_INTERFACE, 79 | .bDescriptorSubtype = USB_CDC_TYPE_UNION, 80 | .bControlInterface = 0, 81 | .bSubordinateInterface0 = 0, 82 | } 83 | }; 84 | 85 | static struct usb_interface_descriptor cdc_comm_iface = { 86 | .bLength = USB_DT_INTERFACE_SIZE, 87 | .bDescriptorType = USB_DT_INTERFACE, 88 | .bInterfaceNumber = 0, 89 | .bAlternateSetting = 0, 90 | .bNumEndpoints = 1, 91 | .bInterfaceClass = USB_CLASS_CDC, 92 | .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, 93 | .bInterfaceProtocol = USB_CDC_PROTOCOL_AT, 94 | .iInterface = 0, 95 | 96 | .endpoint = cdc_comm_endp, 97 | 98 | .extra = &cdcacm_functional_descriptors, 99 | .extralen = sizeof(cdcacm_functional_descriptors) 100 | }; 101 | 102 | static struct usb_interface_descriptor cdc_data_iface = { 103 | .bLength = USB_DT_INTERFACE_SIZE, 104 | .bDescriptorType = USB_DT_INTERFACE, 105 | .bInterfaceNumber = 0, 106 | .bAlternateSetting = 0, 107 | .bNumEndpoints = 2, 108 | .bInterfaceClass = USB_CLASS_DATA, 109 | .bInterfaceSubClass = 0, 110 | .bInterfaceProtocol = 0, 111 | .iInterface = 0, 112 | 113 | .endpoint = cdc_data_endp, 114 | }; 115 | 116 | static int cdcacm_control_request(usbd_device *dev, struct usb_setup_data *req, uint8_t **buf, 117 | uint16_t *len, void (**complete)(usbd_device *dev, struct usb_setup_data *req)) 118 | { 119 | (void)complete; 120 | (void)buf; 121 | (void)dev; 122 | 123 | switch(req->bRequest) { 124 | case USB_CDC_REQ_SET_CONTROL_LINE_STATE: { 125 | /* 126 | * This Linux cdc_acm driver requires this to be implemented 127 | * even though it's optional in the CDC spec, and we don't 128 | * advertise it in the ACM functional descriptor. 129 | */ 130 | char local_buf[10]; 131 | struct usb_cdc_notification *notif = (void *)local_buf; 132 | 133 | /* We echo signals back to host as notification. */ 134 | notif->bmRequestType = 0xA1; 135 | notif->bNotification = USB_CDC_NOTIFY_SERIAL_STATE; 136 | notif->wValue = 0; 137 | notif->wIndex = 0; 138 | notif->wLength = 2; 139 | local_buf[8] = req->wValue & 3; 140 | local_buf[9] = 0; 141 | // usbd_ep_write_packet(0x83, buf, 10); 142 | return 1; 143 | } 144 | case USB_CDC_REQ_SET_LINE_CODING: 145 | if(*len < sizeof(struct usb_cdc_line_coding)) 146 | return 0; 147 | 148 | return 1; 149 | } 150 | return 0; 151 | } 152 | 153 | struct output_task { 154 | fibre_t fibre; 155 | uint8_t ch; 156 | }; 157 | static int output_fibre(fibre_t *fibre) 158 | { 159 | struct output_task *t = containerof(fibre, struct output_task, fibre); 160 | PT_BEGIN_FIBRE(fibre); 161 | 162 | /* Initial timeout to allow things to settle. This proved necessary 163 | * to make coldboot connections work on STM32F4-Discovery (issue was 164 | * not deeply investigated but likely to be due to trying to send 165 | * serial output whilst the usb thread is going through the hotplug 166 | * sequence). 167 | */ 168 | if (time64_now() < 2000000) 169 | PT_WAIT_UNTIL(fibre_timeout(2000000)); 170 | 171 | while (true) { 172 | int ch = ringbuf_get(&outring); 173 | if (-1 == ch) 174 | PT_EXIT(); 175 | 176 | t->ch = ch; 177 | 178 | while (!usbd_ep_write_packet(usbd_dev, 0x82, &t->ch, 1)) 179 | PT_YIELD(); 180 | 181 | } 182 | 183 | PT_END(); 184 | } 185 | static struct output_task output_task = { 186 | .fibre = FIBRE_VAR_INIT(output_fibre) 187 | }; 188 | 189 | static void cdcacm_data_rx_cb(usbd_device *dev, uint8_t ep) 190 | { 191 | (void)ep; 192 | 193 | char buf[64]; 194 | int len = usbd_ep_read_packet(dev, 0x01, buf, 64); 195 | 196 | for (int i = 0; i < len; i++) { 197 | (void)ringbuf_put(&outring, buf[i]); 198 | if (buf[i] == '\r') { 199 | (void)ringbuf_put(&outring, '\n'); 200 | console_putchar(&console, '\n'); 201 | } else { 202 | console_putchar(&console, buf[i]); 203 | } 204 | } 205 | fibre_run_atomic(&output_task.fibre); 206 | } 207 | 208 | int _write(int fd, char *ptr, int len) 209 | { 210 | if (fd == 1 || fd == 2) { 211 | for (int i=0; i 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "usb.h" 10 | #include "usb_dfu.h" 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | const struct usb_dfu_descriptor dfu_function = { 19 | .bLength = sizeof(struct usb_dfu_descriptor), 20 | .bDescriptorType = DFU_FUNCTIONAL, 21 | .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH, 22 | .wDetachTimeout = 255, 23 | .wTransferSize = 1024, 24 | .bcdDFUVersion = 0x011A, 25 | }; 26 | 27 | static struct usb_interface_descriptor dfu_iface = { 28 | .bLength = USB_DT_INTERFACE_SIZE, 29 | .bDescriptorType = USB_DT_INTERFACE, 30 | .bInterfaceNumber = 0, 31 | .bAlternateSetting = 0, 32 | .bNumEndpoints = 0, 33 | .bInterfaceClass = 0xFE, 34 | .bInterfaceSubClass = 1, 35 | .bInterfaceProtocol = 1, 36 | .iInterface = 0, 37 | 38 | .extra = &dfu_function, 39 | .extralen = sizeof(dfu_function), 40 | }; 41 | 42 | static int dfu_control_request(usbd_device *dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, 43 | void (**complete)(usbd_device *, struct usb_setup_data *)) 44 | { 45 | (void)complete; 46 | (void)buf; 47 | (void)len; 48 | (void)dev; 49 | 50 | if ((req->bmRequestType != DFU_FUNCTIONAL) || (req->bRequest != DFU_DETACH)) 51 | return 0; /* Only accept class request. */ 52 | 53 | printf("Resetting!\n"); 54 | 55 | char *const marker = (char *)0x20004800; 56 | const char key[] = "remain-in-loader"; 57 | memcpy(marker, key, sizeof(key)); 58 | scb_reset_system(); 59 | 60 | return 1; 61 | } 62 | 63 | void init_usb_dfu(void) { 64 | register_usb_interface_descriptor(&dfu_iface); 65 | register_usb_callback(USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE, USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, dfu_control_request); 66 | } 67 | -------------------------------------------------------------------------------- /src/usb/usb_hid.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "usb.h" 11 | #include "usb_hid.h" 12 | 13 | #include 14 | #include 15 | 16 | static const uint8_t hid_report_descriptor[] = { 17 | 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ 18 | 0x09, 0x05, /* USAGE (Gamepad) */ 19 | 0xa1, 0x01, /* COLLECTION (Application) */ 20 | 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ 21 | 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ 22 | 0x35, 0x00, /* PHYSICAL_MINIMUM (0) */ 23 | 0x45, 0x01, /* PHYSICAL_MAXIMUM (1) */ 24 | 0x75, 0x01, /* REPORT_SIZE (1) */ 25 | 0x95, 0x0e, /* REPORT_COUNT (14) */ 26 | 0x05, 0x09, /* USAGE_PAGE (Buttons) */ 27 | 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */ 28 | 0x29, 0x0e, /* USAGE_MAXIMUM (Button 14) */ 29 | 0x81, 0x02, /* INPUT (Data,Var,Abs) */ 30 | 0x95, 0x02, /* REPORT_COUNT (2) */ 31 | 0x81, 0x01, /* INPUT (Data,Var,Abs) */ 32 | 0x05, 0x01, /* USAGE_PAGE (Generic Desktop Ctr) */ 33 | 0x25, 0x07, /* LOGICAL_MAXIMUM (7) */ 34 | 0x46, 0x3b, 0x01, /* PHYSICAL_MAXIMUM (315) */ 35 | 0x75, 0x04, /* REPORT_SIZE (4) */ 36 | 0x95, 0x01, /* REPORT_COUNT (1) */ 37 | 0x65, 0x14, /* UNIT (20) */ 38 | 0x09, 0x39, /* USAGE (Hat Switch) */ 39 | 0x81, 0x42, /* INPUT (Data,Var,Abs) */ 40 | 0x65, 0x00, /* UNIT (0) */ 41 | 0x95, 0x01, /* REPORT_COUNT (1) */ 42 | 0x81, 0x01, /* INPUT (Data,Var,Abs) */ 43 | 0x26, 0xff, 0x00, /* LOGICAL_MAXIMUM (255) */ 44 | 0x46, 0xff, 0x00, /* PHYSICAL_MAXIMUM (255) */ 45 | 0x09, 0x30, /* USAGE (Direction-X) */ 46 | 0x09, 0x31, /* USAGE (Direction-Y) */ 47 | 0x09, 0x32, /* USAGE (Direction-Z) */ 48 | 0x09, 0x35, /* USAGE (Rotate-Z) */ 49 | 0x75, 0x08, /* REPORT_SIZE (8) */ 50 | 0x95, 0x04, /* REPORT_COUNT (4) */ 51 | 0x81, 0x02, /* INPUT (Data,Var,Abs) */ 52 | 0x75, 0x08, /* REPORT_SIZE (8) */ 53 | 0x95, 0x01, /* REPORT_COUNT (1) */ 54 | 0x81, 0x01, /* INPUT (Data,Var,Abs) */ 55 | 0xc0, /* END_COLLECTION */ 56 | }; 57 | 58 | static const struct { 59 | struct usb_hid_descriptor hid_descriptor; 60 | struct { 61 | uint8_t bReportDescriptorType; 62 | uint16_t wDescriptorLength; 63 | } __attribute__((packed)) hid_report; 64 | } __attribute__((packed)) hid_function = { 65 | .hid_descriptor = { 66 | .bLength = sizeof(hid_function), 67 | .bDescriptorType = USB_DT_HID, 68 | .bcdHID = 0x0111, 69 | .bCountryCode = 0, 70 | .bNumDescriptors = 1, 71 | }, 72 | .hid_report = { 73 | .bReportDescriptorType = USB_DT_REPORT, 74 | .wDescriptorLength = sizeof(hid_report_descriptor), 75 | } 76 | }; 77 | 78 | const struct usb_endpoint_descriptor hid_endpoint[] = {{ 79 | .bLength = USB_DT_ENDPOINT_SIZE, 80 | .bDescriptorType = USB_DT_ENDPOINT, 81 | .bEndpointAddress = 0x02, 82 | .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, 83 | .wMaxPacketSize = 64, 84 | .bInterval = 5, 85 | }, { 86 | .bLength = USB_DT_ENDPOINT_SIZE, 87 | .bDescriptorType = USB_DT_ENDPOINT, 88 | .bEndpointAddress = 0x81, 89 | .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, 90 | .wMaxPacketSize = 64, 91 | .bInterval = 5, 92 | }}; 93 | 94 | static struct usb_interface_descriptor hid_iface = { 95 | .bLength = USB_DT_INTERFACE_SIZE, 96 | .bDescriptorType = USB_DT_INTERFACE, 97 | .bInterfaceNumber = 0, 98 | .bAlternateSetting = 0, 99 | .bNumEndpoints = 2, 100 | .bInterfaceClass = 3, // HID 101 | .bInterfaceSubClass = 0, 102 | .bInterfaceProtocol = 0, 103 | .iInterface = 0, 104 | 105 | .endpoint = hid_endpoint, 106 | 107 | .extra = &hid_function, 108 | .extralen = sizeof(hid_function), 109 | }; 110 | 111 | static int hid_control_request(usbd_device *dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, 112 | void (**complete)(usbd_device *, struct usb_setup_data *)) 113 | { 114 | (void)complete; 115 | (void)dev; 116 | 117 | if((req->bmRequestType != 0x81) || 118 | (req->bRequest != USB_REQ_GET_DESCRIPTOR) || 119 | (req->wValue != 0x2200)) 120 | return 0; 121 | 122 | /* Handle the HID report descriptor. */ 123 | *buf = (uint8_t *)hid_report_descriptor; 124 | *len = sizeof(hid_report_descriptor); 125 | 126 | return 1; 127 | } 128 | 129 | void init_usb_hid(void) { 130 | register_usb_interface_descriptor(&hid_iface); 131 | register_usb_endpoint(0x81, USB_ENDPOINT_ATTR_INTERRUPT, 8, NULL); 132 | register_usb_callback(USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_INTERFACE, USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, hid_control_request); 133 | } 134 | 135 | void usb_hid_write(uint8_t *bytes, uint32_t len) { 136 | usbd_ep_write_packet(usbd_dev, 0x81, bytes, len); 137 | } 138 | -------------------------------------------------------------------------------- /stuff/bootloader/README.md: -------------------------------------------------------------------------------- 1 | # Bootloader 2 | 3 | Here you can find a binary release of the bootloader I'm using on my blue-pill. 4 | 5 | ## Code 6 | 7 | I'm using Daniel Thompson's modified version of the example libopencm3 bootloader, which can be found in the [i2c-star repository](https://github.com/daniel-thompson/i2c-star/tree/master/src/bootloader). 8 | 9 | I changed VID/PID to match the controller so dfu-util can automatically reprogram the device without a manual reset: 10 | 11 | ```diff 12 | diff --git a/src/bootloader/usbdfu.c b/src/bootloader/usbdfu.c 13 | index ba6ce07..97be28a 100644 14 | --- a/src/bootloader/usbdfu.c 15 | +++ b/src/bootloader/usbdfu.c 16 | @@ -54,8 +54,8 @@ const struct usb_device_descriptor dev = { 17 | .bDeviceSubClass = 0, 18 | .bDeviceProtocol = 0, 19 | .bMaxPacketSize0 = 64, 20 | - .idVendor = 0x0483, 21 | - .idProduct = 0xDF11, 22 | + .idVendor = 0x0f0d, 23 | + .idProduct = 0x00c1, 24 | .bcdDevice = 0x0200, 25 | .iManufacturer = 1, 26 | .iProduct = 2, 27 | ``` 28 | 29 | ## Other bootloaders / No bootloader 30 | 31 | Basically any DFU bootloader should work, but you may need to add the special `remain-in-loader` flag 32 | that Daniel Thompson's loader uses, or use a physical switch / button to enter the bootloader when you need it. 33 | 34 | If you don't want to you don't even have to use a bootloader, just change the data address in the 35 | linker script and you can use a blackmagic probe or stlink device to flash and debug your system, 36 | or flash the stm32 through USART, I don't know, do whatever you like. 37 | -------------------------------------------------------------------------------- /stuff/bootloader/stm32f103c8t6.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukas2511/swiitch-controller/10d88c1d742bea528fcd7fc5f73ee4378f5b9163/stuff/bootloader/stm32f103c8t6.bin --------------------------------------------------------------------------------