pwm_display: remove the need to manually call update

This commit is contained in:
hap 2021-04-24 12:14:21 +02:00
parent 17662b19eb
commit 27eaa91cb1
15 changed files with 83 additions and 118 deletions

View file

@ -2,28 +2,28 @@
// copyright-holders:hap
/*
This thing is a generic helper for PWM(strobed) display elements, to prevent flickering
and optionally handle perceived brightness levels.
This thing is a generic helper for PWM(strobed) display elements, to prevent
flickering and optionally handle perceived brightness levels.
Common usecase is to call matrix(selmask, datamask), a collision between the 2 masks
implies a powered-on display element (eg. a LED, or VFD sprite). The maximum matrix
size is 64 by 64, simply due to uint64_t constraints. If a larger size is needed,
create an array of pwm_display_device.
Common usecase is to call matrix(selmask, datamask), a collision between the
2 masks implies a powered-on display element (eg. a LED, or VFD sprite).
The maximum matrix size is 64 by 64, simply due to uint64_t constraints.
If a larger size is needed, create an array of pwm_display_device.
If display elements are directly addressable, you can also use write_element or write_row
to set them. In this case it is required to call update() to apply the changes.
If display elements are directly addressable, you can also use write_element
or write_row to set them.
Display element states are sent to output tags "y.x" where y is the matrix row number,
x is the row bit. It is also sent to "y.a" for all rows. The output state is 0 for off,
and >0 for on, depending on brightness level. If segmask is defined, it is also sent
to "digity", for use with multi-state elements, eg. 7seg leds.
Display element states are sent to output tags "y.x" where y is the matrix row
number, x is the row bit. It is also sent to "y.a" for all rows. The output state
is 0 for off, and >0 for on, depending on brightness level. If segmask is defined,
it is also sent to "digity", for use with multi-state elements, eg. 7seg leds.
If you use this device in a slot, or use multiple of them (or just don't want to use
the default output tags), set a callback.
If you use this device in a slot, or use multiple of them (or just don't want
to use the default output tags), set a callback.
Brightness tresholds (0.0 to 1.0) indicate how long an element was powered on in the last
frame, eg. 0.01 means a minimum on-time for 1%. Some games use two levels of brightness
by strobing elements longer.
Brightness tresholds (0.0 to 1.0) indicate how long an element was powered on
in the last frame, eg. 0.01 means a minimum on-time for 1%. Some games use two
levels of brightness by strobing elements longer.
TODO:
@ -43,7 +43,7 @@ DEFINE_DEVICE_TYPE(PWM_DISPLAY, pwm_display_device, "pwm_display", "PWM Display"
// constructor
//-------------------------------------------------
pwm_display_device::pwm_display_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
pwm_display_device::pwm_display_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
device_t(mconfig, PWM_DISPLAY, tag, owner, clock),
m_out_x(*this, "%u.%u", 0U, 0U),
m_out_a(*this, "%u.a", 0U),
@ -63,7 +63,6 @@ pwm_display_device::pwm_display_device(const machine_config &mconfig, const char
}
//-------------------------------------------------
// device_start/reset
//-------------------------------------------------
@ -84,16 +83,14 @@ void pwm_display_device::device_start()
}
// initialize
m_rowsel = 0;
std::fill(std::begin(m_rowdata), std::end(m_rowdata), 0);
std::fill(std::begin(m_rowdata_prev), std::end(m_rowdata_prev), 0);
for (auto &bri : m_bri)
std::fill(std::begin(bri), std::end(bri), 0.0);
m_frame_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pwm_display_device::frame_tick),this));
m_update_time = machine().time();
m_rowsel = 0;
m_rowsel_prev = 0;
m_sync_time = machine().time();
// register for savestates
save_item(NAME(m_width));
@ -107,12 +104,10 @@ void pwm_display_device::device_start()
save_item(NAME(m_segmask));
save_item(NAME(m_rowsel));
save_item(NAME(m_rowsel_prev));
save_item(NAME(m_rowdata));
save_item(NAME(m_rowdata_prev));
save_item(NAME(m_bri));
save_item(NAME(m_update_time));
save_item(NAME(m_sync_time));
save_item(NAME(m_acc));
}
@ -122,11 +117,10 @@ void pwm_display_device::device_reset()
fatalerror("%s: Invalid size %d*%d, maximum is 64*64!\n", tag(), m_height, m_width);
schedule_frame();
m_update_time = machine().time();
m_sync_time = machine().time();
}
//-------------------------------------------------
// public handlers (most of the interface is in the .h file)
//-------------------------------------------------
@ -156,8 +150,10 @@ pwm_display_device &pwm_display_device::set_segmask(u64 digits, u64 mask)
return *this;
}
void pwm_display_device::matrix_partial(u8 start, u8 height, u64 rowsel, u64 rowdata, bool upd)
void pwm_display_device::matrix_partial(u8 start, u8 height, u64 rowsel, u64 rowdata)
{
sync();
u64 selmask = (u64(1) << height) - 1;
rowsel &= selmask;
selmask <<= start;
@ -170,45 +166,8 @@ void pwm_display_device::matrix_partial(u8 start, u8 height, u64 rowsel, u64 row
m_rowdata[y] = (rowsel & 1) ? (rowdata & rowmask) : 0;
rowsel >>= 1;
}
if (upd)
update();
}
void pwm_display_device::update()
{
// call this every time after m_rowdata is changed (automatic with matrix)
const attotime now = machine().time();
const attotime diff = (m_update_time >= now) ? attotime::zero : now - m_update_time;
u64 sel = m_rowsel_prev;
m_rowsel_prev = m_rowsel;
// accumulate active time
for (int y = 0; y < m_height; y++)
{
u64 row = m_rowdata_prev[y];
m_rowdata_prev[y] = m_rowdata[y];
if (diff != attotime::zero)
{
if (sel & 1)
m_acc[y][m_width] += diff;
for (int x = 0; x < m_width; x++)
{
if (row & 1)
m_acc[y][x] += diff;
row >>= 1;
}
}
sel >>= 1;
}
m_update_time = now;
}
//-------------------------------------------------
// internal handlers
@ -239,7 +198,7 @@ TIMER_CALLBACK_MEMBER(pwm_display_device::frame_tick)
if (cutoff > 1.0)
cutoff = 1.0;
update(); // final timeslice
sync(); // final timeslice
for (int y = 0; y < m_height; y++)
{
@ -290,3 +249,33 @@ TIMER_CALLBACK_MEMBER(pwm_display_device::frame_tick)
schedule_frame();
}
void pwm_display_device::sync()
{
const attotime now = machine().time();
const attotime last = m_sync_time;
m_sync_time = now;
if (last >= now)
return;
const attotime diff = now - last;
u64 sel = m_rowsel;
// accumulate active time
for (int y = 0; y < m_height; y++)
{
u64 row = m_rowdata[y];
if (sel & 1)
m_acc[y][m_width] += diff;
for (int x = 0; x < m_width; x++)
{
if (row & 1)
m_acc[y][x] += diff;
row >>= 1;
}
sel >>= 1;
}
}

View file

@ -35,9 +35,8 @@ public:
void set_bri_one(u8 i, double level) { m_levels[i] = level; }
void segmask_one(u8 y, u64 mask) { m_segmask[y] = mask; }
void matrix_partial(u8 start, u8 height, u64 rowsel, u64 rowdata, bool upd = true);
void matrix(u64 rowsel, u64 rowdata, bool upd = true) { matrix_partial(0, m_height, rowsel, rowdata, upd); }
void update(); // apply changes to m_rowdata
void matrix_partial(u8 start, u8 height, u64 rowsel, u64 rowdata);
void matrix(u64 rowsel, u64 rowdata) { matrix_partial(0, m_height, rowsel, rowdata); }
void clear() { matrix(0, 0); }
// directly handle individual element (does not affect m_rowsel), y = row num, x = row bit
@ -46,8 +45,8 @@ public:
// directly handle row data
u64 read_row(offs_t offset) { return m_rowdata[offset]; }
void write_row(offs_t offset, u64 data) { m_rowdata[offset] = data; m_rowsel |= u64(1) << offset; }
void clear_row(offs_t offset, u64 data = 0) { m_rowdata[offset] = 0; m_rowsel &= ~(u64(1) << offset); }
void write_row(offs_t offset, u64 data) { sync(); m_rowdata[offset] = data; m_rowsel |= u64(1) << offset; }
void clear_row(offs_t offset, u64 data = 0) { sync(); m_rowdata[offset] = 0; m_rowsel &= ~(u64(1) << offset); }
// directly handle element current brightness
double read_element_bri(u8 y, u8 x) { return m_bri[y][x]; }
@ -81,17 +80,16 @@ private:
u64 m_segmask[0x40];
u64 m_rowsel;
u64 m_rowsel_prev;
u64 m_rowdata[0x40];
u64 m_rowdata_prev[0x40];
double m_bri[0x40][0x41];
attotime m_update_time;
attotime m_sync_time;
attotime m_acc[0x40][0x41];
emu_timer *m_frame_timer;
TIMER_CALLBACK_MEMBER(frame_tick);
void schedule_frame();
void sync();
};

View file

@ -169,8 +169,6 @@ void desdis_state::update_lcd()
u8 mask = (m_select & 8) ? 0 : 0xff;
for (int i = 0; i < 4; i++)
m_display->write_row(i+2, (m_lcd_data >> (8*i) & 0xff) ^ mask);
m_display->update();
}
void desdis_state::control_w(offs_t offset, u8 data)
@ -188,7 +186,7 @@ void desdis_state::control_w(offs_t offset, u8 data)
m_dac->write(BIT(sel, 9));
// 74259 Q0,Q1: led select (active low)
m_display->matrix_partial(0, 2, ~m_select & 3, led_data, false);
m_display->matrix_partial(0, 2, ~m_select & 3, led_data);
// 74259 Q2: book rom A14
if (m_rombank != nullptr)

View file

@ -267,7 +267,7 @@ void excel_state::ttl_w(offs_t offset, u8 data)
u8 seg_data = bitswap<8>(m_7seg_data,0,1,3,2,7,5,6,4);
// update display: 4 7seg leds, 2*8 chessboard leds
m_display->matrix_partial(0, 2, led_sel, led_data, false);
m_display->matrix_partial(0, 2, led_sel, led_data);
m_display->matrix_partial(2, 4, led_sel >> 2, seg_data); // 6093
// speech (model 6092)

View file

@ -217,8 +217,6 @@ void phantom_state::update_lcd()
u8 mask = (m_select & 0x80) ? 0xff : 0;
for (int i = 0; i < 4; i++)
m_display->write_row(i+1, (m_lcd_data >> (8*i) & 0xff) ^ mask);
m_display->update();
}
void phantom_state::control_w(offs_t offset, u8 data)
@ -230,7 +228,7 @@ void phantom_state::control_w(offs_t offset, u8 data)
// 74259 Q0-Q3: 7442 a0-a3
// 7442 0-8: led data, input mux
// 74259 Q4: led select
m_display->matrix_partial(0, 1, BIT(~m_select, 4), 1 << (m_select & 0xf), false);
m_display->matrix_partial(0, 1, BIT(~m_select, 4), 1 << (m_select & 0xf));
// 74259 Q6: bookrom bank
m_rombank->set_entry(BIT(m_select, 6));

View file

@ -582,7 +582,6 @@ void maniac_state::update_display()
{
m_display->write_row(0, ~m_b & 0x7f);
m_display->write_row(1, ~m_c & 0x7f);
m_display->update();
}
void maniac_state::update_speaker()
@ -1281,7 +1280,7 @@ public:
void rockpin_state::update_display()
{
// 3 7seg leds from ports A and B
m_display->matrix_partial(0, 3, m_a, m_b, false);
m_display->matrix_partial(0, 3, m_a, m_b);
// 44 leds from ports C and D
m_display->matrix_partial(3, 6, m_d, m_c);

View file

@ -3230,7 +3230,6 @@ void qfire_state::write_r(u16 data)
// R3,R4,R6-R8: leds (direct)
m_display->write_row(2, (data >> 3 & 3) | (data >> 4 & 0x1c));
m_display->update();
// R9: speaker out
m_speaker->level_w(data >> 9 & 1);
@ -3242,7 +3241,6 @@ void qfire_state::write_o(u16 data)
// O1-O7: 2nd digit segments
m_display->write_row(0, (data & 1) ? 6 : 0);
m_display->write_row(1, data >> 1 & 0x7f);
m_display->update();
}
u8 qfire_state::read_k()
@ -3784,13 +3782,12 @@ void ebball3_state::set_clock()
void ebball3_state::update_display()
{
m_display->matrix_partial(0, 10, m_r, m_o, false);
m_display->matrix_partial(0, 10, m_r, m_o);
// R0,R1 are normal 7segs
// R4,R7 contain segments(only F and B) for the two other digits
m_display->write_row(10, (m_display->read_row(4) & 0x20) | (m_display->read_row(7) & 0x02));
m_display->write_row(11, ((m_display->read_row(4) & 0x10) | (m_display->read_row(7) & 0x01)) << 1);
m_display->update();
}
void ebball3_state::write_r(u16 data)
@ -5591,7 +5588,6 @@ void fxmcr165_state::update_display()
// leds from R4-R10
m_display->write_row(1, m_r >> 4 & 0x7f);
m_display->update();
}
void fxmcr165_state::write_r(u16 data)
@ -6177,7 +6173,7 @@ public:
void elecbowl_state::update_display()
{
// standard 7segs
m_display->matrix_partial(0, 4, m_r >> 4, m_o, false);
m_display->matrix_partial(0, 4, m_r >> 4, m_o);
// lamp muxes
u8 sel = m_o & 7;
@ -6189,7 +6185,6 @@ void elecbowl_state::update_display()
// digit 4 is from mux2 Q7
m_display->write_row(4, m_display->read_element(6, 7) ? 6 : 0);
m_display->update();
}
void elecbowl_state::write_r(u16 data)
@ -6339,13 +6334,11 @@ void horseran_state::lcd_output_w(offs_t offset, u32 data)
return;
// update lcd segments
m_display->matrix_partial(0, 3, 1 << offset, data, false);
m_display->matrix_partial(0, 3, 1 << offset, data);
// col5-11 and col13-19 are 7segs
for (int i = 0; i < 2; i++)
m_display->write_row(3 + (offset << 1 | i), bitswap<8>(data >> (4+8*i),7,3,5,2,0,1,4,6) & 0x7f);
m_display->update();
}
void horseran_state::write_r(u16 data)
@ -7704,8 +7697,6 @@ void mbdtower_state::update_display()
m_display->write_row(2, (m_o & 0x80) ? o : 0);
m_display->write_row(1, (m_o & 0x80) ? 0 : o);
m_display->write_row(0, (m_r >> 8 & 1) | (m_r >> 4 & 0xe));
m_display->update();
}
else
{
@ -9760,13 +9751,12 @@ public:
void tisr16_state::update_display()
{
m_display->matrix(m_r, m_o, false);
m_display->matrix(m_r, m_o);
// exponent sign is from R10 O1, and R10 itself only uses segment G
u8 r10 = m_display->read_row(10);
m_display->write_row(11, r10 << 5 & 0x40);
m_display->write_row(10, r10 & 0x40);
m_display->update();
}
void tisr16_state::write_r(u16 data)
@ -10957,14 +10947,13 @@ void lilprof78_state::write_r(u16 data)
// update leds state
u8 seg = bitswap<8>(m_o,7,4,3,2,1,0,6,5) & 0x7f;
u16 r = (data & 7) | (data << 1 & 0x1f0);
m_display->matrix(r, seg, false);
m_display->matrix(r, seg);
// 3rd digit A/G(equals sign) is from O7
m_display->write_row(3, (r != 0 && m_o & 0x80) ? 0x41 : 0);
// 6th digit is a custom 7seg for math symbols (see wizatron_state write_r)
m_display->write_row(6, bitswap<8>(m_display->read_row(6),7,6,1,4,2,3,5,0));
m_display->update();
}
void lilprof78_state::write_o(u16 data)
@ -12123,16 +12112,14 @@ void tbreakup_state::set_clock()
void tbreakup_state::update_display()
{
// 7seg leds from R0,R1 and O0-O6
m_display->matrix_partial(0, 2, m_r, m_o & 0x7f, false);
m_display->matrix_partial(0, 2, m_r, m_o & 0x7f);
// 22 round leds from O2-O7 and expander port 7
m_display->matrix_partial(2, 6, m_o >> 2, m_exp_port[6], false);
m_display->matrix_partial(2, 6, m_o >> 2, m_exp_port[6]);
// 24 rectangular leds from expander ports 1-6 (not strobed)
for (int y = 0; y < 6; y++)
m_display->write_row(y+8, m_exp_port[y]);
m_display->update();
}
void tbreakup_state::expander_w(offs_t offset, u8 data)

View file

@ -117,8 +117,6 @@ void cforte_state::lcd_output_w(u64 data)
data = bitswap<8>(data,7,2,0,4,6,5,3,1);
m_display->write_row(dig+3, data);
}
m_display->update();
}

View file

@ -179,7 +179,7 @@ DEVICE_IMAGE_LOAD_MEMBER(sag_state::cart_load)
void sag_state::update_display()
{
// grid 0-7 are the 'pixels'
m_display->matrix_partial(0, 8, m_grid, m_plate, false);
m_display->matrix_partial(0, 8, m_grid, m_plate);
// grid 8-13 are 7segs
u8 seg = bitswap<7>(m_plate,4,5,6,7,8,9,10);

View file

@ -119,11 +119,11 @@ void corona_state::machine_reset()
void corona_state::update_leds()
{
// button leds
m_display->matrix_partial(0, 2, 1 << (m_control1 >> 5 & 1), ~m_led_data1 & 0xff, false);
m_display->matrix_partial(0, 2, 1 << (m_control1 >> 5 & 1), ~m_led_data1 & 0xff);
m_display->write_row(2, ~m_select1 >> 4 & 0xf);
// chessboard leds
m_display->matrix_partial(3, 8, 1 << (m_select1 & 0xf), m_led_data2, true);
m_display->matrix_partial(3, 8, 1 << (m_select1 & 0xf), m_led_data2);
}
void corona_state::leds1_w(u8 data)

View file

@ -125,8 +125,8 @@ void leo_state::machine_start()
void leo_state::update_display()
{
m_display->matrix_partial(0, 8, 1 << (m_inp_mux & 0xf), m_led_data[0], false);
m_display->matrix_partial(8, 2, 1 << BIT(m_inp_mux, 5), (~m_inp_mux << 2 & 0x300) | m_led_data[1], true);
m_display->matrix_partial(0, 8, 1 << (m_inp_mux & 0xf), m_led_data[0]);
m_display->matrix_partial(8, 2, 1 << BIT(m_inp_mux, 5), (~m_inp_mux << 2 & 0x300) | m_led_data[1]);
}
void leo_state::mux_w(u8 data)

View file

@ -131,8 +131,8 @@ void ren_state::lcd_output_w(offs_t offset, u64 data)
void ren_state::update_display()
{
m_display->matrix_partial(0, 9, 1 << (m_inp_mux & 0xf), (m_inp_mux << 4 & 0x100) | m_led_data[0], false);
m_display->matrix_partial(9, 1, 1, (m_inp_mux >> 2 & 0x30) | m_led_data[1], true);
m_display->matrix_partial(0, 9, 1 << (m_inp_mux & 0xf), (m_inp_mux << 4 & 0x100) | m_led_data[0]);
m_display->matrix_partial(9, 1, 1, (m_inp_mux >> 2 & 0x30) | m_led_data[1]);
}
void ren_state::mux_w(u8 data)

View file

@ -118,7 +118,6 @@ void schess_state::leds2_w(offs_t offset, u8 data)
{
// button panel leds (direct)
m_display->write_row(8 + (offset ? 1 : 0), ~data);
m_display->update();
}
void schess_state::control_w(u8 data)

View file

@ -181,8 +181,8 @@ void simultano_state::select_w(u8 data)
// d0-d3: input/chessboard mux
// d6,d7: side panel led mux
// d4,d5: led data
m_display->matrix_partial(0, 2, data >> 4 & 3, 1 << (data & 0xf), false);
m_display->matrix_partial(2, 2, data >> 6 & 3, ~data >> 4 & 3, true);
m_display->matrix_partial(0, 2, data >> 4 & 3, 1 << (data & 0xf));
m_display->matrix_partial(2, 2, data >> 6 & 3, ~data >> 4 & 3);
m_select = data;
}

View file

@ -236,7 +236,6 @@ void ssystem3_state::control_w(u8 data)
for (int i = 0; i < 4; i++)
m_display[0]->write_row(i, m_lcd1_data >> (8*i) & 0xff);
m_display[0]->write_row(4, (m_shift ^ xorval) | 0x100);
m_display[0]->update();
}
// PB3: device serial out