Jump to content

DPC+ARM - Part 10, Score & Timer display

Posted by SpiceWare, in DPC+ ARM Development 25 April 2015 · 618 views

NOTE: This is an advanced Atari 2600 programming series. It will not cover basic things like triggering a Vertical Sync, what a Kernel is, why a timer is used in Vertical Blank, etc. If you need to learn that, check out with the following:
  • Collect - detailed development of a 2K game
  • 2600 Programming for Newbies - use the Sorted Table of Contents topic that's pinned at the top in order to easily access the tutorial topics in order.
Score & Timer display

If you've followed any of my development blogs before, you've probably noticed that I like to implement routines for displaying the score early on in the project - you can see that in these early builds of Collect, Frantic, Medieval Mayhem, and Space Rocks. Even though I'm not ready to show the player's score, the display is very useful for showing diagnostic information. In this build of Frantic where I used the score to display the results of the software sprite collision1 routines.

One of the challenges when developing DPC+ARM code is that Stella does not emulate how long ARM code takes to run. As far as it's concerned, all ARM code will finish executing in 0 cycles of 6507 time. Because of this it's very easy to write something that will run just fine in emulation, but will cause screen jitters or even a fatal crash when run on a real Atari. We're already checking timers in our 6507 code, so we can easily save those values and display them in the score.

For the game we need to display two scores and a timer. In the 2K version of Collect I used the playfield to show that information. For Collect 2 we're going to do things a different way:
  • players are set to 3 copies with medium spacing
  • players are positioned so the middle copies occupy the middle 16 pixels of the display
  • playfield pixels are turned on behind all copies of the players
  • just like the ShowTwoColorGraphic routine used to display the menu, the players are colored black and used as a mask to show the colored playfield behind it
  • playfield color is changed on the fly so each group of 16 pixels has a different color
  • use a 3 pixel font, and 1 pixel spacing, so we can display 4 digit scores for each player
6507 Revision, Time Remaining

NOTE: As the 6507 changes are basic stuff, I won't be showing them in this blog entry. I will point out what's changed though, so be sure to review collect2.asm.

In order to display the time remaining for Vertical Blank and Overscan, the 6507 routines have been updated to save the values and pass them over to the ARM routines.
  • Allocated storage in Zero Page RAM for TimeLeftOS and TimeLeftVB
  • Modified the routines in bank 5 to save the time left in INTIM at the end of OverScan and VerticalBlank processing
  • Allocates storage in DS_ToARM (in Display Data) for ARMvbtime and ARMostime
  • Modified CallArmCode subroutine to save the time remaining values in Display Data
  • Added echoed #define statements for ARMvbtime and ARMostime
6507 Revision, Score & Timer
  • The rainbow kernel was decreased in height to make space for the score & timer dipslay
  • New ScoreDisplay kernel was added after the rainbow kernel
  • First few scanlines of ScoreDisplay prep TIA and DPC+ datastreams
  • ScoreLoop displays 2 scores and the timer
  • 7 new datastreams were added in Display Data. Note that these datastreams were positioned to be on the same page2 in order to save setup time in the ScoreDisplay kernel.
    - LeftScoreA
    - LeftScoreB
    - TimerA
    - TimerB
    - RightScoreA
    - RightScoreB
    - ScoreTimerColors
  • Font graphics have been added. They consist of the digits 0-9, A-F (for hex values) and a few special characters such as a colon (used for the timer display).
  • a Score and Timer block of echo statements has been added
The A datastreams are displayed in player 0 and the B datastreams in player 1. The datastreams were pre-populated with an alternating line pattern in order to test the timing of the updates. This revealed a minor issue:

score timer test pattern
Attached Image

The timing is off by a single pixel, resulting in the leftmost pixel of TimerB to also show up as the leftmost pixel of RightScoreB. We can easily work around this by designing the font to not use the leftmost pixel.

The echo statement for the FONT is slightly different than the others:
echo "#define FONT                  ", [[Font & $fff] + $4000]d
The ARM needs the value to be in relation to the cartridge as a whole. The value that DASM generates is as the 6507 sees it - an address within the 4K cartridge's addressing space. To convert the value for the ARM we need to strip off the leftmost hex digit of the address and replace it with a $4 because Font is located in 6507's bank 4. If Font is relocated to another bank, we need to remember to update the echo statement.

C Revision, PrepScoreDatastream()

A new function has been added to prepare the datastreams for the new Score Display kernel. Since we're using a small font, each datastream will end up holding the graphics to show 2 digits. MergeCharacters() was written to combine the characters for a given datastream.
void MergeCharacters(int datastream, int left_character, int right_character)
    int i;
    unsigned char *stream = queue + datastream;
    unsigned char *left = flashdata + FONT + left_character * 7;
    unsigned char *right = flashdata + FONT + right_character * 7;
    for (i=0;i<7;i++)
        *stream++ = ((*left++) & 0xf0) + ((*right++) & 0x0f);
The character values 0-15 correspond to 0-9 and A-F so that hex values can be shown using simple masking and/or bit shifting:
        MergeCharacters(LEFT_SCORE_A,   (gScore[0] >> 12) & 0x0f,   (gScore[0] >> 8) & 0x0f);
        MergeCharacters(LEFT_SCORE_B,   (gScore[0] >> 4) & 0x0f,    gScore[0] & 0x0f);
This also means we'll be using binary-coded decimal (BCD) for the score, which you should be familiar with from writing 6507 code.

Constants have been defined for the special characters, such as the colon used in the timer display:
        MergeCharacters(TIMER_A,        2,  CHARACTER_COLON);
        MergeCharacters(TIMER_B,        0,  0);
For testing, PrepScoreDatastream() will populate the datastreams based on the state of the difficulty switches:
  • Left A - ARM Time Remaining
  • Right A - Test Pattern
  • Both B, score and timer example

time remaining
Attached Image

score timer
Attached Image

Some new defines have been added to custom/defines.h for reading the state of the console switches:
#define GAME_RESET_PRESSED  (!(SWCHB    & 0x01))
#define GAME_SELECT_PRESSED (!(SWCHB    & 0x02))
#define TV_TYPE_COLOR       ( (SWCHB    & 0x08))
#define TV_TYPE_BW          (!(SWCHB    & 0x08))
#define LEFT_DIFFICULTY_A   ( (SWCHB    & 0x40))
#define LEFT_DIFFICULTY_B   (!(SWCHB    & 0x40))
#define RIGHT_DIFFICULTY_A  ( (SWCHB    & 0x80))
#define RIGHT_DIFFICULTY_B  (!(SWCHB    & 0x80))
This makes the C code self-documenting, which is always a plus:
void PrepScoreDatastream()
    if (LEFT_DIFFICULTY_A)  // <-- this IF statement is self-documented
        // show VB and OS Time Remaining
    else if (RIGHT_DIFFICULTY_A) // <-- as is this IF statement
        // show test pattern
        // show score and timer
The 6507 in the Atari does not support hardware multiplication so score fonts tend to be designed to use 8 scanlines. This is because times 8 can be simply calculated using just 4 ASL commands. The ARM does support hardware multiplication, so I created the font in Collect2 using 7 scanlines just to show we have that option. Do note that the ARM in the Harmony/Melody does not support hardware division.

C Revision, CPU Load testing

In order to test the Time Remaining display, some delay loops have been added in ProcessJoystick() and VerticalBlank(). The left joystick controls the delay for Overscan while the right joystick controls it for VerticalBlank.

The delay has no effect when using Stella. This is useful because we can use the values displayed in Stella and the values from a real Atari to see how long the ARM code actually took to run. Per Stella the time remaining is $29 and $21 for Vertical Blank and Overscan. That's 41 and 33 when converted to decimal.

In this test, with each joystick held in a different direction, the times remaining are $21 and $1A or 33 and 26 in decimal.

CPU load test
Attached Image

We set the timer using TIM64T, so each tick of the timer represents 64 cycles of 6507 CPU time. Calculating that out yields that this particular test used:
  • (41-33) * 64 = 512 cycles of time for Vertical Blank
  • (33-26) * 64 = 448 cycles of time for Overscan
C Revision, other changes
  • MenuVerticalBlank() initializes the ScoreTimerColors datastream
  • NewGame() initializes new score variables to 0
  • VerticalBlank() calls PrepScoreDatastream()
Task for you

When the Left Difficulty switch is set to A, show the selected value of your new menu option in the timer's 4 digit display area (the center digits).

Upload your changes to the forum topic DPC+ ARM Development - Task(s) for you. Who knows, your new option might become part of this project!

Attached File  collect2_20150425.bin (32KB)
downloads: 78

Attached File  Collect2_20150425.zip (145.27KB)
downloads: 282

1 TIA's hardware collision registers aren't very helpful when multiplexing a multitude of sprite images through TIA's two players, so collision detection must be handled in software.

2 items are on the same page when the high-byte of their addresses have the same value, so addresses $F100 and $F1FF are both on the same page while $F1FF and $F200 are on different pages.

Search My Blog

Recent Entries

Recent Comments

Latest Visitors

3 user(s) viewing

0 members, 3 guests, 0 anonymous users