Jump to content

Rotary controller


Recommended Posts


I'd like to add support for rotary controller in the Removers library.

However, right now, I have not a clear understanding on how to read data from joypad, interpret them, and report them.

Is there some reference documentation about that? And/or even better some available code to do that?

Thank you,


  • Like 1
Link to comment
Share on other sites

Rotary basics

The way the rotary works is pretty simple: it replaces the left and right directions of the D-pad. Instead of encoding the left/right movement directly like a normal pad, it has four different states: left direction on, both directions on, right direction on, and both directions off. Each time you rotate the rotary by one step, it goes from one state to either the next one or the previous one, depending on the rotation direction:


(clockwise and counter-clockwise may be swapped on this diagram, I'd have to double-check)


Another thing that's different from the standard pad is the poll rate: to avoid missing states, the rotary must be read more frequently; once per VBL is not enough. I read the rotary 2000 times per second in my code, but that's because the same code can also handle a mouse; for the rotary itself, you don't need such a high frequency. I don't have a lower bound, but maybe @LinkoVitch can tell us what frequency he's using in U235 SE (I believe it's lower).



Supporting the rotary in practice

You need one temporary variable (JoyState) and two permanent ones (PrevJoyState, RotationCounter). RotationCounter is signed and should be initialized to zero.



1) Select joypad row 0 (Write $817E to JOYSTICK).

2) Read from JOYSTICK and store the result in JoyState.

3) Mask out everything except the Left and Right bits.

4) Compare JoyState to PrevJoyState:

    • If they are identical, the rotary didn't move: there's nothing to do.

    • If JoyState is one state beyond PrevJoyState, the rotary moved one step in the forward direction: add 1 to RotationCounter.

    • If JoyState is one state behind PrevJoyState, the rotary moved one step in the backwards direction: subtract 1 from RotationCounter.

    • If none of those conditions are true, then you missed at least one step (this shouldn't happen if the frequency is high enough). Don't do anything.

5) Copy JoyState to PrevJoyState.


Once per frame:

1) To avoid race conditions, pause the periodic reading (e.g. if you're using a timer interrupt, disable it).

2) Copy RotationCounter to a temporary variable, then set it to zero.

3) Use the usual code to read the states of buttons from the pad. Don't forget to mask out the left and right directions.
4) Restart the periodic reading.



• Allow the player to customize the sensitivity of the rotary, to account for different number of steps/rotation and personal preference. You can do that by using fixed-point arithmetic and multiplying RotationCounter by a constant (bigger constant -> more sensitivity).

• Auto-detect the rotary by detecting the "left + right directions" condition (it will happen naturally as soon as the player rotates the rotary a bit). To make sure it's not a glitch, only enable rotary mode if the condition persists for a few frames.




• You can skip step 1 of the rotary reading if you leave joypad row 0 selected all the time. (Write $817E to JOYSTICK after step 3 of the "once per frame" code.)


• Step 4 can be done efficiently with a few binary operations (see "Code" below).

• You don't actually need to read the rotary at a precise frequency ; as long as the delay between two consecutive reads is always shorter than the limit, it will work fine.


Instead of using a timer interrupt, you can use the DSP audio interrupt ; this avoids the risk of a rotary read delaying the audio processing and introducing audio glitches:

    1) when you get a DSP audio interrupt, process the audio first.

    2) increment a counter. If the counter is below (audio_samplerate / rotary_read_frequency), return from the interrupt.

    3) reset the counter to zero.

    4) set a flag to signal that your main DSP code should read the rotary. You can also try reading the rotary directly from the interrupt, but it may not be fast enough, depending on the audio samplerate you're using.

    5) return from the interrupt.

Note that you can't disable the audio interrupt to pause the rotary reading code, as this would cause audio glitches. Use a global disable variable instead.


• If you can, use the DSP to read the buttons as well as the rotary. It is faster than the 68000, so there is less risk of missing rotary steps.




Unfortunately, the code I use was developed to handle a mouse, and rotary support was hacked later. This means the code is needlessly complicated if you only need the rotary, and harder to integrate into something else. Also, I lost my Jaguar's video cable, so I can't test the refactored code until I find it :D


In the meantime, here's some untested but should-be-working DSP code for the periodic part (this assumes row 0 is already selected):   

movei   #JOYSTICK, Tmp
loadw   (Tmp), JoyState
move    PrevJoyState, Tmp
shrq    #1, PrevJoyState
shlq    #1, Tmp
xor     JoyState, PrevJoyState
xor     JoyState, Tmp
shlq    #(31 - 10), PrevJoyState
shlq    #(31 - 11), Tmp   
shrq    #31, PrevJoyState
shrq    #31, Tmp  
sub     PrevJoyState, RotationCounter
add     Tmp, RotationCounter
move    JoyState, PrevJoyState



Don't hesitate to ask if you have questions or if something doesn't make sense :)


Edited by Zerosquare
  • Like 3
Link to comment
Share on other sites

LOL, was about to hit submit but @Zerosquare beat me to it :D.


FWIW what I was going to post:

Basically just use Zerosquare's code from [url=http://www.jagware.org/index.php?/topic/822-joypads-teamtaps-and-other-controllers/&do=findComment&comment=11629]here[/url] and you're done!


As a bonus: unless someone uses a modified joypad, if you read the direction joypad bits and detect both left+right pressed at the same time you can assume that a rotary is plugged in. So: auto detection! (That's what I did i n Downfall Falcon)


Oh and since sampling frequencies were mentioned: on the Falcon 200Hz sampling rate was more than enough with my rotary but YMMV.

  • Like 2
Link to comment
Share on other sites

I have been working on doing this in the Removers Library, strictly in C code, using Zerosquare's code linked above as a baseline for a while now.  I can get things working pretty well, but I get a weird scrolling pixels visual glitch at the top of the screen though (only shows up on real hardware).  I think that this has something to do with interrupts, but my knowledge ends there.  After hours of blindly trying things with what is available in the Removers library, I haven't been able to get much further than this.  Could be a problem with my simple vector library as well, but it seems like I got similar results after stripping the vector stuff out (Its been a few months since I last tackled this).


I attached a sample program of the code that I am working with (a simple pong clone), I hope that this might help with getting some streamlined support for rotary in the Remover's Library.  I definitely want to get this working in Odd-Ball before that game gets released.


@SebRmv I am ready to go in regards to a build environment for the removers library (thanks to the great people here getting things building with rmac/rln), if you need another person to test things.


  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...