Arduinoでボタン1つで行うストップウォッチ
2022.02.23
知り合いに相談を受けたのでサササっと記述。
条件はストップウォッチの機能をボタン1つでスタート、ストップ。
リセット機能は要件になし。
液晶にはストップしたときにH:M:S:MSで表示する。
ボタンの動作はデジタル3番ピンを使用。
不安定防止の為、5Vで受けるようにする。
やり方はいろいろあると思うけど、深夜の殴り書き程度ならこんなもんかな?
もっと最適化できる? ウン、ソウダネ。
CASE文を嫌う人って結構いるけど、
IFの階層が深いよりはコードが見やすく
バグも発見しやすいので、私はためらいもなく使います。
インデントをタブで行ってたところは
ブログにコピペしたら消されちゃった(;´Д`)
なので「}」なんかは左寄せになってしまって読みにくくなってしまったけど
我慢してください(;´Д`)


#include
LiquidCrystal_I2C lcd(0x27,16,2);

unsigned long W_time = 0; //タイマーの値
unsigned long W_counttime = 0; //経過時間

int W_msec = 0; //ミリ秒の保存
int W_sec = 0; //秒の保存
int W_min = 0; //分の保存
int W_hr = 0; //時間の保存
int W_amari = 0;

int Push_Count = 0; //何回押したか。1回目ならスタート、2回目ならストップ&表示
int Push_Flg = 0; //現在押しているかのフラグ

unsigned long Click_konkai = 0; //長押し誤動作の対策
unsigned long Click_zenkai = 0;

int Work_int = 0;
int B_Switch = 0; //スイッチの判断用

void setup() {
//***シリアル定義*******************
Serial.begin(9600);

//***液晶初期化******************
lcd.init();
lcd.begin(20,2); //LCD2004の表示範囲を指定
//LCD2004は20x2キャラクタ表示なので、20,2といれる。
lcd.clear(); //表示をクリアしてカーソルを上段の先頭の位置に移動
lcd.backlight(); //LCD2004のバックライトを点ける
lcd.print("READY");
//***ピン定義*******************
pinMode(3,INPUT); //デジタルピン3番をスイッチ用として使用する

}

void loop(){
B_Switch = digitalRead(3);//ボタンが押されたかを確認する
if(B_Switch == HIGH){
delay(10); //チャタリングじゃないかを判断するために10ms後も押し続けたかで判断
B_Switch = digitalRead(3);//ボタンが押されたかを確認する
if(B_Switch == HIGH){
Push_Flg = 1;
}else{
Push_Flg = 0;
}
}else{
Push_Flg = 0;
}


if (Push_Flg == 1){
Click_konkai = millis();
Work_int = Click_konkai - Click_zenkai;
if (Work_int > 1000 ){ //1秒未満の再クリックは押しっぱなしの可能性があるので無視する

Click_zenkai = Click_konkai;

switch(Push_Count){
case 0:
W_time = millis(); //開始時刻を記録
lcd.clear();
lcd.print("START");
Push_Count = 1;
break;
case 1:
W_counttime = millis() - W_time; //経過時間を導く
W_msec = W_counttime % 1000; //1000で割った余りをmsにする
W_counttime = W_counttime /1000; //経過時間を1000で割って最小単位を秒にする。
W_hr = W_counttime / 3600; //経過時間を3600で割って時間を導く。
W_amari = W_counttime % 3600; //経過時間を3600で割った余りを保存。
W_min = W_amari / 60; //経過時間を60で割って分を導く。
W_sec = W_amari % 60; //経過時間を60で割った余りを秒にする。
lcd.clear();
lcd.print("STOP");
lcd.setCursor(0,1);
lcd.print(W_hr); lcd.print("h:");
lcd.print(W_min); lcd.print("m:");
lcd.print(W_sec); lcd.print("s:");
lcd.print(W_msec); lcd.print("ms");
Push_Count = 0;
break;
}
}
}
}
Arduino 9-1) 4連 Digital Tube LED Displayの点滅問題を考える
2019.03.03
その後、いろんな角度からテストをしてみました。

まず、5, 6ピンを使うとmillis(), micros(), delay(), delayMicroseconds()などの関数が正常に動かなくなるという情報をネットでみたので、接続するピンを
#define SCLK 4
#define RCLK 7
#define DIO 8
としてみる。
失敗

電源をUSB供給から電池に変えてみる。
失敗

Deleyの代わりにmillisを使ってみる。
Deleyのように簡単に使えるようにするため
void milliswait(int loop_i){
old_time=millis(); //最新の実行時間を保存
while (millis() - old_time < loop_i);
}

このような関数を作って実行してみた。
どうやらDelayが悪いのではなく、
処理能力が低下すると高速書き換えができなくなるだけのことだった。
複雑な処理を乗せるとDelayじゃなくても起きる現象ということで
失敗

先人のライブラリを使ってみる。
TM74HC595で検索すると結構使ってる人がいるもので、
Youtubeに投稿してる人も何人かいました。
https://www.youtube.com/watch?v=S_Hnx20GzGs
↑この方の意見を全面的に取り入れて試してみます。
https://github.com/kostarev/TM74HC595-4dig-display
にアクセスしてダウンロードしインストール。
ソースコードを書き換えて実行。
同じ症状でした。
失敗

結局の所、ネットで調べ倒して見た結果、
この製品は4キャラクタのうち、1文字を表示した直後、
他のモジュールの文字が消えてしまう仕様のようで
高速に4モジュールを表示し直して、目をごまかすような処理が必要のようだ。
Loop内でDelayを使うとその更新が阻害されるため
点滅したように見えてしまうというのが真実らしい。
これの解決策として、割り込みを利用し
ある一定周期で更新させることができれば、
Loop内だろうがDelayで止めようが
表示は安定するのではというのが最適解のようです。

#include <mstimer2.h>
int Denatu = 0;
int OutNum = 0;


//接続するピンを設定する
#define SCLK 4
#define RCLK 7
#define DIO 8


byte digitBuffer[4]; //キャラ数(文字数を設定)この配列に入れた数字がそのまま表示される。

void setup(){
Serial.begin( 9600 ); //シリアルモニタを使うときの儀式
pinMode(13, OUTPUT); //ボードのLEDを使用する
pinMode(RCLK, OUTPUT);
pinMode(SCLK, OUTPUT);
pinMode(DIO, OUTPUT);
MsTimer2::set(1, showDisplay); // 1msの周期で結果を4LEDに表示
MsTimer2::start();
}


void loop(){
digitalWrite(13, HIGH); //LEDを光らせる
Denatu = analogRead(0); //A0ピンの電圧を測る0(0V)~1023(5V)
Serial.println(Denatu); //シリアルモニタに値を出力
OutNum = Denatu;


//4文字目に数字を表示
digitBuffer[3] = OutNum % 10; //10で割ってあまりを求める。
OutNum = OutNum / 10; //値を10で割って結果を元の数字に返す。これで4ケタが3ケタにシフトする。

//3文字目に数字を表示
digitBuffer[2] = OutNum % 10; //10で割ってあまりを求める。
OutNum = OutNum / 10; //値を10で割って結果を元の数字に返す。これで3ケタが2ケタにシフトする。

//2文字目に数字を表示
digitBuffer[1] = OutNum % 10; //10で割ってあまりを求める。
OutNum = OutNum / 10; //値を10で割って結果を元の数字に返す。これで2ケタが1ケタにシフトする。


//1文字目に数字を表示
digitBuffer[0] = OutNum % 10; //10で割ってあまりを求める。


delay(Denatu); //読み取った値分処理を止める1023=1.023秒
digitalWrite(13, LOW); //LEDを消す
delay(Denatu); //読み取った値分処理を止める1023=1.023秒
}


//表示するために関数を定義
void showDisplay(){
const byte digit[10] = {
0b11000000,//0
0b11111001,//1
0b10100100,//2
0b10110000,//3
0b10011001,//4
0b10010010,//5
0b10000010,//6
0b11111000,//7
0b10000000,//8
0b10010000 //9
};
const byte chr[4] = {
0b00001000,//1
0b00000100,//2
0b00000010,//3
0b00000001,//4
};


for(byte i = 0; i <= 3; i++){
digitalWrite(RCLK, LOW);
shiftOut(DIO,SCLK,MSBFIRST, digit[digitBuffer[i]]);
shiftOut(DIO,SCLK,MSBFIRST, chr[i]);
digitalWrite(RCLK, HIGH);
delayMicroseconds(1);
}
}


最初の実験の影響で、ピンの位置を先日のものと変えています。注意。
そして実行



イエ━ヽ(*´∇`)人(´∇`*)━ィ!!
ただこの方法、人間には区別はつかないけど高速点滅しているので目には優しくないと思われる。

Arduino 9) 4連 Digital Tube LED Displayを試す
2019.02.28


注文してから1ヶ月以上かかりました。長かった・・orz



左からVCC、SCLK、RCLK、DIO、GND
この5ピンを接続し、SCLK、RCLK、DIOの3ピンで制御します。



基本配線は上記でいいと思います。



どうせなら変化があったほうが面白いので
前回の可変抵抗を読み取って表示することを踏まえて配線しています。



LED文字には各パーツごとに光るようになっています。
それらを組み合わせて人間が数字として読み取れるように
場所を指定し光らせます。
ビットが0なら光る。1なら消えるを意味します。
つまり
0なら 0b11000000
1なら 0b11111001
2なら 0b10100100
3なら 0b10110000
4なら 0b10011001
5なら 0b10010010
6なら 0b10000010
7なら 0b11111000
8なら 0b10000000
9なら 0b10010000
といった2進数コードを使用します。

ではコーディングですが、いちから作るのは面倒なので、
どこぞから拾ってきたものをカスタマイズして仕上げました。

//接続するピンを設定する
#define SCLK 3
#define RCLK 4
#define DIO 5


byte digitBuffer[4]; //キャラ数(文字数を設定)この配列に入れた数字がそのまま表示される。

void setup(){
pinMode(RCLK, OUTPUT);
pinMode(SCLK, OUTPUT);
pinMode(DIO, OUTPUT);
}


void loop()
{
digitBuffer[0] = 1; //1文字目に1を表示
digitBuffer[1] = 2; //2文字目に2を表示
digitBuffer[2] = 3; //3文字目に3を表示
digitBuffer[3] = 4; //4文字目に4を表示

showDisplay();
}


//表示するために関数を定義
void showDisplay(){
const byte digit[10] = {
0b11000000,//0
0b11111001,//1
0b10100100,//2
0b10110000,//3
0b10011001,//4
0b10010010,//5
0b10000010,//6
0b11111000,//7
0b10000000,//8
0b10010000 //9
};
const byte chr[4] = {
0b00001000,//1
0b00000100,//2
0b00000010,//3
0b00000001,//4

};


for(byte i = 0; i <= 3; i++)
{
digitalWrite(RCLK, LOW);
shiftOut(DIO,SCLK,MSBFIRST, digit[digitBuffer[i]]);
shiftOut(DIO,SCLK,MSBFIRST, chr[i]);
digitalWrite(RCLK, HIGH);
delayMicroseconds(1000);
}
}




無事光りました。
では可変抵抗の値を表示するプログラムに改造してみたいと思います。

int Denatu = 0;
int OutNum = 0;
int StackNum = 0;


//接続するピンを設定する
#define SCLK 3
#define RCLK 4
#define DIO 5


byte digitBuffer[4]; //キャラ数(文字数を設定)この配列に入れた数字がそのまま表示される。

void setup(){
Serial.begin( 9600 ); //シリアルモニタを使うときの儀式
pinMode(13, OUTPUT); //ボードのLEDを使用する
pinMode(RCLK, OUTPUT);
pinMode(SCLK, OUTPUT);
pinMode(DIO, OUTPUT);
}


void loop()
{
//digitalWrite(13, HIGH); //LEDを光らせる
Denatu = analogRead(0); //A0ピンの電圧を測る0(0V)~1023(5V)
if (Denatu != StackNum) { //前回と値が違うときだけ処理を行う


Serial.println(Denatu); //シリアルモニタに値を出力
OutNum = Denatu;


//4文字目に数字を表示
digitBuffer[3] = OutNum % 10; //10で割ってあまりを求める。
OutNum = OutNum / 10; //値を10で割って結果を元の数字に返す。これで4ケタが3ケタにシフトする。

//3文字目に数字を表示
digitBuffer[2] = OutNum % 10; //10で割ってあまりを求める。
OutNum = OutNum / 10; //値を10で割って結果を元の数字に返す。これで3ケタが2ケタにシフトする。

//2文字目に数字を表示
digitBuffer[1] = OutNum % 10; //10で割ってあまりを求める。
OutNum = OutNum / 10; //値を10で割って結果を元の数字に返す。これで2ケタが1ケタにシフトする。


//1文字目に数字を表示
digitBuffer[0] = OutNum % 10; //10で割ってあまりを求める。


//結果を4LEDに表示
showDisplay();
//今回の電圧を記録する。
StackNum = Denatu;
}

delay(Denatu); //読み取った値分処理を止める1023=1.023秒
digitalWrite(13, LOW); //LEDを消す
delay(Denatu); //読み取った値分処理を止める1023=1.023秒


}

//表示するために関数を定義
void showDisplay(){
const byte digit[10] = {
0b11000000,//0
0b11111001,//1
0b10100100,//2
0b10110000,//3
0b10011001,//4
0b10010010,//5
0b10000010,//6
0b11111000,//7
0b10000000,//8
0b10010000 //9
};
const byte chr[4] = {
0b00001000,//1
0b00000100,//2
0b00000010,//3
0b00000001,//4

};


for(byte i = 0; i <= 3; i++)
{
digitalWrite(RCLK, LOW);
shiftOut(DIO,SCLK,MSBFIRST, digit[digitBuffer[i]]);
shiftOut(DIO,SCLK,MSBFIRST, chr[i]);
digitalWrite(RCLK, HIGH);
delayMicroseconds(1000);
}
}


赤字の部分はこの仕組のキモで、
可変抵抗の4ケタまでの数字を分解して4つのLEDに表示する。
プログラマーを長くやっていると定番のアルゴリズムですが、
ここでも役立つとは思いませんでした。
では実行。



表示がオカシイ。
値が1024でも一桁目の4しか正常に表示しない。
他の桁はチカチカしてしまう。
いろいろソースを変えて試してみたけど
Delayで処理を止めるとその間だけ表示も止まっているようだ。

今の所解決方法が見つからない。
よく使われる部品でこんなトラブルとはがっかり(;´Д`)
Arduino 8) EEPROMを使って電源を切っても変数が消えないようにする
2019.02.15
プログラムを作るに当たって、電源を切ったらデータが消えてしまうというのは
考えられません。あってはならないことです。
Windowsはデータベースに保存したり、iniファイルを使用したりと様々な方法で
環境の保存を行っております。
Arduinoは頻繁に電源を入り切りするコンピュータです。
Windowsのようにアプリの終了やシャットダウンで環境保存することはできません。
そこでプログラム動作中に変数を退避する必要が出てくるわけですが、
それを実現するのがEEPROMに記憶するという方法です。

EEPROMのエリアは基本0~255の256個(256Byte)となってますが、
Unoの場合1KB(1024Byte)Megaの場合4KBまで使用可能です。
一般的には10万回の書き込みに耐えられるとされていますが、
実際にテストを行った方が居て、300万回は耐えたという情報もあります。

1秒毎にカウントして記憶するプログラムを組んでみました。
EEPROM.hを覆っている<>はhtmlの都合上全角になっております。

#include <EEPROM.h>
int Access_count;

void setup() {
Serial.begin( 9600 ); //シリアルモニタを使うときの儀式
pinMode(13, OUTPUT); //ボードのLEDを使用する
Access_count = EEPROM.read(0); //0番地にEEPROMから値を読み込む
}


void loop() {
digitalWrite(13, HIGH); //LEDを光らせる
delay(1000); //1秒
Access_count = Access_count + 1; //アクセスカウンターを追加
Serial.println(Access_count); //シリアルモニタに値を出力
EEPROM.write(0, Access_count); //0番地からEEPROMに値を書き込む。
}


シンプルにまとめてみました。
正常にEEPROMに書き込まれたかは電源入れた都度
シリアルモニタで確認できるようにしています。
このプログラムは1秒毎にカウントしていくものですが、
1Byteを超える数字256以上まで進んだ場合は正常に保存できません。
255より大きい数字は自分で分割して保存エリアを分ける必要があります。
それではちょっと勝手が悪いので、別の方法を模索していると、
ちょうどいい命令を見つけました。

EEPROM.put (番地, データ);  //保存
EEPROM.get (番地, データ); //読込

INT型であれば、-32768から32767まで扱うことができます。
これは2byte使うので0番地で保存した場合同時に1番地も使用しているとみた方がいいです。
他の変数も保存する場合はぶつからないように2番地から使うと行った配慮が必要になるかと思われます。
ためしに以下のようなプログラムを作って検証してみました。

#include <EEPROM.h>
int TestNum = 12345;

void setup() {
Serial.begin( 9600 ); //シリアルモニタを使うときの儀式
EEPROM.put(0,0);
EEPROM.put(1,0);
EEPROM.put(2,0);
EEPROM.put(3,0);
EEPROM.put(0,TestNum);
Serial.println(EEPROM.read(0));
Serial.println(EEPROM.read(1));
Serial.println(EEPROM.read(2));
Serial.println(EEPROM.read(3));
Serial.println(EEPROM.get(0,TestNum));
}


結果は以下の通り



0番地に57(2進数00111001)
1番地に48(2進数00110000)
連結すると0011000000111001 = 12345
計算は合ってます。
想像通り自動的に2byte使ってますね。
これは大変便利な機能ですが大きいプログラムを組むときは
アドレスマップを作らないと危険ですね。

では次の疑問に行きたいと思います。
intをlong(-2147483684から2147483647)の変数に変えた場合はどうなるのか。
上の経験を踏まえれば32bitなので4byte使われると思いますが。

ソースを以下のように変更して実行してみます。

#include <EEPROM.h>
long TestNum = 1234567890;

void setup() {
Serial.begin( 9600 ); //シリアルモニタを使うときの儀式
EEPROM.put(0,0);
EEPROM.put(1,0);
EEPROM.put(2,0);
EEPROM.put(3,0);
EEPROM.put(4,0);
EEPROM.put(5,0);
EEPROM.put(0,TestNum);
Serial.println(EEPROM.read(0));
Serial.println(EEPROM.read(1));
Serial.println(EEPROM.read(2));
Serial.println(EEPROM.read(3));
Serial.println(EEPROM.read(4));
Serial.println(EEPROM.read(5));
Serial.println(EEPROM.get(0,TestNum));
}


結果は想像したとおりになりました。



では事前に0番地から5番地に255を入れ込んでおき、
long TestNum = 123;
とした場合はどうなるのでしょうか。

#include <EEPROM.h>
long TestNum = 123;

void setup() {
Serial.begin( 9600 ); //シリアルモニタを使うときの儀式
EEPROM.put(0,255);
EEPROM.put(1,255);
EEPROM.put(2,255);
EEPROM.put(3,255);
EEPROM.put(4,255);
EEPROM.put(5,255);
EEPROM.put(0,TestNum);
Serial.println(EEPROM.read(0));
Serial.println(EEPROM.read(1));
Serial.println(EEPROM.read(2));
Serial.println(EEPROM.read(3));
Serial.println(EEPROM.read(4));
Serial.println(EEPROM.read(5));
Serial.println(EEPROM.get(0,TestNum));
}


値は1Byte、変数は4Byteのもの。
命令が変数のサイズで反応しているなら5番地と6番地は255のまま。
値のサイズで反応しているなら0番地以外全て255のままとなります。
結果は・・



つまり値ではなく、変数の種類で書き込み場所が決まるようです。
これはとても重要なことですよ。ぜひ頭に焼き付けて置くべき事柄です。
実験してよかった(*´ω`*)

あと問題があるとすればEEPROMの書き込み寿命ですかね。
今、F-RAMっていうのが登場しているようですが、
F-RAMはCypressのFM25W256を使うと、ほぼ制限を気にすることなく書き込みができるという記事があります。
https://www.switch-science.com/catalog/1406/
結構いい値段がしますね。庶民にはまだまだ手が出ないかなぁ。
Arduino 7) 可変抵抗器でボリューム値を知る
2019.02.05
Arduinoはアナログピンの入力電圧を0~1023段階で測ることができます。
Arduinoの5Vを可変抵抗器につなげ、その抵抗の変化を読み取り数値化するのです。
可変抵抗は電圧をコントロールできます。
一般的に抵抗と言えば電圧が一定の場合は電流を制御するものというイメージがあります。
可変抵抗はキルヒホッフの第二法則を利用した分圧を行い電圧をコントロールできるのです。

Arduinoのアナログピンは0vで0、2.5Vで512、5Vで1023という値を返します。
では実際に読み取ってみます。
変化を目視するためにボードについているLEDを点滅させその速度で判断してみましょう。

配線はこのようにして


ソースは以下のようにします

int Denatu = 0;

void setup() {
Serial.begin( 9600 ); //シリアルモニタを使うときの儀式
pinMode(13, OUTPUT); //ボードのLEDを使用する
}

void loop() {
digitalWrite(13, HIGH); //LEDを光らせる
Denatu = analogRead(0); //A0ピンの電圧を測る0(0V)~1023(5V)
Serial.println(Denatu); //シリアルモニタに値を出力
delay(Denatu); //読み取った値分処理を止める1023=1.023秒
digitalWrite(13, LOW); //LEDを消す
delay(Denatu); //読み取った値分処理を止める1023=1.023秒
}

実行した結果はこちら



ダイヤルに合わせてLEDの速度が変化しているのがわかります。
TXのLEDがチカチカしているのはシリアルモニタと通信しているためです。
この仕組は頻繁に使われ応用される基礎なので、
自分自身の再確認のために記録を残しておきます。

- CafeNote -