/** ****************************************************************************** * @file gpio.c * @author Разваляев Алексей * @brief Драйвер GPIO на основе PLIB035. * Этот файл содержит: * + Инициализацию портов GPIOA и GPIOB * + Функции для работы со светодиодами: * - Инициализацию структуры светодиода * - Включение, выключение, переключение светодиода * - Динамические режимы: моргание и плавное затухание * + Функции для работы с кнопками: * - Инициализацию структуры кнопки * - Чтение состояния кнопки с защитой от дребезга * + Утилитарные функции для получения конфигурации пинов * ****************************************************************************** * @attention * * Использование этого драйвера предполагает наличие корректных настроек: * - Определены массивы конфигурации gpioa_config и gpiob_config в periph_config.h * ****************************************************************************** * @verbatim ============================================================================== ##### Как использовать этот драйвер ##### ============================================================================== 1. Настройка периферии (periph_config.h): (+) Определить массивы GPIO_Init_TypeDef для портов: gpioa_config[32], gpiob_config[32] (+) Настроить режим пинов: Input, Output, AltFunc или не используется (аналог режим) 2. Инициализация GPIO: (+) gpio_init() — инициализация портов GPIOA и GPIOB 3. Работа со светодиодами: (+) GPIO_LED_Init(&led, GPIOA, GPIO_PIN_5, SET) — инициализация светодиода (+) GPIO_LED_On(&led) — включение светодиода (+) GPIO_LED_Off(&led) — выключение светодиода (+) GPIO_LED_Toggle(&led) — переключение светодиода (+) GPIO_LED_Blink_Start(&led, 500) — запуск моргания (период 500 мс) (+) GPIO_LED_Fading_Start(&led, 1000) — запуск плавного затухания (период 1 секунда) (+) GPIO_LED_Dynamic_Handle(&led) — обработка динамических режимов в основном цикле 4. Работа с кнопками: (+) GPIO_Switch_Init(&sw, GPIOB, GPIO_PIN_0, SET) — инициализация кнопки (+) GPIO_Read_Switch(&sw) — чтение состояния кнопки с фильтрацией 5. Получение конфигурации пинов: (+) gpio_get_init(порт, пин) - для периферии ============================================================================== ##### Особенности работы ##### ============================================================================== - Динамические режимы светодиодов: - Моргание: простой on/off с заданным периодом - Плавное затухание: квадратичное изменение яркости через ШИМ - Требуют вызова GPIO_LED_Dynamic_Handle() в основном цикле - Защита от дребезга: - Фильтрация по времени (Sw_FilterDelay в миллисекундах) - Неблокирующий алгоритм с отслеживанием состояния - При изменении уровня запускается таймер, состояние обновляется после задержки - ШИМ для плавного затухания: - Программная генерация с 15 уровнями яркости (LED_PWM_TICKS) - Полный цикл затухания-разтухания: 2 × LED_PWM_TICKS шагов - Конфигурация пинов: - Периферия может перезаписывать конфигурацию пинов (например, UART на AltFunc) - gpio_get_init() позволяет периферии получить исходную конфигурацию - После использования периферии можно восстановить исходные настройки - Табличный подход: - Каждый пин имеет свою строку в массиве конфигурации - Позволяет централизованно управлять всеми пинами - Упрощает отладку и модификацию конфигурации @endverbatim ****************************************************************************** */ //-- Includes ------------------------------------------------------------------ #include "periph_config.h" //-- Defines ------------------------------------------------------------------- //-- GPIO init functions ------------------------------------------------------- void gpio_init(void) { RCU_AHBClkCmd(RCU_AHBClk_GPIOA, ENABLE); RCU_AHBRstCmd(RCU_AHBRst_GPIOA, ENABLE); RCU_AHBClkCmd(RCU_AHBClk_GPIOB, ENABLE); RCU_AHBRstCmd(RCU_AHBRst_GPIOB, ENABLE); /* Сброс пинов */ GPIO_DeInit(GPIOA); GPIO_DeInit(GPIOB); // Инициализация порта A for (int i = 0; i < sizeof(gpioa_config) / sizeof(gpioa_config[0]); i++) { GPIO_Init(GPIOA, &gpioa_config[i]); } // Инициализация порта B for (int i = 0; i < sizeof(gpiob_config) / sizeof(gpiob_config[0]); i++) { GPIO_Init(GPIOB, &gpiob_config[i]); } } GPIO_Init_TypeDef *gpio_get_init(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin) { uint8_t pin_index = (31 - __CLZ(GPIO_Pin & -GPIO_Pin)); if (GPIOx == GPIOA) { // Проверяем границы массива if (pin_index < sizeof(gpioa_config) / sizeof(gpioa_config[0])) { return &gpioa_config[pin_index]; } } else if (GPIOx == GPIOB) { // У тебя была опечатка: возвращал gpioa_config вместо gpiob_config if (pin_index < sizeof(gpiob_config) / sizeof(gpiob_config[0])) { return &gpiob_config[pin_index]; } } return NULL; } //-- GPIO LED functions -------------------------------------------------------- /** * @brief Инициализировать светодиод (структуру светодиода) * @param led Указатель на структуру светодиода * @param GPIOx Указатель на структуру порта для светодиода * @param GPIO_PIN_X Пин для светодиода * @param LED_ActiveLevel Состояния пина, при котором светодиод будет включен */ OperationStatus GPIO_LED_Init(GPIO_LEDTypeDef *led, GPIO_TypeDef *GPIOx, uint32_t GPIO_PIN_X, BitState LED_ActiveLevel) { if(!led || !GPIOx ||!GPIO_PIN_X) return ERROR; led->LED_Port = GPIOx; led->LED_Pin = GPIO_PIN_X; led->LED_ActiveLvl = LED_ActiveLevel; GPIO_LED_Off(led); return OK; } /** * @brief Включить светодиод * @param led Указатель на структуру светодиода * @return Operation Status */ OperationStatus GPIO_LED_On(GPIO_LEDTypeDef *led) { if(!led || !led->LED_Port || !led->LED_Pin) return ERROR; led->state = LED_IS_ON; GPIO_WriteBit(led->LED_Port, led->LED_Pin, led->LED_ActiveLvl); return OK; } /** * @brief Выключить светодиод * @param led Указатель на структуру светодиода * @return Operation Status */ OperationStatus GPIO_LED_Off(GPIO_LEDTypeDef *led) { if(!led || !led->LED_Port || !led->LED_Pin) return ERROR; led->state = LED_IS_OFF; BitState offstate = (led->LED_ActiveLvl == SET)? CLEAR: SET; GPIO_WriteBit(led->LED_Port, led->LED_Pin, offstate); return OK; } /** * @brief Переключить светодиод * @param led Указатель на структуру светодиода * @return Operation Status */ OperationStatus GPIO_LED_Toggle(GPIO_LEDTypeDef *led) { if(!led || !led->LED_Port || !led->LED_Pin) return ERROR; if(led->state == LED_IS_ON || led->state == LED_IS_OFF) { if(led->state == LED_IS_OFF) led->state = LED_IS_ON; else led->state = LED_IS_OFF; GPIO_ToggleBits(led->LED_Port, led->LED_Pin); return OK; } else return ERROR; } /** * @brief Выставить светодиод по переменной * @param led Указатель на структуру светодиода * @param led_state Состояние светодиода * @return Operation Status */ OperationStatus GPIO_LED_Set(GPIO_LEDTypeDef *led, uint8_t led_state) { if(!led || !led->LED_Port || !led->LED_Pin) return ERROR; if(led_state) { return GPIO_LED_On(led); } else { return GPIO_LED_Off(led); } } /** * @brief Активировать моргание светодиодом * @param led Указатель на структуру светодиода * @param period Период плавного моргания светодиода * @return Operation Status * @details Функция ставит режим моргания, который после управляется в @ref GPIO_LED_Dynamic_Handle */ OperationStatus GPIO_LED_Blink_Start(GPIO_LEDTypeDef *led, uint32_t period) { if(!led || !led->LED_Port || !led->LED_Pin) return ERROR; led->state = LED_IS_BLINKING; led->LED_Period = period; return OK; } /** * @brief Активировать моргание светодиодом * @param led Указатель на структуру светодиода * @param period Период плавного моргания светодиода * @return Operation Status * @details Функция ставит режим моргания, который после управляется в @ref GPIO_LED_Dynamic_Handle */ OperationStatus GPIO_LED_Fading_Start(GPIO_LEDTypeDef *led, uint32_t period) { if(!led || !led->LED_Port || !led->LED_Pin) return ERROR; led->state = LED_IS_FADING; led->LED_Period = period; return OK; } //uint8_t LED_PWM_FADING_DUTYS[LED_PWM_TICKS] = {0 1 2 3 4 5 6 7 8 9 10 11 12 } /** * @brief Управление динамическими режимами свечения светодиода * @param Указатель на структуру светодиода * @details Функция моргает/плавно моргает светодиодом в неблокирующем режиме * Т.е. функцию надо вызывать постоянно, чтобы она мониторила тики * и в нужный момент переключала светодиод */ void GPIO_LED_Dynamic_Handle(GPIO_LEDTypeDef *led) { if(!led || !led->LED_Port || !led->LED_Pin) return; /* Режим моргания светодиода */ if(led->state == LED_IS_BLINKING) { uint32_t tickcurrent = millis(); /* Ожидание истечения периода моргания */ if((tickcurrent - led->tickprev) > led->LED_Period) { /* Моргание */ GPIO_ToggleBits(led->LED_Port, led->LED_Pin); led->tickprev = tickcurrent; } } /* Режим плавного моргания светодиода */ else if(led->state == LED_IS_FADING) { static unsigned direction = 0; static int duty = 0; uint32_t tickcurrent = millis(); /* Ожидание момента изменения яркости */ /* Период ШИМ 20 мс, поэтому менять яроксть надо 40 раз за период (туда обратно) */ if((tickcurrent - led->tickprev) > led->LED_Period/(LED_PWM_TICKS*2)) { /* Формирование разтухания */ if(direction == 0) { if(++duty >= LED_PWM_TICKS) { direction = 1; duty = LED_PWM_TICKS; } } /* Формирование затухания */ else { if(--duty <= 0) { direction = 0; duty = 0; } } led->tickprev = tickcurrent; } /* Формирование ШИМ для изменения яркости */ int duty_crt = (duty*duty/LED_PWM_TICKS); if(tickcurrent%LED_PWM_TICKS < duty_crt) { GPIO_WriteBit(led->LED_Port, led->LED_Pin, led->LED_ActiveLvl); } else { BitState offstate = (led->LED_ActiveLvl == SET)? CLEAR: SET; GPIO_WriteBit(led->LED_Port, led->LED_Pin, offstate); } } } //-- GPIO Switch functions ----------------------------------------------------- /** * @brief Инициализировать кнопку (структуру кнопки) * @param sw Указатель на структуру кнопки * @param GPIOx Указатель на структуру порта для кнопки * @param GPIO_PIN_X Пин для кнопки * @param SW_ActiveLevel Состояния пина, когда кнопка нажата * @return Operation Status */ OperationStatus GPIO_Switch_Init(GPIO_SwitchTypeDef *sw, GPIO_TypeDef *GPIOx, uint32_t GPIO_PIN_X, BitState SW_ActiveLevel) { if(!sw || !GPIOx || !GPIO_PIN_X) return ERROR; sw->Sw_Port = GPIOx; sw->Sw_Pin = GPIO_PIN_X; sw->Sw_ActiveLvl = SW_ActiveLevel; return OK; } /** * @brief Считать состоянии кнопки * @param sw Указатель на структуру кнопки * @return 1 - если кнопка нажата, * 0 - если отжата, * -1 - если ошибка * @details Функция включает в себя неблокирующую проверку на дребезг * Т.е. функцию надо вызывать постоянно, чтобы она мониторила состояние кнопки */ int GPIO_Read_Switch(GPIO_SwitchTypeDef *sw) { if(!sw || !sw->Sw_Port || !sw->Sw_Pin) return -1; int current_level = (GPIO_ReadBit(sw->Sw_Port, sw->Sw_Pin) == sw->Sw_ActiveLvl); if(sw->Sw_FilterDelay) // если включена защита от дребезга { // Если таймер не запущен и состояние изменилось - запускаем таймер if(sw->tickprev == 0 && current_level != sw->Sw_CurrentState) { sw->tickprev = millis(); } // Если таймер запущен if(sw->tickprev != 0) { // Проверяем, прошел ли достаточный интервал для фильтрации if((millis() - sw->tickprev) >= sw->Sw_FilterDelay) { // Обновляем состояние только если оно все еще отличается if(current_level != sw->Sw_CurrentState) { sw->Sw_CurrentState = current_level; } // Останавливаем таймер в любом случае sw->tickprev = 0; } } } else // если нет защиты от дребезга { sw->Sw_CurrentState = current_level; } return sw->Sw_CurrentState; }