Compare commits

...

96 Commits
show ... master

Author SHA1 Message Date
Raphaël Assenat c681f648a5
Merge pull request #8 from Mcharlsto/master
Fix & Improve ATmega32u4 support
2021-11-12 10:13:57 +09:00
Matthew C 62711a3236
Add clarity to readme 2021-10-29 21:43:07 +01:00
Matthew C 0ec5f22f0f
Remove completed task 2021-10-29 21:41:57 +01:00
Matthew C 1514d08f44
Add new 32u4 specific makefile 2021-10-29 21:40:40 +01:00
Matthew C ff893e2152
Fix bootloader address bug 2021-10-29 21:40:01 +01:00
Raphael Assenat 52eea713be Version 3.6.1 2021-03-05 13:37:24 +09:00
Raphael Assenat a698f8cd06 Correct accuracy of poll interval settings on 2-player adapters
The pauses recently introduced for supporting the Brawler 64 wireless
work fine in single-player mode (i.e. the poll interval setting in
the adapter manager is honored), but in two-player mode, the pauses
add up and the actual polling rate was slower than what was requested.

This corrects the issue, but unfortunately, on dual port adapters,
this means that a setting of 4ms or higher is needed to use a Brawler 64.
2021-03-04 15:47:12 +09:00
Raphael Assenat 5d06a6d6a3 Export the number of players from main 2021-03-04 15:46:59 +09:00
Raphael Assenat 786088e4b1 Increase version 2021-02-17 22:44:42 +09:00
Raphael Assenat 07c4cc7a4a Save memory by not using an unnecessary buffer.
Saves (64 - sizeof(struct usb_request)) bytes.
2021-02-17 22:43:08 +09:00
Raphael Assenat e5f6c6ee02 Add a workaround for the Brawler 64 wireless gamepad
This controller (or its receiver) needs a pause between the
"get caps" and "get status" commands. Introduce a pause
of a duration based on the poll interval.

Unfortunately poll interval that works with this controller is 2ms.
2021-02-17 22:41:13 +09:00
Raphael Assenat ee3adafd26 Correct the bootloader entry address 2021-02-17 22:40:45 +09:00
Raphael Assenat 90779c014b Add changelog entry for next version 2019-07-16 16:07:57 -04:00
Raphaël Assénat 59c2627f40
Merge pull request #5 from dsprenkels/atmega32u4
Add support for atmega32u4 chip
2019-07-16 16:06:14 -04:00
Daan Sprenkels 7e90c06cf9 Add support for atmega32u4 chip 2019-07-13 14:17:26 +02:00
Raphael Assenat 2dd64af033 update changelog for next version 2019-02-04 17:10:12 -05:00
Raphael Assenat 74cf60d839 Add support for the N64 mouse
For now, just recognizes it's a mouse and treat it like a controller.
2019-02-04 17:08:52 -05:00
Raphael Assenat 4999f07c14 Add support for Gamecube keyboards 2018-11-06 10:38:39 -05:00
Raphael Assenat 9872399739 changelog entry for release 3.5.2 2018-09-18 14:37:24 -04:00
Raphael Assenat c7992e5334 Improve PID (force feedback) implementation
Honor the effect duration
2018-09-18 14:34:38 -04:00
Raphael Assenat f5e268fb19 bump version to 3.5.2 2018-09-05 10:43:04 -04:00
Raphael Assenat 9c99048c6f run avr-size in AVR mode 2018-09-05 10:42:53 -04:00
Raphael Assenat 2d3795c29a Implement reset and echo requests 2018-09-05 10:42:05 -04:00
Raphael Assenat b01df9f239 Version 3.5.1 2018-04-10 17:17:51 -04:00
Raphael Assenat 55fe6d0312 Adjust vibration duration to feel more like on a real console 2018-04-10 17:17:46 -04:00
Raphael Assenat 1dc71a835b Disable debug 2018-04-10 15:13:34 -04:00
Raphael Assenat 03ebfde089 Force feedback: Don't ignore the loop count
The PID Effect Operation output report contains a 'loop count'
field that has an influence on how long the effect runs. Ignoring
it can lead to never stopping vibration for software that use
the loop count to let effects stop automatically instead of
issuing a stop command.
2018-04-10 15:13:25 -04:00
Raphael Assenat f42a29b888 Add a second interval timer 2018-04-10 15:07:35 -04:00
Raphael Assenat 2a2b326001 Release date for version 3.5.0 2017-11-25 14:09:34 -05:00
Raphael Assenat 9d5ca629a7 Update changelog with recent changes 2017-11-22 09:42:26 -05:00
Raphael Assenat 708fb22072 2-player: Re-order joystick and management interfaces
Make sure the management (non-joystick) interface is the last. Works
around a presumed Windows bug (Joystick ID confusion where the
second controller stops working or gives an error in the Game controller
test dialog)
2017-11-22 09:39:14 -05:00
Raphael Assenat 0e0c381fbd Implement feature set query commands
The adapter can now be queried by the management tool to see
what configuration options and requests are available without
harcoding them for each release.
2017-11-22 09:37:42 -05:00
Raphael Assenat 95d6b2ec15 Simplify config code and add disable triggers feature 2017-11-22 09:35:59 -05:00
Raphael Assenat 5328827234 Version 3.5 product IDs 2017-11-21 23:03:46 -05:00
Raphael Assenat 66a1a45f22 Runtime endpoint sizes for HID 2017-11-20 00:13:57 -05:00
Raphael Assenat 9f665068ae Bump version to 3.5.0 2017-11-20 00:13:50 -05:00
Raphael Assenat 7b01a454d4 Prepare for release 3.5.0
3.4.1 was never formally released so the changelog entry is reused.
2017-11-20 00:05:52 -05:00
Raphael Assenat be2894b68c Set .bcdUSB to the correct value
Should be 0x0110 for 1.1
2017-11-19 14:41:41 -05:00
Raphael Assenat e7426e684a Do not send more descriptor bytes than we have
The request may be for more bytes than our descriptor has. Do not
send more when asked for more.
2017-11-19 14:40:53 -05:00
Raphael Assenat 05b47f9967 Prepare for release 3.4.1 2017-08-14 17:59:12 -04:00
Raphael Assenat c123fd6b52 Finish "sliders as button" configurability 2017-08-14 17:58:40 -04:00
Raphael Assenat d72815e9e6 Adjust "sliders as buttons" threshold
The initial value of 32 was too low. Buttons were triggered
without even touching the sliders.
2017-08-14 17:57:55 -04:00
Raphael Assenat a041e8eaf8 Implement "sliders as buttons" mode
In this mode, when the analog values of the L/R triggers exceed a
certain threshold, the corresponding L and R digital buttons are
triggered.
2017-08-05 17:13:21 -04:00
Raphael Assenat a9d5df2b8f Add "triggers as buttons" flag 2017-08-05 17:12:26 -04:00
Raphael Assenat 324bb81b1e Version 3.4.0 changelog entry 2017-01-08 16:24:08 -05:00
Raphael Assenat eeb2ecbae4 Merge branch 'performance' 2017-01-08 16:21:51 -05:00
Raphael Assenat 158d9bffda Makefile 'restart' target
Useful for some tests...
2017-01-08 16:19:41 -05:00
Raphael Assenat c7e8dc7ad4 Save memory
Store the device string in 8 bit and in program memory. Also
reduce the size of a few variables.
2017-01-08 15:58:45 -05:00
Raphael Assenat b22985712f Add stack overgrow detection
If the stack ever grows too large (and starts overwriting variables
in .bss) the firmware jumps into the bootloader. This is better than
just continuing to run with strange side effects.
2017-01-08 15:14:55 -05:00
Raphael Assenat 2eaafb7786 ifdef unused rawdata fields to save memory
Save 32 bytes.
2017-01-08 10:50:01 -05:00
Raphael Assenat 412c1a42eb ifdef unused rawdata fields to save memory
Save 32 bytes.
2017-01-08 10:49:51 -05:00
Raphael Assenat 083c915c34 Implement RQ_GCN64_BLOCK_IO 2016-12-03 14:15:22 -05:00
Raphael Assenat 6d8d2d27bf Increate feature report size to 63 bytes 2016-12-03 14:15:00 -05:00
Raphael Assenat 5eb8a587c4 Add RQ_GCN64_BLOCK_IO 2016-12-03 14:14:51 -05:00
Raphael Assenat 64115ad9ef remove dead code 2016-11-28 14:06:52 -05:00
Raphael Assenat e944017c36 Bump version to 3.4, change product IDs 2016-11-28 11:45:52 -05:00
Raphael Assenat e7bca6ce08 forgot to increase version... 2016-11-27 16:58:50 -05:00
Raphael Assenat 1623a46179 update project name and homepages 2016-11-27 14:34:03 -05:00
Raphael Assenat d573ff55c3 Remove tool instructions as it is a separate project 2016-11-27 14:27:05 -05:00
Raphael Assenat 6bb9ce18a9 Changelog entries for 3.3.2 2016-11-27 11:16:33 -05:00
Raphael Assenat c2fd3a10ba Need one current_pad_type var. per channel 2016-11-27 11:15:13 -05:00
Raphael Assenat 434aec0d4b Version 3.3.1 2016-11-02 22:10:54 -04:00
Raphael Assenat bbefc8203c Prevent freeze if host only polls one endpoint
Each joystick has its own interrupt endpoint, and the main loop
used to wait until each became ready (i.e. Serviced) in turn.

On some systems, when only one joystick is in use, only one endpoint
is polled. The inactive endpoint therefore never became ready and
the mainloop would freeze.

Rewrote part of the loop to prevent this. New flow:

1) Wait until it is time to poll the controllers
(based on current poll frequency setting)

2) Wait until either endpoints is ready.

3) Write data to first endpoint if ready, otherwise skip.

4) Write data to second endpoint if ready, otherwise skip.
2016-11-02 21:46:52 -04:00
Raphael Assenat 1659d2d106 Adjust release date 2016-10-25 22:19:23 -04:00
Raphael Assenat 1c05ad1326 Gamecube: Read from the specified channel... 2016-10-25 22:14:20 -04:00
Raphael Assenat 21ebef2ed3 Each channel needs its own buffer... 2016-10-23 21:44:42 -04:00
Raphael Assenat d7bdf59860 Need to initialize stuff *after* knowing the number of players. 2016-10-23 16:15:01 -04:00
Raphael Assenat bb9e6dce17 Initialize context for hid set/get report 2016-10-17 22:21:58 -04:00
Raphael Assenat 835a4935c5 Fix stk525 makefile 2016-10-17 22:21:46 -04:00
Raphael Assenat 81662cef2a Update copyright year 2016-10-10 15:01:22 -04:00
Raphael Assenat 07ffccc787 Changelog for v3.3 2016-10-10 14:58:47 -04:00
Raphael Assenat 8da31387f6 Version 3.3 2016-10-10 14:56:02 -04:00
Raphael Assenat 96bd132b66 Give equal priority to both controllers! 2016-10-10 14:53:40 -04:00
Raphael Assenat af12f0d242 Add new product IDs to udev rules 2016-10-10 14:53:25 -04:00
Raphael Assenat f6e4281321 Dual controller now available as a configured flavor 2016-10-10 14:48:42 -04:00
Raphael Assenat 4ecc3f571e Multi-player support complete
Works, but very light testing so far
2016-10-10 14:20:14 -04:00
Raphael Assenat 90aae55470 Support for an extra interrupt endpoint
- Reduced interrupt endpoint size (reports are 15 bytes so it fits in
   16)
2016-10-09 23:47:36 -04:00
Raphael Assenat fe3814f7d1 honor the channel argument 2016-10-09 22:33:07 -04:00
Raphael Assenat fb43524d34 Update gcn64txrx code for multiple channels 2016-10-09 16:17:56 -04:00
Raphael Assenat 0c8dc9e7de Cleanup and copyright year update 2016-10-09 16:07:07 -04:00
Raphael Assenat 1fcfe8b594 Implement personalities
./wait_then_flash.sh atmega32u2 $HEXFILE
2016-08-22 23:18:47 -04:00
Raphael Assenat 0c66478dc5 Dynamic device string 2016-08-22 23:18:37 -04:00
Raphael Assenat 7d8e8af290 Defines for N64-only and GC-only modes 2016-08-22 23:18:05 -04:00
Raphael Assenat b6386b8948 Add shorter version string 2016-08-22 23:17:49 -04:00
Raphael Assenat 158fc4fdca Device descriptor in RAM 2016-08-22 22:00:54 -04:00
Raphael Assenat 553598ab64 usb usbpad_init 2016-08-22 21:57:21 -04:00
Raphael Assenat 671c2e403f get report must return the proper size 2016-08-22 21:56:30 -04:00
Raphael Assenat b9a6f23607 reduce HID max packet size to 32 2016-05-28 00:17:43 -04:00
Raphael Assenat 4fbba42896 tweak changelog 2016-05-27 23:28:11 -04:00
Raphael Assenat 6f6d77956b Enable EP2 2016-05-27 23:27:12 -04:00
Raphael Assenat 4fac12c760 fix changelog 2016-05-24 21:42:08 -04:00
Raphael Assenat cc34233b76 Version 3.2 changelog 2016-05-22 17:33:43 -04:00
Raphael Assenat 3ec1c0b711 Version 3.2 strings and PID 2016-05-22 17:33:26 -04:00
Raphael Assenat a333980812 reportdesc: Use Z instead of Slider
Fixes mapping issues in OpenEMU. Still works everywhere else.
2016-05-22 17:26:38 -04:00
Raphael Assenat fb1be3eee5 Version 3.1.1 2016-02-29 18:45:27 -05:00
Raphael Assenat b8be4c754b Create an annotated tag 2016-02-29 18:44:52 -05:00
48 changed files with 2433 additions and 565 deletions

View File

@ -7,7 +7,7 @@ include Makefile.inc
PROGNAME=gcn64usb
OBJDIR=objs-$(PROGNAME)
CPU=atmega32u2
CFLAGS=-Wall -mmcu=$(CPU) -DF_CPU=16000000L -Os -DUART1_STDOUT -DVERSIONSTR=$(VERSIONSTR)
CFLAGS=-Wall -mmcu=$(CPU) -DF_CPU=16000000L -Os -DUART1_STDOUT -DVERSIONSTR=$(VERSIONSTR) -DVERSIONSTR_SHORT=$(VERSIONSTR_SHORT) -DVERSIONBCD=$(VERSIONBCD) -std=gnu99
LDFLAGS=-mmcu=$(CPU) -Wl,-Map=$(PROGNAME).map
HEXFILE=$(PROGNAME).hex
@ -16,6 +16,18 @@ all: $(HEXFILE)
clean:
rm -f $(PROGNAME).elf $(PROGNAME).hex $(PROGNAME).map $(OBJS)
gcn64txrx0.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=0 -DGCN64_DATA_BIT=0
gcn64txrx1.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=1 -DGCN64_DATA_BIT=2
gcn64txrx2.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=2 -DGCN64_DATA_BIT=1
gcn64txrx3.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=3 -DGCN64_DATA_BIT=3
%.o: %.S
$(CC) $(CFLAGS) -c $< -o $@
@ -30,7 +42,7 @@ $(PROGNAME).elf: $(OBJS)
$(PROGNAME).hex: $(PROGNAME).elf
avr-objcopy -j .data -j .text -O ihex $(PROGNAME).elf $(PROGNAME).hex
avr-size $(PROGNAME).elf
avr-size $(PROGNAME).elf -C --mcu=$(CPU)
fuse:
@ -42,7 +54,11 @@ justflash: $(HEXFILE)
./scripts/wait_then_flash.sh $(CPU) $(HEXFILE)
chip_erase:
dfu-programmer atmega32u2 erase
dfu-programmer $(CPU) erase
reset:
dfu-programmer atmega32u2 reset
dfu-programmer $(CPU) reset
restart:
- ./scripts/enter_bootloader.sh
./scripts/start.sh $(CPU)

64
Makefile.32u4 Normal file
View File

@ -0,0 +1,64 @@
CC=avr-gcc
AS=$(CC)
LD=$(CC)
include Makefile.inc
PROGNAME=gcn64usb
OBJDIR=objs-$(PROGNAME)
CPU=atmega32u4
CFLAGS=-Wall -mmcu=$(CPU) -DF_CPU=16000000L -Os -DUART1_STDOUT -DVERSIONSTR=$(VERSIONSTR) -DVERSIONSTR_SHORT=$(VERSIONSTR_SHORT) -DVERSIONBCD=$(VERSIONBCD) -std=gnu99
LDFLAGS=-mmcu=$(CPU) -Wl,-Map=$(PROGNAME).map
HEXFILE=$(PROGNAME).hex
all: $(HEXFILE)
clean:
rm -f $(PROGNAME).elf $(PROGNAME).hex $(PROGNAME).map $(OBJS)
gcn64txrx0.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=0 -DGCN64_DATA_BIT=0
gcn64txrx1.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=1 -DGCN64_DATA_BIT=2
gcn64txrx2.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=2 -DGCN64_DATA_BIT=1
gcn64txrx3.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=3 -DGCN64_DATA_BIT=3
%.o: %.S
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.c %.h
$(CC) $(CFLAGS) -c $< -o $@
$(PROGNAME).elf: $(OBJS)
$(LD) $(OBJS) $(LDFLAGS) -o $(PROGNAME).elf
$(PROGNAME).hex: $(PROGNAME).elf
avr-objcopy -j .data -j .text -O ihex $(PROGNAME).elf $(PROGNAME).hex
avr-size $(PROGNAME).elf -C --mcu=$(CPU)
fuse:
flash: $(HEXFILE)
- ./scripts/enter_bootloader.sh
./scripts/wait_then_flash.sh $(CPU) $(HEXFILE)
justflash: $(HEXFILE)
./scripts/wait_then_flash.sh $(CPU) $(HEXFILE)
chip_erase:
dfu-programmer $(CPU) erase
reset:
dfu-programmer $(CPU) reset
restart:
- ./scripts/enter_bootloader.sh
./scripts/start.sh $(CPU)

View File

@ -1,2 +1,4 @@
OBJS=main.o usb.o usbpad.o mappings.o gcn64_protocol.o n64.o gamecube.o usart1.o bootloader.o eeprom.o config.o hiddata.o usbstrings.o intervaltimer.o version.o gcn64txrx.o gamepads.o
VERSIONSTR=\"3.1.0\"
OBJS=main.o usb.o usbpad.o mappings.o gcn64_protocol.o n64.o gamecube.o usart1.o bootloader.o eeprom.o config.o hiddata.o usbstrings.o intervaltimer.o intervaltimer2.o version.o gcn64txrx0.o gcn64txrx1.o gcn64txrx2.o gcn64txrx3.o gamepads.o stkchk.o gc_kb.o
VERSIONSTR=\"3.6.1\"
VERSIONSTR_SHORT=\"3.6\"
VERSIONBCD=0x0361

View File

@ -7,7 +7,7 @@ include Makefile.inc
PROGNAME=gcn64usb-stk500
OBJDIR=objs-$(PROGNAME)
CPU=at90usb1287
CFLAGS=-Wall -mmcu=$(CPU) -DF_CPU=16000000L -Os -DUART1_STDOUT -DSTK525 -DVERSIONSTR=$(VERSIONSTR)
CFLAGS=-Wall -mmcu=$(CPU) -DF_CPU=16000000L -Os -DUART1_STDOUT -DSTK525 -DVERSIONSTR=$(VERSIONSTR) -DVERSIONSTR_SHORT=$(VERSIONSTR_SHORT) -DVERSIONBCD=$(VERSIONBCD) -std=gnu99
LDFLAGS=-mmcu=$(CPU) -Wl,-Map=$(PROGNAME).map
HEXFILE=$(PROGNAME).hex
@ -17,6 +17,19 @@ all: $(HEXFILE)
main.o: main.c reportdesc.c dataHidReport.c
$(CC) $(CFLAGS) -c $< -o $@
gcn64txrx0.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=0 -DGCN64_DATA_BIT=0
gcn64txrx1.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=1 -DGCN64_DATA_BIT=2
gcn64txrx2.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=2 -DGCN64_DATA_BIT=1
gcn64txrx3.o: gcn64txrx.S
$(CC) $(CFLAGS) -c $< -o $@ -DSUFFIX=3 -DGCN64_DATA_BIT=3
%.o: %.S
$(CC) $(CFLAGS) -c $< -o $@
@ -46,7 +59,7 @@ justflash: $(HEXFILE)
./scripts/wait_then_flash.sh $(CPU) $(HEXFILE)
chip_erase:
dfu-programmer atmega32u2 erase
dfu-programmer at90usb1287 erase
reset:
dfu-programmer atmega32u2 reset
dfu-programmer at90usb1287 reset

View File

@ -1,27 +1,24 @@
# Gamecube/N64 to USB adapter
# Gamecube/N64 to USB adapter firmware (3rd generation)
## Introduction
This is the source code for a Gamecube/N64 controller to USB adapter firmware
meant to run on [raphnet.net Multiuse PCB-X](http://www.raphnet.net/electronique/multiuse_pcbX/index_en.php).
The project homepage is located at: http://www.raphnet.net/electronique/multiuse_pcbX/index.php
## Homepage
* English: [Gamecube/N64 controller to USB adapter (Third generation)](http://www.raphnet.net/electronique/gcn64_usb_adapter_gen3/index_en.php)
* French: [Adaptateur manette Gamecube/N64 à USB (Troisième génération)](http://www.raphnet.net/electronique/gcn64_usb_adapter_gen3/index.php)
## License
The project is released under the General Public License version 3.
## Directories
* / : The firmware source code
* tool/ : This directory contains utilities to configure and update an adapter, manipulate mempak
image files, etc.
* scripts/ : Scripts and/or useful files
## Compiling the firmware
You will need a working avr-gcc toolchain with avr-libc and standard utilities such as make. Just
type 'make' and it should build just fine. Under Linux at least.
If you are compiling for a custom board or Arduino running on an ATmega32u4, then run 'make -f Makefile.32u4' instead.
## Programming the firmware
@ -29,36 +26,3 @@ The makefile has a convenient 'flash' target which sends a command to the firmwa
the bootloader and then executes dfu-programmer (it must of course be installed) with the
correct arguments.
Note that the tool must be compiled first and your permissions must also be set so that your
user may access the device. See 'Using the tools' below for more information.
## Compiling the tools
In the tool/ directory, just type make.
There are a few dependencies:
- libhidapi-dev
- libhidapi-hidraw0
- pkg-config
- gtk3 (for the gui only)
Provided you have all the dependencies installed, under Linux at least, it should
compile without errors. For other environments such has MinGW, there are provisions
in the makefile to auto-detect and tweak the build accordingly, but it if fails, be
prepared to hack the makefile.
## Using the tools
Under Linux, you should configure udev to give the proper permissions to your user,
otherwise communicating with the device won't be possible. The same requirement
also applies to dfu-programmer.
An easy way to do this is to copy the two files below to /etc/udev/rules.d, restart
udev and reconnect the devices.
scripts/99-atmel-dfu.rules
scripts/99-raphnet.rules
For information on how to actually /use/ the tools, try --help. Ex:
$ ./gcn64ctl --help

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2021 Raphael Assenat <raph@raphnet.net>
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
@ -15,18 +15,44 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include "usb.h"
void enterBootLoader(void)
{
cli();
usb_shutdown();
_delay_ms(10);
#if defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega32U4__)
// ATmega32u2 : 0x3800
asm volatile(
"cli \n"
"ldi r30, 0x00 \n" // ZL
"ldi r31, 0x38 \n" // ZH
"ijmp");
#elif defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__)
// AT90USB1287/1286 : 0xF000 (word address)
// ATmega32u2 : ???
asm volatile(
"cli \n"
"ldi r30, 0x00 \n" // ZL
"ldi r31, 0xF0 \n" // ZH
"ijmp");
#else
#error Bootloader address unknown for this CPU
#endif
}
void resetFirmware(void)
{
usb_shutdown();
// jump to the application reset vector
asm volatile(
"cli \n"
"ldi r30, 0x00 \n" // ZL
"ldi r31, 0x00 \n" // ZH
"ijmp");
}

View File

@ -1,2 +1,7 @@
#ifndef _bootloader_h__
#define _bootloader_h__
void enterBootLoader(void);
void resetFirmware(void);
#endif // _bootloader_h__

View File

@ -1,3 +1,65 @@
- Future ideas / TODO
- Add very basic N64 mouse support (detect and treat it like a controller)
- March 5, 2021 : Version 3.6.1
- Alter timing so the brawler64 wireless gamepad will work. Those will now
work with a poll interval >= 2ms on single port adapters, and >= 4ms on dual
port adapters.
- Add a feature to swap the main analog stick and the D-Pad
- Save memory in usb.c (no user visible effects)
- Correct bootloader entry address (*maybe* less chances of failing)
- November 6, 2018 : Version 3.6.0
- Add gamecube keyboard support
- September 18, 2018 : Version 3.5.2
- Improve PID (force feedback) implementation
- Implement reset firmware command
- Add echo feature (USB comm test)
- April 10, 2018 : Version 3.5.1
- Fix never-stopping vibration issues (Dolphin)
- Novembre 25, 2017 : Version 3.5.0
- Add a "triggers as buttons" mode for Gamecube controllers
- Add a "disable analog triggers" mode for Gamecube controllers
- Internal changes to workaround a presumed Windows bug (Joystick ID
confusion where the second controller stops working or gives an
error in the Game controller test dialog)
- Implement a feature to let the adapter manager query the feature
set of the current firmware.
- January 8, 2017 : Version 3.4.0
- New IO request for even lower latency when using the raphnetraw plugins
- Reduced memory footprint
- November 27, 2016 : Version 3.3.2
- Fix the get controller type command
- November 2, 2016 : Version 3.3.1
- Fix freeze when only one joystick was used in dual controller mode.
- October 25, 2016 : Version 3.3.0
- Implement multi player support (maximum two on Atmega32u2 due to endpoint
limit). New personalities, each with unique USB PID and product name:
- Dual GC/N64 to USB mode
- Dual N64-only to USB mode
- Dual GC-only to USB mode
- Core communication code updated to support up to four channels.
- August 22, 2016 : Version 3.2.1
- Implement N64-only and GC-only personalities (Different product ID and
device name)
- May 22, 2016 : Version 3.2.0
- Fix reconnecting loop in MacOS X
- Change gamecube trigger HID usage (Slider became Z). Now it works fine in openEMU.
- Version and product string updated.
- USB product ID changed.
- February 29, 2016 : Version 3.1.1
- Fix vibration code for x360ce (Was always on)
- February 24, 2016 : Version 3.1.0
- Add a test rumble command (for GUI tool, or for simple rumble control)
- Adjust Gamecube/N64 wire protocol timing (Fixes Mad Catz controller)

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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
@ -42,8 +42,41 @@ static void config_set_serial(char serial[SERIAL_NUM_LEN])
eeprom_commit();
}
struct paramAndFlag {
uint8_t param; // CFG_PARAM_* (requests.h)
uint32_t flag; // FLAG_* (config.h)
};
static struct paramAndFlag paramsAndFlags[] = {
{ CFG_PARAM_INVERT_TRIG, FLAG_GC_INVERT_TRIGS },
{ CFG_PARAM_FULL_SLIDERS, FLAG_GC_FULL_SLIDERS },
{ CFG_PARAM_TRIGGERS_AS_BUTTONS, FLAG_GC_SLIDERS_AS_BUTTONS },
{ CFG_PARAM_DISABLE_ANALOG_TRIGGERS, FLAG_DISABLE_ANALOG_TRIGGERS },
{ CFG_PARAM_SWAP_STICK_AND_DPAD, FLAG_SWAP_STICK_AND_DPAD },
{ },
};
uint8_t config_getSupportedParams(uint8_t *dst)
{
uint8_t n = 0, i;
dst[n++] = CFG_PARAM_MODE;
dst[n++] = CFG_PARAM_SERIAL;
for (i=0; i<NUM_CHANNELS; i++) {
dst[n++] = CFG_PARAM_POLL_INTERVAL0 + i;
}
for (i=0; paramsAndFlags[i].flag; i++) {
dst[n++] = paramsAndFlags[i].param;
}
return n;
}
unsigned char config_getParam(unsigned char param, unsigned char *value, unsigned char max_len)
{
int i;
switch (param)
{
case CFG_PARAM_MODE:
@ -70,12 +103,14 @@ unsigned char config_getParam(unsigned char param, unsigned char *value, unsigne
*value = g_eeprom_data.cfg.poll_interval[3];
return 1;
#endif
case CFG_PARAM_INVERT_TRIG:
*value = (g_eeprom_data.cfg.flags & FLAG_GC_INVERT_TRIGS) ? 1 : 0;
return 1;
case CFG_PARAM_FULL_SLIDERS:
*value = (g_eeprom_data.cfg.flags & FLAG_GC_FULL_SLIDERS) ? 1 : 0;
return 1;
default:
for (i=0; paramsAndFlags[i].flag; i++) {
if (param == paramsAndFlags[i].param) {
*value = (g_eeprom_data.cfg.flags & paramsAndFlags[i].flag) ? 1 : 0;
return 1;
}
}
}
return 0;
@ -83,6 +118,8 @@ unsigned char config_getParam(unsigned char param, unsigned char *value, unsigne
unsigned char config_setParam(unsigned char param, const unsigned char *value)
{
int i;
if (!value)
return 0;
@ -112,22 +149,24 @@ unsigned char config_setParam(unsigned char param, const unsigned char *value)
g_eeprom_data.cfg.poll_interval[3] = value[0];
break;
#endif
case CFG_PARAM_FULL_SLIDERS:
if (value[0]) {
g_eeprom_data.cfg.flags |= FLAG_GC_FULL_SLIDERS;
} else {
g_eeprom_data.cfg.flags &= ~FLAG_GC_FULL_SLIDERS;
}
break;
case CFG_PARAM_INVERT_TRIG:
if (value[0]) {
g_eeprom_data.cfg.flags |= FLAG_GC_INVERT_TRIGS;
} else {
g_eeprom_data.cfg.flags &= ~FLAG_GC_INVERT_TRIGS;
}
break;
default:
return 0;
for (i=0; paramsAndFlags[i].flag; i++) {
if (param == paramsAndFlags[i].param) {
if (value[0]) {
g_eeprom_data.cfg.flags |= paramsAndFlags[i].flag;
} else {
g_eeprom_data.cfg.flags &= ~paramsAndFlags[i].flag;
}
break;
}
}
// if we made it through the list without finding
// a matching parameter, do nothing.
if (!paramsAndFlags[i].flag) {
return 0;
}
}
eeprom_commit();

View File

@ -10,8 +10,11 @@ struct eeprom_cfg {
uint32_t flags;
};
#define FLAG_GC_FULL_SLIDERS 1
#define FLAG_GC_INVERT_TRIGS 2
#define FLAG_GC_FULL_SLIDERS 0x01
#define FLAG_GC_INVERT_TRIGS 0x02
#define FLAG_GC_SLIDERS_AS_BUTTONS 0x04
#define FLAG_DISABLE_ANALOG_TRIGGERS 0x08
#define FLAG_SWAP_STICK_AND_DPAD 0x10
void eeprom_app_write_defaults(void);
void eeprom_app_ready(void);
@ -19,4 +22,6 @@ void eeprom_app_ready(void);
unsigned char config_setParam(unsigned char param, const unsigned char *value);
unsigned char config_getParam(unsigned char param, unsigned char *value, unsigned char max_len);
uint8_t config_getSupportedParams(uint8_t *dst);
#endif

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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
@ -24,7 +24,7 @@ const uint8_t dataHidReport[] PROGMEM = {
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 40, // REPORT_COUNT (40)
0x95, 63, // REPORT_COUNT (63)
0x09, 0x01, // USAGE (Vendor defined)
0xB1, 0x00, // FEATURE (Data,Ary,Abs)
0xc0 // END_COLLECTION

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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
@ -23,20 +23,30 @@
#include "gcn64_protocol.h"
/*********** prototypes *************/
static void gamecubeInit(void);
static char gamecubeUpdate(void);
static char gamecubeChanged(void);
static void gamecubeInit(unsigned char chn);
static void gamecubeInitKB(unsigned char chn);
static char gamecubeUpdate(unsigned char chn);
static char gamecubeUpdateKB(unsigned char chn);
static char gamecubeChanged(unsigned char chn);
static char gc_rumbling = 0;
static char origins_set = 0;
static unsigned char orig_x, orig_y, orig_cx, orig_cy;
static char gc_rumbling[GAMEPAD_MAX_CHANNELS] = { };
static char origins_set[GAMEPAD_MAX_CHANNELS] = { };
static unsigned char orig_x[GAMEPAD_MAX_CHANNELS];
static unsigned char orig_y[GAMEPAD_MAX_CHANNELS];
static unsigned char orig_cx[GAMEPAD_MAX_CHANNELS];
static unsigned char orig_cy[GAMEPAD_MAX_CHANNELS];
static void gamecubeInit(void)
static void gamecubeInit(unsigned char chn)
{
gamecubeUpdate();
gamecubeUpdate(chn);
}
void gc_decodeAnswer(unsigned char data[8])
static void gamecubeInitKB(unsigned char chn)
{
gamecubeUpdateKB(chn);
}
void gc_decodeAnswer(unsigned char chn, unsigned char data[8])
{
unsigned char x,y,cx,cy;
@ -81,107 +91,120 @@ void gc_decodeAnswer(unsigned char data[8])
56-63 Right Btn Val
*/
last_built_report.pad_type = PAD_TYPE_GAMECUBE;
last_built_report.gc.buttons = data[0] | data[1] << 8;
last_built_report[chn].pad_type = PAD_TYPE_GAMECUBE;
last_built_report[chn].gc.buttons = data[0] | data[1] << 8;
x = data[2];
y = data[3];
cx = data[4];
cy = data[5];
last_built_report.gc.lt = data[6];
last_built_report.gc.rt = data[7];
memcpy(last_built_report.gc.raw_data, data, 8);
last_built_report[chn].gc.lt = data[6];
last_built_report[chn].gc.rt = data[7];
if (origins_set) {
last_built_report.gc.x = ((int)x-(int)orig_x);
last_built_report.gc.y = ((int)y-(int)orig_y);
last_built_report.gc.cx = ((int)cx-(int)orig_cx);
last_built_report.gc.cy = ((int)cy-(int)orig_cy);
#ifdef PAD_DATA_HAS_RAW
memcpy(last_built_report[chn].gc.raw_data, data, 8);
#endif
if (origins_set[chn]) {
last_built_report[chn].gc.x = ((int)x-(int)orig_x[chn]);
last_built_report[chn].gc.y = ((int)y-(int)orig_y[chn]);
last_built_report[chn].gc.cx = ((int)cx-(int)orig_cx[chn]);
last_built_report[chn].gc.cy = ((int)cy-(int)orig_cy[chn]);
} else {
orig_x = x;
orig_y = y;
orig_cx = cx;
orig_cy = cy;
last_built_report.gc.x = 0;
last_built_report.gc.y = 0;
last_built_report.gc.cx = 0;
last_built_report.gc.cy = 0;
origins_set = 1;
orig_x[chn] = x;
orig_y[chn] = y;
orig_cx[chn] = cx;
orig_cy[chn] = cy;
last_built_report[chn].gc.x = 0;
last_built_report[chn].gc.y = 0;
last_built_report[chn].gc.cx = 0;
last_built_report[chn].gc.cy = 0;
origins_set[chn] = 1;
}
}
static char gamecubeUpdate()
static char gamecubeUpdateKB(unsigned char chn)
{
unsigned char tmpdata[GC_GETSTATUS_REPLY_LENGTH];
unsigned char count;
unsigned char i, lrc;
#if 0
/* The GetID command. This is required for the Nintendo Wavebird to work... */
tmp = GC_GETID;
count = gcn64_transaction(&tmp, 1);
if (count != GC_GETID_REPLY_LENGTH) {
return 1;
}
tmpdata[0] = GC_POLL_KB1;
tmpdata[1] = GC_POLL_KB2;
tmpdata[2] = GC_POLL_KB3;
/*
* The wavebird needs time. It does not answer the
* folowwing get status command if we don't wait here.
*
* A good 2:1 safety margin has been chosen.
*
*/
// 10 : does not work
// 20 : does not work
// 25 : works
// 30 : works
_delay_us(50);
#endif
tmpdata[0] = GC_GETSTATUS1;
tmpdata[1] = GC_GETSTATUS2;
tmpdata[2] = GC_GETSTATUS3(gc_rumbling);
count = gcn64_transaction(tmpdata, 3, tmpdata, GC_GETSTATUS_REPLY_LENGTH);
count = gcn64_transaction(chn, tmpdata, 3, tmpdata, GC_GETSTATUS_REPLY_LENGTH);
if (count != GC_GETSTATUS_REPLY_LENGTH) {
return 1;
}
gc_decodeAnswer(tmpdata);
// Compute LRC
for (i=0, lrc=0; i<6; i++) {
lrc ^= tmpdata[i];
}
if (tmpdata[7] != lrc) {
return 1; // LRC error
}
// Ok, fill the report
last_built_report[chn].pad_type = PAD_TYPE_GC_KB;
for (i=0; i<3; i++) {
last_built_report[chn].gckb.keys[i] = tmpdata[4+i];
}
return 0;
}
static void gamecubeHotplug(void)
static char gamecubeUpdate(unsigned char chn)
{
// Make sure next read becomes the refence center values
origins_set = 0;
unsigned char tmpdata[GC_GETSTATUS_REPLY_LENGTH];
unsigned char count;
tmpdata[0] = GC_GETSTATUS1;
tmpdata[1] = GC_GETSTATUS2;
tmpdata[2] = GC_GETSTATUS3(gc_rumbling[chn]);
count = gcn64_transaction(chn, tmpdata, 3, tmpdata, GC_GETSTATUS_REPLY_LENGTH);
if (count != GC_GETSTATUS_REPLY_LENGTH) {
return 1;
}
gc_decodeAnswer(chn, tmpdata);
return 0;
}
static char gamecubeProbe(void)
static void gamecubeHotplug(unsigned char chn)
{
origins_set = 0;
// Make sure next read becomes the refence center values
origins_set[chn] = 0;
}
if (gamecubeUpdate()) {
static char gamecubeProbe(unsigned char chn)
{
origins_set[chn] = 0;
if (gamecubeUpdate(chn)) {
return 0;
}
return 1;
}
static char gamecubeChanged(void)
static char gamecubeChanged(unsigned char chn)
{
return memcmp(&last_built_report, &last_sent_report, sizeof(gamepad_data));
return memcmp(&last_built_report[chn], &last_sent_report[chn], sizeof(gamepad_data));
}
static void gamecubeGetReport(gamepad_data *dst)
static void gamecubeGetReport(unsigned char chn, gamepad_data *dst)
{
if (dst)
memcpy(dst, &last_built_report, sizeof(gamepad_data));
memcpy(dst, &last_built_report[chn], sizeof(gamepad_data));
}
static void gamecubeVibration(char enable)
static void gamecubeVibration(unsigned char chn, char enable)
{
gc_rumbling = enable;
gc_rumbling[chn] = enable;
}
Gamepad GamecubeGamepad = {
@ -198,3 +221,17 @@ Gamepad *gamecubeGetGamepad(void)
{
return &GamecubeGamepad;
}
Gamepad GamecubeKeyboard = {
.init = gamecubeInitKB,
.update = gamecubeUpdateKB,
.changed = gamecubeChanged,
.getReport = gamecubeGetReport,
.probe = gamecubeProbe,
};
Gamepad *gamecubeGetKeyboard(void)
{
return &GamecubeKeyboard;
}

View File

@ -1,7 +1,5 @@
#include "gamepads.h"
#define GAMECUBE_UPDATE_NORMAL 0
#define GAMECUBE_UPDATE_ORIGIN 1
Gamepad *gamecubeGetGamepad(void);
Gamepad *gamecubeGetKeyboard(void);

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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
@ -16,7 +16,8 @@
*/
#include "gamepads.h"
/* Shared between N64 and GC (only one is used at a time). Saves memory. */
gamepad_data last_sent_report;
gamepad_data last_built_report;
/* Shared between N64 and GC (only one is used at a time). Saves memory. */
gamepad_data last_sent_report[GAMEPAD_MAX_CHANNELS];
gamepad_data last_built_report[GAMEPAD_MAX_CHANNELS];

View File

@ -1,9 +1,12 @@
#ifndef _gamepads_h__
#define _gamepads_h__
#undef PAD_DATA_HAS_RAW
#define PAD_TYPE_NONE 0
#define PAD_TYPE_N64 4
#define PAD_TYPE_GAMECUBE 5
#define PAD_TYPE_GC_KB 6
#define N64_RAW_SIZE 4
#define GC_RAW_SIZE 8
@ -12,7 +15,9 @@ typedef struct _n64_pad_data {
unsigned char pad_type; // PAD_TYPE_N64
char x,y;
unsigned short buttons;
#ifdef PAD_DATA_HAS_RAW
unsigned char raw_data[N64_RAW_SIZE];
#endif
} n64_pad_data;
#define N64_BTN_A 0x8000
@ -38,7 +43,9 @@ typedef struct _gc_pad_data {
char x,y,cx,cy;
unsigned char lt,rt;
unsigned short buttons;
#ifdef PAD_DATA_HAS_RAW
unsigned char raw_data[GC_RAW_SIZE];
#endif
} gc_pad_data;
#define GC_BTN_A 0x0001
@ -63,29 +70,36 @@ typedef struct _gc_pad_data {
#define GC_ALL_BUTTONS (GC_BTN_START|GC_BTN_Y|GC_BTN_X|GC_BTN_B|GC_BTN_A|GC_BTN_L|GC_BTN_R|GC_BTN_Z|GC_BTN_DPAD_UP|GC_BTN_DPAD_DOWN|GC_BTN_DPAD_RIGHT|GC_BTN_DPAD_LEFT)
typedef struct _gc_keyboard_data {
unsigned char pad_type;
unsigned char keys[3];
} gc_keyboard_data;
typedef struct _gamepad_data {
union {
unsigned char pad_type; // PAD_TYPE_*
n64_pad_data n64;
gc_pad_data gc;
gc_keyboard_data gckb;
};
} gamepad_data;
#define GAMEPAD_MAX_CHANNELS 2
typedef struct {
void (*init)(void);
char (*update)(void);
char (*changed)(void);
void (*hotplug)(void);
void (*getReport)(gamepad_data *dst);
void (*setVibration)(char enable);
char (*probe)(void); /* return true if found */
void (*init)(unsigned char chn);
char (*update)(unsigned char chn);
char (*changed)(unsigned char chn);
void (*hotplug)(unsigned char chn);
void (*getReport)(unsigned char chn, gamepad_data *dst);
void (*setVibration)(unsigned char chn, char enable);
char (*probe)(unsigned char chn); /* return true if found */
} Gamepad;
/* What was most recently read from the controller */
extern gamepad_data last_built_report;
extern gamepad_data last_built_report[GAMEPAD_MAX_CHANNELS];
/* What was most recently sent to the host */
extern gamepad_data last_sent_report;
extern gamepad_data last_sent_report[GAMEPAD_MAX_CHANNELS];
#endif // _gamepads_h__

93
gc_kb.c Normal file
View File

@ -0,0 +1,93 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2013 Raphael Assenat <raph@raphnet.net>
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <avr/pgmspace.h>
#include "gc_kb.h"
#include "hid_keycodes.h"
#include "gcn64_protocol.h"
// http://www2d.biglobe.ne.jp/~msyk/keyboard/layout/usbkeycode.html
static const unsigned char gc_to_hid_table[] PROGMEM = {
GC_KEY_RESERVED, HID_KB_NOEVENT,
GC_KEY_HOME, HID_KB_HOME,
GC_KEY_END, HID_KB_END,
GC_KEY_PGUP, HID_KB_PGUP,
GC_KEY_PGDN, HID_KB_PGDN,
GC_KEY_SCROLL_LOCK, HID_KB_SCROLL_LOCK,
GC_KEY_DASH_UNDERSCORE, HID_KB_DASH_UNDERSCORE,
GC_KEY_PLUS_EQUAL, HID_KB_EQUAL_PLUS,
GC_KEY_YEN, HID_KB_INTERNATIONAL3,
GC_KEY_OPEN_BRKT_BRACE, HID_KB_OPEN_BRKT_BRACE,
GC_KEY_SEMI_COLON_COLON,HID_KB_SEMI_COLON_COLON,
GC_KEY_QUOTES, HID_KB_QUOTES,
GC_KEY_CLOSE_BRKT_BRACE,HID_KB_CLOSE_BRKT_BRACE,
GC_KEY_BRACKET_MU, HID_KB_NONUS_HASH_TILDE,
GC_KEY_COMMA_ST, HID_KB_COMMA_SMALLER_THAN,
GC_KEY_PERIOD_GT, HID_KB_PERIOD_GREATER_THAN,
GC_KEY_SLASH_QUESTION, HID_KB_SLASH_QUESTION,
GC_KEY_INTERNATIONAL1, HID_KB_INTERNATIONAL1,
GC_KEY_ESC, HID_KB_ESCAPE,
GC_KEY_INSERT, HID_KB_INSERT,
GC_KEY_DELETE, HID_KB_DELETE_FORWARD,
GC_KEY_HANKAKU, HID_KB_GRAVE_ACCENT_AND_TILDE,
GC_KEY_BACKSPACE, HID_KB_BACKSPACE,
GC_KEY_TAB, HID_KB_TAB,
GC_KEY_CAPS_LOCK, HID_KB_CAPS_LOCK,
GC_KEY_MUHENKAN, HID_KB_INTERNATIONAL5,
GC_KEY_SPACE, HID_KB_SPACE,
GC_KEY_HENKAN, HID_KB_INTERNATIONAL4,
GC_KEY_KANA, HID_KB_INTERNATIONAL2,
GC_KEY_LEFT, HID_KB_LEFT_ARROW,
GC_KEY_DOWN, HID_KB_DOWN_ARROW,
GC_KEY_UP, HID_KB_UP_ARROW,
GC_KEY_RIGHT, HID_KB_RIGHT_ARROW,
GC_KEY_ENTER, HID_KB_ENTER,
/* "shift" keys */
GC_KEY_LEFT_SHIFT, HID_KB_LEFT_SHIFT,
GC_KEY_RIGHT_SHIFT, HID_KB_RIGHT_SHIFT,
GC_KEY_LEFT_CTRL, HID_KB_LEFT_CONTROL,
/* This keyboard only has a left alt key. But as right alt is required to access some
* functions on japanese keyboards, I map the key to right alt.
*
* eg: RO-MAJI on the hiragana/katakana key */
GC_KEY_LEFT_ALT, HID_KB_RIGHT_ALT,
};
unsigned char gcKeycodeToHID(unsigned char gc_code)
{
int i;
if (gc_code >= GC_KEY_A && gc_code <= GC_KEY_0) {
// Note: This works since A-Z, 1-9, 0 have consecutive keycode values.
return (gc_code - GC_KEY_A) + HID_KB_A;
}
if (gc_code >= GC_KEY_F1 && gc_code <= GC_KEY_F12) {
return (gc_code - GC_KEY_F1) + HID_KB_F1;
}
for (i=0; i<sizeof(gc_to_hid_table); i++) {
if (pgm_read_byte(gc_to_hid_table + i*2) == gc_code) {
return pgm_read_byte(gc_to_hid_table + i*2 + 1);
}
}
return 0x38; // HID /? key for unknown keys
}

3
gc_kb.h Normal file
View File

@ -0,0 +1,3 @@
unsigned char gcKeycodeToHID(unsigned char gc_code);

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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
@ -31,13 +31,19 @@
#define GCN64_DATA_PORT PORTD
#define GCN64_DATA_DDR DDRD
#define GCN64_DATA_PIN PIND
#define GCN64_DATA_BIT (1<<0)
#define GCN64_DATA_BIT0 (1<<0)
#define GCN64_DATA_BIT1 (1<<1)
#define GCN64_DATA_BIT2 (1<<2)
#define GCN64_DATA_BIT3 (1<<3)
#define GCN64_BIT_NUM_S "0" // for asm
#else
#define GCN64_DATA_PORT PORTA
#define GCN64_DATA_DDR DDRA
#define GCN64_DATA_PIN PINA
#define GCN64_DATA_BIT (1<<0)
#define GCN64_DATA_BIT0 (1<<0)
#define GCN64_DATA_BIT1 (1<<2) // This is not an error
#define GCN64_DATA_BIT2 (1<<1) // This is not an error
#define GCN64_DATA_BIT3 (1<<3)
#define GCN64_BIT_NUM_S "0" // for asm
#endif
@ -46,11 +52,11 @@
void gcn64protocol_hwinit(void)
{
// data as input
GCN64_DATA_DDR &= ~(GCN64_DATA_BIT);
GCN64_DATA_DDR &= ~(GCN64_DATA_BIT0 | GCN64_DATA_BIT1 | GCN64_DATA_BIT2 | GCN64_DATA_BIT3);
// keep data low. By toggling the direction, we make the
// pin act as an open-drain output.
GCN64_DATA_PORT &= ~GCN64_DATA_BIT;
GCN64_DATA_PORT &= ~(GCN64_DATA_BIT0 | GCN64_DATA_BIT1 | GCN64_DATA_BIT2 | GCN64_DATA_BIT3);
/* debug bit PORTB4 (MISO) */
DDRB |= 0x10;
@ -59,15 +65,31 @@ void gcn64protocol_hwinit(void)
/**
* \brief Send n data bytes + stop bit, wait for answer.
* \return The number of bytes received, 0 on timeout/error.
*
* The result is in gcn64_workbuf.
* \param chn Channel (0 to 3)
* \param tx Data to be transmitted
* \param tx_len Transmission length
* \param rx Reception buffer
* \param rx_max Buffer size
* \return The number of bytes received, 0 on timeout/error.
*/
unsigned char gcn64_transaction(const unsigned char *tx, int tx_len, unsigned char *rx, unsigned char rx_max)
unsigned char gcn64_transaction(unsigned char chn, const unsigned char *tx, int tx_len, unsigned char *rx, unsigned char rx_max)
{
int count;
unsigned char sreg = SREG;
void (*sendBytes)(const unsigned char *data, unsigned char n_bytes);
unsigned char (*receiveBytes)(unsigned char *dstbuf, unsigned char max_bytes);
switch(chn)
{
case 0: sendBytes = gcn64_sendBytes0; receiveBytes = gcn64_receiveBytes0; break;
case 1: sendBytes = gcn64_sendBytes1; receiveBytes = gcn64_receiveBytes1; break;
case 2: sendBytes = gcn64_sendBytes2; receiveBytes = gcn64_receiveBytes2; break;
case 3: sendBytes = gcn64_sendBytes3; receiveBytes = gcn64_receiveBytes3; break;
default: return 0;
}
#ifdef TRACE_GCN64
int i;
@ -81,8 +103,8 @@ unsigned char gcn64_transaction(const unsigned char *tx, int tx_len, unsigned ch
#ifdef DISABLE_INTS_DURING_COMM
cli();
#endif
gcn64_sendBytes(tx, tx_len);
count = gcn64_receiveBytes(rx, rx_max);
sendBytes(tx, tx_len);
count = receiveBytes(rx, rx_max);
SREG = sreg;
if (count == 0xff) {
@ -119,14 +141,14 @@ unsigned char gcn64_transaction(const unsigned char *tx, int tx_len, unsigned ch
#if (GC_GETID != N64_GET_CAPABILITIES)
#error N64 vs GC detection commnad broken
#endif
int gcn64_detectController(void)
int gcn64_detectController(unsigned char chn)
{
unsigned char tmp = GC_GETID;
unsigned char count;
unsigned short id;
unsigned char data[4];
count = gcn64_transaction(&tmp, 1, data, sizeof(data));
count = gcn64_transaction(chn, &tmp, 1, data, sizeof(data));
if (count == 0) {
return CONTROLLER_IS_ABSENT;
}
@ -159,6 +181,9 @@ int gcn64_detectController(void)
* 0000 0101 0000 0000 0000 0001 : 0x050001 With expansion pack
* 0000 0101 0000 0000 0000 0010 : 0x050002 Expansion pack removed
*
* -- N64 mouse --
* 0000 0010 0000 0000 0000 0000 : 0x020000
*
* -- Ascii keyboard (keyboard connector)
* 0000 1000 0010 0000 0000 0000 : 0x082000
*
@ -193,6 +218,9 @@ int gcn64_detectController(void)
#endif
switch ((id >> 8) & 0x0F) {
case 0x02:
return CONTROLLER_IS_N64_MOUSE;
case 0x05:
return CONTROLLER_IS_N64;

View File

@ -6,6 +6,7 @@
#define CONTROLLER_IS_GC 2
#define CONTROLLER_IS_GC_KEYBOARD 3
#define CONTROLLER_IS_UNKNOWN 4
#define CONTROLLER_IS_N64_MOUSE 5
/* Return many unknown bits, but two are about the expansion port. */
@ -160,7 +161,12 @@
#define GC_KEY_ENTER 0x61
void gcn64protocol_hwinit(void);
int gcn64_detectController(void);
unsigned char gcn64_transaction(const unsigned char *tx, int tx_len, unsigned char *rx, unsigned char rx_max);
#define GCN64_CHANNEL_0 0
#define GCN64_CHANNEL_1 1
#define GCN64_CHANNEL_2 2
#define GCN64_CHANNEL_3 3
int gcn64_detectController(unsigned char chn);
unsigned char gcn64_transaction(unsigned char chn, const unsigned char *tx, int tx_len, unsigned char *rx, unsigned char rx_max);
#endif // _gcn64_protocol_h__

View File

@ -1,8 +1,20 @@
#include <avr/io.h>
; When compiling, you must define the following.
;
; SUFFIX : The suffix for exported function (eg: Set to 0 to generate gcn64_sendBytes0)
; GCN64_DATA_BIT : The bit number in the port.
;
; Port and Pin IOs are defined below.
;
#define CONCAT(a,b) a##b
#define EXPORT_SYMBOL(a, b) .global CONCAT(a,b)
#define FUNCTION(a, b) CONCAT(a,b)
.text
.global gcn64_sendBytes
.global gcn64_receiveBytes
EXPORT_SYMBOL(gcn64_sendBytes, SUFFIX)
EXPORT_SYMBOL(gcn64_receiveBytes, SUFFIX)
#define xl r26
#define xh r27
@ -24,7 +36,7 @@
#define GCN64_DATA_DDR _SFR_IO_ADDR(DDRD)
#define GCN64_DATA_PIN _SFR_IO_ADDR(PIND)
#endif
#define GCN64_DATA_BIT 0
;#define GCN64_DATA_BIT 0
#if F_CPU != 16000000L
#error Only 16MHz clock supported
@ -50,7 +62,7 @@
; r24,r25 : dstbuf
; r22 : max bytes (for fututre use)
; return: count in r24,r25 (0xff: Error, 0xfe: Overflow [max_bytes too low])
gcn64_receiveBytes:
FUNCTION(gcn64_receiveBytes, SUFFIX):
clr xl
clr xh
mov zl, r24
@ -149,7 +161,7 @@ rxdone:
* A stop bit is added at thy end of the packet.
*
************************************************/
gcn64_sendBytes:
FUNCTION(gcn64_sendBytes, SUFFIX):
; Move r23,r24 pointer to z
mov zl, r24
mov zh, r25

View File

@ -1,7 +1,10 @@
#ifndef _gcn64txrx_h__
#define _gcn64txrx_h__
void gcn64_sendBytes(const unsigned char *data, unsigned char n_bytes);
void gcn64_sendBytes0(const unsigned char *data, unsigned char n_bytes);
void gcn64_sendBytes1(const unsigned char *data, unsigned char n_bytes);
void gcn64_sendBytes2(const unsigned char *data, unsigned char n_bytes);
void gcn64_sendBytes3(const unsigned char *data, unsigned char n_bytes);
/**
* \brief Receive up to \max_bytes bytes
@ -9,6 +12,9 @@ void gcn64_sendBytes(const unsigned char *data, unsigned char n_bytes);
* \param max_bytes The maximum number of bytes
* \return The number of received bytes. 0xFF in case of error, 0xFE in case of overflow (max_bytes too small)
*/
unsigned char gcn64_receiveBytes(unsigned char *dstbuf, unsigned char max_bytes);
unsigned char gcn64_receiveBytes0(unsigned char *dstbuf, unsigned char max_bytes);
unsigned char gcn64_receiveBytes1(unsigned char *dstbuf, unsigned char max_bytes);
unsigned char gcn64_receiveBytes2(unsigned char *dstbuf, unsigned char max_bytes);
unsigned char gcn64_receiveBytes3(unsigned char *dstbuf, unsigned char max_bytes);
#endif // _gcn64txrx_h__

110
hid_keycodes.h Normal file
View File

@ -0,0 +1,110 @@
#ifndef _hid_keycodes_h__
#define _hid_keycodes_h__
/* From Hut1_12.pdf : Universal Serial Bus HID Usage Tables,
* section 10 Keyboard/Keypad Page (0x07) */
#define HID_KB_NOEVENT 0x00
#define HID_KB_A 0x04
#define HID_KB_B 0x05
#define HID_KB_C 0x06
#define HID_KB_D 0x07
#define HID_KB_E 0x08
#define HID_KB_F 0x09
#define HID_KB_G 0x0a
#define HID_KB_H 0x0b
#define HID_KB_I 0x0c
#define HID_KB_J 0x0d
#define HID_KB_K 0x0e
#define HID_KB_L 0x0f
#define HID_KB_M 0x10
#define HID_KB_N 0x11
#define HID_KB_O 0x12
#define HID_KB_P 0x13
#define HID_KB_Q 0x14
#define HID_KB_R 0x15
#define HID_KB_S 0x16
#define HID_KB_T 0x17
#define HID_KB_U 0x18
#define HID_KB_V 0x19
#define HID_KB_W 0x1a
#define HID_KB_X 0x1b
#define HID_KB_Y 0x1c
#define HID_KB_Z 0x1d
#define HID_KB_1 0x1e
#define HID_KB_2 0x1f
#define HID_KB_3 0x20
#define HID_KB_4 0x21
#define HID_KB_5 0x22
#define HID_KB_6 0x23
#define HID_KB_7 0x24
#define HID_KB_8 0x25
#define HID_KB_9 0x26
#define HID_KB_0 0x27
#define HID_KB_ESCAPE 0x29
#define HID_KB_BACKSPACE 0x2a
#define HID_KB_TAB 0x2b
#define HID_KB_SPACE 0x2c
#define HID_KB_DASH_UNDERSCORE 0x2d
#define HID_KB_EQUAL_PLUS 0x2e
#define HID_KB_OPEN_BRKT_BRACE 0x2f
#define HID_KB_CLOSE_BRKT_BRACE 0x30
#define HID_KB_NONUS_HASH_TILDE 0x32
#define HID_KB_SEMI_COLON_COLON 0x33
#define HID_KB_QUOTES 0x34
#define HID_KB_GRAVE_ACCENT_AND_TILDE 0x35
#define HID_KB_COMMA_SMALLER_THAN 0x36
#define HID_KB_PERIOD_GREATER_THAN 0x37
#define HID_KB_SLASH_QUESTION 0x38
#define HID_KB_CAPS_LOCK 0x39
#define HID_KB_F1 0x3a
#define HID_KB_F2 0x3b
#define HID_KB_F3 0x3c
#define HID_KB_F4 0x3d
#define HID_KB_F5 0x3e
#define HID_KB_F6 0x3f
#define HID_KB_F7 0x40
#define HID_KB_F8 0x41
#define HID_KB_F9 0x42
#define HID_KB_F10 0x43
#define HID_KB_F11 0x44
#define HID_KB_F12 0x45
#define HID_KB_SCROLL_LOCK 0x47
#define HID_KB_INSERT 0x49
#define HID_KB_HOME 0x4a
#define HID_KB_PGUP 0x4b
#define HID_KB_DELETE_FORWARD 0x4c
#define HID_KB_END 0x4d
#define HID_KB_PGDN 0x4e
#define HID_KB_RIGHT_ARROW 0x4f
#define HID_KB_LEFT_ARROW 0x50
#define HID_KB_DOWN_ARROW 0x51
#define HID_KB_UP_ARROW 0x52
#define HID_KB_ENTER 0x28
#define HID_KB_INTERNATIONAL1 0x87
#define HID_KB_INTERNATIONAL2 0x88
#define HID_KB_INTERNATIONAL3 0x89
#define HID_KB_INTERNATIONAL4 0x8a
#define HID_KB_INTERNATIONAL5 0x8b
#define HID_KB_LEFT_CONTROL 0xe0
#define HID_KB_LEFT_SHIFT 0xe1
#define HID_KB_LEFT_ALT 0xe2
#define HID_KB_LEFT_GUI 0xe3
#define HID_KB_RIGHT_CONTROL 0xe4
#define HID_KB_RIGHT_SHIFT 0xe5
#define HID_KB_RIGHT_ALT 0xe6
#define HID_KB_RIGHT_GUI 0xe7
#endif // _hid_keycodes_h__

118
hiddata.c
View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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
@ -25,8 +25,8 @@
#include "version.h"
#include "main.h"
// dataHidReport is 40 bytes.
#define CMDBUF_SIZE 41
// dataHidReport is 63 bytes. Endpoint is 64 bytes.
#define CMDBUF_SIZE 64
#define STATE_IDLE 0
#define STATE_NEW_COMMAND 1 // New command in buffer
@ -41,7 +41,7 @@ static unsigned char cmdbuf[CMDBUF_SIZE];
static volatile unsigned char cmdbuf_len = 0;
/*** Get/Set report called from interrupt context! */
uint16_t hiddata_get_report(struct usb_request *rq, const uint8_t **dat)
uint16_t hiddata_get_report(void *ctx, struct usb_request *rq, const uint8_t **dat)
{
// printf("Get data\n");
if (state == STATE_COMMAND_DONE) {
@ -56,7 +56,7 @@ uint16_t hiddata_get_report(struct usb_request *rq, const uint8_t **dat)
}
/*** Get/Set report called from interrupt context! */
uint8_t hiddata_set_report(const struct usb_request *rq, const uint8_t *dat, uint16_t len)
uint8_t hiddata_set_report(void *ctx, const struct usb_request *rq, const uint8_t *dat, uint16_t len)
{
#ifdef DEBUG
int i;
@ -74,9 +74,54 @@ uint8_t hiddata_set_report(const struct usb_request *rq, const uint8_t *dat, uin
return 0;
}
static void hiddata_processCommandBuffer(void)
static uint8_t processBlockIO(void)
{
//unsigned char channel;
uint8_t requestCopy[CMDBUF_SIZE];
int i, rx_offset = 0, rx;
uint8_t chn, n_tx, n_rx;
memcpy(requestCopy, cmdbuf, CMDBUF_SIZE);
memset(cmdbuf + 1, 0xff, CMDBUF_SIZE-1);
for (rx_offset = 1, i=1; i<CMDBUF_SIZE; ) {
if (i + 3 >= CMDBUF_SIZE)
break;
chn = requestCopy[i];
if (chn == 0xff)
break;
i++;
n_tx = requestCopy[i];
i++;
n_rx = requestCopy[i];
i++;
if (n_tx == 0)
continue;
if (rx_offset + 1 + n_rx >= CMDBUF_SIZE) {
break;
}
rx = gcn64_transaction(chn, requestCopy + i, n_tx, cmdbuf + rx_offset + 1, n_rx);
cmdbuf[rx_offset] = n_rx;
if (rx <= 0) {
// timeout
cmdbuf[rx_offset] |= 0x80;
} else if (rx < n_rx) {
// less than expected
cmdbuf[rx_offset] |= 0x40;
}
rx_offset += n_rx + 1;
i += n_tx;
}
return 63;
}
static void hiddata_processCommandBuffer(struct hiddata_ops *ops)
{
unsigned char channel;
#ifdef DEBUG
int i;
#endif
@ -89,14 +134,23 @@ static void hiddata_processCommandBuffer(void)
// printf("Process cmd 0x%02x\r\n", cmdbuf[0]);
switch(cmdbuf[0])
{
case RQ_GCN64_ECHO:
// Cmd : RQ, data[]
// Answer: RQ, data[]
break;
case RQ_GCN64_JUMP_TO_BOOTLOADER:
enterBootLoader();
break;
case RQ_RNT_RESET_FIRMWARE:
resetFirmware();
break;
case RQ_GCN64_RAW_SI_COMMAND:
// TODO : Range checking
// cmdbuf[] : RQ, CHN, LEN, data[]
//channel = cmdbuf[1];
cmdbuf_len = gcn64_transaction(cmdbuf+3, cmdbuf[2], cmdbuf + 3, CMDBUF_SIZE-3);
channel = cmdbuf[1];
if (channel >= NUM_CHANNELS)
break;
cmdbuf_len = gcn64_transaction(channel, cmdbuf+3, cmdbuf[2], cmdbuf + 3, CMDBUF_SIZE-3);
cmdbuf[2] = cmdbuf_len;
cmdbuf_len += 3; // Answer: RQ, CHN, LEN, data[]
break;
@ -114,7 +168,9 @@ static void hiddata_processCommandBuffer(void)
break;
case RQ_GCN64_SUSPEND_POLLING:
// CMD: RQ, PARAM
g_polling_suspended = cmdbuf[1];
if (ops && ops->suspendPolling) {
ops->suspendPolling(cmdbuf[1]);
}
break;
case RQ_GCN64_GET_VERSION:
// CMD: RQ
@ -129,15 +185,49 @@ static void hiddata_processCommandBuffer(void)
case RQ_GCN64_GET_CONTROLLER_TYPE:
// CMD : RQ, CHN
// Answer: RQ, CHN, TYPE
cmdbuf[2] = current_pad_type;
channel = cmdbuf[1];
if (channel >= NUM_CHANNELS)
break;
cmdbuf[2] = current_pad_type[channel];
cmdbuf_len = 3;
break;
case RQ_GCN64_SET_VIBRATION:
// CMD : RQ, CHN, Vibrate
// Answer: RQ, CHN, Vibrate
usbpad_forceVibrate(cmdbuf[2]);
if (ops && ops->forceVibration) {
ops->forceVibration(cmdbuf[1], cmdbuf[2]);
}
cmdbuf_len = 3;
break;
case RQ_GCN64_BLOCK_IO:
cmdbuf_len = processBlockIO();
break;
case RQ_RNT_GET_SUPPORTED_REQUESTS:
cmdbuf[1] = RQ_GCN64_JUMP_TO_BOOTLOADER;
cmdbuf[2] = RQ_GCN64_RAW_SI_COMMAND;
cmdbuf[3] = RQ_GCN64_GET_CONFIG_PARAM;
cmdbuf[4] = RQ_GCN64_SET_CONFIG_PARAM;
cmdbuf[5] = RQ_GCN64_SUSPEND_POLLING;
cmdbuf[6] = RQ_GCN64_GET_VERSION;
cmdbuf[7] = RQ_GCN64_GET_SIGNATURE;
cmdbuf[8] = RQ_GCN64_GET_CONTROLLER_TYPE;
cmdbuf[9] = RQ_GCN64_SET_VIBRATION;
cmdbuf[10] = RQ_GCN64_BLOCK_IO;
cmdbuf[11] = RQ_RNT_GET_SUPPORTED_CFG_PARAMS;
cmdbuf[12] = RQ_RNT_GET_SUPPORTED_MODES;
cmdbuf[13] = RQ_RNT_GET_SUPPORTED_REQUESTS;
cmdbuf_len = 14;
break;
case RQ_RNT_GET_SUPPORTED_CFG_PARAMS:
cmdbuf_len = 1 + config_getSupportedParams(cmdbuf + 1);
break;
case RQ_RNT_GET_SUPPORTED_MODES:
cmdbuf_len = 1;
if (ops && ops->getSupportedModes) {
cmdbuf_len += ops->getSupportedModes(cmdbuf + 1);
}
break;
}
#ifdef DEBUG
@ -151,7 +241,7 @@ static void hiddata_processCommandBuffer(void)
state = STATE_COMMAND_DONE;
}
void hiddata_doTask(void)
void hiddata_doTask(struct hiddata_ops *ops)
{
switch (state)
{
@ -161,7 +251,7 @@ void hiddata_doTask(void)
break;
case STATE_NEW_COMMAND:
hiddata_processCommandBuffer();
hiddata_processCommandBuffer(ops);
break;
case STATE_COMMAND_DONE:

View File

@ -4,9 +4,15 @@
#include <stdint.h>
#include "usb.h"
uint16_t hiddata_get_report(struct usb_request *rq, const uint8_t **dat);
uint8_t hiddata_set_report(const struct usb_request *rq, const uint8_t *dat, uint16_t len);
struct hiddata_ops {
void (*suspendPolling)(uint8_t suspend);
void (*forceVibration)(uint8_t channel, uint8_t force);
uint8_t (*getSupportedModes)(uint8_t *dst);
};
void hiddata_doTask(void);
uint16_t hiddata_get_report(void *ctx, struct usb_request *rq, const uint8_t **dat);
uint8_t hiddata_set_report(void *ctx, const struct usb_request *rq, const uint8_t *dat, uint16_t len);
void hiddata_doTask(struct hiddata_ops *ops);
#endif

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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

45
intervaltimer2.c Normal file
View File

@ -0,0 +1,45 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <avr/io.h>
#include "intervaltimer2.h"
void intervaltimer2_init(void)
{
TCCR0A = (1<<WGM01);
TCCR0B = (1<<CS02) | (1<<CS00); // CTC, /1024 prescaler
}
void intervaltimer2_set16ms(void)
{
int interval_ms = 16;
// Maximum 16ms. 17ms overflows the 8-bit OCR0A register (value of 265)
// 16ms: 250
TCNT0 = 0;
OCR0A = interval_ms * (F_CPU/1024) / 1000;
}
char intervaltimer2_get(void)
{
if (TIFR0 & (1<<OCF0A)) {
TIFR0 = 1<<OCF0A;
return 1;
}
return 0;
}

9
intervaltimer2.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef _interval_timer2_h__
#define _interval_timer2_h__
void intervaltimer2_init(void);
void intervaltimer2_set16ms(void);
char intervaltimer2_get(void);
#endif // _interval_timer_h__

814
main.c
View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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
@ -36,6 +36,25 @@
#include "hiddata.h"
#include "usbstrings.h"
#include "intervaltimer.h"
#include "intervaltimer2.h"
#include "requests.h"
#include "stkchk.h"
#define MAX_PLAYERS 2
#define GCN64_USB_PID 0x0060
#define N64_USB_PID 0x0061
#define GC_USB_PID 0x0062
#define DUAL_GCN64_USB_PID 0x0063
#define DUAL_N64_USB_PID 0x0064
#define DUAL_GC_USB_PID 0x0065
#define KEYBOARD_PID 0x0066
#define KEYBOARD_PID2 0x0067
#define KEYBOARD_JS_PID 0x0068
int keyboard_main(void);
/* Those .c files are included rather than linked for we
* want the sizeof() operator to work on the arrays */
@ -43,6 +62,7 @@
#include "dataHidReport.c"
#define MAX_READ_ERRORS 30
static uint8_t error_count[MAX_PLAYERS] = { };
struct cfg0 {
struct usb_configuration_descriptor configdesc;
@ -60,13 +80,13 @@ static const struct cfg0 cfg0 PROGMEM = {
.bLength = sizeof(struct usb_configuration_descriptor),
.bDescriptorType = CONFIGURATION_DESCRIPTOR,
.wTotalLength = sizeof(cfg0), // includes all descriptors returned together
.bNumInterfaces = 2,
.bNumInterfaces = 1 + 1, // one interface per player + one management interface
.bConfigurationValue = 1,
.bmAttributes = CFG_DESC_ATTR_RESERVED, // set Self-powred and remote-wakeup here if needed.
.bMaxPower = 25, // for 50mA
},
// Main interface, HID
// Main interface, HID (player 1)
.interface = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = INTERFACE_DESCRIPTOR,
@ -91,7 +111,7 @@ static const struct cfg0 cfg0 PROGMEM = {
.bDescriptorType = ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_RQT_DEVICE_TO_HOST | 1, // 0x81
.bmAttributes = TRANSFER_TYPE_INT,
.wMaxPacketsize = 64,
.wMaxPacketsize = 16,
.bInterval = LS_FS_INTERVAL_MS(1),
},
@ -123,20 +143,305 @@ static const struct cfg0 cfg0 PROGMEM = {
.wMaxPacketsize = 64,
.bInterval = LS_FS_INTERVAL_MS(1),
},
};
const struct usb_device_descriptor device_descriptor PROGMEM = {
static const struct cfg0 cfg0_kb PROGMEM = {
.configdesc = {
.bLength = sizeof(struct usb_configuration_descriptor),
.bDescriptorType = CONFIGURATION_DESCRIPTOR,
.wTotalLength = sizeof(cfg0), // includes all descriptors returned together
.bNumInterfaces = 1 + 1, // one interface per player + one management interface
.bConfigurationValue = 1,
.bmAttributes = CFG_DESC_ATTR_RESERVED, // set Self-powred and remote-wakeup here if needed.
.bMaxPower = 25, // for 50mA
},
// Main interface, HID Keyboard
.interface = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = INTERFACE_DESCRIPTOR,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_DEVICE_CLASS_HID,
.bInterfaceSubClass = HID_SUBCLASS_NONE,
.bInterfaceProtocol = HID_PROTOCOL_NONE,
},
.hid = {
.bLength = sizeof(struct usb_hid_descriptor),
.bDescriptorType = HID_DESCRIPTOR,
.bcdHid = 0x0101,
.bCountryCode = HID_COUNTRY_NOT_SUPPORTED,
.bNumDescriptors = 1, // Only a report descriptor
.bClassDescriptorType = REPORT_DESCRIPTOR,
.wClassDescriptorLength = sizeof(gcKeyboardReport),
},
.ep1_in = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_RQT_DEVICE_TO_HOST | 1, // 0x81
.bmAttributes = TRANSFER_TYPE_INT,
.wMaxPacketsize = 16,
.bInterval = LS_FS_INTERVAL_MS(1),
},
// Second HID interface for config and update
.interface_admin = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = INTERFACE_DESCRIPTOR,
.bInterfaceNumber = 1,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_DEVICE_CLASS_HID,
.bInterfaceSubClass = HID_SUBCLASS_NONE,
.bInterfaceProtocol = HID_PROTOCOL_NONE,
},
.hid_data = {
.bLength = sizeof(struct usb_hid_descriptor),
.bDescriptorType = HID_DESCRIPTOR,
.bcdHid = 0x0101,
.bCountryCode = HID_COUNTRY_NOT_SUPPORTED,
.bNumDescriptors = 1, // Only a report descriptor
.bClassDescriptorType = REPORT_DESCRIPTOR,
.wClassDescriptorLength = sizeof(dataHidReport),
},
.ep2_in = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_RQT_DEVICE_TO_HOST | 2, // 0x82
.bmAttributes = TRANSFER_TYPE_INT,
.wMaxPacketsize = 64,
.bInterval = LS_FS_INTERVAL_MS(1),
},
};
struct cfg0_2p {
struct usb_configuration_descriptor configdesc;
struct usb_interface_descriptor interface;
struct usb_hid_descriptor hid;
struct usb_endpoint_descriptor ep1_in;
struct usb_interface_descriptor interface_p2;
struct usb_hid_descriptor hid_p2;
struct usb_endpoint_descriptor ep2_in;
struct usb_interface_descriptor interface_admin;
struct usb_hid_descriptor hid_data;
struct usb_endpoint_descriptor ep3_in;
};
static const struct cfg0_2p cfg0_2p PROGMEM = {
.configdesc = {
.bLength = sizeof(struct usb_configuration_descriptor),
.bDescriptorType = CONFIGURATION_DESCRIPTOR,
.wTotalLength = sizeof(cfg0_2p), // includes all descriptors returned together
.bNumInterfaces = 2 + 1, // one interface per player + one management interface
.bConfigurationValue = 1,
.bmAttributes = CFG_DESC_ATTR_RESERVED, // set Self-powred and remote-wakeup here if needed.
.bMaxPower = 25, // for 50mA
},
// Main interface, HID (player 1)
.interface = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = INTERFACE_DESCRIPTOR,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_DEVICE_CLASS_HID,
.bInterfaceSubClass = HID_SUBCLASS_NONE,
.bInterfaceProtocol = HID_PROTOCOL_NONE,
},
.hid = {
.bLength = sizeof(struct usb_hid_descriptor),
.bDescriptorType = HID_DESCRIPTOR,
.bcdHid = 0x0101,
.bCountryCode = HID_COUNTRY_NOT_SUPPORTED,
.bNumDescriptors = 1, // Only a report descriptor
.bClassDescriptorType = REPORT_DESCRIPTOR,
.wClassDescriptorLength = sizeof(gcn64_usbHidReportDescriptor),
},
.ep1_in = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_RQT_DEVICE_TO_HOST | 1, // 0x81
.bmAttributes = TRANSFER_TYPE_INT,
.wMaxPacketsize = 16,
.bInterval = LS_FS_INTERVAL_MS(1),
},
// Main interface, HID (player 2)
.interface_p2 = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = INTERFACE_DESCRIPTOR,
.bInterfaceNumber = 1,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_DEVICE_CLASS_HID,
.bInterfaceSubClass = HID_SUBCLASS_NONE,
.bInterfaceProtocol = HID_PROTOCOL_NONE,
},
.hid_p2 = {
.bLength = sizeof(struct usb_hid_descriptor),
.bDescriptorType = HID_DESCRIPTOR,
.bcdHid = 0x0101,
.bCountryCode = HID_COUNTRY_NOT_SUPPORTED,
.bNumDescriptors = 1, // Only a report descriptor
.bClassDescriptorType = REPORT_DESCRIPTOR,
.wClassDescriptorLength = sizeof(gcn64_usbHidReportDescriptor),
},
.ep2_in = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_RQT_DEVICE_TO_HOST | 2, // 0x82
.bmAttributes = TRANSFER_TYPE_INT,
.wMaxPacketsize = 16,
.bInterval = LS_FS_INTERVAL_MS(1),
},
// Second HID interface for config and update
.interface_admin = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = INTERFACE_DESCRIPTOR,
.bInterfaceNumber = 2,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_DEVICE_CLASS_HID,
.bInterfaceSubClass = HID_SUBCLASS_NONE,
.bInterfaceProtocol = HID_PROTOCOL_NONE,
},
.hid_data = {
.bLength = sizeof(struct usb_hid_descriptor),
.bDescriptorType = HID_DESCRIPTOR,
.bcdHid = 0x0101,
.bCountryCode = HID_COUNTRY_NOT_SUPPORTED,
.bNumDescriptors = 1, // Only a report descriptor
.bClassDescriptorType = REPORT_DESCRIPTOR,
.wClassDescriptorLength = sizeof(dataHidReport),
},
.ep3_in = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_RQT_DEVICE_TO_HOST | 3, // 0x83
.bmAttributes = TRANSFER_TYPE_INT,
.wMaxPacketsize = 64,
.bInterval = LS_FS_INTERVAL_MS(1),
},
};
static const struct cfg0_2p cfg0_2p_keyboard PROGMEM = {
.configdesc = {
.bLength = sizeof(struct usb_configuration_descriptor),
.bDescriptorType = CONFIGURATION_DESCRIPTOR,
.wTotalLength = sizeof(cfg0_2p), // includes all descriptors returned together
.bNumInterfaces = 2 + 1, // one interface per player + one management interface
.bConfigurationValue = 1,
.bmAttributes = CFG_DESC_ATTR_RESERVED, // set Self-powred and remote-wakeup here if needed.
.bMaxPower = 25, // for 50mA
},
// Joystick interface
.interface = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = INTERFACE_DESCRIPTOR,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_DEVICE_CLASS_HID,
.bInterfaceSubClass = HID_SUBCLASS_NONE,
.bInterfaceProtocol = HID_PROTOCOL_NONE,
},
.hid = {
.bLength = sizeof(struct usb_hid_descriptor),
.bDescriptorType = HID_DESCRIPTOR,
.bcdHid = 0x0101,
.bCountryCode = HID_COUNTRY_NOT_SUPPORTED,
.bNumDescriptors = 1, // Only a report descriptor
.bClassDescriptorType = REPORT_DESCRIPTOR,
.wClassDescriptorLength = sizeof(gcn64_usbHidReportDescriptor),
},
.ep1_in = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_RQT_DEVICE_TO_HOST | 1, // 0x81
.bmAttributes = TRANSFER_TYPE_INT,
.wMaxPacketsize = 16,
.bInterval = LS_FS_INTERVAL_MS(1),
},
// HID Keyboard interface
.interface_p2 = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = INTERFACE_DESCRIPTOR,
.bInterfaceNumber = 1,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_DEVICE_CLASS_HID,
.bInterfaceSubClass = HID_SUBCLASS_NONE,
.bInterfaceProtocol = HID_PROTOCOL_NONE,
},
.hid_p2 = {
.bLength = sizeof(struct usb_hid_descriptor),
.bDescriptorType = HID_DESCRIPTOR,
.bcdHid = 0x0101,
.bCountryCode = HID_COUNTRY_NOT_SUPPORTED,
.bNumDescriptors = 1, // Only a report descriptor
.bClassDescriptorType = REPORT_DESCRIPTOR,
.wClassDescriptorLength = sizeof(gcKeyboardReport),
},
.ep2_in = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_RQT_DEVICE_TO_HOST | 2, // 0x82
.bmAttributes = TRANSFER_TYPE_INT,
.wMaxPacketsize = 16,
.bInterval = LS_FS_INTERVAL_MS(1),
},
// Second HID interface for config and update
.interface_admin = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = INTERFACE_DESCRIPTOR,
.bInterfaceNumber = 2,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_DEVICE_CLASS_HID,
.bInterfaceSubClass = HID_SUBCLASS_NONE,
.bInterfaceProtocol = HID_PROTOCOL_NONE,
},
.hid_data = {
.bLength = sizeof(struct usb_hid_descriptor),
.bDescriptorType = HID_DESCRIPTOR,
.bcdHid = 0x0101,
.bCountryCode = HID_COUNTRY_NOT_SUPPORTED,
.bNumDescriptors = 1, // Only a report descriptor
.bClassDescriptorType = REPORT_DESCRIPTOR,
.wClassDescriptorLength = sizeof(dataHidReport),
},
.ep3_in = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_RQT_DEVICE_TO_HOST | 3, // 0x83
.bmAttributes = TRANSFER_TYPE_INT,
.wMaxPacketsize = 64,
.bInterval = LS_FS_INTERVAL_MS(1),
},
};
struct usb_device_descriptor device_descriptor = {
.bLength = sizeof(struct usb_device_descriptor),
.bDescriptorType = DEVICE_DESCRIPTOR,
.bcdUSB = 0x0101,
.bcdUSB = 0x0110,
.bDeviceClass = 0, // set at interface
.bDeviceSubClass = 0, // set at interface
.bDeviceProtocol = 0,
.bMaxPacketSize = 64,
.idVendor = 0x289B,
.idProduct = 0x0017,
.bcdDevice = 0x0300, // 1.0.0
.idProduct = GCN64_USB_PID,
.bcdDevice = VERSIONBCD,
.bNumConfigurations = 1,
.iManufacturer = 1,
.iProduct = 2,
@ -145,29 +450,40 @@ const struct usb_device_descriptor device_descriptor PROGMEM = {
/** **/
static uint16_t _usbpad_hid_get_report(void *ctx, struct usb_request *rq, const uint8_t **dat)
{
return usbpad_hid_get_report((struct usbpad*)ctx, rq, dat);
}
static uint8_t _usbpad_hid_set_report(void *ctx, const struct usb_request *rq, const uint8_t *dat, uint16_t len)
{
return usbpad_hid_set_report((struct usbpad*)ctx, rq, dat, len);
}
static struct usb_parameters usb_params = {
.flags = USB_PARAM_FLAG_CONFDESC_PROGMEM |
USB_PARAM_FLAG_DEVDESC_PROGMEM |
USB_PARAM_FLAG_REPORTDESC_PROGMEM,
.devdesc = (PGM_VOID_P)&device_descriptor,
.configdesc = (PGM_VOID_P)&cfg0,
.configdesc_ttllen = sizeof(cfg0),
.configdesc = (PGM_VOID_P)&cfg0, // Patched in main() for two players
.configdesc_ttllen = sizeof(cfg0), // Patched in main() for two players
.num_strings = NUM_USB_STRINGS,
.strings = g_usb_strings,
.n_hid_interfaces = 2,
.n_hid_interfaces = 1 + 1, // One per player + one management interface (patched in main() for two players)
.hid_params = {
[0] = {
.reportdesc = gcn64_usbHidReportDescriptor,
.reportdesc_len = sizeof(gcn64_usbHidReportDescriptor),
.getReport = usbpad_hid_get_report,
.setReport = usbpad_hid_set_report,
.getReport = _usbpad_hid_get_report,
.setReport = _usbpad_hid_set_report,
.endpoint_size = 16,
},
[1] = {
.reportdesc = dataHidReport,
.reportdesc_len = sizeof(dataHidReport),
.getReport = hiddata_get_report,
.setReport = hiddata_set_report,
.endpoint_size = 64,
},
},
};
@ -225,28 +541,28 @@ void hwinit(void)
}
#define NUM_PAD_TYPES 2
uint8_t num_players = 1;
unsigned char current_pad_type[NUM_CHANNELS] = { };
unsigned char current_pad_type = CONTROLLER_IS_ABSENT;
Gamepad *detectPad(void)
Gamepad *detectPad(unsigned char chn)
{
current_pad_type = gcn64_detectController();
current_pad_type[chn] = gcn64_detectController(chn);
switch (current_pad_type)
switch (current_pad_type[chn])
{
case CONTROLLER_IS_ABSENT:
case CONTROLLER_IS_UNKNOWN:
return NULL;
case CONTROLLER_IS_N64_MOUSE:
case CONTROLLER_IS_N64:
printf("Detected N64 controller\n");
return n64GetGamepad();
break;
case CONTROLLER_IS_GC:
printf("Detected GC controller\n");
return gamecubeGetGamepad();
case CONTROLLER_IS_GC_KEYBOARD:
return gamecubeGetKeyboard();
}
return NULL;
@ -265,46 +581,176 @@ void eeprom_app_ready(void)
g_usb_strings[USB_STRING_SERIAL_IDX] = serial_from_eeprom;
}
char g_polling_suspended = 0;
static struct usbpad usbpads[MAX_PLAYERS];
static char g_polling_suspended = 0;
void pollDelay(void)
static void setSuspendPolling(uint8_t suspend)
{
int i;
for (i=0; i<g_eeprom_data.cfg.poll_interval[0]; i++) {
_delay_ms(1);
g_polling_suspended = suspend;
}
static void forceVibration(uint8_t channel, uint8_t force)
{
if (channel < MAX_PLAYERS) {
usbpad_forceVibrate(&usbpads[channel], force);
}
}
static uint8_t getSupportedModes(uint8_t *dst)
{
uint8_t idx = 0;
switch (g_eeprom_data.cfg.mode)
{
// Allow toggling between keyboard and joystick modes on
// single-port gamecube adapter
case CFG_MODE_GC_ONLY:
case CFG_MODE_KEYBOARD:
dst[idx++] = CFG_MODE_GC_ONLY;
dst[idx++] = CFG_MODE_KEYBOARD;
break;
// Allow toggling between two joysticks and joystick + keyboard modes
// on dual-port gamecube adapter
case CFG_MODE_2P_GC_ONLY:
case CFG_MODE_KB_AND_JS:
dst[idx++] = CFG_MODE_2P_GC_ONLY;
dst[idx++] = CFG_MODE_KB_AND_JS;
break;
// On N64/GC adapters, there is a GC port so we should support
// keyboards there. Use KEYBOARD_2 config here to avoid mixup
// with the GC-only adapter variation.
case CFG_MODE_STANDARD:
case CFG_MODE_KEYBOARD_2:
dst[idx++] = CFG_MODE_STANDARD;
dst[idx++] = CFG_MODE_KEYBOARD_2;
break;
default:
dst[idx++] = CFG_MODE_STANDARD;
dst[idx++] = CFG_MODE_N64_ONLY;
dst[idx++] = CFG_MODE_GC_ONLY;
dst[idx++] = CFG_MODE_2P_STANDARD;
dst[idx++] = CFG_MODE_2P_N64_ONLY;
dst[idx++] = CFG_MODE_2P_GC_ONLY;
dst[idx++] = CFG_MODE_KEYBOARD;
dst[idx++] = CFG_MODE_KB_AND_JS;
break;
}
return idx;
}
static struct hiddata_ops hiddata_ops = {
.suspendPolling = setSuspendPolling,
.forceVibration = forceVibration,
.getSupportedModes = getSupportedModes,
};
#define STATE_WAIT_POLLTIME 0
#define STATE_POLL_PAD 1
#define STATE_WAIT_INTERRUPT_READY 2
#define STATE_TRANSMIT 3
#define STATE_WAIT_INTERRUPT_READY_P2 4
#define STATE_TRANSMIT_P2 5
int main(void)
{
Gamepad *pad = NULL;
Gamepad *pads[MAX_PLAYERS] = { };
gamepad_data pad_data;
unsigned char gamepad_vibrate = 0;
unsigned char state = STATE_WAIT_POLLTIME;
int error_count=0;
uint8_t gamepad_vibrate = 0;
uint8_t state = STATE_WAIT_POLLTIME;
uint8_t channel;
uint8_t i;
hwinit();
usart1_init();
eeprom_init();
intervaltimer_init();
intervaltimer2_init();
stkchk_init();
/* Init the buffer with idle data */
usbpad_update(NULL);
switch (g_eeprom_data.cfg.mode)
{
default:
case CFG_MODE_STANDARD:
usbstrings_changeProductString_P(PSTR("GC/N64 to USB v"VERSIONSTR_SHORT));
break;
case CFG_MODE_N64_ONLY:
usbstrings_changeProductString_P(PSTR("N64 to USB v"VERSIONSTR_SHORT));
device_descriptor.idProduct = N64_USB_PID;
break;
case CFG_MODE_GC_ONLY:
usbstrings_changeProductString_P(PSTR("Gamecube to USB v"VERSIONSTR_SHORT));
device_descriptor.idProduct = GC_USB_PID;
break;
case CFG_MODE_2P_STANDARD:
usbstrings_changeProductString_P(PSTR("Dual GC/N64 to USB v"VERSIONSTR_SHORT));
device_descriptor.idProduct = DUAL_GCN64_USB_PID;
num_players = 2;
break;
case CFG_MODE_2P_N64_ONLY:
usbstrings_changeProductString_P(PSTR("Dual N64 to USB v"VERSIONSTR_SHORT));
device_descriptor.idProduct = DUAL_N64_USB_PID;
num_players = 2;
break;
case CFG_MODE_2P_GC_ONLY:
usbstrings_changeProductString_P(PSTR("Dual Gamecube to USB v"VERSIONSTR_SHORT));
device_descriptor.idProduct = DUAL_GC_USB_PID;
num_players = 2;
break;
case CFG_MODE_KB_AND_JS:
case CFG_MODE_KEYBOARD:
case CFG_MODE_KEYBOARD_2:
keyboard_main();
break;
}
// 2-players common
if (num_players == 2) {
usb_params.configdesc = (PGM_VOID_P)&cfg0_2p;
usb_params.configdesc_ttllen = sizeof(cfg0_2p);
usb_params.n_hid_interfaces = 3;
// Move the management interface is the last position
memcpy(usb_params.hid_params + 2, usb_params.hid_params + 1, sizeof(struct usb_hid_parameters));
// Add a second player interface between them
memcpy(usb_params.hid_params + 1, usb_params.hid_params + 0, sizeof(struct usb_hid_parameters));
}
for (i=0; i<num_players; i++) {
usbpad_init(&usbpads[i]);
usb_params.hid_params[i].ctx = &usbpads[i];
}
sei();
usb_init(&usb_params);
// Timebase for force feedback 'loop count'
intervaltimer2_set16ms();
while (1)
{
static char last_v = 0;
static char last_v[MAX_PLAYERS] = { };
if (stkchk_verify()) {
enterBootLoader();
}
usb_doTasks();
hiddata_doTask();
hiddata_doTask(&hiddata_ops);
// Run vibration tasks
if (intervaltimer2_get()) {
for (channel=0; channel < num_players; channel++) {
usbpad_vibrationTask(&usbpads[channel]);
}
}
switch(state)
{
@ -318,64 +764,282 @@ int main(void)
break;
case STATE_POLL_PAD:
/* Try to auto-detect controller if none*/
if (!pad) {
pad = detectPad();
if (pad && (pad->hotplug)) {
// For gamecube, this make sure the next
// analog values we read become the center
// reference.
pad->hotplug();
for (channel=0; channel<num_players; channel++)
{
/* Try to auto-detect controller if none*/
if (!pads[channel]) {
pads[channel] = detectPad(channel);
if (pads[channel] && (pads[channel]->hotplug)) {
// For gamecube, this make sure the next
// analog values we read become the center
// reference.
pads[channel]->hotplug(channel);
}
}
}
if (pad) {
if (pad->update()) {
error_count++;
if (error_count > MAX_READ_ERRORS) {
pad = NULL;
state = STATE_WAIT_POLLTIME;
error_count = 0;
break;
/* Read from the pad by calling update */
if (pads[channel]) {
if (pads[channel]->update(channel)) {
error_count[channel]++;
if (error_count[channel] > MAX_READ_ERRORS) {
pads[channel] = NULL;
error_count[channel] = 0;
continue;
}
} else {
error_count[channel]=0;
}
if (pads[channel]->changed(channel))
{
pads[channel]->getReport(channel, &pad_data);
usbpad_update(&usbpads[channel], &pad_data);
state = STATE_WAIT_INTERRUPT_READY;
continue;
}
} else {
error_count=0;
/* Just make sure the gamepad state holds valid data
* to appear inactive (no buttons and axes in neutral) */
usbpad_update(&usbpads[channel], NULL);
}
if (pad->changed()) {
pad->getReport(&pad_data);
usbpad_update(&pad_data);
state = STATE_WAIT_INTERRUPT_READY;
break;
}
} else {
/* Just make sure the gamepad state holds valid data
* to appear inactive (no buttons and axes in neutral) */
usbpad_update(NULL);
}
state = STATE_WAIT_POLLTIME;
/* If there were change on any of the gamepads, state will
* be set to STATE_WAIT_INTERRUPT_READY. Otherwise, go back
* to WAIT_POLLTIME. */
if (state == STATE_POLL_PAD) {
state = STATE_WAIT_POLLTIME;
}
break;
case STATE_WAIT_INTERRUPT_READY:
if (usb_interruptReady()) {
/* Wait until one of the interrupt endpoint is ready */
if (usb_interruptReady_ep1() || (num_players>1 && usb_interruptReady_ep2())) {
state = STATE_TRANSMIT;
}
break;
case STATE_TRANSMIT:
usb_interruptSend(usbpad_getReportBuffer(), usbpad_getReportSize());
if (usb_interruptReady_ep1()) {
usb_interruptSend_ep1(usbpad_getReportBuffer(&usbpads[0]), usbpad_getReportSize());
}
if (num_players>1 && usb_interruptReady_ep2()) {
usb_interruptSend_ep2(usbpad_getReportBuffer(&usbpads[1]), usbpad_getReportSize());
}
state = STATE_WAIT_POLLTIME;
break;
}
gamepad_vibrate = usbpad_mustVibrate();
if (last_v != gamepad_vibrate) {
if (pad && pad->setVibration) {
pad->setVibration(gamepad_vibrate);
for (channel=0; channel < num_players; channel++) {
gamepad_vibrate = usbpad_mustVibrate(&usbpads[channel]);
if (last_v[channel] != gamepad_vibrate) {
if (pads[channel] && pads[channel]->setVibration) {
pads[channel]->setVibration(channel, gamepad_vibrate);
}
last_v[channel] = gamepad_vibrate;
}
last_v = gamepad_vibrate;
}
}
return 0;
}
int keyboard_main(void)
{
Gamepad *pads[MAX_PLAYERS] = { };
gamepad_data pad_data;
uint8_t gamepad_vibrate = 0;
uint8_t state = STATE_WAIT_POLLTIME;
uint8_t channel;
uint8_t i;
hwinit();
usart1_init();
eeprom_init();
intervaltimer_init();
intervaltimer2_init();
stkchk_init();
switch (g_eeprom_data.cfg.mode)
{
default:
case CFG_MODE_KEYBOARD_2:
usbstrings_changeProductString_P(PSTR("KB to USB v"VERSIONSTR_SHORT));
device_descriptor.idProduct = KEYBOARD_PID2;
usb_params.configdesc = (PGM_VOID_P)&cfg0_kb;
usb_params.configdesc_ttllen = sizeof(cfg0_kb);
// replace Joystick report descriptor by keyboard
usb_params.hid_params[0].reportdesc = gcKeyboardReport;
usb_params.hid_params[0].reportdesc_len = sizeof(gcKeyboardReport);
break;
case CFG_MODE_KEYBOARD:
usbstrings_changeProductString_P(PSTR("GC KB to USB v"VERSIONSTR_SHORT));
device_descriptor.idProduct = KEYBOARD_PID;
usb_params.configdesc = (PGM_VOID_P)&cfg0_kb;
usb_params.configdesc_ttllen = sizeof(cfg0_kb);
// replace Joystick report descriptor by keyboard
usb_params.hid_params[0].reportdesc = gcKeyboardReport;
usb_params.hid_params[0].reportdesc_len = sizeof(gcKeyboardReport);
break;
case CFG_MODE_KB_AND_JS:
usbstrings_changeProductString_P(PSTR("GC KB+JS to USB v"VERSIONSTR_SHORT));
device_descriptor.idProduct = KEYBOARD_JS_PID;
usb_params.configdesc = (PGM_VOID_P)&cfg0_2p_keyboard;
usb_params.configdesc_ttllen = sizeof(cfg0_2p_keyboard);
// Move the management interface to the last position
memcpy(usb_params.hid_params + 2, usb_params.hid_params + 1, sizeof(struct usb_hid_parameters));
// Add a second player interface between them (still a joystick)
memcpy(usb_params.hid_params + 1, usb_params.hid_params + 0, sizeof(struct usb_hid_parameters));
// Convert second Joystick report descriptor to a keyboard
usb_params.hid_params[1].reportdesc = gcKeyboardReport;
usb_params.hid_params[1].reportdesc_len = sizeof(gcKeyboardReport);
usb_params.n_hid_interfaces = 3;
num_players = 2;
break;
}
for (i=0; i<num_players; i++) {
usbpad_init(&usbpads[i]);
usb_params.hid_params[i].ctx = &usbpads[i];
}
sei();
usb_init(&usb_params);
// Timebase for force feedback 'loop count'
intervaltimer2_set16ms();
while (1)
{
static char last_v[MAX_PLAYERS] = { };
if (stkchk_verify()) {
enterBootLoader();
}
usb_doTasks();
hiddata_doTask(&hiddata_ops);
// Run vibration tasks
if (intervaltimer2_get()) {
for (channel=0; channel < num_players; channel++) {
usbpad_vibrationTask(&usbpads[channel]);
}
}
switch(state)
{
case STATE_WAIT_POLLTIME:
if (!g_polling_suspended) {
intervaltimer_set(g_eeprom_data.cfg.poll_interval[0]);
if (intervaltimer_get()) {
state = STATE_POLL_PAD;
}
}
break;
case STATE_POLL_PAD:
for (channel=0; channel<num_players; channel++)
{
/* Try to auto-detect controller if none*/
if (!pads[channel]) {
pads[channel] = detectPad(channel);
if (pads[channel] && (pads[channel]->hotplug)) {
// For gamecube, this make sure the next
// analog values we read become the center
// reference.
pads[channel]->hotplug(channel);
}
}
/* Read from the pad by calling update */
if (pads[channel]) {
if (pads[channel]->update(channel)) {
error_count[channel]++;
if (error_count[channel] > MAX_READ_ERRORS) {
pads[channel] = NULL;
error_count[channel] = 0;
continue;
}
} else {
error_count[channel]=0;
}
if (pads[channel]->changed(channel))
{
pads[channel]->getReport(channel, &pad_data);
if ((num_players == 1) && (channel == 0)) {
// single-port adapter in keyboard mode (kb in port 1)
usbpad_update_kb(&usbpads[channel], &pad_data);
} else if ((num_players == 2) && (channel == 1)) {
// dual-port adapter in keyboard mode (kb in port 2)
usbpad_update_kb(&usbpads[channel], &pad_data);
} else {
usbpad_update(&usbpads[channel], &pad_data);
}
state = STATE_WAIT_INTERRUPT_READY;
continue;
}
} else {
/* Just make sure the gamepad state holds valid data
* to appear inactive (no buttons and axes in neutral) */
usbpad_update(&usbpads[channel], NULL);
}
}
/* If there were change on any of the gamepads, state will
* be set to STATE_WAIT_INTERRUPT_READY. Otherwise, go back
* to WAIT_POLLTIME. */
if (state == STATE_POLL_PAD) {
state = STATE_WAIT_POLLTIME;
}
break;
case STATE_WAIT_INTERRUPT_READY:
/* Wait until one of the interrupt endpoint is ready */
if (usb_interruptReady_ep1() || (num_players>1 && usb_interruptReady_ep2())) {
state = STATE_TRANSMIT;
}
break;
case STATE_TRANSMIT:
if (usb_interruptReady_ep1()) {
if (num_players == 1) {
// Single-port adapters have the keyboard in port 1
usb_interruptSend_ep1(usbpad_getReportBuffer(&usbpads[0]), usbpad_getReportSizeKB());
} else {
usb_interruptSend_ep1(usbpad_getReportBuffer(&usbpads[0]), usbpad_getReportSize());
}
}
// Keyboard is always in second port on dual port adapters
if (num_players>1 && usb_interruptReady_ep2()) {
usb_interruptSend_ep2(usbpad_getReportBuffer(&usbpads[1]), usbpad_getReportSizeKB());
}
state = STATE_WAIT_POLLTIME;
break;
}
for (channel=0; channel < num_players; channel++) {
gamepad_vibrate = usbpad_mustVibrate(&usbpads[channel]);
if (last_v[channel] != gamepad_vibrate) {
if (pads[channel] && pads[channel]->setVibration) {
pads[channel]->setVibration(channel, gamepad_vibrate);
}
last_v[channel] = gamepad_vibrate;
}
}
}
return 0;
}

7
main.h
View File

@ -1,6 +1,11 @@
#ifndef _main_h__
#define _main_h__
extern unsigned char current_pad_type;
#include <stdint.h>
#include "config.h"
extern unsigned char current_pad_type[NUM_CHANNELS];
extern uint8_t num_players;
#endif // _main_h__

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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

181
n64.c
View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2021 Raphael Assenat <raph@raphnet.net>
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
@ -21,36 +21,39 @@
#include "gamepads.h"
#include "n64.h"
#include "gcn64_protocol.h"
#include "eeprom.h"
#include "main.h" // for num_players
#undef BUTTON_A_RUMBLE_TEST
/*********** prototypes *************/
static void n64Init(void);
static char n64Update(void);
static char n64Changed(void);
static void n64GetReport(gamepad_data *dst);
static void n64SetVibration(char enable);
static void n64Init(unsigned char chn);
static char n64Update(unsigned char chn);
static char n64Changed(unsigned char chn);
static void n64GetReport(unsigned char chn, gamepad_data *dst);
static void n64SetVibration(unsigned char chn, char enable);
static char must_rumble = 0;
static char must_rumble[GAMEPAD_MAX_CHANNELS] = { };
#ifdef BUTTON_A_RUMBLE_TEST
static char force_rumble = 0;
static char force_rumble[GAMEPAD_MAX_CHANNELS] = { };
#endif
static unsigned char n64_rumble_state[GAMEPAD_MAX_CHANNELS] = { };
static void n64Init(void)
{
n64Update();
}
unsigned char tmpdata[40]; // Shared between channels
#define RSTATE_INIT 0
#define RSTATE_UNAVAILABLE 0
#define RSTATE_OFF 1
#define RSTATE_TURNON 2
#define RSTATE_ON 3
#define RSTATE_TURNOFF 4
#define RSTATE_UNAVAILABLE 5
static unsigned char n64_rumble_state = RSTATE_UNAVAILABLE;
unsigned char tmpdata[40];
#define RSTATE_INIT 5
static char initRumble(void)
static void n64Init(unsigned char chn)
{
n64Update(chn);
}
static char initRumble(unsigned char chn)
{
int count;
unsigned char data[4];
@ -60,14 +63,14 @@ static char initRumble(void)
tmpdata[2] = 0x01;
memset(tmpdata+3, 0x80, 32);
count = gcn64_transaction(tmpdata, 35, data, sizeof(data));
count = gcn64_transaction(chn, tmpdata, 35, data, sizeof(data));
if (count == 1)
return 0;
return -1;
}
static char controlRumble(char enable)
static char controlRumble(unsigned char chn, char enable)
{
int count;
unsigned char data[4];
@ -76,14 +79,14 @@ static char controlRumble(char enable)
tmpdata[1] = 0xc0;
tmpdata[2] = 0x1b;
memset(tmpdata+3, enable ? 0x01 : 0x00, 32);
count = gcn64_transaction(tmpdata, 35, data, sizeof(data));
count = gcn64_transaction(chn, tmpdata, 35, data, sizeof(data));
if (count == 1)
return 0;
return -1;
}
static char n64Update(void)
static char n64Update(unsigned char chn)
{
unsigned char count;
unsigned char x,y;
@ -101,78 +104,112 @@ static char n64Update(void)
* Bit 1 tells is if there was something connected that has been removed.
*/
tmpdata[0] = N64_GET_CAPABILITIES;
count = gcn64_transaction(tmpdata, 1, caps, sizeof(caps));
count = gcn64_transaction(chn, tmpdata, 1, caps, sizeof(caps));
if (count != N64_CAPS_REPLY_LENGTH) {
// a failed read could mean the pack or controller was gone. Init
// will be necessary next time we detect a pack is present.
n64_rumble_state = RSTATE_INIT;
n64_rumble_state[chn] = RSTATE_INIT;
return -1;
}
/* The brawler 64 wireless gamepad does not like when the get caps command is followed
* too closely by the get status command. Without a long pause between the two commands,
* it just returns all zeros. */
if (num_players > 1) {
// n64Update is called two times on two-player adapters. This
// means time lost waiting here doubles. Without this, the controllers
// are no longer being polled at the rate set in the gui, which is bad.
// (I want this setting to be reliable)
//
// So on dual port adapters, brawler 64 wireless controllers will be
// usable only at intervals >= 4ms.
//
// TODO: Interleave access like this to allow a higher polling
// frequency: Read caps port 1, Read caps port 2, delay, Read status port 1,
// read status port 2. (Maybe this could make 3ms intervals work)
//
if (g_eeprom_data.cfg.poll_interval[0] >= 8) {
_delay_ms(2.5);
} else if (g_eeprom_data.cfg.poll_interval[0] >= 6) {
_delay_ms(1.5);
} else if (g_eeprom_data.cfg.poll_interval[0] >= 4) {
_delay_ms(1.25);
}
}
else {
if (g_eeprom_data.cfg.poll_interval[0] >= 4) {
_delay_ms(2.5);
} else if (g_eeprom_data.cfg.poll_interval[0] >= 3) {
_delay_ms(1.5);
} else if (g_eeprom_data.cfg.poll_interval[0] >= 2) {
_delay_ms(1.25); // does not work at 1ms
}
}
/* Detect when a pack becomes present and schedule initialisation when it happens. */
if ((caps[2] & 0x01) && (n64_rumble_state == RSTATE_UNAVAILABLE)) {
n64_rumble_state = RSTATE_INIT;
if ((caps[2] & 0x01) && (n64_rumble_state[chn] == RSTATE_UNAVAILABLE)) {
n64_rumble_state[chn] = RSTATE_INIT;
}
/* Detect when a pack is removed. */
if (!(caps[2] & 0x01) || (caps[2] & 0x02) ) {
n64_rumble_state = RSTATE_UNAVAILABLE;
n64_rumble_state[chn] = RSTATE_UNAVAILABLE;
}
#ifdef BUTTON_A_RUMBLE_TEST
must_rumble = force_rumble;
must_rumble[chn] = force_rumble[chn];
//printf("Caps: %02x %02x %02x\r\n", caps[0], caps[1], caps[2]);
#endif
switch (n64_rumble_state)
switch (n64_rumble_state[chn])
{
case RSTATE_INIT:
/* Retry until the controller answers with a full byte. */
if (initRumble() != 0) {
if (initRumble() != 0) {
n64_rumble_state = RSTATE_UNAVAILABLE;
if (initRumble(chn) != 0) {
if (initRumble(chn) != 0) {
n64_rumble_state[chn] = RSTATE_UNAVAILABLE;
}
break;
}
if (must_rumble) {
controlRumble(1);
n64_rumble_state = RSTATE_ON;
if (must_rumble[chn]) {
controlRumble(chn, 1);
n64_rumble_state[chn] = RSTATE_ON;
} else {
controlRumble(0);
n64_rumble_state = RSTATE_OFF;
controlRumble(chn, 0);
n64_rumble_state[chn] = RSTATE_OFF;
}
break;
case RSTATE_TURNON:
if (0 == controlRumble(1)) {
n64_rumble_state = RSTATE_ON;
if (0 == controlRumble(chn, 1)) {
n64_rumble_state[chn] = RSTATE_ON;
}
break;
case RSTATE_TURNOFF:
if (0 == controlRumble(0)) {
n64_rumble_state = RSTATE_OFF;
if (0 == controlRumble(chn, 0)) {
n64_rumble_state[chn] = RSTATE_OFF;
}
break;
case RSTATE_ON:
if (!must_rumble) {
controlRumble(0);
n64_rumble_state = RSTATE_OFF;
if (!must_rumble[chn]) {
controlRumble(chn, 0);
n64_rumble_state[chn] = RSTATE_OFF;
}
break;
case RSTATE_OFF:
if (must_rumble) {
controlRumble(1);
n64_rumble_state = RSTATE_ON;
if (must_rumble[chn]) {
controlRumble(chn, 1);
n64_rumble_state[chn] = RSTATE_ON;
}
break;
}
tmpdata[0] = N64_GET_STATUS;
count = gcn64_transaction(tmpdata, 1, status, sizeof(status));
count = gcn64_transaction(chn, tmpdata, 1, status, sizeof(status));
if (count != N64_GET_STATUS_REPLY_LENGTH) {
return -1;
}
@ -206,22 +243,24 @@ static char n64Update(void)
#ifdef BUTTON_A_RUMBLE_TEST
if (btns1 & 0x80) {
force_rumble = 1;
force_rumble[chn] = 1;
} else {
force_rumble = 0;
force_rumble[chn] = 0;
}
#endif
last_built_report.pad_type = PAD_TYPE_N64;
last_built_report.n64.buttons = (btns1 << 8) | btns2;
last_built_report.n64.x = x;
last_built_report.n64.y = y;
last_built_report[chn].pad_type = PAD_TYPE_N64;
last_built_report[chn].n64.buttons = (btns1 << 8) | btns2;
last_built_report[chn].n64.x = x;
last_built_report[chn].n64.y = y;
#ifdef PAD_DATA_HAS_RAW
/* Copy all the data as-is for the raw field */
last_built_report.n64.raw_data[0] = btns1;
last_built_report.n64.raw_data[1] = btns2;
last_built_report.n64.raw_data[2] = x;
last_built_report.n64.raw_data[3] = y;
last_built_report[chn].n64.raw_data[0] = btns1;
last_built_report[chn].n64.raw_data[1] = btns2;
last_built_report[chn].n64.raw_data[2] = x;
last_built_report[chn].n64.raw_data[3] = y;
#endif
/* Some cheap non-official controllers
* use the full 8 bit range instead of the
@ -242,15 +281,15 @@ static char n64Update(void)
* on a N64, or maybe a little better. This should
* help people realise they got what the paid for
* instead of suspecting the adapter. */
if (last_built_report.n64.x == -128)
last_built_report.n64.x = -127;
if (last_built_report.n64.y == -128)
last_built_report.n64.y = -127;
if (last_built_report[chn].n64.x == -128)
last_built_report[chn].n64.x = -127;
if (last_built_report[chn].n64.y == -128)
last_built_report[chn].n64.y = -127;
return 0;
}
static char n64Probe(void)
static char n64Probe(unsigned char chn)
{
int count;
char i;
@ -267,14 +306,14 @@ static char n64Probe(void)
* Bit 1 tells is if there was something connected that has been removed.
*/
n64_rumble_state = RSTATE_UNAVAILABLE;
n64_rumble_state[chn] = RSTATE_UNAVAILABLE;
for (i=0; i<15; i++)
{
_delay_ms(30);
tmp = N64_GET_CAPABILITIES;
count = gcn64_transaction(&tmp, 1, data, sizeof(data));
count = gcn64_transaction(chn, &tmp, 1, data, sizeof(data));
if (count == N64_CAPS_REPLY_LENGTH) {
return 1;
@ -283,22 +322,22 @@ static char n64Probe(void)
return 0;
}
static char n64Changed(void)
static char n64Changed(unsigned char chn)
{
return memcmp(&last_built_report, &last_sent_report, sizeof(gamepad_data));
return memcmp(&last_built_report[chn], &last_sent_report[chn], sizeof(gamepad_data));
}
static void n64GetReport(gamepad_data *dst)
static void n64GetReport(unsigned char chn, gamepad_data *dst)
{
if (dst)
memcpy(dst, &last_built_report, sizeof(gamepad_data));
memcpy(dst, &last_built_report[chn], sizeof(gamepad_data));
memcpy(&last_sent_report, &last_built_report, sizeof(gamepad_data));
memcpy(&last_sent_report[chn], &last_built_report[chn], sizeof(gamepad_data));
}
static void n64SetVibration(char enable)
static void n64SetVibration(unsigned char chn, char enable)
{
must_rumble = enable;
must_rumble[chn] = enable;
}
static Gamepad N64Gamepad = {

View File

@ -32,7 +32,7 @@ if [ -f $RELEASEDIR/$FILENAME ]; then
exit 1
fi
git tag $TAG -f
git tag $TAG -f -a
git archive --format=tar --prefix=$DIRNAME/ HEAD | gzip > $RELEASEDIR/$FILENAME
cd $RELEASEDIR

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
Copyright (C) 2007-2015 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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
@ -41,12 +41,18 @@ const uint8_t gcn64_usbHidReportDescriptor[] PROGMEM = {
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0x00, 0x7D, // LOGICAL_MAXIMUM (32000)
// Main analog stick
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
// Gamecube C-stick
0x09, 0x33, // USAGE (Rx)
0x09, 0x34, // USAGE (Ry)
// Triggers
0x09, 0x35, // USAGE (Rz)
0x09, 0x36, // USAGE (Slider)
0x09, 0x32, // USAGE (Z)
0x81, 0x02, // INPUT
0xc0, // END COLLECTION
@ -639,3 +645,21 @@ const uint8_t gcn64_usbHidReportDescriptor[] PROGMEM = {
0xC0, // End Collection
0xC0, // End Collection
};
static const unsigned char gcKeyboardReport[] PROGMEM = {
0x05, 0x01, // Usage page : Generic Desktop
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Key Codes)
0x95, 0x03, // Report Count(3)
0x75, 0x08, // Report Size(8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0xE7, // Logical maximum (231)
0x19, 0x00, // Usage Minimum(0)
0x29, 0xE7, // Usage Maximum(231)
0x81, 0x00, // Input (Data, Array)
0xc0, // END_COLLECTION
};

View File

@ -2,6 +2,7 @@
#define _gcn64_requests_h__
/* Commands */
#define RQ_GCN64_ECHO 0x00
#define RQ_GCN64_SET_CONFIG_PARAM 0x01
#define RQ_GCN64_GET_CONFIG_PARAM 0x02
#define RQ_GCN64_SUSPEND_POLLING 0x03
@ -10,11 +11,27 @@
#define RQ_GCN64_GET_CONTROLLER_TYPE 0x06
#define RQ_GCN64_SET_VIBRATION 0x07
#define RQ_GCN64_RAW_SI_COMMAND 0x80
#define RQ_GCN64_BLOCK_IO 0x81
#define RQ_RNT_GET_SUPPORTED_REQUESTS 0xF0
#define RQ_RNT_GET_SUPPORTED_MODES 0xF1
#define RQ_RNT_GET_SUPPORTED_CFG_PARAMS 0xF2
#define RQ_RNT_RESET_FIRMWARE 0xFE
#define RQ_GCN64_JUMP_TO_BOOTLOADER 0xFF
/* Configuration parameters and constants */
#define CFG_PARAM_MODE 0x00
/* Values for mode */
#define CFG_MODE_STANDARD 0x00
#define CFG_MODE_N64_ONLY 0x01
#define CFG_MODE_GC_ONLY 0x02
#define CFG_MODE_2P_STANDARD 0x10
#define CFG_MODE_2P_N64_ONLY 0x11
#define CFG_MODE_2P_GC_ONLY 0x12
#define CFG_MODE_KEYBOARD 0x13
#define CFG_MODE_KB_AND_JS 0x14
#define CFG_MODE_KEYBOARD_2 0x15
#define CFG_PARAM_SERIAL 0x01
@ -28,6 +45,10 @@
#define CFG_PARAM_GC_CSTICK_SQUARE 0x22 // Not implemented
#define CFG_PARAM_FULL_SLIDERS 0x23
#define CFG_PARAM_INVERT_TRIG 0x24
#define CFG_PARAM_TRIGGERS_AS_BUTTONS 0x25
#define CFG_PARAM_DPAD_AS_AXES 0x31
#define CFG_PARAM_DISABLE_ANALOG_TRIGGERS 0x32
#define CFG_PARAM_SWAP_STICK_AND_DPAD 0x34
#endif

View File

@ -1 +1,21 @@
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0017", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0017", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="001d", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0020", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0021", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0022", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0030", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0031", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0032", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0033", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0034", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0035", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0036", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0037", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0038", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="0039", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="003A", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="003B", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="003C", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="003D", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="003E", MODE="0664", GROUP="plugdev"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="289b", ATTRS{idProduct}=="003F", MODE="0664", GROUP="plugdev"

21
scripts/start.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
if [ $# -ne 1 ]; then
echo "Syntax: ./start.sh CPU"
exit 1;
fi
CPU=$1
echo "Polling for chip..."
while true; do
dfu-programmer $1 start
if [ $? -eq 0 ]; then
echo "Chip found. Started."
break;
fi
sleep 1
done

60
stkchk.c Normal file
View File

@ -0,0 +1,60 @@
#include <stdio.h>
#include <stdint.h>
#include <avr/io.h>
#include <util/atomic.h>
#include "stkchk.h"
extern uint16_t __stack;
extern uint16_t _end;
/** Write a canary at the end of the stack. */
void stkchk_init(void)
{
*((&_end)-1) = 0xDEAD;
}
/** Check if the canary is still alive.
*
* Call this perdiocally to check if the
* stack grew too large.
**/
char stkchk_verify(void)
{
if (*((&_end)-1) != 0xDEAD) {
return -1;
}
return 0;
}
/* In order to get an approximate idea of how
* much stack you are using, this can be called
* at strategic places where you know the
* call stack is deep or where large automatic
* buffers are used.
*/
#ifdef STKCHK_WITH_STATUS_CHECK
void stkchk(const char *fname)
{
static int max_usage = 0;
uint16_t end = ((uint16_t)&_end);
uint16_t s_top = ((uint16_t)&__stack);
uint16_t s_cur = SPL | SPH<<8;
uint16_t used, s_size;
uint8_t grew = 0;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
used = s_top - s_cur;
s_size = s_top - end;
if (used > max_usage) {
max_usage = used;
grew = 1;
}
}
if (grew) {
printf("[stkchk] %s: %d/%d\r\n", fname, used, s_size);
}
}
#endif

16
stkchk.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef _stkchk_h__
#define _stkchk_h__
#undef STKCHK_WITH_STATUS_CHECK
#ifdef STKCHK_WITH_STATUS_CHECK
#define stkcheck() stkchk(__FUNCTION__);
void stkchk(const char *label);
#else
#define stkcheck()
#endif
void stkchk_init(void);
char stkchk_verify(void);
#endif // _stkchk_h__

1
tools/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
gcn64ctl

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2013 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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

332
usb.c
View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2013 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2021 Raphael Assenat <raph@raphnet.net>
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
@ -24,17 +24,23 @@
#include "usb.h"
#undef VERBOSE
#define STATE_POWERED 0
#define STATE_DEFAULT 1
#define STATE_ADDRESS 2
#define STATE_CONFIGURED 3
static volatile uint8_t g_usb_suspend;
static uint8_t g_ep0_buf[64];
//static uint8_t g_ep0_buf[64];
static uint8_t g_device_state = STATE_DEFAULT;
static uint8_t g_current_config;
static void *interrupt_data;
static volatile int interrupt_data_len = -1;
static void *interrupt_data2;
static volatile int interrupt_data_len2 = -1;
static void *interrupt_data3;
static volatile int interrupt_data_len3 = -1;
#define CONTROL_WRITE_BUFSIZE 64
static struct usb_request control_write_rq;
@ -62,8 +68,28 @@ static int wcslen(const wchar_t *str)
return i;
}
static void setupEndpoints(void)
/** Return the values for the UECFG1X register
*
* \return The EPSIZE bits if supported, 0xFF if invalid.
**/
static uint8_t getEPsizebits(int epsize)
{
switch(epsize)
{
case 64: return (1<<EPSIZE0)|(1<<EPSIZE1);
case 32: return (1<<EPSIZE1);
case 16: return (1<<EPSIZE0);
case 8: return 0;
}
return -1;
}
static void setupEndpoints()
{
uint8_t epsize;
int i;
/*** EP0 ***/
// Order from figure 23-2
@ -72,7 +98,8 @@ static void setupEndpoints(void)
UECONX = 1<<EPEN; // activate endpoint
UECFG0X = 0; // Control OUT
UEIENX = (1<<RXSTPE) | (1<<RXOUTE) | (1<<NAKINE); /* | (1<<STALLEDE) | (1<<NAKOUTE) | (1<<TXINE) | (1<<RXOUTE) */;
UECFG1X |= (1<<EPSIZE0)|(1<<EPSIZE1)|(1<<ALLOC); // 64 bytes, one bank, and allocate
epsize = getEPsizebits(64);
UECFG1X |= epsize|(1<<ALLOC); // 64 bytes, one bank, and allocate
UEINTX = 0;
if (!(UESTA0X & (1<<CFGOK))) {
@ -81,21 +108,57 @@ static void setupEndpoints(void)
}
// printf_P("ok\r\n");
/*** EP1 ***/
UENUM = 0x01; // select endpoint
for (i=0; i<g_params->n_hid_interfaces; i++) {
UENUM = 0x01 + i; // select endpoint
UECONX = 1<<EPEN; // activate endpoint
UECFG0X = (3<<6) | (1<<EPDIR); // Interrupt IN
UEIENX = (1<<TXINE);
UECFG1X |= (1<<EPSIZE0)|(1<<EPSIZE1)|(1<<ALLOC); // 64 bytes, one bank, and allocate
UEINTX = 0;
UECONX = 1<<EPEN; // activate endpoint
UECFG0X = (3<<6) | (1<<EPDIR); // Interrupt IN
UEIENX = (1<<TXINE);
epsize = getEPsizebits(g_params->hid_params[i].endpoint_size);
if (epsize == 0xff) {
printf_P(PSTR("Invalid ep size\r\n"));
return;
}
UECFG1X = epsize|(1<<ALLOC); // one bank, and allocate
UEINTX = 0;
if (!(UESTA0X & (1<<CFGOK))) {
printf_P(PSTR("CFG EP1 fail\r\n"));
return;
if (!(UESTA0X & (1<<CFGOK))) {
printf_P(PSTR("CFG EP fail\r\n"));
return;
}
}
}
// Requires UENUM already set
static uint16_t getEPlen(void)
{
#ifdef UEBCHX
return UEBCLX | (UEBCHX << 8);
#else
return UEBCLX;
#endif
}
// Requires UENUM already set
// writes up to n bytes
static uint16_t readEP2buf_n(void *dstbuf, int n)
{
uint16_t len;
int i;
uint8_t *dst = dstbuf;
#ifdef UEBCHX
len = UEBCLX | (UEBCHX << 8);
#else
len = UEBCLX;
#endif
for (i=0; i<len && i<n; i++) {
*dst = UEDATX;
dst++;
}
return i;
}
// Requires UENUM already set
static uint16_t readEP2buf(uint8_t *dst)
{
@ -141,11 +204,40 @@ static void buf2EP(uint8_t epnum, const void *src, uint16_t len, uint16_t max_le
}
}
/**
*/
static void longDescriptorHelper(const uint8_t *data, uint16_t len, uint16_t rq_len, uint8_t progmem)
{
uint16_t todo = rq_len > len ? len : rq_len;
uint16_t pos = 0;
while(1)
{
if (todo > 64) {
buf2EP(0, data+pos, 64, 64, progmem);
UEINTX &= ~(1<<TXINI);
pos += 64;
todo -= 64;
while (!(UEINTX & (1<<TXINI)));
}
else {
buf2EP(0, data+pos, todo,
todo,
progmem);
UEINTX &= ~(1<<TXINI);
while (!(UEINTX & (1<<TXINI)));
break;
}
}
}
static void handleSetupPacket(struct usb_request *rq)
{
char unhandled = 0;
// printf_P(PSTR("t: %02x, rq: 0x%02x, val: %04x, l: %d\r\n"), rq->bmRequestType, rq->bRequest, rq->wValue, rq->wLength);
#ifdef VERBOSE
printf_P(PSTR("t: %02x, rq: 0x%02x, val: %04x, l: %d\r\n"), rq->bmRequestType, rq->bRequest, rq->wValue, rq->wLength);
#endif
if (USB_RQT_IS_HOST_TO_DEVICE(rq->bmRequestType))
{
@ -160,7 +252,9 @@ static void handleSetupPacket(struct usb_request *rq)
UEINTX &= ~(1<<TXINI);
while (!(UEINTX & (1<<TXINI)));
UDADDR |= (1<<ADDEN);
#ifdef VERBOSE
printf_P(PSTR("Addr: %d\r\n"), rq->wValue);
#endif
if (!rq->wValue) {
g_device_state = STATE_DEFAULT;
} else {
@ -177,7 +271,9 @@ static void handleSetupPacket(struct usb_request *rq)
}
while (!(UEINTX & (1<<TXINI)));
UEINTX &= ~(1<<TXINI);
#ifdef VERBOSE
printf_P(PSTR("Configured: %d\r\n"), g_current_config);
#endif
break;
default:
@ -191,10 +287,10 @@ static void handleSetupPacket(struct usb_request *rq)
case USB_RQT_CLASS:
switch(rq->bRequest)
{
case HID_CLSRQ_SET_IDLE:
while (!(UEINTX & (1<<TXINI)));
UEINTX &= ~(1<<TXINI);
break;
// case HID_CLSRQ_SET_IDLE:
// while (!(UEINTX & (1<<TXINI)));
// UEINTX &= ~(1<<TXINI);
// break;
case HID_CLSRQ_SET_REPORT:
while (!(UEINTX & (1<<TXINI)));
UEINTX &= ~(1<<TXINI);
@ -257,8 +353,8 @@ static void handleSetupPacket(struct usb_request *rq)
g_params->flags & USB_PARAM_FLAG_DEVDESC_PROGMEM);
break;
case CONFIGURATION_DESCRIPTOR:
// Check index if more than 1 config
buf2EP(0, (unsigned char*)g_params->configdesc, g_params->configdesc_ttllen,
// Would need to check index if more than 1 configs...
longDescriptorHelper(g_params->configdesc, g_params->configdesc_ttllen,
rq->wLength, g_params->flags & USB_PARAM_FLAG_CONFDESC_PROGMEM);
break;
case STRING_DESCRIPTOR:
@ -330,51 +426,17 @@ static void handleSetupPacket(struct usb_request *rq)
{
case REPORT_DESCRIPTOR:
{
uint16_t rqlen = rq->wLength;
uint16_t todo = rqlen;
uint16_t pos = 0;
unsigned char *reportdesc;
// HID 1.1 : 7.1.1 Get_Descriptor request. wIndex is the interface number.
//
if (rq->wIndex > g_params->n_hid_interfaces)
if (rq->wIndex > g_params->n_hid_interfaces) {
unhandled = 1;
break;
reportdesc = (unsigned char*)g_params->hid_params[rq->wIndex].reportdesc;
if (rqlen > g_params->hid_params[rq->wIndex].reportdesc_len) {
// rqlen = g_params->hid_params[rq->wIndex].reportdesc_len;
};
// printf_P(PSTR("t: %02x, rq: 0x%02x, val: %04x, l: %d\r\n"), rq->bmRequestType, rq->bRequest, rq->wValue, rq->wLength);
while(1)
{
// printf_P(PSTR("pos %d todo %d\r\n"), pos, todo);
if (todo > 64) {
buf2EP(0, reportdesc+pos, 64,
64,
g_params->flags & USB_PARAM_FLAG_REPORTDESC_PROGMEM);
UEINTX &= ~(1<<TXINI);
pos += 64;
todo -= 64;
while (!(UEINTX & (1<<TXINI)));
} else {
buf2EP(0, reportdesc+pos, todo,
todo,
g_params->flags & USB_PARAM_FLAG_REPORTDESC_PROGMEM);
UEINTX &= ~(1<<TXINI);
while (!(UEINTX & (1<<TXINI)));
break;
}
}
while (1)
{
if (UEINTX & (1<<RXOUTI)) {
UEINTX &= ~(1<<RXOUTI); // ACK
return;
}
}
longDescriptorHelper(g_params->hid_params[rq->wIndex].reportdesc,
g_params->hid_params[rq->wIndex].reportdesc_len,
rq->wLength,
g_params->flags & USB_PARAM_FLAG_REPORTDESC_PROGMEM);
}
break;
@ -400,7 +462,9 @@ static void handleSetupPacket(struct usb_request *rq)
if (g_params->hid_params[rq->wIndex].getReport) {
const unsigned char *data;
uint16_t len;
len = g_params->hid_params[rq->wIndex].getReport(rq, &data);
len = g_params->hid_params[rq->wIndex].getReport(
g_params->hid_params[rq->wIndex].ctx,
rq, &data);
if (len) {
buf2EP(0, data, len, rq->wLength, 0);
}
@ -460,7 +524,7 @@ static void handleSetupPacket(struct usb_request *rq)
} // IS DEVICE-TO-HOST
if (unhandled) {
// printf_P(PSTR("t: %02x, rq: 0x%02x, val: %04x\r\n"), rq->bmRequestType, rq->bRequest, rq->wValue);
printf_P(PSTR("t: %02x, rq: 0x%02x, val: %04x\r\n"), rq->bmRequestType, rq->bRequest, rq->wValue);
UECONX |= (1<<STALLRQ);
}
}
@ -479,7 +543,9 @@ static void handleDataPacket(const struct usb_request *rq, uint8_t *dat, uint16_
return;
if (g_params->hid_params[rq->wIndex].setReport) {
if (g_params->hid_params[rq->wIndex].setReport(rq, dat, len)) {
if (g_params->hid_params[rq->wIndex].setReport(
g_params->hid_params[rq->wIndex].ctx,
rq, dat, len)) {
UECONX |= (1<<STALLRQ);
} else {
// xmit status
@ -506,7 +572,9 @@ ISR(USB_GEN_vect)
UDINT &= ~(1<<SUSPI);
g_usb_suspend = 1;
UDIEN |= (1<<WAKEUPE);
// printf_P(PSTR("SUSPI\r\n"));
#ifdef VERBOSE
printf_P(PSTR("SUSPI\r\n"));
#endif
// CPU could now be put in low power mode. Later,
// WAKEUPI would wake it up.
}
@ -516,13 +584,17 @@ ISR(USB_GEN_vect)
UDINT &= ~(1<<WAKEUPE);
if (g_usb_suspend) {
g_usb_suspend = 0;
// printf_P(PSTR("WAKEUPI\r\n"));
#ifdef VERBOSE
printf_P(PSTR("WAKEUPI\r\n"));
#endif
UDIEN &= ~(1<<WAKEUPE); // woke up. Not needed anymore.
}
}
if (i & (1<<EORSTI)) {
// printf_P(PSTR("EORSTI\r\n"));
#ifdef VERBOSE
printf_P(PSTR("EORSTI\r\n"));
#endif
g_usb_suspend = 0;
setupEndpoints();
UDINT &= ~(1<<EORSTI);
@ -530,17 +602,45 @@ ISR(USB_GEN_vect)
if (i & (1<<SOFI)) {
UDINT &= ~(1<<SOFI);
// printf_P(PSTR("SOFI\r\n"));
#ifdef VERBOSE
printf_P(PSTR("SOFI\r\n"));
#endif
}
if (i & (1<<EORSMI)) {
UDINT &= ~(1<<EORSMI);
// printf_P(PSTR("EORSMI\r\n"));
#ifdef VERBOSE
printf_P(PSTR("EORSMI\r\n"));
#endif
}
if (i & (1<<UPRSMI)) {
UDINT &= ~(1<<UPRSMI);
#ifdef VERBOSE
printf_P(PSTR("UPRSMI\r\n"));
#endif
}
}
static void handle_interrupt_xmit(uint8_t ep, void **interrupt_data, volatile int *interrupt_data_len)
{
uint8_t i;
UENUM = ep;
i = UEINTX;
if (i & (1<<TXINI)) {
if (*interrupt_data_len < 0) {
// If there's not already data waiting to be
// sent, disable the interrupt.
UEIENX &= ~(1<<TXINE);
} else {
UEINTX &= ~(1<<TXINI);
buf2EP(ep, (void*)*interrupt_data, *interrupt_data_len, *interrupt_data_len, 0);
*interrupt_data = NULL;
*interrupt_data_len = -1;
UEINTX &= ~(1<<FIFOCON);
}
}
}
@ -557,23 +657,26 @@ ISR(USB_COM_vect)
i = UEINTX;
if (i & (1<<RXSTPI)) {
// printf_P(PSTR("RXSTPI\r\n"));
readEP2buf(g_ep0_buf);
struct usb_request rq;
// readEP2buf(g_ep0_buf);
readEP2buf_n(&rq, sizeof(struct usb_request));
UEINTX &= ~(1<<RXSTPI);
handleSetupPacket((struct usb_request *)g_ep0_buf);
handleSetupPacket(&rq);
}
if (i & (1<<RXOUTI)) {
uint16_t len;
len = readEP2buf(g_ep0_buf);
UEINTX &= ~(1<<RXOUTI);
len = getEPlen();
if (control_write_in_progress) {
// printf_P(PSTR("chunk: %d\r\n"), len);
if (control_write_len + len < CONTROL_WRITE_BUFSIZE) {
memcpy(control_write_buf + control_write_len, g_ep0_buf, len);
readEP2buf(control_write_buf + control_write_len);
control_write_len += len;
}
}
UEINTX &= ~(1<<RXOUTI);
}
if (i & (1<<NAKINI)) {
@ -587,22 +690,15 @@ ISR(USB_COM_vect)
}
if (ueint & (1<<EPINT1)) {
UENUM = 1;
i = UEINTX;
handle_interrupt_xmit(1, &interrupt_data, &interrupt_data_len);
}
if (i & (1<<TXINI)) {
if (interrupt_data_len < 0) {
// If there's not already data waiting to be
// sent, disable the interrupt.
UEIENX &= ~(1<<TXINE);
} else {
UEINTX &= ~(1<<TXINI);
buf2EP(1, interrupt_data, interrupt_data_len, interrupt_data_len, 0);
interrupt_data = NULL;
interrupt_data_len = -1;
UEINTX &= ~(1<<FIFOCON);
}
}
if (ueint & (1<<EPINT2)) {
handle_interrupt_xmit(2, &interrupt_data2, &interrupt_data_len2);
}
if (ueint & (1<<EPINT3)) {
handle_interrupt_xmit(3, &interrupt_data3, &interrupt_data_len3);
}
#if 0
@ -613,12 +709,56 @@ ISR(USB_COM_vect)
#endif
}
char usb_interruptReady(void)
char usb_interruptReady_ep3(void)
{
return interrupt_data_len3 == -1;
}
void usb_interruptSend_ep3(void *data, int len)
{
uint8_t sreg = SREG;
while (interrupt_data_len3 != -1) { }
cli();
interrupt_data3 = data;
interrupt_data_len3 = len;
UENUM = 3;
UEIENX |= (1<<TXINE);
SREG = sreg;
}
char usb_interruptReady_ep2(void)
{
return interrupt_data_len2 == -1;
}
void usb_interruptSend_ep2(void *data, int len)
{
uint8_t sreg = SREG;
while (interrupt_data_len2 != -1) { }
cli();
interrupt_data2 = data;
interrupt_data_len2 = len;
UENUM = 2;
UEIENX |= (1<<TXINE);
SREG = sreg;
}
char usb_interruptReady_ep1(void)
{
return interrupt_data_len == -1;
}
void usb_interruptSend(void *data, int len)
void usb_interruptSend_ep1(void *data, int len)
{
uint8_t sreg = SREG;
@ -664,7 +804,9 @@ void usb_doTasks(void)
#ifdef USBSTA
if (USBSTA & (1<<VBUS)) {
#endif
#ifdef VERBOSE
printf_P(PSTR("ATTACH\r\n"));
#endif
UDCON &= ~(1<<DETACH); // clear DETACH bit
usb_state = STATE_ATTACHED;
#ifdef USBSTA
@ -676,7 +818,7 @@ void usb_doTasks(void)
}
}
#if defined(__AVR_ATmega32U2__)
#if defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega32U4__)
/* Atmega32u2 datasheet 8.11.6, PLLCSR.
* But register summary says PLLP0... */
@ -742,8 +884,10 @@ void usb_init(const struct usb_parameters *params)
USBCON |= (1<<FRZCLK); // initial value
#ifdef UHWCON
UHWCON |= (1<<UVREGE); // Enable USB pad regulator
#if defined(UIDE) && defined(UIMOD)
UHWCON &= ~(1<<UIDE);
UHWCON |= (1<UIMOD);
#endif
#endif
#ifdef UPOE

18
usb.h
View File

@ -169,15 +169,18 @@ struct usb_hid_descriptor {
#define USB_PARAM_FLAG_CONFDESC_PROGMEM 2
#define USB_PARAM_FLAG_REPORTDESC_PROGMEM 4
#define MAX_HID_INTERFACES 2
#define MAX_HID_INTERFACES 5
struct usb_hid_parameters {
uint16_t reportdesc_len;
const unsigned char *reportdesc;
int endpoint_size;
// Warning: Called from interrupt handler. Implement accordingly.
uint16_t (*getReport)(struct usb_request *rq, const uint8_t **dat);
uint8_t (*setReport)(const struct usb_request *rq, const uint8_t *dat, uint16_t len);
void *ctx;
uint16_t (*getReport)(void *ctx, struct usb_request *rq, const uint8_t **dat);
uint8_t (*setReport)(void *ctx, const struct usb_request *rq, const uint8_t *dat, uint16_t len);
};
struct usb_parameters {
@ -198,8 +201,13 @@ struct usb_parameters {
struct usb_hid_parameters hid_params[MAX_HID_INTERFACES];
};
char usb_interruptReady(void);
void usb_interruptSend(void *data, int len); // EP1
char usb_interruptReady_ep1(void);
void usb_interruptSend_ep1(void *data, int len);
char usb_interruptReady_ep2(void);
void usb_interruptSend_ep2(void *data, int len);
char usb_interruptReady_ep3(void);
void usb_interruptSend_ep3(void *data, int len);
void usb_init(const struct usb_parameters *params);
void usb_doTasks(void);
void usb_shutdown(void);

331
usbpad.c
View File

@ -16,15 +16,20 @@
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <avr/interrupt.h>
#include "usb.h"
#include "gamepads.h"
#include "usbpad.h"
#include "mappings.h"
#include "eeprom.h"
#include "config.h"
#include "hid_keycodes.h"
#include "gc_kb.h"
#define STICK_TO_BTN_THRESHOLD 40
#define REPORT_ID 1
#define REPORT_SIZE 15
// Output Report IDs for various functions
#define REPORT_SET_EFFECT 0x01
@ -48,25 +53,34 @@
#define PID_SIMULTANEOUS_MAX 3
#define PID_BLOCK_LOAD_REPORT 2
static volatile unsigned char gamepad_vibrate = 0; // output
static unsigned char vibration_on = 0, force_vibrate = 0;
static unsigned char constant_force = 0;
static unsigned char periodic_magnitude = 0;
#undef DEBUG
static unsigned char _FFB_effect_index;
#define LOOP_MAX 0xFFFF
static unsigned int _loop_count;
static unsigned char gamepad_report0[REPORT_SIZE];
static unsigned char hid_report_data[8]; // Used for force feedback
void usbpad_init()
#ifdef DEBUG
static void hexdump(const unsigned char *ptr, int len)
{
int i;
for (i=0; i<len; i++) {
printf_P(PSTR("%02x "), ptr[i]);
}
printf_P(PSTR("\n"));
}
#else
#define printf_P(...)
#define hexdump(...)
#endif
static void buildIdleReport(unsigned char dstbuf[USBPAD_REPORT_SIZE]);
void usbpad_init(struct usbpad *pad)
{
memset(pad, 0, sizeof(struct usbpad));
buildIdleReport(pad->gamepad_report0);
}
int usbpad_getReportSize(void)
{
return REPORT_SIZE;
return USBPAD_REPORT_SIZE;
}
static int16_t minmax(int16_t input, int16_t min, int16_t max)
@ -85,7 +99,7 @@ static void btnsToReport(unsigned short buttons, unsigned char dstbuf[2])
dstbuf[1] = buttons >> 8;
}
static void buildIdleReport(unsigned char dstbuf[REPORT_SIZE])
static void buildIdleReport(unsigned char dstbuf[USBPAD_REPORT_SIZE])
{
int i;
@ -102,10 +116,23 @@ static void buildIdleReport(unsigned char dstbuf[REPORT_SIZE])
dstbuf[14] = 0;
}
static void buildReportFromGC(const gc_pad_data *gc_data, unsigned char dstbuf[REPORT_SIZE])
int usbpad_getReportSizeKB(void)
{
return 3;
}
static void buildIdleReportKB(unsigned char dstbuf[USBPAD_REPORT_SIZE])
{
dstbuf[0] = HID_KB_NOEVENT;
dstbuf[1] = HID_KB_NOEVENT;
dstbuf[2] = HID_KB_NOEVENT;
}
static void buildReportFromGC(const gc_pad_data *gc_data, unsigned char dstbuf[USBPAD_REPORT_SIZE])
{
int16_t xval,yval,cxval,cyval,ltrig,rtrig;
uint16_t buttons;
uint16_t gcbuttons = gc_data->buttons;
/* Force official range */
xval = minmax(gc_data->x, -100, 100);
@ -115,6 +142,24 @@ static void buildReportFromGC(const gc_pad_data *gc_data, unsigned char dstbuf[R
ltrig = gc_data->lt;
rtrig = gc_data->rt;
if (g_eeprom_data.cfg.flags & FLAG_SWAP_STICK_AND_DPAD) {
// Generate new D-Pad button status based on stick
gcbuttons &= ~(GC_BTN_DPAD_UP|GC_BTN_DPAD_DOWN|GC_BTN_DPAD_LEFT|GC_BTN_DPAD_RIGHT);
if (xval <= -STICK_TO_BTN_THRESHOLD) { gcbuttons |= GC_BTN_DPAD_LEFT; }
if (xval >= STICK_TO_BTN_THRESHOLD) { gcbuttons |= GC_BTN_DPAD_RIGHT; }
if (yval <= -STICK_TO_BTN_THRESHOLD) { gcbuttons |= GC_BTN_DPAD_DOWN; }
if (yval >= STICK_TO_BTN_THRESHOLD) { gcbuttons |= GC_BTN_DPAD_UP; }
// Generate new stick values based on button (use gc_data here)
xval = 0; yval = 0;
if (gc_data->buttons & GC_BTN_DPAD_UP) { yval = 100; }
if (gc_data->buttons & GC_BTN_DPAD_DOWN) { yval = -100; }
if (gc_data->buttons & GC_BTN_DPAD_LEFT) { xval = -100; }
if (gc_data->buttons & GC_BTN_DPAD_RIGHT) { xval = 100; }
}
/* Scale -100 ... + 1000 to -16000 ... +16000 */
xval *= 160;
yval *= -160;
@ -122,25 +167,41 @@ static void buildReportFromGC(const gc_pad_data *gc_data, unsigned char dstbuf[R
cxval *= 160;
cyval *= -160;
if (g_eeprom_data.cfg.flags & FLAG_GC_FULL_SLIDERS) {
int16_t lts = (int16_t)ltrig - 127;
int16_t rts = (int16_t)rtrig - 127;
lts *= 126;
ltrig = lts;
rts *= 126;
rtrig = rts;
if (g_eeprom_data.cfg.flags & FLAG_GC_SLIDERS_AS_BUTTONS) {
/* In this mode, the sliders control buttons */
if (ltrig > 64)
gcbuttons |= GC_BTN_L;
if (rtrig > 64)
gcbuttons |= GC_BTN_R;
} else {
/* Scale 0...255 to 0...16000 */
ltrig *= 63;
if (ltrig > 16000) ltrig=16000;
rtrig *= 63;
if (rtrig > 16000) rtrig=16000;
/* And the sliders analog values are fixed. */
ltrig = rtrig = 0;
}
else {
if (g_eeprom_data.cfg.flags & FLAG_GC_FULL_SLIDERS) {
int16_t lts = (int16_t)ltrig - 127;
int16_t rts = (int16_t)rtrig - 127;
lts *= 126;
ltrig = lts;
rts *= 126;
rtrig = rts;
} else {
/* Scale 0...255 to 0...16000 */
ltrig *= 63;
if (ltrig > 16000) ltrig=16000;
rtrig *= 63;
if (rtrig > 16000) rtrig=16000;
}
if (g_eeprom_data.cfg.flags & FLAG_GC_INVERT_TRIGS) {
ltrig = -ltrig;
rtrig = -rtrig;
}
}
if (g_eeprom_data.cfg.flags & FLAG_GC_INVERT_TRIGS) {
ltrig = -ltrig;
rtrig = -rtrig;
if (g_eeprom_data.cfg.flags & FLAG_DISABLE_ANALOG_TRIGGERS) {
ltrig = rtrig = 0;
}
/* Unsign for HID report */
@ -167,19 +228,37 @@ static void buildReportFromGC(const gc_pad_data *gc_data, unsigned char dstbuf[R
dstbuf[11] = ((uint8_t*)&rtrig)[0];
dstbuf[12] = ((uint8_t*)&rtrig)[1];
buttons = mappings_do(MAPPING_GAMECUBE_DEFAULT, gc_data->buttons);
buttons = mappings_do(MAPPING_GAMECUBE_DEFAULT, gcbuttons);
btnsToReport(buttons, dstbuf+13);
}
static void buildReportFromN64(const n64_pad_data *n64_data, unsigned char dstbuf[REPORT_SIZE])
static void buildReportFromN64(const n64_pad_data *n64_data, unsigned char dstbuf[USBPAD_REPORT_SIZE])
{
int16_t xval, yval;
uint16_t buttons;
uint16_t usb_buttons, n64_buttons = n64_data->buttons;
/* Force official range */
xval = minmax(n64_data->x, -80, 80);
yval = minmax(n64_data->y, -80, 80);
if (g_eeprom_data.cfg.flags & FLAG_SWAP_STICK_AND_DPAD) {
// Generate new D-Pad button status based on stick
n64_buttons &= ~(N64_BTN_DPAD_UP|N64_BTN_DPAD_DOWN|N64_BTN_DPAD_LEFT|N64_BTN_DPAD_RIGHT);
if (xval <= -STICK_TO_BTN_THRESHOLD) { n64_buttons |= N64_BTN_DPAD_LEFT; }
if (xval >= STICK_TO_BTN_THRESHOLD) { n64_buttons |= N64_BTN_DPAD_RIGHT; }
if (yval <= -STICK_TO_BTN_THRESHOLD) { n64_buttons |= N64_BTN_DPAD_DOWN; }
if (yval >= STICK_TO_BTN_THRESHOLD) { n64_buttons |= N64_BTN_DPAD_UP; }
// Generate new stick values based on button (use n64_data here)
xval = 0; yval = 0;
if (n64_data->buttons & N64_BTN_DPAD_UP) { yval = 80; }
if (n64_data->buttons & N64_BTN_DPAD_DOWN) { yval = -80; }
if (n64_data->buttons & N64_BTN_DPAD_LEFT) { xval = -80; }
if (n64_data->buttons & N64_BTN_DPAD_RIGHT) { xval = 80; }
}
/* Scale -80 ... +80 to -16000 ... +16000 */
xval *= 200;
yval *= 200;
@ -194,26 +273,26 @@ static void buildReportFromN64(const n64_pad_data *n64_data, unsigned char dstbu
dstbuf[3] = ((uint8_t*)&yval)[0];
dstbuf[4] = ((uint8_t*)&yval)[1];
buttons = mappings_do(MAPPING_N64_DEFAULT, n64_data->buttons);
btnsToReport(buttons, dstbuf+13);
usb_buttons = mappings_do(MAPPING_N64_DEFAULT, n64_buttons);
btnsToReport(usb_buttons, dstbuf+13);
}
void usbpad_update(const gamepad_data *pad_data)
void usbpad_update(struct usbpad *pad, const gamepad_data *pad_data)
{
/* Always start with an idle report. Specific report builders can just
* simply ignore unused parts */
buildIdleReport(gamepad_report0);
buildIdleReport(pad->gamepad_report0);
if (pad_data)
{
switch (pad_data->pad_type)
{
case PAD_TYPE_N64:
buildReportFromN64(&pad_data->n64, gamepad_report0);
buildReportFromN64(&pad_data->n64, pad->gamepad_report0);
break;
case PAD_TYPE_GAMECUBE:
buildReportFromGC(&pad_data->gc, gamepad_report0);
buildReportFromGC(&pad_data->gc, pad->gamepad_report0);
break;
default:
@ -222,38 +301,71 @@ void usbpad_update(const gamepad_data *pad_data)
}
}
void usbpad_forceVibrate(char force)
void usbpad_update_kb(struct usbpad *pad, const gamepad_data *pad_data)
{
force_vibrate = force;
unsigned char i;
/* Always start with an idle report. Specific report builders can just
* simply ignore unused parts */
buildIdleReportKB(pad->gamepad_report0);
if (pad_data->pad_type == PAD_TYPE_GC_KB) {
for (i=0; i<3; i++) {
pad->gamepad_report0[i] = gcKeycodeToHID(pad_data->gckb.keys[i]);
}
}
}
char usbpad_mustVibrate(void)
void usbpad_forceVibrate(struct usbpad *pad, char force)
{
if (force_vibrate) {
pad->force_vibrate = force;
}
void usbpad_vibrationTask(struct usbpad *pad)
{
uint8_t sreg;
sreg = SREG;
cli();
if (pad->_loop_count) {
pad->_loop_count--;
}
SREG = sreg;
}
char usbpad_mustVibrate(struct usbpad *pad)
{
if (pad->force_vibrate) {
return 1;
}
if (!vibration_on) {
gamepad_vibrate = 0;
if (!pad->vibration_on) {
pad->gamepad_vibrate = 0;
} else {
if (constant_force > 0x7f) {
gamepad_vibrate = 1;
} else if (periodic_magnitude > 0x7f) {
gamepad_vibrate = 1;
if (pad->_loop_count > 0) {
if (pad->constant_force > 0x7f) {
pad->gamepad_vibrate = 1;
} else if (pad->periodic_magnitude > 0x7f) {
pad->gamepad_vibrate = 1;
} else {
pad->gamepad_vibrate = 0;
}
} else {
gamepad_vibrate = 0;
// Loop count = 0 -> Stop
pad->gamepad_vibrate = 0;
}
}
return gamepad_vibrate;
return pad->gamepad_vibrate;
}
unsigned char *usbpad_getReportBuffer(void)
unsigned char *usbpad_getReportBuffer(struct usbpad *pad)
{
return gamepad_report0;
return pad->gamepad_report0;
}
uint16_t usbpad_hid_get_report(struct usb_request *rq, const uint8_t **dat)
uint16_t usbpad_hid_get_report(struct usbpad *pad, struct usb_request *rq, const uint8_t **dat)
{
uint8_t report_id = (rq->wValue & 0xff);
@ -268,15 +380,16 @@ uint16_t usbpad_hid_get_report(struct usb_request *rq, const uint8_t **dat)
if (report_id == 1) { // Joystick
// report_id = rq->wValue & 0xff
// interface = rq->wIndex
*dat = gamepad_report0;
*dat = pad->gamepad_report0;
printf_P(PSTR("Get joy report\r\n"));
return 9;
return USBPAD_REPORT_SIZE
;
} else if (report_id == 2) { // 2 : ES playing
hid_report_data[0] = report_id;
hid_report_data[1] = 0;
hid_report_data[2] = _FFB_effect_index;
pad->hid_report_data[0] = report_id;
pad->hid_report_data[1] = 0;
pad->hid_report_data[2] = pad->_FFB_effect_index;
printf_P(PSTR("ES playing\r\n"));
*dat = hid_report_data;
*dat = pad->hid_report_data;
return 3;
} else {
printf_P(PSTR("Get input report %d ??\r\n"), rq->wValue & 0xff);
@ -286,32 +399,32 @@ uint16_t usbpad_hid_get_report(struct usb_request *rq, const uint8_t **dat)
case HID_REPORT_TYPE_FEATURE:
if (report_id == PID_BLOCK_LOAD_REPORT) {
hid_report_data[0] = report_id;
hid_report_data[1] = 0x1; // Effect block index
hid_report_data[2] = 0x1; // (1: success, 2: oom, 3: load error)
hid_report_data[3] = 10;
hid_report_data[4] = 10;
pad->hid_report_data[0] = report_id;
pad->hid_report_data[1] = 0x1; // Effect block index
pad->hid_report_data[2] = 0x1; // (1: success, 2: oom, 3: load error)
pad->hid_report_data[3] = 10;
pad->hid_report_data[4] = 10;
printf_P(PSTR("block load\r\n"));
*dat = hid_report_data;
*dat = pad->hid_report_data;
return 5;
}
else if (report_id == PID_SIMULTANEOUS_MAX) {
hid_report_data[0] = report_id;
pad->hid_report_data[0] = report_id;
// ROM Effect Block count
hid_report_data[1] = 0x1;
hid_report_data[2] = 0x1;
pad->hid_report_data[1] = 0x1;
pad->hid_report_data[2] = 0x1;
// PID pool move report?
hid_report_data[3] = 0xff;
hid_report_data[4] = 1;
pad->hid_report_data[3] = 0xff;
pad->hid_report_data[4] = 1;
printf_P(PSTR("simultaneous max\r\n"));
*dat = hid_report_data;
*dat = pad->hid_report_data;
return 5;
}
else if (report_id == REPORT_CREATE_EFFECT) {
hid_report_data[0] = report_id;
hid_report_data[1] = 1;
pad->hid_report_data[0] = report_id;
pad->hid_report_data[1] = 1;
printf_P(PSTR("create effect\r\n"));
*dat = hid_report_data;
*dat = pad->hid_report_data;
return 2;
} else {
printf_P(PSTR("Unknown feature %d\r\n"), rq->wValue & 0xff);
@ -323,7 +436,7 @@ uint16_t usbpad_hid_get_report(struct usb_request *rq, const uint8_t **dat)
return 0;
}
uint8_t usbpad_hid_set_report(const struct usb_request *rq, const uint8_t *data, uint16_t len)
uint8_t usbpad_hid_set_report(struct usbpad *pad, const struct usb_request *rq, const uint8_t *data, uint16_t len)
{
if (len < 1) {
printf_P(PSTR("shrt\n"));
@ -342,37 +455,58 @@ uint8_t usbpad_hid_set_report(const struct usb_request *rq, const uint8_t *data,
break;
case REPORT_DISABLE_ACTUATORS:
printf_P(PSTR("disable actuators\r\n"));
periodic_magnitude = 0;
constant_force = 0;
vibration_on = 0;
pad->periodic_magnitude = 0;
pad->constant_force = 0;
pad->vibration_on = 0;
break;
case REPORT_PID_POOL:
printf_P(PSTR("pid pool\r\n"));
break;
case REPORT_SET_EFFECT:
_FFB_effect_index = data[1];
printf_P(PSTR("set effect %d\r\n"), data[1]);
pad->_FFB_effect_index = data[1];
pad->_FFB_effect_duration = data[3] | (data[4]<<8);
printf_P(PSTR("set effect %d. duration: %u\r\n"), data[1], pad->_FFB_effect_duration);
hexdump(data, len);
break;
case REPORT_SET_PERIODIC:
periodic_magnitude = data[2];
printf_P(PSTR("periodic mag: %d"), data[2]);
pad->periodic_magnitude = data[2];
printf_P(PSTR("Set periodic - mag: %d, period: %u\r\n"), data[2], data[5] | (data[6]<<8));
hexdump(data, len);
break;
case REPORT_SET_CONSTANT_FORCE:
if (data[1] == 1) {
constant_force = data[2];
pad->constant_force = data[2];
printf_P(PSTR("Constant force %d\r\n"), data[2]);
}
hexdump(data, len);
break;
case REPORT_EFFECT_OPERATION:
if (len != 4)
if (len != 4) {
printf_P(PSTR("Hey!\r\n"));
return -1;
}
/* Byte 0 : report ID
* Byte 1 : bit 7=rom flag, bits 6-0=effect block index
* Byte 2 : Effect operation
* Byte 3 : Loop count */
_loop_count = data[3]<<3;
printf_P(PSTR("EFFECT OP: rom=%s, idx=0x%02x"), data[1] & 0x80 ? "Yes":"No", data[1] & 0x7F);
printf_P(PSTR("EFFECT OP: rom=%s, idx=0x%02x : "), data[1] & 0x80 ? "Yes":"No", data[1] & 0x7F);
// With dolphin, an "infinite" duration is set. The effect is started, then never
// stopped. Maybe I misunderstood something? In any case, the following works
// and feels about right.
if (pad->_FFB_effect_duration == 0xffff) {
if (data[3]) {
pad->_loop_count = data[3] + 1; // +1 for a bit more strength
} else {
pad->_loop_count = 0;
}
} else {
// main.c uses a 16ms interval timer for vibration "loops"
pad->_loop_count = (pad->_FFB_effect_duration / 16) * data[3];
printf_P(PSTR("%d loops for %d ms\r\n"), data[3], pad->_loop_count * 16);
}
switch(data[1] & 0x7F) // Effect block index
{
@ -382,24 +516,27 @@ uint8_t usbpad_hid_set_report(const struct usb_request *rq, const uint8_t *data,
switch (data[2]) // effect operation
{
case EFFECT_OP_START:
printf_P(PSTR("Start\r\n"));
vibration_on = 1;
printf_P(PSTR("Start (lp=%d)\r\n"), pad->_loop_count);
pad->vibration_on = 1;
break;
case EFFECT_OP_START_SOLO:
printf_P(PSTR("Start solo\r\n"));
vibration_on = 1;
printf_P(PSTR("Start solo (lp=%d)\r\n"), pad->_loop_count);
pad->vibration_on = 1;
break;
case EFFECT_OP_STOP:
printf_P(PSTR("Stop\r\n"));
vibration_on = 0;
printf_P(PSTR("Stop (lp=%d)\r\n"), pad->_loop_count);
pad->vibration_on = 0;
break;
default:
printf_P(PSTR("OP?? %02x (lp=%d)\r\n"), data[2], pad->_loop_count);
break;
}
break;
// TODO : should probably drop these from the descriptor since they are
default:
case 2: // ramp
case 5: // triangle
case 6: // sawtooth up
@ -421,7 +558,7 @@ uint8_t usbpad_hid_set_report(const struct usb_request *rq, const uint8_t *data,
switch(data[0])
{
case REPORT_CREATE_EFFECT:
_FFB_effect_index = data[1];
pad->_FFB_effect_index = data[1];
printf_P(PSTR("create effect %d\n"), data[1]);
break;

View File

@ -4,18 +4,39 @@
#include "gamepads.h"
#include "usb.h"
void usbpad_init(void);
#define USBPAD_REPORT_SIZE 15
struct usbpad {
volatile unsigned char gamepad_vibrate; // output
unsigned char vibration_on, force_vibrate;
unsigned char constant_force;
unsigned char periodic_magnitude;
unsigned short _FFB_effect_duration; // in milliseconds
unsigned char _FFB_effect_index;
#define LOOP_MAX 0xFFFF
unsigned int _loop_count;
unsigned char gamepad_report0[USBPAD_REPORT_SIZE];
unsigned char hid_report_data[8]; // Used for force feedback
};
void usbpad_init(struct usbpad *pad);
int usbpad_getReportSize(void);
unsigned char *usbpad_getReportBuffer(void);
unsigned char *usbpad_getReportBuffer(struct usbpad *pad);
void usbpad_update(const gamepad_data *pad_data);
char usbpad_mustVibrate(void);
void usbpad_forceVibrate(char force);
void usbpad_update(struct usbpad *pad, const gamepad_data *pad_data);
void usbpad_vibrationTask(struct usbpad *pad);
char usbpad_mustVibrate(struct usbpad *pad);
void usbpad_forceVibrate(struct usbpad *pad, char force);
uint8_t usbpad_hid_set_report(const struct usb_request *rq, const uint8_t *data, uint16_t len);
uint16_t usbpad_hid_get_report(struct usb_request *rq, const uint8_t **dat);
uint8_t usbpad_hid_set_report(struct usbpad *pad, const struct usb_request *rq, const uint8_t *data, uint16_t len);
uint16_t usbpad_hid_get_report(struct usbpad *pad, struct usb_request *rq, const uint8_t **dat);
// For mappings. ID starts at 0.
#define USB_BTN(id) (0x0001 << (id))
int usbpad_getReportSizeKB(void);
void usbpad_update_kb(struct usbpad *pad, const gamepad_data *pad_data);
#endif // USBPAD_H__

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2013 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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
@ -17,9 +17,32 @@
#include <stdlib.h> // for wchar_t
#include "usbstrings.h"
static wchar_t product_string[PRODUCT_STRING_MAXCHARS]; // = L"GC/N64 to USB v"VERSIONSTR_SHORT;
const wchar_t *g_usb_strings[] = {
[0] = L"raphnet technologies", // 1 : Vendor
[1] = L"GC/N64 to USB v3.0", // 2: Product
[1] = product_string, // 2: Product
[2] = L"123456", // 3 : Serial
};
void usbstrings_changeProductString_P(const char *str)
{
const char *s = str;
wchar_t *d = product_string;
uint8_t c;
int n = 0;
do {
/* Make sure target is always NUL terminated. */
n++;
if (n == PRODUCT_STRING_MAXCHARS) {
*d = 0;
break;
}
c = pgm_read_byte(s);
*d = c;
s++; d++;
} while (c);
}

View File

@ -1,11 +1,21 @@
#ifndef _usbstrings_h__
#define _usbstrings_h__
#include <avr/pgmspace.h>
extern const wchar_t *g_usb_strings[];
/* Sample: "Dual Gamecube to USB v3.4" (25) */
#define PRODUCT_STRING_MAXCHARS 32
#define NUM_USB_STRINGS 3
/* Array indexes (i.e. zero-based0 */
#define USB_STRING_SERIAL_IDX 2
/**
* \param str Must be in PROGMEM
*/
void usbstrings_changeProductString_P(const char *str);
#endif

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2013 Raphael Assenat <raph@raphnet.net>
Copyright (C) 2007-2016 Raphael Assenat <raph@raphnet.net>
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