Introduction to STM32 Key Handling: From Basics to Complete Implementation (with Code Examples)

Introduction to STM32 Key Handling: From Basics to Complete Implementation (with Code Examples)

🔑 What is a “Key”?

In embedded development, keys (Button/Key) are one of the most common forms of human-machine interaction. It is essentially a mechanical switch:

Based on the above schematic analysis, we can conclude:

  • Not Pressed: The circuit is open, and the pin is pulled high by a pull-up resistor.
  • Pressed: The circuit is closed, and the pin is pulled low.

👉 Therefore, we usually agree that: Pressed = Low, Released = High.

💡 Note: If your hardware connections are different, the logic will be reversed, and adjustments should be made according to the actual situation.

⚙️ Hardware and Level Agreement

Assuming we have four independent keys connected as follows:

  • • KEY1 → PB0
  • • KEY2 → PB1
  • • KEY3 → PB2
  • • KEY4 → PA0

And configured as:

  • <span>GPIO_MODE_INPUT</span>
  • <span>GPIO_PULLUP</span> (Pull-up input, grounding when pressed results in low level)

💻 Complete Code Implementation

1️⃣ Header File <span>key_app.h</span>

#ifndef __KEY_APP_H__
#define __KEY_APP_H__

#include "bsp_system.h"
void key_proc(void);

#endif

2️⃣ Source File <span>key_app.c</span>

#include "key_app.h"

uint8_t key_val = 0;  // Current key state
uint8_t key_old = 0;  // Previous key state
uint8_t key_down = 0; // Pressed key
uint8_t key_up = 0;   // Released key

/**
 * @brief Read key state
 * @return Returns the key number. 0 indicates no key pressed, 1-4 indicates the corresponding key is pressed.
 */
uint8_t key_read(void)
{
  uint8_t temp = 0;

  if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) temp = 1;
  if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET) temp = 2;
  if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET) temp = 3;
  if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) temp = 4;

  return temp;
}

/**
 * @brief Key processing function
 *        Scans the current state and generates "press/release" events
 */
void key_proc(void)
{
  key_val = key_read();
  key_down = key_val & (key_old ^ key_val); // Just pressed key
  key_up   = ~key_val & (key_old ^ key_val); // Just released key
  key_old  = key_val;
    
  // Key pressed
  switch(key_down)
  {
      // Key 1 pressed, led1 on
      case 1:
          led1_on();
      break;
      // Key 2 pressed, led2 on
      case 2:
          led2_on();
      break;
      // Key 3 pressed, led3 on
      case 3:
          led3_on();
      break;
      // Key 4 pressed, led4 on
      case 4:
          led4_on();
      break;
  }
  // Key released
  switch(key_up)
  {
      // Key 1 released, led1 off
      case 1:
          led1_off();
      break;
      // Key 2 released, led2 off
      case 2:
          led2_off();
      break;
      // Key 3 released, led3 off
      case 3:
          led3_off();
      break;
      // Key 4 released, led4 off
      case 4:
          led4_off();
      break;
  }
}

🧩 Why These Three Lines?

Many people may be confused at this point: why use this bitwise operation? Below is a line-by-line explanation.

1)<span>key_old ^ key_val</span> —— Find the changes

The characteristic of XOR (<span>^</span>) is: the same is 0, different is 1.

  • • If this time and the last state are the same → no change → the result is 0.
  • • If this time and the last state are different → there is a change → the result is non-zero.

So <span>(key_old ^ key_val)</span><span> is the </span><strong><span>changing part</span></strong><span>.</span>

2)<span>key_down = key_val & (key_old ^ key_val);</span>

  • • Condition one: this time is pressed (<span>key_val</span> is 1).
  • • Condition two: different from the last time (<span>key_old ^ key_val</span> is 1).

If both conditions are met, it indicates thatthis key is “just pressed”.

3)<span>key_up = ~key_val & (key_old ^ key_val);</span>

  • • Condition one: this time not pressed (<span>~key_val</span> is 1).
  • • Condition two: different from the last time (<span>key_old ^ key_val</span> is 1).

If both conditions are met, it indicates thatthis key is “just released”.

4)<span>key_old = key_val;</span>

Finally, the current state must be saved for the next comparison; otherwise, it will not be possible to determine the change.

🚀 How to Use in Projects?

  1. 1. Periodic Call (in the scheduler or timer, call every 10~30ms): You can refer to previous articles on the public account for knowledge about the scheduler.
key_proc();  // Scan keys
  1. 1. Detect “Just Pressed” Keys:
switch (key_down) {
  case 1: /* KEY1 pressed */ break;
  case 2: /* KEY2 pressed */ break;
  case 3: /* KEY3 pressed */ break;
  case 4: /* KEY4 pressed */ break;
  default: break;
}
  1. 1. Detect “Just Released” Keys:
switch (key_up) {
  case 1: /* KEY1 released */ break;
  case 2: /* KEY2 released */ break;
  case 3: /* KEY3 released */ break;
  case 4: /* KEY4 released */ break;
  default: break;
}

📌 Precautions

  1. 1. Numbering Model:
  • • The return value is 0~4 numbering, not a bitmap.
  • • When multiple keys are pressed simultaneously, only the last detected key (fixed priority) will be returned.
  • 2. Judgment Method:
    • • Since it is a number, the writing should be <span>if (key_down == 2)</span> or <span>switch-case</span>.
    • • Do not use bitwise operations <span>if (key_down & 0x02)</span>.

    ✅ Summary

    • • This code implements a minimal usable key driver: supports numbering return and detects press and release events.
    • • Advantages: simple and intuitive, suitable for beginners and small projects.
    • • Limitations: does not support simultaneous detection of multiple keys, needs to be expanded in the project according to requirements.

    Leave a Comment