casio/pickytlk.cpp: Add tablet support (#12226)

This commit is contained in:
qufb 2024-06-07 12:54:54 +01:00 committed by GitHub
parent 4726781363
commit 6a08db6399
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 392 additions and 17 deletions

View File

@ -42,7 +42,7 @@ enum
HCD62121_R70, HCD62121_R74, HCD62121_R78, HCD62121_R7C
};
// TODO - Max value stored with "movb reg,f" is 0x3f, is bit 5 set in other instructions?
constexpr u8 FLAG_INPUT = 0x20;
constexpr u8 FLAG_CL = 0x10;
constexpr u8 FLAG_Z = 0x08;
constexpr u8 FLAG_C = 0x04;
@ -80,6 +80,7 @@ hcd62121_cpu_device::hcd62121_cpu_device(const machine_config &mconfig, const ch
, m_opt_cb(*this)
, m_ki_cb(*this, 0)
, m_in0_cb(*this, 0)
, m_input_flag_cb(*this, 0)
{
}
@ -639,6 +640,15 @@ inline void hcd62121_cpu_device::set_cl_flag(bool is_cl)
}
void hcd62121_cpu_device::set_input_flag(bool is_input_set)
{
if (is_input_set)
m_f |= FLAG_INPUT;
else
m_f &= ~FLAG_INPUT;
}
inline void hcd62121_cpu_device::op_msk(int size)
{
bool zero_high = true;
@ -888,7 +898,13 @@ void hcd62121_cpu_device::execute_run()
m_cycles_until_timeout = 0;
m_is_infinite_timeout = false;
}
else if (m_is_infinite_timeout)
if (m_input_flag_cb() != 0)
{
set_input_flag(false);
}
if (m_is_infinite_timeout)
{
m_icount = 0;
}
@ -2180,6 +2196,8 @@ void hcd62121_cpu_device::execute_run()
// Approximately 209.34ms
m_cycles_until_timeout = 0x400 * TIMER_STATE_READ_CYCLES;
break;
case 0x09:
case 0x0b:
case 0x49:
case 0x4b:
case 0xc9:

View File

@ -17,6 +17,7 @@ public:
auto opt_cb() { return m_opt_cb.bind(); }
auto ki_cb() { return m_ki_cb.bind(); }
auto in0_cb() { return m_in0_cb.bind(); }
auto input_flag_cb() { return m_input_flag_cb.bind(); }
protected:
enum
@ -74,6 +75,7 @@ private:
void set_zl_flag(bool is_zl);
void set_zh_flag(bool is_zh);
void set_cl_flag(bool is_cl);
void set_input_flag(bool is_input_set);
void op_msk(int size);
void op_and(int size);
void op_or(int size);
@ -126,6 +128,7 @@ private:
devcb_write8 m_opt_cb;
devcb_read8 m_ki_cb;
devcb_read8 m_in0_cb;
devcb_read8 m_input_flag_cb;
};

View File

@ -4,7 +4,11 @@
Driver for Casio Picky Talk
Missing inputs, and I/O callbacks copied from CFX9850G need to be reviewed.
TODO:
- Communication port;
- Panel active buttons display;
- Review PORT/OPT callbacks copied from CFX9850G;
Some points of interest can be accessed under the debugger:
@ -28,9 +32,17 @@
#include "cpu/hcd62121/hcd62121.h"
#include "crsshair.h"
#include "emupal.h"
#include "screen.h"
#include "pickytlk.lh"
#define LOG_IO (1U << 1)
#define LOG_TABLET (1U << 2)
//#define VERBOSE (LOG_IO | LOG_TABLET)
#include "logmacro.h"
namespace {
@ -39,9 +51,12 @@ class pickytlk_state : public driver_device
public:
pickytlk_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag)
, m_video_ram(*this, "video_ram")
, m_display_ram(*this, "display_ram")
, m_maincpu(*this, "maincpu")
, m_io_buttons(*this, "BUTTONS")
, m_io_pen_x(*this, "PEN_X")
, m_io_pen_y(*this, "PEN_Y")
, m_io_pen_y_rescale(*this, "PEN_Y_RESCALE")
, m_ko(0)
, m_port(0)
, m_opt(0)
@ -49,31 +64,134 @@ public:
void pickytlk(machine_config &config);
DECLARE_CROSSHAIR_MAPPER_MEMBER(pen_y_mapper);
DECLARE_CUSTOM_INPUT_MEMBER(pen_y_rescale_r);
ioport_value pen_target_r();
private:
enum pen_target : u8
{
PEN_TARGET_LCD = 0,
PEN_TARGET_PANEL = 1,
};
enum pen_state : u8
{
PEN_RELEASE = 0,
PEN_PRESS = 1,
PEN_HOLD = 2,
};
virtual void machine_start() override;
virtual void machine_reset() override;
void kol_w(u8 data);
void koh_w(u8 data);
void port_w(u8 data);
void opt_w(u8 data);
u8 ki_r();
u8 in0_r();
u8 input_flag_read();
TIMER_CALLBACK_MEMBER(io_timer_tick);
u8 io_pen_x_read();
u8 io_pen_y_read();
u8 tablet_read(offs_t offset);
void tablet_write(offs_t offset, u8 data);
void update_crosshair(screen_device &screen);
void pickytlk_palette(palette_device &palette) const;
u32 screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
void pickytlk_mem(address_map &map);
required_shared_ptr<u8> m_video_ram;
static constexpr float rescale(float x, float min_x, float max_x, float a, float b)
{
// Rescaling (min-max normalization) from [min_x..max_x] to [a..b].
return a + (((x - min_x) * (b - a)) / (max_x - min_x));
}
required_shared_ptr<u8> m_display_ram;
required_device<hcd62121_cpu_device> m_maincpu;
required_ioport m_io_buttons;
required_ioport m_io_pen_x;
required_ioport m_io_pen_y;
required_ioport m_io_pen_y_rescale;
u16 m_ko; // KO lines
u8 m_port; // PORT lines (serial I/O)
u8 m_opt; // OPT lines (contrast)
std::unique_ptr<u8[]> m_io_tablet_regs;
emu_timer *m_io_timer;
u8 m_pen_state;
u8 m_pen_target;
};
void pickytlk_state::machine_start()
{
save_item(NAME(m_ko));
save_item(NAME(m_port));
save_item(NAME(m_opt));
m_io_tablet_regs = make_unique_clear<u8[]>(0x100);
save_pointer(NAME(m_io_tablet_regs), 0x100);
m_io_timer = timer_alloc(FUNC(pickytlk_state::io_timer_tick), this);
save_item(NAME(m_pen_state));
save_item(NAME(m_pen_target));
}
void pickytlk_state::machine_reset()
{
memset(m_io_tablet_regs.get(), 0, 0x100);
m_io_timer->reset(attotime::never);
m_pen_state = PEN_RELEASE;
m_pen_target = PEN_TARGET_LCD;
}
CROSSHAIR_MAPPER_MEMBER(pickytlk_state::pen_y_mapper)
{
// Parameter `linear_value` is ignored, since we will read the input port directly
// for adjustments, just need to return that value in the expected range [0.0f..1.0f].
return (float) pen_y_rescale_r() / 0xff;
}
CUSTOM_INPUT_MEMBER(pickytlk_state::pen_y_rescale_r)
{
/*
There are two distinct areas that can be interacted with the pen:
- LCD screen visible area: pen coordinates in [0x20..0xa0];
- Bottom panel: pen coordinates in [0xa0..0xf0];
In order to transparently map coordinates between each area, we split
the value across these areas, but rescaled to the input port's full range.
*/
const s16 io_pen_y_min = m_io_pen_y->field(0xff)->minval();
const s16 io_pen_y_max = m_io_pen_y->field(0xff)->maxval();
const s16 screen_y_max = io_pen_y_max * 0.6f;
s16 adjusted_value = m_io_pen_y->read();
if (adjusted_value > screen_y_max)
{
adjusted_value = rescale(adjusted_value, screen_y_max, io_pen_y_max, io_pen_y_min, io_pen_y_max);
m_pen_target = PEN_TARGET_PANEL;
}
else
{
adjusted_value = rescale(adjusted_value, io_pen_y_min, screen_y_max, io_pen_y_min, io_pen_y_max);
m_pen_target = PEN_TARGET_LCD;
}
return adjusted_value;
}
ioport_value pickytlk_state::pen_target_r()
{
return m_pen_target;
}
void pickytlk_state::pickytlk_mem(address_map &map)
{
map(0x000000, 0x007fff).mirror(0x008000).rom();
map(0x080000, 0x0807ff).ram().share("video_ram");
map(0x080000, 0x0807ff).ram();
map(0x080300, 0x08030f).rw(FUNC(pickytlk_state::tablet_read), FUNC(pickytlk_state::tablet_write));
// map(0x100000, 0x10ffff) // some memory mapped i/o???
// map(0x110000, 0x11ffff) // some memory mapped i/o???
map(0x200000, 0x2fffff).rom().region("mask_rom", 0);
@ -82,34 +200,124 @@ void pickytlk_state::pickytlk_mem(address_map &map)
// map(0xe10000, 0xe1ffff) // some memory mapped i/o???
}
TIMER_CALLBACK_MEMBER(pickytlk_state::io_timer_tick)
{
if (m_pen_state == PEN_PRESS)
{
m_pen_state = PEN_HOLD;
}
}
u8 pickytlk_state::io_pen_x_read()
{
// Pen callibration tests seem to check coordinates relative to the center of the LCD screen,
// and those offsets also align with the LCD position relative to the full tablet surface.
s16 io_pen_x_min = m_io_pen_x->field(0xff)->minval();
s16 io_pen_x_max = m_io_pen_x->field(0xff)->maxval();
s16 io_pen_x_pos = m_io_pen_x->read();
return rescale(io_pen_x_pos, io_pen_x_min, io_pen_x_max, 0x20, 0xdf);
}
u8 pickytlk_state::io_pen_y_read()
{
s16 io_pen_y_min = m_io_pen_y->field(0xff)->minval();
s16 io_pen_y_max = m_io_pen_y->field(0xff)->maxval();
s16 io_pen_y_pos = pen_y_rescale_r();
return (m_pen_target == PEN_TARGET_LCD)
? rescale(io_pen_y_pos, io_pen_y_min, io_pen_y_max, 0x20, 0xa0)
: rescale(io_pen_y_pos, io_pen_y_min, io_pen_y_max, 0xa0, 0xf0);
}
u8 pickytlk_state::tablet_read(offs_t offset)
{
/*
Pen coordinates can return a mirrored value when bit 4 is not set.
Both pairs of values <x1,x2>, <y1,y2> are approximated from these tests:
- General case:
- x1 + x2 > 0xc8
- y1 + y2 > 0xc8
- Reset screen with "OK" prompt:
- x1 > 0x7d
- y1 > 0x7d
- Pen callibration:
- x1 < 0x72
- x2 < 0x72
- y1 < 0x64
- y1 > 0x68
*/
LOGMASKED(LOG_TABLET, "%s: tablet_read [%02x] = %02x\n", machine().describe_context(), offset, m_io_tablet_regs[offset]);
switch (offset)
{
case 0:
{
u8 y = BIT(m_ko, 7) ? io_pen_y_read() : 0;
LOGMASKED(LOG_TABLET, "%s: pen y = %02x\n", machine().describe_context(), y);
return BIT(m_ko, 4) ? y : (0xff - y);
}
case 1:
{
u8 x = BIT(m_ko, 6) ? io_pen_x_read() : 0;
LOGMASKED(LOG_TABLET, "%s: pen x = %02x\n", machine().describe_context(), x);
return BIT(m_ko, 4) ? x : (0xff - x);
}
case 4:
// Can return 0 if other values are not stable/ready?
return 0x80;
default:
return m_io_tablet_regs[offset];
}
}
void pickytlk_state::tablet_write(offs_t offset, u8 data)
{
LOGMASKED(LOG_TABLET, "%s: tablet_write [%02x] = %02x\n", machine().describe_context(), offset, data);
m_io_tablet_regs[offset] = data;
}
void pickytlk_state::kol_w(u8 data)
{
m_ko = (m_ko & 0xff00) | data;
logerror("%s: KO is now %04x\n", machine().describe_context(), m_ko);
LOGMASKED(LOG_IO, "%s: KO = %04x\n", machine().describe_context(), m_ko);
}
void pickytlk_state::koh_w(u8 data)
{
m_ko = (m_ko & 0x00ff) | (u16(data) << 8);
logerror("%s: KO is now %04x\n", machine().describe_context(), m_ko);
LOGMASKED(LOG_IO, "%s: KO = %04x\n", machine().describe_context(), m_ko);
}
void pickytlk_state::port_w(u8 data)
{
m_port = data;
logerror("%s: PORT is now %02x\n", machine().describe_context(), m_port);
LOGMASKED(LOG_IO, "%s: PORT = %02x\n", machine().describe_context(), m_port);
}
void pickytlk_state::opt_w(u8 data)
{
m_opt = data;
logerror("%s: OPT is now %02x\n", machine().describe_context(), m_opt);
LOGMASKED(LOG_IO, "%s: OPT = %02x\n", machine().describe_context(), m_opt);
}
u8 pickytlk_state::ki_r()
{
// TODO
return 0;
if (BIT(m_io_buttons->read(), 6))
{
if (m_pen_state == PEN_RELEASE)
{
m_pen_state = PEN_PRESS;
// FIXME: Adjust delay when more accurate instruction timings are implemented.
// Program code waits for input flag to be stable by executing `mov DSIZE,0xff`
// then `movq R00,R00` 15 times (see pickytlk ROM @ 2015f6).
m_io_timer->adjust(attotime::from_msec(1), 0, attotime::never);
}
}
else
{
m_pen_state = PEN_RELEASE;
m_io_timer->reset(attotime::never);
}
return m_pen_state == PEN_PRESS ? 0x80 : 0;
}
u8 pickytlk_state::in0_r()
@ -124,6 +332,17 @@ u8 pickytlk_state::in0_r()
return 0x30 & ~0x00;
}
u8 pickytlk_state::input_flag_read()
{
return m_pen_state == PEN_HOLD ? 0 : 1;
}
void pickytlk_state::update_crosshair(screen_device &screen)
{
// Either screen crosshair or layout view's cursor should be visible at a time.
machine().crosshair().get_crosshair(0).set_screen(m_pen_target ? CROSSHAIR_SCREEN_NONE : &screen);
}
void pickytlk_state::pickytlk_palette(palette_device &palette) const
{
palette.set_pen_color(0, 0xee, 0xee, 0xcc);
@ -134,6 +353,8 @@ void pickytlk_state::pickytlk_palette(palette_device &palette) const
u32 pickytlk_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
update_crosshair(screen);
u16 offset = 0;
for (int i = 0; i < 16; i++)
@ -149,7 +370,10 @@ u32 pickytlk_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, c
for (int b = 0; b < 8; b++)
{
row[x + b] = (BIT(data1, b) << 1) | BIT(data2, b);
if (x + b < 127)
{
row[x + b] = (BIT(data1, b) << 1) | BIT(data2, b);
}
}
offset++;
@ -161,7 +385,19 @@ u32 pickytlk_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, c
static INPUT_PORTS_START(pickytlk)
// TODO
// TODO: On/Off/Reset
PORT_START("BUTTONS")
PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_PLAYER(1) PORT_NAME("Pen Down")
PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_CUSTOM ) PORT_READ_LINE_MEMBER(pickytlk_state, pen_target_r)
PORT_START("PEN_X")
PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_X ) PORT_CROSSHAIR(X, 1.0, 0.0, 0) PORT_SENSITIVITY(30) PORT_KEYDELTA(10) PORT_MINMAX(0, 255) PORT_PLAYER(1) PORT_NAME("Pen X")
PORT_START("PEN_Y")
PORT_BIT( 0xff, 0x80, IPT_LIGHTGUN_Y ) PORT_CROSSHAIR(Y, 1.0, 0.0, 0) PORT_SENSITIVITY(30) PORT_KEYDELTA(10) PORT_MINMAX(0, 255) PORT_PLAYER(1) PORT_NAME("Pen Y") PORT_CROSSHAIR_MAPPER_MEMBER(DEVICE_SELF, pickytlk_state, pen_y_mapper)
PORT_START("PEN_Y_RESCALE")
PORT_BIT( 0xff, IP_ACTIVE_HIGH, IPT_CUSTOM ) PORT_CUSTOM_MEMBER(pickytlk_state, pen_y_rescale_r)
INPUT_PORTS_END
@ -175,17 +411,19 @@ void pickytlk_state::pickytlk(machine_config &config)
m_maincpu->opt_cb().set(FUNC(pickytlk_state::opt_w));
m_maincpu->ki_cb().set(FUNC(pickytlk_state::ki_r));
m_maincpu->in0_cb().set(FUNC(pickytlk_state::in0_r));
m_maincpu->input_flag_cb().set(FUNC(pickytlk_state::input_flag_read));
// TODO: Touchpad layout
screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_LCD));
screen.set_refresh_hz(60);
screen.set_size(128, 64);
screen.set_visarea(0, 127, 0, 63);
screen.set_size(127, 64);
screen.set_visarea(0, 126, 0, 63);
screen.set_screen_update(FUNC(pickytlk_state::screen_update));
screen.set_palette("palette");
// TODO: Verify amount of colors and palette. Colors can be changed by changing the contrast.
PALETTE(config, "palette", FUNC(pickytlk_state::pickytlk_palette), 4);
config.set_default_layout(layout_pickytlk);
}

View File

@ -0,0 +1,116 @@
<?xml version="1.0"?>
<!--
license:CC0-1.0
-->
<mamelayout version="2">
<element name="pencursor">
<image state="1">
<data><![CDATA[
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1" height="1" viewBox="0 0 1 1">
<g fill="none" stroke="#6666ff" stroke-width="0.06" stroke-opacity="1">
<circle cx="0.5" cy="0.5" r="0.1" />
<circle cx="0.5" cy="0.5" r="0.47" />
<line x1="0.03" y1="0.5" x2="0.4" y2="0.5" />
<line x1="0.6" y1="0.5" x2="0.97" y2="0.5" />
<line x1="0.5" y1="0.03" x2="0.5" y2="0.4" />
<line x1="0.5" y1="0.6" x2="0.5" y2="0.97" />
<line />
</g>
</svg>
]]></data>
</image>
</element>
<!--
Panel labels:
| ワリア | ▲ | △ |メニュー|
| ◀ | ▶ |
| 修正 | ▼ | ▽ | |
Panel labels (translated):
| Clear | ▲ | △ | Menu |
| ◀ | ▶ |
| Edit | ▼ | ▽ | OK |
-->
<element name="panel">
<!-- background -->
<rect><color red="0.78" green="0.86" blue="0.74" /><bounds x="0" y="0" width="128" height="12" /></rect>
<rect><color red="1" green="1" blue="1" /><bounds x="0" y="12" width="128" height="30" /></rect>
<!-- 1st row -->
<rect><color red="0.82" green="0.47" blue="0.35" /><bounds x="1" y="13" width="30" height="12" /></rect>
<text string="Clear" ><color red="1" green="1" blue="1" /><bounds x="2" y="14" width="28" height="10" /></text>
<rect><color red="0.82" green="0.47" blue="0.35" /><bounds x="33" y="13" width="30" height="8" /></rect>
<text string="▲" ><color red="1" green="1" blue="1" /><bounds x="34" y="12" width="28" height="10" /></text>
<rect><color red="0.82" green="0.47" blue="0.35" /><bounds x="65" y="13" width="30" height="12" /></rect>
<text string="Previous"><color red="1" green="1" blue="1" /><bounds x="66" y="14" width="28" height="10" /></text>
<rect><color red="0.82" green="0.47" blue="0.35" /><bounds x="97" y="13" width="30" height="12" /></rect>
<text string="Menu" ><color red="1" green="1" blue="1" /><bounds x="98" y="14" width="28" height="10" /></text>
<!-- 2nd row -->
<rect><color red="0.82" green="0.47" blue="0.35" /><bounds x="33" y="23" width="14" height="6" /></rect>
<text string="◄" ><color red="1" green="1" blue="1" /><bounds x="34" y="22" width="12" height="8" /></text>
<rect><color red="0.82" green="0.47" blue="0.35" /><bounds x="49" y="23" width="14" height="6" /></rect>
<text string="►" ><color red="1" green="1" blue="1" /><bounds x="50" y="22" width="12" height="8" /></text>
<!-- 3rd row -->
<rect><color red="0.82" green="0.47" blue="0.35" /><bounds x="1" y="27" width="30" height="12" /></rect>
<text string="Edit"><color red="1" green="1" blue="1" /><bounds x="2" y="28" width="28" height="10" /></text>
<rect><color red="0.82" green="0.47" blue="0.35" /><bounds x="33" y="31" width="30" height="8" /></rect>
<text string="▼" ><color red="1" green="1" blue="1" /><bounds x="34" y="30" width="28" height="10" /></text>
<rect><color red="0.82" green="0.47" blue="0.35" /><bounds x="65" y="27" width="30" height="12" /></rect>
<text string="Next"><color red="1" green="1" blue="1" /><bounds x="66" y="28" width="28" height="10" /></text>
<rect><color red="0.82" green="0.47" blue="0.35" /><bounds x="97" y="27" width="30" height="12" /></rect>
<text string="OK" ><color red="1" green="1" blue="1" /><bounds x="98" y="28" width="28" height="10" /></text>
</element>
<view name="Default View">
<screen index="0">
<bounds x="0" y="0" width="128" height="64" />
</screen>
<element id="panel" ref="panel">
<bounds x="0" y="64" width="128" height="42" />
</element>
<element id="pencursor" ref="pencursor">
<!-- will be positioned by script -->
<bounds x="0" y="8" width="0.2" height="0.2" />
<color alpha="0.7" />
</element>
</view>
<script><![CDATA[
file:set_resolve_tags_callback(
function ()
local panel = file.views['Default View'].items['panel']
-- recompute target pen cursor size and area when necessary
local curxoffs, curyoffs, curxscale, curyscale, curwidth, curheight
file.views['Default View']:set_recomputed_callback(
function ()
local bounds = panel.bounds
curwidth = bounds.width / 16
curheight = bounds.height * 4 / 16
curxoffs = bounds.x0 - (curwidth * 0.5)
curyoffs = bounds.y0 - (curheight * 0.5)
curxscale = bounds.width / 255
curyscale = bounds.height / 255
end)
-- animate the position of the pen cursor
local penctrl = file.device:ioport('BUTTONS')
local penx = file.device:ioport('PEN_X')
local peny = file.device:ioport('PEN_Y_RESCALE')
file.views['Default View'].items['pencursor']:set_element_state_callback(
function ()
return (penctrl:read() & 0x80) >> 7
end)
file.views['Default View'].items['pencursor']:set_bounds_callback(
function ()
local x = curxoffs + (penx:read() * curxscale)
local y = curyoffs + (peny:read() * curyscale)
return emu.render_bounds(x, y, x + curwidth, y + curheight)
end)
end)
]]></script>
</mamelayout>