Skip to content

Commit

Permalink
fix[SNESpad]: updates lib to latest with snes mouse to analog working…
Browse files Browse the repository at this point in the history
… correctly
  • Loading branch information
RobertDaleSmith committed May 24, 2023
1 parent 1a4cea6 commit a640ac6
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 43 deletions.
96 changes: 92 additions & 4 deletions lib/SNESpad/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,110 @@
# SNESpad for PicoSDK
# SNESpad for Arduino and PicoSDK

This is an extensive rewrite and port of the original SNESpad Arduino library to be compatible with the Pico SDK. This version of the library is designed to work with the GP2040-CE project.
This is a library for the Raspberry Pi Pico that allows it to interface with a SNES controller. This library was adapted from the Arduino SNESpad library, with changes and optimizations made for better performance and compatibility with the Pico SDK.

## Original Library

This library is based on the [nespad](https://github.com/rahji/nespad) library developed by rahji. The original library was designed for the Arduino platform.

## Features

This library allows for the reading of button states from a SNES controller connected to a Pico or Arduino. It supports standard SNES controller, the NES controller, and the SNES Mouse. Each of these devices can be identified and read by this library.

## Usage

1. Include the SNESpad.h header in your program.
2. Create a SNESpad object, specifying the clock, latch, and data pin numbers.
3. Use the `begin()` and `start()` function to initialize the SNESpad.
4. Use the `poll()` function to update the state of the SNESpad.
5. You can then read the state of the buttons and the direction pad using the appropriate members of the SNESpad class.

## Button Variables

Here are the members of the SNESpad class that hold the state of the buttons:

- `buttonA`
- `buttonB`
- `buttonX`
- `buttonY`
- `buttonStart`
- `buttonSelect`
- `buttonL`
- `buttonR`
- `directionUp`
- `directionDown`
- `directionLeft`
- `directionRight`

For the SNES Mouse, these additional variables are available:

- `mouseX`
- `mouseY`

## Example

Here is an example of how to create a SNESpad object and read the state of the buttons:

```cpp
#include "SNESpad.h"

#define CLOCK 0
#define LATCH 1
#define DATA 2

SNESpad snespad(CLOCK, LATCH, DATA);

void setup() {
snespad.begin();
snespad.start();
}

void loop() {
snespad.poll();

if (snespad.buttonA) {
printf("Button A is pressed.\n");
}

if (snespad.buttonB) {
printf("Button B is pressed.\n");
}

// Add more button checks as needed.
}
```
This example will print a message to the console whenever button A or B is pressed.
## Author
The library was ported and substantially rewritten by Robert Dale Smith.
This library was ported and substantially rewritten by Robert Dale Smith.
Email: <[email protected]>
## Examples
Included in this repository are three examples that demonstrate different use cases for the SNESpad library.
1. [serial.ino](examples/serial/serial.ino) - Demonstrates how to use the SNESpad library to monitor a SNES controller over a USB serial connection.
2. [hid.ino](examples/hid/hid.ino) - Demonstrates how to use the SNESpad library to create HID mouse and HID joystick USB devices using a SNES controller.
3. [xinput.ino](examples/xinput/xinput.ino) - Demonstrates how to use the SNESpad library to create an XInput USB controller using a SNES controller or mouse.
## Changes and Enhancements
The original library was refactored and optimized for performance and compatibility. Notable changes include:
This version of the library is optimized for performance and compatibility. Notable changes from the original Arduino version include:
- The control polling methods have been optimized to closely match the specifications of the original console.
- Added support for the Super Nintendo mouse and the original NES controllers.
## Version History
- Version: 2.0 (2023)
- Extended to Pico SDK (Robert Dale Smith)
- Total refactor of class structure. (Robert Dale Smith)
- Mouse and NES controller support (Robert Dale Smith)
Below is a history of the original SNESpad library:
- Version: 1.3 (11/12/2010) - Removed shortcut constructor which was causing issues.
Expand Down
107 changes: 93 additions & 14 deletions lib/SNESpad/SNESpad.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,37 @@
/*
SNESpad - Arduino/Pico library for interfacing with SNES controllers
github.com/RobertDaleSmith/SNESpad
Version: 2.0 (2023) - Extended to Pico SDK (Robert Dale Smith)
- Mouse and NES controller support (Robert Dale Smith)
Version: 1.3 (11/12/2010) - get rid of shortcut constructor - seems to be broken
Version: 1.2 (05/25/2009) - put pin numbers in constructor (Pascal Hahn)
Version: 1.1 (09/22/2008) - fixed compilation errors in arduino 0012 (Rob Duarte)
Version: 1.0 (09/20/2007) - Created (Rob Duarte)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 FITSNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "SNESpad.h"

#include <cstring>
#include <cstdio>
#ifdef ARDUINO
#include "Arduino.h"
#else
#include <cstring>
#include <cstdio>
#endif

SNESpad::SNESpad(int clock, int latch, int data) {
latchPin = latch;
Expand Down Expand Up @@ -95,12 +125,8 @@ void SNESpad::poll() {

buttonSelect = (state & SNES_SELECT);
buttonStart = (state & SNES_START);
buttonA = (state & SNES_B);
buttonB = (state & SNES_Y);
buttonX = false;
buttonY = false;
buttonL = false;
buttonR = false;
buttonA = (state & SNES_B);

break;
case SNES_PAD_MOUSE:
Expand All @@ -110,14 +136,14 @@ void SNESpad::poll() {
// Mouse X axis
x = (state & SNES_MOUSE_X) >> 25;
x = reverse(x) * SNES_MOUSE_PRECISION;
if (state & SNES_MOUSE_X_SIGN) x = 127 + x;
else x = 127 - x;
if (state & SNES_MOUSE_X_SIGN) x = 127 - x;
else x = 127 + x;

// Mouse Y axis
y = (state & SNES_MOUSE_Y) >> 17;
y = reverse(y) * SNES_MOUSE_PRECISION;
if (state & SNES_MOUSE_Y_SIGN) y = 127 + y;
else y = 127 - y;
if (state & SNES_MOUSE_Y_SIGN) y = 127 - y;
else y = 127 + y;

mouseX = x;
mouseY = y;
Expand Down Expand Up @@ -148,6 +174,15 @@ void SNESpad::poll() {

// init gpio pins
void SNESpad::init() {
#ifdef ARDUINO
// Code specific to Arduino
pinMode(clockPin, OUTPUT);
pinMode(latchPin, OUTPUT);
pinMode(dataPin, INPUT);

digitalWrite(dataPin, HIGH); // pull_up
#else
// Code specific to Pico SDK
gpio_init(clockPin);
gpio_init(latchPin);
gpio_init(dataPin);
Expand All @@ -156,6 +191,7 @@ void SNESpad::init() {
gpio_set_dir(latchPin, GPIO_OUT);
gpio_set_dir(dataPin, GPIO_IN);
gpio_pull_up(dataPin);
#endif

return;
}
Expand All @@ -168,44 +204,83 @@ void SNESpad::speed()
&& mouseSpeed != SNES_MOUSE_FAST
&& mouseSpeedFails < SNES_MOUSE_THRESHOLD
) {
#ifdef ARDUINO
digitalWrite(clockPin,LOW);
delayMicroseconds(6);

digitalWrite(clockPin,HIGH);
delayMicroseconds(12);
#else
gpio_put(clockPin,0);
busy_wait_us(6);

gpio_put(clockPin,1);
busy_wait_us(12);
#endif
}
}

// clock in a data bit
uint32_t SNESpad::clock()
{
uint32_t ret = 0;

#ifdef ARDUINO
digitalWrite(clockPin,LOW);
delayMicroseconds(6);

ret = digitalRead(dataPin);
digitalWrite(clockPin,HIGH);

delayMicroseconds(6);
#else
gpio_put(clockPin,0);
busy_wait_us(6);

uint32_t ret = gpio_get(dataPin);
ret = gpio_get(dataPin);
gpio_put(clockPin,1);

busy_wait_us(6);
#endif
return ret;
}

// latch to start read
void SNESpad::latch()
{
#ifdef ARDUINO
digitalWrite(latchPin,HIGH);
delayMicroseconds(12);

speed();

digitalWrite(latchPin,LOW);
delayMicroseconds(6);
#else
gpio_put(latchPin,1);
busy_wait_us(12);

speed(); // check/set mouse speed

gpio_put(latchPin,0);
busy_wait_us(6);
#endif
}

uint32_t SNESpad::read()
{
uint32_t ret = 0;
uint8_t i;

/* A connected device will pull the data line low prior to latch.
A disconnected pin is kept high by internal pull_up.*/
uint32_t disconnected = false;
#ifdef ARDUINO
disconnected = digitalRead(dataPin);
#else
disconnected = gpio_get(dataPin);
#endif

latch();
for (i = 0; i < 32; i++) {
uint32_t bit = clock(); // clock shift bit in
Expand All @@ -214,13 +289,17 @@ uint32_t SNESpad::read()
if (i == 15) {
bool read_extra = !bit; // check if mouse
if (!read_extra) break; // skip extra bytes if not
#ifdef ARDUINO
delayMicroseconds(12);
#else
busy_wait_us(12);
#endif
}
}

ret = ~ret; // buttons are active low, so invert bits

// verify controller or mouse is connected
if (!ret) {
if (disconnected && !(ret & 0xFFFF)) {
type = SNES_PAD_NONE;
mouseSpeedFails = 0;
mouseSpeed = 0;
Expand Down
49 changes: 39 additions & 10 deletions lib/SNESpad/SNESpad.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
// SNESpad Library
// category=Signal Input/Output
/*
SNESpad - Arduino/Pico library for interfacing with SNES controllers
github.com/RobertDaleSmith/SNESpad
Version: 2.0 (2023) - Extended to Pico SDK (Robert Dale Smith)
- Mouse and NES controller support (Robert Dale Smith)
Version: 1.3 (11/12/2010) - get rid of shortcut constructor - seems to be broken
Version: 1.2 (05/25/2009) - put pin numbers in constructor (Pascal Hahn)
Version: 1.1 (09/22/2008) - fixed compilation errors in arduino 0012 (Rob Duarte)
Version: 1.0 (09/20/2007) - Created (Rob Duarte)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 FITSNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef _SNESPAD_H_
#define _SNESPAD_H_

#include <inttypes.h>
#include "pico/stdlib.h"
// Try to include Arduino.h
#ifdef ARDUINO
#include <Arduino.h>
#else
// If we aren't compiling on Arduino, include the Pico SDK standard library
#include "pico/stdlib.h"
#endif

#define SNES_PAD_NONE -1
#define SNES_PAD_BASIC 0
Expand Down Expand Up @@ -35,19 +64,19 @@
#define SNES_MOUSE_ID 0b1000

#define SNES_MOUSE_SLOW 0
#define SNES_MOUSE_MEDIUM 1
#define SNES_MOUSE_FAST 2
#define SNES_MOUSE_MEDIUM 2
#define SNES_MOUSE_FAST 1

#define SNES_MOUSE_THRESHOLD 10 // max speed fails (Hyperkin compatiblity)
#define SNES_MOUSE_PRECISION 20 // mouse movement velocity multiplier
#define SNES_MOUSE_PRECISION 1 // mouse movement velocity multiplier

#ifndef SNES_PAD_DEBUG
#define SNES_PAD_DEBUG false
#endif

class SNESpad {
protected:
// uint8_t address;
// uint8_t address;
public:
int8_t type = SNES_PAD_NONE;

Expand All @@ -73,10 +102,10 @@ class SNESpad {

// Methods
void begin();
void start();
void poll();
void start();
void poll();
private:
uint8_t latchPin; // output: latch
uint8_t clockPin; // output: clock
uint8_t dataPin; // input: data
Expand Down
Loading

0 comments on commit a640ac6

Please sign in to comment.