brownan / Gamecube-N64-Controller

Connect a Gamecube controller to a Nintendo 64 using only an Arduino and a single resistor
124 stars 25 forks source link

Apparently someone modified it to use two #2

Open o-jasper opened 12 years ago

o-jasper commented 12 years ago

Nice project! I don't know if you already noticed it, but he is the link:

http://www.instructables.com/id/nintendo-64-arduino-2-controllers/

(I am aware that you probably do it in your spare time so dont feel obliged to do anything.)

NicoHood commented 10 years ago

Hi jasper, Hi Andrew! I made a new library that is very easy to use. Check this out and tell me what you think. (contact via blog) https://github.com/NicoHood/Nintendo

brownan commented 10 years ago

Awesome! I was just thinking about revisiting the code, cleaning it up, and making a nice library. I wanted to get one of the new Arduino Leonardo for the usb functionality to have it act as a gamepad for computer games and emulated games. I see you beat me to it =)

Thanks for letting me know

On Tue, Jul 1, 2014 at 4:34 PM, NicoHood notifications@github.com wrote:

Hi jasper, Hi Andrew! I made a new library that is very easy to use. Check this out and tell me what you think. (contact via blog) https://github.com/NicoHood/Nintendo

— Reply to this email directly or view it on GitHub https://github.com/brownan/Gamecube-N64-Controller/issues/2#issuecomment-47706533 .

o-jasper commented 10 years ago

Cool! No time to really try it right now.

Do notice some of the list items it says 'todo', is that just about the IDE versions not having the right timing as mentioned above? That issue seems to be a little minor to put a todo after it..

NicoHood commented 10 years ago

i just wrote functions for accessing a gc controller (not the console). i dont have a n64 controller, so i somehow have to get one. but There shoulnd be so much difference.

The sending part is what i will focus the next days. Problem with that is, that the gamecube sends very inaccurate pulses. about 3,75uS and 1,25 uS pulses instead of 1 and 3. So i need to rewrite the functions for that too. But its not completely new, i will finish this soon.

o-jasper commented 10 years ago

Very cool already, no pressure :)

NicoHood commented 10 years ago

Some status update if you might wonder whats going on: First of i dont have enough time for this (yep...) and i also ran into problems: The controller reading doesnt work for everyone. We have to find out why. I think the longer reading time might not suit to their controllers. With more time i'd like to learn asembler and hardcode this thing. This could be really awesome. Or maybe someone can help? The sending to the console fails in the way that it doesnt want to accept my controller data. Init+ contry code works but the controller data is not accepted. For now this project is paused till i have more time/fun to work on this. Feel free to help ;)

brownan commented 10 years ago

I've been playing with my original code lately. I've found that I had trouble with some games (perfect dark, specifically) until I removed a bunch of debug serial prints, then it suddenly started working fine. My guess is that perfect dark polls the controller faster than other games and writing to the serial console takes too much time.

I'm reading data sheets, instruction references, and gcc assembly output to try and get some of my routines into inline assembly. I haven't taken a look at your code but I imagine once I get some of these routines working in assembly we can start tweaking them to see what works and/or figure out why it's not working for everyone.

On Tue, Aug 19, 2014 at 8:57 PM, NicoHood notifications@github.com wrote:

Some status update if you might wonder whats going on: First of i dont have enough time for this (yep...) and i also ran into problems: The controller reading doesnt work for everyone. We have to find out why. I think the longer reading time might not suit to their controllers. With more time i'd like to learn asembler and hardcode this thing. This could be really awesome. Or maybe someone can help? The sending to the console fails in the way that it doesnt want to accept my controller data. Init+ contry code works but the controller data is not accepted. For now this project is paused till i have more time/fun to work on this. Feel free to help ;)

— Reply to this email directly or view it on GitHub https://github.com/brownan/Gamecube-N64-Controller/issues/2#issuecomment-52721926 .

NicoHood commented 10 years ago

That'd be great! The only difference between mine and you code is: I have got an api around i use digitalwrite (not the function, but setting pin high/low instead of in/out) I pass the pointers to the registers instead of hardcoding them. Then people can pass the pin to the begin function very easily.

I think the compiler loads this pointer on every writing (5 cycles). If we could hardcode this there might be a way to keep the pointer in the ram registers to access them faster and keep the 2 cycles. But well.. i dont know if this is possible and i am very busy with the hoodloader and usb stuff. (and university soon too)

brownan commented 10 years ago

@NicoHood so you must have modified the timings, huh, since the instructions changed a bit with that layer of indirection.

My next step is to turn the gc_send() function of mine to assembly, complete with hardcoded pins, much the same as I did yesterday for gc_get(). Once it's assembly it should be easier to change it around to add in the parameters for pin number and/or change it to use HIGH/LOW instead of INPUT/OUTPUT. I've become very familiar with avr assembly over the last few days so I don't think it'll be hard.

NicoHood commented 10 years ago

I just played with templates any maybe it would be possible to use templates for what we want to do. The cool thing is that it doesnt take any ram for the pointers, the bad thing is that it dupes the function if we use two controllers. But with the pointer overhead this isnt that big deal, and for my IR lib it turned out that such an overhead is bigger than duping the function in my case.

If anyone has time to do that, that'd be an idea. Maybe i could try this, but not in the next weeks sadly :(

any other updates about the assembler hardcoding brownan?

NicoHood commented 9 years ago

I looked through the assembly code again and tried to follow what you did. It is really awesome how you did it and also the explanations. That is really detailed and clear!

Now that i have more experience with templates I think this wont help here, because it only would pass the PORT pointer, and then dupe the function. A normal function pass would work the same, just that the function is not duped. The only thing that a template could help would be reducing the function overhead itself, because only 2 instead of 3 arguments are passed.

Now I am wondering two things: Would it be possible to somehow pass the PORT pointer within the function and then set the pin high/low? Didnt you also mention that there is an asm call that that toogles a pin within a single instruction?

The second thing would be an 8mhz support. Would this work with the new assembly stuff?

Do you have any useful docs about learning assembler? I really would like to know more about this to write a custom ISR and also improve my Gamecube API as well ;)

~Nico

NicoHood commented 9 years ago

Also, is it important that this is a char? static void gc_send(unsigned char *buffer, char length)

i suggest to edit this to uint8_t or int8_t to make it more clear

NicoHood commented 9 years ago

You wrote // the Z register (r31:r30) is the buffer pointer

And here you use r30: https://github.com/brownan/Gamecube-N64-Controller/blob/master/gamecube.ino#L373

If r30 is equal with Z, why not replace it? Or am I wrong with that? This r30 confuses me. Edit: If i understood it correct, then r30 is the lower value and r31 the higher value of the pointer. Dont we have a possible overflow problem here or will this automatically add +1 to r31 if r30 is 255?

Edit2: what if we do Z+ while loading this byte? Or does it increment the byte itself, not the pointer?

Edit: http://www.avr-asm-tutorial.net/avr_en/beginner/REGISTER.html Pointer-register section:

"ld r25, +Z\n" // (2)   increment byte pointer and load the next byte

This should work? This would a) solve the overflow problem and b) make things more clear and c) saves 2 operations. I might be wrong, so you have to check this again.

NicoHood commented 9 years ago

sorry for spamming, but there is a type: two times 'and' https://github.com/brownan/Gamecube-N64-Controller/blob/master/gamecube.ino#L542

I will edit minor bugs i find here later.

edit: https://github.com/brownan/Gamecube-N64-Controller/blob/master/gamecube.ino#L134 the interrupts() is wrong. if initialization fails interrupts are still on!

NicoHood commented 9 years ago

I managed to optimize the code a bit more and also found a pointer overflow bug. Will post my progress soon on my own project and/or here as pullup request.

On top of that I measured the pins with my logic analyzer and was wondering why each bit was exactly 4uS but the timings were not 1 and 3 uS itself. It seems that only setting the pinmode instead of the pin state gives some delay in one direction (about 2 cycles). The controller will work anyways.

work in progress here (with a lot of debug stuff for myself): https://github.com/NicoHood/Gamecube-N64-Controller/blob/master/gamecube.ino

brownan commented 9 years ago

I haven't thought about this project in a while, but I'll answer what I can.

Would it be possible to somehow pass the PORT pointer within the function

and then set the pin high/low? Didnt you also mention that there is an asm call that that toogles a pin within a single instruction?

There's an instruction that toggles a bit, yes, the SBI and CBI insructions: set bit in I/O register and clear bit in I/O register. Thing is, if I'm reading the assembly manual http://www.atmel.com/Images/Atmel-0856-AVR-Instruction-Set-Manual.pdf correctly, it only works on immediate (hardcoded) registers. In order to set a bit and register specified at runtime, you have to create a pointer to the register's memory-mapped address, and do a LD (load indirect), manipulate it, and then ST (store indirect). At least, that's the conclusion I remember making last time I looked into this.

The load and store instructions each take 2 cycles, so that route is sort of expensive.

If anyone knows another way to do this operation, speak up, since it's entirely possible I missed something. I don't have much experience with AVR assembly. I did notice the IN and OUT instructions let you copy an I/O register to a general register, but you still have to hardcode which register to copy. Still, hardcoding the register but specifying the bit at runtime would be better than hardcoding everything.

The SBI and CBI instructions take 2 cycles but it's sort of not clear from the manual. I don't think you can change any I/O bits in one cycle.

My info on how to access I/O locations comes from section 8.5 (page 21) of the ATmega328p datasheet http://www.atmel.com/images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf .

The second thing would be an 8mhz support. Would this work with the new

assembly stuff?

I doubt it. Everything is still timed around 16mhz and the exact timing of the assembly instructions involved.

Do you have any useful docs about learning assembler? I really would like

to know more about this to write a custom ISR and also improve my Gamecube API as well ;)

Not really. The linked assembly manual for AVR chips is pretty terse and hard to decipher in places. If you don't know the basics of assembly then I wouldn't expect that to get you very far. I learned the basics from my assembly class in college. Sorry.

Also, is it important that this is a char?

static void gc_send(unsigned char *buffer, char length) i suggest to edit this to uint8_t or int8_t to make it more clear

Sure. Sounds good.

You wrote

// the Z register (r31:r30) is the buffer pointer And here you use r30:

https://github.com/brownan/Gamecube-N64-Controller/blob/master/gamecube.ino#L373 If r30 is equal with Z, why not replace it? Or am I wrong with that? This r30 confuses me.

In avr chips, there are some registers that can be used as pointers. These are the X, Y, and Z pointers, which correspond to r26:r27, r28:r29, and r30:r31 respectively. The r* registers are used to manipulate the pointer, and the XYZ registers are used when referring to the dereferenced memory pointed to by the corresponding r* registers.

See section 7.4 in the datasheet document linked above (pages 11 and 12).

sorry for spamming, but there is a type: two times 'and'

https://github.com/brownan/Gamecube-N64-Controller/blob/master/gamecube.ino#L542 I will edit minor bugs i find here later.

thanks.

I managed to optimize the code a bit more and also found a pointer overflow bug. Will post my progress soon on my own project and/or here as pullup request.

On top of that I measured the pins with my logic analyzer and was wondering why each bit was exactly 4uS but the timings were not 1 and 3 uS itself. It seems that only setting the pinmode instead of the pin state gives some delay in one direction (about 2 cycles). The controller will work anyways.

I double and triple checked all the timings of all assembly instructions and pathways, but my reading of the datasheet may have been wrong (I found at least one contradiction and a lot of ambiguity), and I could have still made some mistakes. If it seems to work then I'm not too worried about it, though. Also I don't have a logic analyzer.

There are some timing charts in the datasheet section 14.2.4 (page 77) for reading I/O pins, but I can't really make sense of them.

NicoHood commented 9 years ago

In avr chips, there are some registers that can be used as pointers. These are the X, Y, and Z pointers, which correspond to r26:r27, r28:r29, and r30:r31 respectively. The r* registers are used to manipulate the pointer, and the XYZ registers are used when referring to the dereferenced memory pointed to by the corresponding r* registers.

Yeah lets pretent r30 is 255 and you add 1. then the r31 also has to go one up because of the overflow, if you can follow.

To summarize: Today I got the sending working perfectly on any pin. And I think with some modifications even 8mhz should be possible for the sending part. What I am more worried about is the receiving part. I am currently understanding and improving it. The hard thing here is to use a pointer, since it needs more instructions than normal. The methode for sending takes nearly the same amout of instructions like the classic cbi and sbi. For 8mhz this could be more critical.

But I am seeing things positive. Just give me some time and I will report further development. :) After that someone really should have a look at the asm, and see if its all correct. I also found pointer bugs in your code and a possible overflow at reading.

Thx for the response and the code basic you gave me. This thing is getting better and better happy

NicoHood commented 9 years ago

Got a very nice library right now: https://github.com/NicoHood/Nintendo/tree/beta

I need at least someone else to test, then I will merge it to master. I guess you could also create a N64 adapter but I dont own any N64, so I cannot test it. I will try to create a sketch that "could" work.

Anyone willing to test? Or anyone willing to check the code? I tested the timings with a logic analyzer and it should be okay. The next step will be 8mhz and 20mhz support. I think i could even do 2mhz with some very evil tricks but thats probably not worth it XD. But 8mhz would be nice on 3.3v devices.

Edit: interesting would be if someone can report results with a wavebird controller.

NicoHood commented 9 years ago

Still feeling talking to myself :D I'd love to add a gc to n64 example but i simply dont own a n64. The library should be working now. 8 and 20mhz still need to be added.

https://nicohood.wordpress.com/?p=335&preview=true