Học

Nút nhấn của bạn kích hoạt hai lần. Đây là lý do, và đây là cách sửa.

Bắt đầu làm miễn phí

Hoặc xem mọi bài học.

  • 465linh kiện trên bàn làm mạch
  • 43bo mạch đang chạy
  • 1.00xthời gian thực, trên mọi bo mạch
Arduino Uno: đếm bằng ngắt, không chống dộilive0.000 s 0.00x
Giữ nút nhấn
Mỗi cạnh xuống trên chân 2 cộng thêm một vào bộ đếm và đảo trạng thái LED trên bo. Mở mạch trong trình soạn thảo, mở Serial Monitor, rồi nhấn nút một lần.

Nút nhấn là một vòm kim loại nhỏ bị ấn xuống một tiếp điểm, và nó không chạm xuống rồi đứng yên ngay. Nó chạm xuống, bật lên một phần nhỏ của milimét, chạm lại, và lặp lại vài lần như vậy trước khi ổn định, giống như đồng xu rơi xuống bàn không dừng ngay khi vừa chạm mặt bàn. Về mặt điện, tiếp điểm đóng mở vài lần trong mấy mili giây đầu tiên khi nhấn, và cả khi thả ra, trước khi giữ ổn định. Một sketch chỉ đếm các cạnh trên chân đó sẽ đếm sai, vì một lần nhấn có thể bị ghi nhận thành hai, ba lần hoặc nhiều hơn.

Mokxi mô phỏng hiện tượng này thay vì giấu nó đi. Mọi nút nhấn trên bàn làm mạch đều bị dội theo mặc định, với kiểu dội tên là typical: từ ba đến tám lần chuyển trạng thái, ổn định trong khoảng 1 đến 5 mili giây sau khi bắt đầu nhấn. Điều đó gần với một nút nhấn thật, và có nghĩa là mạch ở trên không phải màn dàn dựng để minh họa lỗi; nó chính là lỗi đó, xảy ra đúng như trên bàn làm việc của bạn.

Tải giáo án về (PDF)

Trình mô phỏng cho thấy gì

Chương trình đang chạy ở trên là ví dụ interrupt có sẵn của Uno. Nó gắn một hàm xử lý ngắt vào chân 2, kích hoạt ở mỗi cạnh xuống (mỗi lúc chân rơi từ 5 V xuống 0 V) và cộng thêm một vào bộ đếm, và cố ý không có chút logic chống dội nào. Hãy mở mạch trong trình soạn thảo, mở Serial Monitor, và nhấn nút một lần, dứt khoát, như cách bạn nhấn bất kỳ nút nào. Số đếm in ra sau một lần nhấn rất thường là hai hoặc ba, chứ không phải một, và LED trên bo, vốn đảo trạng thái theo tính chẵn lẻ của bộ đếm, đôi khi lại ở trạng thái bạn không ngờ tới. Không có gì bị hỏng cả. Nút nhấn bị dội, hàm xử lý coi mỗi lần dội là một lần nhấn riêng, và nó đếm từng lần một cách trung thực, vì đó đúng là việc code yêu cầu nó làm.

Cả bài học gói trong một câu: chính nút nhấn, chứ không phải code, tạo ra các lần nhấn thừa, và cách sửa nằm ở code vì không thể làm cho nút nhấn sạch hoàn toàn.

Ba cách sửa

Cách sửa đầu tiên, và rẻ nhất, là một khoảng thời gian chống dội bằng phần mềm: sau bất kỳ thay đổi nào trên chân, bỏ qua các thay đổi tiếp theo trong một thời gian ngắn, đủ dài để phủ hết thời gian ổn định, và đủ ngắn để không bao giờ nhầm một lần nhấn thứ hai thật sự là dội. Khoảng từ 5 đến 20 mili giây là thoải mái cho một nút nhấn thông thường.

Cách thứ hai là sửa bằng phần cứng: một tụ điện nhỏ mắc song song với nút nhấn. Nó làm chậm quá trình điện áp tăng và giảm, đủ để các lần dội không bao giờ đạt tới một lần chuyển mức logic trọn vẹn, nên chân thật sự chỉ thấy một cạnh sạch. Cách này hiệu quả ngoài đời vì tụ điện thật cần thời gian thật để nạp và xả, và nó hiệu quả trong Mokxi vì cùng lý do: tụ điện ở đây tích phân theo thời gian với đáp ứng RC hàm mũ thật thay vì chuyển trạng thái tức thì, nên một phiên bản thu nhỏ của cách sửa này thật sự đứng vững trong trình mô phỏng, chứ không chỉ trên lý thuyết.

Cách thứ ba là chống dội ngay bên trong hàm xử lý ngắt, cách này cần thêm một quy tắc: hàm xử lý nên làm càng ít càng tốt, vì nó chạy trong lúc các ngắt khác bị chặn. Ghi lại thời gian rồi thoát ra; việc quyết định thật sự, gồm cả khoảng thời gian chống dội, hãy để lại trong loop().

Phiên bản dùng millis()

Chương trình toggle có sẵn của Uno là cách sửa bằng phần mềm, đầy đủ, trên chính chân 2 mà phần minh họa hiện tượng dội dùng. Nó lưu lần đọc gần nhất và thời điểm lần cuối nó thay đổi, và chỉ xử lý một thay đổi khi đã qua DEBOUNCE_MS, tức hai mươi lăm mili giây, kể từ lần trước. Nhấn nút một lần khi chương trình này đang chạy thì đèn ở chân 13 bật; nhấn lần nữa thì tắt, đúng một lần cho mỗi lần nhấn, và đó chính là mục đích.

Hãy để ý code thật sự đang so sánh gì. Nó không đếm xem nút đã được giữ bao lâu; nó đo xem đã bao lâu kể từ lần thay đổi được chấp nhận gần nhất, ở cạnh nào cũng vậy, khi nhấn hay khi thả. Sự khác biệt đó làm cho khoảng thời gian chống dội đối xứng: một lần dội ngay sau khi nhấn bị bỏ qua, và một lần dội ngay sau khi thả cũng vậy, cùng một quy tắc hai mươi lăm mili giây áp dụng cho cả hai mà không cần xử lý riêng cho từng cạnh.

firmware/examples/toggle (cách sửa dùng millis(), nối dây y như vậy)
const int BUTTON_PIN = 2;
const int LAMP_PIN = 13;
const unsigned long DEBOUNCE_MS = 25;

int lastReading = HIGH;
unsigned long lastChange = 0;
bool lampOn = false;

void loop() {
  int reading = digitalRead(BUTTON_PIN);
  unsigned long now = millis();

  if (reading != lastReading && now - lastChange >= DEBOUNCE_MS) {
    lastChange = now;
    lastReading = reading;
    if (reading == LOW) {
      lampOn = !lampOn;
      digitalWrite(LAMP_PIN, lampOn ? HIGH : LOW);
    }
  }
}

Chuyện điện trở kéo lên

Cả phần minh họa hiện tượng dội lẫn cách sửa đều nối nút nhấn thẳng vào chân 2 mà không có điện trở bên ngoài, vì sketch bật điện trở kéo lên có sẵn bên trong chân bằng INPUT_PULLUP. Điện trở kéo lên đó giữ chân ở 5 V cho đến khi nút nối nó xuống GND, vì vậy chân đọc LOW khi đang giữ nút chứ không phải HIGH. Chi tiết này hay làm người mới bối rối lần đầu, vì nó ngược với điều mà một nút nhấn có vẻ nên làm.

Cách khác là dùng một điện trở kéo lên thật bên ngoài, một điện trở 10 kilohm từ chân lên 5 V, và nút nhấn kéo chính đường nối đó xuống GND khi được nhấn. Về mặt điện, hai cách là cùng một ý tưởng; điện trở bên trong chỉ yếu hơn (khoảng 35 kilohm) và không cần thêm linh kiện. Trang về điện trở kéo lên lắp phiên bản bên ngoài từ đầu, và đáng đọc nếu có lúc bạn thấy điện trở bên trong như từ trên trời rơi xuống.

Vì sao kiểu dội mặc định được đặt như vậy

typical, từ ba đến tám lần chuyển trạng thái, ổn định trong 1 đến 5 mili giây, là một nút nhấn bình thường một cách có chủ ý, loại có trong gần như mọi bộ kit breadboard, chứ không phải trường hợp xấu nhất được chọn để làm vấn đề trông tệ hơn thực tế. Cũng có kiểu worst, lên tới hai mươi lần chuyển trạng thái trong 10 đến 20 mili giây, gần với một nút nhấn đã mòn hoặc một công tắc hành trình cần dài đã dùng nhiều. Khi đã xử lý xong trường hợp thông thường, bạn nên thử khoảng thời gian chống dội của mình với kiểu này: một cách sửa chỉnh vừa khít cho 5 mili giây dội, không chừa khoảng dư, vẫn có thể bỏ sót một nút nhấn rung lạch cạch tới 15 mili giây.

Câu hỏi

Thời gian chống dội nên dài bao lâu?

Khoảng từ 5 đến 20 mili giây là đủ cho một nút nhấn thông thường, đó cũng là khoảng mà kiểu dội mặc định của Mokxi ổn định trong đó. Quá ngắn thì một số lần dội lọt qua; quá dài thì nhấn đúp nhanh sẽ bắt đầu thấy không ăn.

Nếu đã chống dội bằng phần mềm thì có cần tụ điện không?

Không. Hai cách là hai lựa chọn giải quyết cùng một vấn đề ở hai chỗ khác nhau, và dùng cả hai không sai, chỉ là thừa. Hãy chọn cách hợp với mạch: phần mềm không tốn thêm gì, phần cứng thì vẫn hiệu quả kể cả khi code đọc chân không bao giờ thay đổi.

Vì sao ngắt của mình vẫn kích hoạt hai lần sau khi đã thêm kiểm tra chống dội?

Gần như luôn là vì phần kiểm tra khoảng thời gian chống dội nằm trong hàm xử lý ngắt cùng với những việc chậm, hoặc vì thời gian đang được so với nhầm giá trị đã lưu. Hãy để hàm xử lý chỉ đọc đồng hồ và đặt một cờ, còn phép so sánh thì làm trong loop().

INPUT_PULLUP có cần thêm điện trở bên ngoài không?

Không. Nó bật một điện trở kéo lên có sẵn ngay bên trong ATmega328P, nên nút nhấn chỉ cần hai dây: một tới chân, một tới GND.

Tụ điện giá trị bao nhiêu thì chống dội được một nút nhấn thông thường bằng phần cứng?

Khoảng 100 nanofarad mắc song song với nút, thường đi kèm một điện trở nối tiếp nhỏ để hạn chế dòng xả ở lần nhấn đầu, là điểm khởi đầu phổ biến; giá trị đúng còn tùy vào nút nhấn, và nên kiểm tra ngoài đời với chính linh kiện đó thay vì tin một con số cho mọi nút.

Có phải chỉ nút nhấn mới bị dội?

Không. Mọi tiếp điểm cơ khí đều bị dội, kể cả relay, công tắc gạt và công tắc hành trình trên động cơ hay cửa. Cách sửa cũng vậy: một khoảng thời gian sau lần thay đổi đầu tiên, hoặc một tụ điện mắc song song với tiếp điểm, tùy cách nào hợp với mạch đang đọc nó.

Lắp mạch này ngoài đời

Mở trình soạn thảo, đổi một giá trị và xem con số thay đổi theo. Không cần cài gì, không cần tài khoản.