モノ創りで国造りを

ハード/ソフト問わず知見をまとめてます

PICマイコンの覚書

Print文の出力

XC8の場合、PICでprint文を表示するには、printf関数を書くだけではなくputch関数を記載する必要がある。 XC16では、 printf関数は通常使用が可能。

環境

型番 PIC18F25K80
コンパイラ XC8

実装

なにはともあれ#include <stdio.h>を記述。
main関数内にpirntf("%d",hensu);と記述すると問題なくコンパイル通るもののprint文は記述されない。
print文を表示させるには、以下のputch関数を記載する。
putch関数の記載場所によってはredefineエラーが出る。
その場合は記載場所を変える(とりあえずmain.cに記載すればOK)。
レジスタ名称はPICのシリーズや型番によって若干異なるので注意。
* 以下の例はUSART1に表示する場合。
* USART2に表示する場合は12に変えれば良い。

void putch(char Data){
    while(!TXSTA1bits.TRMT);
    TXREG1 = Data;
}

CAN通信

PICでCAN通信を開通するために、MCCでIDを設定したが、設定した結果はMASKとFilterに反映される。
CANのIOピンの設定がどこに反映されているか不明だったため、念のためPRAGMAを設定した。

環境

:--: :--:
型番 PIC18F25K80
コンパイラ XC8

MaskとFilterについて

CAN通信モジュールは、MCCで使用するIDを設定できる。
全てのIDを受信可能にもできるが、ノイズを考慮し、必要な分だけ受信できるように設定したほうが良い。
設定結果はFilterとMaskに反映される。
Maskで1となっている全bitについて、Filterに記載された値と受信データの値が等しい場合のみ、データは受信できる。
例えばMask=0x3E、Filter=0xA7の場合、受信データは0bxx10011xとなる(xは0/1どちらでも良い)。

Hex BIN
Mask 0x3E 0b0011_1110
Filter 0xA7 0b1010_0111
Valid Data 0bxx10_011x

CANモジュールのPRAGMA設定

なにかしらのヘッダーに以下を追加。

#pragma config CANMX = PORTB

コメントアウトしてもうまくいったので、多分必要ないんだろうけど、
今後のことを考えてブログに残しておく。

ADC

dsPICでADCの設定をMCCに任せっきりにした場合、
単chのADCはうまくいったが、複数chをスキャンする場合にAD変換できなかった。
調査した結果、ADCを行えるようになったのでメモ。

環境

シリーズ dsPIC33E
コンパイラ XC16

実装1:単chのAD変換

CH0にAN0を1っ回だけAD変換する場合は以下。 下記以外の設定はMCCに任せてよい。

AD1CON2bits.CHPS = 0;//CH0をセット
AD1CHS0bits.CH0SA = 0;//AN0をセット

AD1CON1bits.SAMP = 1;//サンプリングを開始する
int i = 0;
for( i=0;i <1000;i++){}//サンプリングするため一定時間設ける
AD1CON1bits.SAMP = 0;//サンプリングを止める           
while(!AD1CON1bits.DONE){}//サンプリングデータのAD変換完了を待つ

printf("%x",ADC1BUF0);//データはADC1BUF0で取り出せる。

実装2:複数chのAD変換(スキャンモード)

CH0にAN0~AN5をAD変換(スキャン)する場合は以下。

AD1CON1bits.ASAM = 1;//自動変換モードをセット
AD1CON2bits.CHPS = 0;//Ch0を使用
AD1CON2bits.BUFM = 0;/バッファのモードを設定
AD1CON2bits.ALTS = 0;//MUXAのみ使用する
AD1CON4bits.ADDMAEN = 0;//DMAは使用しない
AD1CON2bits.CSCNA = 1;//CH0をスキャンモードに使用する

AD1CON2bits.SMPI = 0x5;//AD変換6回実施後に変換完了したことを割り込みで伝える
AD1CSSL = 0x3F;//スキャンするCHをセット(ここでは0~5))
AD1CON1bits.SSRC = 0x7;//サンプリングを自動開始するようにセット


int main(void){
    while(!AD1CON1bits.DONE){}//AD変換完了を待つ
//各信号のAD変換結果はADC1BUFxから取得できる。
    printf("BUF0:0x%x\r\n",ADC1BUF0);
    printf("BUF1:0x%x\r\n",ADC1BUF1);
    printf("BUF2:0x%x\r\n",ADC1BUF2);
    printf("BUF3:0x%x\r\n",ADC1BUF3);
    printf("BUF4:0x%x\r\n",ADC1BUF4);
    printf("BUF5:0x%x\r\n",ADC1BUF5);
}

PWM制御

モーター駆動用にPWM制御を実装した。PWM周期の基準クロックはFosc/2でなくFoscな点に注意。
勘違いしてハマりました。

環境

シリーズ dsPIC33E
コンパイラ XC16

実装

PWMモジュールには、ハイサイドとローサイドのスイッチを制御するモードが用意されている。
PWMを一定周期で動作させるには、タイムベースをプライマリに設定する。
PWM周期は、PTPERレジスタで設定する。
例えばFoscが30MHzかつクロック分周が1:1、PTPERが100だった場合、PWM周期は

1秒 / 30MHz x 100 = 3.3マイクロ秒

となる。
PWMのDutyは、ハイサイドはPDCxレジスタ、ローサイドはSDCxレジスタで設定する。
PDCx/SDCxレジスタの値とPTPERの値の比がDuty比となる。
例えばFoscが30MHzかつクロック分周が1:1、PDC1を40とした場合、PWM1Hピンが'H'となる時間は

1秒 / 30MHz x 40 = 1.32マイクロ秒

となる。

Timer割り込み

PWM周期毎にDuty比変えるためにタイマー2の割り込みを使用。

環境

シリーズ dsPIC33E
コンパイラ XC16

実装

Timer2の割り込みをEnableにする。
割り込みのタイミングはTMR2レジスタで決まる。基準となるクロックはFosc/2。
PWM周期毎にDuty比変えるには、TMR2レジスタ値はPTPERレジスタの半分にする必要がある。
またはPTPERレジスタ値を2倍にするか、PWMのクロック分周を1:2にすれば良い。
割り込み時に実行する処理は、mccで生成されたtmr2.c内にあるCallBack関数内に記述する。

delay関数

delay関数でマイクロ秒以下の制御を行うには、_delay();を使う。

環境

型番 PIC18F25K80
コンパイラ XC8

実装

例えばシステムクロックが16MHzの場合、以下の記述で250ns遅延させられる。

_delay(1);

括弧内の1は実行する命令数。
1命令あたり4クロック必要なので、
1秒/16MHz X 4CLK = 250ns の遅延となる。

余談

if文やfor文を使用すると、それだけで命令が実行される。
その遅れを避けるためには、マイクロで関数のようなものを記述すればよい。

for(int i =0;i<4;i++){
     function();
}

この場合は、

#define FUNC function();
#define FUNC4 FUNC FUNC FUNC FUNC

void main(){
    FUNC4
}

もっといい方法ありそうなもんですが。