PSX Analog in and N64 Controller

Started by kylejw, February 13, 2009, 06:43:31 PM

Previous topic - Next topic


Fwirt - that looks great. I noticed this post on Hackaday too. It was quite surprising since I've been frequenting the thread and was considering submitting it myself. :)

Anyway, I would love to see your code. Being a C newbie, I tried implementing the firmware for avr-gcc (linux...), but couldn't really figure it out. I bought an arduino to cheat and use that, but haven't got to it yet. I think I can make my attiny85 work for the project, which would be really awesome. I just don't have the know-how in the code area.. =\


The code is pretty short, so I'll just put it in a code block. I'll assume that you already have your toolchain setup to compile this... One quick caveat: I'm not entirely proud of this code as it's really touchy... I got my wires crossed so that the X axis was backwards and tried flipping signs (which should work if I'm doing my math correctly) but it made the AVR drop some steps and glitch out in other ways, so I just left it alone and flipped the wires. Also, make sure you set your fuses so that the AVR is running on the internal oscillator (at 8MHz) with no clock division, so that the timers and such work properly.

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 8000000UL
#include <util/delay.h>

#define ADC_MULT 10
#define ADC_SCALE 50
#define E_LENGTH 4
#define OCR_ENCODE 25
#define OCR_TURBO 252
const char encoder[] = {0,1,3,2};

short x_steps, y_steps = 0;
short last_x, last_y = 0;
char axis = 0;

/** ADC interrupt interprets voltages from joystick and converts them
   to number of steps the AVR needs to fake **/
ISR(ADC_vect) {
   unsigned short data = (ADC * ADC_MULT) / ADC_SCALE;
   if (axis) {
       y_steps += last_y - data;
       last_y = data;
       ADMUX = 0;
       axis = 0;
   else {
       x_steps += last_x - data;
       last_x = data;
       ADMUX = 1;
       axis = 1;

/** Timer interrupt updates the output to fake rotary encoding
   and decreases the number of steps left **/
ISR(TIM0_COMPA_vect) {
   // Update the step counter
   if (x_steps > 0)
       x_steps -= 1;
   else if (x_steps < 0)
       x_steps += 1;

   if (y_steps > 0)
       y_steps -= 1;
   else if (y_steps < 0)
       y_steps += 1;
   // Update the encoder
   PORTA = (PORTA & ~(0x3C)) | ((encoder[(last_x + x_steps) % E_LENGTH] << 2) | (encoder[(last_y + y_steps) % E_LENGTH] << 4));

int main(void) {
   char turbo = 0; // Keep track of the turbo state

   // Setup outputs
   DDRA = 0x7C; // Enable outputs on pins 2 - 6 of port A
   PORTA = (1 << 7); // Enable pull-up on button

   // Setup ADC
   ADCSRA |= (1 << ADPS2) | (1 << ADPS1); // Set prescaler to 64 (Which gives us 125KHz ADC clock)
   ADCSRA |= (1 << ADEN); // Enable ADC

   // Initialize x and y (so the controller doesn't shoot to the upper right corner...)
   ADMUX = 0; // X axis
   ADCSRA |= (1 << ADSC);
   loop_until_bit_is_set(ADCSRA, ADIF);
   last_x = (ADC * ADC_MULT) / ADC_SCALE;
   ADMUX = 1; // Y axis
   ADCSRA |= (1 << ADSC);
   loop_until_bit_is_set(ADCSRA, ADIF);
   last_y = (ADC * ADC_MULT) / ADC_SCALE;
   ADMUX = 0;
   sei(); // Enable global interrupts

   // Setup timer for encoder
   TCCR0A |= (1 << WGM01); // Enable CTC
   TCCR0B |= (1 << CS02); // Set prescale to 100 and start timer
   OCR0A = OCR_ENCODE; // Set compare for timer 0
   TIMSK0 |= (1 << OCIE0A); // Enable interrupt

   // Setup timer for turbo
   TCCR1B |= (1 << WGM12); // Set CTC
   TCCR1B |= (1 << CS12) | (1 << CS10); // Set clock prescale to 1024 (and start timer)
   OCR1A = OCR_TURBO; // Set compare for timer 1

   // Start automatic ADC update
   ADCSRA |= (1 << ADATE); // Enable automatic trigger
   ADCSRA |= (1 << ADIE); // Enable ADC interrupt
   ADCSRA |= (1 << ADSC); // Start conversion

   for (;;) {
       if (bit_is_set(PINA, 7) && turbo) {
           // Deactivate turbo
           TCCR1A &= ~(1 << COM1A0); // Deactivate timer output
           PORTA &= ~(1 << 6); // Deactivate pin
           turbo = 0;
       else if (bit_is_clear(PINA, 7) && !turbo){
           // Activate turbo
           TCCR1A |= (1 << COM1A0); // Activate timer output
           turbo = 1;
   return 0;

On a final note, I put some more thought into the turbo function and if you don't want to hold the button down, it would be a pretty trivial edit to have the button toggle turbo instead, but I'll leave that up to you guys.


I'm actually the one who submitted it to HaD. Saw this a while ago, and I plan to do it myself (plus adding extra buttons... was actually going to use the analog button too, but someone beat me to it).

Unfortunately I left my n64 back home when I moved out, so I have to buy an n64 before I can try this.

Few Qs about the quadrature encoding... Looking at (kyle's) code, it looks like the states cycle through, always only jumping forward or back one state. I am not sure exactly how this allows for high-sensitivity, since it doesn't distinguish between a big jump between two X or Y values, and a small one, since it seems both only move the state by one?  How is this handled? I'm guessing the sample rate is so high it doesn't really make a difference?


The thing about quadrature encoding is that it can't handle "jumps".  If you jumped from one position to another without going through the intermediate steps, the N64 controller would lose its place, and the software center of the joystick would move.  (Which was the largest problem I had to overcome when writing my firmware.)  You have to keep in mind that quadrature encoding is a relative positioning system, not an absolute one.  A "big jump" is actually made up of a bunch of small steps right after one another.  However, you have to slow down the rate at which the MCU feeds the quadrature values to the N64 controller, or it'll drop steps.  I originally just tried taking the voltage level at the time of the ADC interrupt, scaling it, modding it by 4 and using it as the address in the step array, but that made it screw up.  It wasn't until I looked at micro's Bascom AVR code that I saw how he was handling it, which is the method I switched to, more or less. (BTW, thanks a bunch for posting your firmware micro!)



I think I see what you're saying, but correct me if I'm wrong.  

Are you asking if for example the joystick is at position 45 and you do a quick jump to 60 do I only increment the state by one?
If so, notice that I'm actually looping through the state transitions until I've done (for this example) 15 jumps.  So this is the flow:

Read ADC -> Check if value is higher than current position -> Do a loop, incrementing the position counter (and change state) each iteration until the position counter equals the ADC value.
So using the 45/60 example:
Read 60 -> 60 is higher than 45 -> change state -> 60 is higher than 46 -> change state -> 60 is higher than 47 ..... and so on.

The code _used_ to work by doing an ADC read, then if the value is higher, jump by one and increment the current position (and change state), then repeat.  
In this case if you jumped from 45 to 60 then after the first iteration the N64 would think you were at 46, then the next time I read the ADC it's still at 60 and I'm at 46, so it would jump again and again and again until I reached 60.  At 8MHz this happened quite fast.  When I did the big rewrite I decided that the ADC was probably slow enough that I would get a small performance improvement from doing all the increments at once.

Hope that makes sense, I can try to clarify if it doesn't.

Edit:  Here is the code that accomplishes what I'm talking about:

/* Our desired value is different from the current position index, so move in the direction of the desired value! */
void makeVertEqual( long *current, long target ){
       if( (*current) == target ){
       while( ((*current) > target) && ((*current) > vertLimitLow) ){
       while( ((*current) < target) && ((*current) < vertLimitHigh) ){

... and looking at the code again I can see some obvious efficiency improvements I could make :p


That's exactly how the quadrature encoding works, it has to be done in sequence.   With a potentiometer you can just ask for the current value any time, but with quadrature you have to read every single movement, if you miss some you get skipping.

You guys may not remember the old days of serial mice, but if you moved those too quickly the cursor would suddenly change direction or zig-zag as the high-speed changes to the quadrature happened faster than the computer could sample them.  We used to bump our PS/2 sampling rate from 40 to 80 or even 200 samples per second to compensate.  That's why we have 'accelerators' for our mice, software that magnifies rapid changes even further so that a smooth, quick movement has a more drastic effect so we didn't have to move the mouse so quick and risk missing a reading.  (There are other benefits for this sort of mouse control too of course)


Ah kyle, that makes perfect sense. I haven't read your code for a while so I guess I missed it/forgot about it.

Lawrence, makes sense about the sequence thing, jumping too far wouldn't allow for the chip reading the encoding to have any bearing on which direction it moved.

Does anyone know the rate at which the controller samples the joy stick's state? By my understanding the make*Equal function should be limited to this rate, to completely eliminate the chance of skipping.


Grazfather, I don't know how you would go about finding the sampling rate of the controller... Correct me if I'm wrong, but I believe the signal would be internal to the controller IC, so there would be no way to measure it directly.  However, at the speeds you're working with, an educated guess would probably suffice unless you're planning on moving your fingers around at 500MPH. ;D What I did to eliminate skipping was put the update routine in a timer interrupt.  The interrupt updates the position by one step each time, so you can be sure that it won't update faster than the controller can handle and end up skipping.  I think the timer interval I used is around 1/1000 of a second, but you could probably crank it up and see how fast you could get before it skips.


The N64 doesn't read the encoder directly.  It polls the controller for data in the form of two 8-bit numbers (one for vertical, one horizontal), as well as a whole bunch of other bits for all the buttons.  The 8-bit numbers are where the theoretical limit of -127 to +128 counts come from (since 2^8 = 256).

I suspect that the encoder lines run to interrupts (or an interrupt tied to 4 ports?) on the N64 controller, so it isn't polled per se, rather every time I do an update the controller code jumps to update its value for the position.  If I were to update too fast, I'd probably hit in the middle of the last interrupt (while the controller is still updating its value), and unless it allows nested interrupts which I doubt, my next movement may be ignored.

So you're correct that the function should be limited.  However, I made the assumption (sue me) that the ADC conversion time would be long enough to prevent any issues.


Hm, I might look into it for curiosities sake. I don't really see the controller tying the four stick signals to interrupts, it would be too much trouble, and you'd want them all to fire the same interrupt, and having four interrupt lines seems excessive..

Regardless, I will probably rewrite a bit of Kyle's code (and probably have it set a 'target' y and x value) then have the timer interrupt continuously change until it reaches that value.

Biggest thing is that I want to add the ability to program in macros, and rapid fire.  I used to play SSB64 a TON and it would be cool (but very cheap) to have ness ddc'd d'airs coming out with one  press.  I also have a bunch of microswitched, and the controller seems to have a decent amount of spare room in there, so I think it would be a fun and feasible little project for me.  I originally started writing it all in ASM (on an 18f1320) but I think I'll cave and do it in C.


Quote from: TrekkiesUnite118 on September 20, 2010, 11:13:16 AM
I barely could get it open really. They didn't use screws the glued it shut. I had to use a scratch awl to break the seal and pop it open and I still couldn't get it to open very far (like half a centimeter) with out the fear of breaking it. From what I could tell it looked like the same kind of pot used in the 360 and Gamecube style controllers and it just came out with wires that connects directly into the N64 controller board.

Here are some pictures from the outside though:

If I can get it open better I'll take some internal pics.

I've just realized how damn small the octagonal gates are on these commercial stick replacements. So that's how the reduced the range, by restricting the stick travel mechanically:D


I tried to complete this mod in December but had a number of problems as I could not get my parallel port programmer to work :( I triple checked my wiring and tried all sorts of BIOS settings, but Bascom still would not recognise the chip. I then came across this thread saying there are some problems with the simple parallel port programmer design:-
In the end I gave up and ordered an Atmel AVR ISP MKII a few weeks ago.  I had to build a new programming circuit with a 6 pin ISP header and use the official software (AVR Studio) as Bascom still didnt work, but it programmed the ATiny24 first time :) Now I just need to get a GameCube analog stick to complete it, so if anyones still interested i'll post pics when im done.


As a side note, a while ago I began wondering if emulator authors took the N64 analog sticks 168 steps into account (I assumed they would have to for perfect compatibility) and in fact they do! If you load up Project64 with the N-Rage input plugin and select 'Configure Controller Plugin' then you can see that the default analog sticks range is set to 66%, and considering that most analog sticks have a resolution of 256 steps you can work out that 66% of 256 equals 168.96 steps - almost exactly what micro said was the upper limit.


I posted these N64 Controller patent links in the thread mentioned below, but thought they might also be useful here:-
N64 Analog Stick:-

In particular on page 44 of the first patent the process of resetting the N64's Analog Stick using L+R+START says that:
Quote from: Patent 5,963,196In order to correct the data from the analog joystick, a timer-interrupt routine shown in FIG. 31 is executed. The timer-interrupt routine is executed at every constant time period such as 1/30 seconds
Could that mean the analog stick values are updated 1/30 second? Or perhaps its only refering to the analog stick reset combination? I know there was some question about how often the values were updated earlier, and just thought their might be some more helpful info in the above patents.


Lastly I thought some people here might also be interested in this project by DarthCloud called Cube64-DX:-
Its an improved version of the original Cube64 adaptor:-

This allows you to use GameCube controllers on the N64 :) I had seen the project before but it lacked WaveBird support which was one of the main reasons I would want to use it. However thanks to DarthCloud it now supports the WaveBird as well! ;D

I think its a perfect compliment to this project since you can now either improve the original N64 controllers analog stick for the more 'authentic' experience, or alternatively you could build the Cube64-DX GameCube controller adaptor if you want wireless.

The code is currently being updated by DarthCloud as I pointed out micro's earlier discovery about the N64 analog stick having an upper limit of 168 steps  ;)


very interested! can't wait to see one of these working!


Quote from: micro on February 27, 2011, 05:29:11 AM
I've just realized how damn small the octagonal gates are on these commercial stick replacements. So that's how the reduced the range, by restricting the stick travel mechanically:D

I got myself a handfull of those. The octagonal gate don't restrict the movement actually. The stick is a lot thinner compared to a Gamecube or Wii stick. But they definitely do  have a problem with the range... it is WAY to sensitive to play games like golden eye etc.. BIG letdown :(  found this site researching how the hell i could make the stick less sensitive :P

Here is an inside pic btw. I forgot to take a photo of the underside, but there is an unmarked chip and a handfull of other components. Guess i have to open the controller again  ::)



Could most likely reduce sensitivity by placing resistors in two places:

1. Between ground and where the joystick X and Y potentiometers connect to ground
2. Between power and where the joystick X and Y potentiometers connect to power

It also should be possible to adjust X sensitivity separately from Y sensitivity using 4 resistors.


Here are a few better pix of the china stick internals if anyone is interested =)


Hi everybody, im was so happy when i read about this mod, :D
The problems are:
1.- I couldn´t find the Atmel Micro or Tiny Micro...
2.- And the pic 16f684... the same story, i couldn´t find it

But i found the 16f616 it´s similar to 684, but it only has twice capacity than the 884, so i was thinking how to change the program to the 616

Is there any way to change it?
I don´t know a lot of pic, just took a short course...




Quote from: micro on June 19, 2011, 06:40:12 PM
Darksun, where are you from, if I may ask? :)

I´m from Mexico :)

Quote from: kylejw on June 22, 2011, 11:54:15 AM
It already is for 16f616.

Sorry i mesed up! :P

i found only the pic16f684 u.u



So I bought such a professionally-made replacement stick for 8$ as shown above because I was curious how good the stick really was. After some tests I must say this stick is absolute shit!

1.) The range of the stick is slightly higher (196 176 steps per axis) than it should be (160 to 170 steps). But that's not the real problem...

2.) Instead increasing/decreasing the +/- 88 steps one by one the program on the IC is skipping steps! When in neutral position, the x- and y-axis got the value 0. When slowly moving the stick to the right, that x-axis' value should increase by one: 0,1,2,3,4,5,...,87,88.
But this shitty stick is skipping most of the steps. At first it increases the value by 2, afterwards the value is increased by 4!
0,2,4,6,8,10,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88.  W T F ?
When moving the stick to the left it get's even a little bit weirder:

That means the real resolution is only about 50 or 52 steps / axis; and these steps are whether 2 or 4 steps worth.

3.) That replacement stick is also SLOW when making those steps. Here's a pic showing the XA and XB lines.
At the beginning, the x-axis voltage was about 2V and has been pulled to GND in an instant. Notice the long time the stick stays in neutral position.
Also notice how much freaking time it takes for the stick to make it's way from neutral position to all the way left.
You can also see the steps are made very irregularly. Most of the time nothing happens, then 2 or 4 steps are made at once  in a very short period of time.

For comparison here's a pic showing how the program for the Atmel microcontroller is handling the steps:
You can see it's much, much faster and much more regular, too.

I really don't know what the designers of the stick were thinking. Did they even test it?
With that replacement stick some special moves in Smash Brother can't be executed properly. The spin attack in Zelda OOT doesn't work, too.

If you remove all the parts on the pcb except for the potentiometer you can install an Atmel microcontroller. That will solve the problems described.

Here's a little video showing the stick skipping most of the steps:
problems with N64 replacement stick



hello fellas, I'm really excited about this mod but I have some dubts and I think you guys can handle them.
The thing is that in this country I can't get the ATtiny24 or the ATmega8 for the mod, but I can get the Attiny2313, ATTINY45-20PU
and ATTINY13-20PU, and I don't know if the code that micro wrote for the ATtiny24 works with that one and I only have
to replace one line of code or it's not possible to do the mode with those microcontrolers.
thankz for the advices!!


No, it's not possible with these microcontrollers because they got no ADC's. Which country are you from?
Maybe you can get some free samples from Atmel directly...


Well, I'm from Colombia, I have been checking the technical information of the microcontrollers, and I think the ATTINY45-20PU has 4 chanel to ADC, check this link:


You're right BUT you'd need to use the reset pin as a normal input/output pin. This means no further (re-)programming of the microcontroller is possible.
In the past I tried to port the program to the small attiny12 which also got only 8 pins. The program worked, but only after resetting/recalibrating the N64 controller (L+R+Start). Because I couldn't reflash the microcontroller without the reset pin I couldn't really work on it... So I'd stick to the Attiny24.


So a mistake is equal to death, ok but as I said before I can't get the ATtiny24, maybe another microcontroller not from Atmel would work? if yes, which one?
thanks anyway for the help


thanks micro for the information, I've already ordered the 10 microcontrollers that you talk before, because it was imposible to get those in Colombia
I think that in a month I will be doing yhe mod so I'll post the results if possitive or asking for more help if needed
the good thing is that there are 10 posibilities of doing it right



Just finished my controller using the atmega8. I took a solid green controller, and put the jungle green back on it. Also added some blue LEDs just for fun. Not sure if I like the blue, but I think it's sweet. Thanks a ton for all the info here.


That's amazing! Really great job!  8)


Quote from: wwwyzzerdd on July 23, 2012, 06:17:39 PM
Just finished my controller using the atmega8.

If I may ask, which AtMega8 did you use for the analog stick?  I have two AtMega8-16PUs laying around, though I'm doubtful of whether they'll work or not as only the AtMega8L chips would work at 3.3V.  Did anyone get the AtMega8-16PU working in the Nintendo 64 controller?

Another thing: is the AtMega8 code compatible with the AtMega328?  I know that chip can work no problem at 3.3V, but I'm not entirely sure if the code for the AtMega8 would work on the AtMega328.


Usually ATMega8-16P MCUs work at 3.3 V without any problems. Just give it a try!


Quote from: micro on October 07, 2012, 05:52:01 PM
Usually ATMega8-16P MCUs work at 3.3 V without any problems. Just give it a try!

Well, I did give it a try, but I guess the chip was either programmed wrong or doesn't like working at 3.3V because all I got was very sluggish response from the analog stick, and when I moved the analog stick just a little bit, if there was a cursor on the screen (say the hand in the character select screen in Super Smash Bros.), it would drift all the way to one side of the screen and never return to normal.  Now, I need to try a Serial programmer (I do have a fairly recent Lenovo PC that does have a Serial port) as on my Parallel programmer, one of my AtMega8s goes unrecognized and the second only gets random data written to it from 0 up to about 3F.

As a side note, I did get one of those pre-made GameCube-style replacement analog sticks to give it a try, and I honestly don't really understand all the b****ing and moaning about the stick.  Okay, it does have some delay issues, but on my particular stick, they're not severe at all, and the stick itself, while being more sensitive than the original Nintendo analog stick (as expected), works a lot a better than the analog sticks on my Tomee, RetroBit and TTX Tech Nintendo 64 controllers.  The Tomee stick is poorly calibrated (MASSIVE deadzone and highly oversensitive), the RetroBit controller has a huge deadzone and the TTX Tech controller is INSANELY oversensitive (all controllers use POT-based analog sticks, by the way), but the analog stick on this controller is not insanely oversensitive nor does it have a massive deadzone.  I may still throw in one of my AtMega8s into this analog stick as Super Smash Bros. is a bit more difficult to play when using this analog stick, and this is a game myself and my buddies play A LOT on the Nintendo 64.


Success!  Turns out the problem was due to the power source I was using while programming the AtMega8 being inadequate.

However, I do still have some issues with the stick itself.  The sensitivity (I'm using a GameCube analog stick) is very low, so low that my controllers with tight analog sticks have a wider range of motion than the controller in which I installed the GameCube analog stick.  Is there some way of making the analog stick more sensitive other than using bigger POTs, like something I can tweak in the code?


Hmm... To say the sensitivity is very low is not right imo. The range is +-80 steps and that is the same range an original stick in good condition will give you. As you've said in your previous post other sticks are highly oversensitive.
If you flash the microcotroller with my program you should get a stick that is not too sensitive. At the same time you should be able to get a full range in all games, for example running at full speed in Mario 64.

One thing about the pre-made GC-style replacement sticks: I love how easy the installation is and I like its look and feel. But imo they really suck. Every person has its own preferences and some may be annoyed by its lag and high sensitivity more than other persons. But when that lag and sensitivity are the causing some moves to be not executeable any longer (quick whirlwind attack in Zelda OOT), then that stick just sucks...


hi ace. I'm glad I found your post. I actually used the atmega8-16pu myself and it works great..  not that it matters now that you've figured it out, lol.

I actually came back to scan this thread for clues as to why the joystick I just "finished" doesn't work right.  I'm also using super smash bros to test it and it behaves exactly like you said yours did.

I won't go into details, but my power source during programming was..    less than adequate, lol..  which leads me to believe that I may have to reprogram the chip. I did NOT want to do that, but oh well.

The point of this post is really to thank you for posting back how you fixed it as I'm almost sure it will fix my problem, I hope, and although I couldn't help you in time, you saved me from a bit of painful searching.


So what's exactly your preoblem, wwwyzzerdd?

Do you also think the sensitivity is too low?
Or dou you also have the weird problem with the cursor drifting to one side?