Russell Tabata
Published © GPL3+

Apple Game Boy: 2004 iPod running RetroPie

A 2004 iPod with full RetroPie functionality via a Raspberry Pi Zero 2w with the iPod clickwheel + bluetooth, wifi, and micro-usb charging.

IntermediateWork in progress659
Apple Game Boy: 2004 iPod running RetroPie

Things used in this project

Hardware components

Apple ipod 4th gen model A1059
×1
Raspberry Pi Zero 2 W
Raspberry Pi Zero 2 W
×1
Adafruit PowerBoost 1000C
Adafruit PowerBoost 1000C
×1
350mah rechargable li-ion battery, 3.7V
×1
0.5mm, 8 Pin FPC cable breakout board
×1
32 GB SD card
×1
waveshare 320x240 lcd
×1

Software apps and online services

Raspbian
Raspberry Pi Raspbian

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Tape, Electrical
Tape, Electrical

Story

Read more

Schematics

The original schematics I based this off

These are from the tutorial by Riccardo Sappia I followed.

Code

The custom click.c code to allow for click wheel input

C/C++
#include <pigpio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <wiringPi.h>
#include <stdbool.h>

#define CLOCK_PIN 23
#define DATA_PIN 22
#define HAPTIC_PIN 26
#define BIT_COUNT 32
#define PORT 9090
#define MAXLINE 1024

#define CENTER_BUTTON_BIT 7
#define LEFT_BUTTON_BIT 9
#define RIGHT_BUTTON_BIT 8
#define UP_BUTTON_BIT 11
#define DOWN_BUTTON_BIT 10
#define WHEEL_TOUCH_BIT 29

#define BUFFER_SIZE 3
#define BUTTON_INDEX 0
#define BUTTON_STATE_INDEX 1
#define WHEEL_POSITION_INDEX 2

#define KEY_LEFT         30
#define KEY_RIGHT       31
#define KEY_UP          27
#define KEY_DOWN        22
#define KEY_ENTER       23
#define SCROLL_DN       26
#define SCROLL_UP       28

#define ALT_RIGHT       24
#define ALT_LEFT        25
#define ALT_UP          29
#define ALT_DOWN        29


// used to store the current packet
uint32_t bits = 0;
// used to store the previous full packet
uint32_t lastBits = 0;
uint8_t bitIndex = 0;
uint8_t oneCount = 0;
uint8_t recording = 0;
// indicates whether the data pin is high or low
uint8_t dataBit = 1;
uint8_t lastPosition = 255;
uint8_t prevWheelPosition = 0;
int hapticWaveId = -1;

bool touchingWheel = false; // flag to tell if the click wheel is actually being touched

char buttons[] = {
    CENTER_BUTTON_BIT,
    LEFT_BUTTON_BIT,
    RIGHT_BUTTON_BIT,
    UP_BUTTON_BIT,
    DOWN_BUTTON_BIT,
    WHEEL_TOUCH_BIT
};

// all valid click wheel packets start with this
const uint32_t PACKET_START = 0b01101;

int sockfd;
char buffer[BUFFER_SIZE]; // consists of [BUTTON_INDEX, BUTTON_STATE_INDEX, WHEEL_POSITION_INDEX]
char prev_buffer[BUFFER_SIZE];
struct sockaddr_in servaddr;

// helper function to print packets as binary
void printBinary(uint32_t value) {
    for(uint8_t i = 0; i < 32; i++) {
        if (value & 1)
            printf("1");
        else
            printf("0");

        value >>= 1;
    }
    printf("\n");
}

// parse packet and broadcast data
void sendPacket() {
    if ((bits & PACKET_START) != PACKET_START) { //checks that the packet is valid
        return;
    }
    for (size_t i = 0; i < BUFFER_SIZE; i++) {
        buffer[i] = -1; // Fills the buffer with filler data for the current button packet
    }

    // BUTTON PROCESSING

    uint8_t wheelPosition = (bits >> 16) & 0xFF; // get wheelPosition data

    for (size_t i = 0; i < sizeof(buttons); i++) {  // cycles through buttons to see which are being actively pushed
        char buttonIndex = buttons[i];
        if ((bits >> buttonIndex) & 1 && !((lastBits >> buttonIndex) & 1)) { // Checks that the button is being pressed in this current package and not pressed in the previous packet
            buffer[BUTTON_INDEX] = buttonIndex;
            buffer[BUTTON_STATE_INDEX] = 1;
            if (buttonIndex == 7) {
                //printf("button pressed: %d\n", buttonIndex);
                if (wheelPosition >= 9 && wheelPosition <= 12 && buttonIndex == 7 && touchingWheel)
                {
                    //printf("ALT RIGHT PRESSED!");
                    digitalWrite(ALT_RIGHT,LOW);
                }

                else if (wheelPosition >= 33 && wheelPosition <= 37 && buttonIndex == 7 && touchingWheel)
                {
                    //printf("ALT LEFT PRESSED!");
                    digitalWrite(ALT_LEFT,LOW);
                }

                else if ((wheelPosition >= 46 || wheelPosition <= 2) && buttonIndex == 7 && touchingWheel)
                {
                    //printf("ALT UP PRESSED!");
                    digitalWrite(ALT_UP,LOW);
                }

                else if (wheelPosition >= 21 && wheelPosition <= 25 && buttonIndex == 7 && touchingWheel)
                {
                    //printf("ALT DOWN PRESSED!");
                    digitalWrite(ALT_DOWN,LOW);
                }

                else if (buttonIndex == 7)//enter
                {	digitalWrite(KEY_ENTER,LOW);
                }
            }
            if (buttonIndex==11) //up
            {
                digitalWrite(KEY_UP,LOW);
            }
            if (buttonIndex==10) //down
            {
                digitalWrite(KEY_DOWN,LOW);
            }
            if (buttonIndex==9) //left
            {
                digitalWrite(KEY_LEFT,LOW);
                //putchar(0x44);
            }
            if (buttonIndex==8) //RIGHT
            {
                digitalWrite(KEY_RIGHT,LOW);
                //putchar(0x43);

            }
            else if (buttonIndex==29)
            {
                touchingWheel = true;
            }


        } else if (!((bits >> buttonIndex) & 1) && (lastBits >> buttonIndex) & 1) { // checks that the button is not currentely being pressed and was pressed in the previous packet
            if (buttonIndex==29)
            {
                touchingWheel = false;
            }
            buffer[BUTTON_INDEX] = buttonIndex;
            buffer[BUTTON_STATE_INDEX] = 0;
            //printf("button released: %d\n", buttonIndex);
            digitalWrite(KEY_ENTER,HIGH);
            digitalWrite(KEY_UP,HIGH);
            digitalWrite(KEY_DOWN,HIGH);
            digitalWrite(KEY_LEFT,HIGH);
            digitalWrite(KEY_RIGHT,HIGH);

            digitalWrite(ALT_RIGHT,HIGH);
            digitalWrite(ALT_LEFT,HIGH);
            digitalWrite(ALT_DOWN,HIGH);
            digitalWrite(ALT_UP,HIGH);
        }
    }

    // WHEEL PROCESSING

    // uint8_t wheelPosition = (bits >> 16) & 0xFF; // obtains the wheel position data from the origional packet
    // send haptics every other position. too sensitive otherwise
    buffer[WHEEL_POSITION_INDEX] = wheelPosition; //adds wheel position data to predefined buffer
    if (memcmp(prev_buffer, buffer, BUFFER_SIZE) == 0) { // checks to see if the current packet is the same as the previous one to avoid sending redundant data
        return;
    }
    if (buffer[BUTTON_INDEX]!=7) // makes sure that the centre button isn't currentely being pressed
    {
        if (wheelPosition==47&&prevWheelPosition==0) // scroll up
        {
            digitalWrite(SCROLL_UP,LOW);
            delay(50);
            digitalWrite(SCROLL_UP,HIGH);
            prevWheelPosition = wheelPosition;
        } else if (wheelPosition==0&&prevWheelPosition==47) // scroll down
        {
            digitalWrite(SCROLL_DN,LOW);
            delay(50);
            digitalWrite(SCROLL_DN,HIGH);
            prevWheelPosition = wheelPosition;
        }else if (wheelPosition > prevWheelPosition) // scroll down
        {
            digitalWrite(SCROLL_DN,LOW);
            delay(50);
            digitalWrite(SCROLL_DN,HIGH);
            prevWheelPosition = wheelPosition;
        } else if (wheelPosition < prevWheelPosition) // scroll up
        {
            digitalWrite(SCROLL_UP,LOW);
            delay(50);
            digitalWrite(SCROLL_UP,HIGH);
            prevWheelPosition = wheelPosition;
        }
    }
    //printf("position %d\n", wheelPosition);

    // CLEAN UP
    lastBits = bits; // stores current packet as new previous packet
    sendto(sockfd, (const char *)buffer, BUFFER_SIZE,
           MSG_CONFIRM, (const struct sockaddr *) &servaddr,
           sizeof(servaddr));
    memcpy(prev_buffer, buffer, BUFFER_SIZE); // stores current buffer as new previous buffer
}

// Function to set the kth bit of n
int setBit(int n, int k) {
    return (n | (1 << (k - 1)));
}

// Function to clear the kth bit of n
int clearBit(int n, int k) {
    return (n & (~(1 << (k - 1))));
}

void onClockEdge(int gpio, int level, uint32_t tick) {
    if (!level) {
        // only care about rising edge
        return;
    }
    if (dataBit == 0) {
        recording = 1;
        oneCount = 0;
    } else {
        // 32 1's in a row means we're definitely not in the middle of a packet
        if (++oneCount >= BIT_COUNT) {
            recording = 0;
            bitIndex = 0;
        }
    }
    // in the middle of the packet
    if (recording == 1) {
        if (dataBit) {
            bits = setBit(bits, bitIndex);
        } else {
            bits = clearBit(bits, bitIndex);
        }
        // we've collected the whole packet
        if (++bitIndex == 32) {
            bitIndex = 0;
            sendPacket();
        }
    }
}

void onDataEdge(int gpio, int level, uint32_t tick) {
    dataBit = level;
}

int main(void *args){ // must set newly added keyboard pins as output & high in this section
    wiringPiSetup () ;
    pinMode(KEY_LEFT, OUTPUT) ;
    pinMode(KEY_RIGHT,OUTPUT);
    pinMode(KEY_UP ,OUTPUT);
    pinMode(KEY_DOWN,OUTPUT);
    pinMode(KEY_ENTER,OUTPUT);
    pinMode(SCROLL_DN,OUTPUT);
    pinMode(SCROLL_UP,OUTPUT);

    pinMode(ALT_RIGHT,OUTPUT);
    pinMode(ALT_LEFT,OUTPUT);
    pinMode(ALT_UP,OUTPUT);
    pinMode(ALT_DOWN,OUTPUT);

    digitalWrite(KEY_LEFT,HIGH);
    digitalWrite(KEY_RIGHT,HIGH);
    digitalWrite(KEY_UP,HIGH);
    digitalWrite(KEY_DOWN,HIGH);
    digitalWrite(KEY_ENTER,HIGH);
    digitalWrite(SCROLL_DN,HIGH);
    digitalWrite(SCROLL_UP,HIGH);

    digitalWrite(ALT_RIGHT,HIGH);
    digitalWrite(ALT_LEFT,HIGH);
    digitalWrite(ALT_UP,HIGH);
    digitalWrite(ALT_DOWN,HIGH);

    // Creating socket file descriptor
    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = INADDR_ANY;

    if (gpioInitialise() < 0) {
        exit(1);
    }

    // haptic waveform - just a simple on-off pulse
    gpioSetMode(HAPTIC_PIN, PI_OUTPUT);
    gpioPulse_t pulse[2];
    pulse[0].gpioOn = (1<<HAPTIC_PIN);
    pulse[0].gpioOff = 0;
    pulse[0].usDelay = 8000;

    pulse[1].gpioOn = 0;
    pulse[1].gpioOff = (1<<HAPTIC_PIN);
    pulse[1].usDelay = 2000;

    gpioWaveAddNew();

    gpioWaveAddGeneric(2, pulse);

    hapticWaveId = gpioWaveCreate();
    gpioSetPullUpDown(CLOCK_PIN, PI_PUD_UP);
    gpioSetPullUpDown(DATA_PIN, PI_PUD_UP);
    gpioSetAlertFunc(CLOCK_PIN, onClockEdge);
    gpioSetAlertFunc(DATA_PIN, onDataEdge);

    while(1) {

    };
    gpioTerminate();
}

Credits

Russell Tabata

Russell Tabata

1 project • 1 follower
I like to try and make cool things.

Comments