mamedev / mame

MAME
https://www.mamedev.org/
Other
8.02k stars 1.99k forks source link

Simulate delay with strobed outputs #729

Open happppp opened 8 years ago

happppp commented 8 years ago

Simulate delay with strobed outputs an 'output' in this case is a MAME output port or pin, such as for a 7segled: output().set_digit_value(index, state);

The problem is really simple. We need to simulate outputs with (inherited) capacitance. There is a too often seen scenario where leds or lamps are strobed and if represented to the emulator user digitally, it will show as a flickering mess. This is the reason I have a custom output-decay handler in many of the handheld drivers.

What needs to be done is a solution in the back-end(HLSL etc?), or MAME output core, that leaves these outputs on for a user-defined interval, by default a few microseconds.

cuavas commented 8 years ago

LEDs don't behave like that though - you can use a high-speed camera
to capture LED multiplexing. LEDs have tens of kHz of bandwidth. LED
multiplexing depends on persistence of vision to no look flickery.
Games that use PWM for incandescent lamp outputs also need better
handling than what we have in MAME currently.

With LEDs fed by PWM, the bandwidth of the LED usually far exceeds the
pulse frequency, and persistence of vision means the brightness
appears proportional to the duty cycle. LEDs are actually a pretty
simple case.

With incandescent lamps you have thermal characteristics of the
filament to consider. As a simplification, the brightness is roughly
proportional to the square of the true RMS voltage applied. You also
get colour temperature shift towards yellow as you reduce the drive.

LCDs are different again, but I don't even pretend to understand them
well enough to give a summary.

A retriggerable monostable like you're suggesting is not a valid
simulation of any of these output devices' behaviours. I'd rather
stick with the flickery mess until such time as we try to model at
least some of these devices properly.

Vas

Quoting hap notifications@github.com:

Simulate delay with strobed outputs an 'output' in this case is a MAME output port or pin, such as for a
7segled: output().set_digit_value(index, state);

The problem is really simple. We need to simulate outputs with
(inherited) capacitance. There is a too often seen scenario where
leds or lamps are strobed and if represented to the emulator user
digitally, it will show as a flickering mess. This is the reason I
have a custom output-decay handler in many of the handheld drivers.

What needs to be done is a solution in the back-end(HLSL etc?), or
MAME output core, that leaves these outputs on for a user-defined
interval, by default a few microseconds.


You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub: https://github.com/mamedev/mame/issues/729

happppp commented 8 years ago

Yes you are mostly right, I think in most cases (leds, not lamps!) the capacitance is very small. But that means in practice we'd still need to filter from x KHz to typical 60Hz screen. I refuse to get rid of the workaround in the meantime that would make all handheld drivers unusable.

happppp commented 8 years ago

After a few moments of thinking: I know leaving the workaround in stubbornly has its cons. pro = me and users can play con = user is oblivious that there's a workaround. Devs are more reluctant to solve the real problem. It's a shame that in reality I'm the only person currently on the MAMEdev side working on the drivers at hand.

cuavas commented 8 years ago

No you aren't. It fucks up my 8-liners as well. But I have absolutely no clue how I could hack up the artwork system to do variable intensity on an analog output even if I could work out how to create a new output type that took PWM input and produced an intensity output.

rb6502 commented 8 years ago

This is a major issue for synths too.

james-wallace-ghub commented 8 years ago

And all the FME stuff could really do with it too, for many reasons, particularly a dimming and ultrabright effect known to be useful. I know time based hysteresis is done by some non open tools that could be MAME friendly if we want to go down that route, but I always wondered how cleanly that could be done.

On Saturday, 19 March 2016, R. Belmont notifications@github.com wrote:

This is a major issue for synths too.

— You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub https://github.com/mamedev/mame/issues/729#issuecomment-198697674

Sent from Gmail Mobile

GuitarAmber commented 8 years ago

Hey all, I was referred here by james. I have a solution that works quite well for FME, but it's not perfect.

It's best to split the problem into 2 separate things, 1) How to determine the brightness of the bulb / LED, & 2) how to display that effectively.

Part 1 is quite straightforward, though if someone has any decent maths / physics skills, I could make the algorithm more accurate. At the moment I calculate the brightness linearly, but I am almost positive it should be logarithmic.

Part 2, there are various solutions, I'm now using DirectX / HLSL. I'm currently developing my emulator and the more advanced lamping is on the todo list, however I already know what I need to do, and I have dimmed & super bright lamps simulated already. But displaying artwork on a screen is very subjective, what looks perfect to someone may look poor to another person. But with HLSL / Directx / OpenGL there are many many solutions. If anyone is interested I am willing to discuss it at length, but it's a much bigger subject than it may first appear. It all depends on the level of realism you want, i've been toying with a raytracer and 3D model, though I must admit it's taking some time for all the relevant maths to sink in. But you can create a decent effect quite simply with a standard texture shader.

So, determining the brightness of the bulb... The method I am describing is based on a standard 8 * 8 matrix, though the principal works just the same for a 16 * 16 matrix by changing a small amount of code.

Let's consider a single bulb attacted to the matrix. Here is the method I use, I know it isn't correct physically, and if anyone here dabbles in physics and would like to chime in, please do. But it does work and looks pretty good.

For each bulb you need to know whether it currently has voltage applied, and how long the voltage has been applied for. If it doesn't have voltage applied, you need to know how long it has been since voltage was last applied. So I keep 2 counters, TimeOffCycles and TimeOnCycles. I track everything in cpu cycles rather than work it out into seconds. Then I use 2 values. GlowCycles, and FadeCycles. GlowCycles is the number of clock cycles that elapse in the time it would take the lamp to go from completely off, to fully on if powered continuously. FadeCycles is the number of clock cycles that elapse in the time it would take to go from fully on, to fully off.

FadeCycles is always a bigger value for an incandescent lamp. The system I developed it on ran at 1.72MHz, and the values I used were; GlowCycles 21000, FadeCycles 111000; How correct this is I don't know, but it looked right to me.

So, using the 4 values (the counters, and the Fade/Glow)... If the lamp is currently powered (strobe is on, data is on), then Offcycles should be reset to 0, and OnCycles should be incremented. Calculate the brightness by comparing the OnCycles to the GlowCycles. If OnCycles is greater than or equal to GlowCycles then the lamp is at full brightness, otherwise I find the value on a linear scale. I store the Maximum brightness value that the lamp got to while under power as we'll need it later.

If the lamp is currently not powered then I set the brightness on a linear scale of how long the lamp has been off for, from the maximum brightness it got to before. Once the lamp has been off power for greater than OffCycles then the lamp is completely off.

To save CPU cycles I keep a single variable TimeSinceDataChange, which is incremented every instruction with the number of cycles executed. And then add this value to either the OnCycles or OffCyles for each lamp as appropriate at each data write. This saves updating all the lamps every instruction cycle as in FME we deal with up to 512 lamps. I also used to update everything at each change of the MUX Lines but that just seemed to make everything more flickery. How you do this will obviously depend on the specifics of the game hardware.

That's pretty much it. If none of that makes sense please ask away and I'll have another go. The only improvement I can see would be to calculate the correct curve for the lamp brightness going from off, getting brighter, and then saturating (I'm a bit dodgy with physics, is that the correct term?).

I'll cover part 2 if anyone is still interested.

Lord-Nightmare commented 8 years ago

This is actually useful for VFD DMD displays on arcade and pinball games as well. As a friend and I discovered when looking at someone's 'Checkpoint' machine ( http://www.ipdb.org/machine.cgi?id=498 ) the DMD needs persistence or it doesn't look right. This is plainly evident on the machine we looked at since the machine used a poorly designed repro LED-based DMD replacement which uses the original display Z80 and ROM, and does NOT emulate the original VFD persistence properly, so the LED-DMD flickers like hell.

A proper DMD replacement would use an FPGA or similar to 'fake' (using pwm) the VFD persistence on each LED. Can use the original z80 code too.

couriersud commented 8 years ago

Just apply a low-pass filter of about 60Hz to 120Hz. That's what our eyes effectively do:

http://www.nature.com/nature/journal/v299/n5883/abs/299553a0.html https://en.wikipedia.org/wiki/Flicker_fusion_threshold

For incandescent lamps there are spice models:

https://groups.google.com/forum/#!topic/sci.electronics.cad/I9QfewHAaSI

This one has nice graphs: www.ee.bgu.ac.il/~pel/pdf-files/conf104.pdf

However, these models are pretty complicated. The following link discusses a simpler model using two resistors and one capacitor.

http://www.candlepowerforums.com/vb/showthread.php?117367-models-for-incandescent-bulbs

This could easily be done using netlist.Alternatively, one could model the differential equations given e.g. in conf104.pdf in a time-stepping approach.

On 03/19/2016 09:01 AM, hap wrote:

Yes you are mostly right, I think in most cases (leds, not lamps!) the capacitance is very small. But that means in practice we'd still need to filter from x KHz to typical 60Hz screen. I refuse to get rid of the workaround in the meantime that would make all handheld drivers unusable.

— You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub https://github.com/mamedev/mame/issues/729#issuecomment-198663149

GuitarAmber commented 8 years ago

The low pass filter wont help with persistance. The pdf with the graphs looks quite useful. But possibly only half the story. How would that translate for a 12v 0.1W bulb being driven by 48v AC but multiplexed to give 12V rms voltage, rather than 12v DC. I never got to look into this properly before, I'll have a play with the maths at some point in the next few days / weeks and see what I can come up with.

Quench0 commented 8 years ago

Just wondering is this flickering partly due to the rate at which MAME renders layout items when there's no "screen". It defaults to 60hz I think. I updated the Bally early solid state Pinball driver last year which uses multiplexed 7 segment displays that are updated at 320Hz, when I implemented the blanking signal the displays flickered like crazy. At the time I put it down to a sync issue with Mame rendering the layout display at 60Hz while the driver was updating its display digits at 320Hz. Am I off the mark? Is there some other better solution in this case I've missed?

couriersud commented 8 years ago

How would that translate for a 12v 0.1W bulb being driven by 48v AC but multiplexed to give 12V rms voltage, rather than 12v DC.

If you model the bulb with netlist: perfectly.

GuitarAmber commented 8 years ago

I've been doing some thinking and reading and I think I've realized what's going on.

On a particular FME tech (MPU4) they dim the lamps by playing with the timings of the multiplexer. My solution works in most cases, but not all. I've finally realized why and what they are doing. They are using the multiplexer to apply a square wave rms value on top of the 48v (12v rms). It's only in reading those links that I've really clicked what they were doing with the hardware and why in some cases it didn't work.

Its also helped to make some things clear, and to refresh my memory in this area. So 12v DC is treated the same as 12v rms, that makes sense.

Sorry if I am cluttering the thread, I'm sort of thinking out loud here as I'm now seeing things with a little more clarity.

GuitarAmber commented 8 years ago

Just wondering is this flickering partly due to the rate at which MAME renders layout items when there's no "screen". It defaults to 60hz I think. I updated the Bally early solid state Pinball driver last year which uses multiplexed 7 segment displays that are updated at 320Hz, when I implemented the blanking signal the displays flickered like crazy. At the time I put it down to a sync issue with Mame rendering the layout display at 60Hz while the driver was updating its display digits at 320Hz. Am I off the mark? Is there some other better solution in this case I've missed?

LED's and Lamps do not go off the moment the power is disconnected, there is a short period where they fade out. The time that takes varies for different LEDs, but a typical value is around 1/20 of a second. This capacitance gives time for the multiplexer to go round and set all the other lamps and get the original lamps under power again before they go out.

If you drive your simulated/emulated lamps directly from the digital signal your lamps will only ever be on (1 / [however many lamp selects]) of the time. The easiest and simplest method of getting round this is to have a single counter. Any time the lamp is powered reset the counter to whatever value. When the lamp is off, decrement the counter (whether you do this in seconds, frames, or clock cycles is up to you). If the counter reaches 0, your lamp is off, any other time the lamp is on.

You'll find the counter value easily with a little trial and error. Too low and lamps will flicker, too high and lamps will stay on as longer than they should.

That's by far the simplest way of doing it, and it'll work just fine, but its not the way it works for real. However it is very much tried and tested. It'll certainly remove the annoying on/off flickering.

cuavas commented 8 years ago

GuitarAmber, what you're saying is just plain not true. LEDs do extinguish pretty much instantaneously - they definitely won't persist for 1/20s unless you actually wire a capacitor across them. You can observe this yourself with a consumer 1,000fps camera pointed at a multiplexed LED display (e.g. the digital LED displays on Yamaha synths and stage pianos). Incandescent lamps fade gradually, but it has nothing to do with capacitance: it's because the filament takes time to physically cool (conducting/radiating heat away). Please stop posting misinformation.

GuitarAmber commented 8 years ago

Hmm, ok well I stand corrected.

First of all I meant persistance, not capactiance. I accidentally a word, so forgive me. Secondly you are absolutely right, LEDs do go off almost instantly. I did the solutions for the lamps in my emulator and when I did the LED segments I encountered the same flickering, so I solved it the same way, which worked just fine. However having thought it through, prompted by yourself, you are right, LED's are an entirely different problem, which under the right conditions have the same (but incorrect) solution.

So I apologise, what I said only works for lamps, not LEDs, but please see that I am trying to contribute and not trying to spread misinformation.

The whole point of this thread was a discussion on multiplexed outputs and their emulation / simulation solutions. I don't know everything and I did say that in my initial post, I am here to be a part of the solution and that involves some learning. There's no need to be quite as defensive, its a friendly chat.

GuitarAmber commented 8 years ago

Having re-read the thread, maybe shifting the discussion to the graphical side of the simulation would be beneficial. At the end of the day you can have the most accurate mathematics possible, if you can't get that onto the screen sensibly, then it's useless. What do you want to see on the screen as the end result?

MooglyGuy commented 8 years ago

Have an interface class called persistence_parameter that has an update() function taking a floating-point analog on/off value (1.0 being fully on, 0.0 being fully off)and an attotime_t time stamp for that particular update. The interface class's implementation will be pure virtual. Then for each type of on-screen item representing a physical element that needs some form of persistence (a specific type of LED, a specific model of incandescent bulb), the specific persistence_parameter subclass's implementation will implement a smooth() function that updates an internal floating-point value that contains a smoothed (i.e., a low-pass-filtered) representation of the updatable parameter since the last call to smooth(). If the parameter was 1.0 the whole time since the last call to tick(), the output value is 1.0, and correspondingly for 0.0. For anything in-between, the ramp-up and ramp-down are handled as necessary by the device.

The end result of a given persistence_parameter subclass's smooth() function would then be used as an alpha value when rendering the UI element, or could be additionally used by a BGFX shader eventually.

GuitarAmber commented 8 years ago

You may want to include values greater than 1.0 though. I am only really familiar with FME games, I was never a big video arcade fan. But I know for certain that some games in FME would alter the mux to hold the power on slightly longer resulting in a super bright flash. If it was done in FME then I bet at least a few video arcade games will have used this. An easy way to display this on screen is to oversaturate the image.

This was only done to Lamps, not LEDs. Maybe separate discussions of LED's and Lamps would be an idea? They are inherrently different problems to solve despite seeming similar at first glance.

For lamps there seems to be 2 parts of the problem to get a reliable brightness value. First you need to calculate the rms Voltage. For this you need to calculate the period of a cycle, and to keep a count of how long the data line is high for. If you have a mux that's constantly going in a loop 0,1,2,3... and so on, you can use the timings from that. For instance to keep track of strobe line 0. When the mux changes to 0, start the counter, when the mux comes round again to 0, stop the counter. Given the two values you can easily calculate the rms if you know the input voltage of the system. The rms value is the voltage the lamp is aiming for. By keeping track of the previous rms voltage calculation and the known time period you can then work out if the lamp can reach the current aimed for voltage in that time period. If yes, then store the rms value to be used as previous next time. If no, calculate the voltage that could be reached from the previous rms value while aiming for the current rms and store that value. On hardware which changes multiplexer bits 1 at a time you will need to 'smooth' the output and only change the multiplexer value when it steps in sequence.

Time to write some code I think.

felipesanches commented 8 years ago

I strongly support the idea presented in the original description of this issue. I have faced the same kind of trouble when writing drivers for devices that use multiplexed displays. One example is the Minicom IV driver.

Here's a video of it running: https://www.youtube.com/watch?v=Mv7bSd9cOvY&html5=true

Even though it looks good, I have the feeling that this is not really the correct emulation for the display because it does not take fading of the segments into account. I could conceive of alternative firmware for the device that would result in diverging visual rendering in the emulator in comparison to the real device.

GuitarAmber commented 8 years ago

Does the Minicom IV drive the segments directly or is it using a VFD driver chip?

felipesanches commented 8 years ago

it uses a shift register to shift-in the multiplexing signal that sequentially selects each of the digits in the display. Then there is a shared set of bits that defined the pattern to be displayed in the currently selected digit. Actually there is also a second layer of multiplexing, since each digit is a 14-segment display, so this extra bit selects which half of the display the pattern refers to.

GuitarAmber commented 8 years ago

I have been working on getting the values coming out of this correct. So I can get the time taken to get to a given temperature. But I need to reverse it to get the temperature after a given time. Can anyone help?

`void GraphicsClass::SetInitialValues(void){

//The values in the data sheet for these bulbs (10mm 12v 1.2W Wedge Bulb) @ 12v (google "CML 1112LF" for info)
//          Current Lumens      
//Max       110mA   6.25
//Nominal   100mA   5.0
//Min       90mA    3.8

//Filament Specifics
F_Length = 0.0007;                                      //Filament length (Meters)
F_Radius = 0.00005;                                     //Filament radius after winding (Meters)
F_Surface__Area = (((F_Radius * 2) * M_PI) * F_Length); //Filament Surface Area (Meters)
F_Density = 1.960 * pow(10, 4);                         //Filament Density (Kg/M3);
F_Mass = (F_Density * F_Surface__Area)  ;               //Filament Mass (Kg)        
F_Voltage = 9.0;                                        //Filament Input Voltage (Volts)
F_Cold_Resistance = 14.2;                               //Filament Resistance at Ambient Temp (Kelvin)
F_Cold_Temp = 294.65;                                   //Filament Ambient Temp 21.5 degrees celcius (Kelvin)
F_Nominal_Power = 1.2;                                  //Filament Power during steady state operation(Watts)
//Physical Values
Gas_Constant = (8.3144598 * pow(10, -5.0));             //Gas Constant (Jules/Moles Kg)
Debeye_Constant = 400;                                  //Debeye Temperature of Tungsten(Kelvin)
//These coefficients may need changing
A = (4.5549 * pow(10.0, -3.0));                         //Coefficient A
B = (5.77874 * pow(10.0, -10.0));                       //Coefficient B 
Exponent = 1.2285;                                      //Exponet

}

double GraphicsClass::GetTime(double TemperatureIn){

double ret, part1, part2, C, C1, C2;

//*** Time (Seconds) as function of Temperature (Kelvin) ***

//Calculate Heat Capacity (Jules/Kg Kelvin)
C1 = (1.0 - (pow(Debeye_Constant, 2.0) / (20.0 * pow(TemperatureIn, 2.0))));
C2 = pow(TemperatureIn, 3.0);   
C = (3 * Gas_Constant * C1) + (2.0 * A * TemperatureIn) + (4.0 * B * C2);   

//Calculate Time Taken to Reach Given Temperature (Seconds)
part1 = ((F_Cold_Resistance * F_Mass * C) / pow(F_Voltage, 2.0));
part2 = pow((TemperatureIn / F_Cold_Temp), Exponent);   
ret = (part1 * part2);

return ret;

}`

happppp commented 5 years ago

I wrote a PWM display device a few weeks ago and hooked it up to my screenless drivers. It has solutions to the most common problems:

https://github.com/mamedev/mame/blob/master/src/devices/video/pwm.cpp