เป็นปัญหาที่เราคอยจับตามองอยู่พักนึงแล้ว ว่าทำไงดีดีกับเจ้าพวกนี้ดี เรามีชุดลำโพงกับโปรเจคเตอร์ แล้วผู้ใช้ชอบทำรีโมทหาย หรือไม่ก็วางผิดที่/ถูกขโมยไปซะงั้น ปกติก็ไม่น่ามีปัญหาอะไร แค่ติดต่อซัพพลายเออร์หรือผู้ผลิตแล้วสั่งของใหม่มาใช่มั้ยล่ะ แต่มันทำไม่ได้แล้วไง
เรามีชุดลำโพงติดผนัง Vision AV-1000 ที่ต่อกับโปรเจคเตอร์ EPSON อยู่ รีโมทโปรเจคเตอร์หาง่าย แต่รีโมท AV-1000 นี่เลิกผลิตไปแล้วครับพี่น้อง ลำโพงพวกนี้ไม่มีปุ่มควบคุมอะไรเลยสำหรับเลือกอินพุต ปรับเสียงแหลม/เสียงเบส ฯลฯ มีแค่ปุ่มปรับระดับเสียงด้านหลังเท่านั้น
ทางออกคืออะไรล่ะ?
เราตัดสินใจทำรีโมทลำโพงใช้เองครับ ราคาต้องถูกที่สุดเท่าที่จะทำได้
ไอเดียง่ายๆ คือเราจับสัญญาณอินฟราเรด (IR) จากรีโมทลำโพงตัวที่เหลืออยู่ แล้วบันทึกคำสั่งที่มันส่งออกมา
เราทำโดยการต่อ IR Receiver เข้ากับ Arduino ตัวรีซีฟเวอร์มี 3 ขา จากซ้ายไปขวาคือ GND, +5V, SIGNAL และใช้ไลบรารี Arduino IRRemote
รันตัวอย่าง Examples > IRRemote > IRrecvDumpv2 ดูรูปด้านล่างที่ต่อ IR Receiver กับ Arduino Uno (สำหรับทำโปรโตไทป์ จะใช้ Arduino nano ก็ได้นะ แต่ต้องอัพโหลด/รีเซ็ตสเก็ตช์ทุกครั้งตอนทดสอบ)
ถ้าทำถูกต้องแล้ว ให้เล็งรีโมทลำโพงไปที่ตัวรับสัญญาณแล้วกดปุ่ม – เมื่อ IRReceiver ได้รับข้อมูล มันจะกระพริบด้วย LED สีแดงบนบอร์ด แสดงว่ามันทำงานแล้วจ้า
เปิด Arduino serial monitor ขึ้นมา แล้วคุณควรจะเห็นข้อมูลที่มันได้รับออกมา (ดูรูปด้านล่าง)
โชคดีของเรา โปรโตคอลที่รีโมทลำโพงใช้ถูกระบุว่าเป็นโปรโตคอล NEC 32 บิต ไลบรารี IRRemote มีฟังก์ชันในตัวสำหรับส่งข้อมูลนี้ออกมาใหม่ ตอนนี้สิ่งที่เราต้องทำคือไล่กดปุ่มทุกปุ่มบนรีโมทลำโพงแล้วบันทึกรหัสของมัน (ข้อมูลด้านล่าง)
RESET: 827D58A7
MUTE: 827DD827
INPUT 1: 827D708F
INPUT 2: 827D48B7
TREBLE UP: 827DF20D
TREBLE DOWN: 827DCA35
BASS UP: 827DF00F
BASS DOWN: 827DC837
VOLUME DOWN: 827D40BF
VOLUME UP: 827DC03F
เราก็ได้ข้อมูลที่จำเป็นสำหรับเลียนแบบสัญญาณ IR ของรีโมทแล้ว นั่นคือทั้งหมดที่เราต้องการจาก IR Receiver ครับ เพราะงั้นจากนี้ไปเราจะถอดมันออก แล้วไปต่อกับโปรเจคที่เหลือ
เพื่อให้เราสื่อสารกับมันได้ เราคิดว่าการมีซอฟต์แวร์บน PC เป็นวิธีที่ดีที่สุด เราจะทำรีโมทอีกอันก็ได้ แต่มันก็ต้องหาย/วางผิดที่อยู่ดีแหละ
ขั้นตอนต่อไปคือต่อ IR LED และตัวต้านทาน (Resistor) เข้ากับบอร์ด Arduino ไลบรารี IRRemote จะส่งสัญญาณออกทาง Pin 3 เป็นวงจรพื้นฐานมากๆ ในการต่อพวกนี้เข้าด้วยกัน โดยใช้ Arduino nano ฉันบัดกรีตัวต้านทานเข้ากับขาข้างหนึ่งของ IR LED และอีกข้างต่อตรงไปที่ Pin บนบอร์ด มันทำงานได้สมบูรณ์แบบเลย เพราะคุณสามารถกด LED ลงบนบอร์ดให้แน่นได้
เราจะใช้โปรแกรม VB.NET ง่ายๆ ในการสื่อสารกับ Arduino ซึ่งจะเขียนไปยังพอร์ตอนุกรมและส่งไบต์ – ในสเก็ตช์ Arduino เราจะตรวจสอบพอร์ตอนุกรมสำหรับไบต์ขาเข้าและจับคู่กับรหัสที่จะส่งผ่าน IR ง่ายๆ เอง!
สเก็ตช์ Arduino ของเรา
/*
* IRremote: AV_Controller
* An IR LED must be connected to Arduino PWM pin 3.
* Version 0.1 01/04/16
*/
#include <IRremote.h>
IRsend irsend;
byte byteRead;
void(* resetFunc) (void) = 0; //declare reset function @ address 0
void setup() {
Serial.begin(9600);
Serial.println("***********************");
Serial.println("** AV-CONTROLLER **");
Serial.println("***********************");
Serial.println("Waiting for commands...");
}
void loop() {
if (Serial.available()) {
byteRead = Serial.read();
processResponse(byteRead);
}
}
void processResponse(byte byteInput) {
switch (byteInput) {
case '4':
Serial.println("Sending Command: TEST 4");
// irsend.sendNEC(0x827DD827, 32);
delay(40);
return;
break;
case 'a':
Serial.println("Sending Command: RESET");
irsend.sendNEC(0x827D58A7, 32);
delay(40);
Serial.println("Command Sent.");
return;
break;
case 'b':
Serial.println("Sending Command: MUTE");
irsend.sendNEC(0x827DD827, 32);
delay(40);
Serial.println("Command Sent.");
return;
break;
case 'c':
Serial.println("Sending Command: INPUT1");
irsend.sendNEC(0x827D708F, 32);
delay(40);
Serial.println("Command Sent.");
return;
break;
case 'd':
Serial.println("Sending Command: INPUT2");
irsend.sendNEC(0x827D48B7, 32);
delay(40);
Serial.println("Command Sent.");
return;
break;
case 'e':
Serial.println("Sending Command: TREBLE UP");
irsend.sendNEC(0x827DF20D, 32);
delay(40);
Serial.println("Command Sent.");
return;
break;
case 'f':
Serial.println("Sending Command: TREBLE DOWN");
irsend.sendNEC(0x827DCA35, 32);
delay(40);
Serial.println("Command Sent.");
return;
break;
case 'g':
Serial.println("Sending Command: BASS UP");
irsend.sendNEC(0x827DF00F, 32);
delay(40);
Serial.println("Command Sent.");
return;
break;
case 'h':
Serial.println("Sending Command: BASS DOWN");
irsend.sendNEC(0x827DC837, 32);
delay(40);
Serial.println("Command Sent.");
return;
break;
case 'i':
Serial.println("Sending Command: VOLUME DOWN");
irsend.sendNEC(0x827D40BF, 32);
delay(40);
Serial.println("Command Sent.");
return;
break;
case 'j':
Serial.println("Sending Command: VOLUME UP");
irsend.sendNEC(0x827DC03F, 32);
delay(40);
Serial.println("Command Sent.");
return;
break;
case 'x':
Serial.println("Resetting System...");
resetFunc();
return;
break;
break;
case 't':
Serial.println("OK");
return;
break;
break;
default:
break;
}
Serial.println("Unknown command.");
}
เบสิคของโค้ดก็ประมาณนี้แหละน้อง เราตรวจจับข้อมูลที่เข้ามาทางพอร์ต Serial ถ้าตัวอักษรที่เข้ามาตรงกับที่เราตั้งไว้ใน switch case เราก็จะส่งคำสั่ง IR ที่ตรงกันออกไป ถ้าไม่ตรง ก็โยนข้อความ "Unknown command." ออกมาให้ดู
เราสามารถทดสอบโค้ดด้านบนได้โดยการเชื่อมต่อกับพอร์ต COM ของเราด้วยโปรแกรม PuTTy เปิด PuTTy ขึ้นมาแล้วใส่หมายเลขพอร์ต COM ที่เจอ Arduino อยู่ พอเชื่อมต่อแล้วน้องควรจะเห็นข้อความจาก Serial.println ในฟังก์ชัน setup() โผล่มา
ลองพิมพ์ตัวอักษร เช่น 'a' เพื่อส่งคำสั่ง RESET ดูสิ น้องควรจะเห็นผลลัพธ์โผล่มา จริงๆแล้วน้องทดสอบกับลำโพงเซ็ตนั้นได้เลย มันควรจะรีเซ็ตระดับเสียง/เบส/ทริเบิล ฯลฯ กลับไปตั้งต้น ดูผลลัพธ์ที่คาดหวังได้จากภาพด้านล่างเลย
โอเค ตอนนี้เรามีแอปที่ทำงานได้แล้ว แต่หน้าตามันยังไม่น่าใช้เลย เราไปคาดหวังให้คนมานั่งเปิด PuTTy เข้าไปส่งคำสั่งทาง Serial ได้ยังไงล่ะ? ต่อไปเราจะมาจัดการออกแบบแอปพลิเคชันสำหรับเดสก์ท็อปให้มันดูดีกัน สู้งานนะน้อง!
แอปพลิเคชัน .NET
อย่างแรกที่ต้องทำคือคุยกับพอร์ตอนุกรม (Serial Port) จริงๆ เราเพิ่งเขียนบทความเกี่ยวกับเรื่องนี้ไปหยกๆ เอง (แต่ลิงก์หายไปแล้ววว) หลักการคือเราจะตรวจจับอัตโนมัติว่าต้องใช้ COM พอร์ตไหน โดยการส่งสตริงตรวจสอบไปถามหา แอปจะไล่ตรวจสอบ COM พอร์ตที่ใช้ได้ทั้งหมด แล้วส่งข้อความ 't' ไป ถ้าพอร์ตไหนตอบกลับมาว่า 'OK' นั่นแหละพอร์ตที่ถูกต้อง! ช่วยเซฟน้องๆ ไม่ต้องมานั่งเลือกพอร์ตเองให้เมื่อยมือ
การส่งคำสั่งต่างๆ เราจัดให้เป็นฟังก์ชันเล็กๆ ชื่อ sendCommand ด้านล่างเป็นโค้ด .NET (มันยังมีอัพเดตสี Picturebox ด้วย เพื่อจำลองการกะพริบของรีโมท LED)
Sub sendCommand(data As String)
PictureBox2.BackColor = Color.Green
If connected = True Then
If data <> "" Then
Try
Using com As IO.Ports.SerialPort =
My.Computer.Ports.OpenSerialPort(comPort)
com.WriteLine(data)
com.Close()
End Using
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End If
End If
PictureBox2.BackColor = Color.White
End Sub
ส่วนโค้ดที่เหลือก็ง่ายมาก แค่เรียกใช้ sendCommand('a') หรือสตริงอะไรก็ตามที่น้องอยากส่งไปหา COM พอร์ต ที่เหลือของแอปคือ GUI สวยๆ ฮะๆ ด้านล่างมีโค้ดเต็มและภาพหน้าจอแอปพลิเคชันตอนรัน
โค้ดเต็มของแอปพลิเคชัน .NET อยู่ด้านล่างนี้เลยจ้า พัฒนาด้วย VS 2015 Community
Imports System.Threading
Public Class Form1
Dim connected As Boolean
Dim comPort As String
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
portConfig()
End Sub
Sub portConfig()
For Each p As String In My.Computer.Ports.SerialPortNames
Try
Using com As IO.Ports.SerialPort =
My.Computer.Ports.OpenSerialPort(p)
com.ReadTimeout = 1000
com.WriteLine("t")
Dim data As String = com.ReadLine()
If InStr(data, "OK") Then
lblConnection.Text = "Connected to " & p
comPort = p
connected = True
End If
com.Close()
End Using
Catch ex As Exception
End Try
Next
End Sub
Sub sendCommand(data As String)
PictureBox2.BackColor = Color.Green
If connected = True Then
If data <> "" Then
Try
Using com As IO.Ports.SerialPort =
My.Computer.Ports.OpenSerialPort(comPort)
com.WriteLine(data)
com.Close()
End Using
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End If
End If
PictureBox2.BackColor = Color.White
End Sub
Private Sub ConnectToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ConnectToolStripMenuItem.Click
portConfig()
End Sub
Private Sub ResetDeviceToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ResetDeviceToolStripMenuItem.Click
sendCommand("x")
End Sub
Private Sub ExitToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ExitToolStripMenuItem.Click
Application.Exit()
End
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
Label1.Parent = PictureBox1
Label1.BackColor = Color.Transparent
Label2.Parent = PictureBox1
Label2.BackColor = Color.Transparent
Label3.Parent = PictureBox1
Label3.BackColor = Color.Transparent
Label4.Parent = PictureBox1
Label4.BackColor = Color.Transparent
Label5.Parent = PictureBox1
Label5.BackColor = Color.Transparent
Label6.Parent = PictureBox1
Label6.BackColor = Color.Transparent
Label7.Parent = PictureBox1
Label7.BackColor = Color.Transparent
Label8.Parent = PictureBox1
Label8.BackColor = Color.Transparent
Label9.Parent = PictureBox1
Label9.BackColor = Color.Transparent
Label10.Parent = PictureBox1
Label10.BackColor = Color.Transparent
End Sub
Private Sub Label1_Click(sender As Object, e As EventArgs) Handles Label1.Click
sendCommand("a")
End Sub
Private Sub Label2_Click(sender As Object, e As EventArgs) Handles Label2.Click
sendCommand("b")
End Sub
Private Sub Label3_Click(sender As Object, e As EventArgs) Handles Label3.Click
sendCommand("c")
End Sub
Private Sub Label4_Click(sender As Object, e As EventArgs) Handles Label4.Click
sendCommand("d")
End Sub
Private Sub Label5_Click(sender As Object, e As EventArgs) Handles Label5.Click
sendCommand("e")
End Sub
Private Sub Label6_Click(sender As Object, e As EventArgs) Handles Label6.Click
sendCommand("f")
End Sub
Private Sub Label7_Click(sender As Object, e As EventArgs) Handles Label7.Click
sendCommand("g")
End Sub
Private Sub Label8_Click(sender As Object, e As EventArgs) Handles Label8.Click
sendCommand("h")
End Sub
Private Sub Label9_Click(sender As Object, e As EventArgs) Handles Label9.Click
sendCommand("j")
End Sub
Private Sub Label10_Click(sender As Object, e As EventArgs) Handles Label10.Click
sendCommand("i")
End Sub
Private Sub Form1_Resize(sender As Object, e As EventArgs) Handles Me.Resize
If Me.WindowState = FormWindowState.Minimized Then
NotifyIcon1.Visible = True
NotifyIcon1.Icon = SystemIcons.Application
NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info
NotifyIcon1.BalloonTipTitle = "AV Controller"
NotifyIcon1.BalloonTipText = "Controller is minimized. Click the icon in the tray to open."
NotifyIcon1.ShowBalloonTip(50000)
ShowInTaskbar = False
End If
End Sub
Private Sub NotifyIcon1_MouseDoubleClick(sender As Object, e As MouseEventArgs) Handles NotifyIcon1.MouseDoubleClick
ShowInTaskbar = True
Me.WindowState = FormWindowState.Normal
NotifyIcon1.Visible = False
End Sub
End Class
### การถอดรหัสสัญญาณอินฟราเรด (Infrared Protocol Decoding)
โปรเจคนี้คือการเปลี่ยนรีโมททีวีธรรมดาๆ ให้กลายเป็นตัวควบคุมอเนกประสงค์สำหรับ PC หรือระบบอัตโนมัติในบ้านของเราเอง ง่ายๆ แต่มันส์จัดไปวัยรุ่น!
- **การถอดรหัสความกว้างพัลส์ (Pulse Width Demodulation)**: เริ่มจาก **ตัวรับสัญญาณ IR (เช่น VS1838b)** มันจะจับพัลส์อินฟราเรดความถี่ 38kHz ที่ส่งมาจากรีโมท จากนั้น Arduino ก็จะใช้ไลบรารี่ `IRremote` ในการถอดรหัสช่วงเวลาพัลส์เหล่านั้นให้กลายเป็นรหัสเลขฐานสิบหกเฉพาะตัว (เช่น `0xFFA25D`) เรียบร้อย!
- **การแมปปุ่มควบคุม (Control mapping)**: Arduino ทำหน้าที่เป็นสะพานเชื่อมต่อ พอเห็นรหัสของปุ่ม "Play" มันก็จะส่งข้อความ (serial string) เฉพาะกลับไปยังคอมพิวเตอร์โฮสต์ ง่ายเหมือนปอกกล้วยเข้าปากเลยน้อง ห้ามช็อตนะตัวนี้
### หน้าต่างแอปพลิเคชันบนวินโดวส์
- **Visual Studio 2015**: เขียนแอปตัวช่วยด้วย C# หรือ VB.NET ให้มันคอยฟังพอร์ต COM อยู่
- **ซอฟต์แวร์ออโตเมชั่น**: แอปบนพีซีจะแปลงรหัสอินฟราเรดเป็นคำสั่งระบบ (เช่น ปรับเสียง, เปิดเบราว์เซอร์, หรือควบคุมพาวเวอร์พอยท์) ทำให้รีโมตอินฟราเรดราคาถูกๆ กลายเป็นอุปกรณ์อินพุตซอฟต์แวร์สุดแกร่งไปเลย
### เรื่องของเคส
ตอนนี้ตัวอุปกรณ์ทำงานได้แล้ว ซอฟต์แวร์ควบคุมก็พร้อม ถึงเวลาคิดแล้วว่าเราจะห่อหุ้มมันยังไง ถ้าปล่อยให้มันโป๊เปลือยแบบนี้ เดี๋ยวก็มีน้ำหกใส่แน่ๆ
เราเลยลงมือออกแบบด้วย 2D Design สร้างเคสแบบง่ายๆ ขึ้นมา
<figure><img src= /><figcaption> </figcaption></figure>
จากภาพสกรีนช็อตโปรแกรม 2D Design ทางซ้ายมือจะเห็นว่าเป็นดีไซน์พื้นฐานมาก ใช้สกรูพลาสติกยึดต่อชิ้นส่วนเข้าด้วยกัน (ลอดผ่านรูพวกนั้น) ส่วนผลงานหลังตัดเลเซอร์เสร็จดูด้านล่างนี้เลย:
### ผลงานชิ้นโบว์แดง
<div id="gallery"><img id="gallery" src= /><figcaption> </figcaption><img id="gallery" src= /><figcaption> </figcaption></div>
สรุปสิ่งที่เราทำสำเร็จในโปรเจกต์นี้:
- จับข้อมูลอินฟราเรดจากรีโมตเดิมของเราได้
- พัฒนาอุปกรณ์ (Arduino & IR LED) สำหรับส่งข้อมูลนั้นกลับออกไปใหม่
- ออกแบบเคสให้มันนั่งอยู่ข้างพีซีได้อย่างสวยงามไม่เกะกะ
โปรเจกต์นี้สนุกมากและมันก็ทำหน้าที่ได้ดีเลยทีเดียว สิ่งที่เราต้องทำก็แค่เสียบอุปกรณ์เข้ากับพีซี แล้วรันแอป .NET ของเราขึ้นมาเพื่อควบคุมลำโพงในห้อง สิ่งต่อไปที่อยากเพิ่มคือการควบคุมโปรเจคเตอร์กับแอร์
ต้นทุนรวมของโปรเจกต์ประมาณ 4 ปอนด์เท่านั้น (ส่วนประกอบสั่งจาก eBay เป็นหลัก) ถูกใจวัยรุ่นไหมล่ะ! สู้งานนะน้อง