here's a DIY spinner i made for arkanoid, etc. this was all code i grabbed from the web and a case from thingiverse so i can take literally no credit for any of it.
spinner outside.jpg (505.96 KiB) Viewed 8804 times
spinner inside.jpg (426.38 KiB) Viewed 8804 times
case was from thingiverse and i just mirrored it over one axis when printed so it could be held in the left hand and use my thumb on the fire button.
because the case is so small you have to get low profile arcade buttons, these were from focus attack
the rotary encoder was from amazon and was the most costly part. seems to be out of stock but i'm sure something similar is available. this one uses hall effect sensors and spins smoothly with no detentes.
spinner knob is a generic aluminum knob. i wanted bigger but settled on this one
micro controller can be any Arduino that utilizes the 32u4 processor as you NEED the native USB functionality. i used an adafruit itsy-bitsy i had already that was small enough to fit inside the case.
last was a spare USB A cable. this was an old apple iphone lightning cable that was busted. just snipped off the apple end and robert's your mothers’ brother.
code was from here. i didn't write it, just used and adapted it for my purposes. it details how to attach the encoder and buttons (i think its setup for 6 buttons if you want but obviously the case is too small for that many so i limited it down to just 2 for fire and player1/coin).
Re: DIY Spinner Controller
Posted: Sun Oct 23, 2022 9:00 pm
by ntt
Great project, thanks for sharing!
Re: DIY Spinner Controller
Posted: Wed Oct 26, 2022 1:50 am
by sonik
Also check out Sorg's paddle/spinner code. It's interesting as it will work on mister as paddle and spinner.
Re: DIY Spinner Controller
Posted: Tue Nov 01, 2022 2:37 pm
by dennis808
I built exactly the same spinner, but have a really hard time getting it properly configured in different MiSTer cores/games, how much code did you change? Would you be willing to share the code?
Re: DIY Spinner Controller
Posted: Sun Nov 06, 2022 6:26 pm
by hellbent
here is the code as i used it. it was tweaked for my button inputs.
/* Arcade Spinner v0.7
* Copyright 2018 Joe W (jmtw000 a/t gmail.com)
* Craig B - Updated code for mouse movement modes(DROP, ACCM) and case statement for Button port bit validation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Mouse.h"
#include "Joystick.h"
#define pinA 2 //The pins that the rotary encoder's A and B terminals are connected to.
#define pinB 3
#define maxBut 2 //The number of buttons you are using up to 10.
int lastButtonState[maxBut] = {1,1};
//The previous state of the AB pins
volatile int previousReading = 0;
//Keeps track of how much the encoder has been moved
volatile int rotPosition = 0;
volatile int rotMulti = 0;
//Create a Joystick object.
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
maxBut, 0, // Button Count, Hat Switch Count
true, true, false, // X, Y, but no Z Axis. We need at least two axes even though they're not used.
false, false, false, // No Rx, Ry, or Rz
false, false, // No rudder or throttle
false, false, false); // No accelerator, brake, or steering;
void setup() {
//Serial.begin (9600);
//pinMode(13, OUTPUT); //LED to see if mouse interrupt is working or not.
PORTD = 0b00000011; //Digital pins D2, D3, D4, D6, and D12.
PORTB = 0b11100000; //Digital pins D11, D10, D9 for buttons
/* pin mapping for adafruit feather 32u4 to PORTX
PORT D available HIGH 0b11001111 (pins D6, D12, X, D4, D1, D0, D2, D3)
D6 06 PD7
D12 12 PD6
xx PD5
D4 25 PD4 - SDcard no don't touch
D1 01 PD3
D0 00 PD2
D2 02 PD1 - interrupt 1
D3 03 PD0 - interrupt 0
PORT B available HIGH (0b11101110) ( pins D11, D10, D9, X, D8, MISO, MOSI, SCK, X)
D11 11 PB7
D10 10 PB6
D9 09 PB5
D8 08 PB4 - LED onboard
MISO 14 PB3
MOSI 16 PB2
SCK 15 PB1
xx PB0 - not useable
*/
//Set up the interrupt handler for the encoder's A and B terminals on digital pins 2 and 3 respectively. Both interrupts use the same handler.
attachInterrupt(digitalPinToInterrupt(pinA), pinChange, CHANGE);
attachInterrupt(digitalPinToInterrupt(pinB), pinChange, CHANGE);
//Start the mouse
Mouse.begin();
//Start the joystick
Joystick.begin();
//Center the X and Y axes on the joystick
Joystick.setXAxis(511);
Joystick.setYAxis(511);
}
//Interrupt handler
void pinChange() {
//Set the currentReading variable to the current state of encoder terminals A and B which are conveniently located in bits 0 and 1 (digital pins 2 and 3) of PIND
//This will give us a nice binary number, eg. 0b00000011, representing the current state of the two terminals.
//You could do int currentReading = (digitalRead(pinA) << 1) | digitalRead(pinB); to get the same thing, but it would be much slower.
int currentReading = PIND & 0b00000011;
//Take the nice binary number we got last time there was an interrupt and shift it to the left by 2 then OR it with the current reading.
//This will give us a nice binary number, eg. 0b00001100, representing the former and current state of the two encoder terminals.
int combinedReading = (previousReading << 2) | currentReading;
//Now that we know the previous and current state of the two terminals we can determine which direction the rotary encoder is turning.
//Going to the right
if(combinedReading == 0b0010 ||
combinedReading == 0b1011 ||
combinedReading == 0b1101 ||
combinedReading == 0b0100) {
rotPosition--; //update the position of the encoder
}
//Going to the left
if(combinedReading == 0b0001 ||
combinedReading == 0b0111 ||
combinedReading == 0b1110 ||
combinedReading == 0b1000) {
rotPosition++; //update the position of the encoder
}
//Serial.println("interrupt!");
//Save the previous state of the A and B terminals for next time
previousReading = currentReading;
}
void loop(){
//If the encoder has moved 1 or more transitions move the mouse in the appropriate direction
//and update the rotPosition variable to reflect that we have moved the mouse. The mouse will move 1/2
//the number of pixels of the value currently in the rotPosition variable. We are using 1/2 (rotPosition>>1) because the total number
//of transitions(positions) on our encoder is 2400 which is way too high. 1200 positions is more than enough.
if(rotPosition >= 1 || rotPosition <= -1) {
rotMulti = rotPosition>> 1; //copy rotPosition/2 to a temporary variable in case there's an interrupt while we're moving the mouse
Mouse.move(rotMulti,0,0);
rotPosition -= (rotMulti<< 1); //adjust rotPosition to account for mouse movement
}
int currentButtonState;
int button = 0;
do {
switch ( button ) {
case 0: //on digital pin 11, Arcade Button 0
currentButtonState = (PINB & 0b10000000) >> 7; //logical AND the 8-bit pin reading with a mask to isolate the specific bit we're interested in and then shift it to the end of the byte
break;
case 1: //on digital pin 10, Arcade Button 1
currentButtonState = (PINB & 0b01000000) >> 6;
break;
default: //should never happen
currentButtonState = 0b00000000;
break;
}
//If the current state of the pin for each button is different than last time, update the joystick button state
if(currentButtonState != lastButtonState[button])
Joystick.setButton(button, !currentButtonState);
//Save the last button state for each button for next time
lastButtonState[button] = currentButtonState;
++button;
} while (button < maxBut);
}
Re: DIY Spinner Controller
Posted: Mon Nov 07, 2022 12:27 am
by aberu
I printed one of those cases out of Wood-filled PLA (https://www.amazon.com/dp/B0833X5NKB) awhile ago, but never put it all together. Good to see someone else finished one.
Re: DIY Spinner Controller
Posted: Tue Dec 20, 2022 10:03 am
by CraigB-spinner
Hellbent,
Did you look at the second name in the code list?
I spawned a github page at https://craigb-spinner.github.io/ for my Spinbox; hey no 3D printer.
Never thought about updating the code to support MiSTer FPGA as I was using a Pie and MAME.
Well the insanity arrived - in-MCU SpinSpeed adjustment without a '/' function. I didn't like the 300 to 500 clock cycles overhead.
I did it old school RISC process, yah division is really subtraction. 5-6 hours debugging a logic bug, yah forgot to do a step. One foot infront of the other, oops trip, splat...
The code should be uploaded to my github with adjustable spin rate from the device - loss of two digital pins sacrifice.
My understanding not all cores can handle the faster pulse rate.
Is that so?
Craig B
Re: DIY Spinner Controller
Posted: Fri Dec 30, 2022 9:58 pm
by hellbent
i've only ever used it with arkanoid!
the sketch works great for me so kudos to your work on it with jmtw000
Re: DIY Spinner Controller
Posted: Thu Mar 02, 2023 9:38 am
by Missus
i just built one too, works great.
thanks for the writeup and code folks!
Re: DIY Spinner Controller
Posted: Wed Mar 08, 2023 3:23 pm
by MrChatouille
Hello,
I built one too, and it works very well with arkanoid.
However, I can't get it to work with some of the Jotego cores, for example puzzle loop 2 and forgotten world.
have you tried these games?
I built one too, and it works very well with arkanoid.
However, I can't get it to work with some of the Jotego cores, for example puzzle loop 2 and forgotten world.
have you tried these games?
I'd like to give you a little follow-up on this topic.
After much effort and trial and error, I finally got my version to work with more games (yeeeh)
This looks very nice. I am wondering about the "feel" of this solution. One of the popular spinners out there has an additional weight to add inertia to the encoder. Another (if I remember correctly) uses a piece of felt to introduce some drag. Is there a spinner out there that you could compare yours to, or can you describe its feel?
Also, would this work in Mame as well?
Re: DIY Spinner Controller
Posted: Wed Apr 05, 2023 3:44 pm
by MrChatouille
It will be difficult for me to describe the feel of the spinner but I will try to do my best.
There is no friction with the encoder since it uses a magnet to detect the rotation, with a simple flick it is easy to make it do several turns.
As far as I know, there was no weight on the original Arkanoid spinner but a mechanism using plastic gears, and I guess if the gears were not sufficiently greased, that it added a little "weight" to the rotation.
With a lot of modification, it may be possible to add a little friction and resistance, but I wouldn't know if that's a good or bad thing to do.
Another option would be to use a heavier knob, the one I'm using is plastic, but probably something in aluminum or steel would be better because it's heavier.
So far, not many people have tested the controller, but most of them found the feel excellent but a bit too sensitive (I added an option to change the sensitivity last week based on this feedback).
It will be difficult for me to describe the feel of the spinner but I will try to do my best.
There is no friction with the encoder since it uses a magnet to detect the rotation, with a simple flick it is easy to make it do several turns.
As far as I know, there was no weight on the original Arkanoid spinner but a mechanism using plastic gears, and I guess if the gears were not sufficiently greased, that it added a little "weight" to the rotation.
With a lot of modification, it may be possible to add a little friction and resistance, but I wouldn't know if that's a good or bad thing to do.
Another option would be to use a heavier knob, the one I'm using is plastic, but probably something in aluminum or steel would be better because it's heavier.
So far, not many people have tested the controller, but most of them found the feel excellent but a bit too sensitive (I added an option to change the sensitivity last week based on this feedback).
I must thank you for this great project, i love the small footprint and the multiple modes
We just need someone to run a few off for users as spinners especially affordable ones are very thin on the ground for MiSTer.
Thanks a lot for sharing the code with us, i'll try to use it on my 3 buttons spinner.
Re: DIY Spinner Controller
Posted: Mon May 01, 2023 12:03 pm
by capitaineflam25
I'm using MrChatouille code and it is working OK when in mr.Spinner mode.
I can also see that the mouse mode is OK (testing with Utility/Gamepad Input Test)
But unfortunately, in "Normal mode" or in "Paddle Emulation" mode, i have no input detection in MisterFPGA (if i plug it to a Windows computer, i can see the buttons and the changes on the paddle when testing with joy.cpl)
Any idea of what i am doing wrong ?
Re: DIY Spinner Controller
Posted: Mon May 01, 2023 12:08 pm
by MrChatouille
Unfortunately joy.cpl doesn't help much with this kind of devices.
I suggest you enable debug mode in the source code and then open the serial monitor to see if the buttons and spinner do anything
Re: DIY Spinner Controller
Posted: Mon May 01, 2023 12:39 pm
by capitaineflam25
Thanks for your answer
joy.cpl was a first way to verify that everything works in default and paddle mode, and it is moving as expected (on a Windows computer )
But when plugged to mister in one of those 2 modes, i don't have any button or spinner input.
Do you confirm that i don't need any special configuration in mister.ini ?
Do you confirm that i should see something in any of the 4 modes in Mister Input Tester ? (The picture illustrate it when started in mouse mode)
Re: DIY Spinner Controller
Posted: Mon May 01, 2023 12:56 pm
by MrChatouille
In default mode you should have A B Select Start and the Spinner
mrspinner.png (181.05 KiB) Viewed 6374 times
Nothing to add to mister.ini but you must add the device in mister (system settings then Define joystick buttons)
Re: DIY Spinner Controller
Posted: Mon May 01, 2023 2:18 pm
by capitaineflam25
Thanks a lot, it works after doing the joystick mapping in the main screen !
I didn't do the mapping because it was asking first for the "Right" button and i didn't know what to do with the spinner...
I finally pressed any button to select the spinner, then press F12 to clear the assignement and then press SPACE for any irrelevant button.
Was it the simplest way to do the mapping ?
Re: DIY Spinner Controller
Posted: Tue May 02, 2023 7:08 am
by capitaineflam25
By the way, you can add Pong to your working list (using Paddle emulation)
A long time ago i was able to play Pop'n Bounce NeoGeo with a mouse, by setting paddle control with the dipswitch, but my first test with my spinner was a fail.
Anyone manage to get it to work?
Re: DIY Spinner Controller
Posted: Tue May 02, 2023 8:51 am
by MrChatouille
For pop'n bounce, enter the dips settings, then soft dips, then pop'n bounce and enable paddle.
You can then play using the default spinner mode.
/* Arcade Spinner v0.7
* Copyright 2018 Joe W (jmtw000 a/t gmail.com)
* Craig B - Updated code for mouse movement modes(DROP, ACCM) and case statement for Button port bit validation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Mouse.h"
#include "Joystick.h"
#define pinA 2 //The pins that the rotary encoder's A and B terminals are connected to.
#define pinB 3
#define maxBut 2 //The number of buttons you are using up to 10.
int lastButtonState[maxBut] = {1,1};
//The previous state of the AB pins
volatile int previousReading = 0;
//Keeps track of how much the encoder has been moved
volatile int rotPosition = 0;
volatile int rotMulti = 0;
//Create a Joystick object.
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
maxBut, 0, // Button Count, Hat Switch Count
true, true, false, // X, Y, but no Z Axis. We need at least two axes even though they're not used.
false, false, false, // No Rx, Ry, or Rz
false, false, // No rudder or throttle
false, false, false); // No accelerator, brake, or steering;
void setup() {
//Serial.begin (9600);
//pinMode(13, OUTPUT); //LED to see if mouse interrupt is working or not.
PORTD = 0b00000011; //Digital pins D2, D3, D4, D6, and D12.
PORTB = 0b11100000; //Digital pins D11, D10, D9 for buttons
/* pin mapping for adafruit feather 32u4 to PORTX
PORT D available HIGH 0b11001111 (pins D6, D12, X, D4, D1, D0, D2, D3)
D6 06 PD7
D12 12 PD6
xx PD5
D4 25 PD4 - SDcard no don't touch
D1 01 PD3
D0 00 PD2
D2 02 PD1 - interrupt 1
D3 03 PD0 - interrupt 0
PORT B available HIGH (0b11101110) ( pins D11, D10, D9, X, D8, MISO, MOSI, SCK, X)
D11 11 PB7
D10 10 PB6
D9 09 PB5
D8 08 PB4 - LED onboard
MISO 14 PB3
MOSI 16 PB2
SCK 15 PB1
xx PB0 - not useable
*/
//Set up the interrupt handler for the encoder's A and B terminals on digital pins 2 and 3 respectively. Both interrupts use the same handler.
attachInterrupt(digitalPinToInterrupt(pinA), pinChange, CHANGE);
attachInterrupt(digitalPinToInterrupt(pinB), pinChange, CHANGE);
//Start the mouse
Mouse.begin();
//Start the joystick
Joystick.begin();
//Center the X and Y axes on the joystick
Joystick.setXAxis(511);
Joystick.setYAxis(511);
}
//Interrupt handler
void pinChange() {
//Set the currentReading variable to the current state of encoder terminals A and B which are conveniently located in bits 0 and 1 (digital pins 2 and 3) of PIND
//This will give us a nice binary number, eg. 0b00000011, representing the current state of the two terminals.
//You could do int currentReading = (digitalRead(pinA) << 1) | digitalRead(pinB); to get the same thing, but it would be much slower.
int currentReading = PIND & 0b00000011;
//Take the nice binary number we got last time there was an interrupt and shift it to the left by 2 then OR it with the current reading.
//This will give us a nice binary number, eg. 0b00001100, representing the former and current state of the two encoder terminals.
int combinedReading = (previousReading << 2) | currentReading;
//Now that we know the previous and current state of the two terminals we can determine which direction the rotary encoder is turning.
//Going to the right
if(combinedReading == 0b0010 ||
combinedReading == 0b1011 ||
combinedReading == 0b1101 ||
combinedReading == 0b0100) {
rotPosition--; //update the position of the encoder
}
//Going to the left
if(combinedReading == 0b0001 ||
combinedReading == 0b0111 ||
combinedReading == 0b1110 ||
combinedReading == 0b1000) {
rotPosition++; //update the position of the encoder
}
//Serial.println("interrupt!");
//Save the previous state of the A and B terminals for next time
previousReading = currentReading;
}
void loop(){
//If the encoder has moved 1 or more transitions move the mouse in the appropriate direction
//and update the rotPosition variable to reflect that we have moved the mouse. The mouse will move 1/2
//the number of pixels of the value currently in the rotPosition variable. We are using 1/2 (rotPosition>>1) because the total number
//of transitions(positions) on our encoder is 2400 which is way too high. 1200 positions is more than enough.
if(rotPosition >= 1 || rotPosition <= -1) {
rotMulti = rotPosition>> 1; //copy rotPosition/2 to a temporary variable in case there's an interrupt while we're moving the mouse
Mouse.move(rotMulti,0,0);
rotPosition -= (rotMulti<< 1); //adjust rotPosition to account for mouse movement
}
int currentButtonState;
int button = 0;
do {
switch ( button ) {
case 0: //on digital pin 11, Arcade Button 0
currentButtonState = (PINB & 0b10000000) >> 7; //logical AND the 8-bit pin reading with a mask to isolate the specific bit we're interested in and then shift it to the end of the byte
break;
case 1: //on digital pin 10, Arcade Button 1
currentButtonState = (PINB & 0b01000000) >> 6;
break;
default: //should never happen
currentButtonState = 0b00000000;
break;
}
//If the current state of the pin for each button is different than last time, update the joystick button state
if(currentButtonState != lastButtonState[button])
Joystick.setButton(button, !currentButtonState);
//Save the last button state for each button for next time
lastButtonState[button] = currentButtonState;
++button;
} while (button < maxBut);
}
Is this for mister.ini ? I have a spinner but i cant get it to register on the mister.