PortaVoltex

—「0X://Explanation」
a portable controller to play k-shoot mania/sound voltex type games, inspired by the pocket voltex, but made by someone not as smart┈ this was my first time really diving into a project i didn't understand, and developing the skills to make it possible along the way, though its not perfect. well at least it's not as loud or clunky as a regular one... but that one at least had lights

back a page

Overview

normally these just track the process of me learning, but this one is so long and convaluded, here is a quick overview in case you want to make this yourself. this is a mini version of one of these controllers:

meant for playing sound voltex, k-shootmania, or similar games. i have (the less cool looking) one of these, but i find it too loud to play on speaker and too big to play conveniently. this (and seeing the pocket voltex) inspired me to try fully designing and programming one of these myself.

things i used (links to specific ones):

outlining assembly is a little difficult, this kinda assuming you know how to wire things together. i mean considering someone as dumb as me could design it, i bet you could build it. reference the logs below for any help, sorry that's all i'm willing to do as of now. if you would like to read about the struggles and process of designing this just have a look below:

Log 1: Preparations

i have a general plan for what this is gunna look like, but im not entirely about the firmware, since i'd have to make it myself, rather than using qmk or someone's arduino script. as far as i know a teensy lc is the only way i can have both knobs and keystrokes, so that is why im doing that (but maybe qmk has knob support, i'll see when i get to that part) for now,

the things i need to get/design/program are:

so i guess this makes the first thing i should do the case design, but im actually gunna hook up the encoders to an arduino to make sure they work, i don't wanna waste my time designing a case for rotary encoders that don't work

i found some documentation about my optical rotary encoder, which gave me the necessary wiring info:

then i found a quick code example to test the encoder with, here is the code:


                  #include ﹤Encoder.h﹥

                  Encoder myEnc(5, 6);
                  
                  
                  void setup() {
                    Serial.begin(9600);
                    Serial.println("Basic Encoder Test:");
                  }
                  
                  long oldPosition  = -999;
                  
                  void loop() {
                    long newPosition = myEnc.read();
                    if (newPosition != oldPosition) {
                      oldPosition = newPosition;
                      Serial.println(newPosition);
                    }
                  }
                           
                           

and after i wired it up (with a nano for testing), it worked perfectly:

so seeing that it works, i guess 3d modelling is the next important step, so should probably start brainstorming that. im no designer but i did a quick sketch of some basic measurements:

it is also worth keeping in mind that the back side is going to be alot heavier than the front, i'm going to be some weights in case i need them to balance the box. after some research i realized i can use an arduino pro micro instead, so before i attempt to design it, im going to wire a test version and get the software working

so i did the code for the encoders using the Encoder and Mouse libraries. games like k-shoot mania and sound voltex have the encoders act as vertical and horizontal mouse movements each, so that is all i had to program. my initial idea was to scan for every value of the encoder and if the current value is greater than the previous then move one direction, and if it is less than then move the other. this almost worked, but there was a bit of oscillation on the readouts when i did a quick turn, as seen in this movement (it was just a quick turn, but it oscillated for about twenty values):

to fix this i just had a counter that counted every time the loop ran (because i don't know the speed at which the chip executes the loop) and after a bit of guess and check found 50 to be a good number to run my code on. so every 50 loops the code checks for a change in position, which seems like it would miss alot of values, but as seen below, every value is still perserved in a change in direction, almost perfectly:

after also learning that i can use analog pins for the encoders, the working code for them is:


                  #include ﹤Encoder.h﹥
                  #include ﹤Mouse.h﹥
                  
                  Encoder encOne(A2, A3);
                  Encoder encTwo(A0, A1);
                  
                  
                  void setup()
                  {
                    Serial.begin(9600);
                    Serial.println("Basic Encoder Test:");
                  }
                  
                  long oldPosition1  = -999;
                  long oldPosition2  = -999;
                  int count = 0; 
                  int sped = 2; // controls speed of mouse movement
                  
                  void loop() 
                  {
                    long newPosition1 = encOne.read();
                    long newPosition2 = encTwo.read();
                    if (count == 50) // slows scan rate (a debounce of sorts)
                    {
                      if (newPosition1 != oldPosition1)
                      {
                        if (newPosition1 ﹤ (oldPosition1)) 
                        {
                          Mouse.move(0, sped, 0);
                        }
                        else if ((newPosition1) ﹥ oldPosition1)
                        {
                          Mouse.move(0, -sped, 0);
                        }
                    
                        oldPosition1 = newPosition1;
                         
                        Serial.println(newPosition1);
                      }
                      if (newPosition2 != oldPosition2)
                      {
                        if (newPosition2 ﹤ (oldPosition2))
                        {
                          Mouse.move(sped, 0, 0);
                        }
                        else if ((newPosition2) ﹥ oldPosition2)
                        {
                          Mouse.move(-sped, 0, 0);
                        }
                    
                        oldPosition2 = newPosition2;
                         
                        Serial.println(newPosition2);
                      }
                      count = 0;
                    }
                    count++;
                  }
                  
                  
                           
                           

okay so its modelling time now, first things first, i designed the holes for keycaps and stabilizers:

wait a minute... i hate stabilizers

that's better, then i intersected it with a box with holes for the encoders:

quick made it into a full case:

ehhh that is kinda ugly, so i edited it a bit:

that's better, now adding a port, fixing the top holes:

and some holes:

tada!~ that took longer than i made it seem but still alot faster than i thought it would. turns out practicing modelling helped me model. crazy i know. the printer is still occupied so i can't print these yet, and assuredly something will go wrong and i will have to remake them, but pretty good for now

well i was planning on procrastinating the matrix code, but then i wrote it. this *should* work, though i'll have to wait till its built to test it:


              《CODE REDACTED》
              (since it doesn't end up working i thought i'd just remove this to save the page from being too long)
                
                         
                         

can you believe this log was called "Preparations"? even tho this was the first day i've worked on this, i got everything done that i can until i have access to my printer, pretty proud ngl

Log 2: The Rest

so my printer was occupied while i did the first log, but its free now, so i started with printing the models i made. the moment of truth came, time to test how the pieces fit:

and these fit perfectly! but sadly the arduino did not fit perfectly, however i did not reprint it cuz i was able to jam it in, but the final model will include an adjusted version. i do not have access to an area to paint the case at the moment (its -5°F outside right now), so i did the next best thing, went into photoshop and designed a sticker (here is the exact file i printed, designed for a 8.5x11in of paper):

and an exacto knife and like 10 minutes resulted in:

with it having a slightly more appealling look now, it is about time to wire it up. i first wired up the optical encoders, because i have the least experience with those, and wanted to make sure it worked.

after flashing a slightly changed version of the code from earlier, it did not work at all. after a bit of panicking i concluded that i 'bricked' the arduino some how and soldered and flashed a new one. after alot of frustration i found this helpful comment online:

for some reason the way i had set up the delay code (which is not what i needed to do anyway, i was just testing the keyboard matrix) caused the arduino to get stuck??? so after shorting it (twice in succession) and reflashing it (with better code) i got it to work again, phew. then i wired up the keyboard matrix:

and proceeded to get increasingly frustrated with the matrix code. after successfully proving that each key works, i needed to get them to send keystrokes. my code became a jumbled mess of combining things i had found, full of test serial print statements and things i didn't think i needed but taking them out made it break. sadly i haven't fully gotten the code how i want it, luckily i can just rebind them in game for now. (if someone happens to know what is wrong please send me a message, i'll still keep trying to figure it out though). anyway here is the awful terrible (but functional {kinda}) code:


              
              #include ﹤Keypad.h﹥
              #include ﹤Keyboard.h﹥
              #include ﹤Encoder.h﹥
              #include ﹤Mouse.h﹥
              
              Encoder encTwo(A3, A2);
              Encoder encOne(A1, A0);
              
              
              const byte ROWS = 3; //four rows
              const byte COLS = 3; //three columns
              char keys[ROWS][COLS] = {
              {'1','2','3'},
              {'4','5','6'},
              {'7','8','9'}
              };
              char knees[9]= {97,98,99,100,101,102,103,104,105};
              byte rowPins[ROWS] = {4, 3, 2}; //connect to the row pinouts of the kpd
              byte colPins[COLS] = {8, 7, 6}; //connect to the column pinouts of the kpd
              
              Keypad kpd = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
              
              unsigned long loopCount;
              unsigned long startTime;
              String msg;
              int bbb;
              
              void setup() {
                  Serial.begin(9600);
                  loopCount = 0;d
                  startTime = millis();
                  msg = "";
              }
              
              long oldPosition1  = -999;
              long oldPosition2  = -999;
              int count = 0; 
              int sped = 1; // controls speed of mouse movement
              
              void loop() {
                  long newPosition1 = encOne.read();
                  long newPosition2 = encTwo.read();
                  if (count == 50) // slows scan rate (a debounce of sorts)
                  {
                    if (newPosition1 != oldPosition1)
                    {
                      if (newPosition1 ﹤ (oldPosition1)) 
                      {
                        Mouse.move(0, sped, 0);
                      }
                      else if ((newPosition1) ﹥ oldPosition1)
                      {
                        Mouse.move(0, -sped, 0);
                      }
                  
                      oldPosition1 = newPosition1;
                       
                      Serial.println(newPosition1);
                    }
                    if (newPosition2 != oldPosition2)
                    {
                      if (newPosition2 ﹤ (oldPosition2))
                      {
                        Mouse.move(sped, 0, 0);
                      }
                      else if ((newPosition2) ﹥ oldPosition2)
                      {
                        Mouse.move(-sped, 0, 0);
                      }
                  
                      oldPosition2 = newPosition2;
                       
                      Serial.println(newPosition2);
                    }
                    count = 0;
                  }
                  count++;
                  loopCount++;
                  if ( (millis()-startTime)>5000 ) {
                      Serial.print("Average loops per second = ");
                      Serial.println(loopCount/5);
                      startTime = millis();
                      loopCount = 0;
                  }
              
                  // Fills kpd.key[ ] array with up-to 10 active keys.
                  // Returns true if there are ANY active keys.
                  if (kpd.getKeys())
                  {
                      for (int i=0; i﹤LIST_MAX; i++)   // Scan the whole key list.
                      {
                          if ( kpd.key[i].stateChanged )   // Only find keys that have changed state.
                          {
                              switch (kpd.key[i].kstate) {  // Report active key state : IDLE, PRESSED, HOLD, or RELEASED
                                  case PRESSED:
                                  Serial.print(kpd.key[i].kchar);
                                  Keyboard.press(kpd.key[i].kchar+92);
                                  msg = " PRESSED.";
                              break;
                                  case HOLD:
                                  Serial.print(kpd.key[i].kchar);
                                  msg = " HOLD.";
                              break;
                                  case RELEASED:
                                  Keyboard.release(kpd.key[i].kchar+92);
                                  msg = " RELEASED.";
                              break;
                                  case IDLE:
                                  Serial.print(kpd.key[i].kchar);
                                  msg = " IDLE.";
                              }
                              Serial.print(kpd.key[i].kchar);
                              Serial.print("Key ");
                              Serial.print(kpd.key[i].kchar);
                              Serial.println(msg);
                          }
                      }
                  }
              }  // End loop
                       
                       

then i just had to design some knobs, (took 4 tries to get the tolerances right), and it is all done:

thanks for reading if you did <3


back a page

┈ ren ​♡