![]() |
完成後、CPUもブレッドボード上へ移動 |
まぁ、またデジタル時計なんですけど。
使ったCPUはMSP430G2553ですが、開発環境はArduino互換なので、RTCを用意すれば簡単に移植することができます。ただ、nanoを使ったとしても消費電流を押さえるのはシンドいと思います。
■仕様■
- 表示は4桁7セグメント赤色LED
- お風呂なので電源は電池
- 電池は1ヶ月ぐらいもたせたい
- 時刻と入浴時間を交互に表示
■実装■
省電力で4桁の7セグを動かせる・・・ということで、CPUにはMSP430G2553を選びます。RTCは使わず、G2553に32768hzのXtalを追加して1秒ごとに割り込みを発生させて時計代わりにします。
さすがにLEDを常時点灯していては電池がもたないので、入浴時のみ表示するようにします。入浴中であることを検出するには、
- 倒立スイッチ、風呂に入ったら時計を立てると点灯→一番簡単
- PIRセンサーで人の動きを検出→簡単だけどお風呂のお湯で誤動作するかも?
- 照度センサで明かりを付けたら動作開始
- ドップラーレーダーで(ry
などいろいろ考えたのですが、ここはAPDS9930という照度・近接センサーを使います。ebayで「gesture sensor」って書いてあったのでAPDS9960みたいなことができるのかと思って買ったら単なる照度・近接センサーだった、というオチ。9960より劇的に安かったんですが、典型的な安物買いの銭失いでした。でも、寝かせて置くのももったいないので、使います。それに携帯電話用のデバイスなので、CDSなどで明るさを検知するよりも電力を消費しません(アナログの達人ならできるのかもしれないけど、動作時の消費電流250μA/待機時90μA,Sleep時2.2μAなんて回路を作るのは私にゃ無理)。
大きな声ではいえませんが、電流制限抵抗などは一切入れてません。Vf=1.8vの赤色LEDを使う場合には3.3v - 1.8v = 1.5v、5mAも流せば十分なので1.5 / 0.005 = 300Ωぐらいの抵抗をG2553とLEDのセグメント側の間に入れておくのが正しい使い方です。正しくない使い方をしているので「1」を表示している時と「8」を表示している時で明るさが異なってしまう(各桁を接続している端子に7セグメント分の電流が集中するので「8」のときは容量が足りなくなってしまう)のですが、まぁ自分用なので壊れなければOK。そのうちボード起こしたら抵抗入れるから、それまでがんばれG2553。
G2553への書き込みにはTIのLauchPadを使います。
ソフトはArduino互換のEnergia IDEを使って書きます。くれぐれもバージョン0101E0017と0101E0016は使わないようにお気をつけてください。
以下配線表。各セグメント7本と桁4本、電源、I2Cだけです。時計合わせはそのうち考えます。hourとminuteの間にコロンが点灯する表示器が欲しい(こないだaitendoで買ったら全品不良で点灯しなかった)。
MSP430G2553 | 相手 | ピン | 備考 |
P1_4 | 7セグLED | 11 | Seg A |
P1_5 | 7 | Seg B | |
P2_0 | 4 | Seg C | |
P2_5 | 2 | Seg D | |
P2_2 | 1 | Seg E | |
P2_3 | 10 | Seg F | |
P2_4 | 5 | Seg G | |
P0_0 | 12 | Dig 1 | |
P0_1 | 9 | Dig 2 | |
P1_2 | 8 | Dig 3 | |
P1_3 | 6 | Dig 4 | |
P1_7 | APDS9930 | SDA | |
P1_6 | SCL | ||
P2_6 | XTAL | ||
P2_7 | XTAL |
この他、電池の赤をブレッドボードの赤、黒をブレッドボードの青、G2553のVccとAPDS9930のVCCをブレッドボードの赤、G2553のGndとAPDS9930のGNDをブレッドボードの青、にそれぞれ接続します。
■ハマり■
他のところに書いたネタもありますが:
- 別のところ(Energia最新版でI2Cが動かない)に書いたけど最新のEnergiaを使ったらI2Cが動かなくなってしまった。2つ前のビルドをダウンロードしたら何事もなかったかのように動いた。うがあ。
- 作業中にミスで電源逆接とG2553逆差しで2個トバしてしまった。
- シリアルポートをGPIOでも使うとSerial.printなどを使った瞬間にリセットがかかる。まぁ当たり前なんですが……ライブラリの中のSerial.printで発症したために「ライブラリを呼ぶとフリーズする」という症状になり、ライブラリの問題か?と誤認してしまったために発見が遅れました。いやはや。
- 明暗のスレッショルド設定にちょっと苦労しました。最終的にはセンサーからの値をLEDに表示させるプログラムを書いていろんんな向きで値を測定して閾値を決めました。やっぱ現場に行かんとダメやで。
- 風呂場の明かりをつけているのに時計が消灯してしまうことがあって当初は上記閾値の問題かと思ったのですが、どうやらセンサーがLED電球の高速点滅の「滅」を拾ってしまうようで照明の明るさ/閾値設定とは関係なく発生します。なので、平均値を取るようにしました。ただ、センサーの値は1秒に1回サンプリングしており点灯時の明るさで500-700、消灯時の閾値が60という設定なので、LED電球を消灯してもすぐには時計が消えません。
- ダイナミック点灯の合間にI2Cでセンサーを取りに行くと一瞬時計表示がチラついてしまいます。なので、時刻と入力時間を切り替えるときに読みに行くように変更しました。RTOS欲しい。
- 照度に合わせてLEDの明るさを変えていますが、違いがわかりません。手で影を作ると少し暗くなるのでソレと判断できるのですが、あまり暗くすると消灯モードになっちゃいますし。
■ソース■
基本的には以前書いたデジタル時計の使い回しです。ArduinoでLEDのダイナミック点灯させるとだいたいこんな処理になっちゃいます。あと、APDS9930のライブラリはArduino用を使いました(APDS9930 Ambient Light and Proximity sensor)。
なんということもないダイナミック点灯プログラムなのですが、CPUのクロックを点灯中には8Mhz、消灯時には1Mhzに切り替えています。切り替えることでI2Cなどに影響がでないか心配だったのですが、問題ありません。1MhzだとG2553の消費電流は230μAで9930は220μA(スリープはさせていないが通信はごく低頻度なので消費電流はもっと低いはず)なので、まったくLEDを点灯させなければ2000時間程度は動くはずですが、点灯しない時計は意味がないので困ったものです。
なんということもないダイナミック点灯プログラムなのですが、CPUのクロックを点灯中には8Mhz、消灯時には1Mhzに切り替えています。切り替えることでI2Cなどに影響がでないか心配だったのですが、問題ありません。1MhzだとG2553の消費電流は230μAで9930は220μA(スリープはさせていないが通信はごく低頻度なので消費電流はもっと低いはず)なので、まったくLEDを点灯させなければ2000時間程度は動くはずですが、点灯しない時計は意味がないので困ったものです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <sRTC.h> | |
#include <legacymsp430.h> | |
#include <APDS9930.h> | |
// LEDのポート | |
const int segA = P1_4; | |
const int segB = P1_5; | |
const int segC = P2_0; | |
const int segD = P2_5; | |
const int segE = P2_2; | |
const int segF = P2_3; | |
const int segG = P2_4; | |
//int segCol = P2_6; | |
int digits[] = { | |
P1_0, P1_1, P1_2, P1_3}; // for serial | |
#define ALL_OFF 10 | |
const uint8_t patA[] = { | |
HIGH, LOW, HIGH, HIGH, LOW, HIGH, HIGH, HIGH, HIGH, HIGH, LOW}; | |
const uint8_t patB[] = { | |
HIGH, HIGH, HIGH, HIGH, HIGH, LOW, LOW, HIGH, HIGH, HIGH, LOW}; | |
const uint8_t patC[] = { | |
HIGH, HIGH, LOW, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, LOW}; | |
const uint8_t patD[] = { | |
HIGH, LOW, HIGH, HIGH, LOW, HIGH, HIGH, LOW, HIGH, HIGH, LOW}; | |
const uint8_t patE[] = { | |
HIGH, LOW, HIGH, LOW, LOW, LOW, HIGH, LOW, HIGH, LOW, LOW}; | |
const uint8_t patF[] = { | |
HIGH, LOW, LOW, LOW, HIGH, HIGH, HIGH, LOW, HIGH, HIGH, LOW}; | |
const uint8_t patG[] = { | |
LOW, LOW, HIGH, HIGH, HIGH, HIGH, HIGH, LOW, HIGH, HIGH, LOW}; | |
const uint8_t sevenPort[] = { | |
segA, segB, segC, segD, segE, segF, segG}; | |
const uint8_t *sevenSeg[] = { | |
patA, patB, patC, patD, patE, patF, patG}; | |
RealTimeClock RTC; | |
// APDS9930 | |
APDS9930 apds = APDS9930(); | |
uint16_t ch0 = 0; | |
uint16_t ch1 = 1; | |
// 最後に表示を更新した時刻(秒) | |
unsigned long prevSec = 99; | |
// sprintf用バッファ | |
char spfBuf[20], buf[20]; | |
unsigned long started; | |
void setup() | |
{ | |
// led | |
for (int i = 0; i < 7; i++) { | |
pinMode(sevenPort[i], OUTPUT); | |
digitalWrite(sevenPort[i], LOW); | |
} | |
for (int i = 0; i < 4; i++) { | |
pinMode(digits[i], OUTPUT); | |
digitalWrite(digits[i], HIGH); | |
} | |
// Initialize APDS-9930 (configure I2C and initial values) | |
if ( apds.init() ) { | |
// Serial.println(F("APDS-9930 initialization complete")); | |
} | |
else { | |
// Serial.println(F("Something went wrong during APDS-9930 init!")); | |
} | |
// Start running the APDS-9930 light sensor (no interrupts) | |
if ( apds.enableLightSensor(false) ) { | |
// Serial.println(F("Light sensor is now running")); | |
} | |
else { | |
// Serial.println(F("Something went wrong during light sensor init!")); | |
} | |
for (int i = 0; i < 5; i++) { | |
digitalWrite(P1_0, HIGH); | |
delay(100); | |
digitalWrite(P1_0, LOW); | |
delay(100); | |
} | |
RTC.begin(); | |
RTC.RTC_hr = 10; | |
RTC.RTC_min = 7; | |
RTC.RTC_sec = 0; | |
setDigit(0); | |
} | |
void setDigit(int d) { | |
for (int i = 0; i < 4; i++) { | |
if (d == i) | |
digitalWrite(digits[i], LOW); | |
else | |
digitalWrite(digits[i], HIGH); | |
} | |
} | |
int digit = 0; | |
int loopCount = 999; | |
#define MODE_DARK 1 | |
#define MODE_BRIGHT 2 | |
int mode = MODE_DARK; | |
int isTime = 0; | |
#define THRESHOLD 30 | |
static int duration = 0; // 入浴時間 | |
static int light = 100; // 直近の明るさ。だいたい100-1000、明かりを消した風呂場で60以下 | |
int lightToTonTime() { | |
int t = light / 100+5; | |
if (t > 10) t = 10; | |
if (t < 5) t = 5; | |
return t; | |
} | |
void updateLight() { | |
float f = 0; | |
apds.readAmbientLightLux(f); | |
int current = f; | |
light = (light + current) / 2; | |
} | |
void loop() | |
{ | |
// turn off | |
setDigit(-1); | |
delayMicroseconds(100-lightToTonTime()); | |
// 点灯中にセンサーを読みに行くとちらつくので頻度を下げている | |
int sec = RTC.RTC_sec; | |
if (sec != prevSec) { | |
if (mode == MODE_DARK) { // mode is Dark | |
updateLight(); | |
if (light > THRESHOLD) { | |
mode = MODE_BRIGHT; | |
BCSCTL1 = CALBC1_8MHZ; | |
DCOCTL = CALDCO_8MHZ; | |
// 入浴時間計測開始 | |
duration = 0; | |
isTime = 0; | |
} | |
} | |
else { // mode is Bright | |
duration++; | |
if ((duration % 2) == 0) { | |
isTime = (isTime == 0); | |
if (isTime) { | |
updateLight(); | |
if (light <= THRESHOLD) { | |
mode = MODE_DARK; | |
BCSCTL1 = CALBC1_1MHZ; | |
DCOCTL = CALDCO_1MHZ; | |
duration = 0; | |
} | |
} | |
} | |
if (isTime) { // 時刻表示 | |
itoa(10000 + RTC.RTC_hr*100+RTC.RTC_min, buf, 10); | |
strcpy(spfBuf, buf+1); | |
} | |
else { // 入浴時間 | |
int m = duration / 60; | |
int s = duration % 60; | |
itoa(10000+m*100+s, buf,10); | |
if (buf[1] == '0') buf[1] = ' '; | |
if (buf[3] == '0') buf[3] = ' '; | |
strcpy(spfBuf, buf+1); | |
} | |
} | |
prevSec = sec; | |
} | |
// 表示処理 | |
// 対象となる桁の値を0-9か空白に変換しセグメントビットに出力 | |
int num = spfBuf[digit]; | |
if (mode == MODE_DARK) { | |
out(ALL_OFF, LOW); | |
setDigit(-1); | |
} | |
else { | |
num = (num >= '0' && num <= '9') ? num - '0' : ALL_OFF; | |
out(num, LOW); | |
// 桁ビットを出力(点灯=high) | |
setDigit(digit); | |
} | |
// 少し維持 | |
delayMicroseconds(lightToTonTime()); // lightはだいたい1000-100 | |
//次の桁へ | |
digit++; | |
if (digit >= 4) digit = 0; | |
} | |
void out(int num, int DP) { | |
for (int i = 0; i < 7; i++) { | |
digitalWrite(sevenPort[i], sevenSeg[i][num]); | |
} | |
} | |
interrupt(TIMER1_A0_VECTOR) Tic_Tac(void) { | |
RTC++; // Update secondes | |
}; | |
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。