Arduino – Chuẩn giao tiếp 1-Wire

Arduino – Chuẩn giao tiếp 1-Wire

Trong chương này chúng ta sẽ cùng tìm hiểu thêm về 1 chuẩn giao tiếp truyền nhận dữ liệu nữa đó là chuẩn giao tiếp 1-Wire, các ví dụ thực hành đọc cảm biến bằng chuẩn giao tiếp 1-Wire, kết hợp chuẩn giao tiếp 1-Wire với các chuẩn giao tiếp khác như I2C hay SPI.

1-Wire

1-Wire là gì?

Chuẩn giao tiếp 1-Wire được nghiên cứu và phát triển bởi Dallas Semiconductor (Maxim). Không như các chuẩn giao tiếp đã được đề cập trước đó như I2C cần 2 dây hoặc SPI cần 4 dây để truyền nhận dữ liệu. 1-Wire chỉ cần một dây để có thể truyền và nhận dữ liệu.

Chuẩn giao tiếp 1-Wire là chuẩn giao tiếp không đồng bộ và bán song công (half-duplex: tại một thời điểm, tín hiệu chỉ có thể chạy theo một hướng). Vì 1-Wire chỉ sử dụng một dây để nối nguồn và truyền dữ liệu, nên khi ở trạng thái rãnh (không có dữ liệu trên đường truyền) thì nó cần phải ở mức cao, do đó cần kết nối dây này với nguồn thông qua một điện trở. Điện trở này thường được gọi là điện trở kéo lên (pull-up resistor). Tùy theo các thiết bị mà giá trị điện trở này có thể thay đổi, cần tham khảo datasheet của thiết bị để biết rõ.

1-Wire thường sử dụng các cổng logic CMOS/TTL tương ứng với mức logic 0 thì điện áp đỉnh ở mức 0.8V và điện áp tối thiểu cho mức logic 12.2V. Các thiết bị 1-Wire có nguồn cấp trong khoảng 2.8V đến 6V.

1-Wire có hai chế độ làm việc là standardoverdrive. Khi làm việc ở chế độ standard thì tốc độ truyền dữ liệu là 15.4kbps, với chế độ overdrive125kbps.

Chuẩn giao tiếp 1-Wire tuân theo mô hình master-slave. Trên một đường truyền dữ liệu có thể gắn nhiều thiết bị slave, nhưng chỉ có duy nhất một thiết bị là master. Mỗi một thiết bị slave sẽ có duy nhất 64-bit địa chỉ lưu trữ trong bộ nhớ ROM của mình. Nhờ thế, khi kết nối vào chung một bus dữ liệu thì thiết bị master sẽ có thể nhận biết được giữa các slave. Trong 8 byte (64 bit) này sẽ được chia ra làm 3 phần chính:

  • Bắt đầu là LSB (least significant bit), byte đầu tiên bao gồm 8 bit được gửi đi là mã họ thiết bị (family codes) giúp xác định đó là loại thiết bị nào. 6 byte tiếp theo lưu trữ một địa chỉ riêng của từng thiết bị với 48 bit tùy biến. Byte cuối cùng MSB (most significant bit) là byte kiểm tra tính toàn vẹn của dữ liệu – cyclic redundancy check (CRC), đây là byte giúp kiểm tra xem tín hiệu gửi đi có bị lỗi hay không.

Với số lượng 2^48 bit địa chỉ được tạo ra thì vấn đề về địa chỉ không phải là vấn đề chính trong chuẩn giao tiếp này.

Khi có nhiều thiết bị 1-Wire trên một mạch thì nó thường được gọi là mạng 1-Wire hoặc MicroLAN. Trong mạng này, yêu cầu chỉ có duy nhất một master và phải có ít nhất là một slave. Có thế kết nối bao nhiêu slave tùy thích, nhưng khi vượt quá con số 20 thì việc truyền nhận dữ liệu có thể sẽ xảy ra lỗi.

Đường truyền giữa master và slave có thể lên tới 200 mét. Khi sử dụng dây ở khoảng cách lớn thì cần được bảo quản tốt hơn, như tránh băng qua các đường dây điện để tránh tín hiệu sẽ bị nhiễu. Khi sử dụng ở khoảng cách xa, loại dây rẻ nhất và dễ sử dụng nhất là dây CAP5 được sử dụng trong mạng LAN (dây cable internet).

1-Wire hoạt động như thế nào?

Chuẩn giao tiếp 1-Wire sử dụng khái niệm time slot (khe thời gian). Một time slot là một khoảng thời gian trong đó mức logic 1 hoặc 0 sẽ được ghi hoặc đọc. Time slot có khoảng thời gian là 60µs khi hoạt động ở chế độ standard, và 8µs với chế độ overdrive.

Có 3 thao tác hoạt động cơ bản của 1 Wire là Reset/Present, Read, Write.

  1. Reset/Present.

    Master sẽ kéo tín hiệu truyền xuống mức thấp trong khoảng 480µs đến 640µs. Khoảng thời gian này được hiểu là khoảng thời gian reset. Sau khoảng thời gian này, nếu có slave sẽ gửi trả tín hiệu present. Tức là slave sẽ kéo tín hiệu xuống mức thấp trong khoảng 60µs đến 240µs. Nếu không có tín hiệu present trả về thì master sẽ hiểu là không có slave nào được kết nối vào mạng, và các quá trình tiếp theo sẽ không được diễn ra.

  2. Write.

    Đối với bit 1 master kéo đường truyền xuống mức thấp trong khoảng 1 đến 15µs, sau đó sẽ giải phóng đường truyền về mức cao.

    Đối với bit 0 master sẽ kéo đường truyền xuống mức thấp trong khoảng 60µs đến 120µs, sau đó sẽ giải phóng đường truyền về mức cao.

    Giữa các lần gửi bit (0 hoặc 1), phải có khoảng thời gian nghỉ (recovery time) tối thiểu 1µs.
  3. Read.

    Master sẽ kéo tín hiệu truyền xuống mức thấp trong khoảng 0-15µs. Nếu slave muốn gửi bit 1 sẽ giải phóng đường truyền trở về mức cao, nếu muốn gửi bit 0 slave sẽ giữ đường truyền ở mức thấp trong khoảng thời gian 15µs đến 60µs.

Các khoảng thời gian trong các hoạt động trên được xác định ở chế độ standard
1wire waveform
Hình 1. Tổng hợp

Tiến trình hoạt động (Workflow)

1wire workflow
Hình 2. Workflow
  1. Trước khi bắt đầu xác định một slave để làm việc, master cần đưa ra lệnh reset để xác định là có slave nào đó có nằm trên đường truyền bằng cách phản hồi lại tín hiêu present.

  2. Sau khi đã xác định có thiết bị slave được kết nối, master sẽ chọn tất cả hay chỉ 1 slave (dựa trên địa chỉ của thiết bị) để làm việc. Hoặc sẽ xác định slave tiếp theo bằng thuật toán tìm kiếm nhị phân.

  3. Sau khi đã xác định được slave làm việc thì tất cả các slave khác sẽ được bỏ qua, và tất cả các tín hiệu truyền đi sẽ chỉ được nhận bởi thiết bị master và slave đã được chọn.

  4. Nếu muốn giao tiếp với một slave khác, master sẽ bắt đầu lại quá trình từ bước 1.

Khi một thiết bị được chọn, master có thể đưa ra các lệnh cụ thể cho thiết bị, gửi dữ liệu đến nó hoặc đọc dữ liệu từ nó. Bởi vì mỗi loại thiết bị thực hiện các chức năng khác nhau và phục vụ cho mục đích khác nhau, mỗi máy có một giao thức duy nhất khi nó đã được chọn. Mặc dù mỗi loại thiết bị có thể có các giao thức và tính năng khác nhau nhưng tất cả chúng đều có quy trình lựa chọn giống nhau và tuân theo tiến trình như trên.

Trên đây là phần giới thiệu về chuẩn giao tiếp 1-Wire, tiếp theo chúng ta sẽ cùng thực hành một số ví dụ để có thể hiểu rõ cách thức hoạt động của chuẩn giao tiếp này.

Ví dụ chuẩn giao tiếp 1-Wire

Ở phần này chúng ta sẽ tìm hiểu về ví dụ đọc dữ liệu từ cảm biến đo nhiệt độ DS18B20. Sẽ có hai ví dụ với cảm biến này là:

  • Một master và một slave.

  • Một master và nhiều slave.

Một master và một slave

Linh kiện cần thiết:

  • Board Arduino Uno

  • DS18B20 (hoặc DS18S20, hoặc DS1822)

  • Màn hình OLED 0.96″

  • Điện trở 4k7Ω

Giới thiệu DS18B20

Đầu dò nhiệt độ sử dụng DS18B20, đầu ra digital, được tích hợp trong ống thép không rỉ, độ nhạy cao, đo chính xác nhiệt độ trong môi trường ẩm ướt, với chuẩn giao tiếp 1-Wire. DS18B20 cung cấp 9-12 bit cấu hình đọc nhiệt độ theo chuẩn giao tiếp 1-Wire, do đó chúng ta chỉ cần 1 dây để kết nối với một vi xử lý.

cam bien nhiet do do am probe ds18b20
Hình 3. Hình ảnh cảm biến nhiệt độ DS18B20

Thông tin cảm biến

  • Điện áp đầu vào: 3.0-5.5V.

  • Không thấm nước, không rỉ.

  • Khoảng nhiệt độ đo: -55 ° C đến +125 ° C.

  • Độ chính xác từ ± 0.5 ° C trong khoảng -10 ° C đến +85 ° C.

  • Đầu ra : VCC (Red), DATA (Blue), GND (Black).

Kết nối với board Arduino Uno
Bảng 1. Bảng sơ đồ kết nối cảm biến DS18B20 và board Arduino Uno
DS18B20 Arduino Uno

Đỏ

5V

Đen

GND

Vàng

GPIO2

temperature sensor dallas pinout diagram
Hình 4. Hình ảnh kết nối với board Arduino Uno
oled ds18b20
Hình 5. Hình ảnh kết nối của DS18B20 với Arduino Uno
Chú ý
  • Dây data của DS18B20 cần phải được gắn với một điện trở kéo 4k7Ω và được nối với nguồn 5V.

  • Người dùng có thể kết nối dây vàng của cảm biến đến bất cứ chân digital nào của vi điều khiển.

Thư viện sử dụng

Giải thích một số hàm trong thư viện OneWire

  • OneWire myWire(pin): Tạo một đối tượng myWire thuộc thư viện OneWire bằng một pin cụ thể. Mặc dù có thể kết nối với nhiều thiết bị trên cùng một dây, nhưng nếu có quá nhiều thì bạn có thể chia nhỏ ra và kết nối vào các pin riêng và tạo ra các đối tượng 1-Wire với từng pin.

  • myWire.(addrArray): Tìm thiết bị được kế nối kế tiếp. addArray là một mảng gồm 8 bit. Nếu tìm thấy một thiết bị, addArray sẽ chứa địa chỉ của thiết bị đó và sẽ trả về giá trị là true. Nếu không tìm thấy thiết bị nào sẽ trả về false.

  • myWire.reset_search(): Tìm kiếm lại từ đầu, thiết bị tìm thấy sẽ là thiết bị đầu tiên.

  • myWire.reset(): Reset 1-wire bus. Thường sử dụng hàm này trước khi bắt đầu giao tiếp với một thiết bị bất kỳ.

  • myWire.select(addrArray): Chọn một thiết bị để làm việc dựa trên địa chỉ của nó. Sử dụng lệnh này sau khi đã reset.

  • myWire.skip(): Bỏ qua bước lựa chọn thiết bị. Cách này chỉ dùng được khi chỉ có một thiết bị slave.

  • myWire.write(num, power): Gửi 1 byte, và nếu sau khi bạn cần sử dụng nguồn sau khi gửi thì set power = 1, nếu không thì bỏ trống hoặc set power = 0 (đối với cảm biến DS18B20 có thể làm việc trong parasite mode – dây đỏ và đen nối chung xuống GND).

  • myWire.read(): Đọc 1 byte.

  • myWire.crc8(dataArray, length): Tính bit CRC trên dữ liệu được nhận.

Mã nguồn
//  Khai báo thư viện sử dụng
#include <OneWire.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
#if (SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif
OneWire  ds(2); //  Khai báo chân 2 lấy dữ liệu (cần có điện trở 4k7 để kéo lên nguồn)
//  Thiết lập các giá trị ban đầu
void setup()
{
  Serial.begin(9600);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(18, 14);
  display.println("1-Wire Example");
  display.display();
  delay(2000);
  display.clearDisplay();
}

void loop(void)
{
  byte i;
  byte present = 0;
  byte type_s;        //  Biến lưu giá trị xác định loại cảm biến
  byte data[12];      //  Biến lưu giá trị nhiệt độ (HEX)
  byte addr[8];       //  Biến lưu địa chỉ cảm biến (gồm 8 bit)
  float celsius, fahrenheit;

  if ( !ds.search(addr)) {
    Serial.println("No more addresses.");
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println("No more addresses.");
    display.display();
    Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }
  //  In địa chỉ ROM ra Serial và OLED
  Serial.print("ROM =");
  display.clearDisplay();
  display.setCursor(0, 0);
  display.print("ROM = ");
  for( i = 0; i < 8; i++) {
    Serial.write(' ');
    Serial.print(addr[i], HEX);
    display.print(addr[i], HEX);
  }
  if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.println("CRC is not valid!");
      return;
  }
  Serial.println();
  switch (addr[0]) { // Dùng byte đầu tiền trong ROM để xác định loại cảm biến
    case 0x10:
      Serial.println("  Chip = DS18S20");
      display.setCursor(0,8);
      display.println("Chip = DS18S20");
      type_s = 1;
      break;
    case 0x28:
      Serial.println("  Chip = DS18B20");
      display.setCursor(0,8);
      display.println("Chip = DS18B20");
      type_s = 0;
      break;
    case 0x22:
      Serial.println("  Chip = DS1822");
      display.setCursor(0,8);
      display.println("Chip = DS1822");
      type_s = 0;
      break;
    default:
      Serial.println("Device is not a DS18x20 family device.");
      return;
  }

  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);        // Gửi lệnh bắt đầu giao tiếp (đối với cảm biến DS18B20)

  delay(1000);              // Cần một khoảng thời gian chờ, ít nhất là 750ms
  present = ds.reset();
  ds.select(addr);
  ds.write(0xBE);           // Gửi lệnh bắt đầu đọc dữ lliệu (đối với cảm biến DS18B20)

  Serial.print("  Data = ");
  Serial.print(present, HEX);
  Serial.print(" ");
  for ( i = 0; i < 9; i++) {
    data[i] = ds.read();
    Serial.print(data[i], HEX);
    Serial.print(" ");
  }
  Serial.print(" CRC=");
  Serial.print(OneWire::crc8(data, 8), HEX);
  Serial.println();

  int16_t raw = (data[1] << 8) | data[0]; // Chuyển đổi giá trị nhiệt độ từ 16 bit
  if (type_s) {
    raw = raw << 3;                       // Độ phân giải mặc định 9 bit
    if (data[7] == 0x10) {
      raw = (raw & 0xFFF0) + 12 - data[6];
    }
  } else {
    byte cfg = (data[4] & 0x60);
    // Tại độ phân giải thấp hơn, các bit thấp đều không xác định được do đó, chúng ta cho nó bằng
    if (cfg == 0x00) raw = raw & ~7;        //  Độ phân giải 9 bit, 93.75 ms
    else if (cfg == 0x20) raw = raw & ~3;   //  Độ phân giải 10 bit, 187.5 ms
    else if (cfg == 0x40) raw = raw & ~1;   //  Độ phân giải 11 bit, 375 ms
    //  Độ phân giải mặc định 12 bit, thời gian chuyển đổi 750 ms
  }
  celsius = (float)raw / 16.0;
  fahrenheit = celsius * 1.8 + 32.0;

  //  In các giá trị nhiêt độ ra Serial và OLED
  Serial.print("  Temperature = ");
  Serial.print(celsius);
  Serial.print(" Celsius, ");
  Serial.print(fahrenheit);
  Serial.println(" Fahrenheit");
  display.setCursor(0,16);
  display.print("Temp = ");
  display.print(String(celsius));
  display.print((char)247);display.print("C");
  display.setCursor(42, 24);
  display.print(String(fahrenheit));
  display.print((char)247);display.print("F");
  display.display();
  delay(1500);
}
Output

Kết quả trả về trên cổng Serial trên máy tính sẽ có dạng như sau:

ROM = 28 FF 3C D6 63 16 4 83
  Chip = DS18B20
  Data = 1 DB 1 4B 46 7F FF C 10 95  CRC=95
  Temperature = 29.69 Celsius, 85.44 Fahrenheit
No more addresses.

Kết quả xuất hiện trên Oled sẽ có dạng như sau:

ROM = 28 FF 3C D6 63 16 4 83
Chip = DS18B20
Temperature = 29.69 °C
              85.44 °F

Một master và nhiều slave

Đối với ví dụ này sẽ có hai cách kết nối cảm biến như sau:

ds18b20 normal power
Hình 6. Normal Power Mode (Nguồn Tweaking4All.com)

Các cảm biến có thể nối song song với nhau và các dây được nối như khi sử dụng một cảm biến.

ds18b20 parasite power
Hình 7. Parasite Power Mode (Nguồn Tweaking4All.com)

Parasite mode về cơ bản là sẽ nối dây đỏ và dây đen của cảm biến xuống GND, và chỉ có một dây vàng là được nối lên nguồn.

Ví dụ này sẽ sử dụng hai cảm biến DS18B20 kết nối theo kiểu Normal Power như hình.

Mã nguồn
Ở ví dụ này ta sẽ sử dụng chung một mã nguồn trên để thiết lập việc lấy dữ liệu.
Kết quả

Vì ở đây chỉ kết nối hai cảm biến nên trên OLED và Serial sẽ hiện kết quả lấy được của cả hai cảm biến.

Serial

ROM = 28 49 BA 3 0 0 80 B
  Chip = DS18B20
  Data = 1 E5 1 FF FF 7F FF FF FF D3  CRC=D3
  Temperature = 30.31 Celsius, 86.56 Fahrenheit
ROM = 28 FF 3C D6 63 16 4 83
  Chip = DS18B20
  Data = 1 DE 1 4B 46 7F FF C 10 C3  CRC=C3
  Temperature = 29.87 Celsius, 85.77 Fahrenheit
No more addresses.

OLED sẽ lần lượt xuất hiện.

ROM = 28 49 BA 3 0 0 80 B
Chip = DS18B20
Temperature = 30.31 Celsius
              86.56 Fahrenheit
ROM = 28 FF 3C D6 63 16 4 83
Chip = DS18B20
Temperature = 29.87 Celsius
              85.77 Fahrenheit

Tổng kết

Qua phần này chúng ta đã tìm hiểu được về một chuẩn giao tiếp mới là 1-Wire, và các cách lập trình sử dụng chuẩn giao tiếp này. Dưới đây là một số trang web để bạn có thể tham khảo thêm nếu muốn tìm hiểu sâu về chuẩn giao tiếp này:

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *