Letakkan pin CLK KY-040 pada D2, kira detent di dalam interrupt, dan panggil display() hanya bila kiraan itu benar-benar berubah. Peraturan terakhir itulah keseluruhan artikel ini: satu redraw penuh OLED 128Γ64 menyekat Arduino Uno selama lebih kurang 25 ms, dan satu pusingan pantas pada tombol menimbus beberapa detent di dalam celah tersebut.
Kenapa rotary encoder perlukan dua pin output, bukan satu?
KY-040 ada dua sentuhan suis di dalam bekas perak itu, dibawa keluar sebagai CLK dan DT, sengaja diserongkan supaya satu bertukar keadaan suku kitaran lebih awal daripada satu lagi. Satu edge pada CLK semata-mata tidak berguna β sentuhan itu terbuka dengan cara yang sama sama ada anda memusing aci ikut jam atau lawan jam. Arah tidak duduk di dalam mana-mana isyarat itu. Ia duduk pada fasa antara kedua-duanya.
Jadi bacalah channel yang satu lagi pada saat yang pertama itu bergerak: sampel DT ketika CLK menjunam, dan ia masih HIGH pada satu arah, sudah LOW pada arah sebaliknya. Satu digitalRead β itulah sebabnya modul ini ada dua pin isyarat.
| Dalam satu detent | Pusing ikut jam | Pusing lawan jam |
|---|---|---|
| Diam di dalam detent | CLK HIGH, DT HIGH | CLK HIGH, DT HIGH |
| Peralihan ke-1 | CLK menjunam β DT masih HIGH | DT menjunam β CLK masih HIGH |
| Peralihan ke-2 | DT menjunam β kedua-duanya LOW | CLK menjunam β DT sudah LOW |
| Peralihan ke-3 | CLK naik β DT masih LOW | DT naik β CLK masih LOW |
| Peralihan ke-4 | DT naik β kembali diam | CLK naik β kembali diam |
Dua baris tebal itulah keseluruhan decoder tadi. Baca pula jadual ini ke bawah dan anda dapat separuh yang satu lagi: satu klik ialah empat peralihan elektrik, bukan satu β punca aduan paling kerap tentang modul ini. Rakaman ini menyusuri prinsip dua denyut yang sama dan meletakkan sepasang isyarat quadrature pada osiloskop sebagai gelombang segi empat yang sebenar:
Senarai komponen β tombol, skrin, dan pendawaian yang benar-benar sampai kepada kedua-duanya
Parts list
| Item | Price | Qty | |
|---|---|---|---|
Power Supply Adapter DC Universal AC to DC Converter PSU 5V2A 5V3A 9V2A 12V2A - P.S. ADAPTOR (9V2A)PSA0902 | RM13.95 |
Adaptor 9V 2A centre-positive, kalau anda mahu menu yang siap itu berjalan tanpa PC tercucuk padanya. 9V duduk selesa di dalam julat barrel jack 7β12V Uno R3; 12V pun boleh, cuma regulator Uno ialah jenis linear, jadi setiap volt lebihan dibakar menjadi haba tanpa sebarang faedah.
Bagaimana cara menyambung KY-040 dan OLED pada Uno?
KY-040 dan OLED 0.96″ kedua-duanya membawa pin male, sedangkan header Uno pula soket female, jadi kedua-duanya tidak boleh dicantum terus. Sensor Shield V5.0 menyelesaikannya: ia ditekan masuk ke atas Uno dan menerbitkan semula setiap pin sebagai blok male tiga pin dengan silkscreen G, V dan S β ground, lima volt, signal. Sembilan jumper female-to-female kemudian sampai ke kedua-dua modul, tanpa perlu menyolder di mana-mana.
Breadboard, iaitu alternatif yang biasa, tidak menjadi di sini. Pada header lima pin KY-040, badan hitam itu berdiri di tepi kanan dan pin peraknya membengkok sembilan puluh darjah dan keluar ke sisi, selari dengan PCB β tiada satu pun menghala ke bawah, jadi tiada apa yang boleh ditolak masuk ke dalam lubang breadboard. Soket female pada pin mendatar itu sahaja pilihannya.
OLED pula tidak perlukan sebarang persiapan: header male empat pinnya sampai dalam keadaan sudah tersolder pada muka belakang. Apa yang kelihatan seperti empat lubang kosong di sebelah depan itu sebenarnya fillet solder dan hujung pin yang dilihat dari hujung. Cucuk jumper female padanya dan anda sedang mendawai, bukan menyolder.
Periksa dua perkara sebelum jumper pertama dipasang. Pertama, pin OLED disusun GND, VCC, SCL, SDA β ground dahulu, terbalik daripada susunan VCC-dahulu yang biasa β sedangkan header IIC pada shield disusun SCL, SDA, β, +. Kedua-duanya bercermin, jadi ribbon yang dipasang lurus melintang menjadikan keempat-empatnya salah. Turunkan satu wayar pada satu masa: GND dan VCC yang bertukar tempat meletakkan bekalan secara terbalik merentasi panel dan boleh membunuhnya.
Kedua, jumper SEL kuning pada shield, di sebelah screw terminal biru itu. Ia sampai dalam keadaan sudah terpasang, dan itulah yang build ini mahukan: ia menyuap pin V pada blok D0βD13 daripada 5V Uno sendiri. Cabut ia dan pin V tersebut kekal mati sehingga anda menyuap screw terminal itu sendiri. Blok A0βA5 dan header IIC mengambil 5V Uno tanpa mengira keadaan itu, jadi SEL yang tercabut memberikan kerosakan yang sangat meyakinkan: menu terlukis dengan sempurna dan tombol pula tidak buat apa-apa, kerana skrin ada kuasa dan encoder tidak pernah ada. Dan jangan sesekali menyuap screw terminal semasa SEL masih terpasang: itu mengikat bekalan luar terus ke atas rel 5V Uno.
| Pin modul | Ke mana pada shield | Pin Uno di belakangnya |
|---|---|---|
| KY-040 CLK | blok D2, pin S | D2 (INT0) |
| KY-040 DT | blok D3, pin S | D3 (INT1) |
| KY-040 SW | blok D4, pin S | D4 |
| KY-040 + | pin V mana-mana blok | 5V |
| KY-040 GND | pin G mana-mana blok | GND |
| OLED GND | header IIC, β | GND |
| OLED VCC | header IIC, + | 5V |
| OLED SCL | header IIC, SCL | A5 |
| OLED SDA | header IIC, SDA | A4 |
Pada Uno, A4 dan A5 itulah bas I2C; header IIC pada shield cuma tempat yang lebih kemas untuk mendaratkannya.

Aci encoder itu ialah D-flat 6 mm yang bogel, jadi mana-mana knob D-shaft 6 mm boleh ditekan masuk terus.

Kenapa menu melompat dua atau empat item setiap klik?
Satu detent KY-040 bukan satu denyut. Tombol itu berpusing menerusi 20 detent setiap pusingan, dan satu detent merangkumi satu kitaran quadrature yang penuh β empat peralihan dalam jadual di atas. Encoder jenis ini memang dipesan mengikut pasangan itu: datasheet PEC11R Bourns menyenaraikan pilihan detent “1 = 18 Detents (18 pulses)” dan “2 = 24 Detents (12, 24 pulses)” β satu kitaran quadrature penuh bagi setiap detent, atau dua detent bagi satu kitaran pada varian yang dibahagi dua. “Pulse per 360Β° rotation” dikira pada satu channel sahaja, jadi 20 detent KY-040 memberi 20 denyut pada CLK dan 20 pada DT.
Kira setiap edge pada kedua-dua channel dan anda dapat empat setiap klik; kedua-dua edge pada satu channel pula, dua. Kira edge menjunam CLK sahaja, seperti yang dibuat oleh sketch di bawah, dan anda dapat tepat satu. Encoder itu tidak pernah rosak; model pengiraan itu yang rosak.
Satu lagi perkara yang menggelembungkan kiraan ialah contact bounce. Ini semua wiper mekanikal, dan datasheet Bourns yang sama menetapkan bounce pada 2.0 ms maksimum, jadi satu detent boleh melemparkan beberapa edge tambahan ke pin. Ubatnya ialah cop masa micros() di dalam handler interrupt yang menolak mana-mana edge yang tiba kurang daripada 2 ms selepas edge terakhir yang diterima. Ia mesti micros(): millis() digerakkan oleh interrupt pemasanya sendiri, dan interrupt itu tidak boleh berjalan selagi handler anda memegang CPU. Jangan juga sesekali delay() di dalam situ β interrupt sedang dimatikan, jadi ia menyekat pemindahan I2C berserta edge yang seterusnya.
Isytiharkan CLK, DT dan SW sebagai INPUT_PULLUP. Sentuhan KY-040 hanya menyambung ke ground, jadi tanpa pull-up satu sentuhan yang terbuka meninggalkan pin itu terapung dan membaca bunyi bising. Datasheet ATmega328P meletakkan pull-up dalamannya pada 20β50 kΞ©; pada revisi yang turut membawa pull-up 10 kΞ© sendiri, kedua-duanya duduk selari dan tetap memegang pin itu kukuh pada HIGH, dan pada revisi yang tiada, pull-up dalaman itulah satu-satunya yang membuat kerja tersebut. Betul pada kedua-dua keadaan.
Kenapa melukis semula skrin membuatkan tombol terlepas langkah?
Adafruit_SSD1306 menyimpan keseluruhan skrin di dalam RAM dan menghantarnya sekali gus. Ia memperuntukkan WIDTH Γ ((HEIGHT + 7) / 8) bait β bagi panel 128Γ64, 128 Γ 8 = 1024 bait β dan display() menolak kesemuanya menerusi I2C. Library itu menaikkan bas kepada 400 kHz untuk pemindahannya sendiri, dan setiap bait pada I2C berharga sembilan tempoh bit: lapan bit data ditambah acknowledge yang dihantar balik oleh paparan. Jadi 1024 Γ 9 = 9216 tempoh bit, dan pada 400 kHz itu bersamaan 23 ms. Tambah pula restart dan bait alamat yang dibelanjakan library pada setiap ketulan 32 bait, maka satu redraw penuh menyekat Uno selama lebih kurang 25 ms. Sepanjang tempoh itu, loop() tidak berjalan. Hanya interrupt yang boleh masuk.
Ukur sendiri pada board anda:
unsigned long t = micros();
display.display();
Serial.println(micros() - t); // mikrosaat bagi satu redraw penuh
Buffer itu memakan memori dan juga masa, dan ia menyorok: 1024 bait ialah tepat separuh daripada 2 KB SRAM ATmega328P, tetapi ia diperuntukkan pada waktu larian di dalam display.begin(), jadi ia tidak pernah muncul dalam angka yang dicetak IDE selepas kompil. Sketch di bawah melaporkan 543 bait global β sehinggalah begin() menuntut 1024 lagi dan angka sebenarnya menjadi lebih kurang 1567 daripada 2048. Sebab itulah label menu duduk di dalam flash di belakang PROGMEM dan strcpy_P: pada chip ini anda tidak mampu membelanjakan RAM untuk empat patah perkataan semata-mata untuk mencetaknya.
Itulah sebabnya pengiraan tadi berpindah masuk ke dalam interrupt. D2 dan D3 ialah satu-satunya pin external-interrupt pada Uno (INT0 dan INT1), jadi meletakkan CLK pada D2 bukan sesuka hati β itulah sebabnya jadual pin tadi berbunyi begitu. Satu ISR pada D2 dicetuskan semasa pemindahan itu berlangsung, jadi detent yang mendarat di dalam titik buta tadi tetap terekod. loop() kemudian membandingkan kiraan itu dengan apa yang ada di atas skrin dan melukis semula hanya bila kedua-duanya berbeza: pusingkan tombol sepuluh detent dengan pantas dan anda dapat jauh lebih sedikit daripada sepuluh redraw, tetapi anda tetap mendarat pada item yang betul.
Bagaimana rupa sketch menu itu sebenarnya?
Sebuah menu ialah mesin keadaan yang kecil dengan tiga kepingan state: menuIndex untuk baris tempat penanda itu berada, satu bool editing untuk menentukan tombol itu sedang melekat pada apa, dan array values[] untuk nombor-nombornya. Tombol menggerakkan menuIndex semasa melayari dan values[menuIndex] semasa menyunting; suis tekan itu pula menukar antara kedua-dua mod. Corak ini boleh dipindahkan ke atas apa sahaja yang ada tetapan β jam DS3231 yang memang menjadi sebab menu ini direka, atau tempoh tamat masa pada kunci keypad itu.
// Menu boleh skrol guna rotary encoder + OLED, Arduino Uno.
// Pendawaian: KY-040 CLK -> D2 (INT0), DT -> D3, SW -> D4, + -> 5V, GND -> GND.
// OLED SCL -> A5, SDA -> A4, VCC -> 5V, GND -> GND.
// Intipatinya: kira di dalam interrupt, lukis semula hanya bila kiraan berubah.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDR 0x3C // silkscreen tulis 0x78; itu bentuk 8-bit, 0x78 >> 1 = 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
const uint8_t PIN_CLK = 2; // INT0 - satu daripada dua sahaja pin external-interrupt Uno
const uint8_t PIN_DT = 3;
const uint8_t PIN_SW = 4;
const uint8_t ITEMS = 4;
// Label duduk dalam flash, bukan dalam SRAM - frame buffer sudah pun makan separuh RAM.
const char item0[] PROGMEM = "Hour";
const char item1[] PROGMEM = "Minute";
const char item2[] PROGMEM = "Brightness";
const char item3[] PROGMEM = "Beep";
const char *const MENU[ITEMS] PROGMEM = {item0, item1, item2, item3};
const int8_t VAL_MIN[ITEMS] PROGMEM = {0, 0, 0, 0};
const int8_t VAL_MAX[ITEMS] PROGMEM = {23, 59, 15, 1};
int8_t values[ITEMS] = {7, 30, 8, 1};
uint8_t menuIndex = 0;
bool editing = false;
volatile int8_t encDelta = 0; // detent yang masuk sejak loop() tengok kali terakhir
volatile uint32_t lastEdgeUs = 0;
// Berjalan pada setiap edge menjunam CLK. Satu detent = satu kitaran quadrature penuh,
// dan satu kitaran ada tepat satu edge menjunam CLK, jadi ini dicetuskan sekali bagi setiap klik.
void onClkFalling() {
uint32_t now = micros();
if (now - lastEdgeUs < 2000UL) return; // contact bounce: abaikan 2 ms chatter
lastEdgeUs = now;
if (digitalRead(PIN_DT)) encDelta++; // DT masih HIGH ketika CLK menjunam
else encDelta--; // DT sudah pun jatuh ke LOW
}
// Dicetuskan sekali pada tekanan, tidak pernah pada lepasan, dan tidak pernah pada bounce.
bool buttonPressed() {
static bool wasDown = false;
static uint32_t lastChangeMs = 0;
bool down = (digitalRead(PIN_SW) == LOW); // INPUT_PULLUP: ditekan terbaca LOW
if (down != wasDown && millis() - lastChangeMs > 25UL) {
lastChangeMs = millis();
wasDown = down;
return down;
}
return false;
}
// Panggilan yang mahal. Semua di atas wujud supaya ini berjalan sejarang mungkin.
void drawMenu() {
char label[16]; // mesti muat label terpanjang berserta penamatnya
display.clearDisplay();
display.setTextSize(1);
for (uint8_t i = 0; i < ITEMS; i++) {
uint8_t y = 4 + i * 15;
strcpy_P(label, (char *)pgm_read_ptr(&MENU[i]));
display.setCursor(0, y);
if (i != menuIndex) display.print(F(" ")); // tidak dipilih
else if (editing) display.print(F("* ")); // tombol menyunting nilai ini
else display.print(F("> ")); // tombol menggerakkan kursor
display.print(label);
display.setCursor(98, y);
display.print(values[i]);
}
display.display(); // ~25 ms CPU tersekat - sebab itulah ISR tadi wujud
}
void setup() {
pinMode(PIN_CLK, INPUT_PULLUP);
pinMode(PIN_DT, INPUT_PULLUP);
pinMode(PIN_SW, INPUT_PULLUP);
Serial.begin(9600);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("No SSD1306 at 0x3C - check SDA/SCL and 5V"));
for (;;) { }
}
display.setTextColor(SSD1306_WHITE);
attachInterrupt(digitalPinToInterrupt(PIN_CLK), onClkFalling, FALLING);
drawMenu();
}
void loop() {
bool changed = false;
// encDelta cuma satu bait, tapi mengosongkannya ialah read-modify-write, jadi kawal ia.
noInterrupts();
int8_t steps = encDelta;
encDelta = 0;
interrupts();
if (steps != 0) {
if (editing) {
int8_t lo = (int8_t)pgm_read_byte(&VAL_MIN[menuIndex]);
int8_t hi = (int8_t)pgm_read_byte(&VAL_MAX[menuIndex]);
int16_t v = (int16_t)values[menuIndex] + steps;
if (v < lo) v = lo; // hadkan, jangan biar nilai berpusing balik
if (v > hi) v = hi;
values[menuIndex] = (int8_t)v;
} else {
int16_t i = (int16_t)menuIndex + steps;
while (i < 0) i += ITEMS; // biar kursor berpusing dua-dua arah
menuIndex = (uint8_t)(i % ITEMS);
}
changed = true;
}
if (buttonPressed()) {
editing = !editing;
changed = true;
}
if (changed) drawMenu(); // barulah lukis
}
Pasang Adafruit SSD1306 dan Adafruit GFX Library daripada Library Manager; sketch ini dikompil menjadi 15,342 bait flash. Sengaja tiada library encoder di sini β handler di atas cuma sembilan baris. Perhatikan pengawal noInterrupts() di dalam loop(): membaca satu bait memang atomik pada AVR, tetapi membacanya kemudian mengosongkannya ialah dua operasi, dan satu detent yang tiba di antara kedua-duanya akan dibuang secara senyap.
Satu had sebelum anda guna semula corak ini: empat baris pada jarak 15 piksel ialah tepat apa yang muat pada panel 64 piksel, jadi tiada apa-apa di sini yang skrol lagi β item kelima mendarat pada y = 64, di luar bahagian bawah kaca. Melebihi empat, lukis satu tetingkap: simpan satu topIndex, lukis empat baris bermula daripadanya, dan gerakkan ia hanya bila menuIndex terkeluar dari salah satu hujung.

Kesilapan biasa yang kami nampak daripada pelanggan sebenar
Menu bergerak dua atau empat item setiap klik. Hampir selalunya model pengiraan yang bertindak balas kepada setiap edge, atau kepada kedua-dua edge pada satu channel. Kira edge menjunam CLK sahaja.
Butang terbaca seolah-olah ditekan berterusan. Suis KY-040 hanya menyambung SW terus ke ground dan tidak buat apa-apa selain itu, jadi INPUT biasa meninggalkan pin itu terapung dan sketch pula nampak tekanan hantu. Guna INPUT_PULLUP dan anggap LOW sebagai ditekan.
Tombol “terlepas langkah” hanya semasa skrin mengemas kini. Petandanya: pusingan perlahan sempurna, pusingan pantas tidak. Tiada apa-apa yang rosak pada encoder β redraw 25 ms itulah yang menyekat gelung polling. Pindahkan pengiraan itu ke dalam interrupt.
Skrin kosong. Buka Serial Monitor pada 9600 dahulu β sketch memang memberitahu kalau paparan itu tidak pernah menjawab, kemudian ia berhenti, sebab itulah board pun nampak macam mati. Selalunya SCL dan SDA bertukar tempat pada header IIC shield, atau 0x78 yang tertulis dalam sketch dan bukan 0x3C β panduan monitor OLED kami merangkumi bahagian paparannya.
Soalan Lazim
Pin Arduino mana yang patut digunakan oleh rotary encoder KY-040?
CLK pada D2, DT pada D3, SW pada D4, + pada 5V dan GND pada GND. D2 dan D3 ialah satu-satunya pin external-interrupt pada Uno, jadi CLK patut duduk pada salah satu daripadanya kalau pengiraan itu mahu terselamat daripada redraw skrin.
Kenapa rotary encoder saya melangkau dua atau empat item menu setiap klik?
Satu detent ialah satu kitaran quadrature penuh, iaitu empat peralihan elektrik merentasi CLK dan DT. Bertindak balas kepada keempat-empatnya memberi empat kiraan setiap klik; kedua-dua edge pada satu channel pula memberi dua. Bertindak balas kepada edge menjunam CLK sahaja dan anda dapat tepat satu.
Adakah saya perlukan library untuk KY-040?
Tidak. Dua pin INPUT_PULLUP, satu attachInterrupt pada edge menjunam CLK, dan satu digitalRead pada DT di dalam handler itu sudah menjadi decoder yang lengkap. Anda memang perlukan Adafruit SSD1306 dan Adafruit GFX untuk paparannya.
Alamat I2C OLED 0.96″ ini 0x78 atau 0x3C?
Kedua-duanya, cuma dalam notasi yang berbeza. Silkscreen board mencetak 0x78, iaitu bentuk 8-bit yang termasuk bit baca/tulis; library Wire Arduino pula mahukan bentuk 7-bit, jadi anjakkan ia ke kanan sebanyak satu: 0x78 >> 1 = 0x3C. Kedudukan jumper alternatif memberi 0x7A, atau 0x3D di dalam sketch.
Boleh tak menu yang siap itu berjalan tanpa komputer?
Boleh β satu adaptor 9V centre-positive ke dalam barrel jack Uno. Uno R3 menerima 7β12V di situ, dan encoder serta OLED cuma menarik puluhan miliampere daripada rel 5V board itu sendiri.
Kemas kini terakhir Ogos 2026. Tersangkut? Chat dengan kami di WhatsApp.



Rotary Encoder KY-040 360 Degree Encoder Sensor Module High Precision Knob
0.96β OLED Display Module Blue White Screen I2C IIC Serial 128X64 LCD Display Board - 0.96' OLED WHITE
Arduino Uno Compatible SMD UNO R3 with Type B Cable - ATMEGA328P with CH340G-Microcontroller Project
Sensor Shield V5.0 Arduino Uno R3 Shield For Arduino Protoype Application
40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE F-F 20CM
Power Supply Adapter DC Universal AC to DC Converter PSU 5V2A 5V3A 9V2A 12V2A - P.S. ADAPTOR (9V2A)