diff --git a/src/win32com/Controller.cpp b/src/win32com/Controller.cpp index 0ea86b1ed..ea4780bf6 100644 --- a/src/win32com/Controller.cpp +++ b/src/win32com/Controller.cpp @@ -8,6 +8,10 @@ 01/08/12 (SJE): Added Warning message for game flagged with IMPERFECT SOUND 01/09/26 (SJE): Added Check to see if Invalid CRC games can be run. 03/09/22 (SJE): Added IMPERFECT GRAPHICS FLAG and reworked code to allow more than 1 message to display together + + PinMame runs on another thread than the controller without blocking synchronization primitives. Therefore when getters are called, + the return value is the last known state. For lazy updated values like physic outputs, getters also trigger an update that will be + serviced asynchronously. The theory of operation is that these getters are to be called repeatedly to update/get the actual value. */ // Controller.cpp : Implementation of Controller and DLL registration. @@ -396,8 +400,10 @@ STDMETHODIMP CController::get_Lamp(int nLamp, VARIANT_BOOL *pVal) if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) *pVal= false; - else + else { + core_request_pwm_output_update(); *pVal = vp_getLamp(nLamp)?VARIANT_TRUE:VARIANT_FALSE; + } return S_OK; } @@ -411,8 +417,10 @@ STDMETHODIMP CController::get_Solenoid(int nSolenoid, VARIANT_BOOL *pVal) if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) *pVal= false; - else + else { + core_request_pwm_output_update(); *pVal = vp_getSolenoid(nSolenoid)?VARIANT_TRUE:VARIANT_FALSE; + } return S_OK; } @@ -485,6 +493,8 @@ STDMETHODIMP CController::get_Lamps(VARIANT *pVal) if ( !pVal ) return S_FALSE; + core_request_pwm_output_update(); + SAFEARRAY *psa = SafeArrayCreateVector(VT_VARIANT, 0, 89); VARIANT* pData; @@ -858,6 +868,9 @@ STDMETHODIMP CController::get_ChangedLampsState(int **buf, int *pVal) if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) { *pVal = 0; return S_OK; } + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + /*-- Count changes --*/ vp_tChgLamps chgLamps; int uCount = vp_getChangedLamps(chgLamps); @@ -891,6 +904,9 @@ STDMETHODIMP CController::get_LampsState(int **buf, int *pVal) if (!pVal) return S_FALSE; + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + /*-- list lamps states to array --*/ int *dst = reinterpret_cast(buf); @@ -924,6 +940,9 @@ STDMETHODIMP CController::get_ChangedSolenoidsState(int **buf, int *pVal) if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) { *pVal = 0; return S_OK; } + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + /*-- Count changes --*/ vp_tChgSols chgSol; int uCount = vp_getChangedSolenoids(chgSol); @@ -958,6 +977,9 @@ STDMETHODIMP CController::get_SolenoidsState(int **buf, int *pVal) if (!pVal) return S_FALSE; + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + /*-- list lamps states to array --*/ int *dst = reinterpret_cast(buf); @@ -993,6 +1015,9 @@ STDMETHODIMP CController::get_ChangedGIsState(int **buf, int *pVal) if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) { *pVal = 0; return S_OK; } + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + /*-- Count changes --*/ int uCount = vp_getChangedGI(chgGI); @@ -1245,6 +1270,9 @@ STDMETHODIMP CController::get_ChangedLamps(VARIANT *pVal) if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) { pVal->vt = 0; return S_OK; } + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + /*-- Count changes --*/ int uCount = vp_getChangedLamps(chgLamps); @@ -1286,6 +1314,9 @@ STDMETHODIMP CController::get_ChangedLEDs(int nHigh, int nLow, int nnHigh, int n if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) { pVal->vt = 0; return S_OK; } + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + /*-- Count changes --*/ int uCount = vp_getChangedLEDs(chgLED, mask, mask2); @@ -1340,6 +1371,9 @@ STDMETHODIMP CController::get_ChangedLEDsState(int nHigh, int nLow, int nnHigh, if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) { *pVal = 0; return S_OK; } + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + /*-- Count changes --*/ int uCount = vp_getChangedLEDs(chgLED, mask, mask2); @@ -1428,7 +1462,12 @@ STDMETHODIMP CController::put_Mech(int mechNo, int newVal) STDMETHODIMP CController::get_GIString(int nString, int *pVal) { if (!pVal) return S_FALSE; - *pVal = (WaitForSingleObject(m_hEmuIsRunning, 0) != WAIT_TIMEOUT) ? vp_getGI(nString) : 0; + if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) + *pVal = 0; + else { + core_request_pwm_output_update(); + *pVal = vp_getGI(nString); + } return S_OK; } @@ -1446,6 +1485,9 @@ STDMETHODIMP CController::get_ChangedGIStrings(VARIANT *pVal) { if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) { pVal->vt = 0; return S_OK; } + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + int uCount = vp_getChangedGI(chgGI); if (uCount == 0) @@ -1489,6 +1531,9 @@ STDMETHODIMP CController::get_ChangedSolenoids(VARIANT *pVal) if (WaitForSingleObject(m_hEmuIsRunning, 0) == WAIT_TIMEOUT) { pVal->vt = 0; return S_OK; } + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + /*-- Count changed solenoids --*/ int uCount = vp_getChangedSolenoids(chgSol); @@ -1547,6 +1592,9 @@ STDMETHODIMP CController::get_Solenoids(VARIANT *pVal) if ( !pVal ) return S_FALSE; + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + SAFEARRAY *psa = SafeArrayCreateVector(VT_VARIANT, 0, 65); VARIANT* pData; @@ -1593,6 +1641,9 @@ STDMETHODIMP CController::get_GIStrings(VARIANT *pVal) if ( !pVal ) return S_FALSE; + /*-- Request an update that will be processed asynchronously --*/ + core_request_pwm_output_update(); + SAFEARRAY *psa = SafeArrayCreateVector(VT_VARIANT, 0, CORE_MAXGI); VARIANT* pData; diff --git a/src/wpc/core.c b/src/wpc/core.c index 182fcaad9..5edd80651 100644 --- a/src/wpc/core.c +++ b/src/wpc/core.c @@ -677,6 +677,7 @@ static tSegData segData[2][18] = {{ /-------------------*/ static struct { core_tSeg lastSeg; // previous segments values + UINT8 lastSegDim[CORE_SEGCOUNT]; // previous segment dimming level int displaySize; // 1=compact 2=normal tSegData *segData; // segments to use (normal/compact) void *timers[5]; // allocated timers @@ -690,8 +691,12 @@ static struct { UINT8 lastLampMatrix[CORE_MAXLAMPCOL]; int lastGI[CORE_MAXGI]; UINT64 lastSol; + /*-- Multithreaded synchronization of physics output --*/ + int pwmUpdateRequested; // Flag set to request an update of all physic outputs } locals; +void core_update_pwm_outputs(int forceUpdate); + /*------------------------------- / Initialize the game palette /-------------------------------*/ @@ -1108,6 +1113,7 @@ static void updateDisplay(struct mame_bitmap *bitmap, const struct rectangle *cl int ii = layout->length; UINT16 *seg = &coreGlobals.segments[layout->start].w; UINT16 *lastSeg = &locals.lastSeg[layout->start].w; + UINT8 *lastSegDim = &locals.lastSegDim[layout->start]; const int step = (layout->type & CORE_SEGREV) ? -1 : 1; #if defined(VPINMAME) || defined(LIBPINMAME) @@ -1120,19 +1126,38 @@ static void updateDisplay(struct mame_bitmap *bitmap, const struct rectangle *cl int char_width = locals.segData[layout->type & 0x0f].cols+1; #endif - if (step < 0) { seg += ii-1; lastSeg += ii-1; } + if (step < 0) { seg += ii-1; lastSeg += ii-1; lastSegDim += ii-1; } while (ii--) { UINT16 tmpSeg = *seg; + UINT8 tmpSegDim = 0; int tmpType = layout->type & CORE_SEGMASK; + + if (options.usemodsol & (CORE_MODOUT_FORCE_ON | CORE_MODOUT_ENABLE_PHYSOUT)) { + // TODO this evaluates dimming as a whole for the alpha numeric display character while it should be per segment + /*for (int kk = 0; kk < 16; kk++) { + UINT8 v = saturatedByte(coreGlobals.physicOutputState[CORE_MODOUT_SEG0 + (layout->start + layout->length - 1 - ii) * 16 + kk].value); + if (v > tmpSegDim) tmpSegDim = v; + }*/ + int bits = tmpSeg; + for (int kk = 0; bits; kk++, bits >>= 1) { + if (bits & 0x01) { + UINT8 v = saturatedByte(coreGlobals.physicOutputState[CORE_MODOUT_SEG0 + (layout->start + layout->length - 1 - ii) * 16 + kk].value); + if (v > tmpSegDim) tmpSegDim = v; + } + } + tmpSegDim = 255 - tmpSegDim; + } #ifdef VPINMAME //SJE: Force an update of the segments ALWAYS in VPM - corrects Pause Display Bugs if(1) { #else - if ((tmpSeg != *lastSeg) || + if ((tmpSeg != *lastSeg) || (tmpSegDim != *lastSegDim) || inRect(cliprect,left,top,locals.segData[layout->type & CORE_SEGALL].cols,locals.segData[layout->type & CORE_SEGALL].rows)) { #endif tmpSeg >>= (layout->type & CORE_SEGHIBIT) ? 8 : 0; + + *lastSegDim = tmpSegDim; switch (tmpType) { @@ -1156,11 +1181,11 @@ static void updateDisplay(struct mame_bitmap *bitmap, const struct rectangle *cl break; } #if defined(VPINMAME) || defined(LIBPINMAME) - seg_dim[seg_idx] = coreGlobals.segDim[*pos] > 15 ? 15 : coreGlobals.segDim[*pos]; + seg_dim[seg_idx] = tmpSegDim >> 4; seg_data[seg_idx++] = tmpSeg; #endif if (!pmoptions.dmd_only || !(layout->fptr || layout->lptr)) { - drawChar(bitmap, top, left, tmpSeg, tmpType, coreGlobals.segDim[*pos] > 15 ? 15 : coreGlobals.segDim[*pos]); + drawChar(bitmap, top, left, tmpSeg, tmpType, tmpSegDim >> 4); #ifdef PROC_SUPPORT if (coreGlobals.p_rocEn) { if ((core_gameData->gen & (GEN_WPCALPHA_1 | GEN_WPCALPHA_2 | GEN_ALLS11)) && @@ -1183,7 +1208,7 @@ static void updateDisplay(struct mame_bitmap *bitmap, const struct rectangle *cl } (*pos)++; left += locals.segData[layout->type & CORE_SEGALL].cols+1; - seg += step; lastSeg += step; + seg += step; lastSeg += step; lastSegDim += step; } #ifdef PROC_SUPPORT if (coreGlobals.p_rocEn) { @@ -1337,6 +1362,9 @@ static void updateDisplay(struct mame_bitmap *bitmap, const struct rectangle *cl VIDEO_UPDATE(core_gen) { int count = 0; + /*-- Update all physic output at least once per frame --*/ + core_update_pwm_outputs(TRUE); + #ifdef PROC_SUPPORT int alpha = (core_gameData->gen & (GEN_WPCALPHA_1|GEN_WPCALPHA_2|GEN_ALLS11)) != 0; if (coreGlobals.p_rocEn) { @@ -2073,8 +2101,8 @@ static void drawChar(struct mame_bitmap *bitmap, int row, int col, UINT32 bits, } typedef struct { - float minPWMIntegrationTime; /* Length under which state must be simply accumulated */ - int isFixedRate; /* True if integration does not support variable rate integration, therefore needing minPWMIntegrationTime to be always respected */ + float pwmIntegrationPeriod; /* Length above which an integration should be done to keep integration accuracy */ + float pwmSubIntegrationPeriod; /* Length under which output states flip should be accumulated as a duty ratio instead of integrated */ union { struct @@ -2084,6 +2112,7 @@ typedef struct { float U; /* voltage (Volts) */ float serial_R; /* serial resistor (Ohms) */ } bulb; + float led_power; // Physical model of a LED }; } core_tOutputTypeInfo; @@ -2093,8 +2122,6 @@ static core_tOutputTypeInfo coreOutputinfos[CORE_MODOUT_NTYPES]; /*---------------------- / Initialize PinMAME /-----------------------*/ -void core_update_pwm_outputs(int unused); - static MACHINE_INIT(core) { #ifdef PROC_SUPPORT char * yaml_filename = pmoptions.p_roc; @@ -2105,6 +2132,7 @@ static MACHINE_INIT(core) { memset(&coreGlobals, 0, sizeof(coreGlobals)); memset(&locals, 0, sizeof(locals)); memset(&locals.lastSeg, -1, sizeof(locals.lastSeg)); + memset(&locals.lastSegDim, 0, sizeof(locals.lastSegDim)); coreData = (struct pinMachine *)&Machine->drv->pinmame; coreGlobals.flipperCoils = 0xFFFFFFFFFFFFFFFFul; //-- initialise timers -- @@ -2197,101 +2225,110 @@ static MACHINE_INIT(core) { if (coreData->init) coreData->init(); /*-- init PWM integration (needs to be done after coreData->init() which defines the number of outputs and the physical model to be used on each output) --*/ - // If physic output enabled and defined by driver, update all outputs every 1ms to allow async reading done by VPinMame (should be optimized to something less heavy...) - // Note that some table made during 3.6 beta phase define output type even AFTER init. They will not work with the new implementation and need to be updated //options.usemodsol = CORE_MODOUT_ENABLE_PHYSOUT; // Uncomment for testing - if ( ((options.usemodsol & CORE_MODOUT_ENABLE_MODSOL ) && coreGlobals.nSolenoids) +#ifdef VPINMAME + // If physic output is enabled and supported, we add a 1ms timer that will service physic outputs requests from other threads, that is to say the VPinMame client thread + // Note that physic outputs are also updated once per frame by the core machine driver video update callback. + if (((options.usemodsol & CORE_MODOUT_ENABLE_MODSOL) && coreGlobals.nSolenoids) || ((options.usemodsol & CORE_MODOUT_ENABLE_PHYSOUT) && (coreGlobals.nSolenoids || coreGlobals.nLamps || coreGlobals.nGI || coreGlobals.nAlphaSegs)) || (options.usemodsol & CORE_MODOUT_FORCE_ON)) - timer_pulse(TIME_IN_HZ(999), 0, core_update_pwm_outputs); + timer_pulse(TIME_IN_HZ(1000), FALSE, core_update_pwm_outputs); +#endif memset(coreOutputinfos, 0, sizeof(coreOutputinfos)); // Nothing, just keep the value other code put on its output value - coreOutputinfos[CORE_MODOUT_NONE].minPWMIntegrationTime = 0.0f; + coreOutputinfos[CORE_MODOUT_NONE].pwmIntegrationPeriod = 0.0f; // No integration, just the pulsed binary state - coreOutputinfos[CORE_MODOUT_PULSE].minPWMIntegrationTime = 0.0f; + coreOutputinfos[CORE_MODOUT_PULSE].pwmIntegrationPeriod = 0.0f; // binary output: fire as soon as a low to high transition is found, then go low if low and not pulsed - coreOutputinfos[CORE_MODOUT_SOL_2_STATE].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_SOL_2_STATE].pwmIntegrationPeriod = 0.001f; // legacy binary output: like CORE_MODOUT_SOL_2_STATE but with legacy behavior of delaying the state by a few 'VBlank's (also used to have a bug on WPC which would only check if not pulsed but not base state) - coreOutputinfos[CORE_MODOUT_LEGACY_SOL_2_STATE].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_LEGACY_SOL_2_STATE].pwmIntegrationPeriod = 0.001f; // legacy binary output: return binary value for driver's getSol function - coreOutputinfos[CORE_MODOUT_LEGACY_SOL_CUSTOM].minPWMIntegrationTime = 0.0f; + coreOutputinfos[CORE_MODOUT_LEGACY_SOL_CUSTOM].pwmIntegrationPeriod = 0.0f; // Legacy WPC modulated output: merge state over 2ms, then every 56ms compute average of these - coreOutputinfos[CORE_MODOUT_LEGACY_SOL_WPC].minPWMIntegrationTime = 0.002f; - coreOutputinfos[CORE_MODOUT_LEGACY_SOL_WPC].isFixedRate = TRUE; + coreOutputinfos[CORE_MODOUT_LEGACY_SOL_WPC].pwmIntegrationPeriod = 0.002f; + coreOutputinfos[CORE_MODOUT_LEGACY_SOL_WPC].pwmSubIntegrationPeriod = 0.002f; // LED: eye integration of PWM duty ratio - coreOutputinfos[CORE_MODOUT_LED].minPWMIntegrationTime = 0.001f; - coreOutputinfos[CORE_MODOUT_LED_STROBE_1_10MS].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_LED].pwmIntegrationPeriod = 0.001f; + coreOutputinfos[CORE_MODOUT_LED].led_power = 1.f; + coreOutputinfos[CORE_MODOUT_LED_STROBE_1_10MS].pwmIntegrationPeriod = 0.001f; + coreOutputinfos[CORE_MODOUT_LED_STROBE_1_10MS].led_power = 10.f / 1.f; + // VFD: Vacuum Fluorescent Display (similar response as LEDs) + coreOutputinfos[CORE_MODOUT_VFD_STROBE_05_20MS].pwmIntegrationPeriod = 0.001f; + coreOutputinfos[CORE_MODOUT_VFD_STROBE_05_20MS].led_power = 20.f / 0.5f; + coreOutputinfos[CORE_MODOUT_VFD_STROBE_1_16MS].pwmIntegrationPeriod = 0.001f; + coreOutputinfos[CORE_MODOUT_VFD_STROBE_1_16MS].led_power = 16.f / 1.f; /*-- GI Bulbs --*/ // AC bulb outputs used for GI strings. Voltage drop through WPC triacs are considered as neglictible. - coreOutputinfos[CORE_MODOUT_BULB_44_6_3V_AC].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_44_6_3V_AC].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_44_6_3V_AC].bulb.type = BULB_44; coreOutputinfos[CORE_MODOUT_BULB_44_6_3V_AC].bulb.U = 6.3f; coreOutputinfos[CORE_MODOUT_BULB_44_6_3V_AC].bulb.isAC = TRUE; coreOutputinfos[CORE_MODOUT_BULB_44_6_3V_AC].bulb.serial_R = 0.0f; // - coreOutputinfos[CORE_MODOUT_BULB_47_6_3V_AC].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_47_6_3V_AC].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_47_6_3V_AC].bulb.type = BULB_47; coreOutputinfos[CORE_MODOUT_BULB_47_6_3V_AC].bulb.U = 6.3f; coreOutputinfos[CORE_MODOUT_BULB_47_6_3V_AC].bulb.isAC = TRUE; coreOutputinfos[CORE_MODOUT_BULB_47_6_3V_AC].bulb.serial_R = 0.0f; // - coreOutputinfos[CORE_MODOUT_BULB_86_6_3V_AC].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_86_6_3V_AC].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_86_6_3V_AC].bulb.type = BULB_86; coreOutputinfos[CORE_MODOUT_BULB_86_6_3V_AC].bulb.U = 6.3f; coreOutputinfos[CORE_MODOUT_BULB_86_6_3V_AC].bulb.isAC = TRUE; coreOutputinfos[CORE_MODOUT_BULB_86_6_3V_AC].bulb.serial_R = 0.0f; // Sega/Stern Whitestar uses 5.7V AC wired to #44 bulbs for GI which leads to a (very slow) bulb equilibrium around 78% of the rated bulb brightness. Likely for less heat and longer bulb life ? - coreOutputinfos[CORE_MODOUT_BULB_44_5_7V_AC].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_44_5_7V_AC].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_44_5_7V_AC].bulb.type = BULB_44; coreOutputinfos[CORE_MODOUT_BULB_44_5_7V_AC].bulb.U = 5.7f; coreOutputinfos[CORE_MODOUT_BULB_44_5_7V_AC].bulb.isAC = TRUE; coreOutputinfos[CORE_MODOUT_BULB_44_5_7V_AC].bulb.serial_R = 0.0f; /*-- Lamp Matrix Bulbs --*/ // 18V switched through a TIP102 and a TIP107 (voltage drop supposed of 0.7V per semiconductor switch, datasheet states Vcesat=2V for I=3A), resistor from schematics (TZ, TOTAN, CFTBL, WPC95 general) - coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_WPC].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_WPC].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_WPC].bulb.type = BULB_44; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_WPC].bulb.U = 18.f - 0.7f - 0.7f; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_WPC].bulb.isAC = FALSE; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_WPC].bulb.serial_R = 0.22f; // Switched through MOSFETs (12P06 & 12N10L, no drop), serial 3,5 Ohms, then serie with 1N4004 (0,7V drop) for bulbs / 120 Ohms with 1N4004 for LEDs, resistor from schematics (Cue Ball Wizard) - coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_GTS3].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_GTS3].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_GTS3].bulb.type = BULB_44; coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_GTS3].bulb.U = 20.f - 0.7f; coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_GTS3].bulb.isAC = FALSE; coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_GTS3].bulb.serial_R = 3.5f; // 18V switched through ?, resistor from schematics (Guns'n Roses) - coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_S11].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_S11].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_S11].bulb.type = BULB_44; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_S11].bulb.U = 18.f; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_S11].bulb.isAC = FALSE; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_S11].bulb.serial_R = 4.3f; // 18V switched through VN02N (Solid State Relay, 0.4 Ohms resistor), a 19N06L MOSFET transitor (no drop) and a diode (0.7V drop) from Sega/Stern Whitestar schematics - coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_SE].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_SE].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_SE].bulb.type = BULB_44; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_SE].bulb.U = 18.f - 0.7f; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_SE].bulb.isAC = FALSE; coreOutputinfos[CORE_MODOUT_BULB_44_18V_DC_SE].bulb.serial_R = 0.4f; // 20V switched through VN02 (Solid State Relay, 0.4 Ohms resistor), a STP20N10L MOSFET transitor (no drop), a 0.02 Ohms resistor, and 2x 1N4004 diode (0.7V drop) from Capcom schematics - coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_CC].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_CC].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_CC].bulb.type = BULB_44; coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_CC].bulb.U = 20.f - 0.7f - 0.7f; coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_CC].bulb.isAC = FALSE; coreOutputinfos[CORE_MODOUT_BULB_44_20V_DC_CC].bulb.serial_R = 0.4f + 0.02f; /*-- Flasher Bulbs --*/ // 20V DC switched through a TIP102 (voltage drop supposed of 0.7V per semiconductor switch, datasheet states Vcesat=2V for I=3A), resistor from WPC schematics (TZ, TOTAN, CFTBL, WPC95 general) - coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_WPC].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_WPC].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_WPC].bulb.type = BULB_89; coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_WPC].bulb.U = 20.f - 0.7f; coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_WPC].bulb.isAC = FALSE; coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_WPC].bulb.serial_R = 0.12f; // 20V DC switched through a 12N10L Mosfet (no voltage drop), resistor from schematics (Cue Ball Wizard) - coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_GTS3].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_GTS3].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_GTS3].bulb.type = BULB_89; coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_GTS3].bulb.U = 20.0f; coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_GTS3].bulb.isAC = FALSE; coreOutputinfos[CORE_MODOUT_BULB_89_20V_DC_GTS3].bulb.serial_R = 0.3f; // 32V DC switched through a TIP 122 (Vcesat max= 2 to 4V, 1V used here) with a 3 Ohms serial resistor and a diode (1V voltage drop), resistor from board photos - coreOutputinfos[CORE_MODOUT_BULB_89_32V_DC_S11].minPWMIntegrationTime = 0.001f; + coreOutputinfos[CORE_MODOUT_BULB_89_32V_DC_S11].pwmIntegrationPeriod = 0.001f; coreOutputinfos[CORE_MODOUT_BULB_89_32V_DC_S11].bulb.type = BULB_89; coreOutputinfos[CORE_MODOUT_BULB_89_32V_DC_S11].bulb.U = 32.f - 1.0f - 1.0f; coreOutputinfos[CORE_MODOUT_BULB_89_32V_DC_S11].bulb.isAC = FALSE; @@ -2489,6 +2526,7 @@ INLINE void core_eye_flicker_fusion(core_tPhysicOutput* output, const float dt, //#define LOG_PWM_OUT (CORE_MODOUT_LAMP0 + 18) //#define LOG_PWM_OUT (CORE_MODOUT_SOL0 + CORE_FIRSTCUSTSOL - 1) +//#define LOG_PWM_OUT (CORE_MODOUT_SEG0 + 0) // Update output state, taking in account of previously cumulated data and current state until now, eventually performing PWM integration // This function supports variable rate integration and high frequency controllers that perform fast flipping. @@ -2510,31 +2548,31 @@ void core_update_pwm_output(float now, int index, int isFlip) output->dataTimestamp = now; const float elapsedSinceLastFlip = now - output->lastFlipTimestamp; - const float minPWMIntegrationTime = coreOutputinfos[output->type].minPWMIntegrationTime; + const float pwmIntegrationPeriod = coreOutputinfos[output->type].pwmIntegrationPeriod; float integrationLength = output->elapsedInStateTime[0] + output->elapsedInStateTime[1]; float pwmDutyCycle; if (isFlip) { - output->lastFlipTimestamp = now; + // If state change after a period greater than integration period (hence, likely not a PWM change), we force the integration to sync on this state change to align on it. + // This gives more stability to the result since for example a 2ms pulse will be integrated as 2x1ms period with a 100% duty ratio, instead of 3x1ms periods including one with 100% duty ratio but with the surrounding ones only partial, leading to some (slight) flickering. #ifdef LOG_PWM_OUT if (index == LOG_PWM_OUT) - printf("Out. #%d t=%8.5f d=%8.5f Flip from %s (elapsed since last flip: %8.5f)\n", index, now, integrationLength, state ? "x" : "-", elapsedSinceLastFlip); + printf("Out. #%d t=%8.5f Flip from %s to %s (elapsed since last flip: %8.5f)\n", index, now, state ? "x" : "-", (1-state) ? "x" : "-", elapsedSinceLastFlip); #endif - // If state change after a period greater than integration period (hence, likely not a PWM change), we force the integration to sync on this state change to align on it. - // This gives more stability to the result since for example a 2ms pulse will be integrated as 2x1ms period with a 100% duty ratio, instead of 3x1ms periods including one with 100% duty ratio but with the surrounding ones only partial, leading to some (slight) flickering. - if (integrationLength < minPWMIntegrationTime && (coreOutputinfos[output->type].isFixedRate || elapsedSinceLastFlip < minPWMIntegrationTime)) + output->lastFlipTimestamp = now; + // TODO Most integrators do not uses the sub integration feature: this should be moved to the only ones using it for cleaner/better performance + if (integrationLength < pwmIntegrationPeriod && elapsedSinceLastFlip < coreOutputinfos[output->type].pwmSubIntegrationPeriod) return; pwmDutyCycle = output->elapsedInStateTime[1] / integrationLength; output->elapsedInStateTime[0] = output->elapsedInStateTime[1] = 0.0f; } - else if (integrationLength < minPWMIntegrationTime) + else if (integrationLength < pwmIntegrationPeriod) { - // Perform only the basic state accumulation for performance reasons - // This avoid doing full physics model integration for each state change on fast controllers like Capcom or SAM hardware + // Stable state without enough accumulated state, just continue to accumulate for performance reasons return; } - else if (elapsedSinceLastFlip < integrationLength) // integrationLength is >= minPWMIntegrationTime but last flip happened inside the integration period + else if (elapsedSinceLastFlip < integrationLength) // integrationLength is >= pwmIntegrationPeriod but last flip happened inside the integration period { // Last flip happened in this integration period: perform integration up to the last flip event (to align on end of pulse if applicable), keeping the remaining for later integration now -= elapsedSinceLastFlip; @@ -2657,7 +2695,7 @@ void core_update_pwm_output(float now, int index, int isFlip) const int isAC = coreOutputinfos[output->type].bulb.isAC; float integrationTime = now - integrationLength; while (integrationLength > 0.0f) { - const float dt = integrationLength < minPWMIntegrationTime ? integrationLength : minPWMIntegrationTime; + const float dt = integrationLength < pwmIntegrationPeriod ? integrationLength : pwmIntegrationPeriod; // Keeps T within the range of the LUT (between room temperature and melt down point) output->state.bulb.filament_temperature = output->state.bulb.filament_temperature < 293.0f ? 293.0f : output->state.bulb.filament_temperature > (float) BULB_T_MAX ? (float) BULB_T_MAX : output->state.bulb.filament_temperature; const float Ut = isAC ? (1.41421356f * sinf((float)(60.0 * 2.0 * PI) * (integrationTime - coreGlobals.lastACZeroCrossTimeStamp)) * U) : U; @@ -2673,24 +2711,50 @@ void core_update_pwm_output(float now, int index, int isFlip) #endif } break; + case CORE_MODOUT_VFD_STROBE_05_20MS: + case CORE_MODOUT_VFD_STROBE_1_16MS: { + // Vacuum Fluorescent Display: the documentation for the behavior of this device (used by alphanum displays) is sparse, so the integrator likely needs more work + // From the searchs made, it appears that: + // - the relative brightness (in steady state) is linear to PWM duty ratio. + // - the off -> on speed is very short (below 1ms), therefore ignored + // - the on -> off, called persistence depends a lot on used phosphor, for blue VFD uses P11 (0.5ms decay), green VFD uses P24 (0.01ms decay), therefore ignored too + // So for the time being, just apply eye flicker/fusion model like we do for LEDs. + // Sources: + // - https://en.wikipedia.org/wiki/Phosphor has a large phosphor chart + // - https://www.noritake-elec.com/technology/general-technical-information/vfd-operation states a linear relation between PWM and relative brightness + // - http://www.futabahk.com.hk/FTS/-%20FTP_DataBase/Docs_05TechDocs/VFD/AN-1103A-EN%20%20VFD%20Characteristics%20and%20Operation%20Guide%20(EN).pdf states the same + float power = pwmDutyCycle * coreOutputinfos[output->type].led_power; + // Only perform integration if steady state is not reached + if (!((power > (254.0f / 255.0f) && output->value >= 0.999f) || (power < (1.0f / 255.0f) && output->value <= 0.001f))) { + while (integrationLength > 0.0f) { + const float dt = integrationLength < pwmIntegrationPeriod ? integrationLength : pwmIntegrationPeriod; + core_eye_flicker_fusion(output, dt /* * 0.75f */, power); // Artificially longer flicker/fusion to somewhat emulate the VFD persistence + integrationLength -= dt; + } + } + #ifdef LOG_PWM_OUT + if (index == LOG_PWM_OUT) + printf("LED #%d t=%8.5f d=%8.5f r=%.2f => V=%0.3f S=%s\n", index, initialNow, initialIntegrationLength, pwmDutyCycle, output->value, state ? "x" : "-"); + #endif + } + break; case CORE_MODOUT_LED_STROBE_1_10MS: - // LED selected for short strobing of 1ms over 10ms periods (8 value is just magic, no clean physics behind this) - pwmDutyCycle *= 8.0f; case CORE_MODOUT_LED: { // LED reacts almost instantly (<1us), the integration is based on the human eye perception, with some // flashing below 100Hz (limit between 50-100 depends on each human), dimming above (there should be // a "flicker" range in the middle). + float power = pwmDutyCycle * coreOutputinfos[output->type].led_power; // Only perform integration if steady state is not reached - if (!((pwmDutyCycle > 254.0f / 255.0f && output->value >= 0.999f) || (pwmDutyCycle < 1.0f / 255.0f && output->value <= 0.001f))) { + if (!((power > (254.0f / 255.0f) && output->value >= 0.999f) || (power < (1.0f / 255.0f) && output->value <= 0.001f))) { while (integrationLength > 0.0f) { - const float dt = integrationLength < minPWMIntegrationTime ? integrationLength : minPWMIntegrationTime; - core_eye_flicker_fusion(output, dt, pwmDutyCycle); + const float dt = integrationLength < pwmIntegrationPeriod ? integrationLength : pwmIntegrationPeriod; + core_eye_flicker_fusion(output, dt, power); integrationLength -= dt; } } #ifdef LOG_PWM_OUT if (index == LOG_PWM_OUT) - printf("LED #%d t=%8.5f d=%8.5f V=%0.3f r=%.2f S=%s\n", index, initialNow, initialIntegrationLength, output->value, pwmDutyCycle, state ? "x" : "-"); + printf("LED #%d t=%8.5f d=%8.5f r=%.2f => V=%0.3f S=%s\n", index, initialNow, initialIntegrationLength, pwmDutyCycle, output->value, state ? "x" : "-"); #endif } break; @@ -2703,19 +2767,32 @@ void core_update_pwm_output(float now, int index, int isFlip) } } -// Called periodically to perform PWM integration on all outputs. -// This is needed if we need multithreaded access to the output state like for VPinMAME since integration is not thread safe. -void core_update_pwm_outputs(int unused) +// This can be called from any thread to request an update of all integrated outputs +// The call is non blocking: the main PinMame thread will schedule an update and return immediatly +void core_request_pwm_output_update() +{ + locals.pwmUpdateRequested = TRUE; +} + +// Actually perform PWM integration on all outputs: +// - called periodically with forceUpdate=FALSE to check if a client thread requested the update +// - or called with forceUpdate=TRUE when used in a single threaded environment +// Note that the need of explicit integration call depends on the output type. For example, some solenoids +// have immediate response and do not rely on integration. +void core_update_pwm_outputs(int forceUpdate) { - float now = (float) timer_get_time(); - for (int i = 0; i < coreGlobals.nLamps; i++) - core_update_pwm_output(now, CORE_MODOUT_LAMP0 + i, 0); - for (int i = 0; i < coreGlobals.nGI; i++) - core_update_pwm_output(now, CORE_MODOUT_GI0 + i, 0); - for (int i = 0; i < coreGlobals.nSolenoids; i++) - core_update_pwm_output(now, CORE_MODOUT_SOL0 + i, 0); - for (int i = 0; i < coreGlobals.nAlphaSegs; i++) - core_update_pwm_output(now, CORE_MODOUT_SEG0 + i, 0); + if (locals.pwmUpdateRequested || forceUpdate) { + locals.pwmUpdateRequested = FALSE; + float now = (float) timer_get_time(); + for (int i = 0; i < coreGlobals.nLamps; i++) + core_update_pwm_output(now, CORE_MODOUT_LAMP0 + i, 0); + for (int i = 0; i < coreGlobals.nGI; i++) + core_update_pwm_output(now, CORE_MODOUT_GI0 + i, 0); + for (int i = 0; i < coreGlobals.nSolenoids; i++) + core_update_pwm_output(now, CORE_MODOUT_SOL0 + i, 0); + for (int i = 0; i < coreGlobals.nAlphaSegs; i++) + core_update_pwm_output(now, CORE_MODOUT_SEG0 + i, 0); + } } void core_set_pwm_output_type(int startIndex, int count, int type) diff --git a/src/wpc/core.h b/src/wpc/core.h index 941f224c8..c9cb22023 100644 --- a/src/wpc/core.h +++ b/src/wpc/core.h @@ -307,6 +307,7 @@ extern void video_update_core_dmd(struct mame_bitmap *bitmap, const struct recta #define CORE_MAXPORTS 8 /* Maximum input ports */ #define CORE_MAXGI 5 /* Maximum GI strings */ #define CORE_MAXNVRAM 131118 /* Maximum number of NVRAM bytes, only used for get_ChangedNVRAM so far */ +#define CORE_SEGCOUNT 128 /* Maximum number of alpha numeric display (16 segments each) */ /*-- create a custom switch number --*/ /* example: #define swCustom CORE_CUSTSWNO(1,2) // custom column 1 row 2 */ @@ -363,10 +364,10 @@ extern void video_update_core_dmd(struct mame_bitmap *bitmap, const struct recta #define CORE_MODOUT_ENABLE_PHYSOUT 2 /* Bitmask for options.usemodsol to enable physics output for solenoids/Lamp/GI/AlphaSegments */ #define CORE_MODOUT_FORCE_ON 128 /* Bitmask for options.usemodsol for drivers that needs PWM integration to be performed whatever the user settings are */ -#define CORE_MODOUT_LAMP_MAX (CORE_MAXLAMPCOL*8) /* Maximum number of modulated outputs for lamps */ +#define CORE_MODOUT_LAMP_MAX (CORE_MAXLAMPCOL * 8) /* Maximum number of modulated outputs for lamps */ #define CORE_MODOUT_SOL_MAX 72 /* Maximum number of modulated outputs for solenoids */ #define CORE_MODOUT_GI_MAX 8 /* Maximum number of modulated outputs for GI */ -#define CORE_MODOUT_SEG_MAX 256 /* Maximum number of modulated outputs for alphanumeric segments - 256=16x8x2 */ +#define CORE_MODOUT_SEG_MAX (CORE_SEGCOUNT * 16) /* Maximum number of modulated outputs for alphanumeric segments */ #define CORE_MODOUT_LAMP0 0 /* Index of first lamp output */ #define CORE_MODOUT_SOL0 CORE_MODOUT_LAMP_MAX /* Index of first solenoid output */ #define CORE_MODOUT_GI0 (CORE_MODOUT_SOL0 + CORE_MODOUT_SOL_MAX) /* Index of first GI output */ @@ -393,7 +394,9 @@ extern void video_update_core_dmd(struct mame_bitmap *bitmap, const struct recta #define CORE_MODOUT_BULB_89_32V_DC_S11 303 /* Incandescent #89/906 Bulb connected to 32V, used for flashers on S11 with output strobing */ #define CORE_MODOUT_LED 400 /* LED PWM (in fact mostly human eye reaction, since LED are nearly instantaneous) */ #define CORE_MODOUT_LED_STROBE_1_10MS 401 /* LED Strobed 1ms over 10ms for full power */ -#define CORE_MODOUT_NTYPES CORE_MODOUT_LED_STROBE_1_10MS + 1 +#define CORE_MODOUT_VFD_STROBE_05_20MS 450 /* Vacuum Fluorescent Display used for alpha numeric segment displays */ +#define CORE_MODOUT_VFD_STROBE_1_16MS 451 /* Vacuum Fluorescent Display used for alpha numeric segment displays */ +#define CORE_MODOUT_NTYPES CORE_MODOUT_VFD_STROBE_1_16MS + 1 /*------------------------------------------- / Draw data. draw lamps,switches,solenoids @@ -434,11 +437,12 @@ typedef struct { } state; } core_tPhysicOutput; -#define CORE_SEGCOUNT 128 #ifdef LSB_FIRST -typedef union { struct { UINT8 lo, hi; } b; UINT16 w; } core_tSeg[CORE_SEGCOUNT]; +typedef union { struct { UINT8 lo, hi; } b; UINT16 w; } core_tWord; +typedef core_tWord core_tSeg[CORE_SEGCOUNT]; #else /* LSB_FIRST */ -typedef union { struct { UINT8 hi, lo; } b; UINT16 w; } core_tSeg[CORE_SEGCOUNT]; +typedef union { struct { UINT8 hi, lo; } b; UINT16 w; } core_tWord; +typedef core_tWord core_tSeg[CORE_SEGCOUNT]; #endif /* LSB_FIRST */ typedef struct { /*-- Switches --*/ @@ -551,6 +555,7 @@ extern UINT64 core_getAllSol(void); extern void core_getAllPhysicSols(float* state); /*-- AC sync and PWM integration --*/ +extern void core_request_pwm_output_update(); extern void core_set_pwm_output_type(int startIndex, int count, int type); extern void core_set_pwm_output_types(int startIndex, int count, int* outputTypes); extern void core_write_pwm_output(int index, int count, UINT8 bitStates); // Write binary state of count outputs, taking care of PWM integration based on physical model of connected device diff --git a/src/wpc/gts3.c b/src/wpc/gts3.c index 9cd3ccbe9..d06fbb127 100644 --- a/src/wpc/gts3.c +++ b/src/wpc/gts3.c @@ -45,52 +45,40 @@ UINT8 DMDFrames2[GTS3DMD_FRAMES_5C][0x200]; //2nd DMD Display for Strikes N Spar #define logerror1 printf #endif -/* FORCE The 16 Segment Layout to match the output order expected by core.c */ -static const int alpha_adjust[16] = {0,1,2,3,4,5,9,10,11,12,13,14,6,8,15,7}; - static WRITE_HANDLER(display_control); /*Alpha Display Generation Specific*/ -static WRITE_HANDLER(alpha_u4_pb_w); static READ_HANDLER(alpha_u4_pb_r); static WRITE_HANDLER(alpha_display); static WRITE_HANDLER(alpha_aux); -static void alpha_update(void); /*DMD Generation Specific*/ static void dmdswitchbank(int which); -static WRITE_HANDLER(dmd_u4_pb_w); static READ_HANDLER(dmd_u4_pb_r); static WRITE_HANDLER(dmd_display); static WRITE_HANDLER(dmd_aux); static void dmd_vblank(int which); -static void dmd_update(void); /*---------------- / Local variables /-----------------*/ struct { int alphagen; - core_tSeg segments, pseg; + int alphaNumCol, alphaNumColShitRegister; + core_tWord activeSegments[2]; // Realtime active alphanum segments int vblankCount; UINT32 solenoids; int lampRow, lampColumn; UINT8 diagnosticLed; // bool UINT8 diagnosticLed1; // bool UINT8 diagnosticLed2; // bool - //int swCol; - int ssEn; - //int mainIrq; UINT8 swDiag; // bool UINT8 swTilt; // bool UINT8 swSlam; // bool UINT8 swPrin; // bool - int acol; - int u4pb; - WRITE_HANDLER((*U4_PB_W)); + UINT8 u4pb; READ_HANDLER((*U4_PB_R)); WRITE_HANDLER((*DISPLAY_CONTROL)); WRITE_HANDLER((*AUX_W)); - void (*UPDATE_DISPLAY)(void); UINT8 ax[7], cx1, cx2, ex1; char extra16led; // bool int sound_data; @@ -138,9 +126,9 @@ static READ_HANDLER(alpha_u4_pb_r) PB0-2: Output only PB3: Test Switch PB4: Tilt Switch - PB5- A1P3-3 - Display Controller - Status1 (Not labeld on Schematic) + PB5- A1P3-3 - Display Controller - Status1 (Not labeled on Schematic) PB6- A1P3-1 - Display Controller - DMD Display Strobe (DSTB) - Output only, but might be read! - PB7- A1P3-2 - Display Controller - Status2 (Not labeld on Schematic) + PB7- A1P3-2 - Display Controller - Status2 (Not labeled on Schematic) */ static READ_HANDLER(dmd_u4_pb_r) { @@ -175,17 +163,14 @@ static WRITE_HANDLER( xvia_0_a_w ) // logerror1("WRITE:NA?: via_0_a_w: %x\n",data); } -//PB0-7 Varies on Alpha or DMD Generation! -static WRITE_HANDLER( xvia_0_b_w ) { GTS3locals.U4_PB_W(offset,data); } - -/* ALPHA GENERATION - ---------------- +/* U4 VIA Parralel Out B + --------------------- PB0: Lamp Data (LDATA) PB1: Lamp Strobe (LSTRB) PB2: Lamp Clear (LCLR) - PB5: Display Data (DDATA) + PB5: Display Data (DDATA) Alpha generation only, unused for DMD generation PB6: Display Strobe (DSTRB) - PB7: Display Blank (DBLNK) + PB7: Display Blank (DBLNK) Alpha generation only, unused for DMD generation */ #define LDATA 0x01 #define LSTRB 0x02 @@ -194,115 +179,97 @@ static WRITE_HANDLER( xvia_0_b_w ) { GTS3locals.U4_PB_W(offset,data); } #define DSTRB 0x40 #define DBLNK 0x80 -static WRITE_HANDLER(alpha_u4_pb_w) { - int dispBits = data & 0xf0; - - /* Kept for reference but seems wrong to me (Niwak) since I think LDATA and LCLR have different purposes - if (data & ~GTS3locals.u4pb & LSTRB) { // Positive edge - if ((data & LCLR) && (data & LDATA)) - GTS3locals.lampColumn = 1; - else - GTS3locals.lampColumn = ((GTS3locals.lampColumn << 1) & 0x0fff); - core_setLampBlank(coreGlobals.tmpLampMatrix, GTS3locals.lampColumn, GTS3locals.lampRow); - } else - core_setLamp(coreGlobals.tmpLampMatrix, GTS3locals.lampColumn, GTS3locals.lampRow);*/ - - // As I (Niwak) understand from schematics: +static WRITE_HANDLER( xvia_0_b_w ) { + // From schematics: // - Rows are a simple latch (74HC273) with overcurrent comparators - // - Columns use two 74HS164 (serial 8 bit decoders), when LSTRB is raised (low to high edge), column output are shifted (<<) and LDATA is used for lowest bit (forming a 12 bit shift sequence) - // - LCLR creates a short pulse (through a 555, with R=4.7k and C=0.1uF so 50us pulse) that will reset the overcurrent comparators on the row outputs, not sure why + // - Columns use two 74HS164 (8 bit shit register), when LSTRB is raised (low to high edge), column output are shifted (<<) and LDATA is used for lowest bit (forming a 12 bit shift sequence) + // - LCLR creates a short pulse (through a 555, with R=4.7k and C=0.1uF so 50us pulse) that will reset the overcurrent comparators on the row outputs (likely to cover the latching between row & column and avoid ghosting) // This results in the following write sequence for strobing (observed on Cue Ball Wizard): // - Set lampRow to 0 (turn off all lamps) // - Set LDATA (only if needed, to start strobe on first column) / LSTRB H->L / LSTRB L->H / Clear LDATA // - Set lampRow to expected output (turn on expected lamps of the new column) // - LCLR H->L / LCLR L->H - // We do not simulate the LCLR signals since the pulse is too short (50us) for the output resolution and the aim is not obvious + // We do not simulate the LCLR signals since the pulse is too short (50us) for the output resolution //printf("t=%8.5f Col=%3x STRB=%d DATA=%d LCLR=%d\n", timer_get_time(), GTS3locals.lampColumn, data & LSTRB, data & LDATA, data & LCLR); if (data & ~GTS3locals.u4pb & LSTRB) // Positive edge on LSTRB: shift 12bit register and set bit0 to LDATA { GTS3locals.lampColumn = ((GTS3locals.lampColumn << 1) & 0x0ffe) | (data & LDATA); - if (GTS3locals.lampColumn == 1) // Simple strobe emulation: reset lamp matrix when strobe restarts at column 0 + if (GTS3locals.lampColumn == 0x001) // Simple strobe emulation: accumulate lamp matrix until strobe restarts from first column { memcpy(coreGlobals.lampMatrix, coreGlobals.tmpLampMatrix, sizeof(coreGlobals.tmpLampMatrix)); memset(coreGlobals.tmpLampMatrix, 0, sizeof(coreGlobals.tmpLampMatrix)); } } - core_write_pwm_output_lamp_matrix(CORE_MODOUT_LAMP0 , GTS3locals.lampColumn & 0xFF, GTS3locals.lampRow, 8); - core_write_pwm_output_lamp_matrix(CORE_MODOUT_LAMP0 + 64, (GTS3locals.lampColumn >> 8) & 0x0F, GTS3locals.lampRow, 4); - - if (dispBits == 0xe0) { GTS3locals.acol = 0; } - else if (dispBits == 0xc0) { GTS3locals.acol++; } - - GTS3locals.u4pb = data; -} - -/* DMD GENERATION - ---------------- - PB0: Lamp Data (LDATA) - PB1: Lamp Strobe (LSTRB) - PB2: Lamp Clear (LCLR) - PB6: Display Strobe (DSTRB) -*/ -static WRITE_HANDLER(dmd_u4_pb_w) { - /* Kept for reference but seems wrong to me (Niwak) since I think LDATA and LCLR have different purposes - if (data & ~GTS3locals.u4pb & LSTRB) { // Positive edge - if ((data & LCLR) && (data & LDATA)) - GTS3locals.lampColumn = 1; - else - GTS3locals.lampColumn = ((GTS3locals.lampColumn << 1) & 0x0fff); - core_setLampBlank(coreGlobals.tmpLampMatrix, GTS3locals.lampColumn, GTS3locals.lampRow); - } else - core_setLamp(coreGlobals.tmpLampMatrix, GTS3locals.lampColumn, GTS3locals.lampRow);*/ - - // As I (Niwak) understand from schematics: - // - Rows are a simple latch (74HC273) with overcurrent comparators - // - Columns use two 74HS164 (serial 8 bit decoders), when LSTRB is raised (low to high edge), column output are shifted (<<) and LDATA is used for lowest bit (forming a 12 bit shift sequence) - // - LCLR creates a short pulse (through a 555, with R=4.7k and C=0.1uF so 50us pulse) that will reset the overcurrent comparators on the row outputs, not sure why - // This results in the following write sequence for strobing (observed on Cue Ball Wizard): - // - Set lampRow to 0 (turn off all lamps) - // - Set LDATA (only if needed, to start strobe on first column) / LSTRB H->L / LSTRB L->H / Clear LDATA - // - Set lampRow to expected output (turn on expected lamps of the new column) - // - LCLR H->L / LCLR L->H - // We do not simulate the LCLR signals since the pulse is too short (50us) for the output resolution and the aim is not obvious - //printf("t=%8.5f Col=%3x STRB=%d DATA=%d LCLR=%d\n", timer_get_time(), GTS3locals.lampColumn, data & LSTRB, data & LDATA, data & LCLR); - if (data & ~GTS3locals.u4pb & LSTRB) // Positive edge on LSTRB: shift 12bit register and set bit0 to LDATA - { - GTS3locals.lampColumn = ((GTS3locals.lampColumn << 1) & 0x0ffe) | (data & LDATA); - if (GTS3locals.lampColumn == 1) // Simple strobe emulation: reset lamp matrix when strobe restarts at column 0 + const UINT8 lampRow = GTS3locals.lampRow; // data & LCLR ? 0 : GTS3locals.lampRow; // Not emulated as it does not gives any benefit + core_write_pwm_output_lamp_matrix(CORE_MODOUT_LAMP0 , GTS3locals.lampColumn & 0xFF, lampRow, 8); + core_write_pwm_output_lamp_matrix(CORE_MODOUT_LAMP0 + 64, (GTS3locals.lampColumn >> 8) & 0x0F, lampRow, 4); + + + if (GTS3locals.alphagen) + { // Alpha generation + //printf("%8.5f Alpha Strobe %02x %05x\n", timer_get_time(), data, GTS3locals.alphaNumColShitRegister); + if (data & ~GTS3locals.u4pb & DSTRB) // Positive edge on DSTRB: shift 20bit register and set bit0 to DDATA { - memcpy(coreGlobals.lampMatrix, coreGlobals.tmpLampMatrix, sizeof(coreGlobals.tmpLampMatrix)); - memset(coreGlobals.tmpLampMatrix, 0, sizeof(coreGlobals.tmpLampMatrix)); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol + 20) * 16, 0); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol + 20) * 16 + 8, 0); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol ) * 16 , 0); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol ) * 16 + 8, 0); + GTS3locals.alphaNumColShitRegister = ((GTS3locals.alphaNumColShitRegister << 1) & 0x0ffffe) | ((data & DDATA) >> 5); + GTS3locals.alphaNumCol = GTS3locals.alphaNumColShitRegister == 0 ? 20 : core_BitColToNum(GTS3locals.alphaNumColShitRegister); + // This should never happens but you can drive the hardware to it (multiple resets,...), this will lead to incorrect rendering + // assert((GTS3locals.alphaNumColShitRegister == 0) || (GTS3locals.alphaNumColShitRegister == (1 << GTS3locals.alphaNumCol))); } - } - core_write_pwm_output_lamp_matrix(CORE_MODOUT_LAMP0 , GTS3locals.lampColumn & 0xFF, GTS3locals.lampRow, 8); - core_write_pwm_output_lamp_matrix(CORE_MODOUT_LAMP0 + 64, (GTS3locals.lampColumn >> 8) & 0x0F, GTS3locals.lampRow, 4); - - GTS3_dmdlocals[0].dstrb = (data & DSTRB) != 0; - if (GTS3_dmdlocals[0].version) { // probably wrong, but the only way to show *any* display - if (GTS3_dmdlocals[0].dstrb) { - GTS3locals.bitSet++; - if (GTS3locals.bitSet == 4) + if (data & ~GTS3locals.u4pb & DBLNK) // DBlank start (positive edge) + { + for (int i = 0; i < 20 * 2 * 2; i++) + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + i * 8, 0); + } + else if (GTS3locals.alphaNumCol < 20) { + if (~data & GTS3locals.u4pb & DBLNK) // DBlank end (negative edge) + { + // Basic non dimmed segments emulation: just use the column and value defined during DBLNK + coreGlobals.segments[GTS3locals.alphaNumCol + 20].w = GTS3locals.activeSegments[0].w; + coreGlobals.segments[GTS3locals.alphaNumCol ].w = GTS3locals.activeSegments[1].w; + } + if ((data & DBLNK) == 0) { - // 12/17/16 (djrobx) - Teed Off's animations are running way too fast. Vblank - // appears to be firing too often. Seems to be closer to correct if you do it every - // 3rd time this bit is set, but probably means this method is not right as the - // above comment suggests. - GTS3locals.vblank_counter++; - if (GTS3locals.vblank_counter == 3) + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol + 20) * 16 , GTS3locals.activeSegments[0].b.lo); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol + 20) * 16 + 8, GTS3locals.activeSegments[0].b.hi); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol ) * 16 , GTS3locals.activeSegments[1].b.lo); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol ) * 16 + 8, GTS3locals.activeSegments[1].b.hi); + } + } + } + else + { // DMD generation + GTS3_dmdlocals[0].dstrb = (data & DSTRB) != 0; + if (GTS3_dmdlocals[0].version) { // probably wrong, but the only way to show *any* display + if (GTS3_dmdlocals[0].dstrb) { + GTS3locals.bitSet++; + if (GTS3locals.bitSet == 4) { - GTS3locals.vblank_counter = 0; - dmd_vblank(0); - if(GTS3_dmdlocals[0].version == 2) - dmd_vblank(1); + // 12/17/16 (djrobx) - Teed Off's animations are running way too fast. Vblank + // appears to be firing too often. Seems to be closer to correct if you do it every + // 3rd time this bit is set, but probably means this method is not right as the + // above comment suggests. + GTS3locals.vblank_counter++; + if (GTS3locals.vblank_counter == 3) + { + GTS3locals.vblank_counter = 0; + dmd_vblank(0); + if(GTS3_dmdlocals[0].version == 2) + dmd_vblank(1); + } } - } - } else - GTS3locals.bitSet = 0; + } else + GTS3locals.bitSet = 0; + } } - + GTS3locals.u4pb = data; } + //AUX DATA? See ca2 above! static WRITE_HANDLER( xvia_0_ca2_w ) { @@ -379,18 +346,23 @@ static WRITE_HANDLER( xvia_1_a_w ) AX4 = Latch the Data (set by aux write handler) */ static WRITE_HANDLER( xvia_1_b_w ) { - if (GTS3locals.extra16led) { // used for alpha display digits on Vegas only + // FIXME this looks somewhat wrong to me: I think the 6522 VIA is supposed to latch the data on its programmable inpout/output parallel port, + // then the CPU trigger AX4/5/6 for the aux board to handle it. Here we are doing the opposite + if (GTS3locals.extra16led) { // used for the 3 playfield alpha digit displays on Vegas only + // TODO implement full aux board logic if (data > 0 && data < 4 && GTS3locals.ax[4] == GTS3locals.ax[6]) - GTS3locals.segments[39 + data].w = GTS3locals.pseg[39 + data].w = - (GTS3locals.ax[6] & 0x3f) | ((GTS3locals.ax[6] & 0xc0) << 3) - | ((GTS3locals.ax[5] & 0x0f) << 11) | ((GTS3locals.ax[5] & 0x10) << 2) | ((GTS3locals.ax[5] & 0x20) << 3); + coreGlobals.segments[39 + data].w = (GTS3locals.ax[6] & 0x3f) + | ((GTS3locals.ax[6] & 0xc0) << 3) + | ((GTS3locals.ax[5] & 0x0f) << 11) + | ((GTS3locals.ax[5] & 0x10) << 2) + | ((GTS3locals.ax[5] & 0x20) << 3); } else if (core_gameData->hw.lampCol > 4) { // flashers drived by auxiliary board (for example backbox lights in SF2) // FIXME From the schematics, I would say that the latch only happens if ax[4] is raised up (not checked here) - coreGlobals.lampMatrix[12] = coreGlobals.tmpLampMatrix[12] = data; // These are latched, not strobed + coreGlobals.lampMatrix[12] = coreGlobals.tmpLampMatrix[12] = data; core_write_pwm_output_8b(CORE_MODOUT_LAMP0 + 12 * 8, data); } else if (!(GTS3locals.ax[4] & 1)) { // LEDs if (GTS3locals.alphagen) - GTS3locals.segments[40 + (data >> 4)].w = GTS3locals.pseg[40 + (data >> 4)].w = core_bcd2seg[data & 0x0f]; + coreGlobals.segments[40 + (data >> 4)].w = core_bcd2seg[data & 0x0f]; else coreGlobals.segments[data >> 4].w = core_bcd2seg[data & 0x0f]; } @@ -446,6 +418,7 @@ DMD Generation Listed Below (for different hardware see code) (O)PB0: Lamp Data (LDATA) (O)PB1: Lamp Strobe (LSTRB) (O)PB2: Lamp Clear (LCLR) +(O)PB5: Display Data (DDATA) - Alpha Generation Only! (O)PB6: Display Strobe (DSTRB) (O)PB7: Display Blanking (DBLNK) - Alpha Generation Only! (O)CA2: To A1P6-12 & A1P7-6 Auxiliary @@ -483,33 +456,25 @@ static INTERRUPT_GEN(GTS3_vblank) { /--------------------------------*/ GTS3locals.vblankCount++; - /*-- lamps --*/ - /* Kept for reference. Since lamp matrix strobed is not done at the pace of vblank, the lamp matrix is pushed at the strobe pace. - if ((GTS3locals.vblankCount % GTS3_LAMPSMOOTH) == 0) { - memcpy(coreGlobals.lampMatrix, coreGlobals.tmpLampMatrix, sizeof(coreGlobals.tmpLampMatrix)); - }*/ /*-- solenoids --*/ coreGlobals.solenoids = GTS3locals.solenoids; if ((GTS3locals.vblankCount % GTS3_SOLSMOOTH) == 0) { - if (GTS3locals.ssEn) { + // FIXME ssEn is never set. Special solenoids are handled by core_updateSw triggered from GameOn directly read from solenoid #31 state + // Note that the code here would have a bug because it sets solenoids #23 as GameOn but this solenoid is used for other purposes by GTS3 hardware + /*if (GTS3locals.ssEn) { int ii; coreGlobals.solenoids |= CORE_SOLBIT(CORE_SSFLIPENSOL); /*-- special solenoids updated based on switches --*/ - for (ii = 0; ii < 6; ii++) + /*for (ii = 0; ii < 6; ii++) if (core_gameData->sxx.ssSw[ii] && core_getSw(core_gameData->sxx.ssSw[ii])) coreGlobals.solenoids |= CORE_SOLBIT(CORE_FIRSTSSSOL+ii); - } + }*/ GTS3locals.solenoids = coreGlobals.pulsedSolState; } - /*-- display --*/ - if ((GTS3locals.vblankCount % GTS3_DISPLAYSMOOTH) == 0) { - /*Update alpha or dmd display*/ - GTS3locals.UPDATE_DISPLAY(); - - /*update leds*/ - - //Strikes N Spares has 2 DMD LED, but no Sound Board LED - if (GTS3_dmdlocals[0].version == 2) { + + /*-- diagnostic leds --*/ + if ((GTS3locals.vblankCount % GTS3_DISPLAYSMOOTH) == 0) { // TODO it seems that diag LEDs are PWMed => move to a lamp + if (GTS3_dmdlocals[0].version == 2) { //Strikes N Spares has 2 DMD LED, but no Sound Board LED coreGlobals.diagnosticLed = GTS3locals.diagnosticLed | (GTS3_dmdlocals[0].diagnosticLed << 1) | (GTS3_dmdlocals[1].diagnosticLed << 2); @@ -520,7 +485,8 @@ static INTERRUPT_GEN(GTS3_vblank) { GTS3locals.diagnosticLed; } } - core_updateSw(GTS3locals.solenoids & 0x80000000); + + core_updateSw(GTS3locals.solenoids & 0x80000000); // GameOn is solenoid #31 for all tables } static SWITCH_UPDATE(GTS3) { @@ -565,23 +531,46 @@ static void GTS3_alpha_common_init(void) { via_config(1, &via_1_interface); via_reset(); - GTS3locals.U4_PB_W = alpha_u4_pb_w; GTS3locals.U4_PB_R = alpha_u4_pb_r; GTS3locals.DISPLAY_CONTROL = alpha_display; - GTS3locals.UPDATE_DISPLAY = alpha_update; GTS3locals.AUX_W = alpha_aux; /* Init the sound board */ sndbrd_0_init(core_gameData->hw.soundBoard, 1, memory_region(GTS3_MEMREG_SCPU1), NULL, NULL); - // Initialize outputs + /* Initialize outputs */ + coreGlobals.nGI = 0; // in fact there are 2 GI relays (playfield / backbox) controlled by low power solenoid outputs + coreGlobals.nAlphaSegs = 20 * 16 * 2; coreGlobals.nLamps = 64 + core_gameData->hw.lampCol * 8; - for (int i = 0; i < 64 + 42 * 8; i++) - coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].type = i < coreGlobals.nLamps ? CORE_MODOUT_BULB_44_20V_DC_GTS3 : CORE_MODOUT_NONE; coreGlobals.nSolenoids = CORE_FIRSTCUSTSOL - 1 + core_gameData->hw.custSol; - for (int i = 0; i < coreGlobals.nSolenoids; i++) - coreGlobals.physicOutputState[CORE_MODOUT_SOL0 + i].type = CORE_MODOUT_SOL_2_STATE; - coreGlobals.nGI = 0; + core_set_pwm_output_type(CORE_MODOUT_LAMP0, coreGlobals.nLamps, CORE_MODOUT_BULB_44_20V_DC_GTS3); + core_set_pwm_output_type(CORE_MODOUT_SOL0, coreGlobals.nSolenoids, CORE_MODOUT_SOL_2_STATE); + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 25, 15, CORE_MODOUT_BULB_44_6_3V_AC); // 'A' relay: lightbox insert (backbox), note that schematics read 6V AC, not 6.3 + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 30, 15, CORE_MODOUT_BULB_44_6_3V_AC); // 'T' relay: GI, note that schematics read 6V AC, not 6.3 + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 31, 15, CORE_MODOUT_PULSE); // 'Q' relay: GameOn + // VFD powered through 8.6V AC for the filaments, and 47V DC for grids and anodes, 0.5ms pulse every 20ms (dimmable) + core_set_pwm_output_type(CORE_MODOUT_SEG0, coreGlobals.nAlphaSegs, CORE_MODOUT_VFD_STROBE_05_20MS); + // Game specific hardware + const struct GameDriver* rootDrv = Machine->gamedrv; + while (rootDrv->clone_of && (rootDrv->clone_of->flags & NOT_A_DRIVER) == 0) + rootDrv = rootDrv->clone_of; + const char* const gn = rootDrv->name; + // First generation + if (strncasecmp(gn, "lca", 3) == 0) { // Light Camera Action + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 17, 6, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield & Backbox flashers + } + // Second generation + else if (strncasecmp(gn, "opthund", 7) == 0) { // Operation Thunder + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 13, 10, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield & Backbox flashers + core_set_pwm_output_type(CORE_MODOUT_LAMP0 + 12 * 8, 8, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Aux board flashers + core_set_pwm_output_type(CORE_MODOUT_LAMP0 + 0 * 8 + 1, 7, CORE_MODOUT_LED_STROBE_1_10MS); // SPECIAL + } + else if (strncasecmp(gn, "surfnsaf", 8) == 0) { // Surf'n Safari + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 10, 15, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield & Backbox flashers + core_set_pwm_output_type(CORE_MODOUT_LAMP0 + 4 * 8 + 4, 4, CORE_MODOUT_LED_STROBE_1_10MS); // Left Billboard + core_set_pwm_output_type(CORE_MODOUT_LAMP0 + 5 * 8 + 2, 4, CORE_MODOUT_LED_STROBE_1_10MS); // Right Billboard + core_set_pwm_output_type(CORE_MODOUT_LAMP0 + 6 * 8 + 7, 1, CORE_MODOUT_LED_STROBE_1_10MS); // Monster Nostrils + } } /*Alpha Numeric First Generation Init*/ @@ -619,36 +608,39 @@ static void gts3dmd_init(void) { (memory_region_length(GTS3_MEMREG_DROM1) - 0x8000), 0x8000); } - GTS3locals.U4_PB_W = dmd_u4_pb_w; GTS3locals.U4_PB_R = dmd_u4_pb_r; GTS3locals.DISPLAY_CONTROL = dmd_display; - GTS3locals.UPDATE_DISPLAY = dmd_update; GTS3locals.AUX_W = dmd_aux; /* Init the sound board */ sndbrd_0_init(core_gameData->hw.soundBoard, 2, memory_region(GTS3_MEMREG_SCPU1), NULL, NULL); // Initialize outputs + coreGlobals.nGI = 0; // in fact there are 2 GI relays (playfield / backbox) controlled by low power solenoid outputs coreGlobals.nLamps = 64 + core_gameData->hw.lampCol * 8; coreGlobals.nSolenoids = CORE_FIRSTCUSTSOL - 1 + core_gameData->hw.custSol; core_set_pwm_output_type(CORE_MODOUT_LAMP0, coreGlobals.nLamps, CORE_MODOUT_BULB_44_20V_DC_GTS3); core_set_pwm_output_type(CORE_MODOUT_SOL0, coreGlobals.nSolenoids, CORE_MODOUT_SOL_2_STATE); + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 25, 15, CORE_MODOUT_BULB_44_6_3V_AC); // 'A' relay: lightbox insert (backbox), note that schematics read 6V AC, not 6.3 + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 30, 15, CORE_MODOUT_BULB_44_6_3V_AC); // 'T' relay: GI, note that schematics read 6V AC, not 6.3 + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 31, 15, CORE_MODOUT_PULSE); // 'Q' relay: GameOn // Game specific hardware const struct GameDriver* rootDrv = Machine->gamedrv; while (rootDrv->clone_of && (rootDrv->clone_of->flags & NOT_A_DRIVER) == 0) rootDrv = rootDrv->clone_of; const char* const gn = rootDrv->name; if (strncasecmp(gn, "barbwire", 8) == 0) { // Barbwire - core_set_pwm_output_type(CORE_MODOUT_SOL0 + 13 - 1, 2, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield & Backbox flashers - core_set_pwm_output_type(CORE_MODOUT_SOL0 + 16 - 1, 9, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield & Backbox flashers + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 13, 2, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield & Backbox flashers + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 16, 9, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield & Backbox flashers } else if (strncasecmp(gn, "cueball", 7) == 0) { // Cueball Wizard - core_set_pwm_output_type(CORE_MODOUT_SOL0 + 14 - 1, 8, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield & Backbox flashers + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 14, 8, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield & Backbox flashers core_set_pwm_output_type(CORE_MODOUT_LAMP0 + 0 * 8 + 2, 6, CORE_MODOUT_LED_STROBE_1_10MS); // 'DOUBLE' LEDs core_set_pwm_output_type(CORE_MODOUT_LAMP0 + 1 * 8 + 2, 6, CORE_MODOUT_LED_STROBE_1_10MS); // 'WIZARD' LEDs } else if (strncasecmp(gn, "sfight2", 7) == 0) { // Street Fighter 2 - core_set_pwm_output_type(CORE_MODOUT_SOL0 + 14 - 1, 9, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield flashers + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 23, 15, CORE_MODOUT_PULSE); // 'S' relay: Lower playfield GameOn + core_set_pwm_output_type(CORE_MODOUT_SOL0 + 14, 9, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Playfield flashers core_set_pwm_output_type(CORE_MODOUT_LAMP0 + 6 * 8 + 1, 7, CORE_MODOUT_LED_STROBE_1_10MS); // 'FIGHTER' LEDs core_set_pwm_output_type(CORE_MODOUT_LAMP0 + 12 * 8, 8, CORE_MODOUT_BULB_89_20V_DC_GTS3); // Backbox flasher (from aux board) } @@ -773,46 +765,27 @@ static WRITE_HANDLER(display_control) { GTS3locals.DISPLAY_CONTROL(offset,data); DS3 = enable i,j,k,l,m,n,dot,comma of Top Segment */ static WRITE_HANDLER(alpha_display){ - if ((GTS3locals.u4pb & ~DBLNK) && (GTS3locals.acol < 20)) { - switch ( offset ) { - case 0: - GTS3locals.segments[20+GTS3locals.acol].b.lo |= GTS3locals.pseg[20+GTS3locals.acol].b.lo = data; -/* // commented out segments dimming as it needs more work - if (GTS3locals.pseg[20+GTS3locals.acol].w) { - coreGlobals.segDim[20+GTS3locals.acol] /=2; - } else { - if (coreGlobals.segDim[20+GTS3locals.acol] < 15) - coreGlobals.segDim[20+GTS3locals.acol] +=3; - else - GTS3locals.segments[20+GTS3locals.acol].w = 0; - GTS3locals.pseg[20+GTS3locals.acol].w = GTS3locals.segments[20+GTS3locals.acol].w; - } -*/ - break; - - case 1: - GTS3locals.segments[20+GTS3locals.acol].b.hi |= GTS3locals.pseg[20+GTS3locals.acol].b.hi = data; - break; - - case 2: - GTS3locals.segments[GTS3locals.acol].b.lo |= GTS3locals.pseg[GTS3locals.acol].b.lo = data; -/* // commented out segments dimming as it needs more work - if (GTS3locals.pseg[GTS3locals.acol].w) { - coreGlobals.segDim[GTS3locals.acol] /=2; - } else { - if (coreGlobals.segDim[GTS3locals.acol] < 15) - coreGlobals.segDim[GTS3locals.acol] +=3; - else - GTS3locals.segments[GTS3locals.acol].w = 0; - GTS3locals.pseg[GTS3locals.acol].w = GTS3locals.segments[GTS3locals.acol].w; - } -*/ - break; - - case 3: - GTS3locals.segments[GTS3locals.acol].b.hi |= GTS3locals.pseg[GTS3locals.acol].b.hi = data; - break; - } + /* Adjust the 16 Segment Layout to match the output order expected by core.c */ + if (offset & 1) // Hi byte (8..15) + { + GTS3locals.activeSegments[offset >> 1].w &= 0x063F; // Remove bits 6..8 and 11..15 + GTS3locals.activeSegments[offset >> 1].w |= ((data & 0x0F) << 11) /* 8..11 => 11..14 */ + | ((data & 0x10) << 2) /* 12 => 6 */ + | ((data & 0x20) << 3) /* 13 => 8 */ + | ((data & 0x40) << 9) /* 14 => 15 */ + | ((data & 0x80) ); /* 15 => 7 */ + } + else // Lo byte (0..7) + { + GTS3locals.activeSegments[offset >> 1].w &= 0xF9C0; // Remove bits 0..5 and 9..10 + GTS3locals.activeSegments[offset >> 1].w |= ((data & 0x3F) ) /* 0.. 5 => 0.. 5 */ + | ((data & 0xC0) << 3); /* 6.. 7 => 9..10 */ + } + if (((GTS3locals.u4pb & DBLNK) == 0) && (GTS3locals.alphaNumCol < 20)) { // This should never happen since character pattern is loaded to the latch registers while DBLNK is raised + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol + 20) * 16 , GTS3locals.activeSegments[0].b.lo); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol + 20) * 16 + 8, GTS3locals.activeSegments[0].b.hi); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol ) * 16 , GTS3locals.activeSegments[0].b.lo); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (GTS3locals.alphaNumCol ) * 16 + 8, GTS3locals.activeSegments[1].b.hi); } } @@ -977,30 +950,6 @@ static INTERRUPT_GEN(alphanmi) { xvia_0_cb2_w(0,0); } -static void alpha_update(){ - /* FORCE The 16 Segment Layout to match the output order expected by core.c */ - // There's got to be a better way than this junky code! - UINT16 segbits, tempbits; - int i,j,k; - for (i=0; i<2; i++) { - for (j=0; j<20; j++){ - segbits = GTS3locals.segments[20*i+j].w; - tempbits = 0; - if (segbits > 0) { - for (k=0; k<16; k++) { - if ( (segbits>>k)&1 ) - tempbits |= (1<gen & GENWPC_HASDMD) == 0) { // Alphanumeric segment strobing change => turn off previous segment and on the ones of the new selected position - int prevIndex = (CORE_MODOUT_SEG0 >> 3) + wpc_data[WPC_ALPHAPOS] * 2; - int newIndex = (CORE_MODOUT_SEG0 >> 3) + data * 2; + // Operation is set all segs to 0 (blanking), then strove to next column, then set segments. + // The delay between setting segments then blanking them is used to a rough PWM dimming + // Overall timing is 1ms maximum per digit over a 16ms period + int prevIndex = CORE_MODOUT_SEG0 + wpc_data[WPC_ALPHAPOS] * 2 * 8; + int newIndex = CORE_MODOUT_SEG0 + data * 2 * 8; if (prevIndex != newIndex) for (int i = 0; i < 4; i++) { - int offset = i == 0 ? 0 : i == 1 ? 1 : i == 2 ? 40 : 41; - core_write_pwm_output_8b((newIndex + offset) * 8, coreGlobals.binaryOutputState[prevIndex + offset]); - core_write_pwm_output_8b((prevIndex + offset) * 8, 0); + int offset = i == 0 ? 0 : i == 1 ? 8 : i == 2 ? 320 : 328; + core_write_pwm_output_8b(newIndex + offset, coreGlobals.binaryOutputState[(prevIndex + offset) >> 3]); + core_write_pwm_output_8b(prevIndex + offset, 0); } } break; /* just save position */ @@ -1166,8 +1169,9 @@ static MACHINE_INIT(wpc) { coreGlobals.nGI = 5; core_set_pwm_output_type(CORE_MODOUT_GI0, coreGlobals.nGI, CORE_MODOUT_BULB_44_6_3V_AC); if (core_gameData->gen & (GEN_WPCALPHA_1 | GEN_WPCALPHA_2)) { // BOP, FH, HD alpahanumeric segments - coreGlobals.nAlphaSegs = 16*8*2; - core_set_pwm_output_type(CORE_MODOUT_SEG0, coreGlobals.nAlphaSegs, CORE_MODOUT_LED); // TODO check the schematics and physical properties. Are these LEDS or something more complex ? + coreGlobals.nAlphaSegs = 40 * 16; + core_set_pwm_output_type(CORE_MODOUT_SEG0, 16 * 16, CORE_MODOUT_VFD_STROBE_1_16MS); + core_set_pwm_output_type(CORE_MODOUT_SEG0 + 20*16, 16 * 16, CORE_MODOUT_VFD_STROBE_1_16MS); } const struct GameDriver* rootDrv = Machine->gamedrv; while (rootDrv->clone_of && (rootDrv->clone_of->flags & NOT_A_DRIVER) == 0)