本来はイーサネットシールドを利用してインターネットに接続するのだそうですが、今回は Arduino をパソコンに USB 接続した状態で制御させます。
参考にさせていただいたサイトは以下です。ありがとうございました。
1. Blynk アプリの設定
俺は Android なので、Play ストアから Blynk アプリをインストール。
起動したらまずアカウントを作成します。
次に新しいプロジェクトを作ります。デバイスは「Arduino Nano」、接続タイプは「USB」としておきます。プロジェクトを作るとアカウントのメールアドレスへトークンが送られてきます。このトークンが制御する Arduino を識別することになります。
ウィジェットからボタンを選択し、アウトプットを「Digital D13」、モードを「SWITCH」とします。
2. Arduino の設定
GitHUB から blynk-library-master.zip をダウンロードし、IDE の「.ZIP 形式のライブラリをインストール」で取り込みます。
サンプルスケッチは /Arduino/libraries/blynk-library-master/examples/Boards_USB_Serial/ にある「Arduino_Serial_USB」です。この中の「YourAuthToken」を送られてきたトークンに書き換えて、Arduino に書き込みます。
3. パソコンの設定
USB 接続で Arduino を制御するために blynk-ser.sh を起動させる必要があります。blynk-ser.sh は /Arduino/libraries/blynk-library-master/scripts にあります。
まずこれに実行権限を与えます。
$ chmod +x blynk-ser.sh
実行してみます。
$ ./blynk-ser.sh
This script uses socat utility, but could not find it.
Try installing it using: sudo apt-get install socat
socat がないぞと叱られました。インストールしましょう。
$ sudo apt update
$ sudo apt install socat
もう一度 blynk-ser.sh を実行してみましょう。
$ ./blynk-ser.sh
Resetting device /dev/ttyUSB0...
[ Press Ctrl+C to exit ]
Connecting: FILE:/dev/ttyUSB0, ........:
:
この状態でスマホ側の Blynk アプリからプロジェクトを起動すると、ボタン操作で Arduino の内臓 LED を点滅することができました。
blynk-ser.sh は Ctrl+C で中断できます。Arduino のスケッチを修正する場合などは中断して、USB を開放する必要があります。
ほんとの基本的なものでしたが、スマホから制御できるようになると一段と IoT 機器らしくなってきますね。
]]>
TUTORIALS > Built-In Examples > 02.Digital > StateChangeDetection
フローチャートを書いてみました。
詳細な解説はしませんが、ボタンの状態によって分岐する流れが赤線のようになっています。
スケッチを書き込んで起動すると、最初は点灯します。ボタンを押すと消灯し、4回目の押下で再び点灯します。
このスケッチでは、チャタリング防止 (デバウンス) は delay(50) で行なっています。前回の「Debounce」のような方法との使い分けってどうすれば良いのでしょう? 暇なときにでも考えてみます (^_^;)
]]>
TUTORIALS > Built-In Examples > 02.Digital > Debounce
Debounce
Pushbuttons often generate spurious open/close transitions when pressed, due to mechanical and physical issues: these transitions may be read as multiple presses in a very short time fooling the program. This example demonstrates how to debounce an input, which means checking twice in a short period of time to make sure the pushbutton is definitely pressed. Without debouncing, pressing the button once may cause unpredictable results. This sketch uses the
millis()
function to keep track of the time passed since the button was pressed.
2番ピンに接続されたスイッチを押すたびに 内蔵 LED が点灯と消灯に切り替わるというものですが、この場合に問題となるスイッチのチャタリングを防止するためのスケッチ例です。
スケッチをパッと見てもどうなっているのかよくわかりませんでしたので、フローチャートを書いてみました。
赤い部分がデバウンスを行なっています。
ボタンの状態が変化したことを検出したら lastDebounceTime をリセットし、debounceDelay (50ms) の間は LED 出力を変化させません。ボタンの状態が変化なく 50ms 経過したら LED の制御処理を実行しますが、変化があったときは再び lastDebounceTime をリセットし、変化がなくなるのを待っています。
こうしてデバウンス部分を分けてみると、とても単純な処理をしていることがわかりますね。
ちなみに、このスケッチ例では ledState を反転させるために「!」でブーリアン値の否定を利用しています。俺の好きな XOR によるビット操作 ledState ^= 1 よりわかりやすいです (^_^;)
]]>
TUTORIALS > Built-In Examples > 02.Digital > BlinkWithoutDelay
回路図では外部に LED を接続するようになっていますが、内蔵 LED を点滅させるので必要はありません。スケッチ例をそのまま IDE にコピーアンドペーストし、Arduino へ書き込めば完成ですね。
スケッチ例はコメントが多くて見にくいので、必要な部分だけ抜き出してみました。
const int ledPin = LED_BUILTIN;
int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 1000;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
}
delay を使わずに、millis() でインターバル時間を超えたかどうかをチェックする方法ですね。
俺が気になったのは、LED の点滅のために ledState を反転させる部分 (16〜20行) です。if() で判定するのはとってもわかりやすいですが、別の方法に変更してみましょう。
ledState = ~ledState;
ledState を NOT 演算で反転させてみました。この場合は LOW=0 、HIGH=-1 となります。ビットがすべて反転しているということ。LED の点滅は正常に行われていますので、たぶん 1 ビット目の値で制御しているのでしょう。
NOT はビット単位の否定ですが、次の例はブーリアン値で反転させます。
ledState = !ledState;
出力は 0 と 1 になりました。この場合は、変数をブール型にして値は true - false とするべきなのでしょうね。
次の例は、1 ビット目だけを反転させる方法です。XOR 演算でビット操作しています。
ledState = ledState^1;
これは複合演算子を使って、
ledState ^= 1;
のように書くことができます。個人的にはこの方法が好きですが、理論的にどれがよいのかは、俺にはわかりません (^_^;)
]]>
参考にさせていただいたサイトは「ubuntu 環境の Python」です。
linuxBean 16.04 に入っている Python のバージョンは、2系が 2.7.12 、3系が 3.5.2 です。最新バージョンは 3.7.4 で、これをインストールしてみましょう。
1. ビルドツール・ライブラリのインストール
$ sudo apt update $ sudo apt install build-essential libbz2-dev libdb-dev ¥ libreadline-dev libffi-dev libgdbm-dev liblzma-dev ¥ libncursesw5-dev libsqlite3-dev libssl-dev ¥ zlib1g-dev uuid-dev tk-dev
必要なツールとライブラリの準備です。個々の内容はわかりません (^_^;) build-essential はインストール済みだと思うけど、まぁコピー&ペーストでそのまま実行です。
GUI では、synaptic パッケージマネージャからインストールできると思います。
2. ソースコードのダウンロード
Gzipped source tarball から Python-3.7.4.tgz をダウンロードし、展開します。
$ tar xzf Python-3.7.2.tgz
コマンドでもいいんですが、ブラウザでダウンロードしたらダウンロードフォルダを開いて、アーカイブマネージャで展開するのが簡単ですよ。展開してできたフォルダ Python-3.7.4 に入ったら、「現在のフォルダを端末で開く」すれば次のコマンドの「cd Python-3.7.4」まで完了です。
3. ビルド
ビルドとインストールはコマンドで。
$ cd Python-3.7.4 $ ./configure --enable-shared $ make $ sudo make install $ sudo sh -c "echo '/usr/local/lib' > /etc/ld.so.conf.d/custom_python3.conf" $ sudo ldconfig
バージョンの確認は以下です。
$ python3 --version
Python 3.7.4
ということで、無事 3.7.4 にアップデートできました。
]]>
送られてくるのは文字列なので、そいつを数値に変換するためにいろいろ細工が必要になる。でもきっと何か、もっと簡単な方法があるはずだと思っていたわけですが、なんと、あるじゃないですか、数値をそのまま取り出してくれる関数が。
parseInt()
Description
parseInt() returns the first valid (long) integer number from the serial buffer. Characters that are not integers (or the minus sign) are skipped.
しかも附属のスケッチ例にこんなのがあります。
Read ASCII String
This sketch uses the Serial.parseInt() function to locate values separated by a non-alphanumeric character. Often people use a comma to indicate different pieces of information (this format is commonly referred to as comma-separated-values or CSV), but other characters like a space or a period will work too. The values are parsed into integers and used to determine the color of a RGB LED. You'll use the Arduino Software (IDE) serial monitor to send strings like "5,220,70" to the board to change the light color.
/*
Reading a serial ASCII-encoded string.
This sketch demonstrates the Serial parseInt() function.
It looks for an ASCII string of comma-separated values.
It parses them into ints, and uses those to fade an RGB LED.
Circuit: Common-Cathode RGB LED wired like so:
- red anode: digital pin 3
- green anode: digital pin 5
- blue anode: digital pin 6
- cathode: GND
created 13 Apr 2012
by Tom Igoe
modified 14 Mar 2016
by Arturo Guadalupi
This example code is in the public domain.
*/
// pins for the LEDs:
const int redPin = 3;
const int greenPin = 5;
const int bluePin = 6;
void setup() {
// initialize serial:
Serial.begin(9600);
// make the pins outputs:
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
}
void loop() {
// if there's any serial available, read it:
while (Serial.available() > 0) {
// look for the next valid integer in the incoming serial stream:
int red = Serial.parseInt();
// do it again:
int green = Serial.parseInt();
// do it again:
int blue = Serial.parseInt();
// look for the newline. That's the end of your sentence:
if (Serial.read() == '¥n') {
// constrain the values to 0 - 255 and invert
// if you're using a common-cathode LED, just use "constrain(color, 0, 255);"
red = 255 - constrain(red, 0, 255);
green = 255 - constrain(green, 0, 255);
blue = 255 - constrain(blue, 0, 255);
// fade the red, green, and blue legs of the LED:
analogWrite(redPin, red);
analogWrite(greenPin, green);
analogWrite(bluePin, blue);
// print the three numbers in one string as hexadecimal:
Serial.print(red, HEX);
Serial.print(green, HEX);
Serial.println(blue, HEX);
}
}
}
スケッチ例ではカラー LED を使用していますが、点灯を試すだけなら 3個の LED を使えばいいです。シリアルモニターから 3つの数値をカンマ区切りで送信すると、その値に応じた色 (明るさ) で LED が点灯します。
シリアルポートから送られてくるデータが文字列だの数値だの考えなくても、そのまま数値として出力できました。
なお、Serial.parseInt() はタイムアウトで終了しますので、cu のように入力した数値を即送信してしまう場合は、タイムアウトするまでにすべてのデータを送信する必要があります。シリアルモニターのようにデータをまとめて送信するものであれば問題はないです。
プログラムからなら、データをまとめて最後に改行コードをつけて送信すればよいので、なんとでもなると思います。
ということで、シリアル通信で数値を送ることも難しく考える必要なんてないことがわかりました。やっぱりなぁ、日本語リファレンスだけじゃなくて、ちゃんと REFERENCE を読まないといけないんだよなぁ (^_^;)
]]>
前回は数字を 3つ入力すると LED の明るさが変わるというものでした。これを 0 から 255 までの数字を入力して Enter を押すと、その値に従って LED の明るさを変えるようにしたいと思います。
そのために、桁数の異なる数字をシリアル通信で Arduino に送り、数値として PWM 制御に渡す必要があります。少しググってみたのですが、シリアル通信で数値を送るには、文字列を数値に替えるだけでなく、桁数の違いをどう処理するかなど、けっこう面倒な雰囲気です。単純な処理なのでもっと簡単な方法がないかなと探していたところ、こんなチュートリアルを見つけました。
The toInt() function allows you to convert a String to an integer number.
In this example, the board reads a serial input string until it sees a newline, then converts the string to a number if the characters are digits.
ドンピシャです。文字列を数値に変換する、まさに求めていた内容ですよ。
toInt() は String オブジェクトの関数です… よくわかりません (^_^;) でもこうやって使うとこういう結果が出るということは、簡単に理解できます。なお、このチュートリアルにあるサンプルコードは、IDE のスケッチ例「08.String」に「StringToint」として附属しています。
さて、そのサンプルコードを参考にした、cu から送信するデータに従って 3番ピンに接続した LED を PWM 制御するスケッチです。
// PWM control LED from cu 2019/09/09 meyon
const int ledPin = 3;
String inStr = "";
void setup()
{
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
}
void loop()
{
while(0 < Serial.available()) {
int inChar = Serial.read();
Serial.write(inChar);
if(isDigit(inChar)) {
inStr += (char)inChar;
}
if(13 == inChar) {
int ledBri = inStr.toInt();
ledBri = constrain(ledBri, 0, 255);
analogWrite(ledPin, ledBri);
Serial.print("¥nBrightness: ");
Serial.println(ledBri);
inStr = "";
}
}
}
16行目は cu へのエコーです。Serial.write() はデータを byte 型で出力するので、受け取った数字の ASCII コードをそのまま cu へ送り返し、入力した数字を表示させることができます。
18行目で入力データが数字かどうかを判断し、数字なら文字列として inStr に追加していきます。
22〜25行目。入力データが Enter の場合 ASCII コード (13) が届きますので、それまでに届いた文字列を数値に変換して 3番ピンへアナログ出力します。
数値は 0〜255 の範囲に制限し、256 以上の場合は 255 としています。int 型なので 32767 を超えると出力は 0 になってしまいますね。また、入力に数字以外が含まれていると無視されます。数字だけを抽出して制御値になります。
これでかなりエクセレントになった気がします (^_^;)
]]>
ubuntu では cu コマンドを利用します。まずはインストール。
$ sudo apt install cu
起動・接続は以下。切断・終了は「~.」ですが、Arduino で受信したデータを送り返すスケッチを動かしていると ~. も折り返されてくるだけで終了できません (^_^;) 正しい方法かどうかわからないのですが、Ctrl+c 押した後に ~. で終了できました。
$ cu -s 9600 -l /dev/ttyACM0
シリアルモニタとは動作が異なります。
シリアルモニタでは入力した文字列が「送信」ボタンを押すことで一括して送られましたが、cu では入力ごとに即送信され、たとえば「A」キーを押すとすぐに「65」が返ってきます。
そこで、数字を 3つ入力するとその値で LED の明るさを制御するようなスケッチを書いてみました。
LED は 3番ピンに繋ぎますが、最大で 20mA 程度流しますので LED 駆動用にトランジスタを 1個入れてます。まぁ毎度の回路です。電源は USB を刺しているので、Arduino の 5V 出力を利用しました。
const int ledPin=3;
void setup() {
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
}
void loop() {
while(0 < Serial.available()) {
int data = Serial.read();
Serial.println(data);
static int val[3];
static int i=0;
val[i]=data-48;
i++;
if(2<i) {
int bri = val[0]*100+val[1]*10+val[2];
bri = constrain(bri, 0, 255);
Serial.print("Brightness: ");
Serial.println(bri);
analogWrite(ledPin, bri);
i=0;
}
}
}
Arduino へ書き込んだら IDE を終了します。
cu を起動し接続。数字を 3つ押すごとに LED への出力が変化します。「255」 と押すと LED が 100% で点灯します。「128」なら 50% 、「000」で消灯です。入力値が 255 を超える場合は 255 にしますが、contrain() なんて便利な関数があったので使ってみました。
受信したデータは 48 を引くことで数値に変換しています。3つデータを受けたら 3桁の数字に計算しているだけですが、このあたりがどうもエクセレントじゃないですよねぇ (^_^;)
]]>
では、シリアルモニタから Arduino へ送信する場合はどうでしょうか。簡単なスケッチで試してみます。
void setup() {
Serial.begin(9600);
}
void loop() {
if(Serial.available() > 0) {
int data = Serial.read();
Serial.println(data, DEC);
}
}
シリアルモニタから「A」と送信すると、
65
10
と表示されました。
シリアルモニタ側で「A」と入力すると、文字「A」の ASCII コード (65) がシリアルポートへ送られるはずです。
Arduino 側では、Serial.read() で読み取ったデータを数値変数 data に代入し、Serial.println() で 10 進数のフォーマットで出力しています。そこで表示されたのが「65」ということは、Serial.read() がシリアルポートから送られてきた ASCII コードそのものを読み取っている、ということです。
次に表示されている「10」は、シリアルモニタから送信されるときに付加された改行コード LF のASCII コードです。シリアルモニタでは、入力されたデータは「送信」ボタンを押したときに改行コードを付加してまとめて送出されています。
ということはですよ、入力した文字をそのまま表示させるには、読み取った ASCII コードを文字に変換すればいいんじゃないですか?
char data = Serial.read();
Serial.println(data);
スケッチの一部を上のように書き換えます。シリアルモニタで「A」と入力すると、
A
と出力されました。ASCII コード (65) を文字変数 data に代入すると、その中身は文字「A」となりますね。そーゆーことです (^_^;)
ところで、「ABC」と入力するとどうなりますか?
A
B
C
となってしまいます。スケッチをちょっと工夫しましょう。
void loop() {
if(Serial.available() > 0) {
char data = Serial.read();
if(10 == data) {
data = '¥n';
}
Serial.print(data);
}
}
末尾に改行コード LF がついてくるのですから、LF のときだけ改行するようにしました。これで出力は、
ABC
となりました。
これでシリアル通信の状況がわかりました。わかってしまえば、まぁなんてことないですよねぇ (^_^;) たぶん、ね。
]]>
Reference の Serial.print() を確認してみました。
Prints data to the serial port as human-readable ASCII text. This command can take many forms. Numbers are printed using an ASCII character for each digit. Floats are similarly printed as ASCII digits, defaulting to two decimal places. Bytes are sent as a single character. Characters and strings are sent as is.
データは ASCII コードで出力されます。数字も各桁ごとに ASCII コードになるようですね。浮動小数点数も同じですが、小数点以下 2桁になるとのこと。
バイト形式は一つの文字として送るということなので、この場合は数字のようにバラバラにはならないということでしょうか。あとで確認してみましょう。
文字と文字列は「そのまま」です。
では、実際に確認してみます。ちなみに Serial.println() はデータの末尾に改行コード (CR+LF) を付加する関数で、それ以外は Serial.print() と同じです。
文字列を送ってみます。
Serial.println("hoge");
シリアルモニタには「hoge」とそのまま表示されます。内部的には ASCII コードで送るのでしょうが、文字形式は「そのまま」の文字として出力されるということです。
変数ではどうでしょうか。
char str[]="Hello world!";
Serial.println(str);
変数の場合も意図したように、そのまま「Hello world!」と表示されました。
数字形式で試してみます。
int num=65;
Serial.println(num);
シリアルモニタには「65」と表示されました。この場合は「65」が文字列としてそのまま送信されたということなのでしょう。
浮動小数点数を送ってみましょう。
float num=12.345678;
Serial.println(num);
「12.34」と小数点以下 2桁になって表示されました。
数字では表示フォーマットを指定できます。
int num=65;
Serial.println(num, BIN);
この場合は「1000001」と 2進数形式で表示されます。あくまでも文字列として扱っているのですから、BIN を指定した場合は改行コードを含んで 9 バイト送信しているということになるのでしょうか。
なお、浮動小数点数の場合はフォーマットで小数点以下の桁数を指定できるようです。
バイト形式を試してみましょう。バイト形式はひとつの文字として送るということでした。
byte b=B01000001;
Serial.println(b);
「65」と表示されました。B01000001 は 10 進数の「65」を表していますので、文字列「65」を送信したってことですね。
では、これはどうでしょうか。
byte b='A';
Serial.println(b);
文字「A」をバイト形式にして送信すると、シリアルモニタには「65」と表示されました。「A」の ASCII コードの値「65」が送信されたということですね。
フォーマットを指定してみます。
byte b='A';
Serial.println(b, BIN);
今度は「1000001」と表示されました。「65」が 2進数形式で送信されたということです。
さてと、こうしてみてくるとなんだか様子がわかってきました。Serial.print() 、Serial.println() では、
ってことです。わかってしまえば当たり前のことですけど、どうやら俺は、数字と ASCII コードを混同してわけわからなくなってしまうようです。
次はもう一度、シリアルモニタから Arduino へ送る場合を試してみたいと思います。
]]>
まず、Reference の Serial.available() にあるサンプルスケッチで試してみましょう。
int incomingByte = 0; // for incoming serial data
void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop() {
// reply only when you receive data:
if (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.read();
// say what you got:
Serial.print("I received: ");
Serial.println(incomingByte, DEC);
}
}
シリアルインターフェースから受信したデータをそのまま送り返すスケッチです。シリアルモニタを開いて試してみました。
入力 | 出力 |
123 | I received: 49 I received: 50 I received: 51 I received: 10 |
ABC | I received: 65 I received: 66 I received: 67 I received: 10 |
シリアルモニタから「123」と入力して送信すると、出力のように表示されます。ここで、あれ?と思っちゃう。そのまま送り返しているはずなのに、違うじゃん (^_^;)
よーく考えてみると、文字「1」の ASCII コードは「49」、文字「2」は「50」です。入力したデータが ASCII コードとしてやり取りされている、ということですね。
最後の「10」は改行コードの LF を表します。シリアルモニタでは送信ボタンをクリックすると LF が送られるんです。シリアルモニタの右下の「LFのみ」を「CRのみ」とすると改行コード CR の「13」が表示されます。
シリアルモニタから入力された文字列は Arduino に送られてシリアルバッファに格納されます。Serial.read() を実行するとバッファの最初の 1 バイトが読み取られます。そして最初の 1 バイトはクリアされる (のじゃないかと思います) 。Serial.read() を繰り返すたびに 1 文字ずつ読み取っていき、読み取れるデータがなくなったら次のデータを待ちます。
そんな仕組み。間違っているかもしれません。間違っていたら笑ってやってください。
ところで、これを文字列にしようと思ったら 1 文字ずつ配列変数に格納するなどしなくてはいけません。Serial.available() で文字数を取得して配列変数を定義し、1 文字ずつそこに格納していくって感じかな。難しくはないけれど、面倒くさい (^_^;)
文字列を簡単に読み取れないのかなぁとググってみたら、Serial.readString() という関数があることがわかりました。ただ、この関数はタイムアウト (デフォルトは 1000ms) で終了するので、処理が遅いのが欠点。実際試してみると、データを送信して 1 秒後に表示されます。
そこで、Serial.readStringUntil( terminator ) 関数を使うことにしました。terminator に '¥n' を指定すると、改行コードを受信した時点で終了してくれます。
作り直したスケッチはこんな感じ。変数名やコメントは変えてないので、ちょっと違和感もありますが、ご容赦。
String incomingByte; // for incoming serial data
void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop() {
// reply only when you receive data:
if (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.readStringUntil('¥n');
// say what you got:
Serial.print("I received: ");
Serial.println(incomingByte);
}
}
これで入力した文字列と同じ文字列が出力に表示されるようになりました。
]]>
AtCoder Beginner Contest 138 A - Red or Not
問題文
整数 a と、英小文字からなる文字列 s が入力されます。
a が 3200 以上なら s と出力し、 a が 3200 未満なら
red
と出力するプログラムを書いてください。
難しいことは何もないのですが、いざソースを書こうとしたら文字列の扱い方がわからない (^_^;)
文字列は配列に入れる。文字数が10文字ならば最後にヌルを付けて11文字分必要になるので、
char s[11];
scanf("%s", s);
とする。間違えて s[10] としてもヌルの分は自動的に足してくれるらしい。s[] としておけば必要なだけ割り当ててくれる。
代入するときは s[] じゃなくて s だけ書く。これはポインタを示しているらしい。
出力は同様に、
printf("%s¥n", s);
とすれば、文字列として出力される。
printf("%c¥n", s[2]);
のようにすると、3 文字目が出力される。
まぁそんな感じかな。
ってことでこんなふうになりました。
#include <stdio.h>
int main()
{
int a;
char s[11];
scanf("%d %s", &a, s);
if (3200<=a) {
printf("%s¥n", s);
} else {
printf("red¥n");
}
return 0;
}
なお、ソースコードの表示には「srctohtml ソースをHTMLで見やすく出力するツール」を利用させていただきました。ありがとうございます。
]]>
使っていない CPU 冷却ファンがあったので、これを回してみましょう。
ファンから出ている線は 4 芯です。ググってみると、4 芯のものは PWM で回転数制御ができるんだとか。電源に 12V を与えておき、control に 25KHz の PWM 信号を入れてやるとそのデューティー比によって回転数が変化する。また、sense には 1 回転あたり 2 パルス (デューティー比 50%) の信号がオープンコレクタで出てくる。らしい。
このファン自体の仕様はわからなかったのですが、汎用品のようですのでたぶん同じでしょう。
ということで、回路図。Arduino Nano 互換品を使いました。
ジャンクのサーミスタがあったので、これで温度を検出し回転数を制御することにします。
このサーミスタはレーザープリンタの定着部の部品で 200℃ 程度の計測がベストなんですけど、まぁテキトーに使いましょ。この定数での入力値は、約 26℃ で 830 、約 32℃ で 780 ぐらいになりました。
Arduino の Vin に 12V を入れていますが、USB をつないだまま 12V を落としたときに 5V が逆流するのを防ぐためにダイオードを入れています。実験中は電源を切ることがよくありますのでね。
control は 3.3V 推奨なので、ツェナーダイオードで降圧しています。
control の電流の実測値は吐き出しで約 0.6mA でした。Arduino の 3 番ピンが HIGH (5V) のとき、ツェナーダイオードには R2 を介して約 11mA 流れ、control 側の HIGH レベルは 3.3V になります。3 番ピンが LOW (0V) のとき R2 の電圧降下は 0.09V ですので、control 側は問題なく LOW レベルに落ちます。
sense はオープンコレクタなのでプルアップしています。内部プルアップでも構わないのですが、プルアップを明示的にするために抵抗を入れることにしています。まぁ好みです。
さて、問題の PWM ですが、Arduino の PWM は 490Hz か 980Hz ですのでこのままでは使えません。そこで Arduino で 25KHz PWM を作る方法をググってみると、けっこうたくさんヒットします。なるほどっポンと膝を叩いた、参考にさせていただいたサイトはこちらです。ありがとうございます。
そしてできあがったスケッチは以下。
/* FAN Controller 2019.08.15 meyon
*
* Timer2 Mode5 PhaseCorrect PWM
* Clock 16MHz / 8 = 2MHz
* PWM countuency 25KHz
* OCR2A = 2MHz / 25KHz / 2 = 40
* OCR2B = dutyRatio ratio 0 ~ 40
* PWM output PD3
*/
volatile long riseTime = millis();
volatile long prevTime = millis();
void setup()
{
pinMode(3, OUTPUT);
TCCR2A = _BV(COM2B1) | _BV(WGM20);
TCCR2B = _BV(WGM22) | _BV(CS21);
OCR2A = 40;
OCR2B = 0;
pinMode(2, INPUT);
attachInterrupt(digitalPinToInterrupt(2), sense, RISING);
Serial.begin(115200);
}
void loop()
{
static const int thermMax = 780;
static const int thermMin = 830;
static const float gradients = 40.0/(thermMax-thermMin);
int thermValue = analogRead(A0);
float dutyRatio = (thermValue-thermMin)*gradients;if (0>dutyRatio) {
dutyRatio=0;
} else if (40<dutyRatio) {
dutyRatio=40;
}
OCR2B = (int)dutyRatio;
long cycle=riseTime-prevTime;
long rpm=30000/cycle;
if (100<millis()-riseTime) {
rpm=0;
}
// data monitor
Serial.print(thermValue);
Serial.print("¥t");
Serial.print(dutyRatio);
Serial.print("¥t");
Serial.print(rpm);
Serial.print("¥n");}
void sense()
{
prevTime=riseTime;
riseTime=millis();
}
setup() のなかで PWM の動作を規定しています。
WGM20 、WGM22 をセットすることで Mode 5「PWM Phase Correct」になります。COM2B1 はデジタルピン 3 番の PWM 出力を規定します。CS21 は分周比を 8 としています。OCR2A で TOP を 40 にすることで、出力周波数が 16000 / ( 8 * 40 * 2 ) = 25KHz になります。
OCR2B は 3 番ピンの比較レジスタで、これを 0 〜 40 にすることでデューティー比 0 〜 100% の PWM を発生させることができます。
サーミスタ入力を 830 〜 780 とし、その範囲でデューティー比を 0 〜 40 (0 〜 100%) として OCR2B を設定します。
2 番ピンは sense を入力し、パルスの立ち上がりで関数 sense() を呼び出しています。これによりパルスの周期を計測して回転数を算出しますが、100msを超えて割り込みが無いときは回転数を 0 としています。
ちなみに、ビットアクセスマクロ _BV() は、指定したビットをセットし、他をクリアします。| でつなぐことで複数ビットをセットしています。
指定したビットのみセットするには、たとえば DDRD |= _BV(PD3) のようにします。これは DDRD = DDRD | _BV(PD3) ということですね。PD3 のビットのみセットし、他は変更しません。 指定したビットのみクリアするには DDRD &= ~_BV(PD3) とします。DDRD = DDRD & ~_BV(PD3) ということです。
]]>
余談ですが、いろいろ数値を入力して確認しているときに、答えが負数になる場合がでてきました。確認のために AC の方のプログラムを実行してみたところ、やっぱり負数になります。うーん、マイナスの雨が降るなんてありえない話じゃ ……
考えること小一時間 (^_^;) 問題文の制約に次の一文を見つけました。
入力が表す状況は、各山に非負の偶数リットルの雨が降った際に発生しうる。
わかりにくい文ですが、つまり「答えが負数になるような入力は与えませんよ」ってことなんでしょう。ということで、それは気にする必要はないのだと判断しました。
閑話休題
結果が TLE になったということは計算量が多すぎるってこと。解決策を求めて解説をみてみることにします。ん〜と… そうか、漸化式をそのまま順番に計算していけば O(n) にできるんだ。なるほど (^_^;)
では解説を参考にして、もう一度考えなおしてみましょう。
前回と同じく山は 5 個とします。降雨量の総量を S とすると、
S = A1 + A2 + A3 + A4 + A5 = X1 + X2 + X3 + X4 + X5
X1 = S - ( X2 + X3 + X4 + X5 )
X2 + X3 = 2 × A2 、X4 + X5 = 2 × A4 ですから、
X1 = S - 2 × ( A2 + A4 )
これでまず X1 が求まりました。あとは、
X2 = 2 × A1 - X1
X3 = 2 × A2 - X2
X4 = 2 × A3 - X3
X5 = 2 × A4 - X4
と順番に計算していけばよいです。このような前の式によって定まる式を「漸化式」というそうです。漸化式をそのまま順番に計算していくことで全体の計算量を減らせるなんて、考えが及びませんでした。
ということで、計算部分を改善してみました。太字が変更した部分です。
/* AtCoder Beginner Contest 133 D - Rain Flows into Dams
2019.07.28 by meyon */
#include <stdio.h>
#include <stdlib.h>
void err()
{
printf("Error!¥n");
exit(1);
}
int main()
{
// input N
int N;
scanf("%d", &N);
if(3>N || 99999<N || 0==N%2) {
err();
}
// input A
int A[N];
for(int i=0; i<N; i++) {
scanf("%d", &A[i]);
if(0>A[i] || 1000000000<A[i]) {
err();
}
}
// rainfall X
int X[N];
for(int i=0; i<N; i++) {
X[i]=0;
}
int S=0;
for(int i=0; i<N; i++) {
S += A[i];
}
int P=0;
for(int i=1; i<N; i+=2) {
P += A[i];
}
X[0]=S-2*P; // X1=S-2(A2+A4+...+A[n-1])
for(int i=0; i<N-1; i++) {
X[i+1]=2*A[i]-X[i]; // X[i+1]=2Ai-Xi
}
// display answer
for(int i=0; i<N; i++) {
printf("%d ", X[i]);
}
printf("¥n");return 0;
}
入力して、範囲外の場合はエラー終了。ここは毎度同じです。
改善した rainfall X の部分。
といった感じです。
はい、これで AC (合格) となりました \(^o^)/ 実行時間は 23ms 、メモリ 2048KB でした。
]]>