前回のゲームボーイエミュレータや以前に移植したゲームなどで使えて少ない配線でつなげられるゲームコントローラーが欲しかったので、I2C接続のコントローラーを作ってみました。
使用したマイコンはPIC16F819です。別に他のマイコンでもいいのですが、このPICを使った理由は数年前の秋月電子のお楽しみ袋に大量に入っていたので出来るだけ消費してしまいたかったからです。とはいえPIC16F819はレジスタ数が少なくシンプルなのにSPIやI2C(スレーブのみ)機能もあって使い勝手のいいPICです。今回のようなI2Cスレーブデバイスに向いているマイコンだと思います。
コントローラーのスティック部分は、以前にも使用したAE-JY-DIP(KIT)を使いました。アナログジョイスティックなのですが、簡単に扱えるように押した方向のみデータ出力するようにしました。でもアナログのデータが使えないのはもったいないので、モードを切り替えてアナログデータも出力できるようになっています。スティックのバーは長時間使っていると指先が痛くなってくるので、ノートPCのトラックポイント用のゴムキャップを取り付けました。
ボタンにはこれを使いました。タクトスイッチなのでゲーム向きかどうかは微妙なところですが、小さな基板に納めるのにびったりなサイズでした。
はずは配線から。電源はESP32からの3.3V。PIC16F819のスペックだと5Vのマイコンに接続しても動作すると思います。
[ボタンの配置]
D C
B A
スペースの都合でパスコンは裏側に配線
PICのプログラムはこちら。MPLAB X IDE v6.20でコンパイルしました。
main.c
- // PIC16F819 Configuration Bit Settings
- // 'C' source line config statements
- // CONFIG
- #pragma config FOSC = INTOSCIO // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin)
- #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
- #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
- #pragma config MCLRE = ON // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
- #pragma config BOREN = OFF // Brown-out Reset Enable bit (BOR disabled)
- #pragma config LVP = OFF // Low-Voltage Programming Enable bit (RB3/PGM pin has digital I/O function, HV on MCLR must be used for programming)
- #pragma config CPD = OFF // Data EE Memory Code Protection bit (Code protection off)
- #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off)
- #pragma config CCPMX = RB2 // CCP1 Pin Selection bit (CCP1 function on RB2)
- #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off)
- // #pragma config statements should precede project file includes.
- // Use project enums instead of #define for ON and OFF.
- #include
- #define _XTAL_FREQ 8000000 // 8MHz
- #define I2CADDR 0xF
- #define BUFFER_SIZE 4
- #define XYSMAX 823
- #define XYSMIN 200
- uint8_t skipreading;
- int stage = 0;
- uint8_t adHX, adLX, adHY, adLY;
- volatile uint8_t (*sendData)();
- void getAN0() // Y軸
- {
- ADCON0 = 0b01000001; // RA0を選択
- __delay_us(20); // 充電待ち
- // ADコンバート
- ADCON0bits.GO = 1; // AD変換開始
- while(ADCON0bits.GO); // AD変換終了待ち
- }
- void getAN1() // X軸
- {
- ADCON0 = 0b01001001; // RA1を選択
- __delay_us(20); // 充電待ち
- // ADコンバート
- ADCON0bits.GO = 1; // AD変換開始
- while(ADCON0bits.GO); // AD変換終了待ち
- }
- uint8_t sendDigitalData()
- {
- int adval;
- uint8_t padData = 0;
- // X軸
- getAN0();
- adval = ADRESH;
- adval <<= 8;
- adval |= ADRESL;
- if(adval < XYSMIN)
- padData |= 1;
- else if(adval > XYSMAX)
- padData |= 0b10;
- // Y軸
- getAN1();
- adval = ADRESH;
- adval <<= 8;
- adval |= ADRESL;
- if(adval < XYSMIN)
- padData |= 0b1000;
- else if(adval > XYSMAX)
- padData |= 0b100;
- if(!PORTBbits.RB3)
- padData |= 0b10000000;
- if(!PORTBbits.RB2)
- padData |= 0b1000000;
- if(!PORTBbits.RB7)
- padData |= 0b100000;
- if(!PORTBbits.RB6)
- padData |= 0b10000;
- return padData;
- }
- uint8_t sendAnalogData()
- {
- uint8_t padData = 0;
- switch(stage)
- {
- case 0:
- // X軸
- getAN0();
- adHX = ADRESH;
- adLX = ADRESL;
- // Y軸
- getAN1();
- adHY = ADRESH;
- adLY = ADRESL;
- if(!PORTBbits.RB3)
- padData |= 0b10000000;
- if(!PORTBbits.RB2)
- padData |= 0b1000000;
- if(!PORTBbits.RB7)
- padData |= 0b100000;
- if(!PORTBbits.RB6)
- padData |= 0b10000;
- padData |= adHX << 2;
- padData |= adHY;
- stage = 1;
- break;
- case 1:
- padData = adLX;
- stage = 2;
- break;
- case 2:
- padData = adLY;
- stage = 0;
- break;
- }
- return padData;
- }
- void __interrupt() isr(void)
- {
- if (PIR1bits.SSPIF == 1) // I2C割込み発生
- {
- if(SSPSTATbits.R_W == 1) // マスターからの読み出し要求(スレーブは送信)
- SSPBUF = (*sendData)(); // 送信データセット
- PIR1bits.SSPIF = 0 ; // 割込みフラグクリア
- SSPCONbits.CKP = 1; // SCLラインを開放(通信再開)
- }
- else if(INTCONbits.INTF == 1) // RB0/INT割り込み
- {
- if(PORTBbits.RB0) // デジタルモード
- {
- sendData = sendDigitalData;
- PORTAbits.RA7 = 0;
- OPTION_REGbits.INTEDG = 0; // 立下りで割り込み
- }
- else // アナログモード
- {
- sendData = sendAnalogData;
- PORTAbits.RA7 = 1;
- OPTION_REGbits.INTEDG = 1; // 立上りで割り込み
- }
- INTCONbits.INTF = 0; // 割込みフラグクリア
- }
- }
- void main(void) {
- OSCCONbits.IRCF = 0b111; // 周波数8MHz
- CCP1CON = 0b00000000; // CCP1モジュール無効(リセット)
- ADCON0 = 0b00000000;
- ADCON1 = 0b11000100; // 数値は右揃え システムクロック1/2 Vref+使わない RA0/1/3はアナログ入力
- // I2C
- SSPSTAT= 0b00000000;
- SSPCON = 0b00110110; // SDA/SCLピンはI2Cスレーブモード 7ビットアドレス
- SSPADD = I2CADDR << 1; // 7ビットアドレス
- // ポート設定
- OPTION_REGbits.nRBPU = 0; // PORTBをプルアップ
- TRISA = 0b00000011; // RA0/1入力
- PORTA = 0b00000000;
- TRISB = 0b11111111; // 全て入力 SDA/SCLピンはI2C
- PORTB = 0b00000000;
- // 割り込み
- if(PORTBbits.RB0) // デジタルモード
- {
- sendData = sendDigitalData;
- PORTAbits.RA7 = 0;
- OPTION_REGbits.INTEDG = 0; // 立下りで割り込み
- }
- else // アナログモード
- {
- sendData = sendAnalogData;
- PORTAbits.RA7 = 1;
- OPTION_REGbits.INTEDG = 1; // 立上りで割り込み
- }
- INTCONbits.INTE = 1; // RB0/INT割り込み許可
- PIE1bits.SSPIE = 1; // I2C割り込み許可
- INTCONbits.PEIE = 1; // 周辺装置割り込み許可
- INTCONbits.GIE = 1; // 全割り込み許可
- PIR1bits.SSPIF = 0; // I2C割り込みフラグクリア
- while(1)
- {}
- return;
- }
I2Cのアドレスは0xFにしてあります。配線にあるスイッチはI2Cの出力のデジタルモードとアナログモードの切り替えに使います。アナログモードになるとLEDが点灯します。それぞれのモードの時に出力されるデータの形式は次のようにしました。
部品は73mm×47mm基板に配置しましたが、むき出しでは使いにくいので100円ショップで売られていたミニチュア牛乳コンテナに納めました。
基板のサイズにぴったりですし、持った時にしっかりホールドできていい感じです。そのままケースに固定するとボタンを強く推したときに基板が少し沈むので、中に耐震シートを詰めて変形しないようにしています。
ESP32からの使用方法は前回のエミュレータのソースで確認してください。I2C端子はPIC内部でプルアップしてあるので、ESP32との接続にプルアップ抵抗は必要ありません。
※コメント投稿者のブログIDはブログ作成者のみに通知されます