|
|
nono 1.7.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// X68k キーボード
//
// 送信タイミングについて
// キーボードは MFP の USART に繋がっている。
// MFP USART の RR 線 (Receiver Ready) は受信可能なら 'H'、受信中は 'L' に
// なっている。キーボードは RR が 'H' の時だけデータを送信する。また、
// システムポート#4 の KEY CTRL = 0 (キーデータ送信不可) はこの RR を
// 'L' に落とす AND 回路になっている。キーボードからの送信は 2400bps、
// ストップビット1、パリティなしなので、1バイトの送信に 3.75 msec かかる。
// 実装では、キーボードは MFP の RR が 'H' なら MFP にキーデータを送信。
// MFP 側で RR を 'L' にして 3.75 msec をイベントで待った後、キーを処理
// して RR を 'H' に戻すことにする。
// SHIFT キーについては keyboard.cpp のコメントを参照。
#include "x68kkbd.h"
#include "event.h"
#include "mfp.h"
#include "monitor.h"
#include "scc.h"
#include "scheduler.h"
#include "sysport.h"
#include "uimessage.h"
// コンストラクタ
X68030Keyboard::X68030Keyboard()
{
led.resize(7);
monitor = gMonitorManager->Regist(ID_MONITOR_KEYBOARD, this);
monitor->SetCallback(&X68030Keyboard::MonitorScreen);
monitor->SetSize(47, 5);
}
// デストラクタ
X68030Keyboard::~X68030Keyboard()
{
}
// 初期化
bool
X68030Keyboard::Init()
{
if (inherited::Init() == false) {
return false;
}
mfp = GetMFPDevice();
scc = GetSCCDevice();
sysport = GetSysportDevice();
auto evman = GetEventManager();
rept_event = evman->Regist(this,
ToEventCallback(&X68030Keyboard::ReptCallback),
"Keyboard Repeat");
mouse_event = evman->Regist(this,
ToEventCallback(&X68030Keyboard::MouseCallback),
"Mouse");
return true;
}
// キーボード接続時の MD 固有処理
void
X68030Keyboard::MDConnect()
{
rept_delay_data = 3;
rept_delay = 500_msec;
rept_time_data = 4;
rept_time = 110_msec;
rept_key = KC_none;
scheduler->StopEvent(rept_event);
led_darkness = 0;
// XXX マウスの接続取り外しは未定
scheduler->StopEvent(mouse_event);
mouse_event->code = 0;
}
// キーボード取り外し時の MD 固有処理
void
X68030Keyboard::MDDisconnect()
{
scheduler->StopEvent(rept_event);
scheduler->StopEvent(mouse_event);
mouse_event->code = 0;
}
// モニター
void
X68030Keyboard::MonitorScreen(Monitor *, TextScreen& screen)
{
int y;
screen.Clear();
y = 0;
// 0 1 2 3 4
// 01234567890123456789012345678901234567890123456
// Key Enable: % MSCTRL: %
// Ctrl Enable: OPT.2 Enable: %
// Key Repeat: Delay $x(????msec)
// Repeat $x(????msec)
// LED: $xx Zen Hira INS CAPS Code Roma Kana
// LED Darkness: x
// KEY_EN, CTRL_EN, OPT2_EN, MSCTRL は未実装
// キーリピート
screen.Puts(0, y, "Key Repeat:");
screen.Print(12, y++, "Delay $%x(%4umsec)",
rept_delay_data, (uint)tsec_to_msec(rept_delay));
screen.Print(12, y++, "Repeat $%x(%4umsec)",
rept_time_data, (uint)tsec_to_msec(rept_time));
// マウス
int mx = mouse_x;
int my = mouse_y;
if (mx < -999)
mx = -999;
if (mx > 999)
mx = 999;
if (my < -999)
my = -999;
if (my > 999)
my = 999;
screen.Puts(0, y, "Mouse:");
screen.Print(12, y, "x=%-4d y=%-4d", mx, my);
screen.Print(27, y, TA::OnOff(mouse_l), "L");
screen.Print(29, y, TA::OnOff(mouse_r), "R");
y++;
// LED
screen.Puts(0, y, "LED:");
static const char * const ledname[] = {
"Zen", "Hira", "INS", "CAPS", "Code", "Roma", "Kana"
};
uint val = 0x80;
for (int i = 0; i < 7; i++) {
int n = 6 - i;
screen.Puts(12 + i * 5, y, TA::OnOff(led[n]), ledname[i]);
if (led[n] == 0) {
val |= 1U << n;
}
}
screen.Print(5, y, "$%02x", val);
y++;
// LED の明るさ (というより暗さ)
screen.Print(0, y++, "LED Darkness: %u", led_darkness);
}
// 共通 keystat を X680x0 キーコードに変換して返す。
// 対応する X680x0 のキーがなければ 0 を返す。
uint
X68030Keyboard::KeyToMDKey(uint keystat) const
{
uint keycode = KC_CODE(keystat);
if (keycode >= countof(keycode2x68kkey_table)) {
return 0;
}
uint mdkey = keycode2x68kkey_table[keycode];
if (mdkey && KC_IS_BREAK(keystat)) {
mdkey |= 0x80;
}
return mdkey;
}
// シフト状態なら true を返す
bool
X68030Keyboard::IsShift() const
{
return pressed[KC_SHIFT];
}
// キー入力を1つ処理する
void
X68030Keyboard::KeyInput(uint keystat)
{
uint keycode = KC_CODE(keystat);
bool ismake = KC_IS_MAKE(keystat);
assert(keycode < KC_max);
// X68k のキーボードは左右の [SHIFT] の区別がない。
// KC_SHIFT_L はホストの物理キー左 [SHIFT] に対応、
// KC_SHIFT_R はホストの物理キー右 [SHIFT] に対応、
// KC_SHIFT はソフトウェアキーボードからの左右のない [SHIFT] に対応。
// どのキーコードで問い合わせをされても KC_SHIFT_L と KC_SHIFT_R の OR が
// [SHIFT] キーの状態である。
if (keycode == KC_SHIFT_L || keycode == KC_SHIFT_R) {
// それぞれのシフトキーが押されたことは覚えておく。
SetPressed(keycode, ismake);
// 反対側のキーが押されていたら、いま押した・離したほうは無視する
if (IsPressed(keycode ^ 1)) {
return;
}
keycode = KC_SHIFT;
// keycode を変更したので keystat も更新
keystat = (keystat & 0xff00) | keycode;
}
// 共通処理へ
if (KeyInputCommon(keystat) == false) {
return;
}
// LED は MPU 側から点灯/消灯指示が来るのでここでは何もしなくてよい
// ここでキーリピートのイベント開始処理
if (ismake) {
// いま起きているリピートは中止
rept_key = keycode;
rept_event->time = rept_delay;
scheduler->RestartEvent(rept_event);
} else {
if (keycode == rept_key) {
// 同じキーのブレークでのみリピートを終了する
scheduler->StopEvent(rept_event);
rept_key = KC_none;
}
}
}
void
X68030Keyboard::ReptCallback(Event *ev)
{
// リピート
sendqueue.Enqueue(KeyToMDKey(rept_key));
SendStart();
// 二回目以降のリピートは rept_time 間隔で発生
rept_event->time = rept_time;
scheduler->StartEvent(rept_event);
}
// MFP、システムポートと、キーボードの関係。
//
// キーボードの Ready 信号は MFP の RR とシステムポートの KeyCtrl の
// AND で作られているが、この AND 部分はキーボードが担当することにする。
// そのほうが都合がいいため。
//
// <- 本体 || キーボード ->
// MFP ||
// --------+ : || +-----
// | : |& || |
// RR|o-------|& || |
// --------+ : |&---------|T0 (Ready)
// +-----|& || |
// Sysport | : |& || |
// --------+ | : || |
// KeyCtrl|--+ : || +-----
// | : ||
// :エミュレータでは点線から右をキーボードが担当
// キーデータを送信
void
X68030Keyboard::SendStart()
{
if (mfp->RxIsReady()) {
// 本体が Ready ならここで送信
Ready();
} else {
// そうでない場合、ここでは処理は不要。
// MFP 側が受信可能になると Ready() がコールされ、そっちで処理される
}
}
// MFP からの Ready 通知
// 実際にはキーボード側が MFP (を含む本体側) が Ready になるのを待っている
// のだが、待っているを作るのは大変なので。
void
X68030Keyboard::Ready()
{
if (sysport->GetKeyCtrl()) {
// KEYCTRL が有効なら処理する
// ここで取り出せるのは MDKey。
uint x68kkey;
if (sendqueue.Dequeue(&x68kkey)) {
// あれば送信
// 呼ばれたからには送信できるはず
assert(mfp->RxIsReady() == true);
assert(x68kkey != 0);
// 送信
mfp->Rx(x68kkey);
} else {
// なければ何もせず終了
}
} else {
// KEYCTRL が無効なら何もしないだけでなく、キューをクリア。
// KEYCTRL が無効から有効になった時に、無効だった期間のキーデータを
// 吐き出すのはおかしいので。
sendqueue.Clear();
}
}
// マウス入力
void
X68030Keyboard::MouseInput(int x, int y, bool rb, bool lb, bool mb)
{
// ホストからマウスイベントのたびに呼ばれるので、ここで積算。
// 20msec ごとのサンプリングのタイミングで積算結果を処理する。
// X68k のマウスの Y 方向は下に動く方向がプラス。
mouse_x += x;
mouse_y += y;
mouse_r = rb;
mouse_l = lb;
}
// マウスデータを VM に送信
void
X68030Keyboard::MouseSendStart()
{
uint8 status = 0;
// 最後のバイトを送るまでは、次は受け付けられない。
// 送信中かどうかは mouse_event->code で判断している。
if (mouse_event->code != 0) {
return;
}
if (mouse_x > 127) {
status |= XOVFL;
mouse_x = 127;
}
if (mouse_x < -128) {
status |= XUNFL;
mouse_x = -128;
}
if (mouse_y > 127) {
status |= YOVFL;
mouse_y = 127;
}
if (mouse_y < -128) {
status |= YUNFL;
mouse_y = -128;
}
status |= mouse_l ? SW_L : 0;
status |= mouse_r ? SW_R : 0;
// この時点でラッチしてみる(?)
mouse_data[0] = status;
mouse_data[1] = mouse_x;
mouse_data[2] = mouse_y;
mouse_x = 0;
mouse_y = 0;
// 1バイト目は MSCTRL が立ち下がってから 700us 以上後から送信。
// さらに1バイトの送信に 2.3msec かかる(このすぐ下参照)。
mouse_event->time = 700_usec + 2300_usec;
mouse_event->code = 1;
scheduler->RestartEvent(mouse_event);
}
// マウスデータのイベントコールバック
void
X68030Keyboard::MouseCallback(Event *ev)
{
// マウスは 4800bps で、ストップビット 2 なので11ビット。
// 1/4800 * 11 = 2.29166 msec なので、1バイトあたり 2.3 msec にしておく。
// code 1 なら1バイト目 (mouse_data[0]) の送信完了時刻が来た。
// code 2 なら2バイト目 (mouse_data[1]) の送信完了時刻が来た。
// code 3 なら3バイト目 (mouse_data[2]) の送信完了時刻が来た。
// 3バイト目を送信してイベントループ終了。
scc->Rx(1, mouse_data[mouse_event->code - 1]);
if (mouse_event->code < 3) {
// 次のイベント
mouse_event->time = 2300_usec;
mouse_event->code++;
scheduler->StartEvent(mouse_event);
} else {
// 3 バイト目の送信が終わった。
mouse_event->code = 0;
}
}
// ホストからの制御
void
X68030Keyboard::Command(uint32 data)
{
if (data < 0x40) {
// $00-$3f TV 制御
// (実装不要)
putlog(2, "Command TV control $%02x", (data & 0x1f));
} else if (data < 0x48) {
// $40-$47 MSCTRL 信号制御
// こいつは頻度が高いのでログレベルを上げておく
putlog(3, "Command MSCTRL control %u", (data & 1));
} else if (data < 0x50) {
// $48-$4f キーデータ送出許可
putlog(1, "Command Key send enable %u (NOT IMPLEMENTED)", (data & 1));
} else if (data < 0x54) {
// $50-$53 ディスプレイ制御モード選択
// (実装不要)
putlog(2, "Command Display control mode %u", (data & 1));
} else if (data < 0x58) {
// $54-$57 LED 明るさ選択
// 0 が明るく 3 が暗いので、明るさというより暗さ。
led_darkness = (data & 3);
putlog(1, "Command LED brightness %u", led_darkness);
uimessage->Post(UIMessage::KEYBOARD);
} else if (data < 0x5c) {
// $58-$5b 本体からのディスプレイ制御許可
// (実装不要)
putlog(2, "Command Display control enable %u", (data & 1));
} else if (data < 0x60) {
// $5c-$5f OPT.2 によるディスプレイ制御許可
// (実装不要)
putlog(2, "Command OPT2 control enable %u", (data & 1));
} else if (data < 0x70) {
// $60-$6f リピートまでの時間
rept_delay_data = data & 15;
rept_delay = 200_msec + rept_delay_data * 100_msec;
putlog(1, "Command Repeat Delay %u (%u msec)", rept_delay_data,
(uint)tsec_to_msec(rept_delay));
} else if (data < 0x80) {
// $70-$7f リピート間隔
rept_time_data = data & 15;
rept_time = 30_msec + rept_time_data * rept_time_data * 5_msec;
putlog(1, "Command Repeat time %d (%u msec)", rept_time_data,
(uint)tsec_to_msec(rept_time));
} else {
// $80-$ff LED 制御
data &= 0x7f;
putlog(1, "Command LED control $%02x", data);
uint32 b = 0x01;
for (int i = 0; i < led.size(); i++) {
// data は %0 で点灯、led[] は true で点灯
led[i] = ((data & b) == 0);
b <<= 1;
}
uimessage->Post(UIMessage::KEYBOARD);
}
}
// キーコード変換表
// 共通キーコードから X680x0 キーコードに変換する。
/*static*/ const uint
X68030Keyboard::keycode2x68kkey_table[KC_max] = {
NoKey, // [00]
X68kKey_BREAK, // [01]
NoKey, // [02]
X68kKey_romaji, // [03]
X68kKey_code, // [04]
X68kKey_kigou, // [05]
X68kKey_touroku, // [06]
X68kKey_HELP, // [07]
X68kKey_SHIFT, // [08]
X68kKey_TAB, // [09]
X68kKey_CTRL, // [0a]
X68kKey_kana, // [0b]
X68kKey_SHIFT, // [0c]
X68kKey_SHIFT, // [0d]
X68kKey_CAPS, // [0e]
NoKey, // [0f]
X68kKey_ESC, // [10]
X68kKey_BS, // [11]
X68kKey_enter, // [12]
NoKey, // [13]
X68kKey_space, // [14]
X68kKey_DEL, // [15]
NoKey, // [16]
NoKey, // [17]
NoKey, // [18]
NoKey, // [19]
NoKey, // [1a]
NoKey, // [1b]
X68kKey_up, // [1c]
X68kKey_left, // [1d]
X68kKey_right, // [1e]
X68kKey_down, // [1f]
X68kKey_INS, // [20]
X68kKey_COPY, // [21]
X68kKey_1, // [22]
X68kKey_2, // [23]
X68kKey_3, // [24]
X68kKey_4, // [25]
X68kKey_5, // [26]
X68kKey_6, // [27]
X68kKey_7, // [28]
X68kKey_8, // [29]
X68kKey_9, // [2a]
X68kKey_0, // [2b]
X68kKey_minus, // [2c]
X68kKey_circum, // [2d]
X68kKey_backslash, // [2e]
NoKey, // [2f]
NoKey, // [30]
NoKey, // [31]
X68kKey_Q, // [32]
X68kKey_W, // [33]
X68kKey_E, // [34]
X68kKey_R, // [35]
X68kKey_T, // [36]
X68kKey_Y, // [37]
X68kKey_U, // [38]
X68kKey_I, // [39]
X68kKey_O, // [3a]
X68kKey_P, // [3b]
X68kKey_at, // [3c]
X68kKey_bracketleft, // [3d]
X68kKey_hira, // [3e]
X68kKey_zenkaku, // [3f]
NoKey, // [40]
X68kKey_UNDO, // [41]
X68kKey_A, // [42]
X68kKey_S, // [43]
X68kKey_D, // [44]
X68kKey_F, // [45]
X68kKey_G, // [46]
X68kKey_H, // [47]
X68kKey_J, // [48]
X68kKey_K, // [49]
X68kKey_L, // [4a]
X68kKey_semicolon, // [4b]
X68kKey_colon, // [4c]
X68kKey_bracketright, // [4d]
X68kKey_OPT1, // [4e]
X68kKey_OPT2, // [4f]
X68kKey_XF1, // [50]
X68kKey_XF2, // [51]
X68kKey_Z, // [52]
X68kKey_X, // [53]
X68kKey_C, // [54]
X68kKey_V, // [55]
X68kKey_B, // [56]
X68kKey_N, // [57]
X68kKey_M, // [58]
X68kKey_comma, // [59]
X68kKey_period, // [5a]
X68kKey_slash, // [5b]
X68kKey_underscore, // [5c]
X68kKey_XF3, // [5d]
X68kKey_XF4, // [5e]
X68kKey_XF5, // [5f]
X68kKey_HOME, // [60]
X68kKey_PAD_plus, // [61]
X68kKey_PAD_minus, // [62]
X68kKey_PAD_7, // [63]
X68kKey_PAD_8, // [64]
X68kKey_PAD_9, // [65]
X68kKey_PAD_4, // [66]
X68kKey_PAD_5, // [67]
X68kKey_PAD_6, // [68]
X68kKey_PAD_1, // [69]
X68kKey_PAD_2, // [6a]
X68kKey_PAD_3, // [6b]
X68kKey_PAD_0, // [6c]
X68kKey_PAD_decimal, // [6d]
X68kKey_PAD_enter, // [6e]
X68kKey_PAD_CLR, // [6f]
X68kKey_ROLLUP, // [70]
X68kKey_ROLLDOWN, // [71]
X68kKey_F1, // [72]
X68kKey_F2, // [73]
X68kKey_F3, // [74]
X68kKey_F4, // [75]
X68kKey_F5, // [76]
X68kKey_F6, // [77]
X68kKey_F7, // [78]
X68kKey_F8, // [79]
X68kKey_F9, // [7a]
X68kKey_F10, // [7b]
X68kKey_PAD_multiply, // [7c]
X68kKey_PAD_divide, // [7d]
X68kKey_PAD_equal, // [7e]
X68kKey_PAD_comma, // [7f]
};
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.