Tech Talks: Programming for the PSP with examples (Beginner friendly)

The PSP was Sony's first and biggest foray into the handheld console scene. While it was remembered for being a powerful system with a plethora of good games of console quality, it was also hacked rapidly and the homebrew scene grew at an absurd rate. The PSP scene reached incredible highs and remains one of the consoles with the biggest and proudest homebrew catalogs, being hackable in minutes with a USB cable and with software from a near-fullspeed N64 emulator, all of the standard homebrew goodies and some more interesting things such as a universal remote program, a screen mirroring program so you can play PC games on PSP and even a broken, simple port of nullDC - the Dreamcast emulator.

So as such a massively famous system in the hacking scene, I think it would be good if I introduced you to the PSP as a coding companion. It will certainly serve you well, because it's a perfect programming system; unlike the GBA, it supports easier languages like Python more easily while also allowing for C code to be easily and effectively compiled. It doesn't give you all the overhead you would like, but it gives you enough for 3D experiments. It allows you to go down to the metal as far as C will allow you, and has its own set of nice APIs, but they are also largely avoidable although you won't quite have the same bare-metal experience as PSP.

Hardware
Without further ado, a reminder of the PSP's specs so you know what you're working with. Unlike the GBA, the PSP has a GPU. This allows us to enter a whole new world of programming; once you get comfortable, you can try your hand at accelerating your programs by using the GPU and doing hardware 3D rendering for maximum efficiency, but thankfully the PSP has enough horsepower to brute-force some 3D rendering without the need for a GPU. The PSP's CPU is a MIPS based CPU, similar to the PS2's, and runs at any desired clockspeed from 1 to 333MHz. Generally, these are preset to 222, 266, 300 and 333MHz. The GPU and memory bus are affected by the chosen CPU speed, however, as they operate at exactly half the speed at all times. So at 333MHz, the bus and GPU are operating at 166MHz. The PSP also has a generous 32MB of RAM available, and 4MB of VRAM. This is the same as the PS2, in fact. However, PSP models after the original PSP-1000 have 64MB of RAM. So while commercial games could only utilise 32MB, homebrew games can access practically all of this memory, though some is still reserved for system tasks.

sshot10.jpg

Engines such as the Quake engine have excellent hardware-accelerated ports on PSP and are very flexible. Above: Screenshot from Quake-based "Kurok" designed exclusively for PSP on real hardware.
So we have lots of generous hardware overhead if we ever want to venture into 3D; that's great! But for now, we'll be sticking with simple demos to ease us into this.

Setting Up

Setting everything up is a little less straightforward. The PSP SDK on GitHub is still receiving regular updates, however I have heard that it's currently not as stable as it could be - for example, a friend of mine was baffled when trying to compile DaedalusX64 for PSP for a solid week until he realised the current toolchain on GitHub was the problem. So I recommend checking this version out instead as the last stable version to be safe.

To use this on Windows, I recommend setting up a Linux virtual machine or Cygwin because currently the resources for Windows are scarce and just inferior to Linux's toolset, unfortunately. So it's your best bet. There is an excellent tutorial on setting this up with the PSP toolchain on Wikibooks here, and I strongly advise reading those instructions to set it up. If you are already running Linux, simply download the above toolchain.

Makefile

A makefile is simply a file we use to tell the compiler some information about our program and allowing it to build the program on command. Here is a template for a makefile:


Code:
TARGET = my_program
OBJS   = main.o myLibrary.o

INCDIR   =
CFLAGS   = -G0 -Wall -O2
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS  = $(CFLAGS)

LIBDIR  =
LDFLAGS =
LIBS    = -lm

BUILD_PRX = 1

EXTRA_TARGETS   = EBOOT.PBP
PSP_EBOOT_TITLE = My Program
PSP_EBOOT_ICON= ICON0.png
PSP_EBOOT_PIC1= PIC1.png
PSP_EBOOT_SND0= SND0.at3

PSPSDK=$(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak

It's not too bad, you don't actually have to touch most of this at all. You can change names, such as "My Program", and if you're really looking for a performance gain you can change up flags such as changing -O2 to -O3 for a more aggressive compiler to optimise your program. However, we won't need to change much at all. The "target" tells the compiler the name of our source file, and "objs" to create a "main.o" and "myLibrary.o" as we are using "main.c" as the file where we program the code and “myLibrary.c” as a helper class.

Next we tell it to build a PRX binary rather than a static ELF binary. Static ELFs are generally depreciated, and only PRX-based homebrew can be signed to run on any PSP firmware. Afterwards we also create an "EBOOT.PBP" which is the executable, like an EXE on Windows. Then you give it the title and have an option of an icon (144x80), a background picture (480x272), and a PSP sound file (at3). If you don't want one of these, then simply delete the lines.

Callbacks

Callbacks are common files that will be used frequently in our programs and will make our lives easier in future so we don't have to re-write the same code. Create a folder called "common" in your programming directory so we can store some callbacks here.

Let's create a callback called "callback.h". This is a header file, and it will define and declare a few things so our program can run.

Code:
#ifndef COMMON_CALLBACK_H
#define COMMON_CALLBACK_H

int isRunning();
int setupExitCallback();

#endif

The 'ifndef' is used to make sure that this file only gets imported once, otherwise an error will occur. Then it should be pretty self-explanatory; we will have two functions which we will use: "isRunning()" to check if the user has requested to quit the program, and "setupCallbacks()" which will setup all the necessary things for your program to run on the PSP.

That's all for "callback.h". You can save and close that now. Now that we have the header definitions we can also create the source file: name it "callback.c". We'll start by including the file "pspkernel.h" which gives us access to several kernel methods.

Code:
#include <pspkernel.h>

Next, we create a boolean. A boolean is a simple true or false statement. Executing the method "isRunning()" will tell us whether a request to exit the application was created by the user. We will use this function in our program so that we can clean up any leftover memory and exit the program without crashing it.

Code:
static int exitRequest  = 0;

int isRunning()
{
    return !exitRequest;
}

Simple so far, as you can see. Well, the next part is a bit more complicated. But don't worry, you don't need to understand it. This basically creates a new thread, which creates a callback for exiting our program. The callback will then make "exitRequest" = true if the user presses the "home" and "exit" button on their PSP, and I'm sure that's self explanatory. Here's the complete code for "callback.c":

Code:
[SPOILER]#include <pspkernel.h>

static int exitRequest = 0;

int isRunning()
{
    return !exitRequest;
}

int exitCallback(int arg1, int arg2, void *common)
{
    exitRequest = 1;
    return 0;
}

int callbackThread(SceSize args, void *argp)
{
    int callbackID;

    callbackID = sceKernelCreateCallback("Exit Callback", exitCallback, NULL);
    sceKernelRegisterExitCallback(callbackID);

    sceKernelSleepThreadCB();

    return 0;
}

int setupExitCallback()
{
    int threadID = 0;

    threadID = sceKernelCreateThread("Callback Update Thread", callbackThread, 0x11, 0xFA0, THREAD_ATTR_USER, 0);
    
    if(threadID >= 0)
    {
        sceKernelStartThread(threadID, 0, 0);
    }

    return threadID;
}
[/SPOILER]

Again, understanding it isn't too important right now, since this won't change and it will simply be included in our programs in future for easy exiting.

Hello World

We have already gone through a lot, and we have set down some foundations for our future PSP projects! That's actually a lot of the hard part. The next part is simple; we will make a simple template for a program and throw a "Hello World" into it.

We'll start by including “pspkernel.h” which will allow us to exit the application, "pspdebug.h" so that we can get a simple debug screen started, "pspdisplay.h" for "sceDisplayWaitVblankStart" function - so we can synchronise the screen timing with V-Blanks, as we covered in the GBA programming post - and "callback.h" of course so that the user can quit at any time by pressing 'home' and then 'exit'.

Code:
#include <pspkernel.h>
#include <pspdebug.h>
#include <pspdisplay.h>

#include "../common/callback.h"

Next part is also quite simple; we simply give the PSP some details about our program.

Code:
#define VERS    1 //Talk about this
#define REVS    0

PSP_MODULE_INFO("Hello World", PSP_MODULE_USER, VERS, REVS);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);
PSP_HEAP_SIZE_MAX();

#define printf pspDebugScreenPrintf

In PSP_MODULE_INFO we tell the PSP the name of our program, as well as any attributes and the version of our program. We don't need to get too deep into this part. I have also defined "printf" as "pspDebugScreenPrintf". "printf" is a simple function in C to print text to the screen, and this is just simplifying the longer version of this code from the PSP APIs. So that way, when the program is being compiled, "printf" will simply act as an alias for "pspDebugScreenPrintf". Simple! That's definitions for you. Next part:

Code:
int main()
{       
    pspDebugScreenInit();
    setupExitCallback();

    while(isRunning())
    {
        pspDebugScreenSetXY(0, 0);
        printf("Hello World!");
        sceDisplayWaitVblankStart();
    }

    sceKernelExitGame();   
    return 0;
}

This is the main "loop" of our program, as the "int main()" suggests. This is the heart of the code in a C program. First, we will initialize the debug screen, and setup our callbacks. Then inside the loop we place the position to write to at (0,0) so that printing doesn't go to the next line and print out our message. Then to prevent screen tearing (ie. ensuring that the screen delivers frames with perfect consistency to avoid a "tearing" effect) we call “sceDisplayWaitVblankStart”. Once the user quits and the loop is broken (remember we are using the "isRunning()" method), we do a last call to "sceKernelExitGame()" which will exit our application and return zero to close the program.

Now if you are using an IDE that can also compile your PSP programs, you can hit compile and put the "EBOOT.PBP" in a folder on your PSP, and then run it. If on the other hand you choose to do things manually, then we will have to create the Makefile before compiling. I'll be making the Makefile for the sake of covering it in this tutorial. Remember we talked about one of these earlier? Well, let's flesh it out a bit!

Code:
TARGET        = hello_world
OBJS        = main.o ../common/callback.o

INCDIR        =
CFLAGS        = -G0 -Wall -O2
CXXFLAGS    = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS    = $(CFLAGS)

LIBDIR        =
LDFLAGS    =
LIBS        = -lm

BUILD_PRX = 1

EXTRA_TARGETS    = EBOOT.PBP
PSP_EBOOT_TITLE= Hello World

PSPSDK    = $(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak

So we've added the name of our program and the path we want to compile it to in our Makefile. Now we simply call the "make" command on our toolchain and we have compiled our first program! This EBOOT file will boot on a real PSP. Place it in PSP:/PSP/GAME/helloworld/ and it will appear in the games section of your PSP! (You can also boot it using an emulator.)

That was cool! It's great to see our work pay off like this in such a nice way. As you can see, the PSP is quite a standardised platform and things have to be named and set up. But it's worth it, because the final result looks proudly official and complete.

Button Input

So in the second tutorial we will be learning how to work with button inputs. Let's start with our headers again. It's the same deal as before, except this time we're including "pspctrl.h" so we can accept button inputs.

Code:
#include <pspkernel.h>
#include <pspdebug.h>
#include <pspctrl.h>

The code here is the same, aside from changing the name and version number. 


[CODE]#define VERS    2
#define REVS    1

PSP_MODULE_INFO("Button Input", PSP_MODULE_USER, VERS, REVS);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);
PSP_HEAP_SIZE_MAX();

#define printf pspDebugScreenPrintf

Now for the main part of our program again.

Code:
int main()
{       
    pspDebugScreenInit();
    setupExitCallback();

    int running = isRunning();

    SceCtrlData buttonInput;

    sceCtrlSetSamplingCycle(0);
    sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);

In our main loop we do the usual, but this time create a variable called running so that we can update it if the exit callback occurs or if the user presses the button 'start'. Then we create the button input object and set the sampling rate to 0, and allow analog mode so that we can read where the analog pad is.

Code:
    while(running)
    {
        running = isRunning();

        pspDebugScreenSetXY(0, 0);
        printf("Analog X = %d ", buttonInput.Lx);
        printf("Analog Y = %d \n", buttonInput.Ly);

Now we start a while loop and update our variable from the exit callback telling us if we should quit or not. We reset the position to print, and print out the analog pad position. Printing the variable is done by passing it in the second parameter and using '%d' as a placeholder for it.

One thing to remember, when you get the position of the analog stick using the 'Lx' and 'Ly' properties, you get a value from 0 to 255. You can subtract 128 from that so that the center would be 0. However, there is a chance that the program will pick up an analog position because of the stick's "dead zone". This is when the analog stick is ever so slightly off or imperfectly positioned, and our program is picking it up.

Code:
        sceCtrlPeekBufferPositive(&buttonInput, 1);

        if(buttonInput.Buttons != 0)
        {
            if(buttonInput.Buttons & PSP_CTRL_START){
                                    printf("Start");
                                    running = 0;
            }

Okay, so this is the idea for our program. As you can infer from the code above, if we are looking for button inputs and the start button is pressed, we print "Start". We also set running to 0; by changing the state of "running" from true to false (or 1 to 0 in this case), we can stop our program smoothly. Let's continue with the other buttons.

Code:
if(buttonInput.Buttons & PSP_CTRL_SELECT)    printf("Select");

if(buttonInput.Buttons & PSP_CTRL_UP)        printf("Up");
            if(buttonInput.Buttons & PSP_CTRL_DOWN)        printf("Down");
            if(buttonInput.Buttons & PSP_CTRL_RIGHT)    printf("Right");
            if(buttonInput.Buttons & PSP_CTRL_LEFT)        printf("Left");

            if(buttonInput.Buttons & PSP_CTRL_CROSS)    printf("Cross");
            if(buttonInput.Buttons & PSP_CTRL_CIRCLE)    printf("Circle");
            if(buttonInput.Buttons & PSP_CTRL_SQUARE)    printf("Square");
            if(buttonInput.Buttons & PSP_CTRL_TRIANGLE)    printf("Triangle");
    
            if(buttonInput.Buttons & PSP_CTRL_RTRIGGER)    printf("R-Trigger");
            if(buttonInput.Buttons & PSP_CTRL_LTRIGGER)    printf("L-Trigger");
        }
    }

At the end we close our program. You may notice that we don't check for all buttons, and this is because we need kernel control to check for those buttons. That may be covered in a future tutorial, but for now that's it. Here's a final overview of the code:

Code:
#include <pspkernel.h>
#include <pspdebug.h>
#include <pspctrl.h>

#include "../common/callback.h"

#define VERS    2
#define REVS    1

PSP_MODULE_INFO("Button Input", PSP_MODULE_USER, VERS, REVS);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);
PSP_HEAP_SIZE_MAX();

#define printf pspDebugScreenPrintf

int main()
{       
    pspDebugScreenInit();
    setupExitCallback();

    int running = isRunning();

    SceCtrlData buttonInput;

    sceCtrlSetSamplingCycle(0);
    sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);

    while(running)
    {
        running = isRunning();

        pspDebugScreenSetXY(0, 0);
        printf("Analog X = %d ", buttonInput.Lx);
        printf("Analog Y = %d \n", buttonInput.Ly);

        sceCtrlPeekBufferPositive(&buttonInput, 1);

        if(buttonInput.Buttons != 0)
        {
            if(buttonInput.Buttons & PSP_CTRL_START){
                                    printf("Start");
                                    running = 0;
            }
            if(buttonInput.Buttons & PSP_CTRL_SELECT)    printf("Select");

            if(buttonInput.Buttons & PSP_CTRL_UP)        printf("Up");
            if(buttonInput.Buttons & PSP_CTRL_DOWN)        printf("Down");
            if(buttonInput.Buttons & PSP_CTRL_RIGHT)    printf("Right");
            if(buttonInput.Buttons & PSP_CTRL_LEFT)        printf("Left");

            if(buttonInput.Buttons & PSP_CTRL_CROSS)    printf("Cross");
            if(buttonInput.Buttons & PSP_CTRL_CIRCLE)    printf("Circle");
            if(buttonInput.Buttons & PSP_CTRL_SQUARE)    printf("Square");
            if(buttonInput.Buttons & PSP_CTRL_TRIANGLE)    printf("Triangle");
    
            if(buttonInput.Buttons & PSP_CTRL_RTRIGGER)    printf("R-Trigger");
            if(buttonInput.Buttons & PSP_CTRL_LTRIGGER)    printf("L-Trigger");
        }
    }
    
    sceKernelExitGame();
    return 0;
}

And that's it! Let's set up a makefile for this so we can compile it.

Code:
TARGET        = ButtonInput
OBJS        = main.o ../common/callback.o

INCDIR        =
CFLAGS        = -O2 -G0 -Wall
CXXFLAGS    = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS    = $(CFLAGS)

BUILD_PRX    = 1

LIBDIR        = ./
LIBS        = -lm
LDFLAGS    =

EXTRA_TARGETS        = EBOOT.PBP
PSP_EBOOT_TITLE    = ButtonInput

PSPSDK    = $(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak

Now we run "make" and we should have our second PSP program!

That wraps up this tutorial. Since a lot of new concepts were introduced, such as makefiles, callbacks and such, I thought we would leave it at this for now. (It's also almost 1AM for me)
I hope you guys enjoyed this tutorial as well and if people like this, I'll keep making them! Though admittedly my knowledge is pretty rusty and I'm pretty reliant on pre-existing resources at this point. :P
If you enjoyed it, please like it, tell me what you think in the comments and follow me to get notified of further posts. Thanks for reading!
  • Like
Reactions: 9 people

Comments

The PSP is one of my favourite consoles; it's so accessible to hack, has a crop of good games, it's portable while able to be connected to a TV (even with a dock, like Switch PC, and it's a nice system to program for. Definitely my favourite portable.
 
Would graphic handling be on the list at some point? Including showing/hiding images, moving......ya know, the stuff everyone wants to know.
 
Yeah, this was just getting into the basics. I have no problem writing up some more stuff once I get myself acclimatised to it again.
 
  • Like
Reactions: 1 person
Are you seriously just slightly rewriting tutorials you can find online and passing them off as your own?
 
No - the above tutorial borrowed from the wikibooks tutorial, but these are openly available samples and I'm not "stealing" anything here. My next tutorial is on something that hasn't been covered on any online article.
 
  • Like
Reactions: 1 person

Blog entry information

Author
TheMrIron2
Views
739
Comments
13
Last update

More entries in Personal Blogs

More entries from TheMrIron2

General chit-chat
Help Users
  • No one is chatting at the moment.
    Bunjolio @ Bunjolio: