課題研究報告書
Arduino を使った電子工作(フィーリングカップル)
1. テーマ設定の理由 課題研究を通して、工業祭で展示・実演を行い、学校を盛り上げたいというのが第一の理由で ある。 また、課題研究を通して電子工作などの技術を高めるため、少し難易度の高そうな「フィーリング カップル」というテーマに設定した。 2. 実践内容 (1)制作方法 ① 使用部品・機材 品 名 個数 ノート PC 1 Arduino MEGA 1 抵抗(10kΩ) 10 抵抗(1kΩ) 20 抵抗(300Ω) 10 抵抗(200Ω) 10 抵抗(100Ω) 10 トグルスイッチ 50 パワーMOSFET 2SK2232 10 テープ LED 適量 AC アダプタ(12V) 1 ユニバーサル基板 13③ 制作手順(電子回路部分) 上記回路図のように、ユニバーサル基板を利用して回路を作成する。 そして、木工班の制作した土台へテープ LED を設置し、配線を行って装置の回路部分は 完成となる。 それから、フィーリングカップルの動作に必要なプログラムも制作する。 ④ 制作手順(プログラム部分) Arduino のプログラム作成・書き込み手順 1) Arduino IDE を起動してプログラムを記述する。
2) 「ツール」メニュー → 「ボード」 → 「Arduino Mega or Mega 2560」を選択。 3) 「ツール」メニュー → 「ポート」にて対象の Arduino を選択。 4) 「ファイル」メニュー → 「マイコンボードに書き込む」を選択。 (2)制作物の説明 制作した電子回路では、どのスイッチが ON になっているかを電圧によって判断するため、 Arduino のアナログポートに各スイッチを接続している。 フィーリングカップルの結果発表に利用する LED は、Arduino のデジタルポートからの出力によ って制御し、パワーMOSFET の 2SK2232 を使って AC アダプタから電源供給を行う。 続いて、プログラム部分の説明に移る(ソースコードは非常に長いため別紙参照)。 今回制作したプログラムは2つあり、一つは Arduino 側のプログラムである。 Arduino 側のプログラムでは、スイッチの状態読み取り、LED の制御を行っている。 また、PC とシリアル通信して、結果発表などを PC 上から操作できるようにし、BGM が流れるタイ ミングの制御もしている。 もう一方は PC 側のプログラムで、言語は C#で作成した(図1)。 PC 側のプログラムでは、Arduino とシリアル通信をして、カップルの成立状態の確認、結果発表 の操作を司会側できるようになっている。 図1 PC 側のプログラム
(3)制作物の評価 スイッチが少し不安定ではあるものの、工業祭で実演にまで至ったため成功といっていいので はないだろうか。 また、限られた時間の中で、当初実現したかった BGM の自動再生・シリアル通信も実装できた ので上出来だと思う。 3. 感想・反省 1 年間課題研究をやってきて、自分で研究することの面白さや難しさ、チームワークの大切さを 学ぶことができた。 反省としては、スイッチの電源が1個であるため、分圧により、抵抗が他のスイッチの電圧まで下 げてしまうために不安定になる所も改善したかった。 具体的な改善策としては、スイッチ毎に電源を取る、アナログポートでなくデジタルポートを利用 して入力するなどで実現できると思う。
/* Arduino 側のプログラム */
int sw_a[5] = { -1, -1, -1, -1, -1 }; // スイッチ状態記憶用の配列(男側) int sw_b[5] = { -1, -1, -1, -1, -1 }; // スイッチ状態記憶用の配列(女側) int swa_count = sizeof(sw_a) / sizeof(sw_a[0]); // sw_a 配列の要素をカウント int swb_count = sizeof(sw_b) / sizeof(sw_b[0]); // sw_b 配列の要素をカウント void setup() { Serial.begin(57600); // シリアル通信を開始 delay(100); Serial.println(""); Serial.println(".<START>"); // シリアル開始のメッセージ // LED を点灯させるためにデジタルポート 2~11 を出力に設定 for (int port = 2; port <= 11; port++)
{ pinMode(port, OUTPUT); } for (;;) { // シリアルポートにデータが送られている場合 if (Serial.available()) {
int pin = Serial.read(); // データを変数に格納 int state = LOW;
// シリアルポートにデータが送られてくるまでループ for (;;) { // シリアルポートにデータが送られている場合 if (Serial.available()) { state = Serial.read(); // データを変数に格納 break; // ループから抜ける } } // 1 バイト目が 0xFC、2 バイト目が 0xFF だった場合にスイッチの変更を受付終了 if (pin == 0xFC && state == 0xFF)
{
Serial.println("Switch read stop."); break;
} } // スイッチ判定関数を呼び出し sw_scan(); delay(1200); } //LED 制御コマンド受付 for (;;) { // シリアルポートにデータが送られている場合 if (Serial.available()) {
int pin = Serial.read(); // データを変数に格納 int state = LOW;
// シリアルポートにデータが送られてくるまでループ for (;;) { // シリアルポートにデータが送られている場合 if (Serial.available()) { state = Serial.read(); // データを変数に格納 break; // ループから抜ける } } // 1 バイト~2 バイト目が 0xFE だった場合に LED をすべて消灯 if (pin == 0xFE && state == 0xFE)
{ LedAllOff(); } //カップル非成立の場合 else if (pin == 0xFD) { digitalWrite(state, HIGH);
for (int port = 35; port <= 39; port++) digitalWrite(port, HIGH);
delay(1000);
for (int port = 35; port <= 39; port++) digitalWrite(port, LOW);
delay(2500); Serial.println(".<BGM>0"); digitalWrite(52, HIGH); delay(4000); LedAllOff(); delay(2000); Serial.println(".<BGM>STOP"); Serial.println(".<BTN_ENABLE>"); } //カップル成立の場合 else { digitalWrite(pin, HIGH);
for (int port = 35; port <= 39; port++) digitalWrite(port, HIGH);
delay(1000);
for (int port = 35; port <= 39; port++) digitalWrite(port, LOW); digitalWrite(state, HIGH); delay(1500); Serial.println(".<BGM>2"); delay(1200); digitalWrite(53, HIGH); delay(17500); LedAllOff(); delay(2000); Serial.println(".<BGM>STOP"); Serial.println(".<BTN_ENABLE>"); } } delay(1);
} }
/* 全 LED を消灯させる関数 */ void LedAllOff()
{
for (int port = 30; port <= 39; port++) digitalWrite(port, LOW); digitalWrite(52, LOW); digitalWrite(53, LOW); } void loop() { } /* 電圧の値からスイッチを判定する関数 */ int sw_judge(int voltage)
{
if (voltage >= 0 && voltage <= 50) return 0;
else if (voltage >= 400 && voltage <= 515) return 1;
else if (voltage >= 700 && voltage <= 890) return 2;
else if (voltage >= 900 && voltage <= 975) return 3;
else if (voltage >= 976 && voltage <= 996) return 4; else return -1; } /* スイッチの判定結果を配列に格納する関数 */ void sw_scan() { /* アナログポート A0~A[swa_count]までを走査 */ Serial.print(".<A>");
for (int i = 0; i < swa_count; i++) {
int tmp = analogRead(i); sw_a[i] = sw_judge(tmp);
Serial.print(sw_a[i]); if (i < swa_count - 1) Serial.print("/"); } Serial.println(""); /* アナログポート A[swa_count+1]~A[sw_bcount]までを走査 */ Serial.print(".<B>");
for (int i = 0; i < swb_count; i++) {
int tmp = analogRead(i + swa_count); sw_b[i] = sw_judge(tmp); Serial.print(sw_b[i]); if (i < swb_count - 1) Serial.print("/"); } Serial.println(""); }
/* PC 側のプログラム(C# + .NET Framework 3.5 使用) */ using System; using System.Drawing; using System.Linq; using System.Windows.Forms; using System.Diagnostics; using System.IO.Ports; using System.Threading; using System.Text.RegularExpressions; using System.Media; namespace FeelingGUI {
public partial class Form1 : Form
{
string[] bgm = new string[] { "29-バツ.wav", "50-選択中.wav", "78-成立.wav" }; SoundPlayer sp = null;
int[] sw_a = null; int[] sw_b = null;
int[] line_a = new int[5]; int[] line_b = new int[5];
int start = 0;
Regex regex = new Regex(@"-?[0-9]");
public Form1() {
InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e) {
//利用可能COM番号を取得
foreach (string port in SerialPort.GetPortNames()) comboBox1.Items.Add(port);
if (comboBox1.Items.Count > 0) comboBox1.SelectedIndex = 0; else
// シリアルポートの通信速度指定 sp1.BaudRate = 57600;
InitForm(); }
// ボタンなどの初期状態を設定 private void InitForm() {
button2.Enabled = false; button3.Enabled = false; ResultButtonDisable();
button4.ForeColor = Color.Red; button5.ForeColor = Color.Red; button6.ForeColor = Color.Red; button7.ForeColor = Color.Red; button8.ForeColor = Color.Red;
DrawImage(pictureBox1, 0); DrawImage(pictureBox2, 1); DrawImage(pictureBox3, 2); DrawImage(pictureBox4, 3); DrawImage(pictureBox5, 4);
sw_a = new int[] { -1, -1, -1, -1, -1 }; sw_b = new int[] { -1, -1, -1, -1, -1 }; }
// BGMを再生する
private void BGMPlay(string wav) {
sp = new SoundPlayer(wav); sp.PlayLooping();
}
// BGMを停止する private void BGMStop() {
sp.Stop(); }
private void button1_Click(object sender, EventArgs e) {
sp1.PortName = comboBox1.Text; // ソフトウェアリセットON sp1.DtrEnable = true; // シリアルポートを開く sp1.Open(); if (sp1.IsOpen) { textBox1.Text = comboBox1.Text + "に接続しました。通信まで2秒待機しま す。";
this.Enabled = false; Thread.Sleep(2000); sp1.DtrEnable = false; this.Enabled = true;
this.sp1.DataReceived += new
SerialDataReceivedEventHandler(this.sp1_DataReceived);
textBox1.Text = "シリアル通信を開始します。"; button1.Enabled = false; button2.Enabled = true; button3.Enabled = true; // BGM再生 BGMPlay(bgm[1]); } }
private void button2_Click(object sender, EventArgs e) {
sp1.Close(); if (!sp1.IsOpen) {
this.sp1.DataReceived -= new
SerialDataReceivedEventHandler(this.sp1_DataReceived);
textBox1.Text = sp1.PortName + "を切断しました。"; button1.Enabled = true;
button2.Enabled = false; button3.Enabled = false;
start = 0; InitForm(); //BGM停止 BGMStop(); } }
private void sp1_DataReceived(object sender, SerialDataReceivedEventArgs e) {
// シリアルポートから受け取ったデータを変数に格納
string dat = sp1.ReadLine(); Console.WriteLine(dat); if (start != 0) { // 受け取ったデータによって処理を分岐する // .<A> : 男側のスイッチ状態を読み出して配列に格納 if (dat.IndexOf(".<A>") == 0) { string tmp = dat.Remove(0, 4); string[] tmp_array = tmp.Split('/');
if (tmp_array.Count() == sw_a.Count()) {
for (int i = 0; i < tmp_array.Count(); i++) {
if (regex.IsMatch(tmp_array[i]))
sw_a[i] = Int32.Parse(tmp_array[i]); } } } // .<B> : 女側のスイッチ状態を読み出して配列に格納 else if (dat.IndexOf(".<B>") == 0) { string tmp = dat.Remove(0, 4); string[] tmp_array = tmp.Split('/');
if (tmp_array.Count() == sw_b.Count()) {
for (int i = 0; i < tmp_array.Count(); i++) {
if (regex.IsMatch(tmp_array[i]))
} }
}
else if (dat.IndexOf(".<BGM>STOP") == 0) BGMStop();
else if (dat.IndexOf(".<BGM>") == 0) {
string tmp = dat.Remove(0, 6); int b = Convert.ToInt32(tmp);
if (regex.IsMatch(tmp)) BGMPlay(bgm[b]); }
else if (dat.IndexOf(".<BTN_ENABLE>") == 0) ResultButtonEnable();
// カップルが成立したかどうかを判定し、表示領域に描画する for (int i = 0; i < sw_a.Count(); i++)
{
for (int j = 0; j < sw_b.Count(); j++) {
if (sw_a[i] == j && sw_b[j] == i && sw_a[i] >= 0 && sw_b[j] >= 0) { line_a[i] = 1; line_b[j] = 1; } } if (i == 0) DrawImage(pictureBox1, i); else if (i == 1) DrawImage(pictureBox2, i); else if (i == 2) DrawImage(pictureBox3, i); else if (i == 3) DrawImage(pictureBox4, i); else if (i == 4) DrawImage(pictureBox5, i); line_a = new int[5];
line_b = new int[5]; }
}
else if (dat.IndexOf(".<START>") == 0) {
start = 1; }
}
private void button3_Click(object sender, EventArgs e) { ino_sendCommand(0xFC, 0xFF); BGMStop(); button3.Enabled = false; ResultButtonEnable(); } // 表示領域にカップル成立の状態を表示する private void DrawImage(PictureBox p, int id) {
Bitmap bmp = new Bitmap(p.Width, p.Height); Graphics g = Graphics.FromImage(bmp);
g.FillRectangle(Brushes.White, 0, 0, p.Width, p.Height);
g.FillRectangle(Brushes.DarkBlue, 0, 0, p.Width, 16);
g.DrawString((id + 1).ToString(), newFont("MS UI Gothic", 9), Brushes.White,
new Point(2, 2));
for (int i = 0; i < 5; i++) {
g.DrawString((i + 1).ToString(), new Font("MS UI Gothic", 9),
Brushes.Blue, new Point(10 + (32 * i), 20));
g.DrawString((i + 1).ToString(), new Font("MS UI Gothic", 9),
Brushes.Red, new Point(10 + (32 * i), 120));
DrawLine(line_a, i, g, newPoint(15 + (32 * i), 37), newPoint(79, 75)); DrawLine(line_b, i, g, newPoint(15 + (32 * i), 115), newPoint(79, 75)); }
p.Image = bmp; }
// 線を描く
private void DrawLine(int[] line, int id, Graphics g, Point p0, Point p1) {
if (line[id] == 0)
g.DrawLine(Pens.WhiteSmoke, p0, p1); else
g.DrawLine(Pens.Red, p0, p1); } // 特殊コマンド // FC FF : スイッチの受付終了 // FD xx : xx番の結果発表 // FF xx : delay(0.1秒単位) // 結果発表を行う
private void ViewResult(int id, Button btn) {
int margin = 30; int match = 0;
btn.ForeColor = Color.Black;
ResultButtonDisable();
for (int j = 0; j < sw_b.Count(); j++) {
if (sw_a[id] == j && sw_b[j] == id && sw_a[id] >= 0 && sw_b[j] >= 0) {
match = 1;
byte port = (byte)(id + margin);
byte port2 = (byte)(j + sw_a.Count() + margin); ino_sendCommand(port, port2); } } if (match != 1) {
byte port = (byte)(id + margin);
ResultButtonDisable();
ino_sendCommand((byte)0xFD, port); }
}
// 結果発表のボタンを無効化する private void ResultButtonDisable() { button4.Enabled = false; button5.Enabled = false; button6.Enabled = false; button7.Enabled = false; button8.Enabled = false; } // 結果発表のボタンを有効化する
private delegate void _DButtonEnabled(Button btn, bool enabled);
private void ResultButtonEnable() {
this.Invoke(new _DButtonEnabled(DButtonEnabled), new object[] { button4,
true });
this.Invoke(new _DButtonEnabled(DButtonEnabled), new object[] { button5,
true });
this.Invoke(new _DButtonEnabled(DButtonEnabled), new object[] { button6,
true });
this.Invoke(new _DButtonEnabled(DButtonEnabled), new object[] { button7,
true });
this.Invoke(new _DButtonEnabled(DButtonEnabled), new object[] { button8,
true }); }
private void DButtonEnabled(Button btn, bool enabled) {
btn.Enabled = enabled; }
//シリアルポートに引数の2バイト送信する
private void ino_sendCommand(byte cmd0, byte cmd1) {
byte[] b = new byte[] { cmd0, cmd1 }; sp1.Write(b, 0, b.GetLength(0)); }
private void button4_Click(object sender, EventArgs e) {
}
private void button5_Click(object sender, EventArgs e) {
ViewResult(1, (Button)sender); }
private void button6_Click(object sender, EventArgs e) {
ViewResult(2, (Button)sender); }
private void button7_Click(object sender, EventArgs e) {
ViewResult(3, (Button)sender); }
private void button8_Click(object sender, EventArgs e) {
ViewResult(4, (Button)sender); }
private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
BGMStop();
sp1.DataReceived -= new
SerialDataReceivedEventHandler(this.sp1_DataReceived);
if (sp1.IsOpen) sp1.Close(); Application.Exit(); }
private void comboBox1_TextChanged(object sender, EventArgs e) { if (comboBox1.Text.Length != 0) button1.Enabled = true; } } }