Truyền các số kiểu long, int, float trong giao tiếp Serial (UART)

Mô tả dự án: 

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 đó.

 

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.

Nan giải …??

Đã 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 angryangry, trong khi thực tế thì ta luôn cần nhiều hơn vậy.devil

Đó 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ư  kinh độ 50.45656 độ, vĩ độ 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

Link: (tải lên ngày 14/2/2017)

https://drive.google.com/file/d/0BzMEcyRK_uUFNXNaTnY1NnpablU/view?usp=sharing (mirror)

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é)

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. laughheartyeswinkcheekyenlightenedenlightenedenlightened

<Tác giả Thái Sơn>

lên
10 thành viên đã đánh giá bài viết này hữu ích.
Các dự án được truyền cảm hứng

Select any filter and click on Apply to see results

Các bài viết cùng tác giả

AVR-Xuất xung với tần số và độ rộng theo ý muốn

Tiếp tục chuỗi bài: Điều khiển pin bằng ngôn ngữ chính thống. 

Bài viết này sẽ giúp bạn tạo một xung PWM có tần số và độ rộng xung theo ý muốn. 

lên
16 thành viên đã đánh giá bài viết này hữu ích.

ST7565 | Chuyển động trong lập trình Game và đồ họa | Phần 3

Ở hai bài trước, chúng ta đã làm quen và biết cách điều khiển độc lập 1 đối tượng. ở phần 3 này, các bạn sẽ biết thêm về cách quản lý nhiều hơn 1 đối tượng trong game. Hãy cùng bắt đầu nào.laugh

lên
9 thành viên đã đánh giá bài viết này hữu ích.