Sunday, August 28, 2011

Paint it black

Got my fingers a little dirty and started working on a couple of the playfield toys. I have not yet decided if this particular one will be an animated one...

1) White, die-cast 1957 Chrysler 300C

2) Black undercoat (purposely not 100% covering)

3) Base layer, grey (purposely not 100% covering)

4) Added rust and painted interior

5) More rust and dirt wash
6) The final car - A broken down, war beaten 'Chrysalis Motors Highwayman'
I'm thinking you cannot have a hard time guessing the theme of the game now...

Monday, August 22, 2011

As David would have said - Ch-ch-ch-changes!

I've changed my mind... I will be using a SD card for animations.
It will also be used to store the highscore etc.  The built in EEPROM has a "very" limited write/erase cycle so I figure I'd keep the internal memorycards intact and store everything on external cards to minimize the risk of my pinball machine breaking down during a tournament or whatnot.

The Chipkit 512 KB flash storage means around 120 frames of animation. That data is probably best used as common animation that needs to be readily available and the SD-card ones will be mode-specific graphics. With SPI the datarate is fast enough, and I figure I could use the natural pauses in the game to load new animations into RAM. The current game logic was actually already prepared for this so it would mean minimal changes and/or interruptions in gameplay.

I've also got basic serial communication between master (Arduino) and slave (Chipkit) going on, so the host can send "playMusic", "playAnimation" etc commands to the slave which will then perform them. I will probably do at least one rewrite of that library as time progress to further streamline the communication, but at the moment it works fine. It is prepared for (and is currently) sending batch jobs of commands, but it's uncertain if the final version will send single commands right away or batch them together for sending at the end of the frame.  There are different benefits for both versions.

I've also refactored the code to work with two different boards:
Arduino - Switches, Solenoids, Game logic and SoundFX.
Chipkit - Display, Lights, SD card and Music

With the transition to the Chipkit, it unfortunately turned out that (most likely) the voltage difference from 5V to 3.3V does not allow the display to properly function. I've been in touch with the manufacturers of both the display and the Chipkit and we've come up with the same conclusion. So, I've ordered a 3.3->5V logic translator chip now which will hopefully solve the problem. Kind of a bummer, but it will be worth the extra effort for all the sweet improvements it offers.

The game logic ... Ah, the fun part!
The ruleset is shaping up quite nicely and as it seems to me - a nice adaptation of the original concept. When reading the rules I'm quite satisfied that the gameplay will be fun as well. I guess the hardest part will be actually finding sound and graphics for the game, but if all else fails, I will do them myself.

Wednesday, August 17, 2011

Max32 + Mega2560 = Superpin!


The main bottleneck at this moment is the RAM, as I said earlier. With the Mega2560 I got 8KB of RAM, which gives me around 1 display buffer with 8 colors (´a 4096 bytes). Sure, I could do some voodoo magic to get it working, but it will be very slow to perform any writing to the buffer. With all this extra overhead the 16MHz will running at 100% just to keep up. In order to fully support doublebuffering I would like to have three buffers. One buffer that is sent with SPI to the display straight away, and two that are alternated and built "behind the scenes".

But...
I will be using BOTH boards, actually.

The Max32 will take care of the visuals (lights and display) and the Mega2560 will do solenoids, switches and game logic. Using all the available memory, this would give me around 800 KB to store data (ruleset and logic excluded) for animation - I could easily fit all the game animations there to eliminate the need for an external SD card.

The Max32 is also of more than 4x the MHz, 2x the flash and 16x the RAM!
With this extra card I've more than doubled the performance and the number of I/O pins, which will really come in handy!


1) Poor Man's Motherboard!


Sunday, August 14, 2011

Out of RAM

Ouch, Arduino. Ouch...

Time for plan B - Chipkit Max32

Those buffers...

I found out that I can improve the speed of the display rendering with about 50%.
It's quite simple really.

Currently I'm checking every frame, after every color, after pixel on/off...
But my machine will not feature 60 fps full motion video. Heck, even cinemas don't have that. You can easily see that this is a large overhead with largely redundant information.

By extracting the draw buffer (front) from the videolayer (back) I can keep drawing the frontbuffer "no questions asked" at about 1 ms per color. All the screen updates, such as text and images, happens in the backbuffer and when say a certain interval has passed the buffers are swapped. Rinse and repeat.

The beauty about this is that I remove all the heavy checks from the draw routine and can spread out the work of building a frame over a "lot" of time. Every clock cycle is crucial in this build since the Arduino is a single core, single process type of MCU.

A quick example (numbers are guesstimates, since I have not coded the backbuffer part yet):

Before:
Single frame: 18 ms (drawing) + 20 ms (building) = 38 ms, gives around 26 fps if the frame would be recreated every frame, such as during a video scene. This is very low and flickering is noticeable. Normal case is around 52 fps if the frame is created only once and drawn many times.

Single frame: 8 ms (drawing) + 2 ms (building) =   10 ms, gives around 100 fps if video was created at about 15 frames/sec. I could easily limit the drawing to 70 Hz to further free up resources without being noticeable for the player. Time that they however will feel with the game being more responsive.

I can't believe I've missed this -
I've done this a million times before in regular graphics programming...


Saturday, August 13, 2011

Shades of Sanity

Heureka!
SPI is a godsend. Everything's forgiven!

1) 8 + 1 colors at 18.868 ms, about 52 frames/sec.

The new code is approximately 2.5 x faster, which may not sound a lot - but that's what separates a boring or flickering display from a nice, crisp multicolor display. The main time thief is the "if ( *f++ >= lx)" part - the code runs at about 128 frames/sec with a fixed value instead of "b", so I figure it will be possible to further improve the code. For reference, old Williams games used 4+1 shades, newer Stern games use 16 shades.  Considering the hardware used, I'm quite satisfied with the current performance.


Feel free to look upon and possible improve the code:

void writeDisplay()
{
/*
Write all shades in one go. (i.e around 2.3 ms per color)
*/


register byte x,y,lx,b;
    register byte *f;


cli(); //Disable interrupts 

for (lx=1; lx < DISPLAY_MAX_BRIGHTNESS+1;lx++) //repeat for each color/shade
{   
   f = &frame[0][0];


//row 1  -------------
bitClear(PORTF, 2);          //set 4th bit of PORTF to LOW          
for (x=0; x < 16; x++)   //send full 128 bit row using SPI
{
if ( *f++ >= lx) b  = B10000000; else b=0;
if ( *f++ >= lx) b |= B01000000;
if ( *f++ >= lx) b |= B00100000;
if ( *f++ >= lx) b |= B00010000;
if ( *f++ >= lx) b |= B00001000;
if ( *f++ >= lx) b |= B00000100;
if ( *f++ >= lx) b |= B00000010;
if ( *f++ >= lx) b |= B00000001;

SPI.transfer(b);
}   
   bitSet(PORTF, 2);            //set 4th bit of PORTF to HIGH //Column latch
      bitSet(PORTF, 4);          //set 5th bit of PORTF to HIGH (mark first row)  
bitClear(PORTF, 5);          //set 6th bit of PORTF to LOW     
        bitClear(PORTF, 3);          //set 4th bit of PORTF to LOW
        bitSet(PORTF, 3);            //set 4th bit of PORTF to HIGH      
bitSet(PORTF, 5);            //set 6th bit of PORTF to HIGH


//row 2-31      ------------
for (y=0; y < DISPLAY_MAX_ROWS-2; y++)
{

   bitClear(PORTF, 2);          //set 4th bit of PORTF to LOW          
for (x=0; x < 16; x++)   //send full 128 bit row using SPI
{
if ( *f++ >= lx) b  = B10000000; else b=0;
if ( *f++ >= lx) b |= B01000000;
if ( *f++ >= lx) b |= B00100000;
if ( *f++ >= lx) b |= B00010000;
if ( *f++ >= lx) b |= B00001000;
if ( *f++ >= lx) b |= B00000100;
if ( *f++ >= lx) b |= B00000010;
if ( *f++ >= lx) b |= B00000001;

SPI.transfer(b);
}   
  bitSet(PORTF, 2);            //set 4th bit of PORTF to HIGH //Column latch
    bitClear(PORTF, 4);          //set 5th bit of PORTF to LOW (the rest of the rows are not leading)
  bitClear(PORTF, 5);          //set 6th bit of PORTF to LOW    
       bitClear(PORTF, 3);          //set 4th bit of PORTF to LOW
      bitSet(PORTF, 3);            //set 4th bit of PORTF to HIGH
bitSet(PORTF, 5);            //set 6th bit of PORTF to HIGH
}
 
 
//row 32 ---------------
   bitClear(PORTF, 2);          //set 4th bit of PORTF to LOW          
for (x=0; x < 15; x++)   //send full 128 bit row using SPI
{
if ( *f++ >= lx) b  = B10000000; else b=0;
if ( *f++ >= lx) b |= B01000000;
if ( *f++ >= lx) b |= B00100000;
if ( *f++ >= lx) b |= B00010000;
if ( *f++ >= lx) b |= B00001000;
if ( *f++ >= lx) b |= B00000100;
if ( *f++ >= lx) b |= B00000010;
if ( *f++ >= lx) b |= B00000001;
SPI.transfer(b);
}   
if ( *f++ >= lx) b  = B10000000; else b=0;
if ( *f++ >= lx) b |= B01000000;
if ( *f++ >= lx) b |= B00100000;
if ( *f++ >= lx) b |= B00010000;
if ( *f++ >= lx) b |= B00001000;
if ( *f++ >= lx) b |= B00000100;
if ( *f++ >= lx) b |= B00000010;
if ( *f  >= lx) b |= B00000001;
SPI.transfer(b);
  bitSet(PORTF, 2);            //set 4th bit of PORTF to HIGH //Column latch
      bitClear(PORTF, 4);          //set 5th bit of PORTF to LOW (the rest of the rows are not leading)
bitClear(PORTF, 5);          //set 6th bit of PORTF to LOW
        bitClear(PORTF, 3);          //set 4th bit of PORTF to LOW
        bitSet(PORTF, 3);            //set 4th bit of PORTF to HIGH
bitSet(PORTF, 5);            //set 6th bit of PORTF to HIGH
} //End of color/shade

sei(); //enable interrupts.


bitClear(PORTF, 5);          //set 6th bit of PORTF to LOW
}



Friday, August 12, 2011

The new code...

My code is now full of commands similar to this:

*b = ((*b   << 1) | (*p++ | layer)?1:0); 


My head hurts...

Wednesday, August 10, 2011

Something shady's going on...

Sat down a couple of hours and got started on a multicolor display-routine - only to find out it didn't work. At all...

After extensive troubleshooting of what I believed to be was faulty cables, I've redone all the cables except the powercables in the machine. Now everything is nice and pluggable/unpluggable in a easy manner.

The problem wasn't in the cables however, but the cabling still needed some work.
When I tried a different version of the digitalWriteFast it, for some peculiar reason, broke the pins I was using so I tried plugging the display into another set. The refresh rate of the screen was becoming rather slow and at that time I didn't connect that those pins were not "accelerated" by digitalWriteFast.
Also - the new library was approximately 1.5 x slower than the old. Doh.

So anyway - I reverted to the old library and the old pins and now my code could do its work properly.

1) Multicolor display. Four different reds and one black layer.
Here it's simply displaying f(y)=red, but any pixel can be any shade. 
At the moment it can display 5 different "colors" (4 red + 1 black). The code that enables different shades is quite slow since it needs to check for different values and also draw the screen multiple times, so with 4 colors I'm pushing 'the little MCU that could' beyond its comfort zone.

I will do further testing with SPI enabled since I need to get the time needed for each update to a lot less than it is currently taking. It is currently updating a full 4+1 color frame at 19 ms, which is "really slow". I hope to get this down to under 1 ms, if possible...

Saturday, August 6, 2011

A picture's worth...

You can see it in very crude motion including a crude pong-isch game on Youtube.

What you see above is a PinLED display driven directly via the Arduino microcontroller. I was going for a plasma screen first but they need a lot more power, four different voltages from -115VAC to 115VAC etc. The PinLED hooks up directly to +12VDC which is a much more convenient level. If you're also worried about the environment or electrical bill, the PinLED is also preferred as it stays under the RoHS law.

The current display code draws a frame of information, in which the designer/programmer (me) fills with data. The current supported commands are just 3-5x5 letters and numbers being printed at any x,y coordinate. The final code will of course feature animated images etc. It's actually supported already, I just need to find a tool (or code one myself) that converts regular images to byte arrays that can be used in the code. 

As always, the DMD pixels are just on/off so different shades are created by lighting the various pixels for a different amount of time. I've yet to find a suitable method for this but the idea is that the final version will feature "multicolor" images. 

At the moment that picture (and video) was taken it ran at about 5.5 ms, i.e approx. 180 Hz. The current code revision runs at about 0.5 ms meaning it's too fast for the refresh rate of the screen... Therefor I need to slow my code down to push the refresh rate below 200 Hz (the maximum). 

It's a good day for a programmer when the main problem with the code is that it runs too fast!

Monday, August 1, 2011

Code changes

I've experienced that the Arduino seem to be slow sometimes, especially when writing values. So I did a little research and tested stuff on my own - and the Arduino is not slow. But the code is.

For instance:

Using regular digitalWrites, 2500 on/off cycles:   35764 microseconds.
Using digitalWriteFast, 2500 on/off cycles:            2614 microseconds.

The old code is almost 14 (!) times slower than the new code! Damn...
Of course, there are caveats. It requires the pin number AND state to be static and known at compile time. Most of us do this all the time anyway (const, #define etc) so it's not a big deal.

The syntaxes are similar, if not identical, too.

Old: digitalWrite(PINNR, STATE);
New: digitalWriteFast(PINNR, STATE);

The static STATE can be avoided by simply doing something similar to:
if (someState) 
   digitalWriteFast(PINNR, HIGH);
else
  digitalWriteFast(PINNR, LOW);

So - If your are developing on Arduino, download the digitalWriteFast library before doing any coding! It's  A LOT faster and doesn't require that much planning ahead. I've already begun converting my code (and redesigning it along the way) to gain the juicy speedup.

Disclaimer:
The numbers where taken from the top of my head so I don't remember the two last digits of each value, but the magnitude of the values are correct. With a 14x speedup, the last two digits are indeed the least significant values. 



Audiophilistic

Improved the speaker system today with a nice AC/DC amplifier and it's loud... really loud. Finally I can wake the neighbors with my machine!

I've moved the amplifier from the cabinet to the head, and I've put the audio controls (tone, volume, power, headphones) on the back of the head. I don't want the controls to be sticking out from the head so this will be covered by a steel grill later on, so it won't be that easy to adjust, but still possible.
I've also plugged in the nice Stern 8-Ohm speakers and resoldered the passive mixer with thicker cables.

Basically I will be putting the display, speakers, amplifier and everything related to that inside the head and the rest of the hardware inside the cabinet. The head will of course be connected to, and receive its power from, the cabinet itself.

To sub, or not to sub.... that's the question.