ksp gửi vào
- 209456 lượt xem
Giới thiệu
Ở bài viết Giao tiếp giữa hai mạch Arduino bất kỳ, chúng ta đã tìm hiểu cách giao tiếp giữa 2 vi điểu khiển khác nhau qua giao thức Serial. Trong bài viết đó, mình cũng đã đề cập đến việc có thể điều khiển các mạch Arduino qua giao thức Serial.bằng máy tính. Hôm nay chúng ta sẽ cùng tìm hiểu vấn đề này nhé!
Bạn mà có một ít kiến thức về lập trình Java thì sẽ rất có ích đấy trong bài viết này đấy!
Tại sao cần phải dùng máy tính để điều khiển Arduino?
Máy tính vừa cồng kềnh, to xác, lại chẳng di động được như điện thoại thông minh, vậy tại sao ta cần phải học cách dùng máy tính điều khiển thiết bị Arduino?
Bởi vì giữa máy tính và bất kỳ vi điều khiển (không chỉ Arduino) đều có giao tiếp với nhau công qua giao thức UART hay hiểu một cách nôm na là Serial. Bạn có thể xem, máy tính và vi điều khiển là 2 hòn đảo nhiệt đới nằm giữa biển khơi, cái hay của máy tính là nó có rất nhiều "cây cầu" để bắt đến vùng đất hứa như "internet", "thiết bị ngoại vi phức tạp như Leap Motion, Kinect,...",...; cái hay của Arduino là bạn có thể xây dựng nên một hệ thống điện tử - phần cứng - tin học của riêng mình. Nhưng nếu nó chỉ bó hẹp trong phạm vi 1 sản phẩm hay một nhóm các sản phẩm của bạn thì có vẻ là hơi phí.
Vì vậy, nhiệm vụ của chúng ta là trở thành một kĩ sư cầu đường để xây dựng một cây cầu bắt giữa 2 hòn đảo trên. Như vậy, những thứ tốt đẹp mà ta xây dựng có thể "giao tiếp" với thế giới bên ngoài, từ đó mang đến sự phát triển phồn thịnh (nếu có định hướng đúng đán và ý chí phát triển). Như mình đã nói ở trên, chúng ta sẽ sử dụng cổng Serial để xây dựng cầu.
Nhưng cổng Serial vẫn phải dùng dây mà? KHÔNG. Có vẻ cái từ "cổng" đã bó hẹp ý nghĩa bao quát của nó. Từ "cổng" trong từ "cổng Serial" có ý nghĩa rộng hơn, từ "cổng" ở đây có nghĩa giống như là một "con đường" (như ở trên), nhưng "con đường" này lại không chỉ nằm trên "cạn" (dây dẫn), mà nó còn ám chỉ những loại "con đường" khác, chẳng hạn như "đường không" (không gian - dùng các loại sóng để tạo cổng Serial). Như vậy "cổng Serial" có ý nhắc chúng ta rằng, đó chỉ đơn thuần là tên gọi của một phương thức giúp chúng ta giao tiếp giữa 2 thiết bị. Còn CÁCH NÓ TRUYỀN như thế nào thì tùy vào ý định của bạn (bạn muốn nó như thế nào thì bạn sẽ làm nó như thế).
Làm thế nào để điều khiển Arduino bằng máy tính?
Cách đơn giản mà bạn - nghệ sĩ Arduino đã, đang và sẽ mãi sử dụng, đó là sử dụng bảng Serial Monitor! Hi vọng là bạn sẽ cười khi đọc đến câu vừa rồi, nó vui mà !
Bạn chỉ việc xây dựng một "chương trình command line" - sketch trên Arduino là bạn đã có thể điều khiển bé Arduino của bạn rồi chứ còn gì nữa . Nhưng có vẽ mỗi lần điều khiển có hơi cực thì phải, vì phải gõ từng dòng lệnh rồi từng tham số. Và chúng ta là những nghệ sĩ Arduino nên không thể chấp nhận một việc như vậy được! Chúng ta cần có giao diện đẹp, những trò vui và hơn hết là sự thoái mái của người dùng - tức người thừa hưởng sản phẩm của mình. Một nghệ sĩ sẽ cảm thấy vô cùng hạnh phúc khi người thưởng thức tác phẩm của mình trầm trồ khen ngợi!
Giao tiếp giữa máy tính với mạch Arduino bằng cách nào?
Arduino 1 giao tiếp với Arduino 2 thông qua 3 sợi dây, GND, TX và RX. Còn máy tính giao tiếp với Arduino như thế nào? Cái này tùy vào trường hợp sẽ có những sự lựa chọn cho phù hợp . Nhưng theo mình, nhìn chung, nó có 2 dạng tổng quát sau (ở mức độ sử dụng dây dẫn):
- Nếu bạn sử dụng những mạch Arduino đã có chip hay một module nào đó builtin sẵn trong việc sử dụng chuyển đổi giao thức UART to Serial như Arduino UNO R3, Arduino Mega,... thì bạn chỉ việc gắn dây USB vào là xong .
- Với những mạch như Arduino Promini (không có sẵn module UART to Serial) thì bạn phải chuẩn bị một mạch UART to Serial riêng có tên gọi dân gian là USB to Serial. Sau đó chúng ta lại dùng 3 sợi dây thần thánh. Hoặc bạn sử dụng luôn sợi dây thứ 4 - VCC để cấp nguồn cho con Arduino luôn.
Trong phạm vi bài viết này, chúng ta chỉ tìm hiêu cách 1 để nắm được những phần cơ bản nhất.
Mô hình
Mô hình này rất đơn giản, tương tự như cách sử dụng Serial để giao tiếp với Arduino thông qua Serial Monitor: máy tính sẽ mở cổng COMx ( hoặc /dev/tty..., ....) sau đó các nội dung trong output buffer của Arduino sẽ được tuồn qua máy tính và lưu ở input buffer. Máy tính đọc những dòng đó rồi quy ra lệnh (Serial Command); đồng thời trên Arduino cũng song song tồn tại phương thức ấy. Bạn xem hình dưới để rõ ràng hơn.
Các vấn đề cần giải quyết
Đầu tiên, làm thế nào để lập trình trên máy tính ?
Ở đây, chúng ta sẽ chẳng đời nào dùng Arduino để mở Serial Monitor ra rồi vỗ ngực nói với người dùng rằng "đây là phần mềm của anh, các chú muốn sử dụng thì phải gõ hàm này, hàm nọ,...". Ta phải tiếp cận một hướng khác. Chúng ta cần một ngôn ngữ lập trình có thể viết trên máy tính và nếu có thể chạy trên mọi hệ điều hành thì càng tốt. Và nếu có giống giống như Arduino thì còn gì tuyệt hơn. Lúc đầu khi mới tìm hiểu, mình nghĩ chẳng có cái gì trên đời này tốt đẹp đến thế, nhắc đến 2 cái yêu cầu đầu tiên thì mình nghĩ ngay đến Java chứ mà giống như code Arduino thì chắc không có. Nào ngờ, tìm hiểu thêm thì bất ngờ phát hiện ra phần mềm kim ngôn ngữ lập trình có tên Processing . Ngôn ngữ Processing có rất nhiều sự tương đồng với Arduino, bạn tìm hiểu là thấy ngay (Arduino là con của Processing - A do i know?). Arduino được dựa trên C để viết thêm thư viện Arduino, còn Processing được dựa trên Java để viết nên thư viện Processing. Nhưng nói như vậy là chưa đúng, đó chỉ đơn thuần là sự ngầm hiểu như vậy, và thực thế có lẻ nó là như vậy !
Vậy chúng ta hãy tải Processing về và chơi thôi!
Đây là giao diện của chương trình Processing, Arduino không khác mấy với giao diện đàn anh. Bạn có biết vì sao nó giống nhau đến thế không? Vì khi processing ra đời đã giải phóng một lượng sức lao động trong việc lập trình Java, vì những beginner có thể lập trình Java một cách dễ dàng. Còn Arduino ra đời để những begginer yêu thích sáng tạo, sáng chế có thể tự sáng chế ra những thứ độc đáo. Vì vậy, anh "đẹp" thì em sinh đôi của hắn, cũng phải "đẹp" - u know !
Nếu có sự khác biệt nào gọi là cụ thể, thì tôi sẽ cho bạn 10 tỷ - Kanny S
Đùa thôi, chứ so sánh giữa Processing IDE và Arduino IDE khác nhau rõ nhất đó là Serial Monitor.
Trên Procesing, cái khung đen đen ngoài thông báo những nội dung trong quá trình biên dịch code như trên Arduino IDE, nó còn là một console log nơi bạn dùng để debug. Tương tự như Serial Monitor như Arduino IDE, nhưng đơn giản hơn nhiều.
Thứ hai, vấn đề này khá quan trọng và nếu bạn mới tiếp cận sẽ bị dừng ở vấn đề này rất lâu. Đó là việc, làm thế nào để mở cổng COMx và làm sao biết đó là cổng Serial này là cổng kết nối với một bé Arduino, hay một cách cụ thể hơn là sản phẩm của bạn.
Chia nhỏ vấn đề này ra, chúng ta sẽ thấy có 2 vấn đề con cần giải quyết, đó là:
- Làm sao để biết được có bao nhiêu kết nối Serial
- Làm thế nào kết nối tới một cổng Serial
- Làm thế để xác định đó là của mạch Arduino, hoặc sản phẩm của bạn.
Làm sao để biết được có bao nhiêu kết nối Serial? Nếu bạn có kiến thức về Java thì sẽ dễ dàng làm được điều này. Nhưng với Processing, mọi thứ dễ hơn nhiều với Processing
import processing.serial.*; // import thư viện serial, tương tự như hàm include thôi :D void setup() { size(100, 100); // mở một screen có kích thức rộng x cao = width x heigth = 100 x 100 // cho vui, vì Processing lúc nào cũng có yêu cầu này. // Không có hàm size này thì biên dịch sẽ gặp lỗi, // tương tự như hàm setup() hay loop() trong Arduino println(Serial.list()); } void draw() { }
Kết quả trả về có thể sẽ có dạng danh sách các cổng COM hay cổng /dev/tty,... Ví dụ trên máy của mình khi chỉ gắn một con Arduino Uno R3
[0] "COM3"
Làm thế nào kết nối tới một cổng Serial? Ta chỉ cần việc truyền vị trí của cổng đó trong danh sách thôi (ở đây là số 0).
import processing.serial.*; // import thư viện serial, tương tự như hàm include thôi :D Serial serial; // Khởi tạo đối tượng Serial có tên là serial (phân biệt hoa thường nhé) int portIndex = 0; // Lựa chọn vị trí cổng Serial trong danh sách void setup() { size(100, 100); // mở một screen có kích thức rộng x cao = width x heigth = 100 x 100 // cho vui, vì Processing lúc nào cũng có yêu cầu này. // Không có hàm size này thì biên dịch sẽ gặp lỗi, // tương tự như hàm setup() hay loop() trong Arduino println(Serial.list()); // Mở cổng Serial ở vị trí 0 với mức baudrate 9600 serial = new Serial(this, Serial.list()[portIndex], 9600); } void draw() { if (serial.available() > 0) { // Nếu có thông tin bất kỳ được gửi đến input buffer int val = serial.read(); // đọc nó, tương tự như trên Arduino. // Bạn nên tham khảo thư viện Serial của Arduino nếu chưa hiểu // http://arduino.vn/reference/library/serial/1/gioi-thieu/serial-thu-vien-giao-tiep-giua-cac-mach-arduino-de-hoc-nhat print(char(val)); } }
Nếu bạn đã upload một sketch bất kỳ sử dụng giao thức Serial với mức baudrate là 9600 trên Arduino UNO thì nó sẽ trả về cho bạn một "đống" số ở console!
Lưu ý: Khi bạn sử dụng processing và đang dùng cổng Serial để kết nối với Arduino thì bạn cần "Stop" chương trình Processing của mình lại thì mới nạp sketch mới lên được. Lý do: để sử dụng được cổng Serial, chương trình phải có quyền đọc nội dung trong cổng đó. Nhưng Processing hay Arduino đều cần phải mở cổng trước thì mới thao tác tiếp được. Nghĩa là nếu không mở cổng được (vì cổng đã được mở) thì Processing hay Arudino sẽ báo lỗi: Cổng Serial đang bận!
Ví dụ, mình đã upload sketch sau lên Arduino:
Kết quả trên console sẽ có dạng như thế này
Vậy, ta đã kết nối được đến Serial của Arduino bằng cách sử dụng phần mềm Processing. Và có một vấn đề mới phát sinh đó là, làm thế nào để gửi một lệnh đi từ Processing đến Arduino?
Nghĩ một cách đơn giản, ta chỉ việc sử dụng thư viện Serial Command trên Arduino, như đã nói ở bài Giao tiếp giữa hai mạch Arduino bất kỳ; và trên Processing, ta chỉ việc println dòng lệnh đó ra luôn là xong! Nghĩ là làm, mình sẽ sử dụng đoạn mã trong bài trước nhé. Tuy nhiên, có chỉnh sửa lại một tí !
#include <SerialCommand.h> // Thêm vào sketch thư viện Serial Command SerialCommand sCmd; // Khai báo biến sử dụng thư viện Serial Command const int LED_13 = 13; void setup() { //Khởi tạo Serial ở baudrate 9600 (trùng với HOST) Serial.begin(9600); //pinMode đèn LED 13 pinMode(LED_13, OUTPUT); // Một số hàm trong thư viện Serial Command sCmd.addCommand("LED", LED); } void loop() { sCmd.readSerial(); //Bạn không cần phải thêm bất kỳ dòng code nào trong hàm loop này cả } // hàm LED_red sẽ được thực thi khi gửi hàm LED_RED void LED(){ //Đoạn code này dùng để đọc TỪNG tham số. Các tham số mặc định có kiểu dữ liệu là "chuỗi" char *arg; arg = sCmd.next(); int value = atoi(arg); // Chuyển chuỗi thành số digitalWrite(LED_13, value); }
Mình chỉ xây dựng một command có tên là LED nhận một tham số là status - trạng thái của LED 13.
LED 1 LED 0
Bạn hãy thử bằng Serial Monitor trước nhé . Và khi thử xong đừng quên tắt Serial để Processing còn có cơ để kết nối! Bây giờ thử upload đoạn code sau lên Processing nào
import processing.serial.*; // import thư viện serial, tương tự như hàm include thôi :D Serial serial; // Khởi tạo đối tượng Serial có tên là serial (phân biệt hoa thường nhé) int portIndex = 0; // Lựa chọn vị trí cổng Serial trong danh sách void setup() { size(100, 100); // mở một screen có kích thức rộng x cao = width x heigth = 100 x 100 // cho vui, vì Processing lúc nào cũng có yêu cầu này. // Không có hàm size này thì biên dịch sẽ gặp lỗi, // tương tự như hàm setup() hay loop() trong Arduino println(Serial.list()); // Mở cổng Serial ở vị trí 0 với mức baudrate 9600 serial = new Serial(this, Serial.list()[portIndex], 9600); } long time = 0; int status; void draw() { if (millis() - time > 1000) { // Mỗi một giây thay đổi trạng thái một lân status = (status + 1) % 2; serial.write("LED " + status + "\n");// Tương tự Serial.print hay Serial.write trên Arduino time = millis(); } }
Khà khà, vậy là bây giờ chúng ta có thể gửi lệnh từ máy tính đến Arduino thông qua Processing rồi hehe.
Làm thế để xác định đó là của mạch Arduino, hoặc sản phẩm của bạn. Mình cũng đã nghiên cứu vấn đề này và rút ra một số nhận xét như sau :D. Thực sự chẳng có cách nào để bạn kiểm tra bằng phần mềm rằng một mạch bạn gắn vào máy tính là Arduino hay không. Vì sao? Chúng ta chỉ xét đến những cổng COM trên máy Windows thôi nhé, trên MAC hay Linux thì là các cổng /dev/tty.... Vấn đề trên có thể nói lại là, làm thế nào tìm được mạch Arduino trong tập hợp các cổng COM của máy tính? Rõ ràng, nó chỉ cho chúng ta tập hợp mẫu chứ chẳng cho ta một điều kiện nào để xác định một mạch điện tử là một board Arduino cả. Tuy nhiên, mỗi mạch điện tử có một mã số riêng, và chúng ta có thể xác định được, nhưng vấn đề đó khá phức tạp và mình cũng chưa biết nên chưa nói được. Mặt khác, trong thực tế, thiết nghĩ, nếu có cách nào tìm được thì các begginer không cần phải khổ sở khi lựa chọn cổng COM của Arduino trong vô vàn cổng COM của máy tính. Vậy vế thứ nhất của vấn đề đành bó tay, vậy còn vế còn lại? Làm thế nào để xác định đây là sản phẩm của mình trong vô vàn các thiết bị ? Cái này thiết nghĩ có thể làm được, vì đã có nhiều mô hình làm được thế... Suy nghĩ một cách đơn giản, chúng ta sẽ "ping" các thiết bị và thiết bị nào trả lời lại đúng như yêu cầu của ta thì đó là sản phẩm của ta . Còn bá đạo hơn nữa thì bạn có thể tìm hiểu thêm về cách xây dựng các tập lệnh AT (AT command).
Thử thôi, trên Arduino mình sẽ upload đoạn code này để khi có ai đó ping tới đúng mã số thì nó trả về "ping ok" + mã số
#include <SerialCommand.h> // Thêm vào sketch thư viện Serial Command SerialCommand sCmd; // Khai báo biến sử dụng thư viện Serial Command const int LED_13 = 13; const int ProductCode = 321; // mã sản phẩm của mình void setup() { //Khởi tạo Serial ở baudrate 9600 (trùng với HOST) Serial.begin(9600); //pinMode đèn LED 13 pinMode(LED_13, OUTPUT); // Một số hàm trong thư viện Serial Command sCmd.addCommand("PING", ping); sCmd.addCommand("LED", led); } void loop() { sCmd.readSerial(); //Bạn không cần phải thêm bất kỳ dòng code nào trong hàm loop này cả } // hàm LED_red sẽ được thực thi khi gửi hàm LED_RED void led(){ //Đoạn code này dùng để đọc TỪNG tham số. Các tham số mặc định có kiểu dữ liệu là "chuỗi" char *arg; arg = sCmd.next(); int value = atoi(arg); // Chuyển chuỗi thành số digitalWrite(LED_13, value); free(arg); } void ping() { char *arg; arg = sCmd.next(); int code = atoi(arg); if (code == ProductCode) { // Yêu cầu đúng mã sản phẩm Serial.print(F("PING OK ")); Serial.println(ProductCode); } free(arg); }
Rồi, hãy xem đoạn code của mình trên Processing như sau. Mình sẽ ping tất cả những cổng Serial có thể được!
import processing.serial.*; // import thư viện serial, tương tự như hàm include thôi :D Serial serial; // Khởi tạo đối tượng Serial có tên là serial (phân biệt hoa thường nhé) final int PRODUCT_CODE = 321; final int BAUDRATE[] = {4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200}; final String OK_CODE = "PING OK " + PRODUCT_CODE; // PING OK 321 - độ dài 11 final int OK_CODE_LENGTH = 11; final int TIMEOUT = 1000; // 1000 mili giây = 1 s final int MAXIMUM_SETUP_TIME = 2000; boolean serialStatus = false; long time = 0; void delay(long milli) { time = millis(); while (millis() - time <= milli); time = millis(); } void setup() { size(100, 100); // mở một screen có kích thức rộng x cao = width x heigth = 100 x 100 // cho vui, vì Processing lúc nào cũng có yêu cầu này. // Không có hàm size này thì biên dịch sẽ gặp lỗi, // tương tự như hàm setup() hay loop() trong Arduino println(Serial.list()); String buffer; String PingCode = "PING " + PRODUCT_CODE; // Mở cổng Serial ở vị trí 0 với mức baudrate 9600 String[] list = Serial.list(); for (int i = 0; i < list.length; i++) for (int j = 0; j < BAUDRATE.length; j++) try { serial = new Serial(this, list[i], BAUDRATE[j]); //đợi đến khi hàm setup trong sản phẩm của bạn chạy xong delay(MAXIMUM_SETUP_TIME); serial.write(PingCode); serial.write('\n'); int length = 0; buffer = ""; delay(100);// chờ 1 tí để Arduino còn trả lời bạn nữa :) while (serial.available() > 0) { buffer += serial.readChar(); time = millis(); if (++length >= OK_CODE_LENGTH) break; if (millis() - time > TIMEOUT) { serial.stop(); throw new Exception("timeout\n"); } } println(buffer); if (!OK_CODE.equals(buffer)) { serial.stop(); throw new Exception("not match\n"); } serialStatus = true; println("Connected to my sketch " + list[i] + " at baudrate " + BAUDRATE[j]); break; } catch (Exception e) { println(e); println("Can't connect to port " + list[i] + " at baudrate " + BAUDRATE[j]); } if (!serialStatus) { noLoop(); // không chạy hàm draw nữa println("Can't connect to any port at any baudrate. Sorry! please check your connection"); } else time = millis(); // đặt lại thời gian } int status; void draw() { if (millis() - time > 1000) { // Mỗi một giây thay đổi trạng thái một lân status = (status + 1) % 2; try { serial.write("LED " + status + "\n"); } catch (Exception e) { println(e); } time = millis(); } } /* Tập lệnh try {} catch () {} bạn nên tham khảo thêm ở google nhé. Nôm na là nó sẽ thực hiện đoạn lệnh trong try rồi nếu có lỗi thì tùy vào catch bắt lỗi mà nó thực thi... Ví dụ try {chơi game} catch (bị mét) { die; } */
Oki, vậy là ổn
Ứng dụng của việc này là gì?
Một khi đã biết Why (tại sao phải dùng máy tính điều khiển Arduino), How (làm thế nào để điều khiển), thì việc tìm câu trả lời cho câu hỏi What (tôi sẽ ứng dụng những gì vừa học để làm gì) rất đơn giản. <Golden Circle>
Ví dụ, tôi có thể dùng một module bluetooth tạo một cổng Serial thông qua kết nối bluetooth rồi từ đó gửi lệnh cho Arduino bằng máy tính. Ngoài ra, bạn cần phải làm đồ họa cho phần mềm Processing của bạn để mọi thứ trở nên sinh động hơn. Đừng ngại khó hãy thử xem. Gợi ý cho bạn là Processing cực kì đơn giản, bạn chỉ cần đọc hướng dẫn của Procesing là biết ngay thôi.
Chúc bạn thành công!