What's new

Release N64 Controller Module

Gman

RTFDS
Staff member
.
.
.
.
.
.
Joined
Feb 25, 2016
Messages
1,454
Likes
2,916
Joined
Jan 26, 2024
Messages
87
Likes
206
Portables
3
Dumb question, but what is the crystal oscillator used for this? Is it something I can salvage off a controller or do I need to source it separately?

Edit: I think I may have found the answer to my question but just want to confirm, would it be the CLK on an OEM controller?
 
Last edited:

Gman

RTFDS
Staff member
.
.
.
.
.
.
Joined
Feb 25, 2016
Messages
1,454
Likes
2,916
Yes it is for the main IC on the controller. And yes it is intended to be salvaged from the controller.
 

cy

.
Joined
Sep 3, 2020
Messages
186
Likes
543
Portables
8
In one of my own recent projects, I found myself using the code for this in for a GameCube T3 joystick. After programming the micro controller, I ran into a pretty egregious problem. After going a certain distance on the joystick, it would somehow consistently end up going in the opposite direction than intended. This happened when going up on the Y axis, and when going left on the X axis.

So I spent a while trying to debug this and eventually I figured out there was an integer overflow that was happening somewhere in this function:
C:
static int16_t Map(int x, int inMin, int inMax, int outMin, int outMax) {
    // Map a single value onto a different range
    return (((x - inMin) * (outMax - outMin)) / (inMax - inMin)) +outMin;
}
I considered that the multiplication being used with a 16 bit integer could be leading to an overflow. So I modified the code to use a 32 bit integer for the multiplication here:
C:
static int16_t Map(int x, int inMin, int inMax, int outMin, int outMax) {
    // Map a single value onto a different range
    return (int16_t)(((int32_t)(x - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin);
}
Doing this fixed the problem. I figured there was likely an integer overflow happening since the joystick was just suddenly going from a fairly high positive value to an extremely high negative value. Pairing this with commenting out various bits of code and then testing helped me to figure out where the real issue was.

I'd like to thank @Gman for helping me debug this. I've made him aware of the issue and he said he'll be updating the github repo soon to include this fix. From what I gathered, I wasn't the only person having this issue, so with any luck this should fix the problem once and for all.
 
Joined
Jan 26, 2024
Messages
87
Likes
206
Portables
3
Glad you took the time to figure this out, I knew I wasn't crazy!
 
  • Like
Reactions: cy
Joined
Jan 26, 2024
Messages
87
Likes
206
Portables
3
I have been having repeated issues with calibration on multiple devices so I decided to dig into the code to figure out why and ended up making it a little more reliable.

Problem
When doing a fresh flash of the PIC, running calibration for the first time works great. However, I ran into an issue where I ripped the ribbon cable of my joystick (oops) and had to replace it. I figured I might as well recalibrate it just because, so re-ran the calibration on startup and noticed the stick stopped responding. Thinking that maybe I was doing the combination wrong (it's been a while since I've calibrated it), I attempted to do the same thing on another device I'm building. The same issue occurred for that one as well.

To verify that it wasn't something weird I was doing, I re-flashed both PICs, completely erasing them before I did (probably redundant since the flash performs an erase step). After running the calibration on a fresh flash, the sticks worked great. I then attempted another calibration and they broke again, and I couldn't get them to work until they were freshly flashed.

Troubleshooting
Suspecting that memory may be the culprit, I noticed that HEF (High Endurance Flash) was configured for the PIC rather than writing to regular old memory. HEF for PICs require that an entire block of flash is erased and written to, however, in this use case only 1 byte was being written to it at a time. This meant that anytime something was saved to HEF, it could eventually become garbage which seemed to be happening in my case. So I ended up modifying the address to write and read from EEPROM rather than HEF, as seen below.

C:
void eepromWrite(unsigned char address, char data) {
   
    unsigned char interruptStatus = INTCONbits.GIE;

    INTCONbits.GIE = 0;
    NVMCON1bits.NVMREGS = 1;
    NVMCON1bits.WREN = 1;

    //NVMADRH = 0x80; <- HEF Address
    NVMADRH = 0x00; //EEPROM address
    NVMADRL = address;
    NVMDATL = data;

    nvmUnlock();

    while (NVMCON1bits.WR);

    NVMCON1bits.WREN = 0;

    if (interruptStatus) INTCONbits.GIE = 1;
}
This change then resulted in another problem. After saving a value for the first time, on any subsequent calibration runs only the very last address that was written to would persist the value. This meant that every axis besides the very last one would read 0xFF. Not being an expert with the PIC, I did some research and found that sometimes, without delays between writes, the EEPROM might not be written to. I found this odd since the above code checks the WR bit, which will only be changed when the data has been written to memory.

I decided to try the delay anyway and it worked! Adding a small delay of 5ms between each right allowed each to finish and the data to be persisted as intended. This is definitely a brute forced approach to solving the problem but here is the code snippet of what I did.

C:
        eepromWrite(SX_MIN_EEPROM, SX_MIN);
        delay_ms(5); //seems excessive with writes checking the WR bit, but this makes writing more stable
        eepromWrite(SX_MAX_EEPROM, SX_MAX);
        delay_ms(5);
        eepromWrite(SY_MIN_EEPROM, SY_MIN);
        delay_ms(5);
        eepromWrite(SY_MAX_EEPROM, SY_MAX);
        delay_ms(5);
It's not the most elegant solution but it works, which is what I was aiming for. These changes allowed the calibration to work correctly each time it's run. I did make some other quality of life changes, such as only storing the values when the ranging made sense (basically as long as it's not dead center it will consider it good).
 
Top