上の回転ダイヤルは、ガチャポンから出てくる玩具で、なぜかこの春から突然人気となったようです。ネット通販で4つセットが売られていましたので5月末に予約して最近ようやく手に入れました^^;
小さいですが精巧によくできていて、ダイヤルが戻る感触は抜群です、なるほど・・・人気に納得できそう。
説明書には対象年齢がまさかの「15歳以上」と書いてありますが、こんな電話機はひょっとすると50歳以上しか使ったことがないかもしれませんね。
単に回すだけで楽しむ玩具ですが、キーボードに飽きたらこれを使ったデジタル入力も面白そう。
裏蓋を開けてみると中にはゼンマイ仕掛けのごく小さなギアボックスが入っているだけです。それでもご丁寧にちゃんとネジ2本を使って正確に蓋をしてあります。これならデジタル化できないものかと、ふと考えついてしまったのが事の始まり^^;
ギアボックス自体は開かないし、軸のでっぱりなど、余計なものもありません。もしそういうのがあれば磁石を仕込むとか、何らかのしかけができそyですが、一見取りつくしまがない感じ。こうなるとますます挑戦したくなりますね^^
しばらくは考えるだけでしたが、この4連休週末がきたので、ついにやってみることにしました。
気付いた中で一番かんたんそうな解き方:ダイヤルが戻る際の音の時間を測定してはどうかな!
戻る時間を測ってみた結果、回転量で安定して決まるようです。たぶんぜんまいギアの油による定速と思われます。それならきちんとマイクで音を拾って詳しく調べてみることに。
というわけで小さなコンデンサマイク・モジュールを仕込むことにしました。
1ドルで数個買えるモジュールですがゲインの調整もできるので一応使えそう。やってみると中になぜかぴったりと収まり、蓋をするとちょうどうまく固定されます。
よけいな飾りの鎖部分はカットして、その付近にそれらしくケーブルを通しました。
表から見るとサマになってますね(・・?
ダイヤルをしながらオシロスコープで測定します。
マイクモジュールは若干のノイズ(というより明らかに一定の寄生発振)を出していますが、この用途には問題ないですね。この際音のゲインが振り切るように調整しました。ダイヤル5を回すと次の出力になります。
オフセットは2.5V(電源電圧の丁度半分)で、ダイヤル音で0ボルトから電源電圧(5V)まで振り切るようにゲインを調整しました。
これでダイヤルごとの戻り時間を調べた結果、つぎのようになっています。
式に従って計算をし、次の判定表を作りました。
とはいえ、音の出ている間の波形は次のように乱れていますから、ダイヤルの識別には少し工夫が要ります。
絶対値のオンオフだけで識別するわけにはいきません。しかし1回の測定に数ミリセカンドかかっても問題ないわけなので、多数回のサンプリングをして「オフセットからのずれ幅が一定割合以上起きている場合にダイヤルが戻り中」と判定すればよいと考えつきました。
というわけで値をTM1637ィスプレイに表示するとして、次の回路にしました。ATmega328pで書いてありますが、Arduino-UnoでOKです。毎度きたない手書きのメモですみませんが、今回は回路を書くほどのものではありませんね。
実験した写真は次です。Arduinoのプログラムはこの記事の最後につけておきます。
これでテストすると、マイクを閉じ込めただけあって周囲の雑音はほぼ拾いません。玩具をガンガンたたいたりすると時々1が表示されます。そこで、短い信号を無視したり、定数を調整したり、ダイヤルがずれにくいように滑り止めマットを敷いたりして、無事にうまくできあがりました。
調整には思いのほか時間がかかりました^^;
ちなみに、このおもちゃはガチャポン(たぶん¥300)での購入用のようですが、次のネット販売で5月末に予約して9月に入手しました。1個250円程でした。
1パックに4個はいっています。どれも精巧ですばらしい!
というわけで、今回試験したArduinoスケッチをつけておきます。あまりきれいな書き方ではありませんが。
/* *********************************************************
Rotating dial to TM1637 (for Arduino demonstration)
Initial version V00 Sept. 19, 2020 (c) Akira Tominaga
* ********************************************************/
// for measurement
#define aPin 0 // A0 pin for analogRead
uint32_t onS = 0; // signal-on started time
uint32_t Now; // current time mS from start
uint8_t nSamp; // number of voltage samplings
#define maxSamp 40 // max number of samplings
uint8_t nOn; // counter of signal-on
#define thOn 10 // threshold to detect signal-on
#define Vcenter 511 // analog voltage center value
#define Vth 300 // threshold offset from center
uint32_t sigLen; // signal length in milli second
#define offSt 0 // last satus was off
#define onSt 1 // last satus was on
uint8_t lastSt = offSt; // last status code
uint16_t aVal; // analog read value
uint16_t vVal; // offset = | Vcenter - aVal |
// each dial max duration milli seconds
#define d1max 417
#define d2max 546
#define d3max 674
#define d4max 802
#define d5max 931
#define d6max 1059
#define d7max 1188
#define d8max 1316
#define d9max 1445
#define d0max 1650
#define d1min 340
#define dAdj 27 // adjusting lenght for handling
// for led TM1637
#define lDIO 6 // DIO for TM1637 LED
#define lCLK 7 // CLK for TM1637 LED
#define lBrt 0x03 // duty-r 1/16x 02:4,03:10,05:12,07:14
#define lTu 50 // time unit in micro-second
byte lChr; // single byte sent to LED
byte Data[] = { 0x08, 0x08, 0x08, 0x08 }; // LED init 8888
void setup() { // ***** Arduino setup *****
pinMode(lCLK, OUTPUT);
pinMode(lDIO, OUTPUT);
digitalWrite(lCLK, HIGH);
digitalWrite(lDIO, HIGH);
Serial.begin(9600);
lDispData(); // display 8888
delay(1000);
for (uint8_t i = 0; i < 4; i++) {
Data[i] = 0x10; // display blank
}
lDispData();
delay(1000);
Serial.println("Starting");
/*****************************************
Measure voltage to find signal
*****************************************/
mainLp:
nOn = 0; // set sig counter zero
for (nSamp = 0; nSamp < maxSamp; nSamp++) {
aVal = analogRead(aPin);
vVal = Vcenter - aVal;
if (Vcenter < aVal) { // get abs offset vVal
vVal = aVal - Vcenter;
}
if (vVal > Vth) {
nOn++;
}
}
Now = millis();
if (nOn >= thOn) {
goto onRtn;
}
// goto offRtn;
offRtn:
if (lastSt == offSt) {
goto mainLp;
}
sigLen = Now - onS;
if (sigLen < (d1min+dAdj)) goto mainLp; // if short, noise
lastSt = offSt;
onS = 0;
// valid on length here
//Serial.println(sigLen);
if (sigLen <= (d1max+dAdj)) {
dispDial(0x01);
goto mainLp;
}
if (sigLen <= (d2max+dAdj)) {
dispDial(0x02);
goto mainLp;
}
if (sigLen <= (d3max+dAdj)) {
dispDial(0x03);
goto mainLp;
}
if (sigLen <= (d4max+dAdj)) {
dispDial(0x04);
goto mainLp;
}
if (sigLen <= (d5max+dAdj)) {
dispDial(0x05);
goto mainLp;
}
if (sigLen <= (d6max+dAdj)) {
dispDial(0x06);
goto mainLp;
}
if (sigLen <= (d7max+dAdj)) {
dispDial(0x07);
goto mainLp;
}
if (sigLen <= (d8max+dAdj)) {
dispDial(0x08);
goto mainLp;
}
if (sigLen <= (d9max+dAdj)) {
dispDial(0x09);
goto mainLp;
}
if (sigLen <= (d0max+dAdj)) {
dispDial(0x00);
goto mainLp;
}
dispDial(0x11); // for others, too long signal "L"
goto mainLp;
/*****************************************
Signal on case
*****************************************/
onRtn:
if (lastSt == offSt) {
onS = Now;
}
lastSt = onSt;
goto mainLp;
} // end of Setup
void loop() { // ****** Arduino Loop *****
// never comes here
}
/**************************************
User defined functions
* *********************************** */
// *** dispDial(#) *** display dial number ***
void dispDial(byte dN) {
for (int k = 0; k < 3; k++) {
Data[k] = Data[k + 1]; // shift Data up
}
Data[3] = dN; // set dial number to digit 3
lDispData();
}
// *** lDispData *** display data to 4dig LED TM1637
void lDispData(void) {
#define showDat 0x40 // show this is data
#define showAd0 0xC0 // LED data addr is zero
#define showDcB 0x88+lBrt // show dCtl + brightness
lStart(); // start signal
lChr = showDat; // identifier for data
lSend(); // send it
lStop(); // stop signal
lStart(); // and restart
lChr = showAd0; // identifier for address
lSend(); // send it
for (int j = 0; j < 4; j++) { // for Data[0] to Data[3]
byte edChr = Data[j]; // set a byte to edChr for edit
switch (edChr) {
case 0x00: lChr = 0x3F; break; // 0
case 0x01: lChr = 0x06; break; // 1
case 0x02: lChr = 0x5B; break; // 2
case 0x03: lChr = 0x4F; break; // 3
case 0x04: lChr = 0x66; break; // 4
case 0x05: lChr = 0x6D; break; // 5
case 0x06: lChr = 0x7D; break; // 6
case 0x07: lChr = 0x07; break; // 7
case 0x08: lChr = 0x7F; break; // 8
case 0x09: lChr = 0x6F; break; // 9
case 0x10: lChr = 0x00; break; // blank
case 0x11: lChr = 0x38; break; // L for too large value
default: lChr = 0x79; // E for Error
}
lSend(); // send each byte continuously
} // end of for bytes
lStop(); // stop signal
lStart(); // restart
lChr = showDcB; // identifier for display brightness
lSend(); // send it
lStop(); // stop signal
}
// *** lSend *** send a charater in lChr to TM1637
void lSend(void) {
#define LSb B00000001 // Least Significant bit
for (int i = 0; i < 8; i++) { // do the following for 8bits
if ((lChr & LSb) == LSb) { // if one then
digitalWrite(lDIO, HIGH); // set lDIO high
} else { // else
digitalWrite(lDIO, LOW); // set lDIO low
}
lCLKon(); // clock on to show lDIO
lChr = lChr >> 1; // shift bits to right
}
digitalWrite(lDIO, LOW); // pull down during Ack
lCLKon(); // clock on to simulate reading
}
// *** lStart *** send start signal to TM1637
void lStart(void) {
digitalWrite(lDIO, LOW);
delayMicroseconds(lTu);
digitalWrite(lCLK, LOW);
delayMicroseconds(lTu);
}
// *** lStop *** send stop signal to TM1637
void lStop(void) {
digitalWrite(lCLK, HIGH);
delayMicroseconds(lTu);
digitalWrite(lDIO, HIGH);
delayMicroseconds(lTu);
}
// *** lCLKon ** clock on to show data to TM1637
void lCLKon(void) {
digitalWrite(lCLK, HIGH);
delayMicroseconds(lTu);
digitalWrite(lCLK, LOW);
delayMicroseconds(lTu);
}
/* End of program */
何とかできることが分かったので、同じ処理を小さなPICなどに組んでケース内に収め、本物の回転ダイヤルと同じように数値パルスを出せば完璧な模型となりますね^^
では今回はこのへんで。
©2020 Akira Tominaga, All rights reserved.