言うまでもなくUSB規格の情報はとても膨大で、USB仕様書から1つの機器を作り上げるのは素人にはまず無理です。USBをサポートしたPICマイコンでは、信号の解析まではハードウェアで行ってくれますが、それをレジスタからどう扱うかはプログラムでの処理となります。これを補うため、マイクロチップではUSBライブラリーを別途配布しているわけですが、ここで互換性の問題にぶち当たります。

PIC16FやPIC32MMシリーズでは最新の拡張ライブラリー(MLA)でもUSBはサポートされていますが、それ以外のPIC32シリーズはMicrochip Harmonyフレームワークに取って代わっていて、従来のライブラリーを応用する場合は大幅な修正が必要です。

そんななか、既存のライブラリーに極力頼らないようにしているため、XC32とPIC32MXでも使えるUSBライブラリー「m-stack」がGithubで公開されていましたので、これを組み込んだ開発を試みてみました。

開発環境のMPLAB X IDEを起動してスタンダードプロジェクトを作成したら、Githubからダウンロードしたソースコードから「usb」のフォルダーをまるごとプロジェクトフォルダーに、「apps/hid_mouse」フォルダーにある「usb_config.h」と「usb_descriptors.c」をプロジェクトフォルダーにコピーします。
プロジェクトをICアイコンを右クリックして表示される「Properties」で表示されるウィンドウから「XC32→xc32-gcc」を選び、ドロップダウンリストから「Processing and messages」を指定します。更新されたリストにある「Include directories」より、プロジェクトフォルダーにある「usb/include」フォルダーの場所を入力します。
つぎはメインプログラムの移植です。UPLLIDIVへは「外部発振器のクロック数/UPLLIDIV=4MHz」が成り立つ値を入力します。私の開発ボードでは8MHz水晶発振子を用いているので、1/2を指示しています。設定と環境の組み合わせによっては周波数が不安定になることがあるので、パソコンにつないだときにデバイスを認識しない警告が出るようであれば「UPLLEN」と「UFRCEN」の組み合わせを変えてみましょう。
// Original program was developed by Alan Ott, Signal 11 Software.

#include <p32xxxx.h>
#include <stdio.h>
#include "usb.h"

#pragma config FNOSC = PRIPLL   // 主外部発振器
#pragma config POSCMOD = XT     // 外部高精度発振器を使う
#pragma config FPLLIDIV = DIV_2 // PLL入力分数 = x1/2
#pragma config FPLLMUL = MUL_20 // PLL逓倍比 = x20
#pragma config FPLLODIV = DIV_2 // PLL出力分数 = x1/2
#pragma config FPBDIV = DIV_2   //周辺モジュールクロック倍数 : x1/2

#pragma config UPLLEN = ON      // USBPLLを使用する(デバイスの認識がうまくいかない場合はこの値をOFFにしてみる)
#pragma config UPLLIDIV = DIV_2 // USBクロック倍数 : x1/2(4MHzになるように設定)

// デバッグ用。必要に応じて値を変える
#pragma config ICESEL = ICS_PGx3
#pragma config DEBUG  = ON

void InitHardware(void)
{
    IPC7bits.USBIP = 4; // USBの割り込み優先度
    // マルチベクタ割り込みを有効
    INTCONbits.MVEC = 1;
    __builtin_enable_interrupts();
}


#ifdef MULTI_CLASS_DEVICE
static uint8_t hid_interfaces[] = { 0 };
#endif

void InitSerial()
{     
    // USBシリアルモニターでの検証用
    __XC_UART = 1;            // stdioによる出力先をUART1にする
    U1RXRbits.U1RXR = 0b0100; // UART1/RXにB2を
    RPB3Rbits.RPB3R = 0b0001; // B3をUART1/TXに
    
    U1STAbits.UTXEN = 1;  // UART1の送信を有効
    U1STAbits.URXEN = 1;  // UART1の受信を有効
    U1BRG = 129;          // ボーレート9600 [20M/(16*9600)-1]
    
    U1MODEbits.ON = 1;   // UART1を有効
    
    printf("Hello!\n"); 
}

int main(void)
{
    InitHardware();
    
    InitSerial();

#ifdef MULTI_CLASS_DEVICE
    hid_set_interface_list(hid_interfaces, sizeof(hid_interfaces));
#endif
    usb_init();

    uint8_t x_count = 100;
    uint8_t delay = 7;
    int8_t x_direc = 1;

    while (1) {
        if (usb_is_configured() &&
            !usb_in_endpoint_halted(1) &&
            !usb_in_endpoint_busy(1)) {

            // 送信バッファの更新
            unsigned char *buf = usb_get_in_buffer(1);
            buf[0] = 0x0;
            buf[1] = (--delay)? 0: x_direc;
            buf[2] = 0;
            usb_send_in_buffer(1, 3);

            if (delay == 0) {
                if (--x_count == 0) {
                    x_count = 100;
                    x_direc *= -1;
                }
                delay = 7;
            }
        }

        #ifndef USB_USE_INTERRUPTS
        usb_service();
        #endif
    }

    return 0;
}
サンプルのUSBマウスの定義(usb_descriptors.c)より、送信する命令のバイト配列がわかります。「Report Size」はビット数、「Report Count」はそれらの数なので、「ボタンに3bitが1つ(ビット演算による組み合わせで3つのボタンを同時に押せる)、割り当てられていない5bitが1つ、XY移動に8bitが2つ」と考えることができ、計3バイトの情報を送信することになります。

このことより、main.cでは一定時間ごとに「1バイト目のみを更新している=マウスのX値を更新している」ことがわかります。実際にプログラムを書き込んだマイコンをUSBでパソコンにつなぐと、マウスカーソルが左右にゆらゆらと動きます。

拙作の開発基盤を使用する場合は、写真のようにD+とD-をビニルワイヤーで結線しないと動作しないのでご注意ください。
今回はプロジェクトファイル一式を用意しています。ダウンロードはこちらから。