Bluetooth Low Energy (BLE) là gì: quảng bá, GATT, dịch vụ, đặc tính và ESP32

Giải thích Bluetooth Low Energy: quảng bá và kết nối, cấu trúc GATT gồm dịch vụ, đặc tính, mô tả, UUID. Hướng dẫn cho ESP32 làm thiết bị BLE có đặc tính đọc, ghi, thông báo và thử bằng điện thoại.

Bluetooth Low Energy (BLE) là nhánh tiết kiệm điện của Bluetooth, có mặt trong vòng đeo tay, cảm biến chạy pin, khóa thông minh và hầu hết điện thoại hiện nay. BLE không truyền một luồng byte như cổng nối tiếp; nó tổ chức dữ liệu thành các giá trị có tên để bên kia đọc, ghi hoặc đăng ký nhận thông báo. Bài viết giải thích hai giai đoạn quảng bá và kết nối, cấu trúc GATT gồm dịch vụ, đặc tính, mô tả, rồi cho ESP32 làm một thiết bị BLE hoàn chỉnh để thử bằng điện thoại.

1. BLE khác Bluetooth cổ điển thế nào?

Tiêu chíBluetooth cổ điểnBLE
Mục đíchTruyền liên tục: âm thanh, cổng nối tiếp không dâyTruyền ít dữ liệu, ngắt quãng
Cách trao đổiLuồng byte giống UARTĐọc, ghi các giá trị có cấu trúc (GATT)
Điện năngCao hơn, kết nối luôn hoạt độngRất thấp, hai bên ngủ giữa các nhịp trao đổi
Điện thoạiAndroid; iPhone không mở kiểu cổng nối tiếp cho thiết bị tự làmAndroid và iPhone
Thư viện trên ESP32BluetoothSerialBLE (BLEDevice, BLEServer…)

Hai loại cùng dùng băng tần 2,4 GHz nhưng không nói chuyện được với nhau. ESP32 hỗ trợ cả hai. Bluetooth cổ điển kiểu cổng nối tiếp đã có trong bài điều khiển relay bằng điện thoại qua Bluetooth.

2. Hai giai đoạn: quảng bá và kết nối

Hai giai đoạn của BLE: ở giai đoạn quảng bá ESP32 phát gói quảng bá chứa tên và UUID dịch vụ, điện thoại gửi yêu cầu kết nối; ở giai đoạn kết nối điện thoại tìm dịch vụ, ghi đặc tính, bật thông báo và ESP32 gửi thông báo
ESP32 làm thiết bị ngoại vi, điện thoại làm thiết bị trung tâm: quảng bá trước, kết nối sau.
  • Quảng bá (advertising): thiết bị ngoại vi phát đều đặn những gói rất ngắn chứa tên và UUID của dịch vụ chính. Chưa có kết nối nào; mọi thiết bị đang quét ở gần đều nhận được.
  • Kết nối (connection): thiết bị trung tâm, thường là điện thoại, chọn một thiết bị đang quảng bá và gửi yêu cầu kết nối. Từ đó hai bên trao đổi riêng với nhau theo nhịp đã hẹn và ngủ trong thời gian còn lại.
  • Thiết bị ngoại vi (peripheral) là bên quảng bá và chờ; thiết bị trung tâm (central) là bên quét và chủ động kết nối.
  • Sau khi kết nối, bên giữ dữ liệu gọi là máy chủ GATT (server), bên đọc ghi gọi là máy khách GATT (client). Thông thường thiết bị ngoại vi là máy chủ.
  • Một số thiết bị chỉ quảng bá mà không bao giờ kết nối, ví dụ thẻ định vị và cảm biến phát số đo ngay trong gói quảng bá.

3. GATT: dịch vụ, đặc tính, mô tả

Cấu trúc GATT của thiết bị trong bài: thiết bị chứa hai dịch vụ chuẩn và một dịch vụ riêng; dịch vụ riêng có đặc tính đèn với thuộc tính đọc, ghi và đặc tính thời gian với thuộc tính đọc, thông báo kèm mô tả 0x2902; mỗi tầng có một UUID
Cây GATT của thiết bị trong bài: một dịch vụ riêng với hai đặc tính.
  • Dịch vụ (service) gom các giá trị liên quan thành một nhóm, ví dụ dịch vụ pin, dịch vụ nhịp tim.
  • Đặc tính (characteristic) là một giá trị cụ thể trong dịch vụ, kèm thuộc tính cho biết được phép làm gì với nó: đọc, ghi, thông báo.
  • Mô tả (descriptor) là thông tin phụ gắn với đặc tính. Quan trọng nhất là mô tả 0x2902: máy khách ghi vào đây để bật hoặc tắt việc nhận thông báo.
  • UUID là mã nhận diện của từng dịch vụ, đặc tính, mô tả. UUID 16 bit dành cho các loại chuẩn; dịch vụ tự định nghĩa dùng UUID 128 bit tạo ngẫu nhiên.
  • Giá trị của đặc tính chỉ là một dãy byte. BLE không quy định ý nghĩa; chương trình ở hai phía phải thống nhất với nhau. Với thiết lập mặc định, mỗi thông báo mang tối đa 20 byte.

4. Bốn thao tác trên một đặc tính

Bốn thao tác trên đặc tính BLE: đọc là điện thoại hỏi và ESP32 trả giá trị; ghi là điện thoại gửi giá trị mới và ESP32 xác nhận; thông báo là ESP32 tự gửi giá trị mới; chỉ báo là ESP32 gửi giá trị mới và điện thoại xác nhận
Đọc và ghi do máy khách khởi xướng; thông báo và chỉ báo do máy chủ tự gửi.

Thông báo là điểm mạnh của BLE: thay vì điện thoại hỏi liên tục, ESP32 chỉ gửi khi có số liệu mới, nhờ vậy cả hai bên đều tiết kiệm pin.

5. Chuẩn bị

  • Một mạch ESP32 DevKit V1 30 chân (hoặc bản dùng chip CH340); thêm một mạch nữa nếu muốn chạy hai chương trình cùng lúc.
  • Cáp USB để nạp chương trình. Không cần linh kiện ngoài: bài dùng đèn LED có sẵn trên mạch (GPIO2).
  • Một điện thoại có Bluetooth và một ứng dụng quét BLE, loại cho xem dịch vụ, đặc tính và cho đọc, ghi, nhận thông báo.
  • Arduino IDE 2.x với gói esp32 by Espressif Systems (bài dùng phiên bản 3.3.10). Thư viện BLE có sẵn trong gói, không cần cài thêm.

6. Chương trình 1: ESP32 làm thiết bị BLE

/*
  PYWORLD.VN - BLE với ESP32: thiết bị ngoại vi có một dịch vụ và hai đặc tính, thử bằng ứng dụng quét BLE trên điện thoại
  Đặc tính ĐÈN (đọc, ghi): 1 byte, ghi 01 để bật và 00 để tắt LED GPIO2
  Đặc tính THỜI GIAN (đọc, thông báo): số giây đã chạy, 4 byte, byte thấp trước; gửi thông báo mỗi giây
*/

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

#define LED_PIN 2

const char *TEN_THIET_BI = "PYWORLD-BLE";

// UUID 128 bit tự đặt cho dịch vụ và hai đặc tính của bài
#define UUID_DICH_VU   "c0de0001-7a4b-4e2f-9b1d-5f3a8e6c2d10"
#define UUID_DEN       "c0de0002-7a4b-4e2f-9b1d-5f3a8e6c2d10"
#define UUID_THOI_GIAN "c0de0003-7a4b-4e2f-9b1d-5f3a8e6c2d10"

BLECharacteristic *dacTinhThoiGian = nullptr;
volatile bool dangKetNoi = false;

// Thư viện gọi các hàm này khi có thiết bị kết nối hoặc ngắt kết nối
class SuKienKetNoi : public BLEServerCallbacks {
  void onConnect(BLEServer *mayChu) override {
    dangKetNoi = true;
  }
  void onDisconnect(BLEServer *mayChu) override {
    dangKetNoi = false;
  }
};

// Thư viện gọi hàm này khi điện thoại ghi vào đặc tính ĐÈN
class SuKienGhiDen : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *dacTinh) override {
    if (dacTinh->getLength() < 1) {
      return;
    }
    uint8_t giaTri = dacTinh->getData()[0];
    bool bat = (giaTri != 0x00 && giaTri != '0');   // Nhận cả byte 01 lẫn ký tự '1' gõ dưới dạng chữ
    digitalWrite(LED_PIN, bat ? HIGH : LOW);
    Serial.printf("Nhan lenh ghi: 0x%02X -> %s LED\n", giaTri, bat ? "BAT" : "TAT");
  }
};

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);

  BLEDevice::init(TEN_THIET_BI);
  BLEServer *mayChu = BLEDevice::createServer();
  mayChu->setCallbacks(new SuKienKetNoi());

  BLEService *dichVu = mayChu->createService(UUID_DICH_VU);

  BLECharacteristic *dacTinhDen = dichVu->createCharacteristic(
    UUID_DEN, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
  dacTinhDen->setCallbacks(new SuKienGhiDen());
  uint8_t dangTat = 0x00;
  dacTinhDen->setValue(&dangTat, 1);

  dacTinhThoiGian = dichVu->createCharacteristic(
    UUID_THOI_GIAN, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
  dacTinhThoiGian->addDescriptor(new BLE2902());    // Mô tả 0x2902: nơi điện thoại bật, tắt việc nhận thông báo
  dacTinhThoiGian->setValue((uint32_t)0);

  dichVu->start();

  // Gói quảng bá mang UUID của dịch vụ để bên quét nhận ra thiết bị
  BLEAdvertising *quangBa = BLEDevice::getAdvertising();
  quangBa->addServiceUUID(UUID_DICH_VU);
  quangBa->setScanResponse(true);
  BLEDevice::startAdvertising();

  Serial.printf("\nThiet bi BLE \"%s\", dia chi %s, dang quang ba\n",
                TEN_THIET_BI, BLEDevice::getAddress().toString().c_str());
}

void loop() {
  static unsigned long lucCapNhatCuoi = 0;
  static bool truocDoKetNoi = false;

  // Mỗi giây cập nhật giá trị; chỉ gửi thông báo khi đang có kết nối
  if (millis() - lucCapNhatCuoi >= 1000) {
    lucCapNhatCuoi = millis();
    uint32_t soGiay = millis() / 1000;
    dacTinhThoiGian->setValue(soGiay);              // 4 byte, byte thấp trước
    if (dangKetNoi) {
      dacTinhThoiGian->notify();
    }
  }

  if (dangKetNoi && !truocDoKetNoi) {
    Serial.println("Co thiet bi ket noi");
  }
  if (!dangKetNoi && truocDoKetNoi) {
    delay(500);                                     // Chờ ngăn xếp Bluetooth dọn xong kết nối cũ
    BLEDevice::startAdvertising();
    Serial.println("Thiet bi da ngat ket noi, quang ba lai");
  }
  truocDoKetNoi = dangKetNoi;
  delay(10);
}
  • BLEDevice::init() khởi động Bluetooth và đặt tên sẽ xuất hiện khi quét.
  • Trình tự dựng cây GATT: tạo máy chủ, tạo dịch vụ từ UUID, tạo từng đặc tính với UUID và thuộc tính của nó, rồi start() dịch vụ.
  • Thư viện báo sự kiện qua các lớp callback: onConnect(), onDisconnect() khi kết nối thay đổi, onWrite() khi điện thoại ghi vào đặc tính. Các hàm này chạy trong tác vụ của Bluetooth, không phải trong loop(), nên cần viết ngắn gọn.
  • Đặc tính có thuộc tính thông báo phải có mô tả 0x2902, thêm bằng addDescriptor(new BLE2902()).
  • setValue(soGiay) với số kiểu uint32_t lưu 4 byte theo thứ tự byte thấp trước; notify() gửi giá trị đó cho máy khách đã bật thông báo.
  • Khi máy khách ngắt kết nối, thiết bị phải quảng bá lại thì lần sau mới tìm thấy.
  • Ba UUID trong bài là số tự đặt. Sản phẩm riêng nên tạo bộ UUID ngẫu nhiên mới để không trùng với thiết bị khác.

Thư viện BLE chiếm nhiều bộ nhớ chương trình: một chương trình BLE đơn giản như trên đã dùng hơn ba phần tư vùng chứa mặc định của mạch. Nếu ghép thêm Wi-Fi mà báo chương trình quá lớn, chọn Tools > Partition Scheme loại có vùng ứng dụng lớn hơn.

7. Thử bằng điện thoại

Hình minh họa màn hình ứng dụng quét BLE trên điện thoại khi kết nối với ESP32: hai dịch vụ chuẩn, một dịch vụ riêng với đặc tính có giá trị 01 và đặc tính có giá trị 7D 00 00 00 đang bật thông báo; cách đổi bốn byte thành 125 giây
Màn hình ứng dụng quét BLE (hình minh họa) và cách đổi bốn byte nhận được thành số giây.
  1. Nạp chương trình 1, mở Tools > Serial Monitor ở 115200 baud để xem tên và địa chỉ của thiết bị.
  2. Mở ứng dụng quét BLE trên điện thoại, tìm PYWORLD-BLE và bấm kết nối. Không vào phần cài đặt Bluetooth của điện thoại để ghép đôi: thiết bị BLE kiểu này làm việc qua ứng dụng.
  3. Mở dịch vụ có UUID bắt đầu bằng c0de0001. Ở đặc tính c0de0002, ghi byte 01: đèn LED sáng. Ghi 00: đèn tắt.
  4. Ở đặc tính c0de0003, bấm đọc để lấy số giây một lần, hoặc bật nhận thông báo để giá trị tự cập nhật mỗi giây.
  5. Giá trị hiện dưới dạng bốn byte, ví dụ 7D 00 00 00. Byte thấp đi trước nên số thật là 0x0000007D, tức 125 giây.

8. Chương trình 2: quét các gói quảng bá

Chương trình này cho ESP32 đóng vai bên quét, giống bước đầu tiên của điện thoại. Nó chỉ nghe gói quảng bá, không kết nối với thiết bị nào.

/*
  PYWORLD.VN - BLE với ESP32: quét các gói quảng bá xung quanh, in địa chỉ, cường độ tín hiệu và tên từng thiết bị
  Mỗi lượt quét dài 5 giây; thiết bị nào quảng bá dịch vụ của chương trình 1 được đánh dấu riêng
  Không cần linh kiện ngoài; có hai mạch ESP32 thì nạp chương trình 1 cho mạch kia để thấy nó trong danh sách
*/

#include <BLEDevice.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

#define UUID_DICH_VU "c0de0001-7a4b-4e2f-9b1d-5f3a8e6c2d10"

const int THOI_GIAN_QUET_GIAY = 5;

BLEScan *boQuet = nullptr;

void setup() {
  Serial.begin(115200);
  BLEDevice::init("");
  boQuet = BLEDevice::getScan();
  boQuet->setActiveScan(true);     // Quét chủ động: hỏi thêm gói trả lời quét, thường chứa tên thiết bị
  boQuet->setInterval(100);
  boQuet->setWindow(99);
  Serial.println("\nQuet thiet bi BLE xung quanh");
}

void loop() {
  BLEScanResults *ketQua = boQuet->start(THOI_GIAN_QUET_GIAY, false);
  int soThietBi = ketQua->getCount();
  Serial.printf("--- Tim thay %d thiet bi ---\n", soThietBi);

  for (int i = 0; i < soThietBi; i++) {
    BLEAdvertisedDevice thietBi = ketQua->getDevice(i);
    String ten = thietBi.haveName() ? thietBi.getName() : String("(khong ten)");
    bool coDichVuCuaBai = thietBi.isAdvertisingService(BLEUUID(UUID_DICH_VU));
    Serial.printf("%2d. %s | %4d dBm | %s%s\n", i + 1, thietBi.getAddress().toString().c_str(),
                  thietBi.getRSSI(), ten.c_str(), coDichVuCuaBai ? " | co dich vu cua bai" : "");
  }

  boQuet->clearResults();          // Giải phóng bộ nhớ của lượt quét vừa xong
  delay(3000);
}
  • Quét chủ động (setActiveScan(true)): ngoài gói quảng bá, ESP32 hỏi thêm gói trả lời quét, nơi nhiều thiết bị đặt tên của mình.
  • Cường độ tín hiệu tính bằng dBm, là số âm: càng gần 0 càng mạnh. Khoảng −50 dBm là rất gần, dưới −90 dBm là sắp mất tín hiệu.
  • isAdvertisingService() kiểm tra gói quảng bá có chứa UUID dịch vụ của bài hay không. Đây là cách một ứng dụng tìm đúng thiết bị của mình giữa nhiều thiết bị khác.
  • Danh sách có cả thiết bị của những người xung quanh. Điện thoại và tai nghe thường dùng địa chỉ ngẫu nhiên thay đổi theo thời gian, nên địa chỉ của chúng không cố định giữa các lần quét.

9. Kết quả mong đợi

Hình minh họa Serial Monitor của hai chương trình BLE trên ESP32: chương trình thiết bị ngoại vi in tên, địa chỉ, sự kiện kết nối và lệnh ghi bật tắt LED; chương trình quét in danh sách thiết bị với địa chỉ, cường độ tín hiệu và tên
Kết quả mong đợi trên Serial Monitor (hình minh họa; địa chỉ và cường độ tín hiệu là số ví dụ).
  1. Chương trình 1 in một dòng khi có thiết bị kết nối, một dòng cho mỗi lệnh ghi và một dòng khi ngắt kết nối.
  2. Chương trình 2 in danh sách sau mỗi lượt quét 5 giây. Nếu mạch chạy chương trình 1 ở gần và chưa có ai kết nối, nó xuất hiện kèm ghi chú có dịch vụ của bài.
  3. Khi điện thoại đang kết nối với mạch chạy chương trình 1, mạch đó ngừng quảng bá và biến mất khỏi danh sách quét.

10. Lưu ý khi dùng thật

  • Chưa có bảo mật: chương trình 1 không yêu cầu ghép đôi hay mã hóa, nên bất kỳ ai ở gần có ứng dụng quét BLE cũng kết nối và ghi được. Thiết bị điều khiển thứ quan trọng cần bật ghép đôi có mã và mã hóa kết nối; thư viện BLE có sẵn lớp BLESecurity cho việc này.
  • Mỗi lúc một kết nối: với cách viết trong bài, thiết bị ngừng quảng bá khi đã có một máy khách kết nối.
  • Dữ liệu dài: giá trị lớn hơn 20 byte phải chia thành nhiều thông báo, hoặc hai bên thỏa thuận kích thước gói lớn hơn.
  • Dùng chung với Wi-Fi: BLE và Wi-Fi chung một bộ thu phát 2,4 GHz trên ESP32. Chạy cả hai được nhưng tốn bộ nhớ và mỗi bên chậm đi.
  • Tiết kiệm pin: lợi thế về điện năng của BLE chỉ thấy rõ khi kết hợp với các chế độ ngủ của ESP32; để nguyên như trong bài, mạch vẫn tiêu thụ như khi bật radio liên tục.

11. Xử lý lỗi thường gặp

Hiện tượngNguyên nhân thường gặpCách xử lý
Ứng dụng không tìm thấy PYWORLD-BLEChưa bật Bluetooth hoặc chưa cấp quyền cho ứng dụng; thiết bị đang kết nối với máy khácBật Bluetooth, cấp các quyền ứng dụng yêu cầu; ngắt kết nối ở máy kia
Tìm trong cài đặt Bluetooth của điện thoại không ghép đôi đượcThiết bị BLE trong bài không dùng ghép đôi của hệ thốngKết nối từ ứng dụng quét BLE
Ghi 1 mà đèn không sángỨng dụng gửi dữ liệu ở dạng khác với mong đợiXem Serial Monitor in byte nào; chọn kiểu byte (hex) và nhập 01
Không thấy giá trị thời gian tự đổiChưa bật nhận thông báo cho đặc tínhBấm nút nhận thông báo của đặc tính c0de0003
Điện thoại vẫn hiện tên hoặc dịch vụ cũ sau khi sửa chương trìnhĐiện thoại lưu tạm thông tin của lần kết nối trướcTắt rồi bật lại Bluetooth của điện thoại, quét lại
Báo chương trình quá lớn khi thêm Wi-FiBLE và Wi-Fi cùng lúc vượt vùng chứa mặc địnhChọn Partition Scheme có vùng ứng dụng lớn hơn
Chương trình 2 không thấy mạch chạy chương trình 1Mạch đó đang có điện thoại kết nối nên không quảng báNgắt kết nối trên điện thoại rồi chờ lượt quét sau

12. Xem thêm


Tin tức liên quan

Điều khiển hồng ngoại hoạt động thế nào: sóng mang 38 kHz, giao thức NEC và ESP32
Điều khiển hồng ngoại hoạt động thế nào: sóng mang 38 kHz, giao thức NEC và ESP32

Giải thích điều khiển hồng ngoại: sóng mang 38 kHz, mắt thu và giao thức NEC với khung 32 bit, mã lặp. Hướng dẫn tự giải mã phím điều khiển và tự phát lệnh bằng ESP32, không dùng thư viện ngoài.

RF 433 MHz là gì: điều chế ASK/OOK và tự viết giao thức phát, thu bằng ESP32
RF 433 MHz là gì: điều chế ASK/OOK và tự viết giao thức phát, thu bằng ESP32

Giải thích sóng RF 433 MHz và điều chế ASK/OOK, cách mã hóa bit bằng độ rộng xung và khung tin 24 bit. Hướng dẫn tự viết chương trình phát và thu trên hai mạch ESP32, không dùng thư viện ngoài.

Vòng dòng 4-20 mA là gì và cách đọc cảm biến 4-20 mA bằng ESP32
Vòng dòng 4-20 mA là gì và cách đọc cảm biến 4-20 mA bằng ESP32

Giải thích vòng dòng 4-20 mA: vì sao dùng dòng điện, vì sao bắt đầu từ 4 mA, cách đổi ra giá trị đo và ngưỡng báo lỗi. Hướng dẫn đọc bằng ESP32 qua điện trở shunt 120 Ω, với ADC có sẵn và với ADS1115.

CAN bus (phần 2): bộ lọc nhận, tốc độ bit và xử lý lỗi bus-off trên ESP32
CAN bus (phần 2): bộ lọc nhận, tốc độ bit và xử lý lỗi bus-off trên ESP32

Phần 2 về CAN bus với ESP32: đặt bộ lọc nhận theo mã lọc và mặt nạ, chọn tốc độ bit theo chiều dài dây, hiểu ba trạng thái lỗi của nút và viết chương trình theo dõi bộ đếm lỗi, tự hồi phục sau bus-off.

CAN bus (phần 1): tín hiệu, khung tin, phân xử ưu tiên và gửi nhận bằng ESP32
CAN bus (phần 1): tín hiệu, khung tin, phân xử ưu tiên và gửi nhận bằng ESP32

Giải thích CAN bus: hai dây CAN_H, CAN_L, mức trội và mức lặn, khung tin có ID 11 bit, phân xử ưu tiên, CRC và ACK. Kèm hai chương trình ESP32 dùng bộ điều khiển TWAI: tự kiểm tra và hai nút gửi nhận.

Modbus TCP/IP (phần 2): ESP32 làm máy khách và cổng chuyển đổi Modbus TCP sang RTU
Modbus TCP/IP (phần 2): ESP32 làm máy khách và cổng chuyển đổi Modbus TCP sang RTU

Hướng dẫn dùng ESP32 làm máy khách Modbus TCP đọc thanh ghi của một thiết bị trong mạng, và làm cổng chuyển đổi Modbus TCP sang Modbus RTU qua RS-485: cách đổi khung, mã thiết bị, thời gian chờ, mã lỗi 0x0B.

Modbus TCP/IP (phần 1): phần đầu MBAP và ESP32 làm máy chủ qua Wi-Fi
Modbus TCP/IP (phần 1): phần đầu MBAP và ESP32 làm máy chủ qua Wi-Fi

Giải thích Modbus TCP/IP: phần đầu MBAP, cổng 502, điểm khác với Modbus RTU. Kèm chương trình ESP32 làm máy chủ Modbus TCP qua Wi-Fi với 10 thanh ghi giữ và 8 cuộn, tự phân tích khung, không dùng thư viện.

TCP và UDP là gì: địa chỉ IP, cổng, kết nối và cách dùng trên ESP32
TCP và UDP là gì: địa chỉ IP, cổng, kết nối và cách dùng trên ESP32

Giải thích TCP và UDP cho người làm vi điều khiển: địa chỉ IP, cổng, kết nối, xác nhận, vì sao TCP là dòng byte. Kèm hai chương trình ESP32: máy chủ TCP nhận lệnh và phát, nhận gói UDP trong mạng Wi-Fi.


Bình luận
Đã thêm vào giỏ hàng