Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 19 of 19

Thread: N64 controller -> USB on Teensy LC; button status 'flickering'

  1. #1
    Junior Member
    Join Date
    Sep 2020
    Posts
    18

    N64 controller -> USB on Teensy LC; button status 'flickering'

    Hi,

    I'm a software engineer by trade but this is my first foray into electronics so I apologise in advance for any noobiness.

    I've been trying to build a USB interface for an N64 controller. There are various implementations of this around the web primarily using arduino boards.

    The code* (attached below) should output the status of all the buttons of the N64 controller (using pin 2 / port 0x04 to read). This runs on an Arduino Nano just fine - except that the Nano doesn't support USB HID so that's defunct. But the code is stable (once the references to the Joystick library are removed of course).

    The serial monitor read looks like this and updates every 10ms:

    Code:
    Start: 0
    Z:     0
    B:     0
    A:     0
    L:     0
    R:     0
    Cup:   0
    Cdown: 0
    Cright:0
    Cleft: 0
    Dup:   0
    Ddown: 0
    Dright:0
    Dleft: 0
    Stick X:0
    Stick Y:0
    Stick Reset:0

    When it comes to implementing this on a Teensy LC or a Sparkfun Pro Micro, which both support USB HID, the button 'read' appears unstable. That is, if I don't touch anything on the controller, a couple of the buttons (particularly Dup and Ddown) flicker quickly from 0 to 1 and back. If I hold a button down on the controller, it is output correctly but flickers from 1 to 0 and back.

    As the code/serial monitor/button press is rock solid on the Nano, it doesn't feel like a bug in the code as such. I am leaning towards a difference in the Mhz speed of the boards since the read of the controller is tightly timed. Is that possible? I noticed the flicker seemed slightly different when running at 48mhz vs 24mhz. I did try a 16mhz underclock I found, but this wouldn't compile afterwards. Complaints about PluggableUSB libraries not being found.

    I feel so close - if anyone can help point me in the right direction, it'd be much appreciated.

    Thanks!

    Code:
    /**
     * Gamecube controller to Nintendo 64 adapter
     * by Andrew Brown
     * Rewritten for N64 to HID by Peter Den Hartog
     */
    
    /**
     * To use, hook up the following to the Arduino Duemilanove:
     * Digital I/O 2: N64 serial line
     * All appropriate grounding and power lines
     */
    
    
    #include "pins_arduino.h"
    
    #define N64_PIN 2
    #define N64_PIN_DIR DDRD
    // these two macros set arduino pin 2 to input or output, which with an
    // external 1K pull-up resistor to the 3.3V rail, is like pulling it high or
    // low.  These operations translate to 1 op code, which takes 2 cycles
    #define N64_HIGH DDRD &= ~0x4
    #define N64_LOW DDRD |= 0x4
    #define N64_QUERY (PIND & 0x4)
    
    // 8 bytes of data that we get from the controller
    struct {
        // bits: 0, 0, 0, start, y, x, b, a
        unsigned char data1;
        // bits: 1, L, R, Z, Dup, Ddown, Dright, Dleft
        unsigned char data2;
        char stick_x;
        char stick_y;
    } N64_status;
    char N64_raw_dump[33]; // 1 received bit per byte
    
    
    void N64_send(unsigned char *buffer, char length);
    void N64_get();
    void print_N64_status();
    void translate_raw_data();
    
    
    #include "crc_table.h"
    
    
    void setup()
    {
      Serial.begin(115200);
      Joystick.useManualSend(true);
    
      // Communication with gamecube controller on this pin
      // Don't remove these lines, we don't want to push +5V to the controller
      digitalWrite(N64_PIN, LOW);  
      pinMode(N64_PIN, INPUT);
    
    
      // Initialize the gamecube controller by sending it a null byte.
      // This is unnecessary for a standard controller, but is required for the
      // Wavebird.
      unsigned char initialize = 0x00;
      noInterrupts();
      N64_send(&initialize, 1);
    
      // Stupid routine to wait for the gamecube controller to stop
      // sending its response. We don't care what it is, but we
      // can't start asking for status if it's still responding
      int x;
      for (x=0; x<64; x++) {
          // make sure the line is idle for 64 iterations, should
          // be plenty.
          if (!N64_QUERY)
              x = 0;
      }
    
      // Query for the gamecube controller's status. We do this
      // to get the 0 point for the control stick.
      unsigned char command[] = {0x01};
      N64_send(command, 1);
      // read in data and dump it to N64_raw_dump
      N64_get();
      interrupts();
      translate_raw_data();  
      /*
      */
    }
    
    void translate_raw_data()
    {
        // The get_N64_status function sloppily dumps its data 1 bit per byte
        // into the get_status_extended char array. It's our job to go through
        // that and put each piece neatly into the struct N64_status
        int i;
        memset(&N64_status, 0, sizeof(N64_status));
        // line 1
        // bits: A, B, Z, Start, Dup, Ddown, Dleft, Dright
        for (i=0; i<8; i++) {
            N64_status.data1 |= N64_raw_dump[i] ? (0x80 >> i) : 0;
        }
        // line 2
        // bits: 0, 0, L, R, Cup, Cdown, Cleft, Cright
        for (i=0; i<8; i++) {
            N64_status.data2 |= N64_raw_dump[8+i] ? (0x80 >> i) : 0;
        }
        // line 3
        // bits: joystick x value
        // These are 8 bit values centered at 0x80 (128)
        for (i=0; i<8; i++) {
            N64_status.stick_x |= N64_raw_dump[16+i] ? (0x80 >> i) : 0;
        }
        for (i=0; i<8; i++) {
            N64_status.stick_y |= N64_raw_dump[24+i] ? (0x80 >> i) : 0;
        }
    }
    
    
    /**
     * This sends the given byte sequence to the controller
     * length must be at least 1
     * Oh, it destroys the buffer passed in as it writes it
     */
    void N64_send(unsigned char *buffer, char length)
    {
        // Send these bytes
        char bits;
        
        //bool bit;
    
        // This routine is very carefully timed by examining the assembly output.
        // Do not change any statements, it could throw the timings off
        //
        // We get 16 cycles per microsecond, which should be plenty, but we need to
        // be conservative. Most assembly ops take 1 cycle, but a few take 2
        //
        // I use manually constructed for-loops out of gotos so I have more control
        // over the outputted assembly. I can insert nops where it was impossible
        // with a for loop
        
        asm volatile ("#Starting outer for loop");
    outer_loop:
        {
            asm volatile ("#Starting inner for loop");
            bits=8;
    inner_loop:
            {
                // Starting a bit, set the line low
                asm volatile ("#Setting line to low");
                N64_LOW; // 1 op, 2 cycles
    
                asm volatile ("#branching");
                if (*buffer >> 7) {
                    asm volatile ("#Bit is a 1");
                    // 1 bit
                    // remain low for 1us, then go high for 3us
                    // nop block 1
                    asm volatile ("nop\nnop\nnop\nnop\nnop\n");
                    
                    asm volatile ("#Setting line to high");
                    N64_HIGH;
    
                    // nop block 2
                    // we'll wait only 2us to sync up with both conditions
                    // at the bottom of the if statement
                    asm volatile ("nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  );
    
                } else {
                    asm volatile ("#Bit is a 0");
                    // 0 bit
                    // remain low for 3us, then go high for 1us
                    // nop block 3
                    asm volatile ("nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\n");
    
                    asm volatile ("#Setting line to high");
                    N64_HIGH;
    
                    // wait for 1us
                    asm volatile ("#end of conditional branch, need to wait 1us more before next bit");
                    
                }
                // end of the if, the line is high and needs to remain
                // high for exactly 16 more cycles, regardless of the previous
                // branch path
    
                asm volatile ("#finishing inner loop body");
                --bits;
                if (bits != 0) {
                    // nop block 4
                    // this block is why a for loop was impossible
                    asm volatile ("nop\nnop\nnop\nnop\nnop\n"  
                                  "nop\nnop\nnop\nnop\n");
                    // rotate bits
                    asm volatile ("#rotating out bits");
                    *buffer <<= 1;
    
                    goto inner_loop;
                } // fall out of inner loop
            }
            asm volatile ("#continuing outer loop");
            // In this case: the inner loop exits and the outer loop iterates,
            // there are /exactly/ 16 cycles taken up by the necessary operations.
            // So no nops are needed here (that was lucky!)
            --length;
            if (length != 0) {
                ++buffer;
                goto outer_loop;
            } // fall out of outer loop
        }
    
        // send a single stop (1) bit
        // nop block 5
        asm volatile ("nop\nnop\nnop\nnop\n");
        N64_LOW;
        // wait 1 us, 16 cycles, then raise the line 
        // 16-2=14
        // nop block 6
        asm volatile ("nop\nnop\nnop\nnop\nnop\n"
                      "nop\nnop\nnop\nnop\nnop\n"  
                      "nop\nnop\nnop\nnop\n");
                      
        N64_HIGH;
    }
    
    void N64_get()
    {
        // listen for the expected 8 bytes of data back from the controller and
        // blast it out to the N64_raw_dump array, one bit per byte for extra speed.
        // Afterwards, call translate_raw_data() to interpret the raw data and pack
        // it into the N64_status struct.
        asm volatile ("#Starting to listen");
        unsigned char timeout;
        char bitcount = 32;
        char *bitbin = N64_raw_dump;
    
        // Again, using gotos here to make the assembly more predictable and
        // optimization easier (please don't kill me)
    read_loop:
        timeout = 0x3f;
        // wait for line to go low
        while (N64_QUERY) {
            if (!--timeout)
                return;
        }
        // wait approx 2us and poll the line
        asm volatile (
                      "nop\nnop\nnop\nnop\nnop\n"  
                      "nop\nnop\nnop\nnop\nnop\n"  
                      "nop\nnop\nnop\nnop\nnop\n"  
                      "nop\nnop\nnop\nnop\nnop\n"  
                      "nop\nnop\nnop\nnop\nnop\n"  
                      "nop\nnop\nnop\nnop\nnop\n"  
                );
        *bitbin = N64_QUERY;
        ++bitbin;
        --bitcount;
        if (bitcount == 0)
            return;
    
        // wait for line to go high again
        // it may already be high, so this should just drop through
        timeout = 0x3f;
        while (!N64_QUERY) {
            if (!--timeout)
                return;
        }
        goto read_loop;
    }
    
    void print_N64_status()
    {
        //int i;
        // bits: A, B, Z, Start, Dup, Ddown, Dleft, Dright
        // bits: 0, 0, L, R, Cup, Cdown, Cleft, Cright
        Serial.println();
        Serial.print("Start: ");
        Serial.println(N64_status.data1 & 16 ? 1:0);
    
        Serial.print("Z:     ");
        Serial.println(N64_status.data1 & 32 ? 1:0);
    
        Serial.print("B:     ");
        Serial.println(N64_status.data1 & 64 ? 1:0);
    
        Serial.print("A:     ");
        Serial.println(N64_status.data1 & 128 ? 1:0);
    
        Serial.print("L:     ");
        Serial.println(N64_status.data2 & 32 ? 1:0);
        Serial.print("R:     ");
        Serial.println(N64_status.data2 & 16 ? 1:0);
    
        Serial.print("Cup:   ");
        Serial.println(N64_status.data2 & 0x08 ? 1:0);
        Serial.print("Cdown: ");
        Serial.println(N64_status.data2 & 0x04 ? 1:0);
        Serial.print("Cright:");
        Serial.println(N64_status.data2 & 0x01 ? 1:0);
        Serial.print("Cleft: ");
        Serial.println(N64_status.data2 & 0x02 ? 1:0);
        
        Serial.print("Dup:   ");
        Serial.println(N64_status.data1 & 0x08 ? 1:0);
        Serial.print("Ddown: ");
        Serial.println(N64_status.data1 & 0x04 ? 1:0);
        Serial.print("Dright:");
        Serial.println(N64_status.data1 & 0x01 ? 1:0);
        Serial.print("Dleft: ");
        Serial.println(N64_status.data1 & 0x02 ? 1:0);
    
        Serial.print("Stick X:");
        Serial.println(N64_status.stick_x, DEC);
        Serial.print("Stick Y:");
        Serial.println(N64_status.stick_y, DEC);
        
        Serial.print("Stick Reset:");
        Serial.println(N64_status.data2 & 128 ? 1:0);
    }
    
    void loop()
    {
        //int i;
        //unsigned char data;
        //unsigned char addr;
    
        // Command to send to the gamecube
        // The last bit is rumble, flip it to rumble
        // yes this does need to be inside the loop, the
        // array gets mutilated when it goes through N64_send
        unsigned char command[] = {0x01};
    
        // don't want interrupts getting in the way
        noInterrupts();
        // send those 3 bytes
        N64_send(command, 1);
        // read in data and dump it to N64_raw_dump
        N64_get();
        // end of time sensitive code
        interrupts();
    
        // translate the data in N64_raw_dump to something useful
        translate_raw_data();
    
        //Start:
        Joystick.button(1, N64_status.data1 & 16 ? 1 : 0);
    
        Joystick.X(N64_status.stick_x);
        //Data for the Y axis seems to come out inverted
        Joystick.Y(N64_status.stick_y * -1);
    
        Joystick.send_now();
    
        // DEBUG: print it
        print_N64_status();
        delay(10);
      /*
        */
    }
    *Slight modifications by me; namely the TeensyLC Joystick partial implementation

    Setup:
    Teensy LC / Sparkfun Pro Micro / Arduino Nano
    Arduino IDE
    Windows 10 x64

  2. #2
    Junior Member
    Join Date
    Mar 2014
    Posts
    8
    Aha! This is something that I’ve been thinking about for a few weeks now!

    I also found those years-old Arduino-based tutorials on creating an interface to send button states over serial and then get interpreted through Processing. I got that working well enough with an Uno (and having almost no time for gaming anymore as it is) that I hadn’t gotten around to putting this at the top of my to-do list.
    Currently my power is still out following some severe wind a few days back so I can’t do much other than speculate- but I think the problem you’re seeing is the one that I was anticipating may be an issue (with the differences in processor speed).

    Have you made any progress since your initial post?
    What were your results with reading the controller with the original/unmodified version of the script on the Teensy?
    And just to more concretely rule out issues on the controller side- have you tested with a second controller?

    Looking forward to getting this working properly! I have already modeled and printed a basic N64 controller port and was planning on expanding that into a full enclosure to include the Teensy

  3. #3
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Quote Originally Posted by whiterabbit View Post
    Aha! This is something that I’ve been thinking about for a few weeks now!

    I also found those years-old Arduino-based tutorials on creating an interface to send button states over serial and then get interpreted through Processing. I got that working well enough with an Uno (and having almost no time for gaming anymore as it is) that I hadn’t gotten around to putting this at the top of my to-do list.
    Currently my power is still out following some severe wind a few days back so I can’t do much other than speculate- but I think the problem you’re seeing is the one that I was anticipating may be an issue (with the differences in processor speed).

    Have you made any progress since your initial post?
    What were your results with reading the controller with the original/unmodified version of the script on the Teensy?
    And just to more concretely rule out issues on the controller side- have you tested with a second controller?

    Looking forward to getting this working properly! I have already modeled and printed a basic N64 controller port and was planning on expanding that into a full enclosure to include the Teensy
    Hi - I have made progress in some ways - but still have a few things to figure out.

    Firstly, the code does seem to be tied specifically to a 16mhz board. So my Teensy LC just wouldn't work; the readings were not processed properly. I have tried various boards - Teensy at 24 & 48mhz, and a Pro Micro at 8Mhz and they all do the same kind of thing. I have three 16Mhz boards that read the controller and are fully stable. I don't have a second controller so there was a lot of trial and error and headache to pin that one down!

    I have settled on a Keyestudio atmega32u4 (arduino leonardo clone). It runs at 16mhz and is smaller than the arduino pro mini (which also runs the code fine). The code runs great on it too, just by selecting the Arduino Leonardo board in Arduino IDE.

    My issue at the moment is that I cannot seem to read the controller data when I power the board using a battery. I have a feeling this is something to do with draw or current or something. USB is fine, but battery power seems awry. Even the board itself doesn't seem to behave in the same way - the LEDs are different - but it's hard to debug since without USB, I have no serial output. I modded the code to turn on an LED when the L button is pressed. Works on USB power but no joy on battery. The controller operates at 3.3v. My board is 5v, and I have a 3.3v stepdown. I believe there is a 3.3v/16mhz board by keyestudio - maybe that's the ticket.

    So I'm still experimenting. My full goal is to stick a battery + board + Bluetooth HID chip into a rumblepack - you can run a data wire into the pack via the expansion slot - but unless I can figure out why it's not working on battery power then I'm pretty much stuck. I'd love to get it all working but my frustration is getting the better of me.

    The alternative is to try (!) and get a hold of an 8bitdo N64 controller and swap the shell, buttons, keymats etc. Apparently they all fit. That way the only thing that I lose is the original joystick (and the price of the controller - they're not cheap/easy to find).

  4. #4
    Junior Member
    Join Date
    Mar 2014
    Posts
    8
    Sounds like you’ve made quite a lot of progress! It’s definitely good that you have access to so many boards to test out for this. And I think being able to have a whole bluetooth HID adapter in a Rumble Pak enclosure would be awesome!

    OLD STUFF:

    I was able to test the original code on a Teensy LC and can confirm the issue you posted about. I got no readings at all at 48MHz, and sporadically-shifting readings at 24MHz. I was also getting unexpected and incorrect readings from the joystick, and noticed that in the same loop the D-Up and D-Right values jumped from 0 to 4 the ‘active’ buttons were shifting one place to the right and the analog values would be altered as well.

    For example:
    [touching nothing]


    Code:
    0000000400000000 0 255 
    0000000400000000 0 255 
    0000000400000000 0 255 
    0000040400000000 2 127

    …and…
    [holding down one of the D buttons]

    Code:
    0000400000000000 0 255 
    0000400000000000 0 255 
    0000400000000000 0 255 
    0000040400000000 2 127 
    0000400000000000 0 255 
    0000040400000000 2 127 
    0000400000000000 0 255 
    0000400000000000 0 255 
    0000400000000000 0 255

    Absolutely seems at least partially related to the clock speed, but I think could also be partially related to the different architectures. I don’t know much about that topic overall, and only know the most basic general things about Assembly, but I did see that in the original code the delays are just Assembly NOP statements which on AVR take one clock cycle, and according to this https://en.wikipedia.org/wiki/NOP_(code) they can take different amounts of clock cycles on different architecture. So I’m speculating that even if a Teensy was running at 16MHz that original Assembly code could possibly take a different amount of time to execute a NOP statement. And if that’s true then you’d need to know how long a NOP takes to execute on a Teensy running at whatever speed in order to add or subtract NOPs in the original code to realign the timing with what they had laid out for an AVR chip at 16MHz. Or maybe I’m thinking too hard about it, idk.

    I also found this: https://forum.pjrc.com/threads/30304...16-MHz-Options
    Which seems to indicate that you can manually add customized speed options. Is this what you mentioned in your previous post, related to missing libraries…?

    I don’t have much of a stock of spare microcontrollers on hand and don’t really need to get more right now- so I may attempt to fiddle with the custom speed options and modifying the NOPs to try to get a Teensy LC working.

    ———

    NEW STUFF:

    Quote Originally Posted by drobinson View Post
    I have settled on a Keyestudio atmega32u4 (arduino leonardo clone). It runs at 16mhz and is smaller than the arduino pro mini
    I haven’t used any boards from Keyestudio, although the images I found from searching for “Keyesudio atmega32u4” brought up a board which looks very similar in size and shape to an Arduino Uno/Leonardo, which is much larger than an Arduino Pro Mini- which in my experience is more the size of a Teensy LC. Maybe I’m not understanding something there.

    Quote Originally Posted by drobinson View Post
    My issue at the moment is that I cannot seem to read the controller data when I power the board using a battery.
    What type of battery are you using for this project? Some kind of 3.7v LiPo, or something with a higher voltage..?
    How is the battery connected to the board?

    And can you please clarify about the ‘3.3v step-down’ you mentioned- where are you using that?
    Posting a pic of the connections would be very helpful! That may help me give better suggestions on the issue.

    I’m not entirely sure if this would work, so take this with a grain of salty caution- but you may be able to power your project with a battery and still get serial communication over USB if you’re willing to do some surgery on a cable and cut the V+ wire. So you’re left with a USB cable that still has ground, and D+/- lines connected between your computer and microcontroller. Or if you have a small I2C/SPI screen you could hook that up to use for debugging instead of getting the serial output on your computer- although that also adds a whole other level of extra code and devices to worry about haha.

    Quote Originally Posted by drobinson View Post
    I have a feeling this is something to do with draw or current or something.
    Quote Originally Posted by drobinson View Post
    Even the board itself doesn't seem to behave in the same way - the LEDs are different
    I'm not sure what 'the LEDs are different' means- it's a bit subjective. Could you please clarify that?

    Do you have a multimeter to take any voltage or current readings on your current setup? Getting a baseline current reading of everything running properly via USB should help give you an idea of what to expect to see from battery usage. If you think you have everything hooked up properly and in a way that you expect it to work, you could compare readings between powering by USB vs powering via your chosen battery and look for differences in voltage and current draw.

    If you’re using a 3.7v battery to power a microcontroller that was designed for 5v, it may work fine but at decreased speed, or give you unexpected readings, or just not work at all. And instead of a step-down you’d need a step-up to boost the 3.7v to a usable ~5v for the microcontroller. Again, pic or schematic would help :)

    Quote Originally Posted by drobinson View Post
    I believe there is a 3.3v/16mhz board by keyestudio - maybe that's the ticket.
    Based on what we’ve figured out so far, I think almost 16MHz 3.3v board with AVR architecture that can act as an HID would be a great fit for this scenario. Plus it would be able to run on a 3.7LiPo that would be able to fit inside a Rumble Pak. Idk about a bluetooth HID though I don’t have any experience with those yet- I’m sure there must be some 3.3v-compatible ones around that could reliably communicate with a 3.3v microcontroller and also run off the same 3.7v battery.

  5. #5
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Quote Originally Posted by whiterabbit View Post
    Sounds like you’ve made quite a lot of progress! It’s definitely good that you have access to so many boards to test out for this. And I think being able to have a whole bluetooth HID adapter in a Rumble Pak enclosure would be awesome!
    It's definitely helped. Mostly they have been bought in error - for example, I started with an Arduino Nano, which doesn't (directly) support USB HID. The code was running fine but I couldn't take it any further. So I bought a couple more boards - each, as I found out, with their own limitations. But the differences between them has helped me narrow down the issues. Lots of googling, testing, learning.

    Quote Originally Posted by whiterabbit View Post

    OLD STUFF:

    I was able to test the original code on a Teensy LC and can confirm the issue you posted about. I got no readings at all at 48MHz, and sporadically-shifting readings at 24MHz. I was also getting unexpected and incorrect readings from the joystick, and noticed that in the same loop the D-Up and D-Right values jumped from 0 to 4 the ‘active’ buttons were shifting one place to the right and the analog values would be altered as well.

    For example:
    [touching nothing]


    Code:
    0000000400000000 0 255 
    0000000400000000 0 255 
    0000000400000000 0 255 
    0000040400000000 2 127

    …and…
    [holding down one of the D buttons]

    Code:
    0000400000000000 0 255 
    0000400000000000 0 255 
    0000400000000000 0 255 
    0000040400000000 2 127 
    0000400000000000 0 255 
    0000040400000000 2 127 
    0000400000000000 0 255 
    0000400000000000 0 255 
    0000400000000000 0 255

    Absolutely seems at least partially related to the clock speed, but I think could also be partially related to the different architectures. I don’t know much about that topic overall, and only know the most basic general things about Assembly, but I did see that in the original code the delays are just Assembly NOP statements which on AVR take one clock cycle, and according to this https://en.wikipedia.org/wiki/NOP_(code) they can take different amounts of clock cycles on different architecture. So I’m speculating that even if a Teensy was running at 16MHz that original Assembly code could possibly take a different amount of time to execute a NOP statement. And if that’s true then you’d need to know how long a NOP takes to execute on a Teensy running at whatever speed in order to add or subtract NOPs in the original code to realign the timing with what they had laid out for an AVR chip at 16MHz. Or maybe I’m thinking too hard about it, idk.
    Exactly what I found - and exactly my thoughts. I did mildly attempt to add in some cycles to 'pad out' the running code to wait an extra 8 cycles before looping. I probably didn't do it right at all, and quickly decided that probably wasn't the right way to go just yet. Should probably get it running properly on one architecture before trying to port it to another

    Quote Originally Posted by whiterabbit View Post
    I also found this: https://forum.pjrc.com/threads/30304...16-MHz-Options
    Which seems to indicate that you can manually add customized speed options. Is this what you mentioned in your previous post, related to missing libraries…?

    I don’t have much of a stock of spare microcontrollers on hand and don’t really need to get more right now- so I may attempt to fiddle with the custom speed options and modifying the NOPs to try to get a Teensy LC working.
    Yep exactly right. The code wouldn't compile at all at the lower speeds. In another forum/thread someone mentioned that the USB HID needed a minimum speed of 24mhz to run on the Teensy LC. I assume the libraries aren't included if the Mhz is set to less than that.

    Quote Originally Posted by whiterabbit View Post
    NEW STUFF:

    I haven’t used any boards from Keyestudio, although the images I found from searching for “Keyesudio atmega32u4” brought up a board which looks very similar in size and shape to an Arduino Uno/Leonardo, which is much larger than an Arduino Pro Mini- which in my experience is more the size of a Teensy LC. Maybe I’m not understanding something there.
    This is the one I have (and managed to burn out by incorrectly wiring it to a 9v battery). I just had delivery of a few more.

    Quote Originally Posted by whiterabbit View Post
    What type of battery are you using for this project? Some kind of 3.7v LiPo, or something with a higher voltage..?
    How is the battery connected to the board?

    And can you please clarify about the ‘3.3v step-down’ you mentioned- where are you using that?
    Posting a pic of the connections would be very helpful! That may help me give better suggestions on the issue.
    I started on 3.3v boards in the hope I could power it from 2xAA batteries. When I found that 3.3v boards tend to run at 8mhz I moved to a 5v/16mhz board and used this 3.3v stepdown to power the controller. I wasn't entirely sure how to wire it but it did seem to power the controller (as in I could read it). And as mentioned I moved up to a 9v battery - the Keyes board says it accepts 7-9v as raw input.

    I did try a 5v step up from the AAs too - ramped it up to 5v but it didn't seem to do anything differently - ie the board seemed to get power, but the controller wasn't doing anything (lighting the LED when L is pressed). At this point I think I need to simplify as much as possible to reduce the number of potential problems to debug.

    Quote Originally Posted by whiterabbit View Post
    I’m not entirely sure if this would work, so take this with a grain of salty caution- but you may be able to power your project with a battery and still get serial communication over USB if you’re willing to do some surgery on a cable and cut the V+ wire. So you’re left with a USB cable that still has ground, and D+/- lines connected between your computer and microcontroller. Or if you have a small I2C/SPI screen you could hook that up to use for debugging instead of getting the serial output on your computer- although that also adds a whole other level of extra code and devices to worry about haha.
    That's interesting. I don't have a screen, so if the V+ can just be cut that easily I might give it a go. One more thing to try! I don't have a screen unfortunately. I suppose if I could get the BT HID working, when it's all powered from USB, then I can experiment, knowing that it's probably just the power that's an issue.

    Quote Originally Posted by whiterabbit View Post
    I'm not sure what 'the LEDs are different' means- it's a bit subjective. Could you please clarify that?
    Yes, sorry, I was just tapping out everything in my last post - it's a bit fuzzy in my head, trying to remember everything I tried.

    I mean the LEDs on the Keyes board. When it's running from USB I believe there are two LEDs on - now that I think about it, presumably power & serial communication. It's not entirely clear. But on battery only the power LED comes on (50% brightness if it's given < 5v power). I guess I assumed that the lights would be the same if it was running properly. Maybe, maybe not.

    Quote Originally Posted by whiterabbit View Post
    Do you have a multimeter to take any voltage or current readings on your current setup? Getting a baseline current reading of everything running properly via USB should help give you an idea of what to expect to see from battery usage. If you think you have everything hooked up properly and in a way that you expect it to work, you could compare readings between powering by USB vs powering via your chosen battery and look for differences in voltage and current draw.

    If you’re using a 3.7v battery to power a microcontroller that was designed for 5v, it may work fine but at decreased speed, or give you unexpected readings, or just not work at all. And instead of a step-down you’d need a step-up to boost the 3.7v to a usable ~5v for the microcontroller. Again, pic or schematic would help
    I do, and I'm learning how to use/read that too, haha. Yes I should take readings from each point when it's running properly on USB and try to match that. I'm almost certain that the problem is the power but that's another grey area for me that I should figure out - current, draw etc. If I get really stuck I'll add a schematic to see if I'm doing anything glaringly wrong...


    Quote Originally Posted by whiterabbit View Post
    Based on what we’ve figured out so far, I think almost 16MHz 3.3v board with AVR architecture that can act as an HID would be a great fit for this scenario. Plus it would be able to run on a 3.7LiPo that would be able to fit inside a Rumble Pak. Idk about a bluetooth HID though I don’t have any experience with those yet- I’m sure there must be some 3.3v-compatible ones around that could reliably communicate with a 3.3v microcontroller and also run off the same 3.7v battery.
    Agreed. 3.3v/16mhz Keyes boards are on their way from China. That would simplify a lot! The BT HID looks simple enough to implement; I believe it just acts as a passthrough once connected to Rx/Tx. And they mostly run on 3.3v. Although mine isn't a HID chip, apparently the only difference is the firmware, and they can be flashed with HID firmware... I've been holding off on that though after the above experience! But maybe if I can get that working, it'll feel like progress and give me motivation for the rest of it.

    Thanks for all the above - it's been a good help in confirming some of my assumptions and giving me a few things to try. It all seems like it should all work, with just a minor internal mod to the controller. So I'll take a deep breath, cross my fingers, and dive back into it today

  6. #6
    Junior Member
    Join Date
    Mar 2014
    Posts
    8
    Apologies for the delayed reply- real life getting in the way of fun stuff, as usual haha.

    I managed to get as far as reading through some of your other posts to get caught up with what you've learned there.


    Quote Originally Posted by drobinson View Post
    I did mildly attempt to add in some cycles to 'pad out' the running code to wait an extra 8 cycles before looping. I probably didn't do it right at all, and quickly decided that probably wasn't the right way to go just yet. Should probably get it running properly on one architecture before trying to port it to another
    I haven't yet had time to attempt tweaking the delays, but hopefully tomorrow I'll be able to carve out an hour in the evening to give it a shot. Even though I'm confident that the new 16MHz board you ordered will likely do the trick, at this point my curiosity has gotten the best of me with regard to figuring out what it would take to get this working on an LC. My original plan was still to use a physical USB connection to my computer, so I guess a Teensy 2.0 would likely tick all the boxes to make that work. Right architecture, operating speed, HID functionality, size of the board itself. Although buying a new Teensy definitely defeats the goal of trying to do this as inexpensively as possible with the hardware I have on hand... may have to rewrite the goals of this project on my end to aim for making the best adapter possible as opposed to the cheapest and/or easiest.

    Quote Originally Posted by drobinson View Post
    So I'll take a deep breath, cross my fingers, and dive back into it today
    Hopefully you've managed to make some more progress since your last post! When I finally get back around to it, I'll update this thread with any findings. Happy to continue attempting to help you troubleshoot your configuration if you'd like, otherwise I'll just await the results of your latest Keyes board purchase! And I may have to follow in your footsteps of making the adapter wireless- that's a very handy feature

  7. #7
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Quote Originally Posted by whiterabbit View Post
    Apologies for the delayed reply- real life getting in the way of fun stuff, as usual haha.
    Ain't that the truth

    Well. Here's the brief update. I believe I have all the hardware in place. I relented and bought a proper RN42 I/RM module. I have configured that to present itself as a gamepad and Windows (and retropi) will indeed connect to it. But, the buttons do not register. I believe at this point it's just a matter of software. Which is good, but also bad because low level stuff like this is not my area at all.

    i *thought* that the hardware Tx/Rx on the board (Keyes 3.3v, Arduino Leonardo clone) was effectively a clone of what was going out over USB serial, but I'm no longer sure of that. So in my code I have a Joystick library, and digging deeper into that, it looks like it's tied deep into the USB library. So I could be barking up the wrong tree. I can plug the controller into the USB and output to the BT module at the same time. Windows recognises two devices, but only the USB one registers buttons and the joystick correctly (and at all).

    There are various examples of sending a HID state directly over serial. I think this is what I want but I don't know what I'm doing with bit modifiers and such, and the code seems to - for no reason I can see - set button states using hex and binary rather than sticking to hex. Example a:

    Code:
    Serial.print("B:     ");
        Serial.println(N64_status.data1 & 64 ? 1:0);
        Serial.print("A:     ");
        Serial.println(N64_status.data1 & 128 ? 1:0);
        Serial.print("L:     ");
        Serial.println(N64_status.data2 & 32 ? 1:0);
        Serial.print("R:     ");
        Serial.println(N64_status.data2 & 16 ? 1:0);
    
        Serial.print("Cup:   ");
        Serial.println(N64_status.data2 & 0x08 ? 1:0);
        Serial.print("Cdown: ");
        Serial.println(N64_status.data2 & 0x04 ? 1:0);
        Serial.print("Cright:");
        Serial.println(N64_status.data2 & 0x01 ? 1:0);
        Serial.print("Cleft: ");
        Serial.println(N64_status.data2 & 0x02 ? 1:0);
    Anyway. I know the button mapping works so hopefully it's just a case of sending that over Serial.write() directly. But the format looks something like this from what I have gathered:

    Code:
    void sendGamepadState(uint32_t btnState1, uint32_t btnState2, int8_t x1, int8_t y1)
    {
      Serial.write(0xFD);                // indicates raw hid report
      Serial.write(0x04);                // bLength
      Serial.write(0x01);                // bDescriptorType - constant ( String assigned by USB )
      Serial.write(x1);                  // Byte0
      Serial.write(y1);                  // Byte1
      Serial.write(btnState1 & 0xFF);    // Byte2
      Serial.write(btnState2 & 0xFF);    // Byte3
    }
    x1 and yq are the axis and btnState1 and btnState2 are two bytes representing (up to) 16 buttons. But I'm not sure how to translate the buttons I have into the hex. Also, when I write that out into Serial, on the monitor on the PC I just see 'garbage' - presumably because it's hex and not ASCII.

    Any tips would be appreciated! I'm sure I'm missing some things but this is getting very difficult for me to debug

  8. #8
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Small addendum. I have been looking at a couple of libraries specifically for RN42 and Joysticks:

    https://github.com/chukione/arduinominirn42hidjoystick
    https://github.com/silverball/RN42_HIDRaw_Arduino

    I've slightly modified my code, taking their sample Serial.write() lines and outputting a fixed value that should turn half of the buttons on. Still no joy. And when using the N64 controller data directly (it looks like I should be able to) and using the serial monitor, I can see that the data does change with each button.

    Perhaps it's an issue with the hardware TxRx on my board. Or maybe an issue getting that data to the BT module. I'm confident the serial output should work but again it's difficult to debug.

  9. #9
    Junior Member
    Join Date
    Mar 2014
    Posts
    8
    Hmmm ok, just to quickly summarize and make sure I'm still with you on all this:
    -you have the microcontroller successfully reading the N64 controller
    -the microcontroller also successfully presenting to Windows/RetroPie as a controller
    -all button presses etc. work properly and accurately over wired USB

    BUT

    -although Windows recognizes the bluetooth module as a device, the data being sent via wireless serial is coming out garbled?

    If that's not right please correct me. Most of what you're doing now is with hardware I've never worked with, using libraries I've never even heard of haha

    But speaking generally, if the problem you're having is isolated to the communication with the bluetooth module then that is where I'd focus troubleshooting for now. It sounds like at this point your full code is getting a bit complicated, and uses new libraries you're mostly unfamiliar with. I would try breaking things down into smaller more manageable chunks, which it sounds like you already attempted by modifying the sample sketches.

    Have you confirmed that you can send anything at all successfully over bluetooth? I'd upload a very basic sketch to your microcontroller that sends a single character (eg "A") every second, and see if you can read that on the computer in a serial monitor. Cut out all the N64 stuff, and make sure you can make the hardware do something you want it to do. And then from there maybe turn it back into a bluetooth joystick and have it send a single button press every second. If you can get it working to that extent then we can at least narrow down the issue to the data being sent.

    I've never had a reason to work with Serial.write() so maybe I'm missing something there too but from what I'm reading, a (proper) hex value sent via serial should still be interpreted as a character on the receiving end. At least, if the baud matches on both devices.

    But yeah, I'd take a small lateral step and confirm you're using functioning hardware using the most simple software you can manage, and then work from there back towards sending controller data- first with manual/hardcoded button presses, and then with the live N64 controller data.

    On my end, I still haven't even had time to hack away at this project because my 3D printer wasn't working properly. But it's better than ever now so hopefully I can get back to this soon! *fingers crossed* :]

  10. #10
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Quote Originally Posted by whiterabbit View Post
    Hmmm ok, just to quickly summarize and make sure I'm still with you on all this:
    --snip--

    Whoops! I wrote this and didn't post it!

    ---

    Success!

    The controller is now finally recognised in Windows, and registering the buttons & joystick correctly.

    The trick seemed to be to set up a SoftwareSerial to communicate with the RN42 rather than to use the hardware RxTx pins. And I was indeed able to use the data from the N64 code directly.

    These blocks were the key:

    Code:
    #include <SoftwareSerial.h>  
    
    int bluetoothTx = 14;  // TX-O pin of RN42
    int bluetoothRx = 16;  // RX-I pin of RN42
    
    SoftwareSerial bluetooth(bluetoothTx, bluetoothRx);
    Code:
    void sendGamepadState(uint8_t btnState1, uint8_t btnState2, int8_t x1, int8_t y1)
    {
      bluetooth.write((byte)0xFD); //Start HID Report
      bluetooth.write((byte)0x6);  //Length byte
      
      // 1. X/Y-Axis
      bluetooth.write(x1);  //First X coordinate
      bluetooth.write(y1); //First Y coordinate
      
      // 2. X/Y-Axis
      bluetooth.write((uint8_t)0x00);  // Null
      bluetooth.write((uint8_t)0x00); // Null
      
      // Buttons
      bluetooth.write(btnState1); // Second Byte (Buttons 1-8)
      bluetooth.write(btnState2); // Second Byte (Buttons 9-16)
    }
    We were on the same lines - I simplified by sending a hardcoded 'some buttons on, some buttons off'. And as soon as I swapped to the SoftwareSerial (and reconfigured the baud rate on the RN42 to 57600) it all just kicked in. The button mapping from the N64 functions was already in formats I could use directly.

    And even better, it all runs from 2xAAs. I've wired it so that the rumblepack pushes power into the controller, and then another wire back out into an unused trace on the rumblepack PCB. This means I can power the arduino from that pin, and the RN42 from the arduino, and to turn it off I just unplug the rumblepack. Quite proud of that bit

    Well I'll wire it all up into the pack tomorrow, test it out, then pop my code on github if you'd like a copy. I'll probably do an online write up too

  11. #11
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Well, it's nearly done. Testing with Retropie / EmulationStation, the only issue when mapping is that the analogue stick wasn't being picked up. I mapped it directly in the config but the values don't seem quite right; the range needs to be calibrated as pusing the stick to the max in any direction only seems to be about 50% of what it should be.

    When debugging the controller in the serial output, the values from the stick seem to read around -80 to +80. I think this should really be upscaled to -127 to +127 but I'm not sure how to do that. The values read directly from the bytes read from the controller I presume are so low that they're still in the default deadzone for the joystick, hence the mapping through EmulationStation not picking them up.

    It's possible that one option could be to add the xbox controller driver which appears to allow for calibration but I think I'd rather give the values a boost in the arduino code. This is the code that reads them:

    Code:
    // These are 8 bit values centered at 0x80 (128)
        for (i=0; i<8; i++) {
            N64_status.stick_x |= N64_raw_dump[16+i] ? (0x80 >> i) : 0;
        }
        for (i=0; i<8; i++) {
            N64_status.stick_y |= N64_raw_dump[24+i] ? (0x80 >> i) : 0;
        }
    And debugging, outputting them as a decimal? Which translates to the -80 to +80 as stated:

    Code:
        Serial.print("Stick X:");
        Serial.println(N64_status.stick_x, DEC);
        Serial.print("Stick Y:");
        Serial.println(N64_status.stick_y, DEC);
    Interestingly, when I output the N64_status.stick_y directly, the negative values read as (65335 minus 0 to 80) and the positive values read from 0 to 80.

    Any tips for converting those bytes to a -127 to 127 range (or even just multiplying the value by 1.5) would be much appreciated.

    FWIW the stick when I got it was loose, I fixed it myself so I don't know what a genuine, fully working controller should read as.

  12. #12
    Junior Member
    Join Date
    Mar 2014
    Posts
    8
    Congratulations!!

    How's the responsiveness? Or have you not got around to actually playing anything with it yet?

    I would definitely love to see the final code whenever you've got it working to your satisfaction. A BOM and basic connection guide would be awesome, too :]

    I think I can help on the thumbstick readings, on the software side. When I built my own retropie handheld last year I was working a lot from the sudomod forums, and stumbled across an Arduino sketch that I used as a template for my own Teensy HID.

    Generally speaking, the max/min boundary values of the analog stick readings were not hardcoded into the sketch, but rather required calibration each time the sketch was run. More specifically, each loop you check the value of each axis and compare that to the highest/lowest seen value and if it's higher than the highest recorded value or lower than the lowest recorded value, that becomes the new highest/lowest value. From there, you can map the read value to adjust the current axis value before sending it. Thus, "calibration" just means you give the thumbstick a 360deg swirl when you turn on the controller so it can measure the bounds of what the stick is returning, and from there you can then launch your game and you should have access to the full range of values you're expecting from the stick.

    You'll see the final values getting sent out as joystick values, as this was written for a Teensy acting as a joystick- you'll just need to update your code to work with the freshly 'calibrated' and mapped values. That should be simple enough though, after all the other work you've put in!

    Code:
    ////////////////////////////// SUDOMOD ANALOG /////////////////////////////////////////////////////////////
    //////////////////////////////
    
    // Analog stick input pins, invert, and deadzone:
    int deadzone = 5; // Values under 5 will return 0
    
    int xAxisPin = A5;
    int yAxisPin = A6;
    
    int invertXaxis = 1;
    int invertYaxis = 0;
    
    
    // Variable declarations for analog sticks
    // Zero values are set during initial setup when the sticks are at neutral positions
    int xAxisZero, yAxisZero;
    int xAxisValue, yAxisValue;
    
    // Gave these some default min/max values to prevent the erratic scrolling behavior within RetroPie immediately upon startup when you haven't rolled the analog stick to calibrate it
    int xAxisMin = -350;
    int xAxisMax = 350;
    int yAxisMin = -350;
    int yAxisMax = 350;
    
    int xAxisF1, xAxisF2;
    int yAxisF1, yAxisF2;
    
    
    //////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    
    void setup() {
    
        // Declare specified pins as analog inputs for analog stick
        pinMode(xAxisPin, INPUT);
        pinMode(yAxisPin, INPUT);
    
        // Measure 'neutral' position for analog stick
        xAxisZero = analogRead(xAxisPin);
        yAxisZero = analogRead(yAxisPin);
    
    }
    
    
    
    void loop() {
    
        // Autocalibration of analog sticks (by Helder + wermy)
        // Read analog pins for the thumbstick
        xAxisValue   = analogRead(xAxisPin)   - xAxisZero;
        yAxisValue   = analogRead(yAxisPin)   - yAxisZero;
    
    
        
        // Check to see if readings are within deadzone (center)
        if(abs(xAxisValue) < deadzone) {
          xAxisValue = 0;
        }
        if(abs(yAxisValue) < deadzone) {
          yAxisValue = 0;
        }
        
        
        // Finds the max and min values produced by the stick outputs and uses those as min/max bounds
        if (xAxisValue > 0 && xAxisValue > xAxisMax) {
          xAxisMax = xAxisValue;
        } else if (xAxisValue < 0 && xAxisValue < xAxisMin) {
          xAxisMin = xAxisValue;
        }
    
        if (yAxisValue > 0 && yAxisValue > yAxisMax) {
          yAxisMax = yAxisValue;
        } else if (yAxisValue < 0 && yAxisValue < yAxisMin) {
          yAxisMin = yAxisValue;
        }
    
        float xAxissMax = abs(xAxisMax);
        if (xAxisValue < 0) {
          xAxissMax = abs(xAxisMin);
        }
    
        float yAxissMax = abs(yAxisMax);
        if (yAxisValue < 0) {
          yAxissMax = abs(yAxisMin);
        }
    
    
        // Calculate final values by curving true min/max values compared to potential values
        int16_t xAxisFinal   = (((float)xAxisValue   / xAxissMax)  *512);
        int16_t yAxisFinal   = (((float)yAxisValue   / yAxissMax)  *512);
    
        // Check if Axes need to be inverted, and if yes, do so
        if (invertXaxis == 0) {
          xAxisF1 = 512;
          xAxisF2 = -512;
        }
        else {
          xAxisF1 = -512;
          xAxisF2 = 512;
        }
        if (invertYaxis == 0) {
          yAxisF1 = 512;
          yAxisF2 = -512;
        }
        else {
          yAxisF1 = -512;
          yAxisF2 = 512;
        }
    
        // Parse autocalibrated analog stick values
        Joystick.X(map(xAxisFinal, xAxisF1, xAxisF2, 0, 1023));
        Joystick.Y(map(yAxisFinal, yAxisF1, yAxisF2, 0, 1023));
    
    }
    You could also scrap all the inversion-related code if you don't need it!

  13. #13
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Quote Originally Posted by whiterabbit View Post
    Congratulations!!

    How's the responsiveness? Or have you not got around to actually playing anything with it yet?

    I would definitely love to see the final code whenever you've got it working to your satisfaction. A BOM and basic connection guide would be awesome, too :]

    I think I can help on the thumbstick readings, on the software side. When I built my own retropie handheld last year I was working a lot from the sudomod forums, and stumbled across an Arduino sketch that I used as a template for my own Teensy HID.
    The responsiveness seems spot on - except Mario can only walk at the moment A and B are reversed in mupen after mapping them properly in EmulationStation; looking at the config files, this is due to the mapping of a more standard controller to N64 inputs. No big deal.

    Having a play about, the map() function seems to be what I was looking for, thank you. Quick test does scale the values properly (I found scaling to a -127 to +127 worked best) - in Windows at least. I've not tested natively in the retropie yet.. I will likely extend to include the calibration routines too, once it's tested AOK.

    I think I've blown a resistor in the rumblepak PCB I'm using (possibly at the same time I bricked an arduino board). No biggie, it's only used for routing power at the moment. Getting rumble to work is some future goal anyway!

  14. #14
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Right, well, Mario is running now! I admit I got impatient and just scaled the 0-80 values I was seeing to -127 to +127 using map(). Your way is definitely better, as the values I'm seeing aren't the same in both - and + axis directions so I'll work that in at some point. Unless you want to create a pull request

    Need to change the config on mupen64 - like I say they map the buttons for more standard controllers so right now B isn't working but that's nothing to do with the Arduino.

    All in all - success! The raspi is housed in a snes case so its Bluetooth range isn't great, but that's the raspi and not the controller; same thing happens with a Dualshock 3. Keep meaning to buy a usb BT receiver to extend that.

    Anyway, thanks for all your help & for being a sounding board! I'll do an initial build overview as part of my git repo & drop the link in here over the weekend.

    Happy modding

  15. #15
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Well, I tried to integrate the joystick calibration and unfortunately it didn't go so well!

    I think it's C++ semantics and how values are stored, expressed and compared. As I say when I output the axis as read directly, a negative value will be something like (65535 - axis value) and positive values come out as (axis value). I thought abs() would help this but I can't quite figure it out.

    I have this code in right now:

    Code:
    // default min/max values for joystick
    int axis_x_min = -70;
    int axis_x_max = 70;
    int axis_y_min = -70;
    int axis_y_max = 70;
    int axis_x_val, axis_y_val;
    
    void loop(){
        axis_x_val = (int) (N64_status.stick_x, DEC);
        axis_y_val = (int) (N64_status.stick_y, DEC);
    
        // Track the x/y axis to find its min/max values for auto calibration
        if (axis_x_val > 0 && axis_x_val > axis_x_max) {
          axis_x_max = axis_x_val;
        } else if (axis_x_val < 0 && axis_x_val < axis_x_min) {
          axis_x_min = axis_x_val;
        }
    }
    What I'm seeing is that a) a negative value will set axis_x_min to a *positive* value, and b) a positive value will update the *minimum* value. So it's in a bit of a mess

    I read a couple of threads on StackOverflow which helped me understand the 'whys' but didn't really help clear up what I should be doing.

    Also the negative value ranges (on my controller at least) seem to be smaller than the positive. IE a positive max value will be in the mid-80s, and the negative min value will be in the high 70s, so I think they will need to be handled separately before mapping the value to a new range of -127 to +127, otherwise '0' won't be the mid point:

    Code:
    // pseudo code
    if(axis_x < 0){
        axis_x = map(axis_x, axis_x_min, 0, -127, 0);
    } else {
        axis_x = map(axis_x, 0, axis_max, 0, 127);
    }
    What is it that I'm missing when working with the value comparisons? How can I standardise all values to a plain ol' int and compare them like a 'normal' number?

  16. #16
    Member
    Join Date
    Aug 2018
    Location
    Brisbane, Australia
    Posts
    30
    Can't say too much about the typing without seeing the decalarion of N64_status

    The "normal" way to use map is
    Code:
    map(axis_x, axis_x_min, axis_x_max, -127, 127);

  17. #17
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Quote Originally Posted by thebigg View Post
    Can't say too much about the typing without seeing the decalarion of N64_status
    Yes, bad form, sorry. I should have included that. It's a char:

    Code:
    struct {
        // bits: 0, 0, 0, start, y, x, b, a
        unsigned char data1;
        // bits: 1, L, R, Z, Dup, Ddown, Dright, Dleft
        unsigned char data2;
        char stick_x;
        char stick_y;
    } N64_status;
    It's written to like this, so I don't think I can change it to an int in its definition:

    Code:
        for (i=0; i<8; i++) {
            N64_status.stick_x |= N64_raw_dump[16+i] ? (0x80 >> i) : 0;
        }
    If I want to work with that, should I be converting it to an int? Would that have any side effects?

    Code:
    int axis_x_value = (int) N64_status.stick_x;

  18. #18
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Small update. I have the values compared and the full range working properly now. ardioBut when I map them to a -127 - +127 range, Windows sees any negative value on x/y as 'absolute minimum' (the cursor displays as far left/top). Positive values work fine :/

    The serial monitor does show the values working correctly (or at least, as expected).

    Here's what I have:

    Code:
    // default min/max values for joystick
    int axis_x_min = -70;
    int axis_x_max = 70;
    int axis_y_min = -70;
    int axis_y_max = 70;
    int axis_x_val, axis_y_val;
    int axis_x, axis_y;
    And inside loop():

    Code:
        // Joystick range calibration
        axis_x_val = (int) N64_status.stick_x;
        axis_y_val = (int) N64_status.stick_y;
    
        if(axis_x_val < axis_x_min){
          axis_x_min = axis_x_val;
        }
        if(axis_x_val > axis_x_max){
          axis_x_max = axis_x_val;
        }
        
        if(axis_y_val > axis_y_max){
          axis_y_max = axis_y_val;
        }
        if(axis_y_val < axis_y_min){
          axis_y_min = axis_y_val;
        }
    
        // Positive and negative range may not be even
        if(axis_x_val < 0){
          axis_x = map(axis_x_val, axis_x_min, 0, -127, 0);
        } else {
          axis_x = map(axis_x_val, 0, axis_x_max, 0, 127);
        }
        
        if(axis_y_val < 0){
          axis_y = map(axis_y_val, axis_y_min, 0, -127, 0);
        } else {
          axis_y = map(axis_y_val, 0, axis_y_max, 0, 127);
        }
    
        // Write the status out
        sendGamepadState(N64_status.data1, N64_status.data2, axis_x, axis_y * -1);

  19. #19
    Junior Member
    Join Date
    Sep 2020
    Posts
    18
    Well, the lesson of *that* story is, always test in the real environment! I was outputting to a USB Joystick via a library for debugging, just because it's quicker. As soon as I reconnected back to bluetooth, I realised everything was working... so thanks all

    I've added code, a brief overview and a basic connection list to a git repo. I'll note, at some point, a couple of changes I've had to make to the retropie configs.

    https://github.com/dhrobinson/NUS-005BT

    I will follow up (after a break) with a full writeup with photos.

    Phew!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •