##1. はじめに
この記事はソフトウェアテストの小ネタ Advent Calendar 2019の13日目の記事です。小ネタということで身近なテストツールであるストップウォッチにフォーカスします。
ストップウォッチといえば開発者、テストエンジニア、QA問わず誰もが使い方を知っていて1,000円出せば1/100秒計測の機種が買えるとってもありふれた存在ですが一方で不便なところもあります。
- 操作が手作業で自動化できない
- 値の転記ミスのおそれが付いて回る
- 計測時間をRS-232CやUSBで自動で読み出せるモデルが意外とない
転記ミスもさることながらソフトウェアの改修のたびに同じチェックを繰り返すような場面で自動化できないのはとっても痛いです。また、スポーツのトレーニング向けの機種でPCにデータを転送できるものがありますが手動による転送のためニーズに合いません。そこで自動化システムに組み込めるようなコマンド操作のストップウォッチとデモを兼ねたテストランナーをサクッと作ります。
ストップウォッチのプログラムは割り込みを使わずポーリングでぐるぐる回しているだけだし回路設計の計算も中学校で習うオームの法則だけ。テストランナーはテスト実行からデータの整理、グラフ作成までをこれ一本でできるとっても身近な統合開発環境 "Excel" で実装します。品質技術開発の敷居は決して高くないので自分も始めてみようかなと思っていただけたら幸いです。
##2. 目標
数秒以下(ミリ秒オーダー)の計測はオシロスコープ(以下、オシロ)で行い、長時間の計測はテスト実行ログのタイムスタンプを使うこととし、オシロと実行ログの補完を目指します。
- 利用シーンとして、ストップウォッチで測定を行うような数秒~数分程度の測定を想定します。
- Arduino Uno Rev.3のシステムクロックCSTCE16M0V53-R0の周波数許容偏差は±0.50%以内です。±0.50%というと1秒に対して±5ミリ秒、200秒に対して±1秒、1時間に対して±18秒の誤差に相当しますので、この誤差を許容できるような用途でご使用ください。
- CI/CDや回帰テストに本システムを組み込んで処理時間がやけに長い(短い)といった異常をスクリーニングし、異常を検出したら改めて精密に調べるような使い方が向いていると思います。
- 操作手段として、1. スイッチ押下、2. テスト対象の機器を線出しして接続、3. UARTのコマンド操作、の3種類を想定します。
- ストップウォッチの計測時間はUARTに出力します1。
また、小ネタ縛りで以下の制約を入れます。
- はんだ付けはしない
- プログラムは割り込みを使わない
##3. ストップウォッチの製作
Arduinoの組み込み関数でミリ秒単位のカウントを行うmillis()を利用します。2つの変数time_start、time_endを用意し、計測開始時にtime_startへ、計測終了時にtime_endへmillis()の値をストアし、time_endとtime_startの差分から経過時間を求めます。
###3.1 部品表
部品 | 数量 |
---|---|
Arduino UNO Rev.3 | 1個 |
ブレッドボード | 1枚 |
タクタイルスイッチ | 3個 |
LED(赤色) | 1個 |
LED(黄色) | 1個 |
3kΩのカーボン抵抗 | 2個 |
ブレッドボード用ジャンパー線 | 6本 |
ブレッドボード用ジャンパーワイヤー | 適宜 |
###3.2 実体配線図
Fritzingで描いた実体配線図を以下に示します。
Digital Pin # | Pin Mode | 役割 |
---|---|---|
2 | INPUT_PULLUP | 計測開始スイッチ |
3 | INPUT_PULLUP | 計測終了スイッチ |
4 | INPUT_PULLUP | 経過時間出力スイッチ |
5 | OUTPUT | 計測開始を示すインジケータ(黄色LED) |
6 | OUTPUT | 計測終了を示すインジケータ(赤色LED) |
Digital Pin #2~#4の入力ポートのプルアップはAVRマイコンの機能を利用します。テスト対象の機器を線出しして接続する場合はプルアップの設定や電圧レベルは適宜合わせてください。LEDは極性があり足の長い方がアノード(+側)、短い方がカソード(-側)です。LEDの足をニッパーで切るときはアノード側をちょっと長くしておくと極性を迷わずに済みます。
###3.3 LEDの電流制限抵抗の選定
ArduinoのDigital Pinの出力電圧は5V、LEDの順方向電圧Vfをざっくり2Vとすると電流制限抵抗Rの両端の電圧Vrは3Vとなります。LEDに流す電流Iを1mAとし、オームの法則から電流制限抵抗の値を求めます。
$オームの法則:V = RI$
$オームの法則を変形:R = V / I$
$ここでVr=3V、I = 0.001Aとすると$
$R = 3/0.001$
$ =3000Ω$
$ =3kΩ$
3kΩは許容差5%のE24系列にちょうどありますので3kΩを使用します。
併せて抵抗Rが消費する電力Prも確認します。
$Pr = Vr * I$
$Vr=3V、I=0.001Aとすると$
$Pr=3 * 0.001$
$ = 0.003W$
$ = 3mW$
3kΩ(橙黒赤金)、誤差5%の一般的なカーボン抵抗で1/4Wや1/6Wなど入手しやすいものを用意すればOKです。なお、今時のLEDは少ない電流でも明るく光るのでE12系列の3.3kΩを選定して電流をもうちょっと絞ってもよいかもしれません。
###3.4 プログラム
操作 | 動作 |
---|---|
・計測開始スイッチの押下 ・UARTコマンド"s"の受信 |
・変数time_startへmillis()の値をストア ・計測開始インジケータを点灯 ・ストアした値をUARTへ出力 |
・計測終了スイッチの押下 ・UARTコマンド"e"の受信 |
・変数time_endへmillis()の値をストア ・計測終了インジケータを点灯 ・ストアした値をUARTへ出力 |
・経過時間出力スイッチの押下 ・UARTコマンド"d"の受信 |
・経過時間を計算しUARTへ出力 ・計測開始インジケータと計測終了インジケータを消灯 |
・UARTコマンド"?"の受信 | ・ヘルプメッセージをUARTへ出力 |
/***********************************************************************
* Copyright 2019 ka's@pbjpkas
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
/* pin assign */
#define PUSH_SW_START 2
#define PUSH_SW_END 3
#define PUSH_SW_RESULT 4
#define LED_START 5
#define LED_END 6
/* push switch anti-chattering delay */
#define ANTI_CHATTER_DELAY 5 //msec
void measure_with_stopwatch(unsigned long *timestamp, int led)
{
*timestamp = millis();
digitalWrite(led, HIGH);
Serial.println(*timestamp);
}
unsigned long calc_duration(unsigned long *time_start, unsigned long *time_end)
{
unsigned long duration = 0;
if(*time_start <= *time_end)
{
duration = *time_end - *time_start;
}
else
{
duration = ( 0xffffffff - *time_start ) + *time_end;
}
return duration;
}
void display_result(unsigned long *time_start, unsigned long *time_end)
{
unsigned long duration = 0;
digitalWrite(LED_START, LOW);
digitalWrite(LED_END, LOW);
duration = calc_duration(time_start, time_end);
Serial.println(duration);
}
void print_help(void)
{
Serial.print( F("This is ") );
Serial.print( F(__FILE__) );
Serial.print( F(" ") );
Serial.print( F("Build at ") );
Serial.print( F(__DATE__) );
Serial.print( F(" ") );
Serial.print( F(__TIME__) );
Serial.print( F("\r\n") );
Serial.print( F("s : Start measurement\r\n") );
Serial.print( F("e : End of measurement\r\n") );
Serial.print( F("d : Display of measurement results\r\n") );
}
void setup()
{
pinMode(PUSH_SW_START, INPUT_PULLUP);
pinMode(PUSH_SW_END, INPUT_PULLUP);
pinMode(PUSH_SW_RESULT, INPUT_PULLUP);
pinMode(LED_START, OUTPUT);
pinMode(LED_END, OUTPUT);
digitalWrite(LED_START, LOW);
digitalWrite(LED_END, LOW);
Serial.begin(115200);
}
void loop()
{
unsigned long time_start = 0;
unsigned long time_end = 0;
int port_state_start = HIGH;
int port_state_end = HIGH;
int port_state_result = HIGH;
int port_state;
char buf;
while(1)
{
port_state = digitalRead(PUSH_SW_START);
if(port_state_start != port_state)
{
if(port_state == LOW)
{
measure_with_stopwatch(&time_start, LED_START);
delay(ANTI_CHATTER_DELAY);
}
port_state_start = port_state;
}
port_state = digitalRead(PUSH_SW_END);
if(port_state_end != port_state)
{
if(port_state == LOW)
{
measure_with_stopwatch(&time_end, LED_END);
delay(ANTI_CHATTER_DELAY);
}
port_state_end = port_state;
}
port_state = digitalRead(PUSH_SW_RESULT);
if(port_state_result != port_state)
{
if(port_state == LOW)
{
display_result(&time_start, &time_end);
delay(ANTI_CHATTER_DELAY);
}
port_state_result = port_state;
}
if(Serial.available())
{
buf = Serial.read();
if(buf == 's')
{
measure_with_stopwatch(&time_start, LED_START);
}
if(buf == 'e')
{
measure_with_stopwatch(&time_end, LED_END);
}
if(buf == 'd')
{
display_result(&time_start, &time_end);
}
if(buf == '?')
{
print_help();
}
}
}
}
(略)
スケッチが プログラムストレージ領域の 3,530バイト (10%) を使用しています。最大は 32,256バイト です。
グローバル変数が 182バイト (8%) の 動的メモリを使用しており、ローカル変数に 1,866 バイトが残っています。最高は 2,048バイトです。
Arduino UNO Rev.3に載っているAVRマイコンATmega328PはFlash ROM 32KB、RAM 2KBですがプログラムがコンパクトなのでメモリ空間がとっても広く感じますね。
###3.5 動作確認
オシロ(RIGOL・DS1104Z、100MHz/4ch)で動作確認を行いました。
####3.5.1 計測開始スイッチの押下からLEDが点灯するまでの遅延時間
オシロのCH1はDigital Pin #2(計測開始スイッチの入力ポート)、CH2はDigital Pin #5(計測開始を示すインジケータLED)です。スイッチ押下からLED点灯までの遅延時間を数十回観測したところ約10μs~約25μsでした。変数time_startへmillis()の値をストアするのはLED点灯よりも前のため、スイッチ押下から遅くとも約25μs以内に値がストアされます。ストップウォッチの用途としては十分な速さです。
####3.5.2 チャタリング
こちらはオシロのCH1はDigital Pin #2(計測開始スイッチの入力ポート)、CH2はDigital Pin #3(計測終了スイッチの入力ポート)です。予想に反しチャタリングは見られませんでした。このタクタイルスイッチを使う場合は5ミリ秒のdelay()で十分そうです。
####3.5.3 計測時間
1ミリ秒から120秒まで時間を変えながら約130点の測定を行いオシロと比較しました。
オシロの測定値に対してオシロとArduino Unoの測定値のずれがどれくらいの割合となるかを図示したのが以下のグラフです。1点目(500.0%)、2点目(177.78%)、3点目(87.50%)、5点目(6.67%)はグラフからはみ出していてプロットされていません。N=11(計測時間としては約40ms)以降±1%、N=30(計測時間としては約200ms)以降±0.5%に収まっていてストップウォッチの代替として十分と思います。
測定結果表
2019/12/15 Ver. 2版
N | DS1104Z | Arduino Uno | 差分 | 単位 | DS1104Zの測定値に対する差分の割合 |
---|---|---|---|---|---|
1 | 0.0010 | 0.006 | 0.0050 | [s] | 500.00% |
2 | 0.0018 | 0.005 | 0.0032 | [s] | 177.78% |
3 | 0.0032 | 0.006 | 0.0028 | [s] | 87.50% |
4 | 0.0048 | 0.005 | 0.0002 | [s] | 4.17% |
5 | 0.0075 | 0.008 | 0.0005 | [s] | 6.67% |
6 | 0.0094 | 0.009 | -0.0004 | [s] | -4.26% |
7 | 0.0141 | 0.014 | -0.0001 | [s] | -0.71% |
8 | 0.0192 | 0.019 | -0.0002 | [s] | -1.04% |
9 | 0.0243 | 0.025 | 0.0007 | [s] | 2.88% |
10 | 0.0294 | 0.029 | -0.0004 | [s] | -1.19% |
11 | 0.0379 | 0.038 | 0.0001 | [s] | 0.26% |
12 | 0.0392 | 0.039 | -0.0002 | [s] | -0.51% |
13 | 0.0454 | 0.045 | -0.0004 | [s] | -0.88% |
14 | 0.0564 | 0.056 | -0.0004 | [s] | -0.71% |
15 | 0.0636 | 0.064 | 0.0004 | [s] | 0.63% |
16 | 0.0684 | 0.068 | -0.0004 | [s] | -0.58% |
17 | 0.0804 | 0.080 | -0.0004 | [s] | -0.50% |
18 | 0.0884 | 0.089 | 0.0006 | [s] | 0.68% |
19 | 0.0766 | 0.077 | 0.0004 | [s] | 0.52% |
20 | 0.0912 | 0.091 | -0.0002 | [s] | -0.22% |
21 | 0.0952 | 0.095 | -0.0002 | [s] | -0.21% |
22 | 0.1004 | 0.100 | -0.0004 | [s] | -0.40% |
23 | 0.1056 | 0.106 | 0.0004 | [s] | 0.38% |
24 | 0.1128 | 0.112 | -0.0008 | [s] | -0.71% |
25 | 0.1240 | 0.124 | 0.0000 | [s] | 0.00% |
26 | 0.1352 | 0.134 | -0.0012 | [s] | -0.89% |
27 | 0.1372 | 0.137 | -0.0002 | [s] | -0.15% |
28 | 0.1496 | 0.149 | -0.0006 | [s] | -0.40% |
29 | 0.1726 | 0.173 | 0.0004 | [s] | 0.23% |
30 | 0.1924 | 0.192 | -0.0004 | [s] | -0.21% |
31 | 0.2010 | 0.201 | 0.0000 | [s] | 0.00% |
32 | 0.2320 | 0.232 | 0.0000 | [s] | 0.00% |
33 | 0.2680 | 0.268 | 0.0000 | [s] | 0.00% |
34 | 0.2920 | 0.293 | 0.0010 | [s] | 0.34% |
35 | 0.3220 | 0.321 | -0.0010 | [s] | -0.31% |
36 | 0.3370 | 0.336 | -0.0010 | [s] | -0.30% |
37 | 0.3500 | 0.349 | -0.0010 | [s] | -0.29% |
38 | 0.3730 | 0.373 | 0.0000 | [s] | 0.00% |
39 | 0.4030 | 0.402 | -0.0010 | [s] | -0.25% |
40 | 0.4320 | 0.431 | -0.0010 | [s] | -0.23% |
41 | 0.4590 | 0.458 | -0.0010 | [s] | -0.22% |
42 | 0.4800 | 0.480 | 0.0000 | [s] | 0.00% |
43 | 0.4940 | 0.494 | 0.0000 | [s] | 0.00% |
44 | 0.5400 | 0.539 | -0.0010 | [s] | -0.19% |
45 | 0.5780 | 0.579 | 0.0010 | [s] | 0.17% |
46 | 0.6340 | 0.634 | 0.0000 | [s] | 0.00% |
47 | 0.6720 | 0.671 | -0.0010 | [s] | -0.15% |
48 | 0.7400 | 0.739 | -0.0010 | [s] | -0.14% |
49 | 0.7740 | 0.772 | -0.0020 | [s] | -0.26% |
50 | 0.8380 | 0.837 | -0.0010 | [s] | -0.12% |
51 | 0.9100 | 0.908 | -0.0020 | [s] | -0.22% |
52 | 0.9800 | 0.980 | 0.0000 | [s] | 0.00% |
53 | 1.034 | 1.034 | 0.000 | [s] | 0.00% |
54 | 1.066 | 1.065 | -0.001 | [s] | -0.09% |
55 | 1.050 | 1.048 | -0.002 | [s] | -0.19% |
56 | 1.074 | 1.073 | -0.001 | [s] | -0.09% |
57 | 1.094 | 1.092 | -0.002 | [s] | -0.18% |
58 | 1.138 | 1.139 | 0.001 | [s] | 0.09% |
59 | 1.166 | 1.164 | -0.002 | [s] | -0.17% |
60 | 1.204 | 1.203 | -0.001 | [s] | -0.08% |
61 | 1.246 | 1.241 | -0.005 | [s] | -0.40% |
62 | 1.250 | 1.249 | -0.001 | [s] | -0.08% |
63 | 1.274 | 1.273 | -0.001 | [s] | -0.08% |
64 | 1.306 | 1.303 | -0.003 | [s] | -0.23% |
65 | 1.358 | 1.358 | 0.000 | [s] | 0.00% |
66 | 1.390 | 1.389 | -0.001 | [s] | -0.07% |
67 | 1.418 | 1.416 | -0.002 | [s] | -0.14% |
68 | 1.574 | 1.574 | 0.000 | [s] | 0.00% |
69 | 1.670 | 1.670 | 0.000 | [s] | 0.00% |
70 | 1.766 | 1.766 | 0.000 | [s] | 0.00% |
71 | 1.886 | 1.882 | -0.004 | [s] | -0.21% |
72 | 1.974 | 1.971 | -0.003 | [s] | -0.15% |
73 | 2.006 | 2.008 | 0.002 | [s] | 0.10% |
74 | 2.166 | 2.168 | 0.002 | [s] | 0.09% |
75 | 2.625 | 2.618 | -0.007 | [s] | -0.27% |
76 | 2.885 | 2.877 | -0.008 | [s] | -0.28% |
77 | 3.185 | 3.185 | 0.000 | [s] | 0.00% |
78 | 3.435 | 3.431 | -0.004 | [s] | -0.12% |
79 | 3.845 | 3.837 | -0.008 | [s] | -0.21% |
80 | 3.875 | 3.865 | -0.010 | [s] | -0.26% |
81 | 4.125 | 4.124 | -0.001 | [s] | -0.02% |
82 | 4.405 | 4.403 | -0.002 | [s] | -0.05% |
83 | 4.685 | 4.685 | 0.000 | [s] | 0.00% |
84 | 5.025 | 5.024 | -0.001 | [s] | -0.02% |
85 | 5.410 | 5.412 | 0.002 | [s] | 0.04% |
86 | 5.910 | 5.902 | -0.008 | [s] | -0.14% |
87 | 6.250 | 6.243 | -0.007 | [s] | -0.11% |
88 | 6.750 | 6.760 | 0.010 | [s] | 0.15% |
89 | 7.000 | 6.990 | -0.010 | [s] | -0.14% |
90 | 7.510 | 7.492 | -0.018 | [s] | -0.24% |
91 | 7.910 | 7.897 | -0.013 | [s] | -0.16% |
92 | 8.410 | 8.409 | -0.001 | [s] | -0.01% |
93 | 9.030 | 9.017 | -0.013 | [s] | -0.14% |
94 | 9.630 | 9.624 | -0.006 | [s] | -0.06% |
95 | 10.01 | 9.992 | -0.018 | [s] | -0.18% |
96 | 10.09 | 10.117 | 0.027 | [s] | 0.27% |
97 | 10.65 | 10.662 | 0.012 | [s] | 0.11% |
98 | 10.90 | 10.873 | -0.027 | [s] | -0.25% |
99 | 11.54 | 11.492 | -0.048 | [s] | -0.42% |
100 | 12.06 | 12.059 | -0.001 | [s] | -0.01% |
101 | 12.94 | 12.922 | -0.018 | [s] | -0.14% |
102 | 14.06 | 14.021 | -0.039 | [s] | -0.28% |
103 | 15.10 | 15.054 | -0.046 | [s] | -0.30% |
104 | 16.10 | 16.071 | -0.029 | [s] | -0.18% |
105 | 16.94 | 16.938 | -0.002 | [s] | -0.01% |
106 | 18.14 | 18.140 | 0.000 | [s] | 0.00% |
107 | 19.06 | 19.048 | -0.012 | [s] | -0.06% |
108 | 20.02 | 20.030 | 0.010 | [s] | 0.05% |
109 | 25.15 | 25.120 | -0.030 | [s] | -0.12% |
110 | 30.05 | 30.049 | -0.001 | [s] | 0.00% |
111 | 34.85 | 34.818 | -0.032 | [s] | -0.09% |
112 | 40.05 | 40.040 | -0.010 | [s] | -0.02% |
113 | 45.05 | 45.031 | -0.019 | [s] | -0.04% |
114 | 49.75 | 49.764 | 0.014 | [s] | 0.03% |
115 | 54.65 | 54.643 | -0.007 | [s] | -0.01% |
116 | 60.40 | 60.346 | -0.054 | [s] | -0.09% |
117 | 64.30 | 64.213 | -0.087 | [s] | -0.14% |
118 | 70.10 | 70.046 | -0.054 | [s] | -0.08% |
119 | 75.20 | 75.108 | -0.092 | [s] | -0.12% |
120 | 79.90 | 79.713 | -0.187 | [s] | -0.23% |
121 | 85.50 | 85.480 | -0.020 | [s] | -0.02% |
122 | 90.10 | 90.136 | 0.036 | [s] | 0.04% |
123 | 95.10 | 95.087 | -0.013 | [s] | -0.01% |
124 | 99.90 | 99.913 | 0.013 | [s] | 0.01% |
125 | 103.7 | 103.705 | 0.005 | [s] | 0.00% |
126 | 108.9 | 108.664 | -0.236 | [s] | -0.22% |
127 | 118.3 | 118.376 | 0.076 | [s] | 0.06% |
###3.6 基本的な使い方
- 計測開始スイッチを押下する
- 計測終了スイッチを押下する
- UARTコマンド"d"を送信し戻り値(=経過時間)を受信する
##4. テストランナーの製作
テストツール(ストップウォッチ)をコマンドで操作してテスト結果を取得し、整理(最大値、最小値、平均値、中央値、標準偏差を求める)し、グラフを描くといった性能テストの一連のアクティビティを自動化するExcel VBAのサンプルです。Raspberry Pi、Jenkins、Pythonでテストランナーを作ってテストウェアをGitHubやSubversionで管理するのは今後のネタに取っておき、まずはExcelでスモールスタートします。
今回は5回の測定を行って結果の整理とグラフ作成を行うテストランナーを製作します4。
ボタン | 動作 |
---|---|
Start | ・コマンド"s"を送信し戻り値をアクティブセルに格納する ・アクティブセルを右に1つ移動する (SetStart()を登録しています) |
End | ・コマンド"e"を送信し戻り値をアクティブセルに格納する ・アクティブセルを右に1つ移動する (SetEnd()を登録しています) |
Duration | ・コマンド"d"を送信し戻り値をアクティブセルに格納する ・アクティブセルを左に2つ、下に1つ移動する (GetDuration()を登録しています) |
5回の測定を行って結果の整理とグラフ作成を行う自動テストのデモ動画です。 pic.twitter.com/UG9FI6FGwp
— ka’s (@pbjpkas) December 12, 2019
Option Explicit
Private Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)
Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" _
(ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByVal lpSecurityAttributes As Long, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As Long) As Long
Private Declare Sub CloseHandle Lib "kernel32" _
(ByVal handle As Long)
Private Declare Sub ReadFile Lib "kernel32" _
(ByVal hFile As Long, _
lpBuffer As Any, _
ByVal nNumberOfBytesToRead As Long, _
lpNumberOfBytesRead As Long, _
ByVal lpOverlapped As Long)
Private Declare Sub WriteFile Lib "kernel32" _
(ByVal hFile As Long, _
lpBuffer As Any, _
ByVal nNumberOfBytesToWrite As Long, _
lpNumberOfBytesWritten As Long, _
ByVal lpOverlapped As Long)
'SetCommState
Private Type DCB
DCBlength As Long
BaudRate As Long
Fields As Long
wReserved1 As Integer
XonLim As Integer
XoffLim As Integer
ByteSize As Byte
Parity As Byte
StopBits As Byte
XonChar As Byte
XoffChar As Byte
ErrorChar As Byte
EofChar As Byte
EvtChar As Byte
wReserved2 As Integer
End Type
Private Declare Function SetCommState Lib "kernel32" _
(ByVal hFile As Long, _
ByRef lpDCB As DCB) As Long
Private Declare Function GetCommState Lib "kernel32" _
(ByVal hFile As Long, _
ByRef lpDCB As DCB) As Long
'SetCommTimeouts
Private Type COMMTIMEOUTS
ReadIntervalTimeout As Long
ReadTotalTimeoutMultiplier As Long
ReadTotalTimeoutConstant As Long
WriteTotalTimeoutMultiplier As Long
WriteTotalTimeoutConstant As Long
End Type
Private Declare Sub SetCommTimeouts Lib "kernel32" _
(ByVal hFile As Long, _
lpCommTimeouts As COMMTIMEOUTS)
'SetupComm
Private Declare Function SetupComm Lib "kernel32" _
(ByVal hFile As Long, _
ByVal dwInQueue As Long, _
ByVal dwOutQueue As Long) As Long
'PurgeComm
Private Declare Function PurgeComm Lib "kernel32" _
(ByVal hFile As Long, _
ByVal dwFlags As Long) As Long
'CreateFile
Const GENERIC_READ = (&H80000000)
Const GENERIC_WRITE = (&H40000000)
Const OPEN_EXISTING = 3
'PurgeComm
Const PURGE_TXABORT As Long = &H1
Const PURGE_RXABORT As Long = &H2
Const PURGE_TXCLEAR As Long = &H4
Const PURGE_RXCLEAR As Long = &H8
'Settings
Const COM_PORT = "C4"
Function ArduinoStopWatchReadValue(strCmd As String)
Dim comPort As String
Dim hFile As Long
Dim cs As DCB 'CommState
Dim ct As COMMTIMEOUTS 'CommTimeouts
Dim length As Long
Dim txBuf() As Byte
Dim rxBuf() As Byte
Dim i As Long
Dim char As String
Dim rxStr As String
Dim asciiCode As Integer
Dim strLength As Long
'COM port open
comPort = ActiveSheet.Range(COM_PORT)
hFile = CreateFile(comPort, GENERIC_READ Or GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0)
If hFile = -1 Then
MsgBox ("Can't Open " & comPort)
Exit Function
End If
On Error GoTo hClose
'RS232C通信設定
GetCommState hFile, cs
cs.BaudRate = 115200
cs.ByteSize = 8
cs.Parity = 0
cs.StopBits = 0
cs.Fields = 0 '&H11 'To avoid reset M5Stack at begining serial-communicaton, cs.Fields = &H11
SetCommState hFile, cs
'RS232Cタイムアウト設定
ct.ReadIntervalTimeout = 10
ct.ReadTotalTimeoutMultiplier = 0
ct.ReadTotalTimeoutConstant = 500
ct.WriteTotalTimeoutMultiplier = 0
ct.WriteTotalTimeoutConstant = 500
SetCommTimeouts hFile, ct
SetupComm hFile, 256, 256
PurgeComm hFile, PURGE_TXCLEAR
PurgeComm hFile, PURGE_RXCLEAR
strLength = Len(strCmd)
ReDim txBuf(strLength)
For i = 0 To strLength - 1
char = Mid(strCmd, i + 1, 1)
asciiCode = Asc(char)
txBuf(i) = CByte(asciiCode)
'Debug.Print Chr(txBuf(i))
Next
txBuf(i) = &HD 'add 0x0D(CR)
WriteFile hFile, txBuf(0), strLength + 1, length, 0
Sleep 1 'To avoid reset M5Stack
ReDim rxBuf(256)
ReadFile hFile, rxBuf(0), 256, length, 0
For i = 0 To 255
If rxBuf(i) = &HD Or rxBuf(i) = &HA Then
Exit For
Else
rxStr = rxStr + Chr(rxBuf(i))
End If
Next
ActiveCell.Value = CDbl(rxStr) / 1000# '2019/12/14 update
CloseHandle hFile
Exit Function
hClose:
CloseHandle hFile
End Function
Sub SetStart()
Call ArduinoStopWatchReadValue("s")
ActiveCell.Offset(0, 1).Select
End Sub
Sub SetEnd()
Call ArduinoStopWatchReadValue("e")
ActiveCell.Offset(0, 1).Select
End Sub
Sub GetDuration()
Call ArduinoStopWatchReadValue("d")
ActiveCell.Offset(1, -2).Select
End Sub
##5. ステップ数
かぞえチャオ!Ver.1.68でステップ数を数えました(ファイル名はかぞえチャオで開けるよう適当に変更しています)。
パス名 | モジュール名 | 種類 | 総ステップ数 | 実ステップ数 | コメント[%] |
---|---|---|---|---|---|
ArduinoUnoStopWatch.c | モジュール外 | c | 26 | 6 | 81.4 |
ArduinoUnoStopWatch.c | void measure_with_stopwatch | c | 7 | 6 | 0 |
ArduinoUnoStopWatch.c | double calc_duration | c | 16 | 13 | 0 |
ArduinoUnoStopWatch.c | void display_result | c | 9 | 8 | 0 |
ArduinoUnoStopWatch.c | void print_help | c | 15 | 14 | 0 |
ArduinoUnoStopWatch.c | void setup | c | 12 | 11 | 0 |
ArduinoUnoStopWatch.c | void loop | c | 69 | 63 | 0 |
ArduinoUnoStopWatchTestRunner.bas | モジュール外 | bas | 89 | 71 | 2.7 |
ArduinoUnoStopWatchTestRunner.bas | Function ArduinoStopWatchReadValue | bas | 84 | 62 | 11.1 |
ArduinoUnoStopWatchTestRunner.bas | Sub SetStart | bas | 5 | 4 | 0 |
ArduinoUnoStopWatchTestRunner.bas | Sub SetEnd | bas | 5 | 4 | 0 |
ArduinoUnoStopWatchTestRunner.bas | Sub GetDuration | bas | 4 | 4 | 0 |
全ステップ数 | 341 | 266 | 12 |
実ステップ数はストップウォッチ121行、テストランナー145行、トータル266行でした。
##6. おわりに
- ストップウォッチの代替となるものをArduinoと数点のパーツでサクッと作ることができました\(^o^)/
- 実ステップ300行に満たないテスト自動化システムとはいえ投入している技術を並べてみると抽象的なソフトウェアのレイヤから具象的な物理レイヤまでQAはフルスタックだなあと改めて思いました5。
- 組込みCプログラミング
- ポインタ
- LEDで動作タイミング確認
- Excel VBAプログラミング
- Windows API呼び出し
- 回路設計
- オームの法則
- 部品選定
- チャタリング除去(今回はソフトウェアで実装;delay())
- オシロで測定
- テスト結果の整理
- 最大値、最小値、平均値、中央値、標準偏差
- 組込みCプログラミング
- プログラム更新に伴い測定をやり直しましたが、Excelからオシロとストップウォッチにコマンドを送って値を自動取得するようにしていなかったら手作業での転記で心が折れるところでした。自動化しておいて良かったです。
- 時間を計るだけの単機能なテストツールにもかかわらず記事にまとめたら結構なボリュームになってしまいました。最後までご覧いただきありがとうございます。
##7. 付録:Unit Testのテストコードをマージしたスケッチ
- ArduinoUnitでUnit Testを行うで作成したテストコードをマージしたスケッチです。詳しくはこちらをご参照ください。
- Arduino IDE 1.8.9でビルドを確認しています6。
/***********************************************************************
* Copyright 2019 ka's@pbjpkas
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
#define ENABLE_UT 0
#if ENABLE_UT
#line 19 "ArduinoUnoStopWatchV3.ino"
#include <ArduinoUnit.h>
#endif
/* pin assign */
#define PUSH_SW_START 2
#define PUSH_SW_END 3
#define PUSH_SW_RESULT 4
#define LED_START 5
#define LED_END 6
/* push switch anti-chattering delay */
#define ANTI_CHATTER_DELAY 5 //msec
void measure_with_stopwatch(unsigned long *timestamp, int led)
{
*timestamp = millis();
digitalWrite(led, HIGH);
Serial.println(*timestamp);
}
unsigned long calc_duration(unsigned long *time_start, unsigned long *time_end)
{
unsigned long duration = 0;
if(*time_start <= *time_end)
{
duration = *time_end - *time_start;
}
else
{
duration = ( 0xffffffff - *time_start ) + *time_end;
}
return duration;
}
void display_result(unsigned long *time_start, unsigned long *time_end)
{
unsigned long duration = 0;
digitalWrite(LED_START, LOW);
digitalWrite(LED_END, LOW);
duration = calc_duration(time_start, time_end);
Serial.println(duration);
}
void print_help(void)
{
Serial.print( F("This is ") );
Serial.print( F(__FILE__) );
Serial.print( F(" ") );
Serial.print( F("Build at ") );
Serial.print( F(__DATE__) );
Serial.print( F(" ") );
Serial.print( F(__TIME__) );
Serial.print( F("\r\n") );
Serial.print( F("s : Start measurement\r\n") );
Serial.print( F("e : End of measurement\r\n") );
Serial.print( F("d : Display of measurement results\r\n") );
#if ENABLE_UT
Serial.print( F("t : run Unit Test\r\n") );
#endif
}
void setup()
{
pinMode(PUSH_SW_START, INPUT_PULLUP);
pinMode(PUSH_SW_END, INPUT_PULLUP);
pinMode(PUSH_SW_RESULT, INPUT_PULLUP);
pinMode(LED_START, OUTPUT);
pinMode(LED_END, OUTPUT);
digitalWrite(LED_START, LOW);
digitalWrite(LED_END, LOW);
Serial.begin(115200);
}
void loop()
{
unsigned long time_start = 0;
unsigned long time_end = 0;
int port_state_start = HIGH;
int port_state_end = HIGH;
int port_state_result = HIGH;
int port_state;
char buf;
while(1)
{
port_state = digitalRead(PUSH_SW_START);
if(port_state_start != port_state)
{
if(port_state == LOW)
{
measure_with_stopwatch(&time_start, LED_START);
delay(ANTI_CHATTER_DELAY);
}
port_state_start = port_state;
}
port_state = digitalRead(PUSH_SW_END);
if(port_state_end != port_state)
{
if(port_state == LOW)
{
measure_with_stopwatch(&time_end, LED_END);
delay(ANTI_CHATTER_DELAY);
}
port_state_end = port_state;
}
port_state = digitalRead(PUSH_SW_RESULT);
if(port_state_result != port_state)
{
if(port_state == LOW)
{
display_result(&time_start, &time_end);
delay(ANTI_CHATTER_DELAY);
}
port_state_result = port_state;
}
if(Serial.available())
{
buf = Serial.read();
if(buf == 's')
{
measure_with_stopwatch(&time_start, LED_START);
}
if(buf == 'e')
{
measure_with_stopwatch(&time_end, LED_END);
}
if(buf == 'd')
{
display_result(&time_start, &time_end);
}
#if ENABLE_UT
if(buf == 't')
{
while(1){ Test::run(); }
}
#endif
if(buf == '?')
{
print_help();
}
}
}
}
#if ENABLE_UT
test(calc_duration_start_lt_end)
{
unsigned long test_time_start = 1400;
unsigned long test_time_end = 1800;
unsigned long duration = calc_duration(&test_time_start, &test_time_end);
assertEqual(400, duration);
}
test(calc_duration_start_eq_end)
{
unsigned long test_time_start = 1400;
unsigned long test_time_end = 1400;
unsigned long duration = calc_duration(&test_time_start, &test_time_end);
assertEqual(0, duration);
}
test(calc_duration_start_gt_end)
{
unsigned long test_time_start = 0xffffffff-1200;
unsigned long test_time_end = 1200;
unsigned long duration = calc_duration(&test_time_start, &test_time_end);
assertEqual(2400, duration);
}
#endif
更新履歴
No. | 日付 | 理由 | 内容 |
---|---|---|---|
1 | 2019/12/14 | ・Arduino Uno Rev.3のシステムクロックの周波数許容偏差が±0.50%以内あり、本システムでの時間計測が適するもの、適さないものがある | ・2章の想定する利用シーンに補足を追記 |
2 | 2019/12/14 | ・Arduino Unoでunsigned long型のmillis()の戻り値をdouble型にキャストすると精度がfloat型に落ちてしまう(Unoやその他ATMEGAベースのボードのdouble型の実装はfloat型と同一のため) ・float型の変数同士で演算を行うことによる計算誤差を避けたい |
・Arduino側はunsigned longのまま処理を行いミリ秒の値をUARTへ出力 ・Excel側でDouble型にキャストして秒に変換 ※プログラムと3.6基本的な使い方の図を更新 |
3 | 2019/12/15 | ・プログラム更新に伴う再測定 | 以下の章、節を更新 ・3.5.1 計測開始スイッチの押下からLEDが点灯するまでの遅延時間 ・3.5.3 計測時間 ・6 おわりに |
4 | 2019/12/31 | ・Unit Testのテストコードを移植 | ・7.付録を追加 |
-
UARTに出力すればExcel VBAでもPythonでも拾えるしいったんCI/CDに組み込んだらストップウォッチの画面は見ないと思うので。 ↩
-
待ち時間を数十ミリ秒より大きくしてもチャタリングが発生する場合はスイッチを別のものに交換することをお勧めします。チャタリング除去をdelay()で済ませているためこのストップウォッチはスイッチの品質に強く依存します。 ↩
-
筆者はVer 1.6.4はArduino用、Ver 1.8.xはM5Stack用と分けています。 ↩
-
テストベンチにテキストデータで制御する機器とバイナリデータで制御する機器が混在しても対応できるよう、バイナリ通信対応版を移植しています。 ↩
-
テストツールの開発はソフトウェアのほかにも無鉛はんだでの実装やケース加工などが伴うこともあります。「テスト(QA)エンジニアであっても、開発の知識がフルスタックで必要になる」(テスト設計コンテスト'20 OPENクラス チュートリアル p.42)という話がありますが開発者が専門分野を深掘りするのに対してQAは横に広げていく印象があります。 ↩
-
Arduino IDE 1.6.4ではArduinoUnitを組み込んだスケッチのビルドが通りませんでした。 ↩