// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/*************************************
*
* Implementation of ASIC65
*
*************************************/
#include "emu.h"
#include "asic65.h"
#define LOG_ASIC 0
#define PARAM_WRITE 0
#define COMMAND_WRITE 1
#define DATA_READ 2
#define OP_UNKNOWN 0
#define OP_REFLECT 1
#define OP_CHECKSUM 2
#define OP_VERSION 3
#define OP_RAMTEST 4
#define OP_RESET 5
#define OP_SIN 6
#define OP_COS 7
#define OP_ATAN 8
#define OP_TMATRIXMULT 9
#define OP_MATRIXMULT 10
#define OP_TRANSFORM 11
#define OP_YORIGIN 12
#define OP_INITBANKS 13
#define OP_SETBANK 14
#define OP_VERIFYBANK 15
#define MAX_COMMANDS 0x2b
static const uint8_t command_map[3][MAX_COMMANDS] =
{
{
/* standard version */
OP_UNKNOWN, OP_REFLECT, OP_CHECKSUM, OP_VERSION, /* 00-03 */
OP_RAMTEST, OP_UNKNOWN, OP_UNKNOWN, OP_RESET, /* 04-07 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 08-0b */
OP_UNKNOWN, OP_UNKNOWN, OP_TMATRIXMULT, OP_UNKNOWN, /* 0c-0f */
OP_MATRIXMULT, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 10-13 */
OP_SIN, OP_COS, OP_YORIGIN, OP_TRANSFORM, /* 14-17 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 18-1b */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 1c-1f */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 20-23 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 24-27 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN /* 28-2a */
},
{
/* Steel Talons version */
OP_UNKNOWN, OP_REFLECT, OP_CHECKSUM, OP_VERSION, /* 00-03 */
OP_RAMTEST, OP_UNKNOWN, OP_UNKNOWN, OP_RESET, /* 04-07 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 08-0b */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 0c-0f */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 10-13 */
OP_TMATRIXMULT, OP_UNKNOWN, OP_MATRIXMULT, OP_UNKNOWN, /* 14-17 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 18-1b */
OP_UNKNOWN, OP_UNKNOWN, OP_SIN, OP_COS, /* 1c-1f */
OP_ATAN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 20-23 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 24-27 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN /* 28-2a */
},
{
/* Guardians version */
OP_UNKNOWN, OP_REFLECT, OP_CHECKSUM, OP_VERSION, /* 00-03 */
OP_RAMTEST, OP_UNKNOWN, OP_UNKNOWN, OP_RESET, /* 04-07 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 08-0b */
OP_UNKNOWN, OP_UNKNOWN, OP_INITBANKS, OP_SETBANK, /* 0c-0f */
OP_VERIFYBANK, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 10-13 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 14-17 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 18-1b */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 1c-1f */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 20-23 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN, /* 24-27 */
OP_UNKNOWN, OP_UNKNOWN, OP_UNKNOWN /* 28-2a */
}
};
DEFINE_DEVICE_TYPE(ASIC65, asic65_device, "asic65", "Atari ASIC65")
asic65_device::asic65_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, ASIC65, tag, owner, clock),
m_asic65_type(0),
m_command(0),
m_yorigin(0x1800),
m_param_index(0),
m_result_index(0),
m_reset_state(0),
m_last_bank(0),
m_ourcpu(*this, "asic65cpu"),
m_tfull(0),
m_68full(0),
m_cmd(0),
m_xflg(0),
m_68data(0),
m_tdata(0),
m_log(nullptr)
{
for (auto & elem : m_param)
{
elem = 0;
}
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void asic65_device::device_start()
{
save_item(NAME(m_asic65_type));
save_item(NAME(m_command));
save_item(NAME(m_yorigin));
save_item(NAME(m_param_index));
save_item(NAME(m_result_index));
save_item(NAME(m_reset_state));
save_item(NAME(m_last_bank));
save_item(NAME(m_tfull));
save_item(NAME(m_68full));
save_item(NAME(m_cmd));
save_item(NAME(m_xflg));
save_item(NAME(m_68data));
save_item(NAME(m_tdata));
save_item(NAME(m_param));
}
//-------------------------------------------------
// device_reset - device-specific startup
//-------------------------------------------------
void asic65_device::device_reset()
{
}
void asic65_device::reset_line(int state)
{
address_space &space = subdevice("^maincpu")->memory().space(AS_PROGRAM);
/* rom-based means reset and clear states */
if (m_asic65_type == ASIC65_ROMBASED)
m_ourcpu->set_input_line(INPUT_LINE_RESET, state ? ASSERT_LINE : CLEAR_LINE);
/* otherwise, do it manually */
else
{
m_ourcpu->suspend(SUSPEND_REASON_DISABLE, 1);
/* if reset is being signalled, clear everything */
if (state && !m_reset_state)
m_command = -1;
/* if reset is going high, latch the command */
else if (!state && m_reset_state)
{
if (m_command != -1)
data_w(space, 1, m_command, 0xffff);
}
/* update the state */
m_reset_state = state;
}
}
/*************************************
*
* Handle writes to the chip
*
*************************************/
void asic65_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
case TIMER_M68K_ASIC65_DEFERRED_W:
m_tfull = 1;
m_cmd = param >> 16;
m_tdata = param;
if (m_asic65_type == ASIC65_ROMBASED)
m_ourcpu->set_input_line(0, ASSERT_LINE);
break;
default:
assert_always(false, "Unknown id in asic65_device::device_timer");
}
}
WRITE16_MEMBER( asic65_device::data_w )
{
/* logging */
if (LOG_ASIC && !m_log) m_log = fopen("m_log", "w");
/* rom-based use a deferred write mechanism */
if (m_asic65_type == ASIC65_ROMBASED)
{
synchronize(TIMER_M68K_ASIC65_DEFERRED_W, data | (offset << 16));
machine().scheduler().boost_interleave(attotime::zero, attotime::from_usec(20));
return;
}
/* parameters go to offset 0 */
if (!(offset & 1))
{
if (m_log) fprintf(m_log, " W=%04X", data);
/* add to the parameter list, but don't overflow */
m_param[m_param_index++] = data;
if (m_param_index >= 32)
m_param_index = 32;
}
/* commands go to offset 2 */
else
{
int command = (data < MAX_COMMANDS) ? command_map[m_asic65_type][data] : OP_UNKNOWN;
if (m_log) fprintf(m_log, "\n%s %c%04X:", machine().describe_context().c_str(), (command == OP_UNKNOWN) ? '*' : ' ', data);
/* set the command number and reset the parameter/result indices */
m_command = data;
m_result_index = m_param_index = 0;
}
}
READ16_MEMBER( asic65_device::read )
{
int64_t element, result64 = 0;
uint16_t result = 0;
/* rom-based just returns latched data */
if (m_asic65_type == ASIC65_ROMBASED)
{
m_68full = 0;
machine().scheduler().boost_interleave(attotime::zero, attotime::from_usec(5));
return m_68data;
}
int command = (m_command < MAX_COMMANDS) ? command_map[m_asic65_type][m_command] : OP_UNKNOWN;
/* update results */
switch (command)
{
case OP_UNKNOWN: /* return bogus data */
popmessage("ASIC65: Unknown cmd %02X", m_command);
break;
case OP_REFLECT: /* reflect data */
if (m_param_index >= 1)
result = m_param[--m_param_index];
break;
case OP_CHECKSUM: /* compute checksum (should be XX27) */
result = 0x0027;
break;
case OP_VERSION: /* get version (returns 1.3) */
result = 0x0013;
break;
case OP_RAMTEST: /* internal RAM test (result should be 0) */
result = 0;
break;
case OP_RESET: /* reset */
m_result_index = m_param_index = 0;
break;
case OP_SIN: /* sin */
if (m_param_index >= 1)
result = (int)(16384. * sin(M_PI * (double)(int16_t)m_param[0] / 32768.));
break;
case OP_COS: /* cos */
if (m_param_index >= 1)
result = (int)(16384. * cos(M_PI * (double)(int16_t)m_param[0] / 32768.));
break;
case OP_ATAN: /* vector angle */
if (m_param_index >= 4)
{
int32_t xint = (int32_t)((m_param[0] << 16) | m_param[1]);
int32_t yint = (int32_t)((m_param[2] << 16) | m_param[3]);
double a = atan2((double)yint, (double)xint);
result = (int16_t)(a * 32768. / M_PI);
}
break;
case OP_TMATRIXMULT: /* matrix multiply by transpose */
/* if this is wrong, the labels on the car selection screen */
/* in Race Drivin' will be off */
if (m_param_index >= 9+6)
{
int32_t v0 = (int32_t)((m_param[9] << 16) | m_param[10]);
int32_t v1 = (int32_t)((m_param[11] << 16) | m_param[12]);
int32_t v2 = (int32_t)((m_param[13] << 16) | m_param[14]);
/* 2 results per element */
switch (m_result_index / 2)
{
case 0:
result64 = (int64_t)v0 * (int16_t)m_param[0] +
(int64_t)v1 * (int16_t)m_param[3] +
(int64_t)v2 * (int16_t)m_param[6];
break;
case 1:
result64 = (int64_t)v0 * (int16_t)m_param[1] +
(int64_t)v1 * (int16_t)m_param[4] +
(int64_t)v2 * (int16_t)m_param[7];
break;
case 2:
result64 = (int64_t)v0 * (int16_t)m_param[2] +
(int64_t)v1 * (int16_t)m_param[5] +
(int64_t)v2 * (int16_t)m_param[8];
break;
}
/* remove lower 14 bits and pass back either upper or lower words */
result64 >>= 14;
result = (m_result_index & 1) ? (result64 & 0xffff) : ((result64 >> 16) & 0xffff);
m_result_index++;
}
break;
case OP_MATRIXMULT: /* matrix multiply???? */
if (m_param_index >= 9+6)
{
int32_t v0 = (int32_t)((m_param[9] << 16) | m_param[10]);
int32_t v1 = (int32_t)((m_param[11] << 16) | m_param[12]);
int32_t v2 = (int32_t)((m_param[13] << 16) | m_param[14]);
/* 2 results per element */
switch (m_result_index / 2)
{
case 0:
result64 = (int64_t)v0 * (int16_t)m_param[0] +
(int64_t)v1 * (int16_t)m_param[1] +
(int64_t)v2 * (int16_t)m_param[2];
break;
case 1:
result64 = (int64_t)v0 * (int16_t)m_param[3] +
(int64_t)v1 * (int16_t)m_param[4] +
(int64_t)v2 * (int16_t)m_param[5];
break;
case 2:
result64 = (int64_t)v0 * (int16_t)m_param[6] +
(int64_t)v1 * (int16_t)m_param[7] +
(int64_t)v2 * (int16_t)m_param[8];
break;
}
/* remove lower 14 bits and pass back either upper or lower words */
result64 >>= 14;
result = (m_result_index & 1) ? (result64 & 0xffff) : ((result64 >> 16) & 0xffff);
m_result_index++;
}
break;
case OP_YORIGIN:
if (m_param_index >= 1)
m_yorigin = m_param[m_param_index - 1];
break;
case OP_TRANSFORM: /* 3d transform */
if (m_param_index >= 2)
{
/* param 0 == 1/z */
/* param 1 == height */
/* param 2 == X */
/* param 3 == Y */
/* return 0 == scale factor for 1/z */
/* return 1 == transformed X */
/* return 2 == transformed Y, taking height into account */
element = (int16_t)m_param[0];
if (m_param_index == 2)
{
result64 = (element * (int16_t)m_param[1]) >> 8;
result64 -= 1;
if (result64 > 0x3fff) result64 = 0;
}
else if (m_param_index == 3)
{
result64 = (element * (int16_t)m_param[2]) >> 15;
result64 += 0xa8;
}
else if (m_param_index == 4)
{
result64 = (int16_t)((element * (int16_t)m_param[3]) >> 10);
result64 = (int16_t)m_yorigin - result64 - (result64 << 1);
}
result = result64 & 0xffff;
}
break;
case OP_INITBANKS: /* initialize banking */
m_last_bank = 0;
break;
case OP_SETBANK: /* set a bank */
{
static const uint8_t banklist[] =
{
1,4,0,4,4,3,4,2, 4,4,4,4,4,4,4,4,
3,3,4,4,1,1,0,0, 4,4,4,4,2,2,4,4,
4,4
};
static const uint16_t bankaddr[][8] =
{
{ 0x77c0,0x77ce,0x77c2,0x77cc,0x77c4,0x77ca,0x77c6,0x77c8 },
{ 0x77d0,0x77de,0x77d2,0x77dc,0x77d4,0x77da,0x77d6,0x77d8 },
{ 0x77e0,0x77ee,0x77e2,0x77ec,0x77e4,0x77ea,0x77e6,0x77e8 },
{ 0x77f0,0x77fe,0x77f2,0x77fc,0x77f4,0x77fa,0x77f6,0x77f8 },
{ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000 },
};
if (m_param_index >= 1)
{
if (m_param_index < sizeof(banklist) && banklist[m_param[0]] < 4)
m_last_bank = banklist[m_param[0]];
result = bankaddr[m_last_bank][(m_result_index < 8) ? m_result_index : 7];
m_result_index++;
}
break;
}
case OP_VERIFYBANK: /* verify a bank */
{
static const uint16_t bankverify[] =
{
0x0eb2,0x1000,0x171b,0x3d28
};
result = bankverify[m_last_bank];
break;
}
}
if (LOG_ASIC && !m_log) m_log = fopen("m_log", "w");
if (m_log) fprintf(m_log, " (R=%04X)", result);
return result;
}
READ16_MEMBER( asic65_device::io_r )
{
if (m_asic65_type == ASIC65_ROMBASED)
{
/* bit 15 = TFULL */
/* bit 14 = 68FULL */
/* bit 13 = XFLG */
/* bit 12 = controlled by jumper */
machine().scheduler().boost_interleave(attotime::zero, attotime::from_usec(5));
return (m_tfull << 15) | (m_68full << 14) | (m_xflg << 13) | 0x0000;
}
else
{
/* indicate that we always are ready to accept data and always ready to send */
return 0x4000;
}
}
/*************************************
*
* Read/write handlers for TMS32015
*
*************************************/
WRITE16_MEMBER( asic65_device::m68k_w )
{
m_68full = 1;
m_68data = data;
}
READ16_MEMBER( asic65_device::m68k_r )
{
m_tfull = 0;
if (m_asic65_type == ASIC65_ROMBASED)
m_ourcpu->set_input_line(0, CLEAR_LINE);
return m_tdata;
}
WRITE16_MEMBER( asic65_device::stat_w )
{
m_xflg = data & 1;
}
READ16_MEMBER( asic65_device::stat_r )
{
/* bit 15 = 68FULL */
/* bit 14 = TFULL */
/* bit 13 = CMD */
/* bit 12 = controlled by jumper (0 = test?) */
return (m_68full << 15) | (m_tfull << 14) | (m_cmd << 13) | 0x1000;
}
READ_LINE_MEMBER( asic65_device::get_bio )
{
if (!m_tfull)
m_ourcpu->spin_until_interrupt();
return m_tfull ? CLEAR_LINE : ASSERT_LINE;
}
/*************************************
*
* Address maps for TMS32015
*
*************************************/
void asic65_device::asic65_program_map(address_map &map)
{
map.unmap_value_high();
map(0x000, 0xfff).rom();
}
void asic65_device::asic65_io_map(address_map &map)
{
map(0, 0).mirror(6).rw(this, FUNC(asic65_device::m68k_r), FUNC(asic65_device::m68k_w));
map(1, 1).mirror(6).rw(this, FUNC(asic65_device::stat_r), FUNC(asic65_device::stat_w));
}
//-------------------------------------------------
// device_add_mconfig - add device configuration
//-------------------------------------------------
MACHINE_CONFIG_START(asic65_device::device_add_mconfig)
/* ASIC65 */
MCFG_DEVICE_ADD("asic65cpu", TMS32010, 20000000)
MCFG_DEVICE_PROGRAM_MAP(asic65_program_map)
MCFG_DEVICE_IO_MAP(asic65_io_map)
MCFG_TMS32010_BIO_IN_CB(READLINE(*this, asic65_device, get_bio))
MACHINE_CONFIG_END
/***********************************************************************
Information about various versions:
Notation:
C = command write
W = write
R = read
7 = wait for bit 7
6 = wait for bit 6
Guardians of the Hood:
Version = 0040
Checksum = ????
Command $08: C7
Command $09: C7W6R
Command $0a: C7
Command $0b: C7W7W7W7W7WW7W7W7W
Command $0c: C7W7W7W7W7W7W
Command $0d: C7W7W
Command $0e: C7W7W7W7W7WW7W7W7WWWWWWW6R6R6R6R6R6R
Command $0f: C7W7W7W7W7W7W6R6R6R6R6R6R
Command $10: C7W7W7W7W7WW7W7W7WWWWWWW6R6R6R6R6R6R
Command $11: C7W7W7W7W7W7W6R6R6R6R6R6R
Command $12: C7W7W7W7W7WW7W7W7W6R6R6R6R6R6R6R6R6R
Command $13: C7W7W7W7W7WW7W7W7W
Command $14: C7W6R
Command $15: C7W6R
Command $16: C7W6R6R
Command $17: C7W
Command $18: C76R6R6R6R6R6R6R6R6R
Command $19: C7
Command $1a: C76R6R6R6R6R6R6R6R6R
Command $1b: C7
Command $1c: C76R6R6R6R6R6R6R6R6R
Command $1d: C7
Command $1e: C76R6R6R6R6R6R6R6R6R
Command $1f: C7
Command $20: C76R6R6R6R6R6R6R6R6R
Command $21: C7
Command $22: C76R6R6R6R6R6R6R6R6R
Command $23: C7
Command $24: C7
Command $0e: C76RRRRRRRRR
Command $0f: C7W6RRRRRRRR
Command $16: C7W
Command $17: C7W7W7W7W6R6R6R
Road Riot 4WD:
Version = ????
Checksum = ????
Command $08: C7
Command $09: C7W6R
Command $0a: C7
Command $0b: C7W7WWWWWWWW
Command $0c: C7W7WWWWW
Command $0d: C7W7W
Command $0e: C7W7WWWWWWWWWWWWWW6RRRRRR
Command $0f: C7W7WWWWW6RRRRRR
Command $10: C7W7WWWWWWWWWWWWWW6RRRRRR
Command $11: C7W7WWWWW6RRRRRR
Command $12: C7W7WWWWWWWW6RRRRRRRRR
Command $13: C7W7WWWWWWWW
Command $14: C7W6R
Command $15: C7W6R
Command $16: C7W6RR
Command $17: C7W
Command $18: C76RRRRRRRRR
Command $19: C7
Command $1a: C76RRRRRRRRR
Command $1b: C7
Command $1c: C76RRRRRRRRR
Command $1d: C7
Command $1e: C76RRRRRRRRR
Command $1f: C7
Command $20: C76RRRRRRRRR
Command $21: C7
Command $22: C76RRRRRRRRR
Command $23: C7
Command $24: C7
Command $16: C7W
Command $17: C7W7WWW6RRR
Command $17: C7WRWRWR
Race Drivin':
Version = ????
Checksum = ????
Command $08: C7
Command $09: C7W6R
Command $0a: C7
Command $0b: C7W7WWWWWWWW
Command $0c: C7W7WWWWW
Command $0d: C7W7W
Command $0e: C7W7WWWWWWWWWWWWWW6RRRRRR
Command $0f: C7W7WWWWW6RRRRRR
Command $10: C7W7WWWWWWWWWWWWWW6RRRRRR
Command $11: C7W7WWWWW6RRRRRR
Command $12: C7W7WWWWWWWW6RRRRRRRRR
Command $13: C7W7WWWWWWWW
Command $14: C7W6R
Command $15: C7W6R
Command $16: C7W6RR
Command $17: C7W
Command $18: C76RRRRRRRRR
Command $19: C7
Command $1a: C76RRRRRRRRR
Command $1b: C7
Command $1c: C76RRRRRRRRR
Command $1d: C7
Command $1e: C76RRRRRRRRR
Command $1f: C7
Command $20: C76RRRRRRRRR
Command $21: C7
Command $22: C76RRRRRRRRR
Command $23: C7
Command $24: C7
Command $0e: C7W7WWWWWWWWWWWWWW6RRRRRR
Command $14: C7W6R
Command $15: C7W6R
Steel Talons:
Version = ????
Checksum = ????
Command $08: C7
Command $09: C7W6R
Command $0a: C7
Command $0b: C7W7WWWWWWWW
Command $0c: C7W7WWWWW
Command $0d: C7W7W
Command $14: C7W7WWWWWWWWWWWWWW6RRRRRR
Command $15: C7W7WWWWW6RRRRRR
Command $16: C7W7WWWWWWWWWWWWWW6RRRRRR
Command $17: C7W7WWWWW6RRRRRR
Command $12: C7W7WWWWWWWW6RRRRRRRRR
Command $13: C7W7WWWWWWWW
Command $1e: C7W6R
Command $1f: C7W6R
Command $16: C7W6RR
Command $17: C7W
Command $20: C7W7WWW6R
Command $18: C76RRRRRRRRR
Command $19: C7
Command $1a: C76RRRRRRRRR
Command $1b: C7
Command $1c: C76RRRRRRRRR
Command $1d: C7
Command $1e: C76RRRRRRRRR
Command $1f: C7
Command $20: C76RRRRRRRRR
Command $21: C7
Command $22: C76RRRRRRRRR
Command $23: C7
Command $24: C7
Hard Drivin's Airborne:
Version = ????
Checksum = ????
Command $0e: C7W7WWWWWWWWWWWWWW6RRRRRR
Command $14: C7W6R
Command $08: C7
Command $09: C7W6R
Command $0a: C7
Command $0b: C7W7WWWWWWWW
Command $0c: C7W7WWWWW
Command $0d: C7W7W
Command $0e: C7W7WWWWWWWWWWWWWW6RRRRRR
Command $0f: C7W7WWWWW6RRRRRR
Command $10: C7W7WWWWWWWWWWWWWW6RRRRRR
Command $11: C7W7WWWWW6RRRRRR
Command $12: C7W7WWWWWWWW6RRRRRRRRR
Command $13: C7W7WWWWWWWW
Command $14: C7W6R
Command $15: C7W6R
Command $16: C7W6RR
Command $17: C7W
Command $18: C76RRRRRRRRR
Command $19: C7
Command $1a: C76RRRRRRRRR
Command $1b: C7
Command $1c: C76RRRRRRRRR
Command $1d: C7
Command $1e: C76RRRRRRRRR
Command $1f: C7
Command $20: C76RRRRRRRRR
Command $21: C7
Command $22: C76RRRRRRRRR
Command $23: C7
Command $24: C7
Command $26: C7W
Command $27: C7W7WWWWW
Command $28: C7
Command $2a: C76RRRRRRRRR
***********************************************************************/