Skip to content

Commit 2ce0e23

Browse files
authored
Merge pull request #64 from dmadison/classic_flex
Classic Controller Flex
2 parents c19cb8c + c8f0057 commit 2ce0e23

File tree

9 files changed

+159
-119
lines changed

9 files changed

+159
-119
lines changed

examples/Classic Controller/Classic_DebugPrint/Classic_DebugPrint.ino

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ void setup() {
3737
Serial.println("Classic Controller not detected!");
3838
delay(1000);
3939
}
40-
41-
//classic.setHighRes(true); // uncomment to run in high resolution mode
4240
}
4341

4442
void loop() {

examples/Classic Controller/Classic_Demo/Classic_Demo.ino

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ void setup() {
3636
Serial.println("Classic Controller not detected!");
3737
delay(1000);
3838
}
39-
40-
//classic.setHighRes(true); // uncomment to run in high resolution mode
4139
}
4240

4341
void loop() {
@@ -72,17 +70,13 @@ void loop() {
7270
Serial.println("released");
7371
}
7472

75-
// Read a joystick axis (left XY, right XY)
76-
// Standard Mode: 0-63 Left, 0-31 Right
77-
// High Resolution Mode: 0-255 Left/Right
73+
// Read a joystick axis (0-255, left XY, right XY)
7874
int joyLX = classic.leftJoyX();
7975

8076
Serial.print("The left joystick's X axis is at ");
8177
Serial.println(joyLX);
8278

83-
// Read a trigger (L/R)
84-
// Standard Mode: 0-31
85-
// High Resolution Mode: 0-255
79+
// Read a trigger (0-255, L/R)
8680
int triggerL = classic.triggerL();
8781

8882
Serial.print("The left trigger is at ");

examples/Classic Controller/Classic_HighRes/Classic_HighRes.ino

Lines changed: 0 additions & 64 deletions
This file was deleted.

keywords.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ printDebugRaw KEYWORD2
6565
initialize KEYWORD2
6666

6767
writeRegister KEYWORD2
68+
readRegister KEYWORD2
6869

6970
requestData KEYWORD2
7071
requestControlData KEYWORD2

src/controllers/ClassicController.cpp

Lines changed: 120 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -91,44 +91,145 @@ constexpr BitMap ClassicController_Shared::MapsHR::ButtonHome;
9191
* to copy the mapping name twice.
9292
*
9393
* If the controller is not in "high resolution" mode (according to the class
94-
* data), return the controlData or controlBit function for the specified map
95-
* in the "standard" maps (Maps::). If the controller *is* in "high resolution"
96-
* mode, return the controlData or controlBit function for the specified map
97-
* of the same name in the "high resolution" maps (MapsHR::).
94+
* data), fetch the controlData or controlBit function for the specified map
95+
* in the "standard" maps (Maps::) and bit shift to the left in order to fit
96+
* the full width of the "high res" data range. If the controller *is* in
97+
* "high resolution" mode, fetch the controlData or controlBit function for
98+
* the specified mapof the same name in the "high resolution" maps (MapsHR::).
9899
*/
99-
#define HRDATA(map) !highRes ? getControlData(Maps::map) : getControlData(MapsHR::map)
100+
#define HRDATA(map, shift) !highRes ? (getControlData(Maps::map) << shift) & 0xFF : getControlData(MapsHR::map)
100101
#define HRBIT(map) !highRes ? getControlBit(Maps::map) : getControlBit(MapsHR::map)
101102

102103

103-
boolean ClassicController_Shared::setHighRes(boolean hr) {
104+
boolean ClassicController_Shared::specificInit() {
105+
/* On init, try to set the controller to work in "high resolution" mode so
106+
* we get a full byte of data for each analog input. Then read the current
107+
* "data mode" from the controller so that the control surface functions
108+
* use the correct mappings. This way, the class flexes to support
109+
* all controllers regardless of their available data mode.
110+
*
111+
* This function will only return false if there is a *communciation error*
112+
* on the I2C bus, meaning that the controller did not respond to a write
113+
* or did not provide the right amount of data for a request. It will *not*
114+
* return false if the "high resolution" mode is not successfully set.
115+
*/
116+
delayMicroseconds(I2C_ConversionDelay); // wait after ID read before writing register
117+
return setDataMode(true); // try to set 'high res' mode. 'success' if no comms errors
118+
}
119+
120+
boolean ClassicController_Shared::checkDataMode(boolean *hr) const {
121+
/* Programmator Emptor: vvv This is where all of the headaches stem from vvv */
122+
123+
/* Okay, so here's the deal. The Wii Classic Controller reports its data
124+
* as six bytes with bit-packing for the analog values. When the NES and
125+
* SNES mini consoles were released it turned out that Nintendo had
126+
* included a "high resolution" mode for the Classic Controller. Writing
127+
* '0x03' to the register '0xFE' will make the controller output 8 bytes,
128+
* with each analog control surface using a full byte for its output.
129+
*
130+
* So here's the rub:
131+
* * Bad knockoff Classic Controllers only support "normal" mode
132+
* * Bad knockoff NES Controllers only support "high resolution" mode
133+
* * Genuine controllers support both
134+
*
135+
* Some knockoffs will behave properly and switch between the modes as
136+
* requested, but many will only report their data in one mode and ignore
137+
* the host if it asks otherwise. This results in control data that is
138+
* misinterpreted and users that are unhappy. So not only do we have to
139+
* switch between modes, but we need to come up with a robust method
140+
* to figure out *what mode we're in*.
141+
*
142+
* Here's my idea: in "standard" mode, the controller outputs 6 bytes of
143+
* control data, leaving bytes 7-8 blank (0x00). If we read these two bytes
144+
* and they have data in them, the controller must be in high resolution
145+
* mode! In theory, at least.
146+
*
147+
* This is complicated by the fact that the data from the I2C bus has no
148+
* error checking and is open drain, so if the pull-ups are too weak or
149+
* there is noise on the bus some of these bits may flip 'high' and then
150+
* the check is no good.
151+
*
152+
* To mitigate this, the same data set is requested twice and compared
153+
* against itself. If there is a data mismatch, the requests are repeated
154+
* until the two arrays agree. Not perfect, but better than nothing.
155+
*
156+
* Note that this read starts at 0x00. I tried starting at where the data
157+
* *actually starts* (bytes 7 and 8, i.e. ptr 0x06), but the knockoff
158+
* controllers apparently don't understand how to act as a proper
159+
* register-based I2C device and just return junk. So instead we're starting
160+
* at the beginning of the data block.
161+
*/
162+
static const uint8_t CheckPtr = 0x00; // start of the control data block
163+
static const uint8_t CheckSize = 8; // 8 bytes to cover both std and high res
164+
static const uint8_t DataOffset = 0x06; // start of the data we're interested in (7 / 8)
165+
uint8_t checkData[CheckSize] = { 0x00 }, verifyData[CheckSize] = { 0x00 };
166+
do {
167+
if (!requestData(CheckPtr, CheckSize, checkData)) return false;
168+
delayMicroseconds(I2C_ConversionDelay); // need a brief delay between reads
169+
if (!requestData(CheckPtr, CheckSize, verifyData)) return false;
170+
171+
boolean equal = true;
172+
for (uint8_t i = 0; i < CheckSize - DataOffset; i++) {
173+
if (checkData[i] != verifyData[i]) {
174+
equal = false; // one byte does not match! quit
175+
break;
176+
}
177+
}
178+
179+
if (equal) break; // if data matches, continue
180+
delayMicroseconds(I2C_ConversionDelay); // if we're doing another loop, wait between reads again
181+
} while (true);
182+
183+
*hr = !(checkData[DataOffset] == 0x00 && checkData[DataOffset+1] == 0x00); // if both are '0', high res is false
184+
return true; // successfully read state
185+
}
186+
187+
boolean ClassicController_Shared::setDataMode(boolean hr, boolean verify) {
104188
const uint8_t regVal = hr ? 0x03 : 0x01; // 0x03 for high res, 0x01 for standard
105-
boolean success = writeRegister(0xFE, regVal); // write to controller
106-
if (success == true) {
107-
highRes = hr; // save 'high res' setting
108-
if (highRes == true && getRequestSize() < 8) setRequestSize(8); // 8 bytes needed for hr mode
109-
else if (highRes == false) setRequestSize(MinRequestSize); // if not in HR, set back to min
189+
if (!writeRegister(0xFE, regVal)) return false; // write to controller
190+
191+
if (verify == true) {
192+
boolean currentMode = false; // check controller's HR setting
193+
if (!checkDataMode(&currentMode)) return false; // error: could not read mode
194+
highRes = currentMode; // save current mode to class
195+
}
196+
else {
197+
highRes = hr; // save mode (no verification)
198+
}
199+
200+
if (getHighRes() == true && getRequestSize() < 8) {
201+
setRequestSize(8); // 8 bytes needed for hr mode
202+
}
203+
else if (getHighRes() == false && hr == false) {
204+
setRequestSize(MinRequestSize); // if not in HR and *trying* not to be, set back to min
110205
}
111-
return success;
206+
207+
return true; // 'success' if no communication errors, regardless of setting
208+
}
209+
210+
boolean ClassicController_Shared::setHighRes(boolean hr, boolean verify) {
211+
// 'success' if the mode is changed to the one we're trying to set
212+
return setDataMode(hr, verify) && (getHighRes() == hr);
112213
}
113214

114215
boolean ClassicController_Shared::getHighRes() const {
115216
return highRes;
116217
}
117218

118219
uint8_t ClassicController_Shared::leftJoyX() const {
119-
return HRDATA(LeftJoyX);
220+
return HRDATA(LeftJoyX, 2); // 6 bits for standard range, so shift left (8-6)
120221
}
121222

122223
uint8_t ClassicController_Shared::leftJoyY() const {
123-
return HRDATA(LeftJoyY);
224+
return HRDATA(LeftJoyY, 2);
124225
}
125226

126227
uint8_t ClassicController_Shared::rightJoyX() const {
127-
return HRDATA(RightJoyX);
228+
return HRDATA(RightJoyX, 3); // 5 bits for standard range, so shift left (8-5)
128229
}
129230

130231
uint8_t ClassicController_Shared::rightJoyY() const {
131-
return HRDATA(RightJoyY);
232+
return HRDATA(RightJoyY, 3);
132233
}
133234

134235
boolean ClassicController_Shared::dpadUp() const {
@@ -164,11 +265,11 @@ boolean ClassicController_Shared::buttonY() const {
164265
}
165266

166267
uint8_t ClassicController_Shared::triggerL() const {
167-
return HRDATA(TriggerL);
268+
return HRDATA(TriggerL, 3); // 5 bits for standard range, so shift left (8-5)
168269
}
169270

170271
uint8_t ClassicController_Shared::triggerR() const {
171-
return HRDATA(TriggerR);
272+
return HRDATA(TriggerR, 3);
172273
}
173274

174275
boolean ClassicController_Shared::buttonL() const {
@@ -244,21 +345,14 @@ void ClassicController_Shared::printDebug(Print& output) const {
244345
zlButtonPrint, zrButtonPrint);
245346

246347
output.print(buffer);
247-
if (getHighRes()) output.print(" (High Res)");
348+
if (getHighRes()) output.print(" | (HR)");
248349

249350
output.println();
250351
}
251352

252353

253354
// ######### Mini Controller Support #########
254355

255-
boolean MiniControllerBase::specificInit() {
256-
// all mini controllers use high res format, and some of the cheaper third
257-
// party ones will not work without it. So we're going to set this on
258-
// connection for all of them
259-
return setHighRes(true);
260-
}
261-
262356
void NESMiniController_Shared::printDebug(Print& output) const {
263357
const char fillCharacter = '_';
264358

src/controllers/ClassicController.h

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,15 @@ namespace NintendoExtensionCtrl {
114114
ClassicController_Shared(ExtensionPort &port) :
115115
ClassicController_Shared(port.getExtensionData()) {}
116116

117-
boolean setHighRes(boolean hr = true);
117+
boolean specificInit();
118+
119+
boolean setHighRes(boolean hr = true, boolean verify = true);
118120
boolean getHighRes() const;
119121

120-
uint8_t leftJoyX() const; // 6 bits, 0-63
122+
uint8_t leftJoyX() const; // 8 bits, 6 shifted in std mode
121123
uint8_t leftJoyY() const;
122124

123-
uint8_t rightJoyX() const; // 5 bits, 0-31
125+
uint8_t rightJoyX() const; // 8 bits, 5 shifted in std mode
124126
uint8_t rightJoyY() const;
125127

126128
boolean dpadUp() const;
@@ -133,7 +135,7 @@ namespace NintendoExtensionCtrl {
133135
boolean buttonX() const;
134136
boolean buttonY() const;
135137

136-
uint8_t triggerL() const; // 5 bits, 0-31
138+
uint8_t triggerL() const; // 8 bits, 5 shifted in std mode
137139
uint8_t triggerR() const;
138140

139141
boolean buttonL() const;
@@ -154,28 +156,24 @@ namespace NintendoExtensionCtrl {
154156

155157
protected:
156158
boolean highRes = false; // 'high resolution' mode setting
159+
160+
boolean checkDataMode(boolean *hr) const;
161+
boolean setDataMode(boolean hr, boolean verify = true);
157162
};
158163

159164

160165
/* Nintendo Mini Console Controllers */
161166

162-
class MiniControllerBase : public ClassicController_Shared {
167+
class NESMiniController_Shared : public ClassicController_Shared {
163168
public:
164169
using ClassicController_Shared::ClassicController_Shared;
165170

166-
boolean specificInit();
167-
};
168-
169-
class NESMiniController_Shared : public MiniControllerBase {
170-
public:
171-
using MiniControllerBase::MiniControllerBase;
172-
173171
void printDebug(Print& output = NXC_SERIAL_DEFAULT) const;
174172
};
175173

176-
class SNESMiniController_Shared : public MiniControllerBase {
174+
class SNESMiniController_Shared : public ClassicController_Shared {
177175
public:
178-
using MiniControllerBase::MiniControllerBase;
176+
using ClassicController_Shared::ClassicController_Shared;
179177

180178
void printDebug(Print& output = NXC_SERIAL_DEFAULT) const;
181179
};

src/internal/ExtensionController.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,18 @@ boolean ExtensionController::writeRegister(NXC_I2C_TYPE& i2c, byte reg, byte val
157157
return i2c_writeRegister(i2c, I2C_Addr, reg, value);
158158
}
159159

160-
boolean ExtensionController::requestData(NXC_I2C_TYPE& i2c, uint8_t ptr, size_t size, uint8_t* data) {
161-
return i2c_readDataArray(i2c, I2C_Addr, ptr, size, data);
160+
boolean ExtensionController::readRegister(NXC_I2C_TYPE& i2c, byte reg, uint8_t* dataOut) {
161+
return i2c_readRegister(i2c, I2C_Addr, reg, dataOut);
162+
}
163+
164+
uint8_t ExtensionController::readRegister(NXC_I2C_TYPE& i2c, byte reg) {
165+
uint8_t regOut = 0x00;
166+
i2c_readRegister(i2c, I2C_Addr, reg, &regOut);
167+
return regOut; // return the value read whether it's valid or not
168+
}
169+
170+
boolean ExtensionController::requestData(NXC_I2C_TYPE& i2c, uint8_t ptr, size_t size, uint8_t* dataOut) {
171+
return i2c_readDataArray(i2c, I2C_Addr, ptr, size, dataOut);
162172
}
163173

164174
boolean ExtensionController::requestControlData(NXC_I2C_TYPE& i2c, size_t size, uint8_t* controlData) {

0 commit comments

Comments
 (0)