-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add N3DS EXTHID driver #3
base: master
Are you sure you want to change the base?
Conversation
Device 17 is a bit of a cryptic name. I remember that nocash called it IO expander somewhere which is a bit better but still not sure. https://problemkaputt.de/gbatek-3ds-i2c-new3ds-c-stick-and-zl-zr-buttons.htm |
100% agree that it's a cryptic name but I have no idea what to call it. Any suggestion? nocash mentioned IO expander but not for this one, it was for another device , I think it was the QTM |
Hmm, it seems to be this chip on the A/B/X/Y board i guess. Literally no datasheet. https://de.ifixit.com/Teardown/Nintendo+3DS+XL+2015+Teardown/36346?utm_term=Teardown#s81199 As for ideas got a few from the GodMode9 Discord/IRC. N3DS_EXTHID for example. |
I got some clues that it might be this chip https://gbatemp.net/attachments/rbvaefc22h6aixqgaas6sohprly388_edit-jpg.198252/ I think nocash also mentioned this. HF374 7NU9 But your link mentioning it's a strain gauge sensor is something I totally miss! Let me try some more digging. In the mean time I'll call it Also, the real ir:rst service actually has some init code that includes sending some i2c command. Probably some setting for sensitivity / resolution. I'll try to dig more for the datasheet. |
updated name to N3DS_EXTHID in e80c04b |
I will do some experiments before i merge this or add changes. Something i definitely want to change is making this IRQ driven because bigger I2C reads every frame are not good. They are simply too slow. That chip is apparently connected to a GPIO and sends signals every time data is ready for read. Also i will merge the I2C start functions to de-duplicate a bit of code. |
I was able to get GPIO interrupt working, but I'm hesitant to put it here, seems there's conflict with I was quite confused when at first I tried to register the ISR and just expects it to work, but it never fired. Turns out I was missing call to On the subject of i2c, could you also add this? bool I2C_writeCommand(const I2cDevice devId, const u8 command)
{
const u8 devAddr = g_i2cDevTable[devId].devAddr;
const I2cState *const state = &g_i2cState[g_i2cDevTable[devId].busId];
I2cBus *const i2cBus = state->i2cBus;
const KHandle event = state->event;
const KHandle mutex = state->mutex;
lockMutex(mutex);
if(i2cBus->cnt & I2C_EN) waitForEvent(event);
clearEvent(event);
// Select device and start.
sendByte(i2cBus, devAddr, I2C_START, event);
if(!checkAck(i2cBus))
{
unlockMutex(mutex);
return false;
}
sendByte(i2cBus, command, I2C_STOP, event);
if(!checkAck(i2cBus))
{
unlockMutex(mutex);
return false;
}
unlockMutex(mutex);
return true;
} It's not used at the moment, and ZL & ZR works fine without it. I think the C-stick is not too sensitive without some more configuration, even though it works without configuration. This should correspond to WriteCommand8 |
This is what i came up with in the meantime after looking how ir module interfaces with this chip. Some code isn't ever taken with the hardcoded values. It was a test anyway. And it needs some I2C and GPIO driver changes i did. As for the GPIO conflict we can easily solve this because the depop GPIO is actually only used on old 3DS in codec module. static u8 g_extHidRev = 0;
static bool EXTHID_command(const u8 cmd)
{
return I2C_writeReg(I2C_DEV_N3DS_HID, 0xFFFFFFFF, cmd);
}
static bool EXTHID_read5(void *buf)
{
return I2C_readRegArray(I2C_DEV_N3DS_HID, 0xFFFFFFFF, buf, 5);
}
static bool exthidSleep(void)
{
bool res;
if(g_extHidRev == 2)
{
// Some kind of bug fix for revision 2?
res = EXTHID_command(0x30u | 7);
if(!res) return res;
}
// Disable output.
res = EXTHID_command(0xF7);
if(!res) return res;
TIMER_sleepMs(6); // \ Is this yet another bug fix?
u8 unused[5]; // |
res = EXTHID_read5(unused); // /
if(!res) return res;
// Changes Which GPIO bits are copied to the regs? Prevents the chip from waking up with ZL/ZR?
res = EXTHID_command(0x7C);
if(!res) return res;
// Sleep command with auto calibration?
return EXTHID_command(0xD0);
}
static void exthidWakeChip(void)
{
// Triggers an IRQ to wake the chip up?
GPIO_write(GPIO_EXTHID_WAKE, 0);
TIMER_sleepUs(100);
GPIO_write(GPIO_EXTHID_WAKE, 1);
TIMER_sleepMs(10);
}
static bool exthidWakeup(void)
{
exthidWakeChip();
// Changes which GPIO bits are copied to the regs again?
bool res = EXTHID_command(0x7F);
if(!res) return res;
// Calibration really takes that long?
TIMER_sleepMs(136);
// Enable output.
res = EXTHID_command(0xF9);
if(!res) return res;
if(g_extHidRev == 2)
{
// Revert bug fix on wakeup?
res = EXTHID_command(0x30u | 3);
}
return res;
}
static bool EXTHID_init(void)
{
// External EXTHID IRQ to wake the chip up?
GPIO_config(GPIO_EXTHID_WAKE, GPIO_OUTPUT);
GPIO_write(GPIO_EXTHID_WAKE, 1);
// -----------------------------
// Set device mode.
bool res = EXTHID_command(0xC1); // Selects an ADC curve or something?
if(!res) return res;
res = EXTHID_command(0x52); // Changes conversion mode?
if(!res) return res;
// Disable output.
res = EXTHID_command(0xF7); // Disables output GPIOs or something?
if(!res) return res;
// Get device ID.
struct
{
u8 status;
u8 vendor;
u8 rev;
u8 reserved;
u8 stopByte;
} devId;
res = EXTHID_command(0xF5);
if(!res) return res;
TIMER_sleepMs(2); // wtf, why is this needed?
res = EXTHID_read5(&devId);
if(!res) return res;
g_extHidRev = devId.rev;
ee_printf("Dev ID: %" PRIX8 ", %" PRIX8 ", %" PRIX8 ", %" PRIX8 ", %" PRIX8 "\n",
devId.status, devId.vendor, devId.rev, devId.reserved, devId.stopByte);
if(devId.status != 0x82 || devId.stopByte != 0xFF)
return false;
// TODO: ir module does skip all of the following code except sleep() if vendor is not 1.
if(devId.rev >= 1)
{
// Set sensitivity to 1.0. Only works with the mode set above.
res = EXTHID_command(0x08); // Command 0x08-0x0F. 1.0-1.875. In 0.125 steps.
if(!res) return res;
// Disable idle state.
res = EXTHID_command(0xF3);
if(!res) return res;
// -----------------------------
const u8 calThreshold = 30;
if(true)
{
if(calThreshold == 0)
{
res = EXTHID_command(0xF1); // Automatic calibration?
}
else
{
res = EXTHID_command(0xF2); // Automatic calibration again but with thresholds?
}
}
else
{
res = EXTHID_command(0xF0); // Automatic calibration disable?
}
if(!res) return res;
if(calThreshold > 0)
{
// Set auto calibration threshold. TODO: Can be merged with above code.
// Auto calibration threshold? 0-0xF. Note: There is a lookup table.
res = EXTHID_command(0x20u | 0x9);
if(!res) return res;
}
const u8 opThreshold = 5; // Valid values: 1-16.
res = EXTHID_command(0x80u | ((opThreshold - 1) & 0xFu));
if(!res) return res;
const u8 outThreshold = 7; // Valid values: 2-16.
res = EXTHID_command(0x90u | ((outThreshold - 1) & 0xFu));
if(!res) return res;
// TODO: Lookup table for op timer values.
//const u8 opTimer = 21;
res = EXTHID_command(0xB0u | 0x4);
if(!res) return res;
// TODO: Lookup table for out timer values.
//const u8 outTimer = 9;
res = EXTHID_command(0x60u | 0x3);
if(!res) return res;
// -----------------------------
}
if(devId.rev >= 2)
{
res = EXTHID_command(0x30u | 0x3); // Command 0x30-0x37.
if(!res) return res;
}
return exthidSleep();
}
static bool exthidSetSamplePeriod(const u32 period)
{
if(period < 10 || period > 21) return false;
if(!EXTHID_command(0xA0u | ((period - 10) & 0xFu))) return false;
TIMER_sleepMs(56); // bruh
return true;
}
static KHandle g_extHidEvent = 0;
static void exthidIsr(UNUSED u32 intSource)
{
signalEvent(g_extHidEvent, false);
}
// Note: libctru uses a default period of 10.
static bool EXTHID_startSampling(const u32 period)
{
GPIO_config(GPIO_EXTHID_IRQ, GPIO_IRQ_FALLING | GPIO_INPUT);
IRQ_registerIsr(IRQ_GPIO_3_0, 13, 0, exthidIsr);
g_extHidEvent = createEvent(true);
if(!exthidWakeup()) return false;
return exthidSetSamplePeriod(period);
}
static bool EXTHID_stopSampling(void)
{
if(!exthidSleep()) return false;
// Note: Nintendo does a weird sequence of GPIO changes here.
// In order:
// Disable GPIO IRQ (not ARM11 side IRQ).
// Change to falling edge trigger (keep IRQ disabled). This is a no-op since it's already falling edge.
// Set GPIO to output (wtf).
// Set GPIO high (wtf).
// Set GPIO to back to input.
// Then finally the event is unbound from the IRQ (disables ARM11 side IRQ).
GPIO_config(GPIO_EXTHID_IRQ, GPIO_INPUT); // Input but no IRQ.
IRQ_unregisterIsr(IRQ_GPIO_3_0);
deleteEvent(g_extHidEvent);
g_extHidEvent = 0;
return true;
}
static bool EXTHID_getRawData(void *data)
{
// Note: In polling mode we need to request data and then wait 2 ms.
return EXTHID_read5(data);
}
int main(void)
{
GFX_init(GFX_BGR8, GFX_R5G6B5, GFX_TOP_2D);
GFX_setLcdLuminance(20);
consoleInit(GFX_LCD_BOT, NULL);
//CODEC_init();
ee_printf("init res: %u\n", EXTHID_init());
ee_printf("start sampling: %u\n", EXTHID_startSampling(16));
while(1)
{
waitForEvent(g_extHidEvent);
struct
{
u8 status;
u8 gpio;
u8 x;
u8 y;
u8 stopByte;
} raw;
const bool res = EXTHID_getRawData(&raw);
ee_printf("\rres %u %" PRIX8 ", %" PRIX8 ", %" PRIu8 ", %" PRIu8 ", %" PRIX8 " ",
res, raw.status, raw.gpio, raw.x, raw.y, raw.stopByte);
//GFX_waitForVBlank0();
hidScanInput();
if(hidGetExtraKeys(0) & (KEY_POWER_HELD | KEY_POWER)) break;
}
EXTHID_stopSampling();
fUnmount(FS_DRIVE_SDMC);
CODEC_deinit();
GFX_deinit();
power_off();
return 0;
} |
Wow that's miles ahead from what I found! Could you tell me how you figure it out? I was stuck even figuring out what the commands meant. I couldn't make sense how you'd know which parts are for the devid vendor, for example and I'd love to learn more. |
Join the GodMode9 Discord or IRC (they are bridged). Preferably the offtopic channel. I can explain further there. In the meanwhile looks like i got X/Y data conversion working. |
Add N3DS EXTHID (device 17h) driver for getting ZL, ZR, and C-Stick for supported devices
Needed for profi200/open_agb_firm#173
Notes:
A lot of the device information is still unknown. Here's what we know so far for a fact:
Assumptions:
Images:
https://gbatemp.net/attachments/rbvaefc22h6aixqgaas6sohprly388_edit-jpg.198252/
https://gbatemp.net/attachments/4_power_ic_id_edit-jpg.198242/