New! Dark mode!

Browsing Pinside at night? Getting tired of all the white? Switch to dark mode using the button in the top right (or CTRL-B)!

(Topic ID: 266605)

Replacing the M6800 in a Stern MPU100 with an Arduino


By DickHamill

6 months ago



Topic Stats

  • 129 posts
  • 30 Pinsiders participating
  • Latest reply 87 days ago by DickHamill
  • Topic is favorited by 42 Pinsiders

You

Linked Games

  • Stars Stern Electronics, 1978

Topic Gallery

There have been 50 images uploaded to this topic. (View topic image gallery).

20200721_154629.jpg
20200721_154238.jpg
Perfboard.jpg
20200714_153848.jpg
2020-07-14_15-23-13.png
IMG_1806 (resized).jpeg
BallyNanoAdapterV1 (resized).png
Wiring rev3a (resized).png
Wiring rev3 (resized).png
Untitled (resized).png
20200521_162321 (resized).jpg
IMG_20200520_211156826 (resized).jpg
IMG_20200520_211117323 (resized).jpg
starsduino (resized).jpg
starsduinoloose (resized).jpg
20200421_140230 (resized).jpg

There are 129 posts in this topic. You are on page 1 of 3.
17
#1 6 months ago

After repairing a Stars machine (Stern, 1978), I started thinking about how I could improve the rules of the game. Stars is fun, but it only has two goals (drop targets & lighting stars) and it doesn’t take long before it starts to feel like a grind. I thought it would be interesting to add things like:
* A free play mode
* Ball save
* Hurry ups
* Deeper goals
* A wizard mode
Plus, I wanted to get deep down in the guts of the Stern MPU-100 (or Bally AS-2518-17) and really see how everything worked. In the process, I learned a lot about how those boards work, so I figured it would be good to document the process.

With all the MPU replacements available, including those that are built around Arduinos, you might ask why I’m bothering to do this. I like keeping the original hardware in this old machines. When I got them, the MPUs were broken and it took a long time to figure out the issues and fix them. I could have simply spent the money and replaced them with universal boards, and those would have been easier to hack with my own code. I wanted to keep everything stock except the processor. With this implementation, you can pop out the M6800, plug a simple board onto J5, and the machine is completely different. The Arduino Nano is $4~$5, and the entire board can be built for less than $10.

If you would like to skip ahead and see the results, here’s a link to all the code I produced:

https://github.com/MrEkted/BallySternOS

IMG_1512 (resized).jpg
#2 6 months ago

Interfacing the Arduino Nano to the MPU is really easy using the J5 connector on the board. The J5 connector is at the top of the board and it was originally meant as a diagnostic port. It contains access to all the lines needed except for the IRQ line. The lines used by this project are the following (J5 Pin # in parentheses, and followed by the hookup to the Arduino Nano Pin):

Ground (31) – Nano GND
+5V (30) – Nano VIN
Theta 2 – clock (27) – Nano Pin 4
VMA (26) – Nano Pin A5
HLT (24) – Nano Pin A6
R/W (23) – Nano Pin 3
A0 (22) – Nano Pin A0
A1 (21) – Nano Pin A1
A3 (19) – Nano Pin A2
A4 (18) – Nano Pin A3
A7 (15) – Nano Pin A4
D0 (8) – Nano Pin 5
D1 (7) – Nano Pin 6
D2 (6) – Nano Pin 7
D3 (5) – Nano Pin 8
D4 (4) – Nano Pin 9
D5 (3) – Nano Pin 10
D6 (2) – Nano Pin 11
D7 (1) – Nano Pin 12
In addition, I ran a jumper from the CPU socket pin 4 (IRQ line) to the Arduino Pin 2, and I ran jumpers to connect J5 Pins 10, 11, 12, and 13 (A12-A9) to ground so I could guarantee that I was only accessing the PIA chips.

This project only accesses the MPUs PIA chips (U10 & U11). It doesn’t use or need the RAM or ROM on the MPU board.

If you have an MPU100 board that doesn't work because of problems with the cRAM/RAM/ROM, you can use this mod to use that board anyway. The Arduino will take over those functions.

Wiring (resized).png
#3 6 months ago

The Stern MPU-100 is identical to the Bally AS-2518-17 board. At least I think it is. I’ve worked on both, I can’t find any difference, and I’ve read that plugging the ROMs from one into the other works just fine in a game.

Here’s what I know about the architecture. The M6800 processor talks to the RAM, ROM, and PIAs with 13 address lines and 8 data lines. I don’t think A14 is used at all on this version of the board.

One little sub-circuit (using U14 and some discrete components) creates a “zero-crossing” pulse when the AC voltage crosses zero. This is fed into U10:CB1 to create a zero-crossing interrupt. Another sub-circuit creates a 320 Hz signal using U12 (a 555 timer). That’s fed into U11:CA1 to create an interrupt to update the numeric displays.

The PIA chips (U10 & U11) collect these interrupt lines and combine them to feed into U9:Pin 4 to tell the processor when things need to be done. All the interface with the real world is brokered through the Interrupt Service Routine (ISR) on the M6800. At least I’m pretty sure that’s the way it works. I’ve been through disassembled code and it appears that the timing of switch reads, lamps, and solenoid fires is all relative to the 120Hz interrupt. Displays are updated by the 320Hz interrupt. Technically, one could fire a solenoid at any time as long as it was turned off by the zero-crossing interrupt, but I don’t think that’s the way it works. I could be wrong.

So, game logic is running in a continuous loop on the M6800. It’s either in game, attract, or self-test mode at any given time, and the M6800 reads switch values from RAM (U7) to know what’s going on (the ISR is the one who puts the switch values there), and writes lamp and display updates back to RAM (U7) to be presented back to the hardware by the ISR. The M6800 stores things like credits, high scores, and audit data in U8 so it will be preserved by the batteries even when the machine is turned off.

So, for my purposes, the RAM/ROM chips (U1-U8) are unnecessary. It’s a good thing, too, because on the stock Arduino Nano, we don’t have enough pins to deal with all the address, data, and signal lines to talk to everything. Here’s how the address space breaks out:

U7 (scratchpad RAM) = 0x0000 through 0x007F
U10 (PIO) = 0x0088 through 0x008B
U11 (PIO) = 0x0090 through 0x0093
U8 (battery cRAM) = 0x0200 through 0x027F (I think?)
U2 (2k ROM) = 0x1000 through 0x17FF
U6 (2k ROM) = 0x1800 through 0x1FFF
To conserve how many pins I need on the Arduino, I only address A7, A4, A3, and A1. That allows me to point to U10 & U11. If I was really conserving pins, I could just make A7 always +5V because U10 & U11 both need it on, but I didn’t.

#4 6 months ago

The 6820 PIA (Peripheral Interface Adapter) chips are how the M6800 talks to the outside world. They’re used to either latch data to send to the lamps, display, or solenoids, or they’re used to grab input data and hold it for the processor to read. With only 8 data lines, the M6800 needs other chips to split out enough lines for all the data (input and output). These are those chips.

Each chip (U10 & U11) has two 8-bit I/O ports (PORT A & PORT B), two interrupt input lines (CA1 & CB1), and two other control lines (CA2 & CB2). To access the chips, they’re given four address lines. U10 is mapped to 0x88, 0x89, 0x8A, and 0x8B. U11 is mapped to 0x90, 0x91, 0x92, and 0x93. Because I’m only using five address lines to access those two chips (A0, A1, A2, A3, and A4), the address space used by the Arduino looks like 0x14 through 0x1B.

MPU A0 = Arduino A0
MPU A1 = Arduino A1
MPU A3 = Arduino A2
MPU A4 = Arduino A3
MPU A7 = Arduino A4
Note: I happen to be using the “Analog” pins on the Arduino for the address space, that’s why they’re named A0, A1, etc.

To read from the PIAs, all we have to do is set the Arduino data lines to input (pins 5-12), put the right address on A0-A4, set the R/W line high (Arduino pin 3), wait for a falling edge of the clock (Arduino pin 4), and pulse the VMA line (Arduino pin A5). As soon as the clock goes high again, we can read 8 bits of data on pins 5-12. The timing of these signals can be found in the 6820 datasheet.

byte BSOS_DataRead(int address) {

// Set data pins to input
// Make pins 5-7 input
DDRD = DDRD & 0x1F;
// Make pins 8-12 input
DDRB = DDRB & 0xE0;

// Set R/W to HIGH
DDRD = DDRD | 0x08;
PORTD = (PORTD | 0x08);

// Set up address lines
PORTC = (PORTC & 0xE0) | address;

// Wait for a falling edge of the clock
while((PIND & 0x10));

// Pulse VMA over one clock cycle
// Set VMA ON
PORTC = PORTC | 0x20;

// Wait while clock is low
while(!(PIND & 0x10));

byte inputData = (PIND>>5) | (PINB<<3);

// Set VMA OFF
PORTC = PORTC & 0xDF;

// Wait for a falling edge of the clock
// Doesn't seem to help while((PIND & 0x10));

// Set R/W to LOW
PORTD = (PORTD & 0xF7);

// Clear address lines
PORTC = (PORTC & 0xE0);

return inputData;
}
Note: I access the Arduino ports (PORTD, PORTC) and the data direction registers (DDRD) directly because it’s so much faster than digitalRead(pin).

BSOS_DataWrite(int address, byte data) works in much the same way. Here’s a link to the code so you can review it if you’d like to:
https://github.com/MrEkted/BallySternOS

With the ability for the Arduino to read & write the PIA chip, it’s just a matter of understanding how that chip works and how it’s expected to talk to the rest of the board.

U10 is primarily concerned with reading the switches and controlling the lamps, but it also has a hand in strobing the display latches. Port A is used for output. PA0-PA4 strobe the switch banks. PA0-PA7 also output the lamp addresses & data. PA0-PA3 are also used to strobe the display latches. The pins can be used for different purposes because they’re carefully shared between the two different parts of the ISR. Details of this sharing can be found in the sections on display, lamps, and switches.

U10 Port B is the only input port. When a switch line is strobed, it allows power to flow through a bank of 8 switches and the values are then read back on U10 Port B.

U11 is used for the displays and solenoids. Port A is used for the display digit enable lines (PA1-PA7). Both of the machines that I have only used 6-digit displays, so PA1 is useless. PA0 is used for the 5th display latch strobe (credit & ball in play display).

U11 Port B controls the solenoids. PB0-PB3 are for momentary solenoid control (pop bumpers, slingshots, chimes, etc.), and PB4-PB7 are for continuous solenoids (flippers & coin lockout).

Configuring a port on one of the PIA chips requires at least 3 writes. (the example below assumes we’re talking about Port A)

Write #1 = setting up the control register to describe the behavior of CA1 interrupt, CA2 control line, and telling the chip that the next port write should affect the direction register.
Write #2 = telling the direction register which bits are input and output
Write #3 = changing the control register so the next read/write affects the actual port
Write #4 (optional) = setting the default value for the port if it’s an output port
The control register bits look like this (the example below assumes we’re talking about Port A):

B7 = the interrupt bit – it’s high if an interrupt has occurred on pin CA1
B6 = an interrupt bit for CA2 (but we always use CA2 or CB2 as output, so we never have this bit set)
B5, 4, 3 = the behavior of CA2
in our case, B5 and B4 are alway set, meaning that when we write B3 the line will reflect whatever we write
B2 = unset means we’re writing the data direction register, and set means we’re writing to the port
B1, 0 = behavior of interrupt pin CA1
0x01 is interrupt on falling edge
0x03 is interrupt on rising edge
Here are the values, in order, we’re going to write:

U10 Control Register A = 0x38, which means turn on CA2 and we want to write to the DDR (data direction register)
U10 A = 0xFF, all pins output (used for lamp data/address, switch strobes, and display latch strobes)
U10 Control Register A = 0x3C, same as above, but next write is to the register, not the DDR
U10 A = 0xF0, make pins 0-3 low and 4-7 high
U10 Control Register B = 0x33, which means turn off CB2, and we want CB1 to be an interrupt on a rising edge (for the zero-crossing interrupt)
U10 B = 0x00, all pins input (for the switches)
U10 Control Register B = 0x37, same as above, but next time we access U10 B we’re going to read from the port
U11 Control Register A = 0x31, which means turn off CA2, and we want a falling edge interrupt on pin CA1 (for the 320Hz timer)
U11 A = 0xFF, all pins output (A0 used for display latch strobe 5, A1-A7 used for display digit enable)
U11 Control Register A = 0x35, same as above, but next time we write to U11 A we’re writing to the port
U11 A = 0x00, all data lines off for now
U11 Control Register B = 0x30, which means turn off CB2 so solenoids will be enabled, and we don’t care about an interrupt on CB1
U11 B = 0xFF, all pins output (A0-A3 are momentary solenoid data, and A4-A7 are continuous solenoids)
U11 Control Register B = 0x34, same as above, but next time we’re writing to U11 B we’re writing to the port
U11 B = 0x9F, B6 and B5 are to engage flippers and coin lockout (off is powered for solenoids), and B3-B0 turn off all momentary solenoids because 0x0F means don’t engage any solenoids
This is a lot of information, I know. It only makes sense when you really get into the nuts and bolts of how this hardware is addressed. Not to worry, I’ve already been through every bit listed here and figured out what makes them work. If you have any questions or corrections, please let me know.

With those two chips configured, we’re ready to start getting interrupts on the Arduino and reading/writing the ports. One very important note: an interrupt is cleared on a PIA only when the associated port is read. So when U10:CB1 gets an interrupt from the zero-crossing circuit, the only way that it will be cleared is when U10:Port B is read. This almost makes sense for U10:Port B because it’s an input port (reading the switch returns). It makes far less sense for U11:Port A. That’s an output port, but the only way to clear an interrupt on U11:CA1 (the 320Hz timer) is to read from U11:Port A and throw away the value.

#5 6 months ago

The games I’ve worked on have five 6-digit displays. There are four player displays and one for credits/ball-in-play. These displays only show one digit at a time, but they flip so quickly between them that it looks like all six are on at once. This is what the 320 Hz interrupt is for. Every 3.125 mS (320 Hz) another digit is shown.

Each time the interrupt service routine is called, the following steps are executed:

Displays are blanked (by turning off U10:CA2)
For each display:
Digit select lines are set (digit 1-6), U11A:PA1-PA7
Current digit is put on data lines, U10A:PA4-PA7
Latch is strobed to lock the data into the current display (1-5), U10A:PA0-PA3, and U11A:PA0
Displays are un-blanked (by turning on U10:CA2)

Here’s a picture of the signals generate by the original code (running on the M6800). In blue (mostly obscured by the white line for the frequency measurement), you can see the interrupt signal from the 555 timer. In yellow, you’ll see the blanking signal being sent out on U10:CA2 to blank all displays.

blnk_320 (resized).jpg

The entire blanking sequence takes about 240µS in the original code. The clock cycle is about 2µS, so it takes roughly 120 clock cycles to write all the displays. Here is the blanking signal expanded and each of the display writes shown during the course of one blank:
disp1_blnk (resized).jpg
disp2_blnk (resized).jpg
disp3_alnk (resized).jpg
disp4_blnk (resized).jpg
disp5_blnk (resized).jpg
Right now you might be wondering why it looks like the display 5 latch overlaps the display 4 latch. I wish I knew! It takes a bit longer to latch display 5 because the latch line comes from U11A:PA0, and the value must be read, OR’d, and then written. With the other lines, they’re just the low order bits of the same byte that contains the BCD data. So, I get why it’s slower than writing displays 1-4, but I’m not clear on why it appears to overlap. I don’t have a picture of latch 4 versus latch 5. If I get one, I’ll update this.

So, by the end of the blanking signal, the processor has written data to each of the five displays. The digit is then incremented so the next time in the ISR, the next digit will be written. After it writes the last digit, it loops back around to the first.
Bally Display Signals (resized).jpeg
In the circuit diagram (above), check out the part that I put a purple box around. Those are the five lines that strobe the latches on the displays (U10A:PA0-PA3 & U11A:PA0). If one of those lines go low, one of the 6-digit displays will be latched to the digit enabled by the Display Digit Enable lines. That’s a problem because this interrupt is the most-frequent thing that happens and the lines of U10A are used for other things, like sending the lamp data and enabling the switch banks.

The solution is the U10:CA2 line. It’s the one that we’re using for display blanking, but you can also see that it’s NOR’d with each of the latch lines. Any time that U10:CA2 is high, the latches won’t work. The only way to latch data to the displays is to drop U10:CA2, which is the same way that we blank the displays. To prevent garbage from being inadvertently latched onto the displays when we write out to the lamps, these latch lines require CA2 to be low as well. Here’s a picture of the blanking signal versus data that’s on the U10A:PA0 line.

db0_blnk (resized).jpg
You can see the PA0 line being used for different things (lamp writes, mostly), but then it is set during the blanking in order to control the displays.

It’s important that the displays are updated regularly. A slight variation in the timing of the updates will appear as a flicker. Therefore, when we get into the 120 Hz interrupt, which takes a good chunk of time to complete, you’ll see parts of that ISR where interrupts are unmasked to allow the display interrupt to take control again. Because of this, the 320 Hz Display ISR has to be careful to not disturb shared lines. You’ll see things like this in the code:

// Backup U10A
byte backupU10A = BSOS_DataRead(ADDRESS_U10_A);

// Disable lamp decoders & strobe latch
BSOS_DataWrite(ADDRESS_U10_A, 0xFF);
BSOS_DataWrite(ADDRESS_U10_B_CONTROL, BSOS_DataRead(ADDRESS_U10_B_CONTROL) | 0x08);
BSOS_DataWrite(ADDRESS_U10_B_CONTROL, BSOS_DataRead(ADDRESS_U10_B_CONTROL) & 0xF7);
The lamp decoders are latched to 0xFF, which is an unused address so they won’t get garbage written to them by this routine. At the end, U10A is restored to what it was before we came in:

// Restore 10A from backup
BSOS_DataWrite(ADDRESS_U10_A, backupU10A);
With the ISR implemented on the Arduino, here’s how the timing signals look on the new architecture.
myblnk_1 (resized).jpg
myblnk_5 (resized).jpg
I’ll skip to the end and show the latch of the 5th display. Note the jagged edge on the beginning of the interrupt. This is one bad part of the Arduino implementation. There are a couple of places in the 120Hz handler where interrupts are masked and the routine is delaying (like to let the voltage get high enough for the lamps). Because of this delay, the timing of the displays can shift by 20µS or so.

#6 6 months ago

Compared to the displays, writing to the lamps is fairly easy. Well, that’s not exactly true. It’s fairly easy once you understand how they’re using the MC14514 chip, which actually took me a little while to figure out. My first attempts weren’t working at all, and it wasn’t until I was trying to explain it to my friend that I finally had a revelation. The lamps states are written four at a time on U10A:PA0-PA3, addressed to one of 16 banks by U10A:PA4-PA7, and then strobed into place by U10:CB2. Super easy! The bank addressed by leaving U10A:PA4-PA7 all high is unused so there’s a place to park the address lines when you want to use the lines for other things.

Unfortunately, that’s not exactly how it works. I was picturing this like four bits were being latched off to control the lights. In essence, that’s the way it works, but the MC14514 is not a latch. This chip is a 4->16 memory decoder, like you might use to chip-enable one of 16 memory chips. The “address” is latched on the decoder chip when U10:CB2 is strobed, but the real latching actually occurs on the silicon-controlled rectifiers (SCRs) on the lamp board. Unlike regular transistors (which turn off when you stop biasing them), the SCRs will stay “on” until there’s no more current coming in through the anode. Once a lamp is turned on, it stays on for as long as there is current being sunk into to through the lamp. But our lamps are lit by rectified AC voltage, so it drops to zero volts 120 times a second. So when we “turn on” one of the SCRs, it automatically stays on for (at most) 8.3 milliseconds.

This is how it works: we send lines U10A:PA4-PA7 out to the lamp board as the “address” and lines U10A:PA0-PA3 as data. All the address lines are shared by the four MC14514’s on the lamp board and each of the data lines goes to one chip’s “Inhibit” lines. When the inhibit line is high on the MC14514, all the outputs will be low.

First, an “address” is latched to the MC14514, which tells the MC14514 decoder chip which line we want to go high (0-15). Then, if the inhibit line is set low, that one line will go high and the SCR will be biased so it will turn on. If the inhibit line stays high, the lamp’s SCR is never activated. The lamp then stays on for 8.3 ms, until the supply current drops to zero and it loses its latch.

Timing of this process is crucial. We want to turn on the lights when the voltage is as low as possible so that there’s not a huge inrush of current into a cold bulb. However, if we try to latch the SCR too early, it won’t hold its value because it needs a certain amount of current through the anode in order to stay on. Reading the datasheet for the 2N5060 (the SCR), it looks to me like it requires 20mA through the anode to turn it on and 5mA to hold it. Assuming that the switched illumination bus has 5.7V and the resistance of a cold bulb is (maximum in my small sample set) is 4.4Ω, it should take about… I don’t know. I tried to calculate how much time it would take for enough voltage to appear after the zero crossing, and I came up with about 50µs. Looking at this scan from the actual running machine, it appears that the original designers wait almost a full millisecond after zero crossing before they send out the first lamp strobe.

str_120 (resized).jpg
The sixteen yellow spikes are the lamp strobes on U10:CB2. The other jumps are from the 320Hz interrupt when it latches 0xFF to the lamp bus to make sure that lamps don’t get stomped during a higher-priority interrupt.
For my own implementation, I started with no delay between the zero-crossing interrupt and the lamp writes. Of course, the lamps didn’t come on (there wasn’t enough voltage to the anode yet to latch the SCRs). It wasn’t until I put a delay in there that I could actually get values to stick. Experimentally, this is a scope of the minimum time I could wait (below).

my_lamp (resized).jpg
I was able to shorten the delay to about 700µs after the zero-crossing before I can reliably latch the lights on. This might be a bit early, given the types of bulbs used. If you installed bulbs that had a higher cold resistance, the lights might not turn on every time. I might try some different bulbs to see what happens. I’m currently using #47 bulbs. Another note – currently, to achieve this delay, I’m counting clock cycles and waiting. This is a wasteful implementation, but I haven’t thought of a better wait to get the delay between the interrupt and when I need to latch the lamps. I could set a one-shot timer with a different handler, but I haven’t gotten that working yet. Anyway, because I’m waiting, I have to enable interrupts during the 120Hz interrupt handler, in case the display interrupt needs to fire. This means that sometimes my lamp strobes will be delayed while the Arduino is updating the lamps. That looks like this:
my_lamp1 (resized).jpg

U10:CB2 goes high just after zero-crossing interrupt because the ISR has been interrupted again to do an update to the displays, which delays the lamp strobes to almost 1ms, which makes it like the original M6800 code in the worst-case scenario.
You’ll also notice that my lamp strobes are tighter-packed that the original M6800 code. The Arduino is running 32x the speed of the M6800, so it can fetch and prepare data much faster. Checking the timing of the MC14514, my implementation is within speed tolerances, so I didn’t bother to space out my strobes any.

If you look at my implementation of the 120Hz ISR, you’ll see all kinds of interesting things happening before I strobe the lamps (during that “delay” period). After finishing the lamp implementation, I started working on solenoids and switches. Solenoids want to be turned off at the zero crossing to prevent back EMF from frying the solenoid transistor (I wrote that to sound smart, did it sound smart?). I don’t honestly remember where I read that, but I know for a fact that the M6800 code always turns off the solenoids near a zero-crossing and I’m sure that it’s to prevent wear on the solenoid driver circuits. Anyway, I use the “delay” time to do a couple of things. I turn off solenoids if they need to be turned off, and I read the switches. I’ll talk about those in detail in later sections, but I mention it here in case you’re looking at the code and wondering why I do so much before I strobe the lamps. The Arduino has 700µs to kill, so it might as well be doing something. In the original M6800 implementation, the switches are read after the lamps are strobed. Their 120Hz ISR also took 3.7ms to run. The Arduino implementation gets everything done in about 1ms. That’s a lot of time given that it runs every 8.3ms, but everything works.

One final note: reading about SCRs and how they’re used to drive lamps, I read that many implementations will toggle the bulb on/off at 60Hz in order to get a slightly dimmer lamp. I implemented that by having two sets of bulb values – on/off state and dim state. By OR-ing the dim state into the data every other time through the interrupt, the bulbs can be dimmed. I might put yet another dimming array to get four different brightnesses at 30Hz. I’m not sure what these will do to bulb life. If the game layer chooses not to use dimness, so be it, but it’s there if desired.U10:CB2 goes high just after zero-crossing interrupt because the ISR has been interrupted again to do an update to the displays, which delays the lamp strobes to almost 1ms, which makes it like the original M6800 code in the worst-case scenario.
You’ll also notice that my lamp strobes are tighter-packed that the original M6800 code. The Arduino is running 32x the speed of the M6800, so it can fetch and prepare data much faster. Checking the timing of the MC14514, my implementation is within speed tolerances, so I didn’t bother to space out my strobes any.

#7 6 months ago

The M6800 implementation of solenoids uses the 120Hz ISR to control the solenoids. They did this for several reasons:

* Time out the duration that solenoids are kept on. According to the Bally Theory of Operation document most solenoids need to be kept on for 3 zero-crossings in order to properly energize the solenoid. In reality, they power some of the solenoids for far longer. For example, saucer kickers need to be energized for at least four cycles (30+ ms), and drop target resets are energized for 120ms. Energizing a drop target reset for less time doesn’t guarantee that the targets won’t bounce back down.

* Solenoids need to be turned off close the voltage nearing a zero crossing so that the life of the transistors can be maximized.

* Some solenoids (slingshots and pop bumpers, for example) need to be energized as soon as a switch closes so that the response of the machine is quick.

For those reasons, the Arduino implementation controls the solenoids in the 120Hz ISR as well. To reproduce the M6800 timing, I captured several scope waveforms from the M6800.

sol1 (resized).jpg
This is Solenoid 1 (one of the chimes) being rung twice. You can see the solenoid line being held low (engaged) for three zero crossings and released on the fourth. To accomplish this, the M6800 code kept a countdown value that it decremented each time through the loop. When it got to zero, the solenoid line was released.

The way that solenoids are implemented on the MPU-100 and Bally AS-2518-17 uses four lines to engage one of 15 solenoid lines at a time. The sixteenth line (U11B:PB0-PB3 all high) is unused on the solenoid driver board. The solenoids can be prevented from triggering by raising U11:CB2, but I couldn’t find any time when that’s done. Perhaps I didn’t look hard enough. Maybe during a tilt? At any rate, as long as U10:CB2 is low, any combination of signals on U11B:PB0-PB3 will trigger a solenoid, but since this is a 4->16 decoder, only one solenoid at a time can be triggered. An example of that in a moment. First, here are some other solenoid signals so you can have a sense of how they work.

sol1_5x (resized).jpg
This is one of the chimes being rung 5x in a row (for a 500 score, I believe).

sol6 (resized).jpg
This is the outhole kicker serving up the ball.

sol8_120 (resized).jpg
This is a target bank being reset – note how long it takes (120ms) to make sure the targets get reset.

sol8_9 (resized).jpg
This is two target banks being reset at the beginning of a ball. Still 120ms each, but there’s a small gap between them since they were two different events that were programmed in (as opposed to events triggered by switches).

sol12 (resized).jpg
Pop bumper is held for 5 crossings to really engage it.

sol13 (resized).jpg
Slingshot is held for 4 crossings.

sol14 (resized).jpg
A different slingshot – this time for 5. Why? I’m not sure.

All these solenoid signals shown above pretty much engage the solenoid between the zero crossings and disengage the solenoid right at the zero crossing. Turning them off at a voltage close to zero makes perfect sense – you want to minimize the voltage when you turn off the transistors. I don’t know if turning them on when the voltage is high is a requirement. I don’t think so, but I haven’t found anything to say one way or the other. I believe that these solenoids are turned on between the zero crossings because the switch reads are the last things done in the 120Hz interrupt. That would account for the delay in turning them on. If I’m wrong and you know differently, please let me know.

As mentioned above, the hardware can only energize one solenoid at a time. What happens if you hit the switch on two slingshots at a time? With only one ball in play, it’s not likely, but with the glass off I tested it anyway.

sol14_15a (resized).jpg
The switch from one solenoid to the other is nearly simultaneous, and happens at the zero crossing (you’ll have to trust me on this).

To reproduce this behavior, I created a software solenoid stack. If the switches detect a high priority event (like pop bumpers or sling shots), they push solenoid fires to the head of the stack. The game layer pushes things like drop target resets and chimes to the tail of the stack. Because these machines have single ball play, the stack never gets very deep. In order to play melodies on the chimes, I also created a list of timed events that will happen in the future. When those events expire, they push events to the back of the solenoid stack as well. The Arduino pops solenoid events from the stack during the 120Hz ISR and engages the proper lines. This is done at the head of the ISR so the solenoid will be turned off as close to the zero crossing as possible. Here are some solenoid enable/disable waveforms from the Arduino implementation.

msol_0 (resized).jpg
Arduino firing Solenoid 0 to ring one of the chimes. Plus a special message from the scope!

msol_6 (resized).jpg
Arduino triggering the outhole kicker.

msol_8 (resized).jpg
Drop target bank reset for a little more than 120ms.

msol_12 (resized).jpg
Pop bumper hit. I guess I could hold it for 5? Seems to work this way, but it’s a few milliseconds shorter than the original M6800 implementation. I might try it for one more cycle.

msol_13a (resized).jpg
A slingshot hit.

I didn’t capture back to back solenoid fires, but they switch right at the beginning of the zero crossing signal.

The solenoids require very quick timing in order to make the game playable, since all of the slingshots and pop bumpers are run through software on this generation of games, so it was important to me to test and document all the cases to make sure that they were working just as before. Also, it's important to the life of the coils and driver board that things are shut off near the zero crossing.

#8 6 months ago

Reading switches is the easiest part of this process. The switches are organized into banks of 8, and each bank is energized by a different switch strobe. The lines U10A:PA0-PA4 are the strobes and the return values are read on U10B:PB0-PB7. A closed switch appears as a 1 and an open switch is 0. The DIP switch banks are read in the same way, but those are strobed by U10A:PA5-PA7 and U10B:CB2.

The switches are debounced in software to eliminate errant signals, and there’s enough documentation in the Bally Theory of Pinball to figure out precisely how that can be done. Over the course of three passes through the 120Hz interrupt, if a switch is read as Open, Closed, Closed, it is considered to be a valid switch hit. Any other combination is invalid. This helps the machine filter noise and only register one close event for each ball hit.

There’s also hardware helping to settle the signals on the switch returns. Each return line has a capacitor to ground that filters out the higher frequency signals. Because of these capacitors, the switch strobes have to be held high for a period of time before the capacitors will charge enough for the value to appear on U10B:PB0-PB7. Experimentally, I found that the switches can be reliably read after 80µs. Because we need to waste time before the lamps can be latched, it seems natural to put this switch-reading code before the lamp strobes in the 120Hz interrupt, although that’s not how the M6800 implementation works.

To accomplish the software debouncing, three states of the switches need to be compared: now, last time through, and two times ago. In the Arduino implementation, I refer to these as “SwitchesNow”, “SwitchesMinus1”, and “SwitchesMinus2”. To find valid closures, we only need to look for (SwitchesNow & SwitchesMinus1) & ~SwitchesMinus2. If that byte has a value, then one of the switches in the bank has a valid closure. To pass that event to the game loop, the Arduino implementation uses a switch stack.

One exception to the software debouncing is made for the solenoids that need really fast reactions (pop bumpers and slingshots). For switches associated with those solenoids, as soon as a switch moves from Open to Closed, the solenoid is triggered. If the signal turns out to be noise (the next read shows it Open again), then the solenoid is turned back off immediately. To accomplish this, the Arduino implementation flags “Starting Closures” and “Valid Closures” and puts solenoid hits on the stack right in the ISR.

swoff_120 (resized).jpg
These lines are used by so many things, that it’s hard to see the signals clearly. The blue line above is the zero-crossing signal. The yellow trough is where a switch return would be if the switch were closed.

sw_120 (resized).jpg
Here’s that same line with a switch closed. It’s easier to see live.

My apologies that I didn’t trigger off the same 120Hz line that I showed on the previous captures. There’s no good way to clip onto that line and I got tired of holding the probe to U10:Pin18.

It’s easier to see the switch read in the Arduino implementation because U10B:PB0 is cleaner and the switch read happens very early in the 120Hz ISR.

mswoff (resized).jpg
The yellow trough is where there’s no information being returned (no closed switches) on U10B:PB0.

mswon (resized).jpg
Here’s a closed switch, putting a signal in that trough.

#9 6 months ago

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
BSOS_InitializeMPU
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:

https://github.com/MrEkted/BallySternOS

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.

#10 6 months ago

As mentioned above, this code runs on an Arduino Nano and the whole board can be built for under $10 and plugged into J5 of a Stern MPU-100 or Bally AS-2518-17.

With that, it's pretty simple to build a state machine on top of the "OS" that I've written so you can put any rules you like on an early SS game.

This whole project is also documented here:
http://ikehamill.com/2020/03/21/replacing-the-m6800-with-an-arduino-nano/

I hope someone finds this interesting or gives it a try.
Here's a video of my Stars with an early version of the new rules:

The software has progressed since then. I'll make a new video when I get a chance.

#11 6 months ago

Well.. thats a lot of interesting stuff to read

#12 6 months ago

Here are the new rules I've written for Stars:

New Goal: Completing Levels of Stars – to deepen the action with the Stars stand-ups, I introduced a concept of levels. All stars start unlit and the right spinner is worth nothing. Hitting each star awards 500+bonus, and lights them to a dim state until all 5 are hit. Once all stars are dim, a roving red light appears next to each star, working its way around the board. That roving red light stays on until it’s hit or until the end of the ball. Hitting it when lit locks in that level and lets the player start on the next level (level 2 is full brightness, and level 3 is blinking). Locking in the third level finishes the stars goal and makes all the red special lights blink in time with the stars. At this point, the spinner is worth 3k per spin (200 for each star at each level). At the end of the ball, any star levels that are locked (by completing the roving light) are retained. Any level not locked is lost and has to be re-qualified the next ball.

New Goal: Pop Bumper – hitting the pop bumper 25x lights the rollover for bonus collect. Hitting it 50x lights the inlane/outlane bonus lights. Hitting it 100x completes the pop bumper goal and the inlane/outlane lights flash to indicate that it’s done. Pop bumper progress is retained through all balls in the game.

New Goal: Drop Target Completion – the drop targets still progress through 2x, 3x, WOW (extra ball or bonus), and Special (credit or bonus). Once the player has completed the drop targets four times and has the special lit solid, completing them again will flash the 400 lights on the left spinner as a hurry up. Hitting the spinner 25x while the 400 lights are flashing completes the drop target goal. This goal must be completed over the course of one ball and progress is reset at the end of the ball. If the goal is reached, it will be retained for the rest of the game.

Wizard Mode – when all three of the major goals have been reached, the player will enter a Wizard Mode. This mode has a 30-second ball save and while it’s running, all switches are worth 10,000 points. At the end of Wizard Mode, the ball save is turned off and the switches return to normal values. All goals are reset.

#13 6 months ago

I like where this is going. I've built a few Arduino projects to do heavy processing. I'm for sure not a programmer. I prefer the hardware side.

Keep posting and I will be reading.

#15 6 months ago

this is cool. I have a bunch of old boards too so I am looking forward to playing along

#16 6 months ago

Glad that you're all interested in this project.

I've made a newer video of the rules and features.

There are several things that this game can do now that it didn't do before--multiple levels of brightness on the bulbs, ball save, timers, etc. Even the Arduino Nano is 16x faster than the M6800, and it has plenty of code-space and RAM to spare. With a day or two of work, an experienced coder could reproduce the old rules of any of the compatible machines. Then it's easy to improve whatever you'd like.

With a little soldering, the whole thing can be built for less than $10. And because it has no dependence on U1-U9 on the MPU-100 (or Bally AS-2518-17) board, it can be used to resurrect many old MPUs that otherwise wouldn't even boot up. The Arduino also eliminates the need for a battery. Conceivably, this code could be modified to get rid of the dependence on the 555 timer to create the 320Hz interrupt as well, but I haven't implemented that yet.

#17 6 months ago

So much info to digest. I'll end up reading this a couple more times to get my brain wrapped around it.
This is a great project and really appreciate you taking the time to document it.
Thanks,
-Mike

#18 6 months ago

With non-chime solid state games, couldn't you just skip the sound board and add a shield that has sound capability? I know there are a few and you could use MP3 files for the prerecorded sounds. It would give a whole new life to sounds because you could create a whole library. A small amplifier and your in business.

#19 6 months ago

Great project, and I like the new rules.

#20 6 months ago

Great work!

#21 6 months ago

This is awesome! I just finished fixing up a Stars recently...i might have to give this a shot!

Thanks for taking the time to document all this! Amazing work!

#22 6 months ago

Great work! As an assembly level programmer for real time systems I can really appreciate the work involved.

#23 6 months ago

Great work! Excellent documentation!

#24 6 months ago

Very cool! There are so many old games that could really benefit from just a little more code depth like this.

#25 6 months ago

Thanks to everyone for the comments. I'm very excited that there is some interest in this project. As I wrap up the new Stars code, I'm planning on doing new rules for my Bally Black Jack.

I hope someone else tries this mod on their machine, and I'm more than willing to lend a hand with the coding if someone wants to try. Any machine with a Stern MPU-100 or a Bally AS-2518-17 can be fitted with no changes. Doing a Bally AS-2518-35 would be feasible, I think. I haven't studied that architecture, but I think it has relatively few changes. Recently, I've been working through the Gottlieb System 80 architecture and I've been trying to understand precisely how the RIOT chips are configured and used. It would likely take a week or two to reproduce that IO behavior, but if anyone wants to dive in on that I will go down that path too.

I guess the only question is, "Why?" It would be so much easier to replace the MPU board and have complete freedom with the code. For me, I suppose that I like the idea that everything is running precisely as it did back in 1978 with the exception of the processor. I've gone to great lengths to reproduce all the signals and timing that the M6800 outputted, but still managed to radically change the game. Plus, it's low-cost and doesn't prevent me from switching the chip back and selling a game if I want to.

So, please contact me if you're interested in giving this project a go. I'll help/collaborate in any way I can.

#26 6 months ago
Quoted from DickHamill:

Recently, I've been working through the Gottlieb System 80 architecture and I've been trying to understand precisely how the RIOT chips are configured and used. It would likely take a week or two to reproduce that IO behavior, but if anyone wants to dive in on that I will go down that path too.

Oh ya. I've been contemplating this too...but alas...not time for it.

Great work...I'm following this thread with great interest.
--
Chris Hibler - CARGPB #31
http://www.ChrisHiblerPinball.com/Contact
http://www.PinWiki.com - The Place to go for Pinball Repair Info

#27 6 months ago

Following with hopes of turning Kings of "Right Target" back into Kings of Steel. (AKA not so skewed to just hitting the right target)

#28 6 months ago

Nice work. Right up my alley. You provided a lot of good detail. At first glance, the arduino wouldn’t seem fast enough, but you aren’t reading the ROM and interpreting the instructions. I never thought about skipping the ROM and RAM.

Haven’t worked with the nano. Will have to get one. Is there any advantage to tapping into the processor socket instead of the expansion connector so that the IRQ can accessed?

#29 6 months ago

ejacques - Kings of Steel would be possible with some modifications. I've been looking over the 35 board and it uses an additional lamp strobe and has seven digits. Those additions would be simple. I'm not sure how it talks to the sound card. That could be a challenge depending on how many address lines are needed. I'm using nearly all of the Arduino pins just to talk to the PIA chips, so that's something that would have to be investigated. I'll work with you on the changes if you want to give it a shot.

#30 6 months ago
Quoted from DickHamill:

ejacques - Kings of Steel would be possible with some modifications. I've been looking over the 35 board and it uses an additional lamp strobe and has seven digits. Those additions would be simple. I'm not sure how it talks to the sound card. That could be a challenge depending on how many address lines are needed. I'm using nearly all of the Arduino pins just to talk to the PIA chips, so that's something that would have to be investigated. I'll work with you on the changes if you want to give it a shot.

Can you use a 3-to 8 decoder or 4 to 16 decoder to increase the # of pins (especially for single direction address pins) available? The sound board shares 4 lines with the solenoid driver signal so you have those already, then a separate sound interrupt line.

#31 6 months ago

geeteoh - I've thought about making a "compiler" to bring in the 6800 code, process it, and turn it into Arduino code. It would be a fun project to completely emulate the old chip with the new, but I'm not sure of the point (although who knows what the point of any of this is!).

When I started, I thought I would plug into J5 and just issue a HLT to the 6800 to tell it to freeze. That way I could leave it in the socket. I abandoned that idea when I discovered that I didn't have the IRQ line available, but I had already built the connector to J5. I've considered rebuilding to plug into the chip socket, but I was afraid that doing that might ream out the U9 socket and I like the idea of switching back to the 6800 if I want to sell a machine.

As far as speed goes, since the hardware IO is done in the ISR, the timing works out great. The Arduino is fast enough that I have to pause and wait for rising/falling edges of the clock so I don't blow through the chip timing signals. The 6800 was running at 0.5MHz and the Arduino is at 16MHz, so there is time to kill.

I'd love to replace the Nano with a Mega (tons more I/O lines) and write some test routines to full exercise a board. I did a similar thing with a Gottlieb System 80 (using 2 nanos) and I was able to find intermittent shorts and bad chips that way. On the Bally/Stern boards, it would be fun to write a suite of software to check every chip on the board, validate the 555 timer, the zero-crossing interrupt, and run the solenoids, switches, lights, and displays through their paces while sending all kinds of debug messages to a computer over the serial port. With some creative software, it's possible to guess which address/data lines might be shorted, although it's not possible to predict where. Most people I talk to want to just ditch the old boards and throw in new ones. I understand that - the machine is going to be much more reliable for the future, but I really enjoy fixing instead of replacing...

#32 6 months ago

Thanks slochar , I didn't realize that's how the sound was done. I see it now. You're right, I'm already talking to the 4 lines that send data to the sound. As for the interrupt, it's being sent to the M6800 by U11:CB1, so the ISR just needs to be updated to handle that request.

So, hardware-wise, this project doesn't require any changes to work with the Bally -35 board. It will plug into pins 1-32 of J5 and ignore pin 33 (A14).

For the software, the ISR needs to be updated to handle the aux lamp driver, the seventh display digit, and sound output (and interrupt) through U11.

If someone (maybe ejacques ?) has a -35 based machine and wants to give it a try, I can update the software with a compiler switch or something to build for the -35.

#33 6 months ago
Quoted from DickHamill:

So, hardware-wise, this project doesn't require any changes to work with the Bally -35 board. It will plug into pins 1-32 of J5 and ignore pin 33 (A14).
For the software, the ISR needs to be updated to handle the aux lamp driver, the seventh display digit, and sound output (and interrupt) through U11.

One other change with the 7 digit display -35 MPU boards is they run the display interrupt generator around 430Hz instead of 320hz for the 6 digit games.

See here:
https://pinside.com/pinball/forum/topic/two-bits-mpu-with-flickering-strobing-displays-#post-5408121
with photos of the updated timing resistor a few posts down:
https://pinside.com/pinball/forum/topic/two-bits-mpu-with-flickering-strobing-displays-#post-5455544

Quoted from DickHamill:

Kings of Steel would be possible with some modifications. I've been looking over the 35 board and it uses an additional lamp strobe and has seven digits. Those additions would be simple. I'm not sure how it talks to the sound card.

The "Cheap Squeak" sound board communication protocol is very likely the same as Bally games using the Squawk and Talk sound board where two nibbles are sent to the sound board over the four Solenoid/Sound select lines.

It's described here under "<Techie timing bit - crash helmets on!>":
http://www.pinballnews.com/learn/squawk.html

#34 6 months ago

Great info - thanks quench .

#35 6 months ago

I have a spare -35 MPU that I'll be rebuilding. I would love to give this a try. I have a bunch of new Nanos ready to go.

By the way, I have my one kits out for science class with kids when we run out of other ideas.

I'm not a programmer, but was able to built a very accurate audio Watt meter with multiple modes & display choices. This is the prototype.

And now back to the topic...
20200421_135957 (resized).jpg20200421_140230 (resized).jpg

#36 6 months ago

skidave If you can throw together a board that plugs into J5, you can use the wiring diagram up near the top of this thread. The only modification is that you'll ignore pin33 of J5. Let me know and I'll modify the code to talk to 7 digits and sound. We can stand something up to at least test that it's compatible. I don't see why it wouldn't be (but of course it will be!).

#37 6 months ago

DickHamill - will do. Working on an EM pinball now. Once that is finished, I'm on to the spare MPU. I have a Bally Supersonic that I would use this in. Not really a test machine, but the only SS at my house at the moment. Supersonic is a 6 digit game.

If others come along, please work with them and we will link up later. Thanks!

#38 6 months ago

Looking to collect peoples' thoughts.

I would like to take what DickHamill has put together and also add custom audio, if for nothing but to spruce up the music and sound effects on an old game.

I was looking around, and quality polyphonic options for the Arduino are quite limited. Easily $50 for a shield alone.

So I was thinking a better option for me would be to throw a raspberry pi into the mix, using the Arduino as a realtime controller and the raspberry pi for game logic, sound effects, any optional video, etc., with the two using serial communication over usb. A lot more capability at a price point similar to adding an audio shield.

Overkill? Better to look at other solutions at that point?

#39 6 months ago

https://robertsonics.com/wav-trigger/

$50 but pretty versatile and can stand alone. Could likely be triggered by adding some port expanders or using a different flavor of Arduino.

#40 6 months ago
Quoted from slochar:

https://robertsonics.com/wav-trigger/
$50 but pretty versatile and can stand alone. Could likely be triggered by adding some port expanders or using a different flavor of Arduino.

Thanks. I had also found this on sparkfun and it looked like one of the better options if I were to just add audio.

But, I was thinking that for $35 I can get a raspberry pi 4b w/ 2GB of RAM and open myself up to a lot more functionality/flexibility. And if I want to get really crazy I can upgrade to 4GB of RAM for only an additional $20.

This would obviously require reworking dickhamill's OS, which would seem to be the only downside, as I would be ahead in cost and capability.

Incidentally, I wonder how hard it would be to integrate with the Mission Pinball fwk...

#41 6 months ago
Quoted from joetechbob:

I wonder how hard it would be to integrate with the Mission Pinball fwk...

If you're going to start doing that then you're probably taking this beyond what the OP originally intended. If you want to control with MPF, you might want to take a look at LISY. That replaces the MPU board in your machine, can run the standard game code (it has pinmame embedded to do the control) and can also be connected to a computer running MPF. In that case it basically becomes a "slave" to MPF.

https://www.lisy.dev/lisy35.html

But I love what the OP is doing with this, hooking an Arduino to the board and trying to get it integrated with all the existing components is really clever

#43 6 months ago

joetechbob, I've thought about sound a bit and I have a couple of different ideas.
As others have pointed out, looking at the architecture of the -35, they talk to the sound card through the solenoid select lines. For a robust -35 implementation, I will likely be adding that, so it could be a path for sound in MPU100 or -17 boards as well. (using an off-the-shelf sound card or an Arduino-based implementation)

That said, I think a more robust solution would be this:
I'm currently not using the VMA line in my implementation, although I allocated Arduino:A5 for it. If VMA is tied low, Arduino:A5 could be used for MPU:A8, allowing the Arduino to index into addresses 0x0100 - 0x0193, which are unused in the Bally/Stern architecture. (not all those addresses would be usable, because only A8, A7, A4, A3, A1, and A0 would be connected).

Then, I would hook up a second Arduino (because they're cheap) or a rPi (as you mentioned), using only A8 as a Select for it, and then tying it in to read the data bus. So, strobing A8 would tell the sound daughter card (Arduino or rPi) to play whatever was queued by the 8 bits on the data lines. That would give the ability to play 256 different sound effects.

For the daughter card design, there would be plenty of lines still available to do this on a second Arduino. Using 8 lines and a ladder of resistors, I would do a d/a circuit. Samples could be stored as data arrays in flash. Background music would be a challenge, but voice/FX would be pretty straightforward.

I'd be happy to update my library to incorporate this update if you want to give it a shot.
If someone else has a better idea about the communication between the two cards, I'm open to it. I would steer away from i2c or other serial protocols because they can introduce non-interruptable lag that could make the realtime control of the hardware problematic. That's why I'm proposing a parallel interface as the connection. Even though it eats up 9 pins on the daughter card, I think it's worth it.

After the d/a, more power will have to be tapped from one of the other connectors for the sound amp.

Thoughts?

#44 6 months ago

I've been using the WAV Trigger in a S&T replacement board design with some success. I control the WAV Trigger serially after decoding the two nibbles from the -35 MPU board. I found that you cannot use the polyphonic feature on all sounds all the time - or you just get a huge sound mess. This is true of the stock programs. Voices need to play by themselves (one after another) and it sounds best if they stop any sounds (with the exception of the background) from playing.

#45 5 months ago

Thanks for the great info in that video, geeteoh - how do the nibbles get transmitted to the sound board on the -35? I see the 4 data lines and a select line (and an interrupt into U11:CB1?), but what's the mechanism? The schematic lists U11:CB2 as solenoid bank / sound select. I'm confused as to how the sound board knows when/which nibble to latch.

#46 5 months ago

U11-CB2 is used to signal data coming on the shared solenoid/sound bus. Low edge (signal going from high to low) signals the solenoid board to grab the data. High edge signals the sound board. For the sound board, this is called the SI (Sound Interrupt) line. I believe I got the high/low right. I did all my tests with a logic analyzer connected after the inverter on the sound board. So I am inverting all my notes here.

Once the Sound board sees the transition from low to high (high edge). It immediately grabs the first 4-bits from U11-PA7 through U11-PA4. The data is on the lines for about 130 microseconds. Then the next 4-bits come in. I usually grab the second 4-bits about 300 microseconds after the Sound Interrupt. If I wait 400 microseconds, the data is gone.

Technically, it doesn't matter which bit is MSB or which is the high nibble or the low nibble for the sound address. The sound board calls U11-PB0 as S0 (sound bit 0), U11-PB1 as S1 (sound bit 1), U11-PB2 as S2, and U11-PB3 as S3. The sound board has a S4 (fifth sound bit) but neither my Elektra nor MrMrsPac-Man have that wire in the harness. My guess is that they wanted to use all 6 input inverters in the chip for the Sound Interrupt (SI) and 5 sound bits (S0-4). I read the low nibble first, and the high nibble second.

What's strange is that you would think that there would be a lot of transitions on the solenoid/sound interrupt signal that the sound board would ignore, because there could be a solenoid command with no sound. But there isn't. I think this because all solenoid commands are followed with a valid sound command. But there has to be the opposite - sound commands with no solenoid action. The solenoid board probably has a way to ignore a command - maybe there is a NULL solenoid address. It would be easy for the sound board to do this as there are 256 sound addresses. Just pick one that has no sound. But... it doesn't. All sound interrupts are followed with a valid sound (from the two machines I have tested).

#47 5 months ago

Awesome, thanks geeteoh .
I didn't realize that the second nibble latch was just a timing thing. I figured there had to be some kind of signal to trigger the latch. That solves that mystery for me.

The solenoid board actually doesn't need the U11:CB2 to do anything except be low when solenoids are being triggered. U11:CB2 is hooked into the 74L154 G2 line, and if it's HIGH, all the outputs are masked. If it's low, then the solenoid addressed by U11:PB0 - U11:PB3 is decoded and energized (by pulling the line low). Nothing is hooked to the solenoid address 1111, so the lines are all made high when the machine doesn't want any solenoids on.

So, now that you clarified the sound board's latching strategy, it makes perfect sense to me that U11:CB2 would go high (which masks the solenoid output), so the data lines can be used without triggering random solenoid firings.

When I put a scope on U11:CB2 on an older machine (without a sound board), I never saw U11:CB2 go high during normal operation.

I will update my code to support a "PlaySound" function that takes a byte of data.
It will mask interrupts
Put the lower nibble on U11:PB0-PB3, and raise U11:CB2
Hold for 200 microseconds
Then put the upper nibble on U11:PB0-PB3
Hold for 200 microseconds
Lower U11:CB2
Unmask interrupts

This function should make my code base sound compatible for -35 boards.
I can wrap this function in a "COMPILE_FOR_DASH35" directive, but I don't really see the need. I don't think it will hurt anything if this is run on a -17 or MPU100.

I wonder if the 400 microsecond masking of the solenoids will disrupt gameplay? The solenoid signals aren't latched at all by the 74L154, so any momentary solenoid being energized when a sound call gets sent out will create a blip in the power. I wouldn't think it would matter, since solenoids are energized for 20~50 ms, so the scale is orders of magnitude different, but I wonder if any care was exercised so that sound calls would be held until momentary solenoids were between firings, or if they just let it rip?

#48 5 months ago

My 2 cents. I really like the simplicity of the DickHamill's Nano addition. A simple board with only a plug in simple micro-controller. It utilizes all the hardware and wiring that is already present in the machine. There is very little you need to make a custom game. It's a simple micro-controller that runs one thread instantly at power-up and can be turned off without a shutdown process.

The raspberry pi path has benefits, with it having more bandwidth to take over other features of the machine (better sound, additional light mods, linking, exporting scores) but it requires more machine modifications - which can be done with many other MPU replacement paths that already exist.

In my little world, I'd like to see individual replacement/upgrade boards for the stock Bally light boards, Bally solenoid boards, and Bally sound boards. Boards which function properly in a stock game, but add stuff for those with upgraded MPUs. Like my S&T sound replacement board. It would be cool to have a stock replacement light board that gave us additional lighting capability (like access to RGB LEDs as well) and relays for external lighting mods. Maybe a solenoid board that could control more items.

#49 5 months ago

I added BSOS_PlaySound(byte soundByte) to my library here:
https://github.com/MrEkted/BallySternOS

I don't have a -35 game, so I can't test the new functionality. If someone gets one up and running, please let me know if it works!

#50 5 months ago
Quoted from DickHamill:

Put the lower nibble on U11:PB0-PB3, and raise U11:CB2

U11:CB2 is raised high before any active sound data is placed on the U11:PB3-PB0 solenoid/sound select bits.
And before U11:CB2 is raised high, U11:PB3-PB0 solenoid/sound select bits are all set high to the "idle" state.

Promoted items from the Pinside Marketplace
$ 6.00
Cabinet - Other
Siegecraft Electronics
From: $ 5.00
Playfield - Toys/Add-ons
UpKick Pinball
$ 149.00
$ 149.95
Boards
Allteksystems
From: $ 155.00
From: $ 140.00
$ 5.00
Playfield - Decals
Doc's Pinball Shop
Wanted
Machine - Wanted
Detroit, MI
$ 9.00
Cabinet Parts
Third Coast Pinball
$ 49.99
Electronics
PinballElectronics.com
There are 129 posts in this topic. You are on page 1 of 3.

Hey there! Got a moment?

Great to see you're enjoying Pinside! Did you know Pinside is able to run thanks to donations from our visitors? Please donate to Pinside, support the site and get anext to your username to show for it! Donate to Pinside