STM32 ОБРАБОТКА КНОПОК В ЦЫКЛЕ

 

 

m32f030k6t6 pinout 1

STM32F030K6 — Тест GPIO: 8 кнопок → 6 светодиодов (опрос в цикле, debounce, toggle)

Цель. Базовый тест платы: проверяем входы/выходы без прерываний и RTOS. Восемь кнопок (PB0, PB1, PB3–PB7, PF0) управляют шестью светодиодами (PA8, PA9, PA10, PA11, PA12, PA15). Логика — toggle с жёстким антидребезгом (по времени), удобна для проверки «проводом».

Поведение кнопок

  • PB0..PB6 — переключают (toggle) свой светодиод.
  • PB7 — инвертирует все светодиоды.
  • PF0 — тоже инвертирует все светодиоды (можно поменять на «погасить всё», если нужно).

Соответствие пинов

Кнопка (порт/пин)Светодиод (порт/пин)Комментарий
PB0PA8индивидуальный toggle
PB1PA9индивидуальный toggle
PB3PA10индивидуальный toggle
PB4PA11индивидуальный toggle
PB5PA12индивидуальный toggle
PB6PA15индивидуальный toggle
PB7PA8..PA12, PA15инвертирует все
PF0PA8..PA12, PA15инвертирует все

Электрика (предполагаемая)

  • Кнопки — активный ноль: в покое HIGH, при замыкании — LOW. Для простых тестов можно включить внутренние подтяжки к VCC.
  • Светодиоды: анод → пин PAx через резистор 220–1кΩ, катод → GND.

CubeMX / STM32CubeIDE: как быстро собрать «чистый» проект

1) Новый проект

  1. MCU: STM32F030K6Tx. Toolchain/IDE: STM32CubeIDE.
  2. RCC/Clock: SYSCLK = HSI 8 МГц, без PLL.
  3. GPIO:
    • PA8,9,10,11,12,15 — Output Push-Pull, Low speed, No pull.
    • PB0,1,3,4,5,6,7 и PF0 — Input. Для тестов удобно сразу поставить Pull-up.
  4. NVIC: EXTI не настраиваем (тут опрос в цикле).
  5. Middleware: ничего (никаких FreeRTOS, CMSIS-RTOS2, CMSIS-DSP/NN).
  6. Project Manager → Code Generator:
    • «Generate peripheral initialization as a pair of '.c/.h' files per peripheral» — по желанию.
    • «Keep User Code when re-generating» — включить.
  7. Project Manager → Advanced/Linker Settings:
    • Minimum Heap Size = 0 (если без malloc) или 0x80.
    • Minimum Stack Size = 0x200 (512 B) для HAL + опроса.
  8. Сгенерировать проект.

2) Если случайно «надергало» лишних пакетов (симптомы: arm_math.h, cmsis_os2.h и пр.)

  • В дереве проекта Drivers/CMSIS/NN и Drivers/CMSIS/RTOS2 — удалить или Exclude from Build.
  • В .ioc → Software Packs убедиться, что RTOS/DSP/NN не выбраны.

3) RAM overflow: «section ._user_heap_stack will not fit in region RAM»

  • Это 4 КБ SRAM у F030. Уменьшаем Heap/Stack, как выше.
  • Проверьте, что не подключены тяжелые middleware и что printf без float (используется --specs=nano.specs).

Код (main.c) — опрос, toggle, антидребезг

Жёсткий антидребезг по времени (по HAL_GetTick()). Порог по умолчанию — 25 мс. Для «крокодила с проводом» можно поднять до 40–60 мс. Внутренние подтяжки включаются директивой USE_INTERNAL_PULLUP.

/* HAL demo: 8 кнопок (active-low) → Toggle LED logic + strong debounce (STM32F030K6T6) */
#include "main.h"


/* --- Прототипы --- */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);


/* --- Настройки --- */
#define DEBOUNCE_MS         25      /* устойчивость низкого уровня для фиксации клика */
#define USE_INTERNAL_PULLUP 1       /* 1 = включить PULLUP для PBx/PF0 (удобно для «провода») */


#define LEDS_MASK (GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_15)


/* --- Карта кнопок (PB0..PB7, PF0) --- */
typedef struct { GPIO_TypeDef *port; uint16_t pin; } Btn;
static const Btn btns[8] = {
  {GPIOB, GPIO_PIN_0}, // 0
  {GPIOB, GPIO_PIN_1}, // 1
  {GPIOB, GPIO_PIN_3}, // 2
  {GPIOB, GPIO_PIN_4}, // 3
  {GPIOB, GPIO_PIN_5}, // 4
  {GPIOB, GPIO_PIN_6}, // 5
  {GPIOB, GPIO_PIN_7}, // 6 (ALL TOGGLE)
  {GPIOF, GPIO_PIN_0}  // 7 (ALL TOGGLE по просьбе)
};


/* Соответствие PB0..PB6 → LED (PAx) */
static const uint16_t led_of_btn_idx[7] = {
  GPIO_PIN_8,  GPIO_PIN_9,  GPIO_PIN_10,
  GPIO_PIN_11, GPIO_PIN_12, GPIO_PIN_15,
  0 /* PB7 служебная */
};


/* --- Вспомогательные --- */
static inline uint8_t raw_is_low(uint8_t idx) {
  return (HAL_GPIO_ReadPin(btns[idx].port, btns[idx].pin) == GPIO_PIN_RESET);
}


/* Жёсткий антидребезг: фиксируем устойчивое состояние по таймеру.
   Возвращает 1 РОВНО ОДИН РАЗ на устойчивый переход HIGH→LOW (клик). */
static uint8_t pressed_falling(uint8_t idx)
{
  static uint8_t  last_raw[8]      = {1,1,1,1,1,1,1,1};  // считаем, что при старте «не нажато» (HIGH)
  static uint8_t  stable_state[8]  = {1,1,1,1,1,1,1,1};  // устойчивое состояние
  static uint32_t last_change_ms[8] = {0};               // время последней смены «сыра»
  static uint8_t  armed[8]         = {0};                // готовность выдать фронт


  uint32_t now = HAL_GetTick();
  uint8_t  raw = raw_is_low(idx) ? 0 : 1;                // 0=LOW(нажато), 1=HIGH(отпущено)


  if (raw != last_raw[idx]) {
    last_raw[idx] = raw;
    last_change_ms[idx] = now;                           // замечаем момент изменения
  }


  /* Если «сырой» уровень держится неизменным достаточно долго — обновляем устойчивое состояние */
  if ((now - last_change_ms[idx]) >= DEBOUNCE_MS && raw != stable_state[idx]) {
    stable_state[idx] = raw;
    if (stable_state[idx] == 0) {
      /* Зафиксирован устойчивый LOW — это наш клик (HIGH→LOW) */
      armed[idx] = 1;
    }

  }


  if (armed[idx]) {               // выдаём импульс один раз
    armed[idx] = 0;
    return 1;
  }
  return 0;
}


static inline void leds_write(uint16_t mask_on)
{
  HAL_GPIO_WritePin(GPIOA, LEDS_MASK, GPIO_PIN_RESET);
  if (mask_on) HAL_GPIO_WritePin(GPIOA, mask_on, GPIO_PIN_SET);
}


int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();


  uint16_t leds = 0;


  while (1)
  {
    /* PB0..PB6 — toggle своих LED */
    if (pressed_falling(0)) leds ^= led_of_btn_idx[0];
    if (pressed_falling(1)) leds ^= led_of_btn_idx[1];
    if (pressed_falling(2)) leds ^= led_of_btn_idx[2];
    if (pressed_falling(3)) leds ^= led_of_btn_idx[3];
    if (pressed_falling(4)) leds ^= led_of_btn_idx[4];
    if (pressed_falling(5)) leds ^= led_of_btn_idx[5];


    /* PB7 — инвертировать все */
    if (pressed_falling(6)) leds ^= LEDS_MASK;


    /* PF0 — тоже инвертировать все (по твоей задумке) */
    if (pressed_falling(7)) leds ^= LEDS_MASK;


    leds_write(leds);
    HAL_Delay(1);  // период опроса 1 мс
  }

}


/* --- Тактирование: HSI 8 МГц --- */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};


  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) Error_Handler();


  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) Error_Handler();
}


/* --- GPIO --- */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};


  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();


  /* Светодиоды: PA8,9,10,11,12,15 */
  HAL_GPIO_WritePin(GPIOA, LEDS_MASK, GPIO_PIN_RESET);
  GPIO_InitStruct.Pin   = LEDS_MASK;
  GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull  = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
ф
  /* Кнопки PB0,1,3,4,5,6,7 — входы */
  GPIO_InitStruct.Pin  = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
#if USE_INTERNAL_PULLUP
  GPIO_InitStruct.Pull = GPIO_PULLUP;     // удобно для теста «проводом»
#else
  GPIO_InitStruct.Pull = GPIO_NOPULL;     // если стоят внешние подтяжки
#endif
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);


  /* Кнопка PF0 — вход */
  GPIO_InitStruct.Pin  = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
#if USE_INTERNAL_PULLUP
  GPIO_InitStruct.Pull = GPIO_PULLUP;
#else
  GPIO_InitStruct.Pull = GPIO_NOPULL;
#endif
  HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
}

void Error_Handler(void)
{
  __disable_irq();
  while (1) { }
}

Пояснения по коду

  • DEBOUNCE_MS — время устойчивости уровня LOW для регистрации клика.
  • USE_INTERNAL_PULLUP — быстрое включение внутренних подтяжек для всех входов.
  • Опрос 1 мс + фильтр по времени даёт стабильную работу даже при «тыкалке проводом».

Диагностика PF0 (если «как будто не работает»)

  1. На время проверки включить Pull-up на PF0 (в шаблоне это уже делается при USE_INTERNAL_PULLUP=1).
  2. Убедиться, что PF0 не задействован под HSE (OSC_IN) железом: навесной резонатор/конденсаторы могут тянуть линию.
  3. Проверить фактический вывод корпуса (не перепутать с PF1/PA0).
  4. Если стоит «индикаторный диод на входе», он может сажать линию — лучше ставить буфер/транзистор.

Чек-лист «чтобы собралось с первого раза»

    • Без RTOS/DSP/NN. Только RCC + GPIO.
    • Heap ≤ 0x80 (или 0), Stack ≈ 0x200.
    • HSI 8 МГц, без PLL.
    • Входы — Pull-up (для теста), выходы — push-pull.
    • Если IDE спрашивает про «иерархический проект» — можно жать Yes, это безвредно.