こんにちは、でんゆうです。
新型コロナウイルスが5類感染症移行となりましたが、私の会社では引き続きテレワークが推進されています。
私は妻が妊娠していたこともあり、ほぼ100%テレワーク勤務をしていました。テレワークは通勤時間が無く、プライベート時間を多く取れるため非常に快適なのですが、自宅で仕事をする際に設備面で煩わしいことがありました。
それは、キーボード接続の切り替えです。
製作のきっかけ
自宅のデスクには社給PCと私物PCの2台あり、ひとつのキーボードを都度切り替えて利用しています。使い慣れて愛用しているキーボードのため、他のものはなかなかしっくりきません。。
キーボードは有線のため、切り替えの際は毎回USBコネクタを差し替えていました。この差し替え作業が非常に煩わしく面倒でした。少し文字を打ちたいときもわざわざコネクタを差し替えていました。
また、それぞれのPCに接続した際のケーブルの取り回しが逆なため、屈曲が凄まじく断線のリスクがあります。これらの理由からキーボードの切り替え装置を製作しました。
装置の仕様
切り替え装置は2つのホスト(社給PCと私物PC)と1つのデバイス(キーボード)と接続しており任意のタイミングで切り替えられるようにします。装置の特徴は以下の通りで、これらを元に設計を進めて行きます。
- 接続チャンネルの可視化(LED点灯による)
- スイッチによるチャンネル切り替え
- PC起動検知による自動チャンネル切り替え
- PC終了検知による自動チャンネル切り替え
ブロック図
以下に装置のブロック図を示します。
ポイントとしては、自動チャンネル切り替えのために2つのデバイスのVBUS電源を監視していることや、キーボードに対する電源立ち上がりを監視しているところです。
ハードウェアとしてはそこまで複雑ではなく、ソフトウェアでしっかり制御してあげる必要があります。基本的にはGPIOの制御だけなので難しくはありませんが、チャンネル切り替えのトリガー(スイッチ押下やVBUS検出など)は割り込みを使用します。
回路図
次に回路図です。手元にある部品や秋月電子で簡単に入手できるものを中心に使っています。唯一、USBのマルチプレクサである「FSUSB42」が入手できなかったのでAliExpressで購入しました。(非常に安価だったためダメ元で購入しましたが、問題なく動作しました)
基板図
基板設計は使用する筐体(TW5-3-9(タカチ電機工業))の推奨基板形状を元に外形を作りました。
ポイントは3つのUSBコネクタをすべて同じ方向に向けていることです。こうすることによりケーブルをデスク裏に収納し、デスクをスッキリさせられます。
またLEDなど筐体に穴開け加工が必要なものに関してはできるだけ基板両端に対して中央に配置します。これは穴開け位置をわかりやすくするためです。
ファームウェア
切り替えの際のシーケンスとしては以下の通りです。
詳しくはソースコードを見ていただくとわかりますが、壊れる可能性があるためキーボードに電源供給していない状態でデータ通信できないようにしています。
出力電圧の立ち上がりを確認してからデータ通信を開始するようにしています。
接続チャンネルの可視化(LED点灯による)
現在接続しているチャンネルをLEDで表し、目視で接続チャンネルを可視化します。
スイッチによるチャンネル切り替え
双方のPCが起動しているときはスイッチによるチャンネル切り替えを可能とします。これにより任意のタイミングで切り替えが可能です。
ただし、片方のPCのみ起動(または接続)している場合はスイッチによる切り替えはできないようにしています。
PC起動検知による自動チャンネル切り替え
PCの起動を検知し、起動したPCにチャンネルを切り替えます。
使いたいから起動するので、自動でチャンネル切り替えするようにします。
PC終了検知による自動チャンネル切り替え
PCの電源をOFFにした際、もう一方の起動中のPCに切り替えます。終了したPCに接続していても意味がないためです。
一応、ソースコードを載せておきますが自己流ですのでご了承ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
/* * Terget : USB-MUX Board * Author : denU * MCU : PIC16F18323 * DATE : 2023/11/15, 14:49 * version: 1.00 */ #define _XTAL_FREQ 1000000 #define LED1 RA5 //(O) #define SW RA4 //(I) #define VBUSOUT RA2 //(O) #define ICSPCLK RA1 //(O) #define ICSPDAT RA0 //(O) #define USBSEL RC5 //(O) #define USBOE RC4 //(O) #define LED0 RC3 //(O) #define VIN0 RC2 //(I) #define VIN1 RC1 //(I) #define VBUSIN RC0 //(I) // CONFIG1 #pragma config FEXTOSC = OFF // FEXTOSC External Oscillator mode Selection bits (外部クロックをOFF) #pragma config RSTOSC = HFINT1 // Power-up default value for COSC bits (EXTOSC operating per FEXTOSC bits) #pragma config CLKOUTEN = OFF // Clock Out Enable bit (CLKOUT function is disabled; I/O or oscillator function on OSC2) #pragma config CSWEN = ON // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed) #pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable (外部クロックを監視しない) // CONFIG2 #pragma config MCLRE = ON // Master Clear Enable bit (MCLR/VPP pin function is MCLR; Weak pull-up enabled) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config WDTE = OFF // Watchdog Timer Enable bits (WDT enabled, SWDTEN is ignored) #pragma config LPBOREN = OFF // Low-power BOR enable bit (ULPBOR disabled) #pragma config BOREN = ON // Brown-out Reset Enable bits (Brown-out Reset enabled, SBOREN bit ignored) #pragma config BORV = LOW // Brown-out Reset Voltage selection bit (Brown-out voltage (Vbor) set to 2.45V) #pragma config PPS1WAY = ON // PPSLOCK bit One-Way Set Enable bit (The PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle) #pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a Reset) #pragma config DEBUG = OFF // Debugger enable bit (Background debugger disabled) // CONFIG3 #pragma config WRT = OFF // User NVM self-write protection bits (Write protection off) #pragma config LVP = ON // Low Voltage Programming Enable bit (Low Voltage programming enabled. MCLR/VPP pin function is MCLR. MCLRE configuration bit is ignored.) // CONFIG4 #pragma config CP = OFF // User NVM Program Memory Code Protection bit (User NVM code protection disabled) #pragma config CPD = OFF // Data NVM Memory Code Protection bit (Data NVM code protection disabled) unsigned char g_out_stat = 0x03; //出力しているポート番号を保存(0x01:ch.0 0x10:ch.1 0x02:error(not output) 0x03:default) unsigned char g_port_stat = 0x00; //検知しているHOSTを保存(0x00:Default 0x01:VIN0のみ検知 0x10:VIN1のみ検知 0x11:VIN0,VIN1の両方を検知) #include <xc.h> void main(void) { ANSELA = 0x00; //すべてデジタルピンに設定 ANSELC = 0x00; //すべてデジタルピンに設定 PORTA = 0x00; PORTC = 0x10; TRISA = 0x10; //SWのみInput TRISC = 0x07; //VIN1,VIN0,VBUSINをInput GIE = 0; //Global Interrupt Enable bitはDisable IOCIE = 0; //IOC割り込みはDisable /////関数定義///// void ON_SEQUENCE(void); //オンシーケンス(初期出力チャンネルを設定) unsigned char PORT_SWITCHING(unsigned char); //出力ポート切り替え関数 void LED_SET(unsigned char); //LEDセッティング関数 void CHANNEL_CHECK(void); //各チャンネル確認関数 void INT_EN(void); //割り込み許可関数 void RST_SEQUENCE(void); //ソフトリセットシーケンス ON_SEQUENCE(); //オンシーケンス INT_EN(); //割り込み許可 while(1){ } return; } /////割り込み許可関数///// //説明:検知HOSTを確認し、割り込み許可する //引数:non //戻り値:non void INT_EN(void) { CHANNEL_CHECK(); GIE = 1; //グローバル割り込みを許可 IOCIE = 1; //IOC割り込みを許可 if(g_port_stat == 0x01){ //ch.0のみ検出している場合 IOCAN = 0x10; //SWの立ち下がり待ち IOCCP = 0x02; //VIN1の立ち上がり待ち IOCCN = 0x00; } else if(g_port_stat == 0x10){ //ch.1のみ検出している場合 IOCAN = 0x10; //SWの立ち下がり待ち IOCCP = 0x04; //VIN0の立ち上がり待ち IOCCN = 0x00; } else if(g_port_stat == 0x11){ //ch.0及び1を検出している場合 IOCAN = 0x10; //SWの立ち下がり待ち IOCCP = 0x00; IOCCN = 0x06; //VIN[1:0]の立ち下がり待ち } } /////LEDセッティング関数///// //説明:LEDを任意のパターンで点灯させる //引数:点灯パターン //戻り値:non void LED_SET(unsigned char set) { //0x00: LED1 OFF LED0 OFF //0x01: LED1 OFF LED0 ON //0x10: LED1 ON LED0 OFF //0x11: LED1 ON LED0 ON if(set == 0x00){ LED1 = 0; LED0 = 0; } else if(set == 0x01){ LED1 = 0; LED0 = 1; } else if(set == 0x10){ LED1 = 1; LED0 = 0; } else if(set == 0x11){ LED1 = 1; LED0 = 1; } } /////出力ポート切り替え関数///// //説明:ポートチャンネルを受け取り、USB出力・LED点灯を行う //引数:ポート番号(0x01 or 0x10) //戻り値:0x00:正常完了 0x01異常終了 unsigned char PORT_SWITCHING(unsigned char port) { if(port == g_out_stat) { return(0x01); } else { USBOE = 1; //USB出力をDisable VBUSOUT = 0; //VBUS出力をOFF __delay_ms(50); //VBUS立ち下がり待機 while(VBUSIN == 1){ //VBUSがLになるまで待機 } g_out_stat = port; //ステータス変数切り替え if(port == 0x01){ //LEDの切り替え USBSEL = 0; //ポートの切り替え LED_SET(0x01); } else if(port == 0x10){ USBSEL = 1; //ポートの切り替え LED_SET(0x10); } VBUSOUT = 1; //VBUS出力をON __delay_ms(50); //VBUS立ち上がり待機 while(VBUSIN == 0){ //VBUSがHになるまで待機 } USBOE = 0; //USB出力をEnable return(0x00); } } ///// ///// //説明:各チャンネルの検知確認を行い、「g_port_stat」に状態を保存する //引数:なし //戻り値:non void CHANNEL_CHECK(void) { unsigned char port_num = 0x00; //ポート番号保存用変数 if(VIN0 == 1){ //ch.0を検知の場合は0x01を足す port_num += 0x01; } if(VIN1 == 1){ //ch.1を検知の場合は0x10を足す port_num += 0x10; } g_port_stat = port_num; //検知しているチャンネル情報を保存 } /////オンシーケンス(初期出力チャンネルを設定)///// //説明:接続チャンネルを判定し、出力設定する //引数:なし //戻り値:non void ON_SEQUENCE(void) { __delay_ms(200); CHANNEL_CHECK(); if(g_port_stat == 0x11){ //両チャンネル検知の場合はch.0に優先出力 PORT_SWITCHING(0x01); } else{ PORT_SWITCHING(g_port_stat); } } /////ソフトリセットシーケンス///// //説明:強制リセットする void RST_SEQUENCE() { unsigned char count1; //リセットの可視化(LEDの同時点滅) for(count1 = 0x00; count1 < 0x0A; count1++) { LED_SET(0x11); __delay_ms(100); LED_SET(0x00); __delay_ms(100); } RESET(); //ソフトリセット } /////割り込み関数///// //説明:SW押下またはHPDにて割り込み発生→接続ポートを切り替える //引数:なし //戻り値:non void __interrupt() ISR() { IOCIE = 0; __delay_ms(200); //チャタリング、VBUS安定待ち /////SW押下検出 if(IOCAF == 0x10){ unsigned char count0 = 0x00; //SWがHになるまで待機 while(SW == 0){ __delay_ms(11); count0 += 0x01; //3000msのカウント(200+11*0xFF) if(count0 == 0xFF){ //3000msの長押しを検出した場合、 RST_SEQUENCE(); //リセットシーケンスに以降する } } if(g_out_stat == 0x01 && VIN1 == 1){ //現在の出力がch.0でch.1を検知できている場合 PORT_SWITCHING(0x10); } else if(g_out_stat == 0x10 && VIN0 == 1){ //現在の出力がch.1でch.0を検知できている場合 PORT_SWITCHING(0x01); } } /////VIN0挿入検出 else if(IOCCP == 0x04 && IOCCF == 0x04){ PORT_SWITCHING(0x01); } /////VIN1挿入検出 else if(IOCCP == 0x02 && IOCCF == 0x02){ PORT_SWITCHING(0x10); } /////VIN0抜去検出 else if(IOCCN == 0x06 && IOCCF == 0x04){ PORT_SWITCHING(0x10); } /////VIN1抜去検出 else if(IOCCN == 0x06 && IOCCF == 0x02){ PORT_SWITCHING(0x01); } IOCAF = 0x00; IOCCF = 0x00; INT_EN(); //割り込み許可 } |
製作
簡単に製作の過程をまとめます。
まずは基板からです。基板はFusionPCBさんで製作してもらいました。
部品を実装しました。
次に筐体です。
コネクタやスイッチ、LEDのある部分を手加工で穴開けしていきます。
結果、、、
汚ねぇ・・・
調べてみると、超音波カッターを使うとキレイに切れるらしいのですが、お値段がなかなかするので手が出ませんでした。
筐体に基板を組み込むとこんな感じ。
まあまあいい感じです。
ちなみに基板を固定するネジですが、ボス穴がΦ2.3mmのためM2.3のネジを探したのですが見つかりませんでした。
TW5-3-9B図面|タカチ電機工業
一番近いM2.6のネジを試して見ると取り付けできました。
スイッチやLEDも問題無く動作しており、PCも切り替えを認識しています。
最後に
今回製作したようなUSB切り替え装置は探せば市販されていますが、作れるだろうと思い製作しました。
自作した方が愛着が湧きますし、何よりものづくりする過程が楽しいです。これからも役に立つモノを製作していきたいと思います!
ご覧いただきありがとうございました!