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

マイコンには安価でADも搭載しているPadauk PFS123を使います。書き込み機には、プログラムの書き込みと実行をスイッチ一つで切り替えられるSuper Easy PDK Programmerを使います。
Padaukマイコンは、安価な代わりにIOのドライブ能力が3V時に5mAと貧弱です。しかし、PFS123のPB4,PB5だけは特殊で15mA程度となっています。

そこで、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で販売しています。
コメント