Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* USER CODE BEGIN Header */
- /**
- ******************************************************************************
- * @file : main.c
- * @brief : Main program body (с антишумной обработкой ADC)
- ******************************************************************************
- * @attention
- *
- * © 2025 STMicroelectronics. All rights reserved.
- * This software component is licensed by ST under BSD 3-Clause license,
- * the "License"; You may not use this file except in compliance with the
- * License. You may obtain a copy of the License at:
- * opensource.org/licenses/BSD-3-Clause
- *
- ******************************************************************************
- */
- /* USER CODE END Header */
- /* Includes ------------------------------------------------------------------*/
- #include "main.h"
- /* Private includes ----------------------------------------------------------*/
- /* USER CODE BEGIN Includes */
- #include <stdio.h>
- #include <stdbool.h>
- #include <string.h> // добавим для использования функции strlen
- #include "lcd_shared.h"
- #include "button.h"
- #include "menu.h"
- #include "test_i2c.h"
- #include "eeprom_health.h"
- #include "config_version.h" // CONFIG_VERSION
- #include "app_config.h" // g_cfg (RAM-копия)
- #include "config_defaults.h" // defaults_get, sanitize
- /* USER CODE END Includes */
- /* Private typedef -----------------------------------------------------------*/
- /* USER CODE BEGIN PTD */
- /**
- * @brief Фильтр "скользящее медианное окно" из трёх значений (u16).
- */
- typedef struct {
- uint16_t a, b, c;
- } median3_t;
- /* USER CODE END PTD */
- /* Private define ------------------------------------------------------------*/
- /* USER CODE BEGIN PD */
- #define ADC_REFERENCE_VOLTAGE 1.205f /**< Типовое Vrefint, В (для F1 без калибровки — типовое значение) */
- #define ADC_MAX 0xFFFu /**< 4095: 12-битный АЦП */
- #define UART_OUT // определим вывод по интерфейсу USART
- /** @name Настройки антишумной обработки
- * @{
- */
- #define ADC_OSR 64U /**< Oversampling: число выборок на канал в одном DMA буфере (степень 2) */
- #define ADC_EMA_K 3U /**< EMA: k=3 → α=1/8 (чем больше k — тем плавнее, но медленнее) */
- /** @} */
- /* USER CODE END PD */
- /* Private macro -------------------------------------------------------------*/
- /* USER CODE BEGIN PM */
- #define ARRAY_LEN(x) (sizeof(x)/sizeof((x)[0]))
- /* USER CODE END PM */
- /* Private variables ---------------------------------------------------------*/
- ADC_HandleTypeDef hadc1;
- DMA_HandleTypeDef hdma_adc1;
- I2C_HandleTypeDef hi2c1;
- I2C_HandleTypeDef hi2c2;
- TIM_HandleTypeDef htim4;
- UART_HandleTypeDef huart1;
- /* USER CODE BEGIN PV */
- /* _______________________________________________ ОБЪЯВЛЕН�?Е ПЕРЕМЕННЫХ ________________________________________________________ */
- /** @brief Буфер для преобразования чисел в строку (для LCD/USART). */
- char strBuffer[16]; // буфер для преобразования чисел в строковый вид функцией sprintf
- /** @brief Напряжение питания МК (В) — вычисляется через Vrefint (отфильтрованный). */
- float mcuVoltage = 0;
- /** @brief Последние «мгновенные» отсчёты АЦП (оставлены для совместимости/отладки).
- * Порядок каналов: [0] — VREFINT, [1] — A0, [2] — A1, [3] — A2.
- */
- uint16_t adcData[4] = { 0, }; // 0 - напряж питания . 1 - A0. 2 - A1. 3 - A2
- /** @brief Канальные напряжения (В) — для A0..A2. */
- float adcVoltage_1 = 0;
- float adcVoltage_2 = 0;
- float adcVoltage_3 = 0;
- /* ======= Новые переменные для антишумной обработки (всё задокументировано) ======= */
- /** @brief Большой DMA буфер для oversampling: 4 канала × ADC_OSR выборок (режим Circular). */
- static uint16_t adcDMABuf[4U * ADC_OSR];
- /** @brief Промежуточные суммы oversampling по каждому каналу (набираются между Half/Full колбэками). */
- static uint32_t osSum[4] = { 0, 0, 0, 0 };
- /** @brief Флаг: обработана половина буфера (HalfCplt уже был). */
- static bool osGotHalf = false;
- /** @brief �?стория для медианного фильтра (по 3 значения для каждого канала). */
- static median3_t medHist[4] = { 0 };
- /** @brief Аккумуляторы EMA (u32) по каждому каналу. */
- static uint32_t emaAcc[4] = { 0, 0, 0, 0 };
- /** @brief Отфильтрованные значения АЦП (в отсчётах), после avg → median3 → EMA. */
- static volatile uint16_t adcCountsFilt[4] = { 0, 0, 0, 0 };
- /* _______________________________________________ ОБЪЯВЛЕН�?Е ПЕРЕМЕННЫХ КОНЕЦ _________________________________________________ */
- /* USER CODE END PV */
- /* Private function prototypes -----------------------------------------------*/
- void SystemClock_Config(void);
- static void MX_GPIO_Init(void);
- static void MX_I2C1_Init(void);
- static void MX_USART1_UART_Init(void);
- static void MX_DMA_Init(void);
- static void MX_ADC1_Init(void);
- static void MX_TIM4_Init(void);
- static void MX_I2C2_Init(void);
- /* USER CODE BEGIN PFP */
- // _______________________________________________ ОБЪЯВЛЕН�?Е ФУНКЦ�?Й ________________________________________________________
- /**
- * @brief Получим значения ADC и занесем их в переменные (используя отфильтрованные отсчёты).
- */
- void GetADCData(void);
- /**
- * @brief Выведем на дисплей данные «основного экрана».
- * @note ТВОЙ оригинальный код — сохранён без изменений.
- */
- void MainViewOutputOnLCD(void);
- /* --- Вспомогательные для антишума --- */
- /**
- * @brief Обработать сегмент DMA-буфера: суммирование по каналам.
- * @param ptr Указатель на начало сегмента (uint16_t*).
- * @param length Длина сегмента (кол-во элементов).
- * @note Данные в буфере идут циклом: VREF, A0, A1, A2, VREF, A0, ...
- */
- static void ADC_ProcessSegment(uint16_t *ptr, uint32_t length);
- /**
- * @brief Медиана из трёх (u16).
- */
- static inline uint16_t median3_u16(uint16_t a, uint16_t b, uint16_t c);
- /**
- * @brief Шаг EMA без float: y += (x - y) >> k.
- * @param acc Аккумулятор (u32), хранит текущий «y».
- * @param x Новое значение (u16).
- * @param k Параметр сглаживания (3 → 1/8; 4 → 1/16 и т.д.).
- * @return Текущее значение EMA (u16).
- */
- static inline uint16_t ema_step_u16(uint32_t *acc, uint16_t x, uint8_t k);
- // _______________________________________________ ОБЪЯВЛЕН�?Е ФУНКЦ�?Й КОНЕЦ __________________________________________________
- /* USER CODE END PFP */
- /* Private user code ---------------------------------------------------------*/
- /* USER CODE BEGIN 0 */
- int _write(int32_t file, uint8_t *ptr, int32_t len) {
- for (int i = 0; i < len; i++) {
- ITM_SendChar(*ptr++);
- }
- return len;
- }
- void HAL_SYSTICK_Callback(void) {
- Button_Update(); // тик кнопок раз в 1 мс
- }
- /* USER CODE END 0 */
- /**
- * @brief The application entry point.
- * @retval int
- */
- int main(void) {
- /* USER CODE BEGIN 1 */
- /* USER CODE END 1 */
- /* MCU Configuration--------------------------------------------------------*/
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
- /* USER CODE BEGIN Init */
- /* USER CODE END Init */
- /* Configure the system clock */
- SystemClock_Config();
- /* USER CODE BEGIN SysInit */
- /* USER CODE END SysInit */
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- MX_I2C1_Init();
- MX_USART1_UART_Init();
- MX_DMA_Init();
- MX_ADC1_Init();
- MX_TIM4_Init();
- MX_I2C2_Init();
- /* USER CODE BEGIN 2 */
- HAL_TIM_Base_Start_IT(&htim4);
- // �?нициализация кнопок и меню
- Button_Init();
- HAL_ADCEx_Calibration_Start(&hadc1);
- /**
- * @brief Стартуем ADC+DMA в режим circular с большим буфером для oversampling.
- * @note Раньше было: HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adcData, 4);
- * Теперь: HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcDMABuf, 4*ADC_OSR);
- * Порядок данных в буфере сохраняется: Vref, A0, A1, A2, Vref, A0, ...
- */
- HAL_ADC_Start_DMA(&hadc1, (uint32_t*) adcDMABuf, (uint32_t) (4U * ADC_OSR));
- I2C_LCD_Init(MyI2C_LCD);
- I2C_LCD_CreateCustomChar(MyI2C_LCD, 0, gradus);
- I2C_LCD_CreateCustomChar(MyI2C_LCD, 1, blinking);
- I2C_LCD_Clear(MyI2C_LCD);
- /* USER CODE END 2 */
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- /**
- * @brief Главный цикл программы
- * @note Здесь выполняется вся логика:
- * - обработка кнопок (Button_Process)
- * - мигание символом на экране (BlinkSymbol)
- * - периодическое обновление данных с датчиков и вывод на LCD
- */
- static uint32_t lastUpdate = 0; /**< метка времени последнего обновления */
- static bool needInstantRefresh = true; /**< флаг мгновенного обновления после возврата из меню */
- // проведем тест памяти
- {
- extern I2C_HandleTypeDef hi2c2; // твой I2C для EEPROM (он уже есть в проекте)
- const uint8_t EE_ADDR_7BIT = 0x50;
- EE_HealthInfo info;
- (void) Eeprom_CheckAndAnnounce(&hi2c2, EE_ADDR_7BIT, CONFIG_VERSION,
- &info);
- HAL_Delay(7000); // опционально, чтобы прочитать статус на LCD
- I2C_LCD_Clear(MyI2C_LCD);
- }
- /* 2) Загрузка конфигурации в рабочую RAM-структуру g_cfg */
- uint8_t used_slot = 0, read_ver = 0;
- if (!config_store_load(CONFIG_VERSION, &g_cfg, &used_slot, &read_ver)) {
- /* если вдруг вся память мертвая
- * Ни один слот не валиден
- * если версии не совпали
- * → работаем на дефолтах из config_defaults.h
- * Нет валидной записи этой версии → берём дефолты и записываем */
- if (!config_defaults_get(CONFIG_VERSION, &g_cfg)) {
- /* fallback на v1, если когда-нибудь будет больше версий */
- // (void)config_defaults_get(CONFIG_VERSION, &g_cfg); // работало в версии архива 10.1
- /* fallback на минимальные дефолты из хранилища */
- config_store_fill_defaults(&g_cfg);
- }
- config_sanitize(&g_cfg);
- /* Желательно сразу засевать EEPROM дефолтами (если EEPROM доступна) */
- (void) config_store_save(CONFIG_VERSION, &g_cfg);
- /* Сообщение пользователю */
- I2C_LCD_SetCursor(MyI2C_LCD, 0, 2);
- I2C_LCD_WriteString(MyI2C_LCD, RuChar("Defaults used"));
- HAL_Delay(7000); // опционально, чтобы прочитать статус на LCD
- I2C_LCD_Clear(MyI2C_LCD);
- }
- // включим питение реле , управляющим контактом горелки
- // если температура превысит доп параметры - пин надо снять
- // в таком случае работа сварочной горелки будет заблокирована
- // и свап будет остывать со включенным вентилятором
- HAL_GPIO_WritePin(GPIOB, BLOCK_WORK_Pin, GPIO_PIN_SET);
- while (1) {
- /** Обрабатываем кнопки (события из Button_Update) */
- Button_Process();
- Menu_Tick();
- /** Моргаем символом на дисплее (не блокирующий, сам проверяет main_monitor) */
- BlinkSymbol();
- /**
- * Если у нас работает главная страница монитора (вне меню) —
- * обновляем данные и выводим их на LCD с периодом 1500 мс.
- */
- uint32_t now = HAL_GetTick();
- if (main_monitor) {
- if (needInstantRefresh || (now - lastUpdate >= 1500U)) {
- lastUpdate = now;
- needInstantRefresh = false;
- /** Получаем значения температурных датчиков (уже сглаженные) */
- GetADCData();
- /** Выводим на дисплей основной текст (ТВОЙ код — без изменений) */
- MainViewOutputOnLCD();
- }
- } else {
- /**
- * Если активировано меню — «армируем» мгновенное обновление,
- * чтобы при возврате на монитор обновление произошло сразу.
- */
- needInstantRefresh = true;
- }
- /**
- * Лёгкий idle без блокировок: ждём прерываний (SysTick, DMA и т.д.)
- * Экономит CPU и энергию.
- */
- __WFI();
- /* USER CODE END WHILE */
- /* USER CODE BEGIN 3 */
- #ifdef UART_OUT
- // Пример разового вывода: sprintf(strBuffer, "Vdd=%.3fV A0=%.3fV\r\n", mcuVoltage, adcVoltage_1);
- // HAL_UART_Transmit_IT(&huart1, (uint8_t*)strBuffer, strlen(strBuffer));
- #endif
- }
- /* USER CODE END 3 */
- }
- /**
- * @brief System Clock Configuration
- * @retval None
- */
- void SystemClock_Config(void) {
- RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
- RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };
- RCC_PeriphCLKInitTypeDef PeriphClkInit = { 0 };
- /** Initializes the RCC Oscillators according to the specified parameters
- * in the RCC_OscInitTypeDef structure.
- */
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
- RCC_OscInitStruct.HSEState = RCC_HSE_ON;
- RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
- RCC_OscInitStruct.HSIState = RCC_HSI_ON;
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
- RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
- if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
- Error_Handler();
- }
- /** Initializes the CPU, AHB and APB buses clocks
- */
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
- | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
- if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
- Error_Handler();
- }
- PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
- PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
- if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
- Error_Handler();
- }
- }
- /**
- * @brief ADC1 Initialization Function
- * @param None
- * @retval None
- */
- static void MX_ADC1_Init(void) {
- /* USER CODE BEGIN ADC1_Init 0 */
- /* USER CODE END ADC1_Init 0 */
- ADC_ChannelConfTypeDef sConfig = { 0 };
- /* USER CODE BEGIN ADC1_Init 1 */
- /* USER CODE END ADC1_Init 1 */
- /** Common config
- */
- hadc1.Instance = ADC1;
- hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
- hadc1.Init.ContinuousConvMode = ENABLE;
- hadc1.Init.DiscontinuousConvMode = DISABLE;
- hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
- hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
- hadc1.Init.NbrOfConversion = 4;
- if (HAL_ADC_Init(&hadc1) != HAL_OK) {
- Error_Handler();
- }
- /** Configure Regular Channel
- */
- sConfig.Channel = ADC_CHANNEL_VREFINT;
- sConfig.Rank = ADC_REGULAR_RANK_1;
- sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
- if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
- Error_Handler();
- }
- /** Configure Regular Channel
- */
- sConfig.Channel = ADC_CHANNEL_0;
- sConfig.Rank = ADC_REGULAR_RANK_2;
- if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
- Error_Handler();
- }
- /** Configure Regular Channel
- */
- sConfig.Channel = ADC_CHANNEL_1;
- sConfig.Rank = ADC_REGULAR_RANK_3;
- if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
- Error_Handler();
- }
- /** Configure Regular Channel
- */
- sConfig.Channel = ADC_CHANNEL_2;
- sConfig.Rank = ADC_REGULAR_RANK_4;
- if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
- Error_Handler();
- }
- /* USER CODE BEGIN ADC1_Init 2 */
- // На F1 VREFINT включает TSVREFE; HAL делает это автоматически при выборе ADC_CHANNEL_VREFINT.
- /* USER CODE END ADC1_Init 2 */
- }
- /**
- * @brief I2C1 Initialization Function
- * @param None
- * @retval None
- */
- static void MX_I2C1_Init(void) {
- /* USER CODE BEGIN I2C1_Init 0 */
- /* USER CODE END I2C1_Init 0 */
- /* USER CODE BEGIN I2C1_Init 1 */
- /* USER CODE END I2C1_Init 1 */
- hi2c1.Instance = I2C1;
- hi2c1.Init.ClockSpeed = 100000;
- hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
- hi2c1.Init.OwnAddress1 = 0;
- hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
- hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
- hi2c1.Init.OwnAddress2 = 0;
- hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
- hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
- if (HAL_I2C_Init(&hi2c1) != HAL_OK) {
- Error_Handler();
- }
- /* USER CODE BEGIN I2C1_Init 2 */
- /* USER CODE END I2C1_Init 2 */
- }
- /**
- * @brief I2C2 Initialization Function
- * @param None
- * @retval None
- */
- static void MX_I2C2_Init(void) {
- /* USER CODE BEGIN I2C2_Init 0 */
- /* USER CODE END I2C2_Init 0 */
- /* USER CODE BEGIN I2C2_Init 1 */
- /* USER CODE END I2C2_Init 1 */
- hi2c2.Instance = I2C2;
- hi2c2.Init.ClockSpeed = 100000;
- hi2c2.Init.DutyCycle = I2C_DUTYCYCLE_2;
- hi2c2.Init.OwnAddress1 = 0;
- hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
- hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
- hi2c2.Init.OwnAddress2 = 0;
- hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
- hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
- if (HAL_I2C_Init(&hi2c2) != HAL_OK) {
- Error_Handler();
- }
- /* USER CODE BEGIN I2C2_Init 2 */
- /* USER CODE END I2C2_Init 2 */
- }
- /**
- * @brief TIM4 Initialization Function
- * @param None
- * @retval None
- */
- static void MX_TIM4_Init(void) {
- /* USER CODE BEGIN TIM4_Init 0 */
- /* USER CODE END TIM4_Init 0 */
- TIM_ClockConfigTypeDef sClockSourceConfig = { 0 };
- TIM_MasterConfigTypeDef sMasterConfig = { 0 };
- /* USER CODE BEGIN TIM4_Init 1 */
- /* USER CODE END TIM4_Init 1 */
- htim4.Instance = TIM4;
- htim4.Init.Prescaler = 7200 - 1;
- htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
- htim4.Init.Period = 10000 - 1;
- htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
- htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
- if (HAL_TIM_Base_Init(&htim4) != HAL_OK) {
- Error_Handler();
- }
- sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
- if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK) {
- Error_Handler();
- }
- sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
- sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
- if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig)
- != HAL_OK) {
- Error_Handler();
- }
- /* USER CODE BEGIN TIM4_Init 2 */
- /* USER CODE END TIM4_Init 2 */
- }
- /**
- * @brief USART1 Initialization Function
- * @param None
- * @retval None
- */
- static void MX_USART1_UART_Init(void) {
- /* USER CODE BEGIN USART1_Init 0 */
- /* USER CODE END USART1_Init 0 */
- /* USER CODE BEGIN USART1_Init 1 */
- /* USER CODE END USART1_Init 1 */
- huart1.Instance = USART1;
- huart1.Init.BaudRate = 9600;
- huart1.Init.WordLength = UART_WORDLENGTH_8B;
- huart1.Init.StopBits = UART_STOPBITS_1;
- huart1.Init.Parity = UART_PARITY_NONE;
- huart1.Init.Mode = UART_MODE_TX_RX;
- huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
- huart1.Init.OverSampling = UART_OVERSAMPLING_16;
- if (HAL_UART_Init(&huart1) != HAL_OK) {
- Error_Handler();
- }
- /* USER CODE BEGIN USART1_Init 2 */
- /* USER CODE END USART1_Init 2 */
- }
- /**
- * Enable DMA controller clock
- */
- static void MX_DMA_Init(void) {
- /* DMA controller clock enable */
- __HAL_RCC_DMA1_CLK_ENABLE();
- /* DMA interrupt init */
- /* DMA1_Channel1_IRQn interrupt configuration */
- HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
- HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
- }
- /**
- * @brief GPIO Initialization Function
- * @param None
- * @retval None
- */
- static void MX_GPIO_Init(void) {
- GPIO_InitTypeDef GPIO_InitStruct = { 0 };
- /* GPIO Ports Clock Enable */
- __HAL_RCC_GPIOD_CLK_ENABLE();
- __HAL_RCC_GPIOA_CLK_ENABLE();
- __HAL_RCC_GPIOB_CLK_ENABLE();
- /*Configure GPIO pin Output Level */
- HAL_GPIO_WritePin(GPIOB, LedControl_Pin | BLOCK_WORK_Pin, GPIO_PIN_RESET);
- /*Configure GPIO pins : bUp_Pin bLeft_Pin bCenter_Pin */
- GPIO_InitStruct.Pin = bUp_Pin | bLeft_Pin | bCenter_Pin;
- GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
- /*Configure GPIO pins : bRight_Pin bDown_Pin */
- GPIO_InitStruct.Pin = bRight_Pin | bDown_Pin;
- GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
- /*Configure GPIO pins : LedControl_Pin BLOCK_WORK_Pin */
- GPIO_InitStruct.Pin = LedControl_Pin | BLOCK_WORK_Pin;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
- }
- /* USER CODE BEGIN 4 */
- /* _______________________________________________ ОПРЕДЕЛЕН�?Е ФУНКЦ�?Й _________________________________________________________ */
- /**
- * @brief TIM периодический колбэк: контрольный LED (1 раз в 2 сек).
- */
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
- if (htim->Instance == TIM4) //TIM4 управляет контрольным светодиодом , если все норм - светодиод моргает 1 раз в 2 сек
- {
- HAL_GPIO_TogglePin(LedControl_GPIO_Port, LedControl_Pin);
- }
- }
- /**
- * @brief Колбэк DMA: половина буфера заполнена.
- * @details Обнуляем суммы и накапливаем первую половину буфера.
- */
- void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {
- if (hadc->Instance != ADC1)
- return;
- osSum[0] = osSum[1] = osSum[2] = osSum[3] = 0;
- /* половина буфера = 2*ADC_OSR элементов (последовательность Vref,A0,A1,A2,...) */
- ADC_ProcessSegment(&adcDMABuf[0], (uint32_t) (2U * ADC_OSR));
- osGotHalf = true;
- }
- /**
- * @brief Колбэк DMA: буфер заполнен целиком.
- * @details Докапливаем вторую половину; затем считаем средние, медиану и EMA.
- */
- void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
- if (hadc->Instance != ADC1)
- return;
- ADC_ProcessSegment(&adcDMABuf[2U * ADC_OSR], (uint32_t) (2U * ADC_OSR));
- if (osGotHalf) {
- osGotHalf = false;
- /* 1) Среднее по каждому каналу */
- uint16_t avg[4];
- for (int ch = 0; ch < 4; ch++) {
- avg[ch] = (uint16_t) (osSum[ch] / ADC_OSR);
- }
- /* 2) Медиана из трёх (устранение одиночных выбросов) */
- uint16_t med[4];
- for (int ch = 0; ch < 4; ch++) {
- median3_t *mh = &medHist[ch];
- med[ch] = median3_u16(mh->a, mh->b, avg[ch]);
- mh->a = mh->b;
- mh->b = avg[ch];
- mh->c = med[ch];
- }
- /* 3) EMA без float (мягкое сглаживание) */
- for (int ch = 0; ch < 4; ch++) {
- adcCountsFilt[ch] = ema_step_u16(&emaAcc[ch], med[ch], ADC_EMA_K);
- }
- /* 4) Обновим «последние сырые 4» для совместимости/отладки */
- for (int ch = 0; ch < 4; ch++) {
- adcData[ch] = avg[ch];
- }
- }
- }
- /**
- * @brief Получим значения ADC и занесем их в переменные ...
- * @note �?СПОЛЬЗУЕМ ОТФ�?ЛЬТРОВАННЫЕ counts: adcCountsFilt[0..3].
- */
- void GetADCData() {
- /* Vdd через Vrefint: Vdd = Vref_typ * 4095 / ADC(Vrefint) */
- uint16_t vref_cnt = adcCountsFilt[0];
- if (vref_cnt == 0)
- vref_cnt = 1; // защита от деления на ноль
- mcuVoltage = (float) ADC_MAX * ADC_REFERENCE_VOLTAGE / (float) vref_cnt;
- /* Каналы A0..A2 в В: Vin = ADC * Vdd / 4095 */
- adcVoltage_1 = (float) adcCountsFilt[1] * mcuVoltage / (float) ADC_MAX;
- adcVoltage_2 = (float) adcCountsFilt[2] * mcuVoltage / (float) ADC_MAX;
- adcVoltage_3 = (float) adcCountsFilt[3] * mcuVoltage / (float) ADC_MAX;
- }
- /**
- * @brief Вывод «основного» экрана на LCD 20×4.
- * @details
- * Размещение по строкам (индексы колонок 0..19):
- * - Строка 0: "U питания =" (0..10), значение Vdd шириной 6 символов (12..17), символ 'v' в колонке 18,
- * колонка 19 — под мигающий индикатор BlinkSymbol().
- * - Строки 1..3: метки "T транс =", "T диода =", "T тирист =" (0..10),
- * значение шириной 5 символов (12..16), затем градус (17) и 'C' (18).
- *
- * Формат чисел:
- * - Vdd: %6.3f → всегда 6 символов (например, " 3.300" или "12.345")
- * - Темп-ры: %5.1f → всегда 5 символов (например, " 25.0" или "123.4")
- *
- * Важно:
- * - Убраны завершающие пробелы в форматах, чтобы не оставались «хвосты» старых символов.
- * - Позиции подобраны так, чтобы число и единицы измерения не налезали друг на друга.
- * - Мигающий индикатор занимает (19,0), поэтому символ напряжения 'v' ставим в (18,0).
- */
- void MainViewOutputOnLCD(void) {
- /* ---------- Строка 0: напряжение питания Vdd ---------- */
- I2C_LCD_SetCursor(MyI2C_LCD, 0, 0);
- I2C_LCD_WriteString(MyI2C_LCD, RuChar("U питания =")); // колонки 0..10
- // Число фиксированной ширины: 6 символов в колонках 12..17
- snprintf(strBuffer, sizeof(strBuffer), "%6.3f", mcuVoltage);
- I2C_LCD_SetCursor(MyI2C_LCD, 12, 0);
- I2C_LCD_WriteString(MyI2C_LCD, strBuffer);
- // Единица измерения — 'v' в колонке 18 (19-я колонка занята под BlinkSymbol)
- I2C_LCD_SetCursor(MyI2C_LCD, 18, 0);
- I2C_LCD_WriteString(MyI2C_LCD, "v");
- /* ---------- Строка 1: температура трансформатора (A0) ---------- */
- I2C_LCD_SetCursor(MyI2C_LCD, 0, 1);
- I2C_LCD_WriteString(MyI2C_LCD, RuChar("T транс =")); // колонки 0..8..10 (как у тебя)
- // Температура = adcVoltage_1 * 100 (как у тебя в исходниках), ширина 5 символов: 12..16
- snprintf(strBuffer, sizeof(strBuffer), "%5.1f", adcVoltage_1 * 100.0f);
- I2C_LCD_SetCursor(MyI2C_LCD, 12, 1);
- I2C_LCD_WriteString(MyI2C_LCD, strBuffer);
- // Градус (кастомный символ 0) в колонке 17, затем 'C' в 18
- I2C_LCD_SetCursor(MyI2C_LCD, 17, 1);
- I2C_LCD_PrintCustomChar(MyI2C_LCD, 0);
- I2C_LCD_SetCursor(MyI2C_LCD, 18, 1);
- I2C_LCD_WriteString(MyI2C_LCD, "C");
- /* ---------- Строка 2: температура диода (A1) ---------- */
- I2C_LCD_SetCursor(MyI2C_LCD, 0, 2);
- I2C_LCD_WriteString(MyI2C_LCD, RuChar("T диода ="));
- snprintf(strBuffer, sizeof(strBuffer), "%5.1f", adcVoltage_2 * 100.0f);
- I2C_LCD_SetCursor(MyI2C_LCD, 12, 2);
- I2C_LCD_WriteString(MyI2C_LCD, strBuffer);
- I2C_LCD_SetCursor(MyI2C_LCD, 17, 2);
- I2C_LCD_PrintCustomChar(MyI2C_LCD, 0);
- I2C_LCD_SetCursor(MyI2C_LCD, 18, 2);
- I2C_LCD_WriteString(MyI2C_LCD, "C");
- /* ---------- Строка 3: температура тиристора (A2) ---------- */
- I2C_LCD_SetCursor(MyI2C_LCD, 0, 3);
- I2C_LCD_WriteString(MyI2C_LCD, RuChar("T тирист ="));
- snprintf(strBuffer, sizeof(strBuffer), "%5.1f", adcVoltage_3 * 100.0f);
- I2C_LCD_SetCursor(MyI2C_LCD, 12, 3);
- I2C_LCD_WriteString(MyI2C_LCD, strBuffer);
- I2C_LCD_SetCursor(MyI2C_LCD, 17, 3);
- I2C_LCD_PrintCustomChar(MyI2C_LCD, 0);
- I2C_LCD_SetCursor(MyI2C_LCD, 18, 3);
- I2C_LCD_WriteString(MyI2C_LCD, "C");
- }
- /* ======================= Вспомогательные (static) ======================= */
- /**
- * @brief Просуммировать значения по каждому каналу в сегменте DMA.
- * @param ptr Указатель на начало сегмента.
- * @param length Длина сегмента (кол-во uint16_t элементов).
- * @details Данные идут циклом по каналам: [0]VREF, [1]A0, [2]A1, [3]A2, ...
- */
- static void ADC_ProcessSegment(uint16_t *ptr, uint32_t length) {
- for (uint32_t i = 0; i < length; i += 4U) {
- osSum[0] += ptr[i + 0];
- osSum[1] += ptr[i + 1];
- osSum[2] += ptr[i + 2];
- osSum[3] += ptr[i + 3];
- }
- }
- /**
- * @brief Медиана из трёх (u16).
- */
- static inline uint16_t median3_u16(uint16_t a, uint16_t b, uint16_t c) {
- if (a > b) {
- uint16_t t = a;
- a = b;
- b = t;
- }
- if (b > c) {
- uint16_t t = b;
- b = c;
- c = t;
- }
- if (a > b) {
- uint16_t t = a;
- a = b;
- b = t;
- }
- return b;
- }
- /**
- * @brief Шаг EMA без float: y += (x - y) >> k.
- */
- static inline uint16_t ema_step_u16(uint32_t *acc, uint16_t x, uint8_t k) {
- if (*acc == 0) {
- *acc = x;
- } // инициализация
- int32_t diff = (int32_t) x - (int32_t) (*acc);
- *acc += (diff >> k);
- return (uint16_t) (*acc & 0xFFFF);
- }
- /* _______________________________________________ ОПРЕДЕЛЕН�?Е ФУНКЦ�?Й КОНЕЦ _________________________________________________ */
- /* USER CODE END 4 */
- /**
- * @brief This function is executed in case of error occurrence.
- * @retval None
- */
- void Error_Handler(void) {
- /* USER CODE BEGIN Error_Handler_Debug */
- /* User can add his own implementation to report the HAL error return state */
- __disable_irq();
- while (1) {
- }
- /* USER CODE END Error_Handler_Debug */
- }
- #ifdef USE_FULL_ASSERT
- /**
- * @brief Reports the name of the source file and the source line number
- * where the assert_param error has occurred.
- * @param file: pointer to the source file name
- * @param line: assert_param error line source number
- * @retval None
- */
- void assert_failed(uint8_t *file, uint32_t line)
- {
- /* USER CODE BEGIN 6 */
- (void)file; (void)line;
- /* USER CODE END 6 */
- }
- #endif /* USE_FULL_ASSERT */
- /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
Add Comment
Please, Sign In to add comment