My goal for this software is to create an Arduino-based pinball operating system for Stern MPU-100 and Bally AS-2518-17 machines. Programming for Arduinos is very simple and the IDE isn’t wonderful, but it’s free. The OS will take care of all of the hardware control. The game layer is just responsible for implementing the game rules through the switches, solenoids, displays, and lamps.
Setup – The minimum requirements to start the OS are the following setup functions:
BSOS_SetupGameSwitches just takes a pointer to the array of “PlayfieldAndCabinetSwitch” structures that the game layer defines. Each game can have pop bumpers and slingshots on different solenoids, and I wanted the OS to stay independent of machine-specific configurations. So the array passed in simply defines switches that should trigger immediate solenoid responses. This array needs to be sorted to put the highest-priority solenoids at the top. Some solenoids (pop bumpers & slingshots) will trigger immediately when they see a switch go from Open to Closed. Others, (like chimes) will only trigger when they see a debounced signal show Open, Closed, Closed. So, the parameters for this function are:
s_numSwitches – total switches that have solenoid responses
s_numPrioritySwitches – switches at the top of the array that require immediate responses
s_gameSwitchArray – pointer to the head of an array of PlayfieldAndCabinetSwitch structures with a size of s_numSwitches
BSOS_InitializeMPU sets up all the global variables, configures the ports on the Arduino, and it talks to the PIA chips to get them ready. It then hooks up the ISR to pin 2 to handle the interrupts generated by U10 and U11. Once this function is called, the machine is ready to go. At the moment, there’s no self-test as part of this function. In the future, the function should do the following tests:
Read & write to U10 to make sure it responds correctly
Read & write to U11
Let the U10B:CB1 interrupt run for a second to make sure it’s coming in at approximately 120Hz
Let the U11A:CA1 interrupt run for a second to make sure it’s running at approximately 320Hz
Once those two functions are called, the game layer is free to use any of the functions for reading switches or outputting to solenoids, lamps, and displays.
Note 1: There is one piece of black magic that I have not been able to figure out. The interrupts will not continue to read if the address lines are not cleared out during the game play loop. For this reason, until I figure out a better method, I recommend always including BSOS_DataRead(0); in the main loop of the Arduino sketch.
Note 2: Interrupts are masked by serial communication on the Arduino. I believe there’s a way around this, but I’ve noticed that writing a lot of debugging messages through the serial port will make displays flicker. I recommend encasing those in a “DEBUG” define and turning them off for release versions.
The software implementation of this OS is contained in two files: BallySternOS.cpp and BallySternOS.h. These can be zipped and installed as a contributed library, or simply added to the directory of the game sketch and included. I’ve made a GitHub repository for the code:
Switches – there are only three functions related to switches.
BSOS_GetDipSwitches allows the game layer to read the DIP switch values from the board. The argument “index” selects which bank will be returned. Switch values are packed into the byte that is returned.
BSOS_PullFirstFromSwitchStack returns the next switch event from the stack. Every time a switch is hit, it’s pushed to the stack. This function pulls the first event from the stack. It returns SWITCH_STACK_EMPTY if there are no more events on the stack. This switch number is just an integer between 0 and 39 (inclusive). It’s up the game layer to know which switch is which. The machine’s manual will have a switch diagram and the values can be read from that.
BSOS_ReadSingleSwitchState allows the game level to check the state of a given switch at the moment. This is handy to know if the ball is currently in the outhole trough, or if drop targets are currently down. The only parameter is the switch number, and the return is true if the switch is closed.
Solenoids – There are several functions related to the solenoids. Internally, the OS keeps two solenoid stacks. The first is a FIFO (first-in, first-out) stack used for immediate solenoid firing. The second allows the game level to queue solenoids for later (so it’s not really a stack, but a list of events and the times they should occur). The timed stack can be used for chimes or resetting banks of drop targets.
BSOS_PushToSolenoidStack – this function pushes the given solenoid number “numPushes” amount of times. “disableOverride” allows for stack pushes even if the solenoid stack has been disabled. Example: the solenoid stack might be disabled in Attract mode, but we still want to play a chime when the Start button is pressed.
BSOS_SetCoinLockout and BSOS_SetDisableFlippers are helper functions used to manage common continuous solenoids. They’re coded to use common bit assignments (flippers are often on bit 6, and coin lockout on b5), but that bit can be changed in the second parameter. BSOS_ReadContinuousSolenoids can be used to check on the current value. This is mostly used for debugging.
BSOS_DisableSolenoidStack is useful to disable pop bumpers and slingshots in a Tilt situation or during Attract mode. BSOS_EnableSolenoidStack turns it back on.
BSOS_PushToTimedSolenoidStack is like the other solenoid stack function, but it includes an unsigned long to tell it when to fire. The OS doesn’t concern itself with keeping a real-time clock, so the current clock value must be passed in by BSOS_UpdateTimedSolenoidStack in the program’s loop. When the curTime exceeds whenToFire, entries from the timed stack are moved to the regular solenoid stack.
Lamps – There is one main function to control the lamps and one to update the lamps in case they’re supposed to flash.
BSOS_SetLampState takes a lamp number and then turns it on or off based on s_lampState. These lamp numbers are inherent to the way the lamps are wired to the lamp driver board, so it’s up to the game layer to keep track of which lamp is which. For example, on Stars the first line of the first latch is hooked to the Special light near the purple star, so I have a definition of “SPECIAL_PURPLE_STAR” set to zero. The third parameter is whether the lamp is dim. A dim value of 0 makes the bulb fully on, 1 makes the bulb on 50% of the time at 60Hz, 2 makes it 50% at 30Hz, and 3 makes it 30Hz at a 25% duty cycle. The 30Hz values flicker a bit too much, but the 60Hz looks good. There’s also a parameter to make the bulb flash at the given period (in milliseconds). For the bulb to flash, you have to periodically call the next function.
BSOS_ApplyFlashToLamps called with the current time in milliseconds will flash the bulb at the set period. If the period of the lamp is 0, the bulb will not flash.
BSOS_FlashAllLamps is a self-test function to toggle all 60 circuits on the lamp board.
BSOS_TurnOffAllLamps is a helper function to blank all bulbs when changing machine states.
The game layer could easily implement the last two functions, but I included them in the OS so the game layer wouldn’t have to concern itself with the total possible number of bulbs, or enumerate all the bulbs it knows about.
Displays – there are a ton of functions for the displays, but only a few that are absolutely necessary. The rest are for convenience.
BSOS_SetDisplay – this is the main function to update a display. The first parameter (between 0-4, inclusive) chooses if we’re updating Player 1, Player 2, Player 3, Player 4, or Credits/Ball in Play. The second parameter will be broken up into digits by the OS.
BSOS_SetDisplayBlank allows the game layer to turn on or off individual digits of the displays. It’s a bitmask with 1 meaning show the digit and 0 meaning hide it. Because of confusing internal reasons, b0 is the left-most digit on a display and b5 is the right-most. So, to show 00 in a display, call this function with 0x30 after setting the display to zero.
BSOS_SetDisplayBlankByMagnitude will automatically clear leading zeros based on the magnitude of the number passed in.
BSOS_SetDisplayBlankForCreditMatch will turn off the first and third digits of a display. On some machines, the credit/ball-in-play display will be a single 6-digit display unit instead of two 2-digit ones. For that reason, credits and match should always be masked to 0x36.
BSOS_SetDisplayCredits and BSOS_SetDisplayMatch set only digits 2 and 3 to a 2-digit number.
BSOS_SetDisplayBallInPlay – sets digits 5 and 6 to a 2-digit number.
BSOS_SetDisplayBIPBlank blanks the ball-in-play display.
BSOS_SetDisplayFlash flashes the given display at the given period. This function must be called periodically in order to toggle the display on and off.
BSOS_SetDisplayFlashCredits flashes the credit display.
BSOS_CycleAllDisplays called periodically will cycle through all the digits for a self-test.