418 lines
16 KiB
C
418 lines
16 KiB
C
/**
|
||
******************************************************************************
|
||
* @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;
|
||
}
|