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 — тоже инвертирует все светодиоды (можно поменять на «погасить всё», если нужно).

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

Кнопка (порт/пин)Светодиод (порт/пин)Комментарий
PB0 PA8 индивидуальный toggle
PB1 PA9 индивидуальный toggle
PB3 PA10 индивидуальный toggle
PB4 PA11 индивидуальный toggle
PB5 PA12 индивидуальный toggle
PB6 PA15 индивидуальный toggle
PB7 PA8..PA12, PA15 инвертирует все
PF0 PA8..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, это безвредно.