Compare commits

...

44 Commits

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
32 changed files with 1446 additions and 196 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) -DVERSIONSTR_SHORT=$(VERSIONSTR_SHORT) -std=gnu99
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
@ -42,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:
@ -54,10 +54,10 @@ 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

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,3 +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 gcn64txrx0.o gcn64txrx1.o gcn64txrx2.o gcn64txrx3.o gamepads.o stkchk.o
VERSIONSTR=\"3.4.0\"
VERSIONSTR_SHORT=\"3.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 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) -DVERSIONSTR_SHORT=$(VERSIONSTR_SHORT) -std=gnu99
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

View File

@ -18,6 +18,7 @@ The project is released under the General Public License version 3.
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

View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
Copyright (C) 2007-2016 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,34 @@
- 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

View File

@ -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

@ -24,7 +24,9 @@
/*********** prototypes *************/
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[GAMEPAD_MAX_CHANNELS] = { };
@ -39,6 +41,11 @@ static void gamecubeInit(unsigned char chn)
gamecubeUpdate(chn);
}
static void gamecubeInitKB(unsigned char chn)
{
gamecubeUpdateKB(chn);
}
void gc_decodeAnswer(unsigned char chn, unsigned char data[8])
{
unsigned char x,y,cx,cy;
@ -115,6 +122,39 @@ void gc_decodeAnswer(unsigned char chn, unsigned char data[8])
}
}
static char gamecubeUpdateKB(unsigned char chn)
{
unsigned char tmpdata[GC_GETSTATUS_REPLY_LENGTH];
unsigned char count;
unsigned char i, lrc;
tmpdata[0] = GC_POLL_KB1;
tmpdata[1] = GC_POLL_KB2;
tmpdata[2] = GC_POLL_KB3;
count = gcn64_transaction(chn, tmpdata, 3, tmpdata, GC_GETSTATUS_REPLY_LENGTH);
if (count != GC_GETSTATUS_REPLY_LENGTH) {
return 1;
}
// 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 char gamecubeUpdate(unsigned char chn)
{
unsigned char tmpdata[GC_GETSTATUS_REPLY_LENGTH];
@ -181,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,4 +1,5 @@
#include "gamepads.h"
Gamepad *gamecubeGetGamepad(void);
Gamepad *gamecubeGetKeyboard(void);

View File

@ -6,6 +6,7 @@
#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
@ -69,11 +70,17 @@ 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;

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

@ -181,6 +181,9 @@ int gcn64_detectController(unsigned char chn)
* 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
*
@ -215,6 +218,9 @@ int gcn64_detectController(unsigned char chn)
#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. */

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__

View File

@ -134,9 +134,16 @@ static void hiddata_processCommandBuffer(struct hiddata_ops *ops)
// 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[]
@ -195,6 +202,32 @@ static void hiddata_processCommandBuffer(struct hiddata_ops *ops)
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

View File

@ -7,6 +7,7 @@
struct hiddata_ops {
void (*suspendPolling)(uint8_t suspend);
void (*forceVibration)(uint8_t channel, uint8_t force);
uint8_t (*getSupportedModes)(uint8_t *dst);
};
uint16_t hiddata_get_report(void *ctx, struct usb_request *rq, const uint8_t **dat);

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__

583
main.c
View File

@ -36,18 +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 0x0032
#define N64_USB_PID 0x0033
#define GC_USB_PID 0x0034
#define GCN64_USB_PID 0x0060
#define N64_USB_PID 0x0061
#define GC_USB_PID 0x0062
#define DUAL_GCN64_USB_PID 0x0035
#define DUAL_N64_USB_PID 0x0036
#define DUAL_GC_USB_PID 0x0037
#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 */
@ -138,18 +145,89 @@ static const struct cfg0 cfg0 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_admin;
struct usb_hid_descriptor hid_data;
struct usb_endpoint_descriptor ep2_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;
};
@ -193,40 +271,11 @@ static const struct cfg0_2p cfg0_2p PROGMEM = {
.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),
},
// Main interface, HID (player 2)
.interface_p2 = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = INTERFACE_DESCRIPTOR,
.bInterfaceNumber = 2,
.bInterfaceNumber = 1,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_DEVICE_CLASS_HID,
@ -242,27 +291,157 @@ static const struct cfg0_2p cfg0_2p PROGMEM = {
.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 = 16,
.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 = GCN64_USB_PID,
.bcdDevice = 0x0300, // 1.0.0
.bcdDevice = VERSIONBCD,
.bNumConfigurations = 1,
.iManufacturer = 1,
.iProduct = 2,
@ -297,18 +476,14 @@ static struct usb_parameters usb_params = {
.reportdesc_len = sizeof(gcn64_usbHidReportDescriptor),
.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,
},
[2] = {
.reportdesc = gcn64_usbHidReportDescriptor,
.reportdesc_len = sizeof(gcn64_usbHidReportDescriptor),
.getReport = _usbpad_hid_get_report,
.setReport = _usbpad_hid_set_report,
.endpoint_size = 64,
},
},
};
@ -366,6 +541,7 @@ void hwinit(void)
}
uint8_t num_players = 1;
unsigned char current_pad_type[NUM_CHANNELS] = { };
Gamepad *detectPad(unsigned char chn)
@ -378,14 +554,15 @@ Gamepad *detectPad(unsigned char chn)
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;
@ -419,9 +596,56 @@ static void forceVibration(uint8_t channel, uint8_t 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
@ -438,13 +662,13 @@ int main(void)
uint8_t gamepad_vibrate = 0;
uint8_t state = STATE_WAIT_POLLTIME;
uint8_t channel;
uint8_t num_players = 1;
uint8_t i;
hwinit();
usart1_init();
eeprom_init();
intervaltimer_init();
intervaltimer2_init();
stkchk_init();
switch (g_eeprom_data.cfg.mode)
@ -467,49 +691,50 @@ int main(void)
case CFG_MODE_2P_STANDARD:
usbstrings_changeProductString_P(PSTR("Dual GC/N64 to USB v"VERSIONSTR_SHORT));
device_descriptor.idProduct = DUAL_GCN64_USB_PID;
usb_params.configdesc = (PGM_VOID_P)&cfg0_2p;
usb_params.configdesc_ttllen = sizeof(cfg0_2p);
usb_params.n_hid_interfaces = 3;
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;
usb_params.configdesc = (PGM_VOID_P)&cfg0_2p;
usb_params.configdesc_ttllen = sizeof(cfg0_2p);
usb_params.n_hid_interfaces = 3;
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;
usb_params.configdesc = (PGM_VOID_P)&cfg0_2p;
usb_params.configdesc_ttllen = sizeof(cfg0_2p);
usb_params.n_hid_interfaces = 3;
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++) {
int hid_iface_id;
usbpad_init(&usbpads[i]);
// Skip interface 1 (always used for the dataReport)
if (i>0) {
hid_iface_id = i + 1;
} else {
hid_iface_id = i;
}
usb_params.hid_params[hid_iface_id].ctx = &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] = { };
@ -520,6 +745,12 @@ int main(void)
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)
{
@ -582,7 +813,7 @@ int main(void)
case STATE_WAIT_INTERRUPT_READY:
/* Wait until one of the interrupt endpoint is ready */
if (usb_interruptReady_ep1() || (num_players>1 && usb_interruptReady_ep3())) {
if (usb_interruptReady_ep1() || (num_players>1 && usb_interruptReady_ep2())) {
state = STATE_TRANSMIT;
}
break;
@ -591,8 +822,8 @@ int main(void)
if (usb_interruptReady_ep1()) {
usb_interruptSend_ep1(usbpad_getReportBuffer(&usbpads[0]), usbpad_getReportSize());
}
if (num_players>1 && usb_interruptReady_ep3()) {
usb_interruptSend_ep3(usbpad_getReportBuffer(&usbpads[1]), usbpad_getReportSize());
if (num_players>1 && usb_interruptReady_ep2()) {
usb_interruptSend_ep2(usbpad_getReportBuffer(&usbpads[1]), usbpad_getReportSize());
}
state = STATE_WAIT_POLLTIME;
break;
@ -612,3 +843,203 @@ int main(void)
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;
}

3
main.h
View File

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

38
n64.c
View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2016 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,6 +21,8 @@
#include "gamepads.h"
#include "n64.h"
#include "gcn64_protocol.h"
#include "eeprom.h"
#include "main.h" // for num_players
#undef BUTTON_A_RUMBLE_TEST
@ -110,6 +112,40 @@ static char n64Update(unsigned char chn)
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[chn] == RSTATE_UNAVAILABLE)) {
n64_rumble_state[chn] = RSTATE_INIT;

View File

@ -645,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
@ -11,6 +12,10 @@
#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 */
@ -24,6 +29,9 @@
#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
@ -37,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

1
tools/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
gcn64ctl

165
usb.c
View File

@ -1,5 +1,5 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2016 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
@ -32,11 +32,13 @@
#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;
@ -66,8 +68,28 @@ static int wcslen(const wchar_t *str)
return i;
}
/** 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
@ -76,7 +98,8 @@ static void setupEndpoints()
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))) {
@ -85,52 +108,57 @@ static void setupEndpoints()
}
// printf_P("ok\r\n");
/*** EP1 ***/
UENUM = 0x01; // select endpoint
UECONX = 1<<EPEN; // activate endpoint
UECFG0X = (3<<6) | (1<<EPDIR); // Interrupt IN
UEIENX = (1<<TXINE);
UECFG1X = (1<<EPSIZE0)|(1<<ALLOC); // 16 bytes, one bank, and allocate
UEINTX = 0;
if (!(UESTA0X & (1<<CFGOK))) {
printf_P(PSTR("CFG EP1 fail\r\n"));
return;
}
/*** EP2 ***/
UENUM = 0x02; // 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;
if (!(UESTA0X & (1<<CFGOK))) {
printf_P(PSTR("CFG EP2 fail\r\n"));
return;
}
if (g_params->n_hid_interfaces > 2) {
/*** EP3 ***/
UENUM = 0x03; // 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<<ALLOC); // 16 bytes, one bank, and allocate
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 EP3 fail\r\n"));
while(1);
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)
{
@ -180,7 +208,7 @@ 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;
uint16_t todo = rq_len > len ? len : rq_len;
uint16_t pos = 0;
while(1)
@ -196,8 +224,8 @@ static void longDescriptorHelper(const uint8_t *data, uint16_t len, uint16_t rq_
buf2EP(0, data+pos, todo,
todo,
progmem);
// UEINTX &= ~(1<<TXINI);
// while (!(UEINTX & (1<<TXINI)));
UEINTX &= ~(1<<TXINI);
while (!(UEINTX & (1<<TXINI)));
break;
}
}
@ -259,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);
@ -556,7 +584,9 @@ ISR(USB_GEN_vect)
UDINT &= ~(1<<WAKEUPE);
if (g_usb_suspend) {
g_usb_suspend = 0;
#ifdef VERBOSE
printf_P(PSTR("WAKEUPI\r\n"));
#endif
UDIEN &= ~(1<<WAKEUPE); // woke up. Not needed anymore.
}
}
@ -627,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)) {
@ -660,6 +693,10 @@ ISR(USB_COM_vect)
handle_interrupt_xmit(1, &interrupt_data, &interrupt_data_len);
}
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);
}
@ -694,6 +731,28 @@ void usb_interruptSend_ep3(void *data, int len)
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;
@ -759,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... */
@ -825,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

4
usb.h
View File

@ -175,6 +175,8 @@ struct usb_hid_parameters {
uint16_t reportdesc_len;
const unsigned char *reportdesc;
int endpoint_size;
// Warning: Called from interrupt handler. Implement accordingly.
void *ctx;
uint16_t (*getReport)(void *ctx, struct usb_request *rq, const uint8_t **dat);
@ -201,6 +203,8 @@ struct usb_parameters {
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);

210
usbpad.c
View File

@ -17,12 +17,17 @@
#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
@ -48,6 +53,23 @@
#define PID_SIMULTANEOUS_MAX 3
#define PID_BLOCK_LOAD_REPORT 2
#undef DEBUG
#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)
@ -94,10 +116,23 @@ static void buildIdleReport(unsigned char dstbuf[USBPAD_REPORT_SIZE])
dstbuf[14] = 0;
}
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);
@ -107,6 +142,24 @@ static void buildReportFromGC(const gc_pad_data *gc_data, unsigned char dstbuf[U
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;
@ -114,25 +167,41 @@ static void buildReportFromGC(const gc_pad_data *gc_data, unsigned char dstbuf[U
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 */
@ -159,19 +228,37 @@ static void buildReportFromGC(const gc_pad_data *gc_data, unsigned char dstbuf[U
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[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;
@ -186,8 +273,8 @@ 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(struct usbpad *pad, const gamepad_data *pad_data)
@ -214,11 +301,39 @@ void usbpad_update(struct usbpad *pad, const gamepad_data *pad_data)
}
}
void usbpad_update_kb(struct usbpad *pad, const gamepad_data *pad_data)
{
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]);
}
}
}
void usbpad_forceVibrate(struct usbpad *pad, char force)
{
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) {
@ -228,11 +343,16 @@ char usbpad_mustVibrate(struct usbpad *pad)
if (!pad->vibration_on) {
pad->gamepad_vibrate = 0;
} else {
if (pad->constant_force > 0x7f) {
pad->gamepad_vibrate = 1;
} else if (pad->periodic_magnitude > 0x7f) {
pad->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 {
// Loop count = 0 -> Stop
pad->gamepad_vibrate = 0;
}
}
@ -344,28 +464,49 @@ uint8_t usbpad_hid_set_report(struct usbpad *pad, const struct usb_request *rq,
break;
case REPORT_SET_EFFECT:
pad->_FFB_effect_index = data[1];
printf_P(PSTR("set effect %d\r\n"), 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:
pad->periodic_magnitude = data[2];
printf_P(PSTR("periodic mag: %d"), 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) {
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 */
pad->_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
{
@ -375,24 +516,27 @@ uint8_t usbpad_hid_set_report(struct usbpad *pad, const struct usb_request *rq,
switch (data[2]) // effect operation
{
case EFFECT_OP_START:
printf_P(PSTR("Start\r\n"));
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"));
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"));
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

View File

@ -12,6 +12,7 @@ struct usbpad {
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;
@ -25,6 +26,7 @@ int usbpad_getReportSize(void);
unsigned char *usbpad_getReportBuffer(struct usbpad *pad);
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);
@ -34,4 +36,7 @@ uint16_t usbpad_hid_get_report(struct usbpad *pad, struct usb_request *rq, const
// 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__