//-------------------------------------------------------------------------- // LEDの表示制御プログラム 16F876 //-------------------------------------------------------------------------- // 2016-05-27 Copyright@VividHobby //------------------------------------------------------------------------- // SW操作による点滅の始動、停止を制御 // PWM出力追加。LOOP遅延を10msecに短縮 v012 // PWMを割り込み処理で実施、RCサーボを制御 v020 // 最終版(SW0のON/OFFでサーボを動作) v022 // サーボの動作速度を可変にする v023 // サーボの制御方法の改良(分解能アップ) v024 // プログラムリストの整理、コメントの見直し v026 // 複数のサーボの制御を独立して行うバージョン v100 2016/06/18 // まずはLEDのみ動作確認 // RA2,3,4、RB0-7はLED点滅専用。RC4-7はサーボ専用。同時動作可能。 v101 // // // // //------------------------------------------------------------------------- //------------------------------------------------------------------------- // プログラム初期設定 //------------------------------------------------------------------------- #include #include #include #pragma config FOSC = HS //高速オシレーター(4MHz以上)使用 #pragma config WDTE = OFF //ウォッチドッグタイマーOFF #pragma config CP = OFF //コードプロテクションOFF #pragma config PWRTE = ON //パワーアップタイマーON #pragma config LVP = OFF//PIC16F84Aでは未サポート //PICのクロック設定 #define _XTAL_FREQ 20000000 //PICのクロックをHzで設定(20MHz) //------------------------------------- // スイッチ用ポートの設定 //------------------------------------- #define sw0 RA0 // RC0にsw0を設定 #define sw1 RA1 // RC1にsw1を設定 //----------------------------------------------------------------------- // 点滅設定定義エリア //----------------------------------------------------------------------- // 注意 // RA0-RA1 外部接続のスイッチポート(専用) // RB0-RB7 LED接続専用ポート(順次、一定、ランダム点滅共通) // RC2 PWM出力専用ポート(ゆっくり点滅) // RC4-RC7 RCサーボ専用ポート(動作目標ポイント、動作速度設定可。同時動作可) // 動作目標ポイントに来たら自動停止。新たな目標ポイント設定で自動始動 // // //----- LED 点滅 ---- // 各ポートごとに点滅するLEDの点滅方法を設定する // //(通常の点滅モード) //[x, y, z]の指定パラメータは、 //配列は、LEDを接続するポートの数あって、それぞれ、{x、y、z}三つの //パラメータを指示するだけです。 //xは、以下の様な意味を持ちます。 // //0=ランダムの点滅 //1から12=順次点滅の順番 //13=定期点滅 // //zは、点滅周期の長さで、今回のプログラムでは、1=100msecに指定しています。 //ランダムで点滅する際は、この値が最大値となるように、周期が変わります。 // //yは、定数で、変更してはいけません。 //初めの、1,2,4,8,16は、ポートAのポート0,1,2,3,4に対応します。 //次の、1,2,4,8,16,32,64,128は、ポートBの0,1,2,3,4,5,6,7に対応します。 // // //----- PWM設定 ------ //(自動モード) //x=14:PWMの制御を指定 //y=0 :PWMのデューティー比を0から100%の間で、自動的に増減させる。 //z=n :デューティー比を増減させる速度を指定。1%あたりnx5msecで増減。 //例:2とすると、2x5msecx100=1000msec、つまり1秒で消灯から最大輝度へ、 //また次の1秒で、消灯へ。ただし、最大値は255。 // //(手動モード) //x=14:同上 //y=1-255:周期を1から255までの間で指定可能。 //現在は、255固定(約0.819ミリ秒=約1220Hz)で使用する。 //デューティー比0?100%制御のため固定とする。 //z=n ;デューティー比を指定。0?100(%)として指定可能。 //プログラム内で、上記の周期に合わせてデューティー0?100%で制御。 // //(注意)使用しないポートにも、何か数値を書き込んでください。 //------- RC サーボ --------- //x=15 //yは変更不可(出力ポートの指定の為固定) //z=サーボ動作目標ポイントの指定(0?250) // RCサーボの制御目標値の変更: // 0-100%で動作範囲を指定する方法から、分解能向上の為、0-250の範囲で // 指定する方法に変更。 // 対象の希望動作範囲に応じて変更する // //----------------------------------------------------------------------- unsigned char parameter[16][3] = {// {1, 4, 10}, // RA2ポート用の設定。以下、RA3-RA4とRB0-RB7と続く(例:順次) {2, 8, 10}, // RA3 (例:順次) {3, 16, 10}, // RA4 (例:順次) {4, 1, 10}, // RB0 (例:順次) {5, 2, 10}, // RB1 (例:順次) {13, 4, 10}, // RB2 (例;定期 周期早い) {13, 8, 20}, // RB3 (例;定期 周期遅い) {0, 16, 10}, // RB4 (例;ランダム) {0, 32, 10}, // RB5 (例;ランダム) {0, 64, 10}, // RB6 (例;ランダム) {0, 128, 10}, // RB7 (例;ランダム) {14, 0, 3}, // PWM PWM周期=255、 Dutyは自動変化(y=0なので) {15, 16, 0}, // RC4  サーボ専用ポート(z=目標位置をセットする) {15, 32, 0}, // RC5  サーボ専用ポート(z=目標位置をセットする) {15, 64, 0}, // RC6  サーボ専用ポート(z=目標位置をセットする) {15, 128, 0} // RC7  サーボ専用ポート(z=目標位置をセットする) }; // ここまで //--------------------------------------------------------------------- // サーボ状態記憶用配列 // {x, y}のパラメータ設定は // x=サーボ動作のステップ(1以上)を設定 // y=current 途中結果保存(変更不可) // //--------------------------------------------------------------------- float sv_para[4][2] = { {1, 0}, // RC4のサーボ {1, 0}, // RC5のサーボ {1, 0}, // RC6のサーボ {1, 0} // RC7のサーボ }; //----------------------- 配列から読み込む一時変数 ------------------------------------------- float target = 0, step = 1, current = 0; //サーボ動作のステップ値の定義、現在のサーボ動作数 // このstepを加減することでサーボの動作速度を調整できる //-------------------------ここまで------------------------------------------ //------------------------------------------------------------------------ // 変数初期設定 //------------------------------------------------------------------------ unsigned char portcnt, npc; //ポートカウンターとサーボ様ポートカウンター unsigned char smax; unsigned char scnt; unsigned char led_cnt[14] = {0}; //ポート毎のLED点滅周期のカウント値を記憶初期値0 unsigned char sw0v, sw1v; //swで操作するポートの初期値を記憶する unsigned char updw = 0; int i; int duty, freq; //デューティー比と周期を格納 int servo; //サーボ設定パラメータ unsigned char svv; signed int m, n; //------------------------------------------------------------------------ // 関数定義 //------------------------------------------------------------------------ void serial_max(void); //シリアル点滅のポート数を計算する static void interrupt tmr0_int(void); //割り込み処理ルーチン void rndm(void); //ランダム点滅ルーチン void regular(void); //定期点滅ルーチン void seri(void); //順次点滅ルーチン void port_out(void); //指定ポートの点滅 void port_off(void); void port_on(void); void port_pwm(void); void PWM_setup(void); void sv_on(void); //サーボ出力ポートをONにし、TMR0割り込みを設定する //------------------------------------------------------------------------- // メインプログラム //------------------------------------------------------------------------- main() { //--------------------------------------------- // PICのPORT設定の初期化 //--------------------------------------------- //PICのポート設定 ADCON0 = 0b10000000; //アナログ使用しない ADCON1 = 0b00000110; //For degital I/O TRISA = 0b00000011; //ポートA0とA1はスイッチ入力用 TRISB = 0b00000000; TRISC = 0b00000000; // RC4-7はサーボ専用 PORTA = 0b11111111; // ポートの初期値を設定する(Hで通常、LでON) PORTB = 0b11111111; PORTC = 0b11111111; //---------------------------------------------------------------- // TMR0設定変更 // TRM0 setting 割り込み使用、プリスケーラ1/32設定@20MHz // 割り込みは約1.6384 msec毎に発生する //---------------------------------------------------------------- OPTION_REG = 0b10000100; /// Prescaller 1/32 (1.6384 msec full count) INTCON = 0b00100000; GIE = 1; // 全体割り込み許可 T0IE = 0; // TMR0 タイマー割り込み一時禁止 T0IF = 0; // TMR0 割り込みフラッグクリア //---------------------------------------------------------------- // 変数初期値設定 //---------------------------------------------------------------- portcnt = 0; scnt = 1; smax = 0; PWM_setup(); //PWM初期設定 RA2 = 0; __delay_ms(2000); //PIC動作確認用として、RA2のLEDを点滅させる RA2 = 1; __delay_ms(2000); //---------------------------------------------------------------- // メイン処理開始 //---------------------------------------------------------------- serial_max(); //シリアル設定の最大個数計算 while (1) { //--------------------------------------------- //スイッチの状態をチェックして配列データを変更する //---------------------------------------------- //---------------------------- //SWでサーボの角度を切り替える // サーボの動作は0から250にする //---------------------------- //注意---------------------------------------------------------------- // TMR0はカウントアップ->オーバーフロー型のタイマーなので // 設定するパラメータ(0-255)が小さければ、オーバーフローまでの // 時間が長く、割り込みまで時間がかかり、逆にパラメータが大きいと // 割り込みは短時間で発生します。 // 試行錯誤の結果、パルス立ち上がり時のDelay関数の値は、660マイクロ秒 // TMR0のパラメータは、0-250でサーボが、ほぼフルスイングする様に // なりました。 //--------------------------------------------------------------------- //--------------------------------------------------------------- // スイッチ操作によるサーボの動作確認用設定 //--------------------------------------------------------------- if (sw0 == 0)parameter[14][2] = 0; //SWがON(0)ならサーボパラメータを20に else { parameter[14][2] = 250; //そうでなければ、250にセット } if (sw1 == 0)parameter[15][2] = 0; //SWがON(0)ならサーボパラメータを20に else { parameter[15][2] = 250; //そうでなければ、250にセット } //----------------------------------------------------------- // 配列の設定データにしたがって、LEDを接続したポートをON/OFFする //----------------------------------------------------------- for (portcnt = 0; portcnt <= 15; portcnt++) { if (parameter[portcnt][0] == scnt) seri(); if (parameter[portcnt][0] == 0) rndm(); if (parameter[portcnt][0] == 13) regular(); if (parameter[portcnt][0] == 14) port_pwm(); if (parameter[portcnt][0] == 15) sv_on(); } __delay_ms(5); //処理全体の繰り返し時の待ち時間設定 10msec } } //-------------------------------------------------------------------- // シリアル点滅のポート数を計算する // LEDの接続数は、ポートAとぽーとBで合計3+8=11 // その中で、順番点灯のLEDの数を求める //-------------------------------------------------------------------- void serial_max(void) { m = 0; smax = 0; for (m = 0; m <= 10; m++) { if ((parameter[m][0] >= 1)&(parameter[m][0] <= 10)) { if (smax < parameter[m][0]) smax = parameter[m][0]; } } } //-------------------------------------------------------------------- // ランダム点滅ルーチン // portcntが0-10でコールされなければERROR(それ以外はサーボ処理の為) //-------------------------------------------------------------------- void rndm(void) { if (portcnt >= 11) return; // 誤ってサーボ信号処理で呼ばれたら即戻る if (led_cnt[portcnt] <= 0) { port_out(); //LED出力を反転 n = parameter[portcnt][2]; m = rand() / 5000 * n; led_cnt[portcnt] = m; } else { led_cnt[portcnt]--; } } //-------------------------------------------------------------------- // 定期点滅ルーチン // // portcntが0-10でコールされなければERROR(それ以外はサーボ処理の為) //-------------------------------------------------------------------- void regular(void) { if (portcnt >= 11) return; // 誤ってサーボ信号処理で呼ばれたら即戻る if (led_cnt[portcnt] <= 0) { port_out(); //LED出力を反転 led_cnt[portcnt] = parameter[portcnt][2]; //ポート毎の点滅時間をセット } else { led_cnt[portcnt]--; } } //-------------------------------------------------------------------- // 順次点滅ルーチン // // portcntが0-10でコールされなければERROR(それ以外はサーボ処理の為) //-------------------------------------------------------------------- void seri(void) { // if (portcnt >= 11) return; // 誤ってサーボ信号処理で呼ばれたら即戻る if (led_cnt[portcnt] == 0) { led_cnt[portcnt] = parameter[portcnt][2]; port_on(); } led_cnt[portcnt]--; if (led_cnt[portcnt] == 0) { port_off(); scnt++; if (scnt > smax)scnt = 1; } } //-------------------------------------------------------------------- // 指定ポート出力の反転(配列の最大数を超えない様に) // // portcntが0-10でコールされなければERROR(それ以外はサーボ処理の為) //------------------------------------------------------------------- void port_out(void) { if (portcnt >= 11) return; // 誤ってサーボ信号処理で呼ばれたら即戻る if ((portcnt >= 0) & (portcnt <= 2)) { PORTA = parameter[portcnt][1] ^ PORTA; // portcntが0-2ならPORTA } if ((portcnt >= 3) & (portcnt <= 10)) { PORTB = parameter[portcnt][1] ^ PORTB; // portcntが3-10ならPORTB } if ((portcnt >= 12) & (portcnt <= 15)) { PORTC = parameter[portcnt][1] ^ PORTC; // portcntが12-15ならPORTC サーボ } } //-------------------------------------------------------------------- // 指定ポート出力をOFF(配列の最大数を超えない様に) // この関数はLED制御とサーボ制御の双方から利用される //------------------------------------------------------------------- void port_off(void) { // if (portcnt >= 11) return; // 誤ってサーボ信号処理で呼ばれたら即戻る if ((portcnt >= 0) & (portcnt <= 2)) { PORTA = parameter[portcnt][1] | PORTA; // portcntが0-2ならPORTA } if ((portcnt >= 3) & (portcnt <= 10)) { PORTB = parameter[portcnt][1] | PORTB; // portcntが3-10ならPORTB } if ((portcnt >= 12) & (portcnt <= 15)) { PORTC = parameter[portcnt][1] | PORTC; // portcntが12-15ならPORTC サーボ } } //-------------------------------------------------------------------- // 指定ポート出力をON(配列の最大数を超えない様に) // この関数はLED制御とサーボ制御の双方から利用される //------------------------------------------------------------------- void port_on(void) { // if (portcnt >= 11) return; // 誤ってサーボ信号処理で呼ばれたら即戻る if ((portcnt >= 0) & (portcnt <= 2)) { PORTA = ~(parameter[portcnt][1]) & PORTA; // portcntが0-2ならPORTA } if ((portcnt >= 3) & (portcnt <= 10)) { PORTB = ~(parameter[portcnt][1]) & PORTB; // portcntが3-10ならPORTB } if ((portcnt >= 12) & (portcnt <= 15)) { PORTC = ~(parameter[portcnt][1]) & PORTC; // portcntが12-15ならPORTC サーボ } } //----------------------------------------------------------------------- // PWM処理(自動・手動) //----------------------------------------------------------------------- void port_pwm(void) { if (parameter[portcnt][1] == 0) { //AutoPWM if (updw == 0) { //up led_cnt[portcnt]++; if (led_cnt[portcnt] >= 100) { updw = 1; } } else { led_cnt[portcnt]--; if (led_cnt[portcnt] <= 0) { updw = 0; } } freq = 255; // 自動モードの際は周期は固定(255) PR2 = freq; duty = led_cnt[portcnt]; duty = duty * 255 / 100; CCPR1L = duty; } else { //Manual mode freq = parameter[portcnt][1]; PR2 = freq; duty = parameter[portcnt][2]; duty = duty * 255 / 100; CCPR1L = duty; } } //-------------------------------------------------------------------------- // PWM 初期設定 // 変数設定 freq = 周期、 duty = パルス幅 // 出力 CCP1 = RC2 //-------------------------------------------------------------------------- void PWM_setup(void) { PORTCbits.RC2 = 0; // PWMの出力を設定 PR2 = 0; // PWMの周期をセット CCPR1L = 0; // PWMのパルス幅をセット、ただし初期値はゼロ CCP1CONbits.CCP1X = 0; // パルス幅設定10ビットの下位2ビットはゼロ CCP1CONbits.CCP1Y = 0; CCP1CONbits.CCP1M3 = 1; // PWMモード(M3-M0) CCP1CONbits.CCP1M2 = 1; // CCP1CONbits.CCP1M1 = 0; // CCP1CONbits.CCP1M0 = 0; // T2CONbits.TOUTPS3 = 0; // ポストスケーラ T2CONbits.TOUTPS2 = 0; T2CONbits.TOUTPS1 = 0; T2CONbits.TOUTPS0 = 0; T2CONbits.TMR2ON = 1; // タイマー2スタート( 0=STOP, 1=START) T2CONbits.T2CKPS1 = 1; // プリスケーラ T2CONbits.T2CKPS0 = 1; // 00=1/1, 01=1/4, 10=1/16, 11=1/16 } //---------------------------------------------------------------- // TMR0割り込み処理 // サーボ制御のポートをLに戻す //---------------------------------------------------------------- static void interrupt tmr0_int(void) { port_on(); //サーボ接続ポートをOFF(負論理) T0IE = 0; // タイマー割り込み一時禁止として、すぐに戻る } //------------------------------------------------------------------ // TMR0に割り込み時間をセットして、サーボ信号出力をONに // 660マイクロ秒をDelay関数で待って、 // TMR0に時間をセットして割り込み処理を許可するが、そのまま割り込み // 発生をLOOPで待ち、割り込み検出後に、ポートをOFFにして次の処理にすすむ //------------------------------------------------------------------ void sv_on(void) { if (portcnt <= 11) return; // もしサーボ設定のポートでなければ戻る //----- 配列から作業変数へ読み込み -------------- npc = portcnt - 12;// portcntをnpcに変換してsv_para[]をアクセスする target = parameter[portcnt][2]; //サーボ設定値を読み取り(値=0-255) step = sv_para[npc][0]; // stepをサーボ用パラメータ配列から読み出す current = sv_para[npc][1]; // currentをサーボ用パラメータ配列から読み出す if (target != current) { if (target <= current) current = current - step; //目標値が現在値よりちいさければstepを減算 else current = current + step; //そうでなければ加算 if (current <= 0) current = 0; //下限値チェック if (current >= 250) current = 250; //上限値チェック } //----- 作業変数から配列へもどし ------------------- sv_para[npc][1] = current; // 新しいcurrentを配列にもどす servo = current; //値(0-255)をservoに渡す port_off(); //サーボ接続ポートをON(負論理) __delay_us(660); //サーボ制御パルスの最初の基本部分750マイクロ秒をここで指定する TMR0 = servo; T0IF = 0; // TMR0 割り込みフラッグクリア T0IE = 1; // タイマー割り込み許可 while (T0IF == 0) { // TMR0割り込みが発生するまで待つ } } // -------------------------End of Program --------------------------------