QMKでArduino用キーボードを作る (2/4 protocol改造編)
I/Fのチェックに利用するテスターはできた。
ということで、 su120 の split_keyboard用である GPIO D2
からシリアル(8N1)のTXを送信するように改造する。
su120 のシリアル通信
su120 のPCBデータをみて、オシロスコープで確認すると、下図の配線になっていることがわかる。
QMKのcodeを読むと、スプリット対応のキーボードをつなぐと、defaultでUSB ケーブルに刺さっている方が Master
となり、もう一方を Slave
として独自のシリアル通信が始まる。
transport 層
codeの該当箇所は、以下の辺り。
qmk_firmware/quantum/split_common/transport.c
void transport_rgblight_slave(void) {
if (status_rgblight == TRANSACTION_ACCEPTED) {
rgblight_update_sync((rgblight_syncinfo_t *)&serial_rgblight.rgblight_sync, false);
status_rgblight = TRANSACTION_END;
}
}
# else
# define transport_rgblight_master()
# define transport_rgblight_slave()
# endif
bool transport_master(matrix_row_t matrix[]) {
# ifndef SERIAL_USE_MULTI_TRANSACTION
if (soft_serial_transaction() != TRANSACTION_END) {
return false;
}
# else
transport_rgblight_master();
if (soft_serial_transaction(GET_SLAVE_MATRIX) != TRANSACTION_END) {
return false;
}
# endif
// TODO: if MATRIX_COLS > 8 change to unpack()
for (int i = 0; i < ROWS_PER_HAND; ++i) {
matrix[i] = serial_s2m_buffer.smatrix[i];
}
上記のcode から、 protocol GET_SLAVE_MATRIX
によって、Master 発で、Slave の状態を取得しているとわかる。
Slaveが送ってきたキーのMatrix情報をMaster自身のMatrix情報に上書きして右手分を補完している。
オリジナルprotocol の詳細
Master からSlave に送る情報(request)は、こんな感じ。
SERIAL_USE_MULTI_TRANSACTION
は、シリアル通信で複数のprotocolをサポートできるようになっている。
- GET_SLAVE_MATRIX ...SlaveのキーMatrixを取得
- PUT_RGBLIGHT ... RGBの発光制御
自分でオリジナルのprotcol を追加することも簡単。
typedef struct _Serial_m2s_buffer_t {
# ifdef BACKLIGHT_ENABLE
uint8_t backlight_level;
# endif
# ifdef WPM_ENABLE
uint8_t current_wpm;
# endif
} Serial_m2s_buffer_t;
もし、backlight のlevel 指示、あるいは wpm 情報を利用しない場合、Master -> Slave は、実質 0byte.
それに答えるSlave の情報(reply)は、こんな感じ。
typedef struct _Serial_s2m_buffer_t {
// TODO: if MATRIX_COLS > 8 change to uint8_t packed_matrix[] for pack/unpack
matrix_row_t smatrix[ROWS_PER_HAND];
# ifdef ENCODER_ENABLE
uint8_t encoder_state[NUMBER_OF_ENCODERS];
# endif
} Serial_s2m_buffer_t;
SlaveのキーのMatrix(どのキーが押されているかの状態)と、ロータリーエンcodeの状態。
改造後のprotocol
MasterからだらだらとキーのMatrix情報を流したい(isochronous)。
また、お互いお行儀良くしゃべる必要はなく、Master->Slave の方向のみでよい。
下記のデータ(キーMatrix)をだらだら流す。
typedef struct _Serial_m2s_buffer_t {
matrix_row_t smatrix[ROWS_PER_HAND]; // 追加!
# ifdef BACKLIGHT_ENABLE
uint8_t backlight_level;
# endif
# ifdef WPM_ENABLE
uint8_t current_wpm;
# endif
uint8_t stop;
} Serial_m2s_buffer_t;
network 層
code の該当箇所は、以下の辺り。
qmk_firmware/drivers/avr/serial.c
ムリムリと読んでいくと、シリアルPINを LOW/HIGH 切り換えながら、Master <-> Slave のデータをお行儀良くやり取りしている。
データを送信する serial_write_chunk を 8N1 に書き換える。
void serial_write_chunk(uint8_t data, uint8_t bit) {
uint8_t b;
/* start bit */
serial_low();
serial_delay();
for (b = 1, c = 0; c < bit; b <<= 1) {
if(data & b) {
serial_high();
} else {
serial_lowx();
}
serial_delay();
}
/* stop bit */
serial_high();
serial_delay();
}
だらだらのためには、transaction も書き換える。
int soft_serial_transaction(int sstd_index) {
if( sstd_index > Transaction_table_size )
return TRANSACTION_TYPE_ERROR;
SSTD_t *trans = &Transaction_table[sstd_index];
cli();
// initiator send phase
if( trans->initiator2target_buffer_size > 0 ) {
serial_send_packet((uint8_t *)trans->initiator2target_buffer,
trans->initiator2target_buffer_size);
}
// no wait slave
// always ok
*trans->status = TRANSACTION_END;
sei();
return TRANSACTION_END;
}
こういう改造をすれば、シリアルTXとしてだらだらとデータを流してくれるはず。
code の組み込み
追加ファイル
上記の `transport.c` と `serial.c` をkeymapの自分のdirectory に作成
rules.mk
makefile(rules.mk) に設定
# CONSOLE_ENABLE = yes
ENCODER_ENABLE = yes
SPLIT_KEYBOARD = yes
SPLIT_TRANSPORT = custom
SRC += transport.c serial.c
config.h
software serialの設定など
#pragma once
#define SOFT_SERIAL_PIN D2 // or D1, D2, D3, E6
#define SELECT_SOFT_SERIAL_SPEED 5 // 20kbps
#define MASTER_RIGHT 1
code
qmk_firmware の repository は、ここ。
ここからforkして、自分用のbranchを切るのがお作法みたいだけど、そもそも @e3w2q さんのpatchみたいなものなので、さて。。
とりあえず、 @e3w2q さんの fork repository に修正を加えたものを公開しておく。