特定の比率のオン・オフを周期的かつ正確に繰り返すパルス幅変調「PWM」は、サーボモーターの制御や、LEDの輝度調整やブザーの音階調整などの擬似的なアナログ出力に用いられます。PIC32マイコンでは、このPWM出力を出力コンペア機能の一つとして提供しています。

PWMを使用するには出力コンペア設定レジスタ(OCxCON)のOCMを0110にします。ちなみに、0111でフォルトを有効にすると、指定したフォルトピン(OCFA/OCFB)に0信号が流れたとき、PICはプログラムを介さず直接PWMを停止させます。

PIC32においては、タイマーで周期の長さをカウントし、タイマーカウントが特定の値(OCxR)になるとスイッチを切り、タイマーの1サイクルが終わると再びスイッチを入れることを繰り返しています。
タイマーは2もしくは3を使います。Timer2および出力コンペアをともに32bitモードにするとカウンターの上限が増えるので、極端に低い周波数の生成もできるようになります。

タイマークロックがそのまま1秒あたりの実行回となるので、このクロックを適用したい周波数で割ると、指定すべきタイマー値(PRx)になります。例えば、タイマーが20MHzクロックで動作している環境で1kHzの周期(1000カウントで1単位)を実装したいのなら、20000000/1000=2000が計算値となります。

PIC32MX1xx/2xxシリーズでは、PWMを出力できる主なピンはA0,B3,B4,B7,B15です。 PWMではスイッチのオン・オフの長さの比率がデューティ比になるので、30%にしたいときはPRxの1/3の計算結果をOCxRSの値にすると良いことになります。

このサンプルでは100msごとにタイマー割り込みを発生させ、呼ばれた回数に応じて「2kHz→1kHz→休止」の処理を繰り返します。
#include <p32xxxx.h>
#include <sys/attribs.h>

// 外部の8MHz水晶発振器から40MHzのクロックを生成するための設定
#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

#define BASE_CLK 20000000  // 周辺クロック(20MHz)

void updateDuty(int hz)
{
    // 1サイクルあたりの時間を計算
    int ticks = BASE_CLK / hz;
    PR2 = ticks;
    
    // PR2のタイマーカウントの半分で出力を切り替えるようにするとデューティ比が50%になる
    // 1/4ならデューティ比は25%になる。
    ticks /= 2;
    
    // OCxRSに値をセットするとバッファとして機能する。
    // これによりOCxRの値が急に切り替わることによって発生するノイズ(グリッチ)を抑止できる
    OC1RS = ticks;
    
    // 出力コンペアに割り当てているタイマーをリセットすることでPWMを再始動
    TMR2 = 0;
}

void InitTimer1()
{
    T1CONbits.ON = 0;    // 設定のため、タイマー1を無効にする
    TMR1 = 0;            // タイマー1をリセット
     
    // 100ms周期で割り込み <- 500ns(1 / 20MHz) * 256 * 7812(0x1E84) = 100ms
    T1CONbits.TCKPS = 3; // プリスケーラ : x1/256
    PR1 = 0x1E84;        // カウンター
        
    IPC1bits.T1IS = 3;   // 副優先度(0-3)
    IPC1bits.T1IP = 4;   // 割り込み優先度(0-7)
    IFS0bits.T1IF = 0;   // 割り込みフラグをリセット
    IEC0bits.T1IE = 1;   // タイマー1の割り込みを許可
    
    T1CONbits.ON = 1;    // タイマー1有効
}

int beep_mode = 1;

void __ISR(_TIMER_1_VECTOR, IPL4AUTO) OnTimer1()
{
    IFS0bits.T1IF = 0;

    if(beep_mode == 1){
        OC1CONbits.ON = 1;  // 出力コンペアを有効
        updateDuty(2000);
    }else if(beep_mode == 2){
        updateDuty(1000);
    }else if(beep_mode == 3){
        OC1CONbits.ON = 0;  // 出力コンペアを無効
    }else if(beep_mode == 16){
        beep_mode = 0;
    }
    beep_mode++;
}

int main(void)
{
    ANSELB = 0x00;          // ポートBをすべてデジタルモードにする  
    TRISB = 0;              // ポートBをすべて出力にする
    
    // タイマー2の設定(プリスケーラ1/1、内部クロックを使用)
    T2CONbits.ON = 0;
    T2CONbits.T32 = 1;       // タイマー3と併合して32bitモードで使用
    T2CONbits.TCKPS = 0b000; // 分周比 1:1
    T2CONbits.ON = 1;  
    TMR2 = 0;
        
    OC1CONbits.ON = 0;        // 設定のため出力コンパレータ(OC1)を無効に
    OC1CONbits.OCTSEL = 0;    // タイマー2を使用
    OC1CONbits.OCM = 0b0110;  // PWMで、失敗(fault)の検知を無効
    OC1CONbits.OC32 = 1;      // 32bitで計測
    RPB3Rbits.RPB3R = 0b0101; // B3にOC1を割り当てる
    OC1CONbits.ON = 1;
    
    InitTimer1();             // タイマー1の初期化
    
    INTCONbits.MVEC = 1;            // マルチベクタ割り込みを有効にする      
    __builtin_enable_interrupts();  // マイコンにおける割り込みの有効
    
    while(1) ;
}
PWMをデューティ比50%で出力すると一般的な矩形波になるので、圧電ブザーをB3とGND端子に接続すると、普遍的なブザー音が鳴り響きます。

 | 2018年10月1日

PIC32ではMSTEN=0をセットし、スレーブモードを有効にしてSPI通信を行うと、マスター側から受け取ったクロックのタイミングをハードウェアで認識してデータに変換してくれます。

スレーブモード固有の設定は入力タイミングに関連する指定が主のものとなります。SSENを1にするとSSxピンが閉じる(立ち下がる)を感知して、続いて送られてくるクロック信号とビット列の取得を開始します。SSEN=1に加えてFRMEN=1にすると、フレーム化SPI信号(クロック信号は常に発生していて、データの受信前に一瞬だけSSxピンのスイッチが切り替わる)を識別できます。ただ、私の知る限り、フレーム化SPIを送信するICはほとんど見かけません。

それではサンプルプログラムの紹介です。ここではArduinoで生成したSPI信号をPIC32で解析させます。
まずはマスター側であるArduinoのスケッチから。こちらではSSピンを手動でオフにしてから「hello」というASCIIコードを500msごとにSPI命令で送信しています。
#include <SPI.h>

#define SS 10

void setup() {
    pinMode(SS, OUTPUT);
    digitalWrite(SS, HIGH);

Serial.begin(9600);
    SPI.begin();
    delay(500);
}

void loop() {
    digitalWrite(SS, LOW);
    SPI.transfer('h');
    SPI.transfer('e');
    SPI.transfer('l');
    SPI.transfer('l');
    SPI.transfer('o');
    digitalWrite(SS, HIGH);

    delay(500);
}
つづいてPIC32でのプログラムコードです。SPI2の信号を各ピンに割り当てて(値とピンの関係についてはSPI送信の記事をご覧ください)、割り込み処理内のループで連続したビットを一度に取得しています。
#include <p32xxxx.h>
#include <sys/attribs.h>

// 外部の8MHz水晶発振器から40MHzのクロックを生成するための設定
#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 ICESEL = ICS_PGx3
#pragma config DEBUG  = ON

void __ISR(_SPI2_VECTOR, IPL4AUTO) OnSpi2(void)
{
    int i, n[5];
     IFS1bits.SPI2RXIF = 0;     // 割り込みフラグのクリア
   
    for(i = 0; i < 5; i++){
        SPI2BUF = 0;
        while(!SPI2STATbits.SPIRBF);  // 受信バッファが使用可能になるのを待つ
        n[i] = SPI2BUF;
    }
}

int main(void)
{
    int i;
    ANSELB = 0x00;          // ポートBをすべてデジタルモードにする  
    TRISB = 0;              // ポートBをすべて出力にする
      
    SPI2CONbits.ON = 0;     // 設定のためSPI1を無効に
    
    SPI2CONbits.SSEN = 1;   // SSピンを有効にする
    SPI2CONbits.MSTEN = 0;  // スレーブモード
    SPI2CONbits.CKP = 0;    // ArduinoにおけるMode0
    SPI2CONbits.CKE = 1;    // ArduinoにおけるMode0
    
    SDI2Rbits.SDI2R = 0b0100; // B2にSDI2を割り当てる
    RPB1Rbits.RPB1R = 0b0100; // B1にSDO2を割り当てる
    SS2Rbits.SS2R = 0b0010;   // B0にSS2を割り当てる
    
    IPC9bits.SPI2IP = 4;      // SPI2割り込み優先度
    IPC9bits.SPI2IS = 3;      // SPI2副優先度
    
    IEC1bits.SPI2RXIE = 1;    // SPI2割り込みを有効にする
    IFS1bits.SPI2RXIF = 0;    // 割り込みフラグのクリア
    i = SPI2BUF;              // ダミーデータに格納することでバッファをクリア
    
    INTCONbits.MVEC = 1;      // マルチベクタ割り込みを有効にする
       
    __builtin_enable_interrupts();  // マイコンにおける割り込みの有効
    
    SPI2CONbits.ON = 1;       // SPI2を稼働

    while(1) ;
}
ここではデバッガでの確認でお茶を濁していますが、デバッガ情報の書き込み内容によっては、PIC内に余計な情報が挟まるためか、いつまでたっても正しい値を表示してくれないことがあります。そのときはクリーンビルドを試してみてください。
 | 2018年9月29日

SPIは通信相手(スレーブ)の選択やフレーム同期を行うSS/SSIと、ビットを知らせるSCK、データ入出力・SDO/SDIの4線から成り立つ通信方法で、PIC32MXの場合、最大で周辺クロックの1/2の速度(40MHzクロックなら20MHz)を出すことができます。

SCKのピンは固定(B14/B15)ですが、それ以外は複数のピンの中から選ぶことができます。

SDI1A1(0b00)B5(0b01)B1(0b10)B11(0b11)B8(0b100)
SDO1(0b11)A1B5B1B11
SS1A0(0b00)B3(0b01)B4(0b10)B15(0b11)B7(0b100)
SDI2A2(0b00)B6(0b01)A4(0b10)B13(0b11)B2(0b100)
SDO2(0b100)A1B5B1B11
SS2A3(0b00)B14(0b01)B0(0b10)B10(0b11)B9(0b100)
(26ピンモデルの場合。括弧内はレジスタに登録する数値)

SPIの方式は、同期信号が立ち上がったとき/立ち下がったとき、マスターかスレーブか、同期信号でビット列のグループを判断するかどうかなどで、信号の内容が少しずつ変わってくるため、SPIxCONレジスタで細かく指定する必要があります。

SRXISEL拡張バッファ使用時のSPI受信割り込みのタイミング
0-バッファの中身がすべて吐き出された
1-バッファが空でなくなった
2-バッファの半分以上が埋まった
3-バッファがすべて埋まった
STXISEL拡張バッファ使用時のSPI送信割り込みタイミング
0-送信が完了した
1-バッファが空になった
2-バッファが半分以上からになった
3-バッファに空きができた
DISSDISDIピンの使用(1-使用しない)
MSTENマスターモード(1-マスター:0-スレーブ)
CKP1でクロックがない状態をHighとみなす
SSEN1でスレーブ用にSSxピンを使用する
CKEデータの取得タイミング(0-クロックが開いている[Active]ときに取得:1-閉じている[Idle]ときに取得)
SMPマスターでのデータ受信タイミング(0-クロックの中央:1-クロックの終わり)
MODE1616bit単位で転送(0で8bit単位)
MODE3232bit単位で転送(0ならMODE16の値が適用される)
DISSDOSDOピンの使用(1-使用しない)
SIDLアイドル中の停止(1-停止する)
ONSPIの実行(1-実行する)
ENHBUF拡張バッファ(FIFOバッファ)の使用(1-使用する)
SPIFE同期エッジの位置(0-立ち下がり:1-立ち上がり)
MCLKSELマスタクロックを使用(0-周辺クロックを使用)
FRMCNTフレーム同期信号のタイミング(0-1データごと:1-2:2-4:3-8:4-16:5-32)
FRMSYPWフレーム同期信号の長さ(0-1クロック:1-MODE16/32で指定したビット数)
MSSENスレーブ選択の可否(1-スレーブを選択できるようにする)
FRMPOLフレーム同期信号の極性(0-Lowで開く:1-Highで開く)
FRMSYNCフレーム同期信号の向き(0-出力/マスター:1-入力/スレーブ)
FRMENフレームモードで使用(1-使用)

信号のタイミングは言葉では伝わりにくいので、データシートの内容も引用しておきます。
なおCKP/CKEの組み合わせとArduinoにおけるSPIモードの関係性は以下のようになります。
ModeCKPCKE
Mode001
Mode100
Mode211
Mode310
SPIの周波数は「PBCLK/(2*BRG+1)」で計算します。例えばPBCLKが40MHzの場合、BRG=0なら20MHz、BRG=15なら1.25MHzになります。

それではサンプルを作ってみましょう。今回はSPI通信でGPIOの制御ができるマイクロチップのI/Oエクステンダー「MCP23S17(日本語データシート/PDF)」を使用します。ピン数の少ない「MCP23S08」も基本的な操作方法は同じますが、MCP23S17の方がライブラリーなどの情報が比較的豊富です。

ArduinoではSIはMOSI(No.11)、SOはMISO(No.12)に接続しますが、PICの場合はSIはSDO、SOはSDIにつなぐ点に注意しましょう。またMCP23S17が受信できる信号速度は最大10MHzなので、送信時のクロックも適時落としておきます。I/Oの切り替えは、SSをオフにした間に、3バイトのデータから成る命令をSPI信号として送信することで実行されます。
#include <p32xxxx.h>
#include <sys/attribs.h>

// 外部の8MHz水晶発振器から40MHzのクロックを生成するための設定
#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

void send(int addr, int data)
{
    LATBbits.LATB0 = 0;
    int i, n;
    for(i = 0; i < 3; i++){
        switch(i){
        case 0: n = 0x40; break;
        case 1: n = addr; break;
        case 2: n = data; break;
        }
        SPI2BUF = n;
        while(SPI2STATbits.SPIBUSY) ; // 処理が完了するのを待つ
    }
    LATBbits.LATB0 = 1;
}

int main(void)
{
    int i, j = 0;
    ANSELB = 0x00;          // ポートBをすべてデジタルモードにする  
    TRISB = 0;              // ポートBをすべて出力にする
    
    LATBbits.LATB0 = 1;     // B0 = on   
    
    SPI2CONbits.ON = 0;     // 設定のためSPI1を無効に
    
    SPI2BRG = 15;           // SPIの周波数を2MHzに
    SPI2CONbits.MSTEN = 1;  // マスターモード
    SPI2CONbits.CKP = 0;    // ArduinoにおけるMode0
    SPI2CONbits.CKE = 1;    // ArduinoにおけるMode0
    
    SDI2Rbits.SDI2R = 0b0100; // B2にSDI2を割り当てる
    RPB1Rbits.RPB1R = 0b0100; // B1にSDO2を割り当てる
    SS2Rbits.SS2R = 0b0010;   // B0にSS2を割り当てる
    
    j = SPI2BUF;            // ダミーに出力してバッファをクリア
    
    SPI2CONbits.ON = 1;     // SPI2を稼働
    
    //  MCP23S17の設定
    // 1バイト目はDevice Opecode。0x40で書き込み命令、0x41で読み込み命令
    // 2バイト目は制御レジスタ
    // 3バイト目はパラメータ
    send(0x0A, 0x20);       // IOCON[0x0A]、シーケンシャルモードを無効[SEQOP = 1]
    send(0x00, 0x00);       // IODIRA[0x00]、ポートAのすべてのピンを出力モードに
    
    while(1){
        send(0x12, j);      // GPIOA[0x12]、すべてのピンのオン(1)・オフ(0)を切り替える
        j = ~j;             // 0x00<->0xFFに切り替え
        for(i = 0; i < 1000000; i++) ;
    }
}
SPI通信が成功したのを確認したら、LED(と抵抗)の数を増やし、jの値をビットシフトさせてキラキラさせてみましょう。


(おまけ)SPI信号のテストのために作ったArduinoスケッチ
#include<SPI.h>

#define SS 10

#define MCP_WRITE 0x40
#define MCP_READ 0x41

void sendBytes(int address, int data)
{
  digitalWrite(SS, LOW);
  SPI.transfer(MCP_WRITE);
  SPI.transfer(address);
  SPI.transfer(data);
  digitalWrite(SS, HIGH);
}

void setup()
{
  pinMode(SS, OUTPUT);
  digitalWrite(SS, HIGH);

  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV2);

  sendBytes(0x0A, 0x20); // I/Oの初期設定[SEQOP:1(バイトモード)]
  sendBytes(0x00, 0x00); // ポートAをすべて出力にする
}

int p=0;
void loop() {
  sendBytes(0x12, 1<<p);
  p = (p == 0) ? 7 : 0;
  delay(200);
}
 | 2018年9月28日

古い時代からあるシリアル通信のひとつであるUARTは双方の機器においてデータのやりとりの方法を事前に知っておかなければならない、現在の基準から見ると通信速度が遅いなどの理由により、今では別の手段、とくにパソコンではUSBに取って代わられました。一方で、最低2本の信号線があればやりとりが可能というシンプルさより、特別なデバッグツールなしにマイコンの内部処理をパソコンで調べたいときなどに、この通信方法が使われています。

PIC32ではいくつかの決められたピンの中から通信用に割り当てることができます。これはデータシートの「Peripheral Pin Select」で確認でき、「UnRXR」「UnRTX」のレジスタで指定できます。28ピン版では「A2,B6,A4,B13,B2」のいずれかをUART1のRXに、「A1,B5,B1,B11,B8」をUART2のRXに指定できます。
U1RXRbits.U1RXR = 0b0100; // B2ピンをUART1・RXに割り当て
/*
0000 = RPA2
0001 = RPB6
0010 = RPA4
0011 = RPB13
0100 = RPB2
*/
TXでは「A0,B3,B4,B15,B7」をUART1に、「A3,B14,B0,B10,B9」をUART2に割り振れます。

出力であるTXは「RXに○ピンを割り当てる」という先の指定方法とは逆で、「○ピンにTXを割り当てる」という命令を記述します。
RPB3Rbits.RPB3R = 0b0010; // B3にTXコードを割り当てる
速度を決めるためのボーレート設定レジスタ「UnBRG」の値は、周辺クロック数に基づいて「PBCLK/(16*baud)-1」という式で計算します。たとえば周辺クロックが20MHz、通常ボーレートモード(UxMODEbits.BRGH=0)においてボーレートを115200にしたい場合は、「20000000/(16*115200)-1=9.8」となります。プログラムでは近似値の10を指定することになるので、実行時には若干の誤差が発生します(データの送信ごとに同期し直されるので、誤差が5%未満であれば、データへの影響はありません)。

「UxSTAbits.UTXEN=1」にすると送信が、「UxSTAbits.URXEN = 1」にすると受信が受け付けられます。「UxSTAbits.UTXBA=0」であれば、送信バッファに空きがあるので「UxTXREG」にデータを代入して送信します。受け取ったデータが存在する場合、「UxSTAbits.URXDA=1」になっているので、「UxRXREG」から取得します。

こちらはUARTシリアル通信のサンプルです。この例ではArduino UnoをUSBシリアル変換ケーブルの代わりに使用しています(スケッチは不要。やりかたはこちら)。PIC32のRB3をArduinoのRXに、RB4をTXピンに接続してください。
#include <p32xxxx.h>
#include <sys/attribs.h>

// 外部の8MHz水晶発振器から40MHzのクロックを生成するための設定
#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

char str[51]; int sp = 0;

void sendString(const char *s, int slen)
{
    int i;
     for(i = 0; i < slen; i++){
        U1TXREG = s[i];
        while(U1STAbits.UTXBF) ; // バッファに空きが出るまで待機
    }
}

void __ISR(_UART_1_VECTOR, IPL4AUTO) OnUartReceiving(void) 
{
    IFS1bits.U1RXIF = 0; // UART1受信フラグのリセット

    if(U1STAbits.URXDA == 0) return; // バッファが空の状態なら何もしない
    str[sp] = (char)U1RXREG; // 受信データバッファのデータを格納
    
    // 改行コードを受信するかバッファがいっぱいになったら
    if(str[sp] == '\n' || sp > 50){
        // 受け取ったデータをそのまま返す
        sendString(str, sp + 1);
        sp = 0;
    }else{
        sp++;
    }
}

int main(void)
{
    ANSELB = 0x00;        // ポートBをすべてデジタルモードにする

    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を有効
    IPC8bits.U1IP = 4;   // UART1優先割り込み度
    IPC8bits.U1IS = 3;   // UART1副優先度
    
    IFS1bits.U1RXIF = 0; // UART1受信フラグのリセット
    IFS1bits.U1TXIF = 0; // UART1送信フラグのリセット
    
    IEC1bits.U1RXIE = 1; // UART1受信完了割り込みを有効
    IEC1bits.U1TXIE = 0; // UART1送信完了割り込みを無効
    
    INTCONbits.MVEC = 1;  // マルチベクタ割り込みを有効にする
       
    __builtin_enable_interrupts();  // マイコンにおける割り込みの有効

    sendString("Hello!\n", 7);

    while(1) ;
}
Arduino IDEを開いて、シリアルモニタよりArduinoのCOMポートを開きボーレートに「9600 bps」を指定してPICよりプログラムを実行すると、はじめに文字列が表示されます。あとは、Arduino IDEから任意の文字列を送信すると、全く同じ文字列がPICから返され、シリアルモニタにその内容が表示されます。
また、XC32コンパイラではprintf()を使うことができ、「__XC_UART」で出力先のUARTピンを指定すると、マイコン側で文字列のフォーマットと送信を一度に行えます。
#include <stdio.h>

int main(void)
{
    __XC_UART = 1;
    U1RXRbits.U1RXR = 0b0100;
    RPB3Rbits.RPB3R = 0b0001;
    
    U1STAbits.UTXEN = 1;
    U1STAbits.URXEN = 1;
    U1BRG = 129;
    
    U1MODEbits.ON = 1;
    
    printf("Hello, world!\n");
    while(1);
}
 | 2018年9月26日

前回のタイマープログラムでPIC32マイコンの割り込み処理の基本を学んでいれば、ほかの種類の割り込みをプログラミングするのも難しくありません。今回はピンの状態が変化したときに割り込み処理が発生するプログラムを作ってみましょう。

状態変化検出モジュールによる割り込み関数を実行するには、以下の設定をすべて有効にします。


PIC32MX1XX/2XXの場合、ポートB状態変化検出モジュールのベクタ番号は34番(_CHANGE_NOTICE_VECTOR)となります。また、割り込み優先度は「IPC8bits」の「CNIP」および「CNIS」で定義します。

タクトスイッチに連動してLEDが点灯するサンプルプログラムを記述します。
マイコン内部には抵抗が組み込まれているので、「CNPUxbits.CNPUxn」で対象ピンのプルアップ抵抗を有効にすると、スイッチ回路に外部抵抗を継ぎ足すことなく、安定した情報を取得できます。
#include <p32xxxx.h>
#include <sys/attribs.h>

// 外部の8MHz水晶発振器から40MHzのクロックを生成するための設定
#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_1 //周辺モジュールクロック倍数 : x1/1

// 割り込みレベル4、34番(ポートB入力変化)ベクタ
void __ISR(_CHANGE_NOTICE_VECTOR, IPL4AUTO) OnPortChanged(void) 
{
    LATBbits.LATB0 = ~PORTBbits.RB1;
}

int main(void)
{
    ANSELB = 0x00;        // ポートBをすべてデジタルモードにする
    TRISBbits.TRISB0 = 0; // B0を出力にする
    PORTBbits.RB0 = 0;    // B0 = off
    
    TRISBbits.TRISB1 = 1; // B1を入力にする
    PORTBbits.RB1 = 1;    // B1の初期状態を設定
    
    INTCONbits.MVEC = 1;  // マルチベクタ割り込みを有効にする

    CNCONBbits.ON = 1;    // ポートBの状態変化検出モジュールを使用
    CNENBbits.CNIEB1 = 1; // B1の検出を有効
    CNPUBbits.CNPUB1 = 1; // B1のプルアップ抵抗を使用
    
    IPC8bits.CNIP = 4;    // 割り込み優先度(0-7)
    IPC8bits.CNIS = 3;    // 副優先度(0-3)
    IFS1bits.CNBIF = 0;   // 割り込みフラグをリセット
    IEC1bits.CNBIE = 1;   // 状態変化Bの割り込みを許可
       
    __builtin_enable_interrupts();  // マイコンにおける割り込みの有効

    while(1) ;
}
 | 2018年9月25日

PIC32マイコンのタイマーは指定したクロック(分周比)ごとにタイマーレジスタの値が増えていき、一定の値になると0に戻るとともに割り込みフラグレジスタ(IFS0)がセットされます。ループ中に加算されたタイマー値を計算して経過を時間調べることもできますが、もっぱら割り込みによって呼び出された関数(割り込みサービスルーチン:ISR)で個別の処理をするのが一般的です。

PIC32ではすべての割り込み処理を同じ関数で呼び出すシングルベクタと、割り込みごとに異なる関数で呼び出すマルチベクタがあります。シングルベクタよりもマルチベクタの方が処理が早く、また、関数内で条件分岐させるよりも関数ごとに分けた方がコードの可読性が高いので、シングルベクタが使われることはあまりありません。

PIC32MXにはタイマー1からタイマー5まで用意されています。タイマー1は16bitが上限で副発振回路でのカウントができます。一方で2~5は副発振回路は使えませんが2と3、もしくは4と5を組み合わせることで32bit値でカウントすることができます。

では、タイマー関連の設定をプログラムしてみましょう。タイマーの速度(クロック数)はPBCLKの分周比で決まるため、外部発振器を使用した場合は、FPBDIVの値も定義する必要があります。
#pragma config FPBDIV = DIV_1 //周辺モジュールクロック倍数 : x1/1
この場合はタイマーもCPUクロックと同じ周波数で動作しますが、これをDIV_2にするなどして周波数を落とすと消費電力の削減を図ることができます。

タイマー1の設定はT1CONレジスタに書き込みます。
T1CONbits.TCS   : 対象クロック (0=内部,1=外部)
T1CONbits.TSYNC : 外部クロック時の同期 (0=非同期,1=同期)
T1CONbits.TCKPS : プリスケーラ設定 (0=1,1=8,2=64,3=256)
T1CONbits.TGATE : 内部クロック時のゲート[T1CKピンがHighのときだけカウントするか] (0=無効,1=有効)
T1CONbits.TWIP  : 非同期書き込み中かどうかを調べる (0=完了している,1=処理中である)
T1CONbits.TWDIS : 非同期時の書き込みの許可 (0=許可,1=許可しない)
T1CONbits.SIDL  : アイドル中の動作 (0=動作を続行,1=停止させる)
T1CONbits.ON    : タイマーの実行 (0=中断,1=実行)
「T1CONbits.ON = 1」でタイマーが稼働し、TMR1の値がPR1に設定した値までカウントが増えるとTMR1が0にリセットされるとともに割り込みが発生します。TCKPS値を増やすと、1カウントされるまでの間隔が倍増するので、例えばモジュールクロック40MHzの状況で100msごとに発生させたい場合は「250ns(1/40MHz) * 256(T1CONbits.TCKPS = 3) * 15625(PR1 = 0x3D09) = 100ms」と計算します。

タイマー1による割り込みを有効にするには、プログラム全体での割り込み処理を使用する関数「__builtin_enable_interrupts()」を記述して「IEC0bits.T1IE = 1」をセットします。この状態でマイコン内部の処理によって「IFS0bits.T1IF」の0だった値が1になるとタイマー1モジュール(ベクタ番号4)を指定した関数が呼び出されます。タイマーを繰り返したい場合、T1IFは手動(プログラム内)で0にして再び受け入れられるようにします。

割り込みの優先度は「IPC1bits.T1IP(7で最優先、0で割り込みをしない)」および「IPC1bits.T1IS(副優先。先の優先が同じレベルならこの値の大きい方[0~3]が優先される)」で指定します。

割り込み関数を定義するにはXC32コンパイラの場合、関数名の前に「割り込み優先度○の○番ベクタ」であることを宣言します。今回は「割り込みレベル4、4番ベクタ(_TIMER_1_VECTOR)」なので下記のようになります。
void __attribute__((interrupt(IPL4AUTO))) __attribute__((vector(4))) OnTimer1(void){}
「#include <sys/attribs.h>」をインクルードすれば、__ISRマクロで簡潔に記述することができます。
void __ISR(_TIMER_1_VECTOR, IPL4AUTO) OnTimer1(void){}
以上を集約したプログラム例がこちらになります。回路図は「PIC32でシンプルなLチカをやってみる」と同じです。
#include <p32xxxx.h>
#include <sys/attribs.h>

// 外部の8MHz水晶発振器から40MHzのクロックを生成するための設定
#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_1 //周辺モジュールクロック倍数 : x1/1

unsigned char toggle = 0, count = 0;

// 割り込みレベル4、4番ベクタ(Timer1に該当)
void __ISR(_TIMER_1_VECTOR, IPL4AUTO) OnTimer1(void) 
//void __attribute__((interrupt(IPL4AUTO))) __attribute__((vector(4))) OnTimer1(void)
{
    IFS0bits.T1IF = 0; // 割り込みフラグをリセット

    count++;
    if(count >= 10){
        toggle = !toggle;
        count = 0;
        PORTBbits.RB0 = toggle; // RB0のスイッチを切り替える      
    }
}

void initTimer1()
{
    T1CONbits.ON = 0;    // 設定のため、タイマー1を無効にする
    TMR1 = 0;            // タイマー1をリセット
     
    // 100ms周期で割り込み <- 250ns(1 / 40MHz) * 256 * 15625(0x3D09) = 100ms
    T1CONbits.TCKPS = 3; // プリスケーラ : x1/256
    PR1 = 0x3D09;        // カウンター
    
    INTCONbits.MVEC = 1; // マルチベクタ割り込みを有効にする
    
    IPC1bits.T1IP = 4;   // 割り込み優先度(0-7)
    IPC1bits.T1IS = 3;   // 副優先度(0-3)
    IFS0bits.T1IF = 0;   // 割り込みフラグをリセット
    IEC0bits.T1IE = 1;   // タイマー1の割り込みを許可
    
    __builtin_enable_interrupts();  // マイコンにおける割り込みの有効。「asm volatile("ei")」と同義
    T1CONbits.ON = 1;    // タイマー1有効
}

int main(void)
{
    TRISBbits.TRISB0 = 0; // B0を出力にする
    PORTBbits.RB0 = 0;    // B0 = off
     
    initTimer1();
    while(1) ;
}

プログラムの値を変えて、任意の間隔でLEDを点滅できるようにしてみましょう。
 | 2018年9月24日

前回の記事で単純なLチカを確認できたら、外部発振器の設定をしてみましょう。ハードウェアの設定はpragmaで行います。8MHz水晶発振子で40MHzのクロックを生成させるには次のようにします。PIC32MXシリーズではシステムクロックは40~120MHzの範囲でないといけないため、入力分数を1/4にして20MHzで動作させるということはできません。
#include <p32xxxx.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
// 8(主発振の周波数)/2(入力分数)*20(逓倍比)/2(出力分数)=40
こちらは4MHz発振子を使った場合の設定例です。
#pragma config FPLLIDIV = DIV_2 // PLL入力分割(1/2)
#pragma config FPLLMUL = MUL_20 // PLL倍率(x20)
#pragma config FPLLODIV = DIV_1 // PLL出力分割(1/1)
// 8/2*20/1=20
続いて配線です。ブレッドボードにタクトスイッチと1kΩ抵抗を追加し、RB1ピンにつなぎます。
今回のプログラミングで注意しないといけないのは、標準の入力はアナログモードのため、RB1ピンを入力モードにしただけだとでは正しい結果を得られない点です。つまり、オン・オフの判定をしたいのであれば、対象のピンをデジタルモードに明示的に指定する必要があります。以上を踏まえてコードを書いてみましょう。
#include <p32xxxx.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

int main(int argc, char** argv) {
    // B0ピンを出力にする
    TRISBbits.TRISB0 = 0;
    
    // B1ピンのアナログを無効にする(つまり、B1ピンがデジタルモードになる)
    ANSELBbits.ANSB1 = 0;
    
    // B1ピンを入力にする
    TRISBbits.TRISB1 = 1;
      
    while(1){
        // スイッチが入ると電気がGNDに流れてRB1が0になる
        // つまりRB1が0のときRB0を1にすることで、スイッチを押すとLEDが点く
        PORTBbits.RB0 = ~PORTBbits.RB1;
    }
    
    return (EXIT_SUCCESS);
}
しかしながら、ピンの入出力を正しく設定したにもかかわらず、なおも正しく動作しないと思います。

原因は早すぎるクロック数にあります。PORTレジスタは入力も出力も受け付けることができる反面、検出までにわずかな時間がかかるため、クロックに処理が追いついていないのです。

この問題に対処するにはPORTレジスタではなく、読み込み専用のLATレジスタを使います。以下のようにLATレジスタに置き換えてみましょう。
LATBbits.LATB0 = ~PORTBbits.RB1;
//PORTBbits.RB0 = ~PORTBbits.RB1; クロック数が高い場合、入力の受付も兼ねたこのレジスタでは正しく対応できない
これでスイッチの入力に反応して、LEDが点灯するようになります。


入門編のため、この記事ではループを使ってGPIOを操作しましたが、ちゃんとしたプログラムでは、ループではなく割り込み処理で制御しましょう。割り込み処理を使ったプログラミングはこちらをご覧ください。
 | 2018年9月24日

今回はマイクロチップ謹製の統合開発環境を使った、最も簡素なLチカをプログラミングしてみましょう。

まずはプログラムライターを開発基板に接続します。サンプル基板では、Pickitの▼マークを一番右にして順にピンを差し込めば一致するようになっています。右から6番目のピンにはLEDと抵抗をつなぎます。また、5v電源はUSB端子から取得していますので、USB端子をパソコンやACアダプターなどに接続しておいてください。
マイクロチップのサイトより統合開発環境「MPLAB X IDE(現時点で最新版は5.0)」と32bitコンパイラ「XC32」をダウンロードし、インストールします。

開発ツール・MPLABを起動したら、メニューから「新しいプロジェクト」を選びます。今回はフレームワークを使用しない「Standard Project」を選択してください。
プログラム対象の機器を選びます。「Device」で名称をキーボードで打ち込むと徐々に候補が絞られていくので、「Family」で「PIC32」でフィルターする手間は必要ありません。
プログラミングツールを選択します。ちなみに黄色のマークは「動作はするが、こちらですべての検証は行っていない」を意味しています。私の手持ちはPickit3なので、ここではそれを選んでいます。
コンパイラ選択画面では、先ほどインストールしたXC32を指定します。
プロジェクト名とプロジェクトの保存先を指定します。ほかのエディタでも正しく文字を表示できるように「Encoding」は「UTF-8」にしておきましょう。
プロジェクトが無事作成されたら、C言語ファイルを作成しましょう。まずはメニューより「新規ファイル」を選択。
ファイルの種類は「Cメインファイル」を選びます。ファイル名は任意でかまいません。
コードを記入します。まずは手始めに内部発振回路を使用するなど、すべてマイコンの初期値を使用しています。ちなみに「#include <p32xxxx.h>」をインクルードすることで、レジスタのアドレス番号を名称で指定できるようになります。
#include <p32xxxx.h>

int main(int argc, char** argv) {
    // B0ピンを出力にする
    TRISBbits.TRISB0 = 0;
    
    int i = 0, n = 0;
    while(1){
        PORTBbits.RB0 = n;
        i++;
        if(i >= 100000){
            // 10万クロックに1回の割合でスイッチを切り替える
            n = (n == 0) ? 1 : 0;
            i = 0;
        }
    }
    
    return (EXIT_SUCCESS);
}
プログラムコードを入力したら、実行してみましょう。
プログラムコードにミスがなければ「プロジェクトを実行」ボタンを押すだけで、ビルドからチップへの書き込みまで一括で行われます。



また、以下の2行を追加するとPickit3を使ったデバッグができるようになります。
#pragma config ICESEL = ICS_PGx3 // 環境によってはICS_PGx2の場合もある
#pragma config DEBUG  = ON
 | 2018年9月24日

近所のホームセンターで見かけた電源タップのデコレーション例。この中で一見一番手軽にできそうな塗装による自作ですが、電源プラグの穴に塗料が詰まる可能性があるので、タップにペンキを塗るのは好ましくありません。そこで、電源タップを分解して電気が通る金属部分を外してから塗装に臨むわけですが、100v電源を扱う周辺機器という関係上、多くの電源タップは特殊なタイプのネジで封印されています。
星形やY型に関わらず、中央がへこんだタイプのネジであれば、マイナスドライバーを溝に当てることで一通り対応できるのですが、中央が出っ張っているタイプのネジだと、特殊ドライバーセットでもこの形に対応している商品はなかなか見かけません。電気機器をあつかうプロフェッショナルなら、エンジニア社製の専用ドライバー(DTC-27)を常備しても良いのでしょうが、一生に2回以上使うかわからない道具に2000円近く出すのも勇気がいります。

そこで代替品としておすすめしたいのが、ゲーム機用特殊ドライバーです。Amazonなら4.5mmと3.6mm口径のセットで150円くらい(ドライバーの柄の部分は100円均一のラチェットドライバーでOK)で購入でき、写真のようにぴったりとはまってネジを回すことができます。
漏電や火災の原因になるので、油性塗料は使わず、タップの内側など塗らない部分はマスキングテープなどでしっかりと保護し、塗料が完全に乾ききるのを待ちましょう。
 | 2018年9月23日

マイクロチップの32bitマイコン・PIC32シリーズを使った電子工作を行うために必要な環境を作りましょう。ハードウェア面で必要なのは以下の通りです。

オリジナルの開発基板のKiCadデータと印刷デザインを無料で配布しています。そのまま使用するか、このレイアウトを参考に回路を作ってみましょう。


[高密度基板用(USB 5Vは裏面、もしくは被覆ワイヤーで結線)]


[低密度基板用(USB D+/D-は被覆ワイヤーで結線)]

ユニバーサル基板で作成したい場合はこちらの作成例をヒントにしてください。
基板を作ったら、このような感じでパーツを半田付けします。なお、特に注記のないコンデンサーはすべて0.1uFです。
 | 2018年9月20日