Finally got my first program running on pocketstation. The whole journey is quite painful and it's still far from over, but it's been a learning experience, so I thought I'd share here.
I started the project assuming that things would be simple.
It was not.
But in this post, I'll talk mostly about the rust aspect of things. This is mostly rants and some part sharing I guess.
So in case you're new to the subject, I've been working on things pocketstation related recently. Pocketstation is pretty much Sony Playstation's answer to VMU. I didn't know it existed until this year or so, apparently it's a Japan only thing.
It's pretty much a normal PS1 memory card with capabilities of running small games.
The CPU is ARM7TDMI (armv4t), pretty much the same CPU used by GBA. There's only 2kB of RAM available.
The main problem using Rust is that it's a high level language that abstracts a lot of memory access. That's kind of the point of using these high level language instead of say, using assembly. But there's a quirk that separates pocketstation from GBA.
Memory reads happens in 8-bit, 16-bit, or 32-bit data. on GBA, all memory access can be done with 8-bit.
On Pocketstation, however, most peripherals are strictly 32-bit (some allow 16-bit access). The RAM allows 8-bit access no problem.
However, the FLASH rom doesn't allow 8-bit access at all! I have scratched my head for several days trying to figure out why my code would either resets the device (trips the illegal address ARM handler), or it would make the device hung, or I would try showing texts but it would be garbled, while I could draw lines and circle just fine.
I don't mind writing volatile read of 16 bit for normal peripherals such as LCD / timer. But it's impossible to enforce this on flash rom as... that's where your code lives. Of course, ARM instructions are 32 bit, THUMB 16 bit, but variables? You can't have array of bytes. It's difficult to make sure that you're careful enough and not write any byte array, but libraries? It's super difficult to make sure that every single library that you load does not write any 8-bit variable into the byte array.
Post automatically merged:
So clearly, we can't prevent it in rust, we have to go deeper.
I can't think of any way to prevent 8-bit load on just the FLASH memory, so I just need to disable 8-bit load from being generated (.ll -> .s), so this is the domain of LLVM.
So we need to download rust source code, and modify the llvm that is bundled (git-submoduled) in rust.
Took a while to make the necessary modification in LLVM, I mostly used AI to help me with it since I encounter infinite-loop that was hard to debug, but essentially, I made changes so that ldrb instruction is turned into ldr followed by and 0xff.
Hi,
first of all, the entire tooling is still in very early stage, so I'm sorry things are going to be rough. I'll try to make things easier in the future. This modification is only for rust compiler (specifically the llvm part used by codegen).
If you're new to embedded development / rust, I'm sorry to say that things may be overwhelming, here's the major thing you need to know before proceeding:
You'll need to understand pocketstation architecture, peripherals and all. Martin has a good resource on it. I am in the process of trying things out myself, I'm not ready to make a PAC just yet.
You can start by cloning the rust repository I linked which also includes the llvm fork as a git submodule, then follow the "Installation from source" guide, which involves calling `x.py` and all. Once that's done, you need to switch rust version to use your compiled one, by using rustup.
Once you've done all that, you should have a rust version with my patch, most importantly it converts 8-bit load/store to 32-bit load/store which is needed for pocketstation.
I'm using embedded-graphics for drawing / font, tiny_bmp works too. portable-atomic works but you'll need critical-section and implement its acquire/release (I'm just using compiler fence for now and hope for the best)
Hey, this is cool. I remember seeing your post about getting animations working on it earlier this year, admirable that you've kept at it.
I've always been interested in embedded-type stuff, programming Atmel and ESP32 chips with the Arduino framework is about as far as I've got. Most of what you're explaining goes over my head. Can you share what kind of software stack you use to do this sorta thing? Like what sorta editor + build/flash workflow and how you've connected it to your machine?
Hey, this is cool. I remember seeing your post about getting animations working on it earlier this year, admirable that you've kept at it.
I've always been interested in embedded-type stuff, programming Atmel and ESP32 chips with the Arduino framework is about as far as I've got. Most of what you're explaining goes over my head. Can you share what kind of software stack you use to do this sorta thing? Like what sorta editor + build/flash workflow and how you've connected it to your machine?
Glad you enjoyed my posts, I'll try to keep on posting as I progress!
Let me try to give you a crash course, and terminologies in terms of Arduino since that's what you're familiar with, hopefully it will help you explore more.
I think we all start somewhere, I started on Arduino framework myself, from Arduino Duemilanove (remember those?) then Uno, then ESP. Arduino provides the platform that does the heavylifting.
Let's talk about Arduino Uno, which is an AVR atmega328 chip.
Arduino provides libraries and abstractions, for example, digitalWrite(13, HIGH) is wired to the red LED. In reality, the AVR cpu don't deal with individual "pin 13", but rather with Port, where one port consists of 8 bit, thus 8 pins. So digital pin 13 would be on port B bit 5. So writing PORTB = 0b00100000 would turn on the LED.
Now what is PORTB? PORTB is just an addresss pointer that you can write values into. This snippet of digitalWrite should give you a glimpse on how it looks like.
Access to peripherals (such as digital pins, thus giving you LEDs) are often time achived in similar fashion. This is called Memory-mapped IO (MMIO), so anytime you have an address pointer, it doesn't have to be memory in RAM, you could be "talking" to LCD, timers, etc...
This action of abstracting hardware intrinsics and exposing easy-to-use API is called Hardware Abstraction Layer (HAL)
Aside from that, I'm sure you're used to "setup()" and "loop()" function in Arduino. If you've coded C++/C before, you'll know that those are not something the compiler recognizes as the entry point, there needs to be a "main" function. This is also something that Arduino hides from you. Sure enough, Arduino does have a "main" function which you can check here. You can see how it does some initialization and ultimately calls setup() and loop() as well.
On Arduino, compilation and upload is really abstracted away aside from calling it compile and flash. Under the hood, it calls avr-gcc (as seen here). avr-gcc is a generic C (C++?) compiler that is not-at-all arduino specific. But because arduino provides all those libraries and `.h` includes, you can use their helper functions.
Compilers (like gcc) usually outputs an ELF file (similar to how windows have .exe program, which is a COFF binary). This file contains the binary data (assembly) but also other sections such as what are the global variables, what are their location in memory, etc. Chips don't care about these stuffs, as they only care about being reset to address like 0x0, and somehow being able to read program written there. That's where objcopy comes in: it takes the ELF file and outputs .bin or .hex file that can be flashed to the chip. On AVR, flashing is done to the chip itself, using program like avrdude, over SPI. Of course, you don't plug an SPI cable to the chip, you use USB cable which connects to the on-board programmer, nowadays they're usually Atmega32U series as they have USB functionality, so ArduinoUNO technically has 2 AVRs on-board.
Post automatically merged:
---
On pocketstation, we don't have big corporation helping us making good tools to work with it. So we have to create our own HAL (how to access timer, how to access LCD). You can try reversing existing software written for pocketstation, reversing the kernel, or find documentations online. Essentially, you need to know which address corresponds to which peripheral, often with guessworks. For example, if I know a certain address correspond to LED, and LED only lights up when the screen says "Busy" and pocketstation is receiving data, I can use a disassembler to find all accesses to the LED address and addresses around it are probably related to LCD and communication to PlayStation.
Unlike AVR, pocketstation has a minimal operating system that allows you to see the clock, view save data, and launch any of the games installed. So we don't _just_ flash the entire binary to pocketstation, we need to understand how the OS determines the "entrypoint" of our code. When we're writing for custom hardware like this, we need to be aware of how the device loads our game: which address will it jump to in order to run our program? no$gba has a doc on this.
Pocketstation games are actually normal play station "save data" that happens to contain code and have special headers marking that this is a pocketstation game, icons, and entry point, so flashing is just writing save to it.
Post automatically merged:
---
Coming back to my software stack:
As I mentioned, I had to patch LLVM just a bit so that I can use rust. technically my LLVM patch can also be used for C / C++ for use with clang, just need to be connected.
You don't have to use Rust/LLVM, even arm gcc would work. You just need to make sure the compiler is aware that this is an armv4t architecture, specifically an ARM7TDMI. This is the same CPU that is used by gameboy advanced. Low-level tool such as assembler would work, but don't expect to be able to use sprite editor / music editor, because the peripherals are totally different. I'm making my own HAL layer for now, I can publish it once finished
I'd say my setup is "stable enough" that I can use other poeple's library and it will work. When using rust, I have to use "no-std" libraries. This is similar how in C, you can't just use "stdio.h" or call printf on arduino, because you don't have an OS that implements them.
I use Furnace tracker for making the music, Aseprite for making the sprites as it supports 1bpp BMP format
Post automatically merged:
This is my setup for uploading to pocketstation!
I'm using a definitely modified playstation multitap adapter, open it, and wire it to arduino. Wiring and Program follows memcarduino
It has a python program you could use to "flash" as well as integration using memcardrex.
So... I guess the flow for me is:
rust code -> compile using cargo to produce .elf -> use objcopy to produce .bin with appropriate headers -> use psx-mctool to create a memory card .mcr file -> use memcarduino.py to upload via arduino
Damn nice! Didn't expect so much info, hah. Thanks.
I actually have been learning a tiny bit about PORTs earlier this last year when I made a GB cartridge/ Pokemon save data reader thing. I've been attempting to make my own board for it with an ATMega32A so I can do direct USB serial stuff and your post is kind of inspiring me to pick it back up.
I love Aseprite but wow thanks for mentioning Furnace, I've tinkered with pxtone, milkytracker, sunvox, and lsdj but I suck at all of it, Furnace seems so cool that it emulates actual sound hardware, wow.
Anyway I got sound working. This is made using Furnace as well. I'm totally a noob when it comes to making tracker music, especially fiddling with envelopes.
Post automatically merged:
Okay, I'm going to explain a bit of improvements I'm making.
Up until this point, my project is taking about 5 memory card blocks (which isn't too bad, I've seen games using 14 blocks!) I'm trying to just take the low hanging fruit and switch from compiling to arm to thumb. The result: the game got reduced to only 2 blocks! Very efficient.
There were a couple of issues I need to fix. Some functions need to be in arm mode. (arm exceptions). Of course, I could switch between thumb/arm as I wish for the majority of the code. In Rust, I just need to use #[instruction_set(arm::a32)] for switching to arm (t32 for thumb).
There was a weird crash issue when trying to draw images. The mouse sprites you're seeing is just a bmp image, decoded with tinybmp, but due to some logic of switching images based on frame number, I'm seeing this:
Normally I'd be delighted seeing jump tables, I assume they're super fast!
But wait! Why do I see `ldrb`? I thought I have disabled it in my llvm patch... I think there's another path for lowering jump tables, and maybe it got bypassed. I'm assuming LowerBR_JT in LLVM is the culprit. However I'm exhausted of modifying llvm and would avoid it if possible.
Then I found this linker options I can pass in RUSTFLAGS: `-Cjump-tables=no` which disable jump tables.
Doing so, I got rid of `ldrb` instructions! Now all usages are from static ram (which is totally fine).
I got embassy to be working here with async await. The images shown are separate 3-color sprites (black, white, transparent), and I managed to embed aseprite file in it instead of bmp / gif for flexibility.
I am so happy I found this thread! It is so unbelievably rare to find people interested in this amazing piece of tech and even more rare to find people devving on it! Please don't give up on this project! It is important to preserve and cultivate the knowhow around this old piece of device and its potential -- which I think there is plenty of! I wanna see the day when PocketStation homebrew is more common and tools and methods are easily available and documented -- and you @plasmagrass are doing important job! So please take these words of encouragement and keep on chugging haha!
I've been collecting and playing PocketStations for a few years now, on and off, and all this time I've dreamed about how cool it'd be to be able to make something myself for the system, a small game, app or whatever. Well, that, and also to see english translation of Doko Demo Issyo for the PS1 + PocketStation haha.
And please if you don't already have, make a ko-fi link where people can send you some support on this project! Keep us updated!
It wasn't too long ago we saw our first glimpse of Courage Reborn, another Twilight Princess PC port in the works based on last year's decompilation efforts. With...
After much speculation, Nintendo has finally followed their competitors in announcing price increases for their hardware.
You can find a breakdown of what's changing...
Seemingly out of nowhere a PC port for Pokemon Platinum has surfaced online, bundled alongside the source code for those interested in building and developing it for...
Airing last night with very little in the way of warning, a brand new Nintendo Direct was aired. Running for 15 minutes in total, it took a moment to celebrate the...
Known more widely for their unusual stock price in modern times, GameStop has seen a steady decline as the go-to retail space for US gamers. In what feels like an...
As a part of their Financial Results Briefing for the previous year, Nintendo president Shuntaro Furukawa took to the floor to answer key questions around the Switch...
Earlier this year, Sony announced major price increases for the PS5, PS5 Pro, and PlayStation Portal. Now the company is raising prices again, this time for...
With very little in the way of announcement, Valve has today increased the price of the Steam Deck but some fairly considerable margins. Both of the available models...
We are once again here to tell you about a game leaking before its release, but for once, it's not one published by Nintendo. The game files for Microsoft's upcoming...
Continuing with the great news of Pokémon Platinum getting a native unofficial PC port just a few days ago, today, yet another classic title from the franchise has...
It wasn't too long ago we saw our first glimpse of Courage Reborn, another Twilight Princess PC port in the works based on last year's decompilation efforts. With...
With very little in the way of announcement, Valve has today increased the price of the Steam Deck but some fairly considerable margins. Both of the available models...
After much speculation, Nintendo has finally followed their competitors in announcing price increases for their hardware.
You can find a breakdown of what's changing...
Airing last night with very little in the way of warning, a brand new Nintendo Direct was aired. Running for 15 minutes in total, it took a moment to celebrate the...
Known more widely for their unusual stock price in modern times, GameStop has seen a steady decline as the go-to retail space for US gamers. In what feels like an...
Seemingly out of nowhere a PC port for Pokemon Platinum has surfaced online, bundled alongside the source code for those interested in building and developing it for...
Earlier this year, Sony announced major price increases for the PS5, PS5 Pro, and PlayStation Portal. Now the company is raising prices again, this time for...
As a part of their Financial Results Briefing for the previous year, Nintendo president Shuntaro Furukawa took to the floor to answer key questions around the Switch...
The latest in a growing number of native PC ports, Paper Mario ReCut got its first pre-release build earlier this week. Based on the N64 recompilation toolchain, the...
For the first time in 13 years, the Call of Duty series will again return to Nintendo's consoles. Set to launch on the 23rd of October, the latest release, Modern...