Browse Source

WIP

pull/5/head
Raphael Assenat 6 years ago
commit
c59242383f
14 changed files with 3156 additions and 0 deletions
  1. +5
    -0
      .gitignore
  2. +44
    -0
      Makefile
  3. +25
    -0
      gamepad.h
  4. +492
    -0
      gcn64_protocol.c
  5. +168
    -0
      gcn64_protocol.h
  6. +436
    -0
      main.c
  7. +332
    -0
      n64.c
  8. +4
    -0
      n64.h
  9. +652
    -0
      reportdesc.c
  10. +39
    -0
      usart1.c
  11. +7
    -0
      usart1.h
  12. +725
    -0
      usb.c
  13. +200
    -0
      usb.h
  14. +27
    -0
      wait_then_flash.sh

+ 5
- 0
.gitignore View File

@ -0,0 +1,5 @@
*.o
*.elf
*.hex
*.map
*.swp

+ 44
- 0
Makefile View File

@ -0,0 +1,44 @@
CC=avr-gcc
AS=$(CC)
LD=$(CC)
PROGNAME=n64
OBJDIR=objs-$(PROGNAME)
CPU=atmega32u2
CFLAGS=-Wall -mmcu=$(CPU) -DF_CPU=16000000L -Os -DUART1_STDOUT
LDFLAGS=-mmcu=$(CPU) -Wl,-Map=$(PROGNAME).map
HEXFILE=$(PROGNAME).hex
OBJS=main.o n64.o gcn64_protocol.o usart1.o usb.o
all: $(HEXFILE)
clean:
rm -f $(PROGNAME).elf $(PROGNAME).hex $(PROGNAME).map $(OBJS)
%.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
fuse:
flash: $(HEXFILE)
./wait_then_flash.sh $(CPU) $(HEXFILE)
chip_erase:
dfu-programmer atmega32u2 erase
reset:
dfu-programmer atmega32u2 reset

+ 25
- 0
gamepad.h View File

@ -0,0 +1,25 @@
#ifndef _gamepad_h__
#define _gamepad_h__
typedef struct {
int num_reports;
int reportDescriptorSize;
void *reportDescriptor; // must be in flash
int deviceDescriptorSize; // if 0, use default
void *deviceDescriptor; // must be in flash
void (*init)(void);
char (*update)(void);
char (*changed)(int id);
int (*buildReport)(unsigned char *buf, int id);
void (*setVibration)(int value);
/* Check for the controller */
char (*probe)(void); /* return true if found */
} Gamepad;
#endif // _gamepad_h__

+ 492
- 0
gcn64_protocol.c View File

@ -0,0 +1,492 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2015 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 <avr/interrupt.h>
#include <util/delay.h>
#include "gcn64_protocol.h"
#undef FORCE_KEYBOARD
#define GCN64_BUF_SIZE 300
static volatile unsigned char gcn64_workbuf[GCN64_BUF_SIZE];
/******** IO port definitions and options **************/
#define GCN64_DATA_PORT PORTD
#define GCN64_DATA_DDR DDRD
#define GCN64_DATA_PIN PIND
#define GCN64_DATA_BIT (1<<0)
#define GCN64_BIT_NUM_S "0" // for asm
#define FREQ_IS_16MHZ
#define DISABLE_INTS_DURING_COMM
/*
* \brief Explode bytes to bits
* \param bytes The input byte array
* \param num_bytes The number of input bytes
* \param workbuf_bit_offset The offset to start writing at
* \return number of bits (i.e. written output bytes)
*
* 1 input byte = 8 output bytes, where each output byte is zero or non-zero depending
* on the input byte bits, starting with the most significant one.
*/
static int bitsToWorkbufBytes(unsigned char *bytes, int num_bytes, int workbuf_bit_offset)
{
int i, bit;
unsigned char p;
if (num_bytes * 8 > GCN64_BUF_SIZE)
return 0;
for (i=0,bit=0; i<num_bytes; i++) {
for (p=0x80; p; p>>=1) {
gcn64_workbuf[bit+workbuf_bit_offset] = bytes[i] & p;
bit++;
}
}
return bit;
}
/* Read a byte from the buffer (where 1 byte is 1 bit).
* MSb first.
*/
unsigned char gcn64_protocol_getByte(int offset)
{
unsigned char val, b;
unsigned char volatile *addr = gcn64_workbuf + offset;
for (b=0x80, val=0; b; b>>=1)
{
if (*addr)
val |= b;
addr++;
}
return val;
}
void gcn64_protocol_getBytes(int offset, int n_bytes, unsigned char *dstbuf)
{
int i;
for (i=0; i<n_bytes; i++) {
*dstbuf = gcn64_protocol_getByte(offset + (i*8));
dstbuf++;
}
}
// The bit timeout is a counter to 127. This is the
// start value. Counting from 0 takes hundreads of
// microseconds. Because of this, the reception function
// "hangs in there" much longer than necessary..
#ifdef FREQ_IS_16MHZ
#define TIMING_OFFSET 75
#else
#define TIMING_OFFSET 100 // gives about 12uS. Twice the expected maximum bit period.
#endif
static unsigned char gcn64_receive()
{
register unsigned char count=0;
#define SET_DBG " nop\n"
#define CLR_DBG " nop\n"
//#define SET_DBG " sbi %3, 4 \n"
//#define CLR_DBG " cbi %3, 4 \n"
// The data line has been released.
// The receive part below expects it to be still high
// and will wait for it to become low before beginning
// the counting.
asm volatile(
" push r30 \n" // save Z
" push r31 \n" // save Z
" clr %0 \n"
" clr r16 \n"
"initial_wait_low:\n"
" inc r16 \n"
" breq timeout \n" // overflow to 0
" sbic %2, "GCN64_BIT_NUM_S" \n"
" rjmp initial_wait_low \n"
// the next transition is to a high bit
" rjmp waithigh \n"
"waitlow:\n"
" ldi r16, %4 \n"
"waitlow_lp:\n"
" inc r16 \n"
" brmi timeout \n" // > 127 (approx 50uS timeout)
" sbic %2, "GCN64_BIT_NUM_S" \n"
" rjmp waitlow_lp \n"
" inc %0 \n" // count this timed low level
" breq overflow \n" // > 255
" st z+,r16 \n"
"waithigh:\n"
" ldi r16, %4 \n"
"waithigh_lp:\n"
" inc r16 \n"
" brmi timeout \n" // > 127
" sbis %2, "GCN64_BIT_NUM_S" \n"
" rjmp waithigh_lp \n"
" inc %0 \n" // count this timed high level
" breq overflow \n" // > 255
" st z+,r16 \n"
" rjmp waitlow \n"
"overflow: \n"
"timeout: \n"
" pop r31 \n" // restore z
" pop r30 \n" // restore z
: "=&r" (count) // %0
: "z" ((unsigned char volatile *)gcn64_workbuf), // %1
"I" (_SFR_IO_ADDR(GCN64_DATA_PIN)), // %2
"I" (_SFR_IO_ADDR(PORTB)), // %3
"M" (TIMING_OFFSET) // %4
: "r16"
);
return count;
}
static void gcn64_sendBytes(unsigned char *data, unsigned char n_bytes)
{
unsigned int bits;
if (n_bytes == 0)
return;
// Explode the data to one byte per bit for very easy transmission in assembly.
// This trades memory for ease of implementation.
bits = bitsToWorkbufBytes(data, n_bytes, 0);
// the value of the gpio is pre-configured to low. We simulate
// an open drain output by toggling the direction.
#define PULL_DATA " sbi %0, "GCN64_BIT_NUM_S"\n"
#define RELEASE_DATA " cbi %0, "GCN64_BIT_NUM_S"\n"
#ifdef FREQ_IS_16MHZ
// busy looping delays based on busy loop and nop tuning.
// valid for 16Mhz clock. (Tuned to 1us/3us using a scope)
#define DLY_SHORT_1ST "ldi r17, 2\n nop\nrcall sb_dly%=\n "
#define DLY_LARGE_1ST "ldi r17, 13\n rcall sb_dly%=\n"
#define DLY_SHORT_2ND "nop\nnop\nnop\nnop\n"
#define DLY_LARGE_2ND "ldi r17, 9\n rcall sb_dly%=\nnop\nnop\n"
#else
// busy looping delays based on busy loop and nop tuning.
// valid for 12Mhz clock.
#define DLY_SHORT_1ST "ldi r17, 1\n rcall sb_dly%=\n "
#define DLY_LARGE_1ST "ldi r17, 9\n rcall sb_dly%=\n"
#define DLY_SHORT_2ND "\n"
#define DLY_LARGE_2ND "ldi r17, 5\n rcall sb_dly%=\n nop\nnop\n"
#endif
asm volatile(
// Save the modified input operands
" push r28 \n" // y
" push r29 \n"
" push r30 \n" // z
" push r31 \n"
"sb_loop%=: \n"
" ld r16, z+ \n"
" tst r16 \n"
" breq sb_send0%= \n"
" brne sb_send1%= \n"
" rjmp sb_end%= \n" // not reached
"sb_send0%=: \n"
" nop \n"
PULL_DATA
DLY_LARGE_1ST
RELEASE_DATA
DLY_SHORT_2ND
" sbiw %1, 1 \n"
" brne sb_loop%= \n"
" rjmp sb_end%= \n"
"sb_send1%=: \n"
PULL_DATA
DLY_SHORT_1ST
RELEASE_DATA
DLY_LARGE_2ND
" sbiw %1, 1 \n"
" brne sb_loop%= \n"
" rjmp sb_end%= \n"
// delay sub (arg r17)
"sb_dly%=: \n"
" dec r17 \n"
" brne sb_dly%= \n"
" ret \n"
"sb_end%=:\n"
// going here is fast so we need to extend the last
// delay by 500nS
" nop\n "
#ifdef FREQ_IS_16MHZ
" nop\n"
#endif
" pop r31 \n"
" pop r30 \n"
PULL_DATA
" pop r29 \n"
" pop r28 \n"
//DLY_SHORT_1ST
"nop\nnop\nnop\nnop\n"
RELEASE_DATA
// Now, we need to loop until the wire is high to
// prevent the reception code from thinking this is
// the beginning of the first reply bit.
" ldi r16, 0xff \n" // setup a timeout
"sb_waitHigh%=: \n"
" dec r16 \n" // decrement timeout
" breq sb_wait_high_done%= \n" // handle timeout condition
" sbis %3, "GCN64_BIT_NUM_S" \n" // Read the port
" rjmp sb_waitHigh%= \n"
"sb_wait_high_done%=:\n"
:
: "I" (_SFR_IO_ADDR(GCN64_DATA_DDR)), // %0
"w" (bits), // %1
"z" ((unsigned char volatile *)gcn64_workbuf), // %2
"I" (_SFR_IO_ADDR(GCN64_DATA_PIN)) // %3
: "r16", "r17");
}
/* \brief Decode the received length of low/high states to byte-per-bit format
*
* The result is in workbuf.
*
**/
static void gcn64_decodeWorkbuf(unsigned char count)
{
unsigned char i;
volatile unsigned char *output = gcn64_workbuf;
volatile unsigned char *input = gcn64_workbuf;
unsigned char t;
//
// ________
// ________/
//
// [i*2] [i*2+1]
//
// ________________
// 0 : ____/
// ____
// 1 : ________________/
//
// The timings on a real N64 are
//
// 0 : 1 us low, 3 us high
// 1 : 3 us low, 1 us high
//
// However, HORI pads use something similar to
//
// 0 : 1.5 us low, 4.5 us high
// 1 : 4.5 us low, 1.5 us high
//
//
// No64 us = microseconds
// This operation takes approximately 100uS on 64bit gamecube messages
for (i=0; i<count; i++) {
t = *input;
input++;
*output = t < *input;
input++;
output++;
}
}
void gcn64protocol_hwinit(void)
{
// data as input
GCN64_DATA_DDR &= ~(GCN64_DATA_BIT);
// keep data low. By toggling the direction, we make the
// pin act as an open-drain output.
GCN64_DATA_PORT &= ~GCN64_DATA_BIT;
/* debug bit PORTB4 (MISO) */
DDRB |= 0x10;
PORTB &= ~0x10;
}
/**
* \brief Send n data bytes + stop bit, wait for answer.
* \return The number of bits received, 0 on timeout/error.
*
* The result is in gcn64_workbuf, where each byte represents
* a bit.
*/
int gcn64_transaction(unsigned char *data_out, int data_out_len)
{
int count;
unsigned char sreg = SREG;
#ifdef DISABLE_INTS_DURING_COMM
cli();
#endif
gcn64_sendBytes(data_out, data_out_len);
count = gcn64_receive();
SREG = sreg;
if (!count)
return 0;
if (!(count & 0x01)) {
// If we don't get an odd number of level lengths from gcn64_receive
// something is wrong.
//
// The stop bit is a short (~1us) low state followed by an "infinite"
// high state, which timeouts and lets the function return. This
// is why we should receive and odd number of lengths.
return 0;
}
gcn64_decodeWorkbuf(count);
/* this delay is required on N64 controllers. Otherwise, after sending
* a rumble-on or rumble-off command (probably init too), the following
* get status fails. This starts to work at 2us. 5 should be safe. */
_delay_us(5);
/* return the number of full bits received. */
return (count-1) / 2;
}
#if (GC_GETID != N64_GET_CAPABILITIES)
#error N64 vs GC detection commnad broken
#endif
int gcn64_detectController(void)
{
unsigned char tmp = GC_GETID;
int count;
unsigned short id;
count = gcn64_transaction(&tmp, 1);
if (count == 0) {
return CONTROLLER_IS_ABSENT;
}
if (count != 24) {
return CONTROLLER_IS_UNKNOWN;
}
/*
* -- Standard gamecube controller answer:
* 0000 1001 0000 0000 0010 0011 : 0x090023 or
* 0000 1001 0000 0000 0010 0000 : 0x090020
*
* 0000 1001 0000 0000 0010 0000
*
* -- Wavebird gamecube controller
* 1010 1000 0000 0000 0000 0000 : 0xA80000
* (receiver first power up, controller off)
*
* 1110 1001 1010 0000 0001 0111 : 0xE9A017
* (controller on)
*
* 1010 1000 0000
*
* -- Intec wireless gamecube controller
* 0000 1001 0000 0000 0010 0000 : 0x090020
*
*
* -- Standard N64 controller
* 0000 0101 0000 0000 0000 0000 : 0x050000 (no pack)
* 0000 0101 0000 0000 0000 0001 : 0x050001 With expansion pack
* 0000 0101 0000 0000 0000 0010 : 0x050002 Expansion pack removed
*
* -- Ascii keyboard (keyboard connector)
* 0000 1000 0010 0000 0000 0000 : 0x082000
*
* Ok, so based on the above, when the second nibble is a 9 or 8, a
* gamecube compatible controller is present. If on the other hand
* we have a 5, then we are communicating with a N64 controller.
*
* This conclusion appears to be corroborated by my old printout of
* the document named "Yet another gamecube documentation (but one
* that's worth printing). The document explains that and ID can
* be read by sending what they call the 'SI command 0x00 to
* which the controller replies with 3 bytes. (Clearly, that's
* what we are doing here). The first 16 bits are the id, and they
* list, among other type of devices, the following:
*
* 0x0500 N64 controller
* 0x0900 GC standard controller
* 0x0900 Dkongas
* 0xe960 Wavebird
* 0xe9a0 Wavebird
* 0xa800 Wavebird
* 0xebb0 Wavebird
*
* This last entry worries me. I never observed it, but who knows
* what the user will connect? Better be safe and consider 0xb as
* a gamecube controller too.
*
* */
id = gcn64_protocol_getByte(0)<<8;
id |= gcn64_protocol_getByte(8);
#ifdef FORCE_KEYBOARD
return CONTROLLER_IS_GC_KEYBOARD;
#endif
switch (id >> 8) {
case 0x05:
return CONTROLLER_IS_N64;
case 0x09: // normal controllers
case 0x0b: // Never saw this one, but it is mentionned above.
return CONTROLLER_IS_GC;
case 0x08:
if (id == 0x0820) {
// Ascii keyboard
return CONTROLLER_IS_GC_KEYBOARD;
}
// wavebird, controller off.
return CONTROLLER_IS_GC;
default:
return CONTROLLER_IS_UNKNOWN;
}
return 0;
}

+ 168
- 0
gcn64_protocol.h View File

@ -0,0 +1,168 @@
#ifndef _gcn64_protocol_h__
#define _gcn64_protocol_h__
#define CONTROLLER_IS_ABSENT 0
#define CONTROLLER_IS_N64 1
#define CONTROLLER_IS_GC 2
#define CONTROLLER_IS_GC_KEYBOARD 3
#define CONTROLLER_IS_UNKNOWN 4
/* Return many unknown bits, but two are about the expansion port. */
#define N64_GET_CAPABILITIES 0x00
#define N64_CAPS_REPLY_LENGTH 24
#define OFFSET_EXT_REMOVED 22
#define OFFSET_EXT_PRESENT 23
/* Returns button states and axis values */
#define N64_GET_STATUS 0x01
#define N64_GET_STATUS_REPLY_LENGTH 32
/* Read from the expansion bus. */
#define N64_EXPANSION_READ 0x02
/* Write to the expansion bus. */
#define N64_EXPANSION_WRITE 0x03
/* Return information about controller. */
#define GC_GETID 0x00
#define GC_GETID_REPLY_LENGTH 24
/* 3-byte get status command. Returns axis and buttons. Also
* controls motor. */
#define GC_GETSTATUS1 0x40
#define GC_GETSTATUS2 0x03
#define GC_GETSTATUS3(rumbling) ((rumbling) ? 0x01 : 0x00)
#define GC_GETSTATUS_REPLY_LENGTH 64
/* 3-byte poll keyboard command.
* Source: http://hitmen.c02.at/files/yagcd/yagcd/chap9.html#sec9.3.3
* */
#define GC_POLL_KB1 0x54
#define GC_POLL_KB2 0x00
#define GC_POLL_KB3 0x00
/* Gamecube keycodes are from table 9.3.2:
* http://hitmen.c02.at/files/yagcd/yagcd/chap9.html#sec9.3.2
*
* But the table appears to have been built using a PC keyboard
* converted (Tototek).
*
* I was working with an ASCII keyboard so I made a few adjustments
* to reflect the /real/ key functions. For instance:
*
* LWIN, RWIN and MENU are in fact the Henkan, muhenkan and katakana/hiragana keys.
* They are equivalemnt to the HID keys International 4, International 5 and International 2.
* The - key (code 0x3F) is also HID international 1.
* The PrntScrn key (code 0x36) is in fact the Yen key (International 3).
* The bracket/brace [{ and }] keys are at different places on this Japanese keyboard,
* but following standard PC practice, some keycodes are named after the US usage. Hence,
* the aforementioned keys are the 2 keys right of P, even if they are not labeled.
*/
#define GC_KEY_RESERVED 0x00 // No key
#define GC_KEY_HOME 0x06
#define GC_KEY_END 0x07
#define GC_KEY_PGUP 0x08
#define GC_KEY_PGDN 0x09
#define GC_KEY_SCROLL_LOCK 0x0A
#define GC_KEY_A 0x10
#define GC_KEY_B 0x11
#define GC_KEY_C 0x12
#define GC_KEY_D 0x13
#define GC_KEY_E 0x14
#define GC_KEY_F 0x15
#define GC_KEY_G 0x16
#define GC_KEY_H 0x17
#define GC_KEY_I 0x18
#define GC_KEY_J 0x19
#define GC_KEY_K 0x1A
#define GC_KEY_L 0x1B
#define GC_KEY_M 0x1C
#define GC_KEY_N 0x1D
#define GC_KEY_O 0x1E
#define GC_KEY_P 0x1F
#define GC_KEY_Q 0x20
#define GC_KEY_R 0x21
#define GC_KEY_S 0x22
#define GC_KEY_T 0x23
#define GC_KEY_U 0x24
#define GC_KEY_V 0x25
#define GC_KEY_W 0x26
#define GC_KEY_X 0x27
#define GC_KEY_Y 0x28
#define GC_KEY_Z 0x29
#define GC_KEY_1 0x2A
#define GC_KEY_2 0x2B
#define GC_KEY_3 0x2C
#define GC_KEY_4 0x2D
#define GC_KEY_5 0x2E
#define GC_KEY_6 0x2F
#define GC_KEY_7 0x30
#define GC_KEY_8 0x31
#define GC_KEY_9 0x32
#define GC_KEY_0 0x33
#define GC_KEY_DASH_UNDERSCORE 0x34 // Key next to 0
#define GC_KEY_PLUS_EQUAL 0x35
#define GC_KEY_YEN 0x36 // Yen on ascii keyboard. HID International 3.
#define GC_KEY_OPEN_BRKT_BRACE 0x37 // 1st key right of P
#define GC_KEY_CLOSE_BRKT_BRACE 0x38 // 2nd key right of P
#define GC_KEY_SEMI_COLON_COLON 0x39 // ;:
#define GC_KEY_QUOTES 0x3A // '"
// The 3rd key after 'L'. HID Keyboard non-us # and ~
#define GC_KEY_BRACKET_MU 0x3B
#define GC_KEY_COMMA_ST 0x3C // ,<
#define GC_KEY_PERIOD_GT 0x3D // .>
#define GC_KEY_SLASH_QUESTION 0x3E // /?
// (The extra key before right-shift on japanese keyboards.
// HID code International 1 [HID usage tables Footnote 15-20]).
#define GC_KEY_INTERNATIONAL1 0x3F
#define GC_KEY_F1 0x40
#define GC_KEY_F2 0x41
#define GC_KEY_F3 0x42
#define GC_KEY_F4 0x43
#define GC_KEY_F5 0x44
#define GC_KEY_F6 0x45
#define GC_KEY_F7 0x46
#define GC_KEY_F8 0x47
#define GC_KEY_F9 0x48
#define GC_KEY_F10 0x49
#define GC_KEY_F11 0x4A
#define GC_KEY_F12 0x4B
#define GC_KEY_ESC 0x4C
#define GC_KEY_INSERT 0x4D
#define GC_KEY_DELETE 0x4E
// (Hankaku/zenkaku/kanji button). Also known as `~
#define GC_KEY_HANKAKU 0x4F
#define GC_KEY_BACKSPACE 0x50
#define GC_KEY_TAB 0x51
#define GC_KEY_CAPS_LOCK 0x53
#define GC_KEY_LEFT_SHIFT 0x54
#define GC_KEY_RIGHT_SHIFT 0x55
#define GC_KEY_LEFT_CTRL 0x56
#define GC_KEY_LEFT_ALT 0x57
#define GC_KEY_MUHENKAN 0x58 // HID international 5
#define GC_KEY_SPACE 0x59
#define GC_KEY_HENKAN 0x5A // HID international 4
#define GC_KEY_KANA 0x5B // HID international 2
#define GC_KEY_LEFT 0x5C
#define GC_KEY_DOWN 0x5D
#define GC_KEY_UP 0x5E
#define GC_KEY_RIGHT 0x5F
#define GC_KEY_ENTER 0x61
void gcn64protocol_hwinit(void);
int gcn64_detectController(void);
int gcn64_transaction(unsigned char *data_out, int data_out_len);
unsigned char gcn64_protocol_getByte(int offset);
void gcn64_protocol_getBytes(int offset, int n_bytes, unsigned char *dstbuf);
#endif // _gcn64_protocol_h__

+ 436
- 0
main.c View File

@ -0,0 +1,436 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "usart1.h"
#include "usb.h"
#include "gamepad.h"
#include "gcn64_protocol.h"
#include "n64.h"
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#endif
#ifndef LPSTR
#define LPSTR(s) ((const PROGMEM wchar_t*)(s))
#endif
uint16_t hid_get_report(struct usb_request *rq, const uint8_t **dat);
uint8_t hid_set_report(const struct usb_request *rq, const uint8_t *dat, uint16_t len);
#include "reportdesc.c"
const wchar_t *const g_usb_strings[] = {
[0] = L"raphnet technologies", // 1 : Vendor
[1] = L"GC/N64 to USB v3.0", // 2: Product
[2] = L"123456", // 3 : Serial
};
struct cfg0 {
struct usb_configuration_descriptor configdesc;
struct usb_interface_descriptor interface;
struct usb_hid_descriptor hid;
struct usb_endpoint_descriptor ep1_in;
};
static const struct cfg0 cfg0 PROGMEM = {
.configdesc = {
.bLength = sizeof(struct usb_configuration_descriptor),
.bDescriptorType = CONFIGURATION_DESCRIPTOR,
.wTotalLength = sizeof(cfg0), // includes all descriptors returned together
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.bmAttributes = CFG_DESC_ATTR_RESERVED, // set Self-powred and remote-wakeup here if needed.
.bMaxPower = 25, // for 50mA
},
.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 = 64,
.bInterval = LS_FS_INTERVAL_MS(1),
},
};
const struct usb_device_descriptor device_descriptor PROGMEM = {
.bLength = sizeof(struct usb_device_descriptor),
.bDescriptorType = DEVICE_DESCRIPTOR,
.bcdUSB = 0x0101,
.bDeviceClass = 0, // set at interface
.bDeviceSubClass = 0, // set at interface
.bDeviceProtocol = 0,
.bMaxPacketSize = 64,
.idVendor = 0x289B,
.idProduct = 0x0017,
.bcdDevice = 0x0300, // 1.0.0
.bNumConfigurations = 1,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
};
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),
.num_strings = ARRAY_SIZE(g_usb_strings),
.strings = g_usb_strings,
.reportdesc = gcn64_usbHidReportDescriptor,
.reportdesc_len = sizeof(gcn64_usbHidReportDescriptor),
.getReport = hid_get_report,
.setReport = hid_set_report,
};
void hwinit(void)
{
/* PORTB
*
* 7: NC Output low
* 6: NC Output low
* 5: NC Output low
* 4: NC Output low
* 3: MISO Output low
* 2: MOSI Output low
* 1: SCK Output low
* 0: NC Output low
*/
PORTB = 0x00;
DDRB = 0xFF;
/* PORTC
*
* 7: NC Output low
* 6: NC Output low
* 5: NC Output low
* 4: NC Output low
* 3: (no such pin)
* 2: NC Output low
* 1: RESET (N/A: Reset input per fuses)
* 0: XTAL2 (N/A: Crystal oscillator)
*/
PORTB = 0x00;
DDRB = 0xff;
/* PORTD
*
* 7: HWB Input (external pull-up)
* 6: NC Output low
* 5: NC Output low
* 4: NC Output low
* 3: IO3_MCU Input
* 2: IO2_MCU Input
* 1: IO1_MCU Input
* 0: IO0_MCU Input
*/
PORTD = 0x00;
DDRD = 0x70;
// System clock. External crystal is 16 Mhz and we want
// to run at max. speed.
CLKPR = 0x80;
CLKPR = 0x0; // Division factor of 1
PRR0 = 0;
PRR1 = 0;
}
static unsigned char hid_report_data[32];
static unsigned char gamepad_report0[32];
// Output Report IDs for various functions
#define REPORT_SET_EFFECT 0x01
#define REPORT_SET_PERIODIC 0x04
#define REPORT_SET_CONSTANT_FORCE 0x05
#define REPORT_EFFECT_OPERATION 0x0A
#define REPORT_DISABLE_ACTUATORS 0x0C
#define REPORT_PID_POOL 0x0D
// Feature reports
#define REPORT_CREATE_EFFECT 0x09
// For the 'Usage Effect Operation' report
#define EFFECT_OP_START 1
#define EFFECT_OP_START_SOLO 2
#define EFFECT_OP_STOP 3
// Feature report
#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;
static unsigned char constant_force = 0;
static unsigned char magnitude = 0;
static unsigned char _FFB_effect_index;
#define LOOP_MAX 0xFFFF
static unsigned int _loop_count;
static void effect_loop()
{
if (_loop_count) {
if (_loop_count != LOOP_MAX) {
_loop_count--;
}
}
}
static void decideVibration(void)
{
if (!_loop_count)
vibration_on = 0;
if (!vibration_on) {
gamepad_vibrate = 0;
} else {
if (constant_force > 0x7f) {
gamepad_vibrate = 1;
}
if (magnitude > 0x7f) {
gamepad_vibrate = 1;
}
}
}
uint16_t hid_get_report(struct usb_request *rq, const uint8_t **dat)
{
uint8_t report_id = (rq->wValue & 0xff);
// USB HID 1.11 section 7.2.1 Get_Report
// wValue high byte : report type
// wValue low byte : report id
switch (rq->wValue >> 8)
{
case HID_REPORT_TYPE_INPUT:
{
if (report_id == 1) { // Joystick
// report_id = rq->wValue & 0xff
// interface = rq->wIndex
*dat = gamepad_report0;
printf("Get joy report\r\n");
return 9;
} 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;
printf("ES playing\r\n");
*dat = hid_report_data;
return 3;
} else {
printf("Get input report %d ??\r\n", rq->wValue & 0xff);
}
}
break;
case HID_REPORT_TYPE_FEATURE:
if (report_id == PID_BLOCK_LOAD_REPORT) {
hid_report_data[0] = report_id;
hid_report_data[1] = 0x1;
hid_report_data[2] = 0x1;
hid_report_data[3] = 10;
hid_report_data[4] = 10;
printf("block load\r\n");
*dat = hid_report_data;
return 5;
}
else if (report_id == PID_SIMULTANEOUS_MAX) {
hid_report_data[0] = report_id;
// ROM Effect Block count
hid_report_data[1] = 0x1;
hid_report_data[2] = 0x1;
// PID pool move report?
hid_report_data[3] = 0xff;
hid_report_data[4] = 1;
printf("simultaneous max\r\n");
*dat = hid_report_data;
return 5;
}
else if (report_id == REPORT_CREATE_EFFECT) {
hid_report_data[0] = report_id;
hid_report_data[1] = 1;
printf("create effect\r\n");
*dat = hid_report_data;
return 2;
} else {
printf("Unknown feature %d\r\n", rq->wValue & 0xff);
}
break;
}
printf("Unhandled hid get report type=0x%02x, rq=0x%02x, wVal=0x%04x, wLen=0x%04x\r\n", rq->bmRequestType, rq->bRequest, rq->wValue, rq->wLength);
return 0;
}
uint8_t hid_set_report(const struct usb_request *rq, const uint8_t *data, uint16_t len)
{
if (len < 1) {
printf("shrt\n");
return -1;
}
if ((rq->wValue >> 8) == HID_REPORT_TYPE_OUTPUT) {
switch(data[0])
{
case REPORT_DISABLE_ACTUATORS:
printf("disable actuators\r\n");
break;
case REPORT_PID_POOL:
printf("pid pool\r\n");
break;
case REPORT_SET_EFFECT:
_FFB_effect_index = data[1];
printf("set effect %d\n", data[1]);
break;
case REPORT_SET_PERIODIC:
magnitude = data[2];
decideVibration();
printf("periodic mag: %d", data[2]);
break;
case REPORT_SET_CONSTANT_FORCE:
if (data[1] == 1) {
constant_force = data[2];
decideVibration();
printf("Constant force");
}
break;
case REPORT_EFFECT_OPERATION:
if (len != 4)
return -1;
printf("EFFECT OP\n");
/* 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;
switch(data[1] & 0x7F) // Effect block index
{
case 1: // constant force
case 3: // square
case 4: // sine
switch (data[2]) // effect operation
{
case EFFECT_OP_START:
vibration_on = 1;
decideVibration();
break;
case EFFECT_OP_START_SOLO:
vibration_on = 1;
decideVibration();
break;
case EFFECT_OP_STOP:
vibration_on = 0;
decideVibration();
break;
}
break;
// TODO : should probably drop these from the descriptor since they are
case 2: // ramp
case 5: // triangle
case 6: // sawtooth up
case 7: // sawtooth down
case 8: // spring
case 9: // damper
case 10: // inertia
case 11: // friction
case 12: // custom force data
printf("Ununsed effect %d\n", data[1] & 0x7F);
break;
}
break;
default:
printf("Set output report 0x%02x\r\n", data[0]);
}
}
else if ((rq->wValue >> 8) == HID_REPORT_TYPE_FEATURE) {
switch(data[0])
{
case REPORT_CREATE_EFFECT:
_FFB_effect_index = data[1];
printf("create effect %d\n", data[1]);
break;
default:
printf("What?\n");
}
}
else {
printf("impossible\n");
}
return 0;
}
int main(void)
{
Gamepad *pad = NULL;
hwinit();
usart1_init();
gcn64protocol_hwinit();
pad = n64GetGamepad();
sei();
usb_init(&usb_params);
while (1)
{
static char last_v = 0;
usb_doTasks();
_delay_ms(5);
effect_loop();
decideVibration();
if (last_v != gamepad_vibrate) {
if (pad->setVibration) {
pad->setVibration(gamepad_vibrate);
}
last_v = gamepad_vibrate;
}
pad->update();
if (pad->changed(1)) {
int report_size;
report_size = pad->buildReport(gamepad_report0, 1);
usb_interruptSend(gamepad_report0, report_size);
}
}
return 0;
}

+ 332
- 0
n64.c View File

@ -0,0 +1,332 @@
/* gc_n64_usb : Gamecube or N64 controller to USB firmware
Copyright (C) 2007-2014 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 <avr/interrupt.h>
#include <util/delay.h>
#include <string.h>
#include "gamepad.h"
#include "n64.h"
#include "gcn64_protocol.h"
#define GCN64_REPORT_SIZE 9
#undef BUTTON_A_RUMBLE_TEST
/*********** prototypes *************/
static void n64Init(void);
static char n64Update(void);
static char n64Changed(int id);
static int n64BuildReport(unsigned char *reportBuffer, int id);
static void n64SetVibration(int value);
static char must_rumble = 0;
#ifdef BUTTON_A_RUMBLE_TEST
static char force_rumble = 0;
#endif
/* What was most recently read from the controller */
static unsigned char last_built_report[GCN64_REPORT_SIZE];
/* What was most recently sent to the host */
static unsigned char last_sent_report[GCN64_REPORT_SIZE];
static void n64Init(void)
{
n64Update();
}
#define RSTATE_INIT 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];
static char initRumble(void)
{
int count;
tmpdata[0] = N64_EXPANSION_WRITE;
tmpdata[1] = 0x80;
tmpdata[2] = 0x01;
memset(tmpdata+3, 0x80, 32);
/* Note: The old test (count > 0) was not reliable. */
count = gcn64_transaction(tmpdata, 35);
if (count == 8)
return 0;
return -1;
}
static char controlRumble(char enable)
{
int count;
tmpdata[0] = N64_EXPANSION_WRITE;
tmpdata[1] = 0xc0;
tmpdata[2] = 0x1b;
memset(tmpdata+3, enable ? 0x01 : 0x00, 32);
count = gcn64_transaction(tmpdata, 35);
if (count == 8)
return 0;
return -1;
}
static char n64Update(void)
{
int i;
unsigned char count;
unsigned char x,y;
unsigned char btns1, btns2;
unsigned char rb1, rb2;
unsigned char caps[3];
/* Pad answer to N64_GET_CAPABILITIES
*
* 0x050000 : 0000 0101 0000 0000 0000 0000 : No expansion pack
* 0x050001 : 0000 0101 0000 0000 0000 0001 : With expansion pack
* 0x050002 : 0000 0101 0000 0000 0000 0010 : Expansion pack removed
*
* Bit 0 tells us if there is something connected to the expansion port.
* Bit 1 tells is if there was something connected that has been removed.
*/
tmpdata[0] = N64_GET_CAPABILITIES;
count = gcn64_transaction(tmpdata, 1);
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;
return -1;
}
caps[0] = gcn64_protocol_getByte(0);
caps[1] = gcn64_protocol_getByte(8);
caps[2] = gcn64_protocol_getByte(16);
/* 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;
}
/* Detect when a pack is removed. */
if (!(caps[2] & 0x01) || (caps[2] & 0x02) ) {
n64_rumble_state = RSTATE_UNAVAILABLE;
}
#ifdef BUTTON_A_RUMBLE_TEST
must_rumble = force_rumble;
#endif
switch (n64_rumble_state)
{
case RSTATE_INIT:
/* Retry until the controller answers with a full byte. */
if (initRumble() != 0) {
if (initRumble() != 0) {
n64_rumble_state = RSTATE_UNAVAILABLE;
}
break;
}
if (must_rumble) {
controlRumble(1);
n64_rumble_state = RSTATE_ON;
} else {
controlRumble(0);
n64_rumble_state = RSTATE_OFF;
}
break;
case RSTATE_TURNON:
if (0 == controlRumble(1)) {
n64_rumble_state = RSTATE_ON;
}
break;
case RSTATE_TURNOFF:
if (0 == controlRumble(0)) {
n64_rumble_state = RSTATE_OFF;
}
break;
case RSTATE_ON:
if (!must_rumble) {
controlRumble(0);
n64_rumble_state = RSTATE_OFF;
}
break;
case RSTATE_OFF:
if (must_rumble) {
controlRumble(1);
n64_rumble_state = RSTATE_ON;
}
break;
}
tmpdata[0] = N64_GET_STATUS;
count = gcn64_transaction(tmpdata, 1);
if (count != N64_GET_STATUS_REPLY_LENGTH) {
return -1;
}
/*
Bit Function
0 A
1 B
2 Z
3 Start
4 Directional Up
5 Directional Down
6 Directional Left
7 Directional Right
8 unknown (always 0)
9 unknown (always 0)
10 L
11 R
12 C Up
13 C Down
14 C Left
15 C Right
16-23: analog X axis
24-31: analog Y axis
*/
btns1 = gcn64_protocol_getByte(0);
btns2 = gcn64_protocol_getByte(8);
x = gcn64_protocol_getByte(16); // X axis
y = gcn64_protocol_getByte(24); // Y axis
#ifdef BUTTON_A_RUMBLE_TEST
if (btns1 & 0x80) {
force_rumble = 1;
} else {
force_rumble = 0;
}
#endif
// Remap buttons as they always were by this
// adapter. Might change in v3 when a N64
// specific report descriptor will be used.
//
rb1 = rb2 = 0;
for (i=0; i<4; i++) // A B Z START
rb1 |= (btns1 & (0x80 >> i)) ? (0x01<<i) : 0;
for (i=0; i<4; i++) // C-UP C-DOWN C-LEFT C-RIGHT
rb1 |= btns2 & (0x08 >> i) ? (0x10<<i) : 0;
for (i=0; i<2; i++) // L R
rb2 |= btns2 & (0x20 >> i) ? (0x01<<i) : 0;
for (i=0; i<4; i++) // Up down left right
rb2 |= btns1 & (0x08 >> i) ? (0x04<<i) : 0;
x = (x ^ 0x80) - 1;
y = ((y ^ 0x80) ) ^ 0xFF;
// The following helps a cheap TTX controller
// which uses the full 8 bit range instead
// of +/- 80. The specific test here prevents
// receiving a value of 128 (instead of -127).
//
// This will have no effect on "normal" controllers.
if (x == 0xFF)
x = 0;
// analog joystick
last_built_report[0] = 1; // ID
last_built_report[1] = x;
last_built_report[2] = y;
last_built_report[3] = 0x7f;
last_built_report[4] = 0x7f;
last_built_report[5] = 0x7f;
last_built_report[6] = 0x7f;
// buttons
last_built_report[7] = rb1;
last_built_report[8] = rb2;
return 0;
}
static char n64Probe(void)
{
int count;
char i;
unsigned char tmp;
/* Pad answer to N64_GET_CAPABILITIES
*
* 0x050000 : 0000 0101 0000 0000 0000 0000 : No expansion pack
* 0x050001 : 0000 0101 0000 0000 0000 0001 : With expansion pack
* 0x050002 : 0000 0101 0000 0000 0000 0010 : Expansion pack removed
*
* Bit 0 tells us if there is something connected to the expansion port.
* Bit 1 tells is if there was something connected that has been removed.
*/
n64_rumble_state = RSTATE_UNAVAILABLE;
for (i=0; i<15; i++)
{
_delay_ms(30);
tmp = N64_GET_CAPABILITIES;
count = gcn64_transaction(&tmp, 1);
if (count == N64_CAPS_REPLY_LENGTH) {
return 1;
}
}
return 0;
}
static char n64Changed(int id)
{
return memcmp(last_built_report, last_sent_report, GCN64_REPORT_SIZE);
}
static int n64BuildReport(unsigned char *reportBuffer, int id)
{
if (reportBuffer)
memcpy(reportBuffer, last_built_report, GCN64_REPORT_SIZE);
memcpy( last_sent_report, last_built_report, GCN64_REPORT_SIZE);
return GCN64_REPORT_SIZE;
}
static void n64SetVibration(int value)
{
must_rumble = value;
}
static Gamepad N64Gamepad = {
.init = n64Init,
.update = n64Update,
.changed = n64Changed,
.buildReport = n64BuildReport,
.probe = n64Probe,
.num_reports = 1,
.setVibration = n64SetVibration,
};
Gamepad *n64GetGamepad(void)
{
return &N64Gamepad;
}

+ 4
- 0
n64.h View File

@ -0,0 +1,4 @@
#include "gamepad.h"
Gamepad *n64GetGamepad(void);

+ 652
- 0
reportdesc.c View File

@ -0,0 +1,652 @@
/* 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/>.
*/
/*
* The original file gamepad.c contained this descriptor which was
* adapted from my original reportdesc.c by Sean Green for force
* feedback support. Big thanks to him from sharing back!
*
* The descriptor is intact, except for minor changes such as
* axis types and button quantity.
*/
const uint8_t gcn64_usbHidReportDescriptor[] PROGMEM = {
///// gampad
0x05,0x01, // Usage Page Generic Desktop
0x09,0x05, // Usage Joystick
0xA1,0x01, // Collection Application
//axis
0x85,0x01, // Report ID 1
// not sure how this is to work?
// 0x05, 0x02, //USAGE_PAGE (Simulation Controls)
//0x09, 0xBB, //USAGE (Throttle)
0x09, 0x01, // usage pointer
0xA1, 0x00, // COLLECTION (phys)
0x05, 0x01, // USAGE_PAGE (Generic desktop)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x06, // REPORT_COUNT (6)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xFF, 0x00, // LOGICAL_MAXIMUM (255)
0x35, 0x00, // Physical Minimum (0)
0x46, 0xFF, 0x00, // Physical Minimum (255)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x09, 0x33, // USAGE (Rx)
0x09, 0x34, // USAGE (Ry)
0x09, 0x35, // USAGE (Rz)
0x09, 0x36, // USAGE (Slider)
0x81, 0x02, // INPUT
#define NUM_BUTTONS 16
//buttons
0x05, 0x09, // USAGE_PAGE (Button)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, NUM_BUTTONS, // REPORT_COUNT (14)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, NUM_BUTTONS, // USAGE_MAXIMUM (Button 14)
0x75, 0x01, // REPORT_SIZE (1)
0x95, NUM_BUTTONS, // REPORT_COUNT (16)
0x81, 0x02, // INPUT
0xc0, // END COLLECTION
#if 0
//???
0x06,0x01,0xFF, // Usage Page Generic Desktop
0x09,0x49, // Usage Undefined
0x75,0x01, // Report Size 1
0x95,0x01, // Report Count 1
0x81,0x02, // Input (Variable)
0x75,0x07, // Report Size 7
0x81,0x03, // Input (Constant, Variable)
#endif
0x05,0x0F, // Usage Page Physical Interface
0x09,0x92, // Usage ES Playing
0xA1,0x02, // Collection Datalink
0x85,0x02, // Report ID 2
0x09,0x9F, // Usage DS Device is Reset
0x09,0xA0, // Usage DS Device is Pause
0x09,0xA4, // Usage Actuator Power
0x09,0xA5, // Usage Undefined
0x09,0xA6, // Usage Undefined
0x15,0x00, // Logical Minimum 0
0x25,0x01, // Logical Maximum 1
0x35,0x00, // Physical Minimum 0
0x45,0x01, // Physical Maximum 1
0x75,0x01, // Report Size 1
0x95,0x05, // Report Count 5
0x81,0x02, // Input (Variable)
0x95,0x03, // Report Count 3
0x75,0x01, // Report Size 1
0x81,0x03, // Input (Constant, Variable)
0x09,0x94, // Usage PID Device Control
0x15,0x00, // Logical Minimum 0
0x25,0x01, // Logical Maximum 1
0x35,0x00, // Physical Minimum 0
0x45,0x01, // Physical Maximum 1
0x75,0x01, // Report Size 1
0x95,0x01, // Report Count 1
0x81,0x02, // Input (Variable)
0x09,0x22, // Usage Effect Block Index
0x15,0x01, // Logical Minimum 1
0x25,0x28, // Logical Maximum 28h (40d)
0x35,0x01, // Physical Minimum 1
0x45,0x28, // Physical Maximum 28h (40d)
0x75,0x07, // Report Size 7
0x95,0x01, // Report Count 1
0x81,0x02, // Input (Variable)
0xC0 , // End Collection
0x09,0x21, // Usage Set Effect Report
0xA1,0x02, // Collection Datalink
0x85,0x01, // Report ID 1
0x09,0x22, // Usage Effect Block Index
0x15,0x01, // Logical Minimum 1
0x25,0x28, // Logical Maximum 28h (40d)
0x35,0x01, // Physical Minimum 1
0x45,0x28, // Physical Maximum 28h (40d)
0x75,0x08, // Report Size 8
0x95,0x01, // Report Count 1
0x91,0x02, // Output (Variable)
0x09,0x25, // Usage Effect Type
0xA1,0x02, // Collection Datalink
0x09,0x26, // Usage ET Constant Force
0x09,0x27, // Usage ET Ramp
0x09,0x30, // Usage ET Square
0x09,0x31, // Usage ET Sine
0x09,0x32, // Usage ET Triangle
0x09,0x33, // Usage ET Sawtooth Up
0x09,0x34, // Usage ET Sawtooth Down
0x09,0x40, // Usage ET Spring
0x09,0x41, // Usage ET Damper
0x09,0x42, // Usage ET Inertia
0x09,0x43, // Usage ET Friction
0x09,0x28, // Usage ET Custom Force Data
0x25,0x0C, // Logical Maximum Ch (12d)
0x15,0x01, // Logical Minimum 1
0x35,0x01, // Physical Minimum 1
0x45,0x0C, // Physical Maximum Ch (12d)
0x75,0x08, // Report Size 8
0x95,0x01, // Report Count 1
0x91,0x00, // Output
0xC0 , // End Collection
0x09,0x50, // Usage Duration
0x09,0x54, // Usage Trigger Repeat Interval
0x09,0x51, // Usage Sample Period
0x15,0x00, // Logical Minimum 0
0x26,0xFF,0x7F, // Logical Maximum 7FFFh (32767d)
0x35,0x00, // Physical Minimum 0
0x46,0xFF