FT-991用CATコントローラの製作

# FT-991用CATコントローラの製作
最近の小型のアマチュア無線機は、つまみも少なく、小さな液晶のタッチパネルを駆使しての操作が必要で、操作しにくく、覚えられず、イラつく。
やっぱり物理ボタンがないとなぁ、ということで物理ボタンのついたコントローラを作成した。

# CATってなに？
- Computer Aided Tuningの略らしい
- アマチュア無線機をコンピュータからコントロールするもの
- 八重洲無線ではCATコントロールと読んでいる
- 物理層はRS232C（標準は4800bps）
- コマンド、パラメータは制御する無線機依存あり統一されていない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337553/e86365ae-708d-f683-c5ae-659401c2510f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337553/90d6bbcf-201a-1788-01a9-5e7d20a0d2fa.png)

# ハードウェア
## ベースとなるのは100均（DAISO, CANDO)のテンキー
- CANDO（写真右）とDAISO（写真左）の330円商品（外装がちょっと違うが中身は同じ）
- キーボードは4列×5行のシート電極（ボタンは19個）
- USB HIDコントローラはCJS1026D
- ちなみに旧製品はチップがCOBで改造に不向き、新製品はチップがSOP16に変更されてます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337553/10e9315e-fa4b-29b2-45a9-5e4c217fcdd8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337553/33e36857-1448-8771-6202-1004910b418e.png)

## 改造方法
基板はパターンを含めてなるべく流用します。
USB HIDコントローラCJS1026Dを引っ剥がし、PIC16F1705に貼り替えてジャンパを6本ほど飛ばします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337553/d11f6b89-6ce9-b74e-f8d5-c00aa5a07662.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337553/25d9fe9f-0a6a-29b8-1b16-f0f26eee2f12.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337553/48277aab-8f0c-bf00-2ecc-26e56eb61c60.png)


# ソフトウェア
MPLAB IDEv6.00とMCCでPIC16F1705のソフトウェアをサクッと作成します
MPLABのソースリストは[GitHub](https://github.com/pokibon3/ft991_cat/tree/master/ft991_cat.X)にプロジェクト一式おいてあります。
- キースキャンは10ms単位（チャタリング防止のため2回連続で確定）
- テンキーを使い周波数直打ちによるダイレクト設定
- 送信時LED点灯
- 主要なコマンドは以下を実装
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337553/0374ac6a-53bf-e7ef-5476-a9cfbc43ad6a.png)

```
#include "mcc_generated_files/mcc.h"
#define MAX_ROW 	5 + 1
#define MAX_COLUM 	4 + 1

volatile uint8_t last_code = 0;
volatile uint8_t key_code = 0;

const uint8_t key[MAX_COLUM][MAX_ROW] = {
	//   Y1   Y2   Y3   Y4   Y5
	{0,   0,   0,   0,   0,   0},		
	{0, '0', '1', '4', '7', 'F'},		// X1
	{0, '0', '2', '5', '8', '/'},		// X2
	{0, 'Z', '3', '6', '9', '*'},		// X3
	{0, '.', 'E', 'B', '+', '-'},		// X4
};

#define BUF_SIZE	10
char work_buf[BUF_SIZE];
char cmd_buf[BUF_SIZE];
uint8_t lock_mode = 0;
uint8_t fast_mode = 0;

uint8_t work_cur_pos = 0;
uint8_t cmd_cur_pos = 0;
//======================================================
//	buf control functions 
//======================================================
void clear_buf()
{
	int	i;
	work_cur_pos = 0;
	cmd_cur_pos  = 0;

	for (i = 0; i < BUF_SIZE; i++) {
		work_buf[i] = '0';
		cmd_buf[i]  = '0';
	}
	cmd_buf[BUF_SIZE - 1] = '\0';
	work_cur_pos = 0;
	cmd_cur_pos  = 0;
}

void add_work_buf(char key)
{
	if (work_cur_pos != BUF_SIZE) {
		work_buf[work_cur_pos++] = key;
	}
}

void add_cmd_buf(char key)
{
	if (cmd_cur_pos != BUF_SIZE - 1) {
		cmd_buf[cmd_cur_pos++] = key;
	}
}

void bs_work_buf(void)
{
	if (work_cur_pos > 0) {
		work_buf[--work_cur_pos] = '0';
	}
}

void parse_work_buf()
{
	int i, j;
	for (i = 0; i < BUF_SIZE; i++) {
		if (work_buf[i] == '.') {
			for (j = i - 3; j < i; j++) {
				if (j < 0) {
					add_cmd_buf('0');
				} else {
					add_cmd_buf(work_buf[j]);
				}
			}
			break;
		}
	}
	if (i >= BUF_SIZE) {			// '.' not found then copy all
		for (j = 0; j < BUF_SIZE; j++) {
			add_cmd_buf(work_buf[j]);
		}
	} else {						// copy after '.'
		for (j = i + 1; j < BUF_SIZE; j++) {
			if (work_buf[j] != '.') {
				add_cmd_buf(work_buf[j]);
			}
		}
	}	
}

//======================================================
//	scan_row() : scan keyboard
//======================================================
static uint8_t scan_row(int8_t col)
{
	uint8_t row = 0;

	if 		(col == 1) KEY_X1_SetLow();
	else if (col == 2) KEY_X2_SetLow();
	else if (col == 3) KEY_X3_SetLow();
	else if (col == 4) KEY_X4_SetLow();

	if (KEY_Y1_GetValue() == 0) {
		row = 1;
	} else if (KEY_Y2_GetValue() == 0) {
		row = 2;
	} else if (KEY_Y3_GetValue() == 0) {
		row = 3;
	} else if (KEY_Y4_GetValue() == 0) {
		row = 4;
	} else if (KEY_Y5_GetValue() == 0) {
		row = 5;
	}
	if 		(col == 1) KEY_X1_SetHigh();
	else if (col == 2) KEY_X2_SetHigh();
	else if (col == 3) KEY_X3_SetHigh();
	else if (col == 4) KEY_X4_SetHigh();
	return key[col][row];
}

//======================================================
//	timer_int_callback() : about 8ms timer
//======================================================
void timer_int_callback(void)
{
	uint8_t code = 0;
	static uint8_t repeat = 0;
	int8_t i;

	for (i = 1; i <= 4; i++) {
		code = scan_row(i);
		if (code != 0) break;
	}
	if (code != 0) {
		if (code == last_code) {
			repeat++;
			if (repeat == 1) {
				key_code = code;
			}
		} else {
			repeat = 0;
			last_code = code;
		}
	} else {
        repeat = 0;
        last_code = 0;
    }

    return;
}

//======================================================
//	send_cmd() : send string to uart
//======================================================
void send_cmd(char *cmd)
{
	char *p = cmd;
	LED_SetHigh();
	while(*p){
	    EUSART_Write(*p);
		p++;
	}
	LED_SetLow();
	return;
}

void send_freq()
{
	if (work_cur_pos != 0) {
		parse_work_buf();
		send_cmd("FA");
		send_cmd(cmd_buf);
	    send_cmd(";");
	}

	return;
}
//======================================================
//	decode_key() : decode key function
//======================================================
uint8_t decode_key(char key)
{
    char buf[2];
	switch(key) {
		case 'F' :					// Lock main dial
			if (lock_mode == 0) {
				lock_mode = 1;
				send_cmd("LK1;");	// Lock dial
			} else {
				lock_mode = 0;
				send_cmd("LK0;");	// unLock
			}
			clear_buf();			// clear cmd buffer
			break;
		case '/' :					// change V/M
			send_cmd("VM;");
			clear_buf();			// clear cmd buffer
			break;
		case '*' :
			if (fast_mode == 0) {
				fast_mode = 1;
				send_cmd("FS1;");
			} else {
				fast_mode = 0;
				send_cmd("FS0;");
			}
			clear_buf();			// clear cmd buffer
			break;
		case '+' :
			send_cmd("UP;");
			clear_buf();			// clear cmd buffer
			break;
		case '-' :
			send_cmd("DN;");
			clear_buf();			// clear cmd buffer
			break;
		case 'B' :
			bs_work_buf();
			break;
		case 'E' :
			send_freq();
			clear_buf();			// clear cmd buffer
			break;
        case 'Z' :
            add_work_buf('0');
            add_work_buf('0');
            add_work_buf('0');
            break;
		default :
			add_work_buf(key);
			break;
	}
	return 0;
}

//======================================================
//	setup() : setup app
//======================================================
void setup()
{
    TMR0_SetInterruptHandler(timer_int_callback);
	KEY_X1_SetHigh();
	KEY_X2_SetHigh();
	KEY_X3_SetHigh();
	KEY_X4_SetHigh();
    LED_SetLow();
	clear_buf();			// clear cmd buffer

	return;
}

//======================================================
//	main() : Main application
//======================================================
void main(void)
{
    // initialize the device
    SYSTEM_Initialize();

    // When using interrupts, you need to set the Global and Peripheral Interrupt Enable bits
    // Use the following macros to:

    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts
    INTERRUPT_PeripheralInterruptEnable();

    // Disable the Global Interrupts
    //INTERRUPT_GlobalInterruptDisable();

    // Disable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptDisable();
    
    setup();
    while (1)
    {
        // Add your application code
		if (key_code != 0) {
			decode_key(key_code);
			//DELAY_milliseconds(100);
			key_code = 0;
		}
    }
}
```

# 無線機との接続

- RS232Cのレベル変換は秋月の３Ｖ・３．３Ｖ・５Ｖ系－ＲＳ２３２レベル変換基板[AE-ADM3202]をDサブ9ピンケースに入れて使用
- 電源は無線機本体から13.8Vを借用
- 写真にはないが、Dサブ9ピンコネクタの先に接続をクロスに変更するジェンダーを介してFT-991に接続する
-
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337553/4d88ea6c-ba0d-0c3d-56f4-ae9a28d06283.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337553/26d8b039-fbb1-3b1a-8849-a3d50c87beb9.png)

# まとめ
- 100均一のテンキーを改造して、アマチュア無線機FT-991A用CATコントローラを製作した
- USB HIDコントローラをPIC16F1705（SOP14）に変更し、テンキーの外観を維持
- コントローラのファーム変更で色々な入力装置として使用可能と考える。