// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/*************************************
*
* Implementation of ASIC65
*
*************************************/
#include "emu.h"
#include "asic65.h"
#include <algorithm>
#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 u8 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, u32 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)
{
std::fill(std::begin(m_param), std::end(m_param), 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)
{
/* 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(1, m_command);
}
/* 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:
throw emu_fatalerror("Unknown id in asic65_device::device_timer");
}
}
void asic65_device::data_w(offs_t offset, u16 data)
{
/* 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;
}
}
u16 asic65_device::read()
{
s64 element, result64 = 0;
u16 result = 0;
/* rom-based just returns latched data */
if (m_asic65_type == ASIC65_ROMBASED)
{
if (!machine().side_effects_disabled())
{
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 */
if (!machine().side_effects_disabled())
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 */
if (!machine().side_effects_disabled())
m_result_index = m_param_index = 0;
break;
case OP_SIN: /* sin */
if (m_param_index >= 1)
result = (int)(16384. * sin(M_PI * (double)(s16)m_param[0] / 32768.));
break;
case OP_COS: /* cos */
if (m_param_index >= 1)
result = (int)(16384. * cos(M_PI * (double)(s16)m_param[0] / 32768.));
break;
case OP_ATAN: /* vector angle */
if (m_param_index >= 4)
{
s32 xint = (s32)((m_param[0] << 16) | m_param[1]);
s32 yint = (s32)((m_param[2] << 16) | m_param[3]);
double a = atan2((double)yint, (double)xint);
result = (s16)(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)
{
s32 v0 = (s32)((m_param[9] << 16) | m_param[10]);
s32 v1 = (s32)((m_param[11] << 16) | m_param[12]);
s32 v2 = (s32)((m_param[13] << 16) | m_param[14]);
/* 2 results per element */
switch (m_result_index / 2)
{
case 0:
result64 = (s64)v0 * (s16)m_param[0] +
(s64)v1 * (s16)m_param[3] +
(s64)v2 * (s16)m_param[6];
break;
case 1:
result64 = (s64)v0 * (s16)m_param[1] +
(s64)v1 * (s16)m_param[4] +
(s64)v2 * (s16)m_param[7];
break;
case 2:
result64 = (s64)v0 * (s16)m_param[2] +
(s64)v1 * (s16)m_param[5] +
(s64)v2 * (s16)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);
if (!machine().side_effects_disabled())
m_result_index++;
}
break;
case OP_MATRIXMULT: /* matrix multiply???? */
if (m_param_index >= 9+6)
{
s32 v0 = (s32)((m_param[9] << 16) | m_param[10]);
s32 v1 = (s32)((m_param[11] << 16) | m_param[12]);
s32 v2 = (s32)((m_param[13] << 16) | m_param[14]);
/* 2 results per element */
switch (m_result_index / 2)
{
case 0:
result64 = (s64)v0 * (s16)m_param[0] +
(s64)v1 * (s16)m_param[1] +
(s64)v2 * (s16)m_param[2];
break;
case 1:
result64 = (s64)v0 * (s16)m_param[3] +
(s64)v1 * (s16)m_param[4] +
(s64)v2 * (s16)m_param[5];
break;
case 2:
result64 = (s64)v0 * (s16)m_param[6] +
(s64)v1 * (s16)m_param[7] +
(s64)v2 * (s16)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);
if (!machine().side_effects_disabled())
m_result_index++;
}
break;
case OP_YORIGIN:
if (m_param_index >= 1)
{
if (!machine().side_effects_disabled())
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 = (s16)m_param[0];
if (m_param_index == 2)
{
result64 = (element * (s16)m_param[1]) >> 8;
result64 -= 1;
if (result64 > 0x3fff) result64 = 0;
}
else if (m_param_index == 3)
{
result64 = (element * (s16)m_param[2]) >> 15;
result64 += 0xa8;
}
else if (m_param_index == 4)
{
result64 = (s16)((element * (s16)m_param[3]) >> 10);
result64 = (s16)m_yorigin - result64 - (result64 << 1);
}
result = result64 & 0xffff;
}
break;
case OP_INITBANKS: /* initialize banking */
if (!machine().side_effects_disabled())
m_last_bank = 0;
break;
case OP_SETBANK: /* set a bank */
{
static const u8 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 u16 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)
{
if (!machine().side_effects_disabled())
m_last_bank = banklist[m_param[0]];
}
result = bankaddr[m_last_bank][(m_result_index < 8) ? m_result_index : 7];
if (!machine().side_effects_disabled())
m_result_index++;
}
break;
}
case OP_VERIFYBANK: /* verify a bank */
{
static const u16 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;
}
u16 asic65_device::io_r()
{
if (m_asic65_type == ASIC65_ROMBASED)
{
/* bit 15 = TFULL */
/* bit 14 = 68FULL */
/* bit 13 = XFLG */
/* bit 12 = controlled by jumper */
if (!machine().side_effects_disabled())
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
*
*************************************/
void asic65_device::m68k_w(u16 data)
{
m_68full = 1;
m_68data = data;
}
u16 asic65_device::m68k_r()
{
if (!machine().side_effects_disabled())
{
m_tfull = 0;
if (m_asic65_type == ASIC65_ROMBASED)
m_ourcpu->set_input_line(0, CLEAR_LINE);
}
return m_tdata;
}
void asic65_device::stat_w(u16 data)
{
m_xflg = data & 1;
}
u16 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 (!machine().side_effects_disabled())
{
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(FUNC(asic65_device::m68k_r), FUNC(asic65_device::m68k_w));
map(1, 1).mirror(6).rw(FUNC(asic65_device::stat_r), FUNC(asic65_device::stat_w));
}
//-------------------------------------------------
// device_add_mconfig - add device configuration
//-------------------------------------------------
void asic65_device::device_add_mconfig(machine_config &config)
{
/* ASIC65 */
TMS32010(config, m_ourcpu, 20000000);
m_ourcpu->set_addrmap(AS_PROGRAM, &asic65_device::asic65_program_map);
m_ourcpu->set_addrmap(AS_IO, &asic65_device::asic65_io_map);
m_ourcpu->bio().set(FUNC(asic65_device::get_bio));
}
/***********************************************************************
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
***********************************************************************/