Nick Chung gửi vào
- 79698 lượt xem
Rất dễ dàng để gửi một số hoặc chuỗi kí tự để hiển thị lên màn hình qua cổng Serial. Nhưng mọi chuyện không đơn giản như vậy khi ta muốn truyền số kiểu int, long, double, ..v.v giữa 2 board arduino với nhau bằng Serial. Đừng lo, sau đây mình sẽ giúp bạn giải quyết vấn đề nan giải đó.
Bài viết này đã cũ !
Thông báo ngày 11/4/2018 :
Bài viết này đã cũ ,Thư viện hiện đã được chỉnh sửa để truyền tin an toàn hơn theo link nàyi :
<link bài mới> http://arduino.vn/tutorial/5897-thu-vien-truyen-du-lieu-bat-ky-byte-long-float-double-theo-goi-tin-cho-arduino
Bài viết này được giữ lại dưới dạng tài liệu tham khảo.
Giao tiếp Serial giữa 2 arduino
Ai cũng biết giao tiếp Serial có tên gọi chính thức là giao tiếp UART, sử dụng 2 đường dây là TX (cổng xuất dữ liệu) và RX (cổng nhận dữ liệu). Để kết nối ta phải nối theo quy tắc TX của board phát nối với RX còn lại của board nhận.
Vì giao tiếp này không sử dụng xung Clock, nên để truyền nhận chính xác thì 2 module phải thống nhất về tốc độ xung nhịp (như một dạng cộng hưởng xung nhịp). Điều này cho phép ta chỉ cần nối 1 dây duy nhất trong trường hợp một bên chỉ phát – bên kia chỉ thu.
Đã có những bài giới thiệu về giao tiếp Serial giữa 2 board arduino, nhưng đó chỉ là giới thiệu truyền dẫn 1 byte để demo (khi đó bạn chỉ có thể truyền đi một số có giá trị từ 0 -> 255).
Ví dụ như trong 1 dự án trạm khí tượng, bạn sẽ lên kế hoạch sử dụng 2 board arduino (1 board chính – 1 board phụ). Board chính đặt ở phòng để giám sát và điều khiển, board phụ đặt trên tròi kí tượng đọc các trị cảm biến rồi gửi về qua Serial. Hoặc pro hơn là các dự án về robot thám hiểm.
Một vấn đề mà bạn vấp phải là bạn chỉ biết truyền một số có giá trị trong khoảng từ 0 => 255 , trong khi thực tế thì ta luôn cần nhiều hơn vậy.
Đó có thể là giá trị của biến (nhiệt , độ ẩm=90%,áp suất=2000atm, tốc độ=4500 rpm, ..v.v )một kiểu int, long, unsigned long hay kiểu kiểu số thực như float , double (ví dụ thường thấy ở các module GPS trả về như vĩ độ 50.45656 độ, kinh độ 102.55433).
Và bạn biết không, chính mình cũng đang bị chậm dự án bởi vấn đề này.
Giới thiệu thư viện hỗ trợ.
Đây là thư viện dành cho giao tiếp Serial giữa 2 board arduino dùng để truyền nhận tất cả các kiểu số và kí tự
Công việc của nó cũng khá đơn giản!
Để truyền: Ví dụ để truyền số 12345 kiểu int, như ta đã biết trên arduino kiểu int có cỡ là 2 byte, ta sẽ tách 2 byte lưu trữ của biến này thành từng byte rồi xuất ra đường truyền.
Để nhận: Ta sẽ làm ngược lại là ghép 2 byte đã nhận về một số kiểu int.
Kĩ thuật này đã được mình ứng dụng trong việc lưu trữ các số (lớn hơn 255) vào EEPROM tại bài viết này:
http://arduino.vn/tutorial/1370-huong-dan-su-dung-ic-eeprom-24cxx-cua-atmel-va-thu-vien
Để hiểu hơn về phương pháp tách/ghép, bạn hãy xem mã nguồn của thư viện này nhé.
Giới thiệu thư viện
Hiện tại thư viện có các chức truyền nhận các số kiểu:
Kiểu double: từ 2.2250738585072014 E – 308 đến 1.7976931348623158 E + 308.
void write_double(double value); double read_double();
Kiểu float: từ 1.175494351 E – 38 đến 3.402823466 E + 38
void write_float(float value); float read_float();
Kiểu int8_t: từ -128 đến 127
void write_int8_t(int8_t value); int8_t read_int8_t();
Kiểu uint8_t (là kiểu byte): từ 0 đến 255.
void write_uint8_t(uint8_t value); uint8_t read_uint8_t();
Kiểu int16_t (là kiểu int): từ -32768 đến 32767
void write_int16_t(int16_t value); int16_t read_int16_t();
Kiểu uint16_t (giống kiểu unsigned int): từ 0 đến 65536
void write_uint16_t(uint16_t value); uint16_t read_uint16_t();
Kiểu uint32_t (giống kiểu unsigned long) :từ 0 đến 4,294,967,295
void write_uint32_t(uint32_t value); uint32_t read_uint32_t();
Kiểu int32_t (giống kiểu long):từ -2,147,483,648 đến 2,147,483,647
void write_int32_t(int32_t value); int32_t read_int32_t();
Kiểu unsigned char (là kiểu byte): 0 đến 255
Tương ứng 256 kí tự trong bảng mã ASCII, mỗi lần chỉ gửi được 1 kí tự
void write_char(unsigned char value); unsigned char read_char();
Gửi một chuỗi kí tự
Với n là số lượng kí tự muốn gửi (hoặc nhận)
void write_string(unsigned char value[], uint32_t n ); void read_string(unsigned char *value, uint32_t n);
Gửi một mã dạng kí tự
void send_pass(unsigned char value[], uint32_t n );
Rồi sau đó sử dụng hàm này để kiểm tra xem mã có khớp không, nếu đúng thì trả về 1, sai trả về 0.
bool check_pass(unsigned char value[], uint32_t n );
Xóa bộ nhớ đệm
void clear_buffer();
Ví dụ 1: truyền số kiểu uint32_t (unsigned long)
Mạch phát |
Mạch nhận |
#include "UART_ARDUINO.h" UART Gui; void setup() { Gui.begin(9600); // baud: 200 -> 250,000. } uint32_t Num = 123456789; void loop() { if (Gui.check_pass("abc", 3) == true) { // kiểm tra có yêu cầu từ máy chủ // Gui.clear_buffer();// reset lại bộ nhớ đệm Gui.write_uint32_t(Num); } }
|
#include "UART_ARDUINO.h" UART Nhan; // nhận byte button = 5; // void setup() { Nhan.begin(9600); // baud: 200 -> 250,000. pinMode(13, HIGH); // PIN 13 OUT CHO LED pinMode(button, INPUT_PULLUP); } uint32_t Num; void loop() { if (digitalRead(button) == 0) { delay(300); //chống nhiễu Nhan.send_pass("abc", 3); } if (Serial.available() >= sizeof(Num)) { Num = Nhan.read_uint32_t(); if (Num == 123456789) { digitalWrite(13, 1); // bật đèn, báo hiệu test thành công. } } } //loop
|
Nhấn nút để yêu cầu mạch phát trả về dữ liệu
Nhan.send_pass("abc", 3)
Máy chủ gửi một mật khẩu yêu cầu là: abc
3 : là số lượng chữ cái
Do đó mật khẩu càng ngắn càng tốc độ nhanh hơn nhưng cũng dễ bị sai hơn(do bị trùng với số của dữ liệu)
Ví dụ 2: truyền số kiểu float
Nối mạch như ví dụ trên
Mạch phát |
Mạch nhận |
#include "UART_ARDUINO.h" UART Gui; void setup() { Gui.begin(9600); // baud: 200 -> 250,000. } float Num = 12.345678; void loop() { if (Gui.check_pass("abc", 3) == true) { // kiểm tra có yêu cầu từ máy chủ // Gui.clear_buffer();// reset lại bộ nhớ đệm Gui.write_float(Num); } } |
#include "UART_ARDUINO.h" UART Nhan; // nhận byte button = 5; // void setup() { Nhan.begin(9600); // baud: 200 -> 250,000. pinMode(13, HIGH); // PIN 13 OUT CHO LED pinMode(button, INPUT_PULLUP); } float Num; void loop() { if (digitalRead(button) == 0) { // nhấn nút để yêu cầu mạch phát trả về dữ liệu delay(300); //chống nhiễu Nhan.send_pass("abc", 3); } if (Serial.available() >= sizeof(Num)) { Num = Nhan.read_float(); if (Num == 12.345678) { digitalWrite(13, 1); // bật đèn, báo hiệu test thành công. } } } //loop |
Nhấn nút để yêu cầu mạch phát trả về dữ liệu, nếu đèn sáng thì test truyền nhận thành công.
Truyền số khác
Làm tương tự,
void write_double();
|
2.2250738585072014 E – 308 => 1.7976931348623158 E + 308.
|
void write_float();
|
1.175494351 E – 38 => 3.402823466 E + 38
|
void write_int8_t();
|
-128 => 127
|
void write_uint8_t();
|
0 => 255
|
void write_int16_t();
|
-32768 => 32767
|
void write_uint16_t();
|
0 => 65536
|
void write_uint32_t();
|
0 =>4,294,967,295
|
void write_int32_t();
|
-2,147,483,648 => 2,147,483,647
|
Chú ý: trên arduino uno R3, kiểu float và double có cùng cỡ là 4 byte,( giá trị từ 1.175494351 E – 38 đến 3.402823466 E + 38);
Trên arduino Due, kiểu double mới có cỡ 8 byte.
Tốc độ truyền các số có kích thước lớn (byte) sẽ chậm hơn các số có kích thước nhỏ hơn (điều này là dĩ nhiên).!
Ví dụ 3 truyền 1 kí tự trong bảng ASCII.
Code: mở phần Examples trong file tải xuống để xem code ví dụ!
Ví dụ 4 :Truyền một chuỗi kí tự để nhắn tin.
Code: mở phần Examples trong file tải xuống để xem code ví dụ!
Ví dụ 5 : Truyền cùng lúc nhiều số (đóng gói tập tin).
Đây là đáng nói nhất !
Mạch phát (slave) |
Mạch nhận (master) |
#include "UART_ARDUINO.h" UART Gui; void setup() { Gui.begin(9600); // baud: 200 -> 250,000. } uint8_t nhiet_do; uint16_t do_am; int32_t toc_do; uint32_t do_cao; float toa_do; void loop() { if (Gui.check_pass("abc", 3) == true) { // kiểm tra có yêu cầu từ máy chủ gui_du_lieu(25, 60, 2700, 3600, 12.1234); } } void gui_du_lieu(uint8_t a, uint16_t b, int32_t c, uint32_t d, float e) { // lưu ý : gói tin truyền đi có kích cỡ lớn nhất là 64 byte Gui.write_uint8_t(a); // cần 1 byte Gui.write_uint16_t(b); // cần 2 byte Gui.write_int32_t(c); // cần 4 byte Gui.write_uint32_t(d); // cần 4 byte Gui.write_float(e); // cần 4 byte // tổng cộng cần 15 byte < (max =64) } |
#include "UART_ARDUINO.h" UART Nhan; // nhận byte button = 5; // void setup() { Nhan.begin(9600); // baud: 200 -> 250,000. pinMode(13, HIGH); // PIN 13 OUT CHO LED pinMode(button, INPUT_PULLUP); //sử dụng một nút ấn (button) nối với pin 5 và nguồn âm // nhấn nút để yêu cầu mạch phát trả về dữ liệu } uint8_t nhiet_do; uint16_t do_am; int32_t toc_do; uint32_t do_cao; float toa_do; void loop() { if (digitalRead(button) == 0) { // nhấn nút để yêu cầu mạch phát trả về dữ liệu delay(300); //chống nhiễu Nhan.send_pass("abc", 3); //máy chủ gửi một mật khẩu yêu cầu là : abc // 3 : là số lượng chữ cái //(do đó mật khẩu càng ngắn càng tốc độ nhanh hơn nhưng cũng dễ bị sai hơn(do bị trùng với số của dữ liệu)) } if (nhan_du_lieu(&nhiet_do, &do_am, &toc_do, &do_cao, &toa_do) == true) { if (nhiet_do == 25) { if (do_am == 60) { if (toc_do == 2700) { if (do_cao == 3600) { if (toa_do == 12.1234) { digitalWrite(13, 1); //bật đèn để báo test thử nghiệm thành công (số truyền qua là chính xác) } } } } } } //if lớn } //loop bool nhan_du_lieu(uint8_t* a, uint16_t* b, int32_t* c, uint32_t* d, float* e) { //b1: tìm kích thước để kiểm tra gói tin truyền về đã đủ chưa uint32_t kich_co_goi_tin = sizeof(*a) + sizeof(*b) + sizeof(*c) + sizeof(*d) + sizeof(*e); if (Serial.available() >= kich_co_goi_tin) { //đã nhận đủ gói tin ///////////// (*a) = Nhan.read_uint8_t(); (*b) = Nhan.read_uint16_t(); (*c) = Nhan.read_int32_t(); (*d) = Nhan.read_uint32_t(); (*e) = Nhan.read_float(); ///////////// // Nhan.clear_buffer();// reset lại bộ nhớ đệm return true; // cuối cùng trả về true báo đã nhận đủ và hoàn tất } else { return false; // báo chưa đủ } } |
Tất cả các ví dụ trên đều được mình test thành công.!
Download thư viện
Thông báo ngày 11/4/2018 :
Bài viết này đã cũ ,Thư viện hiện đã được chỉnh sửa để truyền tin an toàn hơn theo link nàyi :
Bài viết này được giữ lại dưới dạng tài liệu tham khảo.
Link: ( đã nâng cấp (ngày 13/6/2017))
https://drive.google.com/file/d/0BzMEcyRK_uUFNXNaTnY1NnpablU/view?usp=sharing (mirror)
https://github.com/NickChungVietNam/UART_ARDUINO
Flie khoảng 500kb có chứa thư viện, hình ảnh, các file ví dụ, tài liệu hướng dẫn do mình biên soạn.
(Hãy liên hệ với mình qua phần bình luận nếu thư viện có lỗi nhé)
Update :
Hiện tại thư viện đã nâng cấp : với chức năng truyền nhận liên tục mà không cần gửi yêu cầu từ máy chủ. Rất phù hợp với các dự án yêu cầu lắng nghe dữ liệu trả về liên tục như robot tự hành, tàu lặn.. Ngoài ra thư viện đã được hỗ trợ sử dụng trên esp8266 và esp32. .Link vẫn như cũ (google drive)
Tạm kết
Mình viết ra thư viện chỉ đơn thuần phục vụ nhu cầu về truyền dẫn số liệu. Tuy không theo quy tắc chuẩn của đóng gói và truyền dẫn (nó cần có thêm các khả năng như yêu cầu gửi lại, hết hạn, ghép các tập tin…). Arduino cũng hỗ trợ có thể có nhiều hơn 1 cổng Serial bằng thư viện SerialSoftWare. Nhưng thôi, tới đâu hay tới đấy, hi vọng trong tương lai mình và các bạn có thời gian để nâng cấp thêm cho nó.
Hi vọng bài viết này sẽ giúp ích cho bạn trong tương lai.
<Tác giả Thái Sơn>