PR 記事には広告が含まれています
Translate

歩行者用信号機のプログラム

スポンサーリンク

信号機の青色LEDを入手したので、歩行者用信号機のようにLEDが点灯するプログラムを作りたいと思います。

スポンサーリンク

マイコン

マイコンには安価でADも搭載しているPadauk PFS123を使います。書き込み機には、プログラムの書き込みと実行をスイッチ一つで切り替えられるSuper Easy PDK Programmerを使います。

Padaukマイコンは、安価な代わりにIOのドライブ能力が3V時に5mAと貧弱です。しかし、PFS123のPB4,PB5だけは特殊で15mA程度となっています。

PFS123のIOピンドライブ能力

そこで、LEDはPB4とPB5に接続しようと思います。

スポンサーリンク

プログラミング

普通の信号

青、青点滅、赤という通常の点灯方法をプログラムしてみます。

歩行者用信号機の回路図

回路図は上記のようになります。PB4とPB5にLEDが接続してあるだけです。プログラムは以下のようになりました。3秒青、3秒青点滅、3秒赤です。

#include <pdk/device.h>
#include <stdint.h>
#include "auto_sysclock.h"
#include "startup.h"
#include "delay.h"

#define CYAN_LED_PIN 4
#define CYAN_LED_ON()   PB &= ~(1 << CYAN_LED_PIN)
#define CYAN_LED_OFF()    PB |= (1 << CYAN_LED_PIN)
#define RED_LED_PIN 5
#define RED_LED_ON()   PB &= ~(1 << RED_LED_PIN)
#define RED_LED_OFF()    PB |= (1 << RED_LED_PIN)

void main() {

    CLKMD &= ~CLKMD_ENABLE_WATCHDOG;    // disable WDT
    PBC |= ( 1 << CYAN_LED_PIN);    //PortB Init
    PBC |= ( 1 << RED_LED_PIN);    //PortB Init
    CYAN_LED_OFF();
    RED_LED_OFF();

    while(1){
        //Cyan turn ON
        CYAN_LED_ON();
        for(uint8_t i=0 ; i<3 ; i++)
        {
            _delay_ms(1000);
        }

        //Cyan Blink
        for(uint8_t i=0 ; i<6 ; i++){
            CYAN_LED_ON();
            _delay_ms(250);
            CYAN_LED_OFF();
            _delay_ms(250);
        }

        //RED turn ON
        RED_LED_ON();
        for(uint8_t i=0 ; i<3 ; i++)
        {
            _delay_ms(1000);
        }
        RED_LED_OFF();
    }

}

// Startup code - Setup/calibrate system clock
unsigned char STARTUP_FUNCTION(void) {

    // Initialize the system clock (CLKMD register) with the IHRC, ILRC, or EOSC clock source and correct divider.
    // The AUTO_INIT_SYSCLOCK() macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC clock source and divider.
    // Alternatively, replace this with the more specific PDK_SET_SYSCLOCK(...) macro from pdk/sysclock.h
    //MISCLVR = MISCLVR_4V;
    PDK_USE_FACTORY_IHRCR_16MHZ();
    PDK_USE_FACTORY_BGTR();
    AUTO_INIT_SYSCLOCK();

    // Insert placeholder code to tell EasyPdkProg to calibrate the IHRC or ILRC internal oscillator.
    // The AUTO_CALIBRATE_SYSCLOCK(...) macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC oscillator.
    // Alternatively, replace this with the more specific EASY_PDK_CALIBRATE_IHRC(...) or EASY_PDK_CALIBRATE_ILRC(...) macro from easy-pdk/calibrate.h

  return 0;   // Return 0 to inform SDCC to continue with normal initialization.
}

LEDの点灯には、レジスタの特定のビットを0にします。消灯する場合には1にします。1にしたり0にしたり、どちらがONでどちらがOFFなのか混乱してしまうため、8行目9行目のように、defineでONとOFFのビット操作を定義しています。こうすることで、ONしたい場合には24行目のようにCYAN_LED_ON();とすればよく、消灯したい場合には34行目のようにCYAN_LED_OFF();とすれば良くなります。

Padaukマイコンのコンパイラが原因なのか、ディレイ関数の_delay_ms(x);は2000以上の値では正常に動作しませんでした。そこで、40行目のように1秒間のディレイを3回実行して3秒としています。

動作させると、上の動画のようになります。信号っぽいですね。

点灯時間を可変にする

先ほどの信号機は青と赤の時間が固定でした。自在に変更できるように、ボリウムで調整できるようにしたいと思います。

時間可変型の信号機の回路図

上の回路図のようにボリウムをPB3に接続し、ADコンバータで電圧を読み取ります。得られた電圧から待ち時間に変換します。

#include <pdk/device.h>
#include <stdint.h>
#include "auto_sysclock.h"
#include "startup.h"
#include "delay.h"
#include "ad.h"

#define CYAN_LED_PIN 4
#define CYAN_LED_ON()   PB &= ~(1 << CYAN_LED_PIN)
#define CYAN_LED_OFF()    PB |= (1 << CYAN_LED_PIN)
#define RED_LED_PIN 5
#define RED_LED_ON()   PB &= ~(1 << RED_LED_PIN)
#define RED_LED_OFF()    PB |= (1 << RED_LED_PIN)

uint32_t cyanOnPeriod = 3000;    //ms
uint32_t cyanBlinkPeriod = 1500;
uint32_t redOnPeriod = 3000;

//Culcrate LED ON Period
void getPeriod()
{
    uint32_t adc = 0;
    adc += (uint32_t)getADC( ADPIN_PB3 );
    adc += (uint32_t)getADC( ADPIN_PB3 );
    adc += (uint32_t)getADC( ADPIN_PB3 );
    adc += (uint32_t)getADC( ADPIN_PB3 );
    adc = adc * adc;
    adc >>= 14;
    adc = adc + 2000;

    cyanOnPeriod = adc;
    cyanBlinkPeriod = adc /2;
    redOnPeriod = adc;
}

void main() {

    CLKMD &= ~CLKMD_ENABLE_WATCHDOG;    // disable WDT
    PBC |= ( 1 << CYAN_LED_PIN);    //PortB Init
    PBC |= ( 1 << RED_LED_PIN);    //PortB Init
    CYAN_LED_OFF();
    RED_LED_OFF();
    initADPin( ADPIN_PB3 );

    uint32_t loopTime = 0;
    while(1){
        //Cyan turn ON
        CYAN_LED_ON();
        loopTime = 0;
        while( cyanOnPeriod > loopTime )
        {
            _delay_ms(100);
            loopTime += 100;
            getPeriod();
        }

        //Cyan Blink
        loopTime = 0;
        while( cyanBlinkPeriod > loopTime )
        {
            CYAN_LED_ON();
            _delay_ms(250);
            CYAN_LED_OFF();
            _delay_ms(250);
            loopTime += 500;
            getPeriod();
        }

        //RED turn ON
        RED_LED_ON();
        loopTime = 0;
        while( redOnPeriod > loopTime )
        {
            _delay_ms(100);
            loopTime += 100;
            getPeriod();
        }
        RED_LED_OFF();
    }

}

// Startup code - Setup/calibrate system clock
unsigned char STARTUP_FUNCTION(void) {

    // Initialize the system clock (CLKMD register) with the IHRC, ILRC, or EOSC clock source and correct divider.
    // The AUTO_INIT_SYSCLOCK() macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC clock source and divider.
    // Alternatively, replace this with the more specific PDK_SET_SYSCLOCK(...) macro from pdk/sysclock.h
    //MISCLVR = MISCLVR_4V;
    PDK_USE_FACTORY_IHRCR_16MHZ();
    PDK_USE_FACTORY_BGTR();
    AUTO_INIT_SYSCLOCK();

    // Insert placeholder code to tell EasyPdkProg to calibrate the IHRC or ILRC internal oscillator.
    // The AUTO_CALIBRATE_SYSCLOCK(...) macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC oscillator.
    // Alternatively, replace this with the more specific EASY_PDK_CALIBRATE_IHRC(...) or EASY_PDK_CALIBRATE_ILRC(...) macro from easy-pdk/calibrate.h

  return 0;   // Return 0 to inform SDCC to continue with normal initialization.
}

getPeriod()関数が、AD値から点灯時間に変換する関数です。

23〜26行目のgetADCでAD変換値を取得します。4回加算することでノイズを軽減しています。AD値を2乗して、右14bitシフトして、2000を加算することで、いい感じの点灯時間になるように値を算出しています。

上の動画が動作の様子です。ボリウムを調整することで、短い時間から長い時間へと調整できることがわかります。最短で2秒、最長で20秒程度になりました。

押しボタン式の信号

続いて、押しボタン式の信号機にしてみます。

押しボタン式の歩行者用信号機の回路図

PB2にスイッチを追加します。

スイッチが押されるまでは赤が点灯し、押されたら青、しばらくすると青点滅、その後赤に戻ります。

#include <pdk/device.h>
#include <stdint.h>
#include "auto_sysclock.h"
#include "startup.h"
#include "delay.h"
#include "ad.h"

#define CYAN_LED_PIN 4
#define CYAN_LED_ON()   PB &= ~(1 << CYAN_LED_PIN)
#define CYAN_LED_OFF()    PB |= (1 << CYAN_LED_PIN)
#define RED_LED_PIN 5
#define RED_LED_ON()   PB &= ~(1 << RED_LED_PIN)
#define RED_LED_OFF()    PB |= (1 << RED_LED_PIN)

#define BUTTON_PIN 2
#define BUTTON_STATES() ( PB & (1 << BUTTON_PIN) ) == 0
#define BUTTON_PRESS 1
#define BUTTON_RELEASE 0

uint32_t cyanOnPeriod = 3000;    //ms
uint32_t cyanBlinkPeriod = 1500;
uint32_t redOnPeriod = 3000;

void getPeriod()
{
    uint32_t adc = 0;
    adc += (uint32_t)getADC( ADPIN_PB3 );
    adc += (uint32_t)getADC( ADPIN_PB3 );
    adc += (uint32_t)getADC( ADPIN_PB3 );
    adc += (uint32_t)getADC( ADPIN_PB3 );
    adc = adc * adc;
    adc >>= 14;
    adc = adc + 2000;

    cyanOnPeriod = adc;
    cyanBlinkPeriod = adc /2;
    redOnPeriod = adc;
}

void main() {

    CLKMD &= ~CLKMD_ENABLE_WATCHDOG;    // disable WDT
    PBC |= ( 1 << CYAN_LED_PIN);    //PortB Init for LED
    PBC |= ( 1 << RED_LED_PIN);    //PortB Init for LED
    CYAN_LED_OFF();
    RED_LED_OFF();

    initADPin( ADPIN_PB3 );
    PBC &= ~(1 << BUTTON_PIN);  //portB Init for button INPUT
    PBPH |= ( 1 << BUTTON_PIN); //pullup
    uint8_t pbdierReg = 0;
    pbdierReg = ( 1 << BUTTON_PIN);   //input
    pbdierReg &= ~(1 << 4); //for AD PB4
    PBDIER = pbdierReg; //PBDIERはリードできないので一括で設定する必要がある PBDIER can't read. Need to set one time.

    uint32_t loopTime = 0;
    while(1){
        //Red trun ON
        CYAN_LED_OFF();
        RED_LED_ON();

        while(BUTTON_STATES() != BUTTON_PRESS)
        {
            _delay_ms(10);
        }

        //Cyan turn ON
        CYAN_LED_ON();
        RED_LED_OFF();
        loopTime = 0;
        while( cyanOnPeriod > loopTime )
        {
            _delay_ms(100);
            loopTime += 100;
            getPeriod();
        }

        //Cyan Blink
        loopTime = 0;
        while( cyanBlinkPeriod > loopTime )
        {
            CYAN_LED_ON();
            _delay_ms(250);
            CYAN_LED_OFF();
            _delay_ms(250);
            loopTime += 500;
            getPeriod();
        }

    }

}

// Startup code - Setup/calibrate system clock
unsigned char STARTUP_FUNCTION(void) {

    // Initialize the system clock (CLKMD register) with the IHRC, ILRC, or EOSC clock source and correct divider.
    // The AUTO_INIT_SYSCLOCK() macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC clock source and divider.
    // Alternatively, replace this with the more specific PDK_SET_SYSCLOCK(...) macro from pdk/sysclock.h
    //MISCLVR = MISCLVR_4V;
    PDK_USE_FACTORY_IHRCR_16MHZ();
    PDK_USE_FACTORY_BGTR();
    AUTO_INIT_SYSCLOCK();

    // Insert placeholder code to tell EasyPdkProg to calibrate the IHRC or ILRC internal oscillator.
    // The AUTO_CALIBRATE_SYSCLOCK(...) macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC oscillator.
    // Alternatively, replace this with the more specific EASY_PDK_CALIBRATE_IHRC(...) or EASY_PDK_CALIBRATE_ILRC(...) macro from easy-pdk/calibrate.h

  return 0;   // Return 0 to inform SDCC to continue with normal initialization.
}

スイッチ入力にするにあたり、はまった点があるのでメモしておきます。デジタル入力に設定するためには、PBDIERの該当ビットを1にセットする必要があります。また、AD入力にするためには該当ビットを0にする必要があります。通常、プログラム的にわかりやすいように、それぞれの初期化のところでPBDIERを設定すると思います。

入力ピンにするために、PBDIER |= ( 1 << BUTTON_PIN );と書いて、AD入力ピンの設定として、PBDIER &= ~(1 << AD_PIN) というような感じです。

しかし、PBDIERレジスタはライトオンリーのレジスタで、レジスタの内容が何であれリードすると0が返ってきます。このため、先程の書き方ですと、2回目のPBDIERレジスタの操作の PBDIER &= ~(1 << AD_PIN)は、リードした値0に該当ビットを0にする操作するのでPBDIERには0が代入されてしまいます。1回目の操作の1をセットしたことは無かったことになってしまいます。

このため面倒ですが51〜54行目のように、PBDIERは全てのビットを一括で設定しなければいけません。

入力ピンの判定は、16行目のように( PB & (1 << BUTTON_PIN) ) == 0として、該当ビットが0だったら1を返すコードをdefineで定義しておきます。そして、押したか押されていないか、どちらが0でどちらが1だかわからなくなるので、17,17行目のように、押されたか押されていないかを定義しておきます。

こうすことで、62行目のように入力ピンの状態に対する条件分岐が、わかりやすくなります。

スイッチを押すと、信号が青に切り替わることがわかります。

プログラム全体は上のリンクからダウンロードできます。

スポンサーリンク

プログラムが完成しました

歩行者用の信号機のプログラムが一通り完成しました。

次回は信号機の形をしたケースを作りたいと思います。

信号機の青色LEDはkohacraftのshopで販売しています。