Skip to content

Pull-ups, pull-downs and buttons

By the end of this lesson you will:

  1. Explain why an input pin with no pull-up or pull-down reads unpredictably
  2. Wire an active-low button, and explain why pressing it reads 0
  3. Measure a button’s bounce duration with a logic analyzer, and choose a debounce time from the measured data
  • You know V_IH and V_IL from the lesson Logic levels and basic gates
  • For the lab: a TESAIoT Dev Kit flashed with the QWA309 Header I/O Test example, a breadboard, one leaded push button (tactile switch), a 10 kΩ resistor and a 1 kΩ resistor, a multimeter, and an inexpensive logic analyzer that works with PulseView
  • This is the first lesson to use a logic analyzer. The steps in the lab are enough for this task; details on sampling rate live in the lesson Capturing your first digital signal

Run the header test program and press the GPIO In button, with nothing at all connected to the header. The program sets pins P13.0, P13.3, P13.4, P13.5, P13.6, P13.7 as no-pull (high-Z) inputs, reads every 100 ms for 8 s, and prints the mask value every time it changes.

While it runs, try touching those pins with a finger, or holding a jumper wire (connected on one end only) near them, and watch whether the mask changes. On some boards the value visibly bounces around; on others it sits steady at 0 even with nothing connected. Both tell you the same thing: a reading from a floating pin cannot be trusted. Steady today does not mean steady tomorrow — not when a hand is nearby, or when a neighbouring wire changes state.

1. Floating pins: an input nobody tells a value

Section titled “1. Floating pins: an input nobody tells a value”

A CMOS input has extremely high input resistance, with leakage current in the nA to µA range. With nothing connected, an input pin behaves like a tiny capacitor that nobody controls. Charge from a finger, a signal from a neighbouring wire, or the electric field around you can push its voltage anywhere, including into the unguaranteed range between V_IL and V_IH. When the voltage sits in the middle, the transistors inside the input circuit can end up partially conducting on both sides at once, drawing more current than normal. So a floating pin is both unpredictable and wasteful of power.

The fix is to always give an input a “default value.”

  • Pull-up: a resistor from the pin to the supply. With nothing driving it, the pin reads 1.
  • Pull-down: a resistor from the pin to GND. With nothing driving it, the pin reads 0.

This resistor must be “weak” enough that another device (or a button) can easily override it, and “strong” enough that noise cannot.

Most microcontrollers have a pull-up and pull-down built into the chip, enabled from software; its real value must be checked in the datasheet (the tolerance is often wide). On the PSoC, this uses drive mode CY_GPIO_DM_PULLUP, and the SDK’s cm33/io/04_gpio_led_button.c example warns of one detail worth remembering: the out-value passed to Cy_GPIO_Pin_FastInit() must be 1, because in this mode that value is what actually enables the pull-up. Send 0, and the pin reads 0 permanently, as if the button were held down forever. The Developer Hub’s Button Monitor example calls Cy_GPIO_Pin_FastInit(..., CY_GPIO_DM_PULLUP, 1UL, HSIOM_SEL_GPIO), following exactly this rule.

2. An active-low button, and choosing a pull-up value

Section titled “2. An active-low button, and choosing a pull-up value”
3V3 R_pu 10 kΩ 1 kΩ input pin (P13.0) BTN_N SW released: pin = 1 (3.3 V) pressed: pin = 0 (0 V)
An active-low button: released, the pull-up holds the pin at 1. Pressed, the switch connects the pin to ground, making it 0. The 1 kΩ series resistor protects the pin in case the program accidentally sets it as an output.
  • Released: the switch is open, no current flows through R_pu, the voltage across R_pu is zero, so the pin equals 3.3 V and reads 1.
  • Pressed: the switch connects the pin directly to GND, the pin is 0 V and reads 0, and current flows through R_pu at 3.3 V / R_pu.

“Pressed means 0” is why this is called active-low. In code we write pressed = !pin, or compare against a constant the BSP provides, such as CYBSP_BTN_PRESSED (whose value is 0). Why is this so common? GND is everywhere on a board, so a switch connecting to GND never needs to route the supply rail out to the button — and this same principle is the basis of open-drain buses like I2C in Module 4.

Choosing R_pu balances three competing concerns.

R_pu Current while pressed (3.3 V / R) Rising-edge speed (τ = R × 10 pF) Voltage drop from 1 µA leakage
1 kΩ 3.3 mA (wasteful) 10 ns 1 mV
10 kΩ 0.33 mA 100 ns 10 mV
100 kΩ 33 µA 1 µs 0.1 V
1 MΩ 3.3 µA 10 µs 1 V (leaving the pin at 2.3 V)

The 10 pF pin/trace capacitance and the 1 µA leakage current in the table are assumed values, just to show the trend — check the real numbers in the datasheet. The last row is worth noticing: at 1 MΩ, leakage current leaves the pin at 3.3 − 1.0 = 2.3 V, below the rule-of-thumb V_IH of 2.31 V (0.7 × VDD). The pin might not reliably read as 1 even while the button is released. For a typical button, a value anywhere from about 4.7 kΩ to 47 kΩ is safe on every front — 10 kΩ is the most commonly used value.

The 1 kΩ series resistor at the pin in the figure is not needed for the button to work — it is insurance. If the program accidentally sets the pin as an output driving 1, and someone presses the button at that moment, the pin would be shorted directly to GND. This resistor limits that current to no more than 3.3 V / 1 kΩ = 3.3 mA. We will always include it in the lab.

3. Contact bounce, and choosing a debounce time

Section titled “3. Contact bounce, and choosing a debounce time”

The metal contacts inside a button do not touch just once — they strike and bounce apart several times in a very short window before settling. This is called contact bounce.

bounce pin (released = 1) pressed = 0 debounce time > worst bounce you measured
A logic analyzer capture of a button press: the pin toggles several times in a short window before settling. This window is contact bounce.

A microcontroller can read a pin on a microsecond timescale, so it sees a single press as many. Bounce duration varies a great deal by button type, age and how hard it is pressed. Some are under a millisecond; others are several milliseconds. So you must measure the actual button you are using, never guess.

A common software debounce approach is “accept a new value only once it has held steady long enough.” The Developer Hub’s Button Monitor example reads the button every 25 ms, and accepts a new value once it reads the same value for two consecutive checks (BUTTON_DEBOUNCE_TICKS = 2) — meaning it must hold steady for about 50 ms.

A data-driven way to choose a debounce time:

  1. Measure bounce several times (at least 10 to 20, both pressing and releasing), and find the longest duration
  2. Debounce time = the longest duration × a safety multiplier of 2 to 3
  3. Check that it is not so long that users feel the button lag — a delay of a few tens of milliseconds generally still feels instant

Hardware debouncing: put a capacitor across the button, such as 100 nF with a 10 kΩ pull-up. On press, the capacitor discharges through the button very quickly; on release, it charges through the 10 kΩ with τ = 1 ms. The voltage reaches 0.7 × VDD at time τ × ln(1 / 0.3) = 1.2 × τ = 1.2 ms. An edge this slow should feed an input with a Schmitt trigger (built-in hysteresis), otherwise it may toggle back and forth while the voltage crosses the middle range.

Problem: connect an external button to pin P13.0 on the TESAIoT Dev Kit’s header, with an external pull-up, and choose a debounce time.

  1. Circuit: 3V3 → R_pu 10 kΩ → point BTN_N → button → GND, and BTN_N → 1 kΩ → P13.0, per the figure in section 2.
  2. Current while pressed: 3.3 V / 10 kΩ = 0.33 mA. Power in R_pu = 3.3 V × 0.33 mA = 1.09 mW.
  3. Levels the pin sees: released, 3.3 V (minus the pin’s leakage current times 11 kΩ, which is negligible); pressed, 0 V. Both are far from V_IH and V_IL.
  4. Bounce data. Suppose you measured 10 presses with a logic analyzer, getting these bounce durations in ms (sample data — not your button’s real values):
0.2 0.4 1.6 0.3 0.9 0.1 2.4 0.5 0.7 1.1
maximum = 2.4 ms average = 0.82 ms
  1. Choose the debounce time: use the maximum, not the average. 2.4 ms × 3 = 7.2 ms, rounded to 10 ms — still fast enough that the user does not notice.
  2. Check against the code: if you read every 5 ms and accept once the value holds for two consecutive checks, you get about 10 ms — matching your choice.
  1. A 4.7 kΩ pull-up at 3.3 V. How much current flows while the button is pressed? What power does the resistor dissipate?
  2. An input pin has up to 1 µA of leakage current. Using a pull-up of (a) 47 kΩ, (b) 470 kΩ, what voltage is left at the pin while the button is released? Is it still above V_IH = 2.31 V?
  3. A debounce RC of a 10 kΩ pull-up with 1 µF: while releasing the button, how long does the pin take to reach 0.7 × VDD?
  4. Measured bounce durations (ms): 0.6, 0.3, 3.1, 0.8, 1.2. What debounce time should you choose, and why not the average?
  5. A button wired as pull-down (resistor to GND, button to 3.3 V) — what does pressing it read, and what is this configuration called?
  6. A program reads a pin every 100 ms, like the GPIO In button in the header test program. Will it see a 2 ms bounce?
  1. I = 3.3 V / 4.7 kΩ = 0.702 mA, and P = 3.3 V × 0.702 mA = 2.32 mW
  2. (a) 1 µA × 47 kΩ = 0.047 V, leaving 3.25 V — passes comfortably. (b) 1 µA × 470 kΩ = 0.47 V, leaving 2.83 V — still passes, but the margin has shrunk a lot.
  3. τ = 10 kΩ × 1 µF = 10 ms. Time to reach 0.7 × VDD = 1.2 × 10 ms = 12 ms.
  4. The maximum, 3.1 ms, times a 2-to-3 safety factor gives about 6 to 9 ms — choosing 10 ms works. The average (1.2 ms) would let half of all presses’ bounce slip through.
  5. Pressed connects the pin to 3.3 V, reading 1 — this is called active-high.
  6. No. Reading every 100 ms only catches things long enough to span a read cycle. A 2 ms bounce will mostly fall between two reads — exactly why a much faster-sampling logic analyzer is needed here.

Answer at least 4 of the 5 questions in quiz.yaml correctly, and explain in your own words why “it looked steady during testing” is not proof that a floating pin is fine to use.

Part A: a floating pin versus a pin with a pull-up

  1. With USB unplugged, wire the circuit from the worked example, but without R_pu 10 kΩ yet (just the button and the 1 kΩ resistor into P13.0).
  2. Plug in USB, press GPIO In, then touch the BTN_N wire with a finger. Record how many times bit 0 (P13.0) changes within 8 s.
  3. Unplug USB, add R_pu 10 kΩ, and repeat. Bit 0 should now stay steady at 1, even while touching the wire.
  4. Press GPIO In again, then alternately hold and slowly release the button. Bit 0 should read 0 while pressed and 1 while released.
  5. Measure the voltage at BTN_N with a multimeter, both pressed and released, then compute the current through R_pu while pressed.

Part B: measuring bounce with a logic analyzer

  1. Connect the logic analyzer’s GND to the board’s GND first, then connect channel 0 (CH0) to point BTN_N.
  2. Open PulseView, select the device, set the sample rate to at least 1 MHz (1 µs resolution), with enough samples for several seconds.
  3. Set a trigger on CH0’s falling edge, press Run, then press the button once.
  4. Zoom in on the first edge, and use the time cursor to measure from the first edge to the last edge before it settles — that is the bounce duration.
  5. Repeat at least 10 times while pressing, and 10 times while releasing (rising-edge trigger). Record it in the table, then choose a debounce time using section 3’s method.
Trial Bounce while pressing (ms) Bounce while releasing (ms)
1
2
…
Maximum

Part C: compare against the Button Monitor example. Run the QWA309 Push Button Monitor example, which reads buttons on pins P17.7 and P17.5 as active-low with the chip’s internal pull-up (if your board does not have buttons on these pins yet, see the example’s README for how to wire them). Press the button quickly and see whether the count matches how many times you actually pressed. Is this example’s roughly 50 ms debounce time longer than the value you chose in Part B? If it is much longer, what are the trade-offs?

If using the Eva Kit: wire the same button circuit to a spare pin on the board, and write a short program that sets that pin as a no-pull input and prints the value when it changes. Part B works exactly the same way.

The next module starts with Measuring voltage and continuity, where we get comfortable and safe with a multimeter. If you want to see a button used in a C program next, see the lesson Buttons and menus in the TESAIoT Firmware Stack course.

Before this lesson, if a button in your program occasionally over-counted, would you have suspected the code or the hardware first? How would you prove which one is at fault now?

Review questions

Answer on your own first, then open the answer.

  1. An unconnected input with no pull-up or pull-down read a steady 0 during a test. Which conclusion is correct? (Objective 1)

    1. ขาลอยในบอร์ดนี้เป็น 0 เสมอ ใช้ได้
    2. ค่าของขาลอยขึ้นกับประจุและสัญญาณรบกวนรอบตัว นิ่งตอนทดสอบไม่ได้รับประกันว่าจะนิ่งตลอดไป
    3. ชิปมี pull-down ภายในเปิดอยู่แน่นอน
    4. ขานั้นเสีย
    Show answer

    Answer: B. ค่าของขาลอยขึ้นกับประจุและสัญญาณรบกวนรอบตัว นิ่งตอนทดสอบไม่ได้รับประกันว่าจะนิ่งตลอดไป

    ขาเข้า CMOS มีความต้านทานสูงมาก แรงดันของมันลอยไปตามประจุรอบตัว บางครั้งนิ่ง บางครั้งแกว่ง ต้องมี pull-up หรือ pull-down กำหนดค่าเริ่มต้นเสมอ

  2. A button connects a pin to GND with a 10 kΩ pull-up to 3.3 V. When pressed, what does the pin read and what current flows in the pull-up? (Objective 2)

    1. อ่านได้ 1 กระแส 0 mA
    2. อ่านได้ 0 กระแส 0.33 mA
    3. อ่านได้ 0 กระแส 33 mA
    4. อ่านได้ 1 กระแส 0.33 mA
    Show answer

    Answer: B. อ่านได้ 0 กระแส 0.33 mA

    ปุ่มต่อขาลง GND โดยตรง ขาจึงเป็น 0 V อ่านได้ 0 (active-low) และแรงดันทั้งหมด 3.3 V ตกคร่อม pull-up กระแส = 3.3 V / 10 kΩ = 0.33 mA

  3. On PSoC, a pin is set to CY_GPIO_DM_PULLUP but Cy_GPIO_Pin_FastInit() gets out-value 0. What happens to an active-low button? (Objective 2)

    1. ทำงานปกติ ค่า out-value ไม่มีผลกับขาเข้า
    2. ขาอ่านได้ 0 ตลอดเหมือนปุ่มถูกกดค้าง เพราะในโหมดนี้ out-value คือตัวที่เปิด pull-up
    3. ขากลายเป็นขาออก
    4. ปุ่มกลายเป็น active-high
    Show answer

    Answer: B. ขาอ่านได้ 0 ตลอดเหมือนปุ่มถูกกดค้าง เพราะในโหมดนี้ out-value คือตัวที่เปิด pull-up

    SDK เตือนไว้ในตัวอย่าง cm33/io/04_gpio_led_button.c ว่าในโหมด pull-up ค่า out-value ต้องเป็น 1 ถ้าเป็น 0 ขาจะถูกดึงลง และโปรแกรมจะเห็นปุ่มถูกกดตลอดเวลา

  4. Measured bounce times are 0.3, 0.5, 2.8, 0.4 and 1.0 ms. Which debounce time should you choose? (Objective 3)

    1. 1 ms (ราวค่าเฉลี่ย)
    2. 2.8 ms พอดี
    3. ราว 6 ถึง 10 ms (ค่ามากที่สุดคูณเผื่อ 2 ถึง 3 เท่า)
    4. 500 ms
    Show answer

    Answer: C. ราว 6 ถึง 10 ms (ค่ามากที่สุดคูณเผื่อ 2 ถึง 3 เท่า)

    ต้องออกแบบจากกรณีที่แย่ที่สุด (2.8 ms) แล้วเผื่อ เพราะปุ่มอาจเด้งนานขึ้นเมื่อเก่าลง ค่าเฉลี่ยทำให้การกดบางครั้งนับซ้ำ ส่วน 500 ms นานจนผู้ใช้รู้สึกว่าปุ่มหน่วง

  5. You want to see bounce with pulses as short as about 20 µs. Which logic analyzer sample rate fits best? (Objective 3)

    1. 10 kHz
    2. 50 kHz
    3. 1 MHz
    4. 10 Hz
    Show answer

    Answer: C. 1 MHz

    ที่ 1 MHz หนึ่งตัวอย่างห่างกัน 1 µs พัลส์ 20 µs จะมีราว 20 ตัวอย่าง เห็นชัดและวัดเวลาได้ ที่ 50 kHz (20 µs ต่อตัวอย่าง) พัลส์นี้อาจหายไปทั้งพัลส์หรือเหลือตัวอย่างเดียว

Cite this lesson

If you teach from this lesson or reuse it in slides or documents, credit it with the text below. If you changed it, add (adapted) after the title.

"Pull-ups, pull-downs and buttons" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

Thai attribution: "Pull-up, pull-down และปุ่มกด" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

Lesson link: https://tesaiot.github.io/tesa-qualification-program/en/courses/electronics-and-instruments/m02-digital-logic/l02-pullups-and-buttons/

Full guide: how to cite TESA

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

Content is licensed CC BY-NC 4.0. Reuse it non-commercially and credit the Thai Embedded Systems Association (TESA) every time. · How to cite TESA