PIC 16F1827時計のソフトウエア開発 (3)
ソースファイルの続きをアップしていきたいと思います。
今回は、メイン文と初期化ルーチンです。
メイン文の中には、今回提示していないルーチンも含みますが、まだ、動かせるだけの状態でないので、ご容赦下さい。
もし、前回のものと合わせてコンパイルしようとする方がいらっしゃったら、エラー発生個所をコメント化していくのが良い方法かもしれません。
少し解説をしておきたいと思います。
最初に出てくるinitialize()が初期化ルーチンで、PICに必要なポートの設定と割り込みの設定を行っています。
ポートは、回路図で示していたように、ボタン入力ようのPORTA4のみをアナログ入力として、あとは、出力設定にします。また、初期時にLEDが光らないように、初期状態を設定します。
続いて、クロックは、割込み動作確認時と同様に8MHzの内部クロックで動作させるようにしています。
割込みは、3種類使うので、それぞれ設定します。タイマ1は、時計の基準クロックとして、250ms毎に割り込みを発生させるようにしました。8MHz=0.125usの周期のクロックでPICは動きますが、4クロックが一回の動作で必要ということになっています。プリスケーラで8倍にし、そのクロックで、25000回カウントして、100msの割り込み信号とします。タイマ1は、16ビットのクロックなので、プリスケーラと呼ばれる機構を使って、長い時間の設定ができるようにしています。
つまり、0.125us*4*8*25000=100msということですね。
この25000回は、BaseCountという名称で定義しています。プログラムでは、これにTimerAdjustIntという変数を加えて、TMR1HとTMR1Lというタイマ用の変数に代入しています。16ビットタイマの上位8ビットがTMR1H、下位8ビットがTMR1Lということになっています。count変数にBaseCountを直接代入せず、マイナス、つまりこの場合-25000を入れているのは、タイマがアップカウンタで、0になった時、割込みが発生するということで、プログラム上は、このような表記の方が見やすいと先人の情報にあったので、それを採用しています。
ここで、TimerAdjustIntという変数がありますが、後で説明するつもりです。PICの内蔵クロックの精度をソフト的に補正しようとするものと、今は思っていて下さい。
その後に、割込みを動作させるための設定を行っています。
続いて、タイマ2の設定をしています。こちらは、8ビットのタイマで、LEDをダイナミック点灯させるため、1ms毎に割り込みが発生するように、設定します。
こちらのタイマでは、PR2という変数とカウンタの値が一致した時に、割り込みが発生するという動作になります。タイマ1とはちょっと違うのでご注意下さい。
最後にタイマ4の設定をしています。これは、ボタンを読み込むための割り込みで、先人の情報から、30ms毎にボタンの状態を取りこむように割り込みを設定しています。こちらは、タイマ2と同様に設定すればOKです。
あとは、RA4のアナログ入力を使うため、アナログ入力の設定を行います。設定には、ちょっと難しAD変換クロックの設定があるのですが、プログラム内コメントを参照下さい。データシートにも詳しく書かれています。
これで、initializeルーチンの説明は終了です。
続いてメインになります。
まず、グローバル変数の初期化を行っています。
そして、少し説明を端折りますが、先ほどのinitialize()ルーチンを呼び出しています。
その後は、永久的にループさせるwhile文が続きます。
この中の説明は、また今度ということにします。関係するルーチンを説明するときに一緒にした方が、良いかと思いましたので。
以下、ソースファイルとなります。では、また。
/*****************************
* PICの初期設定
* PIC自身の設定は、Config記述にて、コンパイラが対応
* 割込み設定
* Timer1=100ms毎に割り込み:時計の基準クロック
* Timer2=1ms毎:LEDをダイナミック点灯させるためのクロック
* Timer4=32ms:ボタン状態の取り込み用割り込み
* AD変換の設定
*****************************/
int initialize() {
int count;
//ポートの設定
PORTA = 0b11111111; //PORTAすべて1に初期化
TRISA = 0b00010000; //PORTA4のみ入力、他は出力に設定(RA5(未使用)は入力only)
ANSELA = 0b00010000; //RA4のみアナログ入力(AN4)、その他デジタルI/Oとする
PORTB = 0b11111111; //PORTBはすべて1にして、LEDが光らないようにする
TRISB = 0b00000000; //PORTBはすべて出力
ANSELB = 0b00000000; //すべてデジタルI/O
OSCCON = 0b01110010; //PLL:OFF、内部クロック8MHz、Internal oscillator block
//bit7: SPLLEN: Software PL Enable bit 0=4x PLL is disabled
//bit6-3:ICRCF<3:0>=1110=8MHz=HFINTOSC
//bit1-0:SCS<1:0>: System Clock Select bits 1x=Internal oscillator block
// 00=FOSC<2:0>でクロック決める
//OSCSTAT:Read Only
//bit 0 HFIOFS: High Frequency Internal Oscillator Stable bit
// 1 = HFINTOSC is at least 0.5% accurate
// 0 = HFINTOSC is not 0.5% accurate
//OSCTUNE
// TUN<5:0>=000000 = Oscillator module is running at the factory-calibrated frequency.
//
//TMR1割り込みの初期設定
//今回は、計時専用にしてみる。
//100ms=0.125us(8MHz)*4*(プリスケーラ=8)*25000カウント
T1CON = 0b00110001; //クロック ソースFosc/4, プリスケーラー8, TMR1ON
//Regi Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
//T1CON TMR1CS1 TMR1CS0 T1CKPS1 T1CKPS0 T1OSCEN /T1SYNC - TMR1ON
//
//TMR1CS<1:0>=00:Timer1 clock source is instruction Clock (FOSC/4), T1OSCEN=X
//T1CKPS<1:0>=11: 1:8 Prescale value
//T1OSCEN=0: LP Oscillator Enable Control bit=Dedicated Timer1 oscillator circuit disabled
///T1SYNC=0: TMR1CS=00なので、これは無視される
//TMR1ON=1:Timer1 Operation On TMR1GE=0の時、常にOn、1の時Count Enabled
//TMR1カウント値設定
//BaseCount回(アップカウンター)カウントして100ms毎に割り込みをかける
//100ms毎にして、パターン表示ステップの0.1sにも流用。
count = -(BaseCount + TimerAdjustInt);
TMR1H = count >> 8;
TMR1L = count & 0x00ff;
PIE1bits.TMR1IE = 1; //Timer1 Overflow interrput Enable bit
INTCONbits.PEIE = 1; //周辺装置割り込みを許可(Timer1の場合必要)
INTCONbits.GIE = 1; //全体割込み許可
//TMR2割り込みの初期設定。8msでは、ちらつきあり⇒1ms
//1ms=0.125us(8Mhz)*4*(プリスケーラ=16=b10)*(125カウント=PR2+1)
T2CON = 0b00000110;
//Regi Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
//T2CON - <--- T2OUTPS<3:0> ----> TMR2ON T2CKPS<1:0>
//postscaler=1:1、Prescale=0b10=16
PR2 = 124; //比較期間
//これ以上短い割り込みすると、正常な処理ができない模様。
//★輝度を下げるには、1回の点灯時間で制御するようにする。
TMR2 = 0; //タイマーレジスタの初期化
TMR2IF = 0; // タイマー2割込フラグを0にする
TMR2IE = 1; // タイマー2割込みを許可する
FlagT2 = 0;
//TMR4割込み。約30ms毎に割り込みかけ、ボタンの状態を取得
//32ms=0.125us*4*(プリスケーラ=64=b11)*(250カウント=PR2+1)*(ポスト1:4=0011)
T4CON = 0b00011111;
PR4 = 249;
TMR4IF = 0; // タイマー4割込みフラグ
TMR4IE = 1; // タイマー4割込みを許可
flagT4 = 0;
//AD変換の初期設定:RA4=AN4
//TRISx、ANSELを使って、所望のポートをアナログ入力とする。
//初期化:ADCON1,ADCON0を初期化
//Regi Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
//ADCON0: - CHS4 CHS3 CHs1 CHs2 CHS0 GO/DONE ADON
//ADCON1:ADFM ADCs1 ADCs2 ADCS0 - ADNREF ADPREF1 ADPREF0
//CHS<4:0>=b00100=AN4
//ADFM(A/D Result Format Select bit)=1, Right justified
//ADSCS<2:0>: AD変換クロックの選択。A/D Acquisition timeから適切なクロックを選択
// TAD(AD変換クロック)の周期が1us~9usになるように設定。
// 8Mhzクロックの場合、1/8でTAD=1us、1/16 (101)でTAD=2us。
// 余裕を見て、FOSC/16に設定
//ADNREF:VREF-の設定。0でVss
//ADPREF<1:0>:VREF+の設定。00でVDD
//ADON=1で、AD変換有効化
//GO=1にすると、AD変換開始。変換完了すると0になる。
// スペックではConversion Time=11*TAD ということ。
//アナログ入力設定:RA4=AN4
ADCON0 = 0b00010000; //AN4を選択
ADCON1 = 0b11010000; //揃え、ADCクロック周期=2us、VDD/GNDでADC
}
/*
* メイン
*/
int main(int argc, char** argv) {
unsigned char scan4;
int LEDnum, LEDsub;
int ss; //何秒の所のLEDか
int ii;
//とりあえずhh:mmに初期化。時刻設定できるようになったので、00:00に。
for (ii = 0; ii < 6; ii++) TimeDigit[ii] = 0;
//AlarmTimeの初期化
for (ii = 0; ii < 4; ii++) AlarmTime[ii] = 0;
//変数の初期化
Cnt = 0; //1s間に10回カウンター値クリア
Mode = 0;
PatternMode = 0;
BrightPos = 1;
PatternPos = 0;
ScanTimer2 = 0;
ss = 0;
LEDnum = 0;
LEDsub = 0;
PreviousTimerAdjust = ReadAdjustData();
//設定範囲外の時は、EEPROMの初期状態のFFFFで-1だが、設定されたものか区別不可
if (abs(PreviousTimerAdjust) > 999) TimerAdjust = 0;
else TimerAdjust = PreviousTimerAdjust;
SetTimerAdjust(); //通常、調整時しかこの関数呼び出されない
initialize();
// AlarmSound(); //デバッグ用に音が鳴るように。
//メイン処理
while (1) {
//1秒(1s = 100ms * 10回) Cntは、100ms毎にカウントアップする
//100ms毎にカウントアップ
if (Cnt >= 10) {
Cnt = 0; //10回カウンター値クリア
time1sUp();
ss = (TimeDigit[4] * 10 + TimeDigit[5]) / 5; //12個のLEDのどこに相当する秒か
//時刻設定時も時計更新しても良いかも 11/6
//黒ボタンを押さずに放っておいた時、表示が進んでいないが、ボタン押した
//瞬間更新されるので。
//AdjustTimerの時は、表示が時間でないので、それはダメ。
if ((Mode == ModeNormal) || (Mode == ModeSetClock)) {
SetLEDreg(TimeDigit);
}
}
if (Mode == ModeDemo) {
Hard_check(LEDnum, LEDsub); //LEDnum=0~3、4~5、LEDsub=0~7、0~5
LEDsub++;
if (((LEDnum > 3) && (LEDsub > 5)) || (LEDsub > 7)) {
LEDsub = 0;
LEDnum = (LEDnum + 1) % 6;
}
} else {
if (FlagT2 != 0) {
FlagT2 = 0;
LATA = 0xFF; //一旦全LEDを消灯させる
LATB = LATB | 0xF7; //PB3のみサウンド用に状態保持
//周辺のLEDは10回に1回のみ
if (ScanTimer2 == 0) LEDset(0, (1 << ss));
else if (ScanTimer2 == 1) LEDset(1, (1 << (ss - 6)) | 0xc0);
else {
//7セグは、ScanTimer2=0~1を除き、10スキャン中2回光らせ、明るめにする
scan4 = ScanTimer2 % 4;
// scan4:どの桁 時間の10の位:3、分の1の位:0
LATA = LEDdata[scan4][0];
LATB = LEDdata[scan4][1] & (~MASKPB3) | (LATB & MASKPB3);
}
}
}
if (flagT4) {
flagT4 = 0;
//ここで入力ボタンのチェックをする。30ms毎
Read_button();
Judge_state();
}
}
return (EXIT_SUCCESS);
}
この記事へのコメント