Recent Blogposts of Electronics

Stay in - My 4th Alakajam Entry. A Game Developed for my Recent Portable Game Console Project!

Hey guys! I've developed a game in 48 hours for the game console that I have been working on lately!

The following Youtube video contains all of the info about the gameplay as well as a bit info about how this game were made. Please do watch it! :)

Links: Source code (Github) | Alakajam Entry Page

Game Mechanics

  • It's a platformer game. The character can walk to the left, right, or jump. There's no double jump.
  • There're new platforms keep spawning from the bottom. The platforms move up continuously. The character can stand on the platforms.
  • If the character get out of the screen (e.g. getting pushed to the top by a platform), you lose the game.
  • There're three coins shown on the screen. Taking one would increase your score by one, along with the difficulty of the game being increased.
  • Some platforms are conveyors. They move the character to the left or to the right while the character is standing on the conveyor. The amount of conveyor platforms to be spawned depends on the current difficulty of the game.
  • Avoid hostile entities. Mines move from the bottom to the top. Bullets move from either the left or the right and travel horizontally across the screen. Enemies chase the player. The player has to shoot them down with laser gun before the they get the player. The spawn rate of hostile entities depends on the current difficulty of the game.
  • The speed of the entire game is also affected by the current difficulty of the game.
  • The background of the game has some black dots (i.e. stars) moving upwards. That creates an illusion of the player falling down.
  • Saving highscore to SD card is implemented

Preparation Work

It isn't possible to develop a game within 48 hours without any preparation work. Therefore, I did spend quite a bit of time on preparing it. The preparation includes:

  • Developing Python scripts for packing game ROM and generating graphical image files
  • Developing a tool for creating sound effects
  • Developing a minimalist test game for the game console
    • Writing Makefile for building the game as well as loading the game into the game console
  • Figuring out how to debug the game with GNU DDD
  • ... and of course, getting the second prototype of the game console ready! :P

Self-review of the Game

I wish I could get a third party to review this game. Unfortunately, it isn't possible for anyone else to play this game for now because the game console isn't released yet. So here's my self-review of this game.

As a game developed within 48 hours, I'm rather satisfied with the result. Thanks to my preparation work as well as my previous experience on game jams, I'm able to finish this game on time.

The game is rather challenging and well-balanced. The game is made interesting by having the player keeps try picking up coins. That's the only way to score in the game. For each coin the player had picked up, the game gets a little bit more difficult until the player couldn't hang in there and lose the game. With randomization of various elements in the game, including the spawn rate of conveyors and hostile entities, the position of the objects, etc. The difficulty of the game is partly based on the luck of the player. That makes it a fun game for players with any level of gaming skill.

For the program of the game, it's rather sad that there's quite a bit of code duplication in the game. That's partly because I'm on a memory-constrained system. Another reason is that there's a time limit for the jam. Anyway, the game jam had ended. I'm not going to fix that. :P

Significance

This game demonstrates that the game console I'm developing is capable for running games! It shows the system API of the game console is good enough for game development. Therefore, I am able to move forward to further develop this game console. In the future, this game console might become an alternative to PICO8 for game jams participants.

Update of the Game Console Project since the Last Blogpost

I'm sorry that I didn't update about the progress of the development of the game console for a while. A lot of progress were being made lately!

Project Update: Second Prototype

The second prototype of the game console is ready! It's soldered on a perfboard. There're a few upgrades made in this new prototype. Here's the specs of the second prototype:

  • STM32F030K6T6 microcontroller with 32kB of flash, 4kB of RAM. (Upgraded from STM32F030F4P6)
    • 12kB of flash and 512B of RAM is taken by the bootloader
  • ST7565R 128x64 monochrome LCD (Upgraded from Nokia 5110 48x84 LCD)
  • Buzzer and audio jack (The old version doesn't have an audio jack)
    • Buzzer and audio jack output switchable by a slider switch (New in second prototype)
    • Volume adjustable by a knob (New in second prototype)
  • 6 buttons, including left, right, up, down, button 1 and button 2
  • SD card slot for storing game ROMs and save data
  • 2-slot AA battery holder (New in second prototype)
  • SWD programming and debugging header pins
  • 12-pin GPIO (New in second prototype)
    • 3 of the pins are shared with master mode SPI bus for communicating with the LCD screen and SD card

The PCB to be designed will be based on this second prototype.

Project Update: Concern of Flash Wearing

Despite that I had professional experience in embedded programming, I'm rather green. Therefore, I've discussed with some veterans in embedded programming about the design of this game console. Since this game console works by loading the game from SD card to the internal flash of the microcontroller by self-flashing, they immediately pointed out that performing frequent self-flashing would wear off the flash of the game console quickly.

I've checked the datasheet of STM32F030K6T6. The guaranteed number of flash write cycles is merely 1000 cycles. That's a little bit small and it will cause problem to our game console. Interestingly, the 1000 number of write cycles is "Guaranteed by design, not tested in production". For other microcontrollers produced by STMicroelectronics, the number of write cycles is often 10000 and it's "Guaranteed based on test during characterization".

It just doesn't make much sense to have this little number of write cycles. Here're some theories I have about the number:

  • STM32F030 is a low-end microcontroller. So STMicroelectronics doesn't bother to test for the for the write cycles at all. Therefore, STMicroelectronics could be being very conservative about the rating of the number of write cycles
  • The 1000 write cycles is for the full rated temperature range of operation. It should be far higher at room temperature
  • It's marketing. They intentionally make STM32F030 looks weaker than other microcontrollers to boost the sales of their higher-end microcontrollers. It not very profitable for them to sell low-end microcontrollers like STM32F030.

In long run, maybe I should do my own research on figuring out the actual number of flash write cycles of the microcontroller I'm using. I refuse to believe that the write cycle is 1000 cycles in room temperature. If the write cycle is like 3000 cycles, it's kinda acceptable because that'd mean that the user can load 10 games every single day for 300 days until the game console breaks. And I doubt that there's such an enthusiastic player of this game console anyway. But 1000 cycles is really a bit too little.

To reduce the number of flash write cycles, I've modified the bootloader firmware so that it only perform flash erase and rewrite if the source ROM on SD card is different from the previousy self-flashed game inside the microcontroller flash. That would cause self-flashing not to be performed if the same game is launched again after a reboot. This should help reducing the flash writes by quite a bit, especially if the player is repeatedly playing the same game over and over again.

Project Update: Change of Game Flash and RAM Offset

Another issue that those professionals pointed out was the Game Flash and RAM Offset. They raised an interesting idea about the offset of Flash and RAM.

In the past, I designed the Flash and RAM layout like this:

  • Start of Flash | [Bootloader Flash][Game Flash] | End of Flash
  • Start of RAM | [Bootloader RAM][Game RAM] | End of RAM

There's a huge problem with this design. For the flash, if I ever update the firmware and the size of the firmware got increased, that would cause the offset of the Game Flash to be changed. That'd require the game to be rebuilt to work on the newer version of the game console. The same issue goes for the RAM.

Therefore, I've modified the layout. Now it looks like this:

  • Start of Flash | [Bootloader Header Flash, first sector][Game Flash][Bootloader Main Flash] | End of Flash
  • Start of RAM | [Game RAM][Bootloader RAM] | End of RAM

For the Game RAM, I put the bootloader-exclusive RAM at the end. This design allows the bootloader RAM to expand without changing the origin offset of the Game RAM. In addition, if I ever upgrade to a microcontroller with more RAM, the entire RAM space would be expanded. And I would push the Bootloader RAM to the end of the RAM, and the Game RAM space would also be expanded. Since the origin of the Game RAM remains unchanged, I can still run the game that's built for the pre-upgrade version of the game console.

The same story goes for the flash. However, it's a bit more tricky because I need the bootloader to take the first sector of the Flash. That's because the first sector contains the interrupt vector and boot-related stuffs. I have to take the first sector so that the bootloader would be loaded on power up instead of the previously flashed game. Other than the first sector, the remaining part of the bootloader is put at the end of the flash space. That brings us the same advantage of putting the bootloader-exclusive RAM to the end.

Project Update: Removal of EXTIF

This thing was done long time ago. But I've been too busy to blog about it. In the past, we made something called EXTIF to allow the game to call the functions located in bootloader by using a software interrupt, just like how BIOS work. It turns out that this design is utterly dumb because there's a function calling convention for ARM. It's called Procedure Call Standard for the ARM Architecture(AAPCS). As it's the go-to standard for functions compiled for ARM microcontrollers, it's possible to call any functions compiled by any compiler with any amount of parameters as long as you have the address of the functions.

For this reason, I just made a veneer on a fixed address for each of the system API functions. The veneer redirects the function call to the actual address of the function inside the bootloader. To call the system API function, the game declares all of the system API functions available in the bootloader and assign those fixed addresses of the veneer to the function declarations. With GCC, it's possible to map a function to an address by using the --just-symbols parameter when you invoke the linker.

Project Update: Availability of Low-Level Interrupts to the Game ROM

The latest bootloader firmware is able to redirect almost all of the interrupts of the microcontroller to the Game ROM. A veneer interrupt handler were used for calling the interrupt handlers in the Game ROM. That enables the game developers of this game console to perform low-level programming. Along with the high level API inside the bootloader, this game console allows its game developers to learn about both higher level programming and low-level programming.

Moving Forward

Here I've managed to complete the 4th Alakajam by developing a game for my game console. :)

Now that the firmware of the game console is ready. The next step of the development of this game console project is to design the PCB. After that, perhaps I'll also draw a case for the game console. If I have the time, perhaps I'd also develop an emulator for it. Since the microcontroller behind this game console is an ARM one, it should be possible to modify a Gameboy emulator for running games developed for this game console.

Just as I planned, I expect this project should be completed some time in 2019. Maybe the emulator would be available in 2020 if I end up working on one.

That's it for this blogpost. I'll update you guys soon! :)

Shameless Plug - I'm Currently Looking for Jobs!

As of 15th Oct 2018, I'm currently in between jobs. I was an embedded programmer of thermostats with a bit more than a year of experience. I did firmware development, Python automation scripts as well as tools for internal use, including setting up MQTT server and web server on a Raspberry Pi. In addition of that, I have been a hobbyist programmer for 9 years since I was back in middle school. Currently I plan to learn further about Python and modern web development technologies, mainly the back-end and devops ones. Then I'll start actively looking for another job. I prefer to go for a remote-working job. It can be full-time, part-time or freelancing.

If you're looking for someone to fill in any sort of programming-related positions, feel free to contact me via "hire dot me at sadale dot net". Alternatively, you can look for me on Freenode for having informal conversation with me. My nickname is "Sadale" there. Keep in mind that you have to identify (i.e. login) on Freenode in order to PM me. That's a new policy of Freenode to deal with the recent IRC spambots.


Portable Game Console Project - Clock Control and Power Saving

As I've mentioned in my previous blogpost, the clock control mechanism were implemented. But I've been too busy to blog about that. Now I've got a moment to do a write-up of the game console project about that.

Clock Control System Design Rationale

The microcontroller STM32F030F4P6 is capable for running at 48Mhz. The 48Mhz system clock is generated from 8Mhz HSI clock. It's multiplied with PLL by up to 12 to generate a system clock of up to 48Mhz.

Reducing Power Consumption

The game console will be battery-powered. Power consumption is critical to the battery life. To minimize the power consumption, we allow the game to enter sleep mode. With frame limiting, it's highly probable for the game to have some idle time before it is required to process the next frame. During that period of time, the game would enter sleep more to save power. Of course we'd disable the PLL in sleep mode to further reduce the power consumption. And that'd cause the system clock to be limited to 8Mhz when it's sleeping.

Sound Distortion Problem: Modification of Timer Prescaler

And here's a problem. There're many clocks derived from the system clock in the microcontroller. Most of them aren't very time-critical. However, for sound generation, timing is very important. Otherwise the sound generated would be distorted.

There're two causes of sound distortion. One is the selection of using, or not using PLL. Another is modification of timer prescaler. It's empirically determined that the sound distortion caused by toggling the state of usage of PLL is negligible. However, changing the timer prescaler does affect the sound quality for quite a bit.

The Compromise between Sound Quality and Performance

Since changing the timer prescaler would affect the sound quality, we'd want to avoid that. Unfortunately, if we want to preserve the value of the timer prescaler, and if we still want to enter sleep mode to save power, the maximum possible clock would be limited 32Mhz instead of 48Mhz.

Here's the reason behind it: The timer clock can be calculated with this (overly simplified) equation:

(timer clock) = (system clock) / (APB1 prescaler [including the x2 mechanism]) / (timer prescaler + 1)

Timer clock has to be a multiple of 8. APB1 prescaler has to be power of two. Timer prescaler can be any integer.

Since we need to enter sleep mode, we have to set the system clock to 8Mhz at some point of the program. Let's assume that we set the APB1 prescaler=1 and timer prescaler=3 at 8Mhz. So (timer clock) = 8Mhz / 1 / (3+1) = 2Mhz.

If we want to maintain the same timer clock without changing the timer prescale at 32Mhz, we could easily do that by just changing the APB1 prescaler to 4. So we get (timer clock) = 32Mhz / 4 / (3+1) = 2Mhz, that's still 2Mhz. Therefore, the sound quality would be good at 32Mhz.

However, things get tricky if we want to maintain the same timer clock as 8Mhz at 48Mhz. If we aren't changing the timer prescaler, we'd need to pick APB1 prescaler=6. However, APB1 prescaler has to be a power of two. So it has to be rounded to APB1 prescaler=8. Then we'd be forced to change the timer prescaler from 3 to 2 in order to maintain the timer clock. So we get (timer clock) = 48Mhz / 8 / (2+1) = 2Mhz. However, the switching of timer prescaler will cause noticable sound distortion as shown below:

32Mhz without frame limiting, no sound distortion:

32Mhz with frame limiting, negligible sound distortion:

48Mhz without frame limiting, no sound distortion:

48Mhz with frame limiting, noticable sound distortion:

Solution: Let the developer decide

Our solution is to let the game itself decide to use 32Mhz or 48Mhz clock. If the game prefers higher sound quality, then it should stick with 32Mhz. If it wants performance, it should choose 48Mhz. If the game has sound, it's recommended to use 32Mhz as possible and switch to 48Mhz only when doing computation-intensive stuffs.

I've made a minimalist breakout game. At clock rate of 32Mhz without frame-limiting, it can achieve 40fps with around 80 bricks, and 250fps with no bricks. The recommended fps is 8~10fps because of the hardware limitation of the update rate of LCD. Since the max fps is much higher than the target fps, the game console can save quite a lot of power by doing the frame-limiting to 8~10fps with the use of sleep mode. :)

Clock Control System Design and API Calls Listing

It's very simple. There's just one API call that modifies the clock system. That's:

  • void systemSleep(unsigned int milliseconds, bool wakeUpToBoostMode);

If wakeUpToBoostMode is true, it'd enter 48Mhz mode after the end of the sleep. Otherwise that'd be 32Mhz.

And it has a few other API calls for querying the status of the clock system:

  • unsigned int systemGetTick(void);
  • bool systemIsBoostMode(void);

Typical Usage of Clock Control System

Clock Control System is mainly used for frame-limiting and saving power. In the game, there's a main loop. The game should call systemSleep() after the end of processing of each frame.

Here's the typical usage inside the main loop:

  1. Get the current tick in millisecond by using systemGetTick(). Save it to the variable previousTick.
  2. Process the game frame and draw the stuffs onto the graphical buffer
  3. Calls systemSleep() with the sleep duration being (1000/fps)-(systemGetTick()-previousTick). If the sleep duration calculated is negative, then do not sleep.

Other Progress to be Blogged in the Future

The following features were implemented. But I'm saving them for the coming blogpost:

  • Removal of EXTIF in favor of using existing function calling convention
  • Sample game (minimalist breakout game)

What's next?

I'll be working on the following stuffs:

  • Tweaking the storage API by adding "offset" parameter to it
  • Second prototype of the Game Console, with microcontroller upgrade, LCD upgrade and GPIO!

Stay tuned. I'll make another blogpost soon! :)


Portable Game Console Project - Graphic System Implemented!

The graphic system of the portable game console project is implemented! In addition, the clock control and power saving stuffs are also implemented. This blog post focus on Graphic System. I'll save other stuffs for my next blog post.

Graphic System Design

  • 84*48 monochrome. Each byte is 8 pixel grouped vertically.
  • Capability of drawing bitmap, rectangle and text
    • Rectangle: Supports both filled rectangle and hollow rectangle
    • Text: Supports specification of width, height and word wrap
  • Drawing mechanism:
    1. First, The background bounding rectangle is drawn.
    2. Then, the item itself is drawn
      • When they're being drawn, four modes can be selected. They are:
        • NONE: pixel = originalPixelGroup
        • AND NOT: pixel = originalPixelGroup&(~newPixelGroup)
        • OR: pixel = originalPixelGroup|newPixelGroup
        • XOR: pixel = originalPixeGroupl^newPixelGroup
      • Example: Drawing opaque item would be (BACKGROUND_AND_NOT|FOREGROUND_OR)
      • Example: Drawing transparent item would be (BACKGROUND_NONE|FOREGROUND_OR)
  • DMA is used for transferring the image from buffer to the LCD display via SPI
    • This allows the transfer operation to be done while the microcontroller is in sleep mode, saving power consumption.
  • Double buffering support
    • However, each buffer takes 504 bytes. With a total of 4kB of RAM, it may not be wise to do double-buffering

API Calls Listing

  • extern void graphicClearDisplay(bool inverted);
  • extern void graphicDrawText(const struct GraphicText *text, int8_t x, int8_t y, uint8_t mode);
  • extern void graphicDrawImage(const struct GraphicImage *image, int8_t x, int8_t y, uint8_t mode); //Can also draw rectangle with a special pointer address
  • extern void graphicLoadImage(const void buf, struct GraphicImage image); //converts binary from game console bitmap format to the struct GraphicImage
  • extern void graphicSetDrawBuffer(void *buf);
  • extern void graphicSetDisplayMode(enum GraphicDisplayMode displayMode); //Set inverted display and stuffs
  • extern bool graphicDisplay(const void *buf);
  • extern bool graphicIsDisplayReady(void);
  • Structs:
    • GraphicImage: void *image; uint8_t width; uint8_t height;
    • GraphicText: char *text; uint16_t length; uint8_t width; uint8_t height;

Typical Procedure of Using Graphic System

Double buffering isn't used in this procedure:

  1. Wait for the completion of the previous DMA transfer process. Function: graphicIsDisplayReady()
  2. Set a graphical buffer to have stuffs to be drawn to. Function: graphicDrawText(), graphicDrawImage()
  3. Clear the buffer to remove previously drawn image
  4. Draw stuffs to the buffer
  5. Start the DMA transfer process to transfer the buffer to the LCD. Then optionally enter sleep mode to save power
    • Do not modify the buffer until the transfer is completed

Updated sample program

I've modified the sample program that I made for testing storage system, input system and synth system. Now that it's also capable for testing the new graphic system!

The updated sample program draws a lot of stuffs on the LCD, including a background image loaded from a resource in the ROM, a square, and system information like system tick, clock frequency and sleep mode.

In the updated sample program, the drawing mode of the square can be configured. Here's a few photo comparing some of the available the drawing modes:

Four photos showing the comparison of drawing modes

Implemented but Removed Features

Due to flash space constraint, some graphical features were removed. They include ellipse, triangle, line and dot drawing support. :(

We have 16kB of flash in total. Currently the bootloader and library takes 10kB, which is taking more space than the originally planned 8kB. It'd be even worse if these features were enabled. :(

Fortunately, it's still possible to draw those shapes by implementing the drawing function in the game itself.

Other Progress to be Blogged in the Future

I've already implemented the following features. However, these features deserve another blog post. I'll blog about them later.

  • Clock control and sleep mode
  • Removal of EXTIF in favor of using existing function calling convention

What's next?

I'll be working on the following stuffs:

  • Clean up on the code
  • Sound System: Check if additive synth is possible
  • Game selection menu
  • LCD contrast adjustment
  • Find out how to put code on the RAM.

That's it for now. I'll update you guys for any progress! :)

And I guess I'm going to take a short break from this project. I'm going to work on a piece of music!


Portable Game Console Project - Storage System Implemented!

Hey guys! The storage system of my portable game console is implemented!

Storage System Design

  • Each game is a ROM file stored in SD card
  • File system support: FAT16 and FAT32
  • FAT library used: Petit FAT File System Module
  • Up to 256 resources can be packed into a ROM
    • A python script were developed for packing the resources
  • Each resource can be flashed to the microcontroller
    • The first one is flashed upon the game is loaded
  • Each resource can be read by the game
    • It can be used to store static assets (like map of a level in the game)
  • Each resource can be rewritten by the game
    • Can be used for implementing game-saving feature
    • Yes, the save content is saved right inside the ROM.
  • Each resource is aligned to 512 bytes.
  • It isn't possible to seek to an offset of any resources. You have to read/write everything in the resource at once.

Why is the game save content located right inside the ROM?

It seems to be dumb to save the save content right inside the ROM file. I don't really have a choice because of the limitation of the FAT library. :(

My original idea was to have two files. One is the ROM file. Another is the save file. The save file is dynamically created by the game by using the API Calls.

However, when I tried to create the file by using the function pf_write() of the FAT library, it didn't work. Then I looked into the documentation and found out this restriction:

  • "Cannot create file. Only existing file can be written."

Alright. Then I had come up with three ideas. They're:

  • Use another FAT library
  • Pre-create a save file on the SD card
  • Write the save content right inside the ROM

Using another FAT library takes a lot of work. Pre-creating a save file on the SD card would bring quite a bit of nuisance to the player because the player would have to manually paste the save file to the SD card in addition of the ROM file. So it's apparently to me that storing the save file content right inside the ROM would be the solution to go.

Resource Alignment

Since the restriction of the file-writing function is "Cannot create file. Only existing file can be written", everything should be working after saving the content right inside the ROM, right?

It turned out that I was mistaken. When I tried writing something on the SD card, the offset of the content being written was wrong! And some of the content were corrupted by zeros. Why?

After reading the documentation again, I've found out why. The library has a function for reading from file, and another function for writing to file. The function for reading works with any file offset. However, the function for writing only works properly if the seek offset of the file is aligned to 512 bytes. Otherwise, the offset would be rounded down to the closest 512-bytes. In addition of that, the number of bytes written are padded with zeros to 512 bytes.

The solution? Simple! Just align the offset of each resources to 512 bytes and pad them.

ROM File Format

Here's the file format of the ROM of the game console.

Offset      Size    Description
0           3       Magic number
3           1       ROM file format version
4           32      Name of the game. NULL-terminated.
36          32      Author of the game. NULL-terminated.
68          256     Game Description. NULL-terminated.
324         700     Reserved
1024+8*N    4       Resource offset in file. N is from 0~255.
1024+8*N+4  4       Resource length. N is from 0~255. Can be zero.
3072        varies  The content of the resources. Each resources are aligned to 512 bytes.

ROM-packing Python Script

The python script takes a CSV input file and generate the ROM. The CSV file looks like this:

NAME,Name of the game goes here
AUTHOR,Sadale.net
DESCRIPTION,Description of the game goes here
0,assets/game.bin
1,assets/level1.map
2,assets/level2.map
128,assets/titleScreen.graphic
254,[1024]

It's rather obvious what does the CSV do except the last line. For the resource #254, it allocates a resource that contains 1024 bytes of zero. It's useful for allocating space for save file of the game.

API Calls

It's simple. There're only three functions.

  • void storageFlash(uint8_t resNum);
  • uint16_t storageRead(uint8_t resNum, void *buf, uint16_t len);
  • uint16_t storageWrite(uint8_t resNum, void *buf, uint16_t len);

It's rather intuitive. I guess I don't need to explain it here. :P

Sample program

To demonstrate that the storage system is working, I've developed a test program. The program is capable for playing three kind of sound with configurable duty cycle. The function of the buttons are shown below:

  • Up: Reduce duty cycle
  • Down: Increase duty cycle
  • Left: Select the previous sound
  • Right: Select the next sound
  • Button A: Play the sound
  • Button B: Save the sound

Click here and see how does the code of the program looks like. Please notice that this isn't a complete project and it's not buildable without extif.h, which I'm not releasing until the completion of the software.

After saving the content of the file, here's how does the content of the ROM looks like:

Content of ROM showing the bytes 0x55, 0x01 and 0x01

As you can see above, the first byte 0x55 is the magic number. It's used for informing the program that the save data is valid.

The second one is the sound ID. The value of 0x01 refers to the second sound.

The third one is duty cycle. A value of 0x01 refers to 50% duty cycle.

After relaunching the program, the sound and the duty cycle will be loaded. I've tested it. The sound was played correctly after I pressed the Button A. :)

What's next?

I'll be working on the following stuffs:

  • Graphic system
  • Power-saving, clock rate adjustment, etc.
  • Menu for game selection and stuffs

That's it for now. I'll update you guys for any progress! :)


Announcing the Commencement of Portable Game Console Project

Photo of the portable game console

If you know me personally, you'd probably heard of this portable game console. I have been working on this project for almost two months! I decided not to blog about this until now because I wasn't confident about the completion of this project. My main concern was the hardware design of my portable game console isn't going to work at all. Now that I've tested and proven that it's possible to do whatever I need to for this project. Therefore, it's now the right time to announce this project in this blog! :)

Introducing Portable Game Console Project! It is a minimalist, low-cost portable game console. It isn't an emulator of existing game console. It's a brand new game console system. It doesn't have a name yet. Until it's getting named, I'll just call it portable game console.

Motivation

It's mostly a "because I can" project. I aim to learn stuffs from this project, including drawing PCB board, electronic design, a little bit mechanic design by drawing the case of the console, embedded programming, etc.

This is an open source project. There're already many existing game consoles. I don't think anyone would be interested in getting this one. Commercialization is out of the question. Instead, upon the completion of this project, I plan to do free giveaway of this console (and hopefully someone would be interested in developing games for this console). Waste all of the time! Lose all of the money! Hooray! :P

Feature

  • Open source (after the completion of this project)
  • Low-cost
  • Minimalist - single color LCD, 1-bit audio.
  • Open design - Anyone can build their own. The software will be made open source
  • Supports loading games from SD card
  • Cross platform compatibility of games - the API will be ported so that PC, and hopefully web browsers can run the games developed for this console

Specs

  • Microcontroller: STM32F030F4P6
  • Graphical interface: Nokia 5110 LCD. 48x84 monochrome
  • Audio playback device: Buzzer
  • Input: 6 push buttons. Left, right, up, down, A and B.
  • microSD card slot (may be replaced with SD card slot in the future. I haven't decided yet)

Development Flow

  1. [Done]Hardware design
  2. [Done]Purchase prototyping material
  3. [Done]Solder a prototype of the circuit on a perfboard
  4. [WIP]Developing the API
  5. Developing Games, mainly for testing the API
  6. Drawing PCB
  7. Making the container case for the game console. Probably by 3D printing
  8. Port the API to PC platforms and possibly browser (Optional)
  9. Small scale production and giveaway

Hardware Wiring

I haven't drawn a schematic diagram. So I'd just write down the wiring of STM32F030F4P6 to other hardware here. It's enough for anyone to reconstruct the circuit

  • BOOT0 -> GND
  • PF0 -> Button Up -> GND
  • PF1 -> Button Left -> GND
  • NRST -> Vcc
  • V -> Vcc
  • PA0 -> Button Right -> GND
  • PA1 -> Button Down -> GND
  • PA2 -> Button A -> GND
  • PA3 -> Button B -> GND
  • PA4 -> Current-limiting resistor -> Buzzer -> GND
  • PA5(SPI1_SCK) -> LCD_SCK and SD_SCK
  • PA6(SPI1_MISO) -> SD_MISO
  • PA7(SPI1_MOSI) -> LCD_Din and SD_MOSI
  • PB1 -> LCD_CE and -parallely-> transistor to invert the signal -> SD_SS
  • VSS -> GND
  • V -> Vcc
  • PA9 -> LCD_DC
  • PA10 -> LCD_RES
  • PA13(SYS_SWDIO) -> JTAG for programming and debugging
  • PA13(SYS_SWCLK) -> JTAG for programming and debugging

Here's how the connection looks like on the prototype hardware:

Photo of the back of the portable game console, showing the soldering trace on perfboard

Software Development Environment

I've never used most of these tools and software before this project.

  • Compiler: gcc ARM cross-compiler
  • IDE: SW4STM32
  • Code template generator: STM32CubeMX
  • Library: STM32F0 LL driver (uses less Flash memory compared with HAL driver)
  • USB-to-JTAG adapter
  • Hardware prototype of the portable game console

OS Design

STM32F030F4P6 is the microcontroller of this portable game console. It's a low-end ARM Cortex-M0 microcontroller.

It has 4kB of RAM and 16kB of Flash. In our design, the flash memory is split into two parts. The first part is the bootloader. The second part is the application (game). The bootloader contains the implementation of the API for accessing the LCD, sound system, buttons and SD card. The game is a playable that calls those API functions to access the hardware function of the game console.

The RAM is also split into two parts. A small part is exclusively used by the API. The other part is available for the application.

In addition to that, the bootloader will show a menu for the user to pick a game stored in the SD card. When the game is chosen, self-flashing will be performed to the application part of the flash memory. Then the BX instruction will be called to set the program counter to the starting point of the application, which will caused the game to be launched. There's, unfortunately, no protection of access. Although there's no API for that, it's technically possible for the application to, says, implement their own function to erase the SD card.

The bootloader will probably take 8kB of flash and very small amount of RAM (like 100 bytes).

Implemented API Calls

The API call can be made by using a software interrupt. There's a fixed-address, 8-bytes storage inside the RAM. The API can be accessed by the application by first manipulating the content of the RAM of that address, then trigger a software interrupt. The interrupt EXTI line 15 is used for software interrupt. This design is inspired by int 80h of unix kernel.

Keys Input System

Three functions are available:

  • void keysSetDebouncePeriod(uint8_t milliseconds);
  • uint8_t keysGetPressedState();
  • uint8_t keysGetJustChangedState();

keysSetDebouncePeriod() is used for setting the software debounce period. The longer it is, the slower the reaction time, and less noisy the response is.

For keysGetPressedState() and keysGetJustChangedState(), each bit represent the state of a button.

For keysGetPressedState(), pressed button would have its bit set to 1. The non-pressed one would be 0.

For keysGetJustChangedState(), pressed button that had changed would have its bit set to 1. Otherwise 0. For example, if the keysGetJustChangedState() of a button is 1, and the keysGetPressedState() is 0, that implies that the button was just released. After calling this function, the justChanged state of all buttons would reset to zero.

Sound Synth System

The sound system consists of two identical 1-bit synthesizer. One of them is for sound effect (foreground), another of them is for background music. Only one of them can output sound at one time. Depending on the configuration, it's possible to pause the background music while the sound effect is being played, or have the sound effect played during its effective duration, replacing the part of background music.

The sound synth has the following capability:

  • Playback duration (10 millisecond per step, range: 0-2.55 seconds)
  • Frequency (32-suboctave frequency scale)
    • Frequency sweep control: shape: saw, triangle, bipolar, noise; repeat: yes/no; fast sweep: 1x/256x
  • Duty cycle (Possible value: (1-1/(n^2)) * 100%. n is a value between 0 and 15. Example: n=2 would be 75%)
    • Duty sweep control: shape: saw, triangle, bipolar, noise; repeat: yes/no
  • Fast sweep mode: reduces the duration of each sweep step to 1/4 of both frequency and duty cycle.

In addition, a musical playback format is defined for this game console, which enables the application to play music by calling an API function.

Sound Synth System: Frequency

Piano uses a 12-suboctave scale, which means that each octave is divided by 12 notes.

However, in this game console, a 32-suboctave frequency scale is used instead. It means that each octave is divided by 32 notes. This game console is able to generate more fine frequency compared with piano, mainly useful for sounds effects. Since 32 isn't divisible by 12, some of the piano notes has to be substituted with a close enough frequency. Fortunately, this is not a problem because "Humans can notice a difference in pitch of about 5 to 6 cents" according to a paper by Beatus Dominik Loeffler. For a 32-suboctave system, the maximum error compared with piano note would be 64-suboctave, which is 1/64*100=1.5625 cent of error, which is far less than 5~6 cents. I've also personally listened to a piece of music played on the game console. It sounds good. So the frequency error isn't a problem. :)

During frequency sweeping, the 32-suboctave is further subdivided into 16 frequencies, forming 512-suboctaves. Each step of sweep would increase/decrease the frequency by a 512-suboctave until the final frequency is reached.

The frequency of the sound to be played is 2^(5+n/32) Hz, where n can be any value from 0 to 255.

Sound Synth System: Duty Cycle

Generation of tone requires vibration of audio signal. For a 1-bit synth, the duty cycle determines the duration of the signal to be LOW or HIGH. Let's take a 1000Hz sound wave as an example. It takes 1ms to complete a period. If the duty cycle is 50%, 0.5ms would be spent on LOW, and another 0.5ms would be spent on HIGH. If the duty cycle is 75%, the signal would be LOW for 0.25ms, and HIGH for 0.75ms.

Although this synth is 1-bit synth, it's possible to control the volume of the synth by using duty cycle. That's because the duty cycle affects the RMS value of the waveform. The closer the duty cycle to 50%, the louder the sound is.

The duty cycle can be adjusted (1-1/(n^2)) * 100%, where n can be any value from 0 to 15.

Unlike frequency, the duty cycle sweep does not have any subdivide mechanism.

Sound Synth System: Music Format

The format is simple. It's a mix of command and data. Data starts with a non-zero byte. Command starts with a byte of zero. It has 6 modes. They are STOP, DF, DCF, FULL, FULL->DF, FULL->DCF.

  • STOP: end of music. No more command to be accepted.
  • DF: Two bytes. duration, frequency. Use the previous values for other parameters
  • DCF: Three bytes. duration, initial frequency, final frequency. Use the previous values for other parameters
  • FULL: Seven bytes. duration, init freq, final freq, freq sweep period, init duty cycle(half byte), final duty cycle(half byte), duty sweep period, sweep control flags
  • FULL->DF: Same as FULL. However, after playing this FULL musical note, it'll get back to DF mode
  • FULL->DCF: Same as FULL. However, after playing this FULL musical note, it'll get back to DCF mode

To switch mode, set the first byte (duration) to 0, then set the second byte to the command ID. The command ID for STOP is 0, DF is 1 and so on. Here's an example of a piece of music:

uint8_t musicArray[] = {
0, 4,  //Switch to FULL->DF mode
20, 97, 97, 0, 0x11, 0, SYNTH_FREQ_SWEEP_TRIANGLE|SYNTH_DUTY_SWEEP_REPEAT_ENABLE, //Play a sound with full parameters specification and switch to DF mode
20,102, //Now that we're in DF (duration+frequency) mode. We play a sound in DF mode
65,105, //Play another sound in DF mode
20,97, //ditto
20,105, //ditto
45,102, //ditto
100,97, //ditto
0, 0 //Switch to STOP mode. It's like a NULL character for string.
};

Sound Synth System: API Functions

The following functions are available:

  • void synthPlayOne(bool foreground, struct SynthData synthData);
  • void synthPlayCommand(bool foreground, uint8_t *command);
  • void synthSetFgFreezeBg(bool freeze);
  • uint8_t synthGetStatus();

Sound Synth System: Demonstration

Here's a piece of recorded music generated with the game console by using the music format:

Unimplemented APIs:

  • [Research performed, but not implemented]SD card interfacing system
  • [Research performed, but not implemented]Graphical system
  • Misc (e.g. changing the clock rate, sleep, etc)

Future

I'll continue be developing this game console. It'll probably be completed some time in 2019. I'll update you guys about any progress! :)