第12回 ADコンバータ(6)〜チャレンジ課題〜

今回はADコンバータを利用したチャレンジ課題です。

目次

チャレンジ課題

❶ ADコンバータの設定を変えてみる

前回の記事で作成したプログラムでは、ADコンバータをの設定のところで、結果の値をADRESに右詰めにするようにしました。

この設定を左詰にすると、プログラムをどのように変更すればよいか検討してみてください。左詰にする場合、ADCON1レジスタの設定を以下のように変更します。

ADCON1 = 0b00000000;  // 結果数値は左寄せ、ADコンバータクロックはFOSC/2、基準電圧はVDD

このように変更すると、ADRESHの値を8ビットの数値として見ることができます。

判定する数値は8ビット(0〜255)になり、判定範囲が変わりますので、その対応も必要です。

❷ プリセットタイマー時間から選択する

電源をONにした場合は、例えば5分タイマーとします。

この状態で、例えば黒のスイッチを押すと3分、白いスイッチを押した場合は10分、というように黒と白のスイッチの動作をプリセット値から選択するように変更してみてください。

今までは、黒のスイッチと白のスイッチはタイマー時間の値を増減する機能としていましたが、ここではプリセット値を選択するスイッチ、という機能にしてみたいと思います。


次回から、EEPROM機能を利用します。

チャレンジ課題解答例

❶ ADコンバータの設定を変えてみる

ADコンバータの結果を左詰めに変更すると、結果の10ビットの値を、上位8ビットの値で判断することになります。

10進数では、0〜1023の値を0〜255の値で判定することになります。

今回は、次のように255を4等分して判定基準を設定してみました。

スイッチ状態8ビットの判定基準
黒スイッチON
(少ない方を選択するスイッチ)
0 〜 63
白スイッチON
(多い方を選択するスイッチ)
64 〜 191

どのスイッチが押されたか判定するには、ADRESHレジスタの値をこの判定基準に変更すれば良いことになります。

プログラムは次のように変更してみました。

/*
 * PICマイコン電子工作入門 応用編 第12回
 *   チャレンジ課題(1)解答例
 */

#include <xc.h>

// PIC12F1822 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = OFF     // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)


// クロック周波数指定
// __delay_ms()関数が使用する
#define _XTAL_FREQ 1000000

void main(void) {

    // PICマイコン設定
    OSCCON = 0b01011010;  // 内部クロック周波数を1MHzに設定
    ANSELA = 0b00000100;  // RA2ピンをアナログ、それ以外のピンはデジタルに設定
    TRISA  = 0b00001100;  // RA2とRA3を入力、それ以外は出力に設定(RA3ピンは設定にかかわらず入力ピン)

    // ADコンバータ設定
    ADCON0 = 0b00001001;  // RA2(AN2)をADコンバータピンに設定し、ADコンバータ機能をEbableにする
    ADCON1 = 0b00000000;  // 結果数値は左寄せ、ADコンバータクロックはFOSC/2、基準電圧はVDD

    // タイマー時間保持用の変数(動作確認しやすいようにデフォルトは5秒)
    uint16_t timerValue = 5;

    // ブザーをOFF、LEDをONにする
    LATA4 = 0; // ブザー
    LATA5 = 1; // LED

    // スイッチが押されるまで、タイマー時間増減スイッチ処理を行う
    while( RA3 ) {
        // ADコンバータ読み取り
        GO = 1;     // 読み取り依頼
        while(GO) { // 読み取り完了待ち
        }
        
        // ADRESの数値に応じてタイマー時間の増減を行う
        //
        // タイマー時間を減らすスイッチの処理(ADRESHが64未満の場合)
        if( ADRESH < 64 ) {
            // タイマー時間を減らす(マイナスにならないようにする)
            if( timerValue > 0 ) {
                timerValue--;
            }

            // タイマー時間をLEDの点滅回数で表現する
            //
            // 少しの間LEDをOFFにする
            LATA5 = 0;
            __delay_ms(500);
            
            // 設定時間分LEDを点滅する
            for(uint16_t i=0; i<timerValue; i++) {
                LATA5 = 0;
                __delay_ms(200);
                LATA5 = 1;
                __delay_ms(200);
            }

            // 少しの間LEDをOFFにする少しの間LEDをOFFにする
            LATA5 = 0;
            __delay_ms(800);

            // LEDを点灯する
            LATA5 = 1;            
        } else {
            // タイマー時間を増やすスイッチの処理(ADRESHが64以上192未満)
            if( ADRESH < 192 ) {
                // タイマー時間を増やす(上限チェックはしない)
                timerValue++;
                
                // タイマー時間をLEDの点滅回数で表現する
                LATA5 = 0;
                __delay_ms(500);
                
                for(uint16_t i=0; i < timerValue; i++) {
                    LATA5 = 0;
                    __delay_ms(200);
                    LATA5 = 1;
                    __delay_ms(200);
                }
                
                LATA5 = 0;
                __delay_ms(800);
                
                LATA5 = 1;
            }
        }
    }
    
    // LEDをtimerValue分点滅する
    for(uint16_t timer=0; timer<timerValue; timer++) {
        // LEDを950ms消灯する
        LATA5 = 0;
        __delay_ms(950);            
        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);
    }

    // アラーム音を3回鳴らす
    for(uint8_t i=0; i<3; i++) {
        // ピッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        __delay_ms(70);
        // ピッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        __delay_ms(70);
        // ピーッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        // 1回分の音パターンの間を少し空ける
        __delay_ms(800);
    }

    // LEDをPWM制御してスムーズな点滅制御をする
    //
    // PWM機能のピン割り当て設定
    APFCONbits.CCP1SEL = 1;     // PWM機能をRA5ピンに設定
    CCP1CONbits.CCP1M = 0b1100; // PWM機能を有効、active-highに設定
    CCP1CONbits.P1M = 0b00;     // RA2ピンはGPIOに設定

    // 周期とデューティーサイクルの設定
    //   周期は1ms
    //   デューティーサイクルは100%から開始するので1msに設定
    T2CONbits.T2CKPS = 0b00;    // プリスケーラ値を1に設定
    PR2 = 249;                  // 周期を1msに設定 (249 + 1) x 4 x 1us = 1000us = 1ms
    CCPR1L = 1000/4;            // デューティーサイクルを0.5msに設定
    CCP1CONbits.DC1B = 1000 & 0b11;

    // PWM制御スタート
    T2CONbits.TMR2ON = 1;

    // LEDをPWM制御
    // デューテー比は10%〜100%の制御にする
    while(1) {
        // 100% -> 5%の制御
        for(uint16_t duty=1000; duty>=50; duty--) {
            CCPR1L = (uint8_t) (duty / 4);    // 上位8ビット
            CCP1CONbits.DC1B = duty & 0b11; // 下位2ビット
            __delay_ms(1);
        }
        // 5% -> 100%の制御
        for(uint16_t duty=50; duty<=1000; duty++) {
            CCPR1L = (uint8_t) (duty / 4);    // 上位8ビット
            CCP1CONbits.DC1B = duty & 0b11; // 下位2ビット
            __delay_ms(1);
        }
    }

    // 以下の命令は実行されない
    return;
}

❷ プリセットタイマー時間から選択する

動作確認のため、次の仕様でプログラムを作成することにします。

スイッチ状態タイマー時間
デフォルト
(スイッチOFF)
5秒
黒スイッチON
(少ない方を選択するスイッチ)
3秒
白スイッチON
(多い方を選択するスイッチ)
10秒

課題❶のプログラムを流用して、黒スイッチが押された時はタイマー時間を3秒に、白スイッチが押された時はタイマー時間を10秒に設定すれば良いので、それぞれのスイッチ処理でタイマー時間を計算する部分を上の表に従って設定するようにしてみました。

なお、スイッチを押した時のLEDによる時間表現ですが、短い方に設定したときは短くLEDが点滅し、長い方に設定した時は長く点滅するようにしてみました。

いいアイデアが思いつかなかったので、この辺りは工夫が必要かもしれませんね…

/*
 * PICマイコン電子工作入門 応用編 第12回
 *   チャレンジ課題(2)解答例
 */

#include <xc.h>

// PIC12F1822 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = OFF     // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)


// クロック周波数指定
// __delay_ms()関数が使用する
#define _XTAL_FREQ 1000000

void main(void) {

    // PICマイコン設定
    OSCCON = 0b01011010;  // 内部クロック周波数を1MHzに設定
    ANSELA = 0b00000100;  // RA2ピンをアナログ、それ以外のピンはデジタルに設定
    TRISA  = 0b00001100;  // RA2とRA3を入力、それ以外は出力に設定(RA3ピンは設定にかかわらず入力ピン)

    // ADコンバータ設定
    ADCON0 = 0b00001001;  // RA2(AN2)をADコンバータピンに設定し、ADコンバータ機能をEbableにする
    ADCON1 = 0b00000000;  // 結果数値は左寄せ、ADコンバータクロックはFOSC/2、基準電圧はVDD

    // タイマー時間保持用の変数(動作確認しやすいようにデフォルトは5秒)
    uint16_t timerValue = 5;

    // ブザーをOFF、LEDをONにする
    LATA4 = 0; // ブザー
    LATA5 = 1; // LED

    // スイッチが押されるまで、タイマー時間増減スイッチ処理を行う
    while( RA3 ) {
        // ADコンバータ読み取り
        GO = 1;     // 読み取り依頼
        while(GO) { // 読み取り完了待ち
        }
        
        // ADRESの数値に応じてタイマー時間の増減を行う
        //
        // タイマー時間を減らすスイッチの処理(ADRESHが64未満の場合)
        if( ADRESH < 64 ) {
            // タイマー時間を少ない方に設定する
            if( timerValue > 0 ) {
                timerValue = 3;

                // タイマー時間をLEDの点滅回数で表現する
                //   短く点滅することにより表現してみました
                //
                // 最初にいったんLEDを消す
                LATA5 = 0;
                __delay_ms(800);

                // 短く2回点滅する
                LATA5 = 1;
                __delay_ms(50);
                LATA5 = 0;
                __delay_ms(100);
                LATA5 = 1;
                __delay_ms(50);
                LATA5 = 0;

                // 少し待ってからLEDを点灯する
                __delay_ms(800);
                LATA5 = 1;
            }
        } else {
            // タイマー時間を増やすスイッチの処理(ADRESHが64以上192未満)
            if( ADRESH < 192 ) {
                // タイマー時間を多い方に設定する
                timerValue = 10;
                
                // タイマー時間をLEDの点滅回数で表現する
                //   短く点滅することにより表現してみました
                //
                // 最初にいったんLEDを消す
                LATA5 = 0;
                __delay_ms(800);

                // 短く2回点滅する
                LATA5 = 1;
                __delay_ms(500);
                LATA5 = 0;
                __delay_ms(500);
                LATA5 = 1;
                __delay_ms(500);
                LATA5 = 0;

                // 少し待ってからLEDを点灯する
                __delay_ms(800);
                LATA5 = 1;            
            }
        }
    }
    
    // LEDをtimerValue分点滅する
    for(uint16_t timer=0; timer<timerValue; timer++) {
        // LEDを950ms消灯する
        LATA5 = 0;
        __delay_ms(950);            
        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);
    }

    // アラーム音を3回鳴らす
    for(uint8_t i=0; i<3; i++) {
        // ピッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        __delay_ms(70);
        // ピッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        __delay_ms(70);
        // ピーッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        // 1回分の音パターンの間を少し空ける
        __delay_ms(800);
    }

    // LEDをPWM制御してスムーズな点滅制御をする
    //
    // PWM機能のピン割り当て設定
    APFCONbits.CCP1SEL = 1;     // PWM機能をRA5ピンに設定
    CCP1CONbits.CCP1M = 0b1100; // PWM機能を有効、active-highに設定
    CCP1CONbits.P1M = 0b00;     // RA2ピンはGPIOに設定

    // 周期とデューティーサイクルの設定
    //   周期は1ms
    //   デューティーサイクルは100%から開始するので1msに設定
    T2CONbits.T2CKPS = 0b00;    // プリスケーラ値を1に設定
    PR2 = 249;                  // 周期を1msに設定 (249 + 1) x 4 x 1us = 1000us = 1ms
    CCPR1L = 1000/4;            // デューティーサイクルを0.5msに設定
    CCP1CONbits.DC1B = 1000 & 0b11;

    // PWM制御スタート
    T2CONbits.TMR2ON = 1;

    // LEDをPWM制御
    // デューテー比は10%〜100%の制御にする
    while(1) {
        // 100% -> 5%の制御
        for(uint16_t duty=1000; duty>=50; duty--) {
            CCPR1L = (uint8_t) (duty / 4);    // 上位8ビット
            CCP1CONbits.DC1B = duty & 0b11; // 下位2ビット
            __delay_ms(1);
        }
        // 5% -> 100%の制御
        for(uint16_t duty=50; duty<=1000; duty++) {
            CCPR1L = (uint8_t) (duty / 4);    // 上位8ビット
            CCP1CONbits.DC1B = duty & 0b11; // 下位2ビット
            __delay_ms(1);
        }
    }

    // 以下の命令は実行されない
    return;
}

更新履歴

日付内容
2017.7.29新規投稿
2025.5.18チャレンジ課題内容変更、解答例追加
通知の設定
通知タイミング
guest
7 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
jh3gpn
jh3gpn
7 年 前

温度・湿度・気圧だけで十分です。
壁掛けで実用になるような大きなディスプレイはないでしょうか?
この先を楽しみにしております。

管理者
管理者
7 年 前

wakazoさま、
プログラミングのご経験がどの程度あるかで、ヒントの内容が異なりますので回答が難しいところですが、以下の前提条件でご回答いたします。

[条件]
 ・以下の主要なC言語の文法は理解している
   -変数型、演算子
   -配列
   -繰り返し構文(while文、for文)
   -条件判断構文(if文、switch文)
   -2進数、16進数

[ヒント]
 ・ADコンバータの設定を変える
   今までADRESの16ビットレジスタを16ビットの変数として扱っていました。レジスタは16ビットですが、ADコンバータの値は10ビットですので、16ビットとしては0〜65535まで表現できますが、ADコンバータとしては0〜1023を使用していました。
   この10ビットの数字を16ビットレジスタに左詰にした場合、ADRSHは、ADコンバータの結果の10ビットのうち上位8ビットがセットされます。10ビットのうち、この上位8ビットは「ADRES >> 2;」とイコールになります。つまり、ADRESに左詰で10ビットをセットした場合、ADRESHはADコンバータの結果の値を4で割った値(2ビット右シフトした値)になっているわけです。0〜1023の値は、0〜255になります。
   ということは、0〜1023のADコンバータの値を例えば500以上で判定する場合、0〜255の値の場合は500の判定値はどうなるか、がポイントになります。

 ・時間表現方法を変える
   プログラム(アルゴリズム)を考える場合、自分が何をしているかじっくり考えるとどのようにプログラムすればわかって来ます。
   1を早い点滅1回、5を長めの点滅1回にしたい場合、具体例で自分がどのような処理をしているか考えてみます。
   例えばタイマーが7秒にセットされている場合、今までのプログラムでは、LEDを7回点滅させていました。では、1秒を早い点滅1回、5秒を長めの点滅1回にしたい場合、7秒はどのような点滅パターンになるかというと、

 1回の長い点滅 + 2回の短い点滅

 になる、というのはすぐにわかると思います。では次に、99秒の点滅バターンはどうなるでしょうか。7秒の場合は感覚的にわかると思いますが、99秒の場合は何か計算式を考える必要がありますよね。

 99秒 = 5秒が◯回 + 1秒が□回
 ※ただし、◯は一番大きい数字にする

 この計算をどうすればよいか考えてみる必要がありそうです。これがわかれば、あとは、

 for文で◯回長い点滅をしたあと、次のfor文で□回短い点滅を□回点滅させれば実現できます。

 ・プリセットタイマー時間から選択する
  今までは、タイマー時間をtimerValueという変数にセットしておき、スイッチが押されたらtimerValueを++したり–したりしていました。
  今回は、1回押されたらあらかじめ設定してあるプリセットの秒数にセットする必要があります。
  このようなケースで一番簡単なのは配列使って実現する方法です。例えば、プリセットの値として、

  presetValue[] = {1, 3, 5, 7, 10};

  などにセットしておき、デフォルトは5秒とすると、配列のインデックスをtimerIndexとすると、timerIndex = 2とすれば、

  presetValue[timerIndex]

  つまり、5がデフォルトの秒数になります。スイッチが押された場合、このtimerIndexを++したり–すればpresetValue[timerIndex]がプリセット値になります。ただし、timerIndexの上限下限がありますので、その範囲を超えないようにする必要もあります。

wakazo
wakazo
返信  管理者
7 年 前

ありがとうございます。
時間を見つけてこつこつやってみたいと思います。

プログラミング経験は、ほぼないです。が用語や文法や2進数、16進数の理解はしている状態なのかなと思います。ただ、今興味がどんどん出てきてはまり始めていると言った感じです。

claynets
claynets
返信  wakazo
7 年 前

wakazoさま、
C言語の文法や2進数、16進数がある程度わかれば、あとは練習あるのみだと思います。

C言語の文法がわかっていても、プログラミングはまた別物、と感じていらっしゃると思います。言葉と一緒で単語や文法を知っていても、言い回しがわからないと話をするのが難しいのと一緒で、プログラミングも色々な考え方を身につけていけば、絶対にオリジナルのプログラムが書けるようになります!

ぜひ色々とチャレンジされてみてください!

wakazo
wakazo
7 年 前

こんばんは、プログラムの経験不足すぎて少し課題がでただけで戸惑っています。もう少し考えかたのヒント、アドバイスがあれば教えて頂けるとありがたいです。よろしくお願いします。

目次