
Привет, Хабр!
Автономные системы становятся все более востребованными — от портативных приборов до сложных промышленных комплексов — надежное управление напряжением питания превращается в ключевой фактор их долговечности и эффективности. Сердце любой такой системы — аккумулятор, а его безопасность и срок службы, напрямую зависит от контроля напряжения.
В данной статье будет представлен пример контроля напряжения, над блоком питания — внутри которого (никель-металлгидридная аккумуляторная сборка NiMH 14.4В/12 банок по 1.2В(1.4В- при полной зарядке)).
В блоке питания уже есть палата управления над аккумулятором, которая выполняет задачи:
-
Работа с кнопкой;
-
Работа со светодиодом;
-
Работа с пъезоэлектрическим излучателем(звуковая индикация);
-
Контроль заряда/разряда аккумулятора(дает звуковой сигнал при напряжении менее 9 вольт и более 14).
В процессе анализа и статистики использования оборудования стало очевидно, что многие пользователи часто забывают своевременно отключать блоки питания. В результате аккумуляторные сборки продолжают разряжаться даже при отсутствии необходимости, напряжение падает до критических значений, и аккумулятор быстро теряет свою емкость, становясь непригодным для дальнейшей эксплуатации.
Помимо этого, подключенная система оказывается в неопределенном состоянии, на плату управления и различные датчики по-прежнему подается питание, что приводит к избыточной нагрузке. В качестве этого сокращается ресурс электронных компонентов и снижается надежность всего устройства.
Для решения данной проблемы я продемонстрирую пример системы контроля напряжения блока питания, в качестве микроконтроллера выбран STM32F103С8T6, который выполняет следующие задачи:
-
Непрерывный мониторинг напряжения аккумуляторной сборки, измерение производится через АЦП с использованием DMA;
-
Оповещение пользователя о низком заряде, при падении напряжения ниже установленного порога (в данном примере — 9.0В) система активизирует звуковой сигнал, время работы оповещения ограничено — звуковая индикация будет длится 5 минут;
-
Переход в энергосберегающий режим, если напряжение остается ниже порога и пользователь не предпринял действий в течении заданного времени, микроконтроллер переводит систему в режим сна, это сопровождается отключением тактирования и всей периферии, что минимизирует и предотвращает глубокий разряд аккумулятора.
Схема подключения NiMH АКБ, делителя напряжения к АЦП МК, кнопки вкл/выкл и пъезо-излучателя

Перечень компонентов
|
|
|
|
|
|
|
R1, R3, R4, R5, R9- (0805 — 10 кОм ± 5%) |
С1-0805 — X7R – 50 В – 0,1 мкФ |
FP1, FP2, FP3- MF-SM100/33-1.1А |
VT1, VT3-IRF7416, P-канал |
МК STM32F103C8T6 |
|
R2- (0805 — 20 кОм ± 5%) |
|
|
VT2, VT4-BC847C |
|
|
R6-(0805 – 221 кОм ± 5%) |
|
|
AD4-BAT54S |
BA-1-Излучатель пьезоэлектрический HCM1206X |
|
R7-(0805 — 27 кОм ± 5%) |
|
|
|
Аккумуляторная сборка NiMh 14.4V |
|
R8-Резистор подстроечный (3314G-1-502E, 5 кОм |
|
|
|
SA2- Выключатель 113-8748 |
Объяснение схемы
Узел[1]
-
Входной силовой ключ, исток подключен к +12V, сток идет к блоку питания(+12_АКК) через предохранители, а затвор подтянут к земле, применение (защищенная подача напряжения питания с аккумуляторного блока);
Узел[2]
-
Вторичный силовой ключ, обеспечивает управляемое включение/отключение напряжения питания основной нагрузки системы, управление происходит через МК, сигнал PWR_ON, после включения данного узла, на стоке напряжение питания +12ВК активизируется и передает напряжение другим частям схемы, в моем примере это (узел[4]-Звуковая индикация и узел[5]-lделитель напряжения), но также можно использовать данный узел и на включение преобразователей напряжения;
Узел[3]
-
Данный узел обеспечивает логику взаимодействия с кнопкой включения/выключения, кнопка sa2, при нажатии формирует управляющий сигнал, диод АD4 и конденсатор С1 обеспечивают фильтрацию и антидребезг.
Узел[4]
-
Данный узел обеспечивает звуковую индикацию, сигнал BEEP подключается к МК;
Узел[5]
Данный узел является делителем напряжения, делит напряжение до уровня, подходящего для измерения АЦП МК (обычно до 3.3V), подстроечный резистор R8, выставлен на 1.7кОм.
Настройка микроконтроллера STM32F103 в CubeIDE

Конфигурация TIM1(PA11)
Таймер TIM1 выполняет роль генератора для звуковой индикации.
Настройка таймера:
-
Предделитель (Prescaler) и период (Auto-Reload) выбраны так, чтобы на выходе формировался сигнал с частотой в диапазоне, воспринимаемом слухом (обычно 1–5 кГц);
-
Режим работы – PWM (широтно-импульсная модуляция);
-
Коэффициент заполнения (Pulse) определяет громкость и характер звучания.
Принцип работы:
Таймер генерирует ШИМ-сигнал, который подаётся на транзисторный ключ. Транзистор управляет пьезоизлучателем или динамиком. В результате получается слышимый звук.
Преимущества такого решения:
-
Микроконтроллеру не нужно вручную формировать частоту – этим занимается таймер;
-
Легко изменять тональность: достаточно переписать значения ARR/PSC;
-
Можно реализовать разные звуковые эффекты (короткие сигналы, мелодии) простым управлением таймером из программы.

Конфигурация ADC(PA1)
В проекте используется многоканальный режим работы АЦП с двумя каналами в последовательности (Regular conversion sequence).
Rank 1 – внешний канал (ADC Channel 1).
Этот вход подключён к делителю напряжения и используется для измерения напряжения аккумулятора.
Благодаря этому микроконтроллер может в реальном времени контролировать состояние питания устройства.
Rank 2 – внутренний канал (Vrefint).
Это встроенный источник опорного напряжения микроконтроллера. Он служит для автоматической калибровки и компенсации возможных изменений питающего напряжения. С его помощью можно более точно измерять значение внешних сигналов, в том числе напряжение аккумулятора.
Для повышения эффективности задействован DMA: результаты обоих измерений (Rank 1 и Rank 2) автоматически передаются в память, а процессор получает только готовые данные.

Для правильной настройки ADC я воспользовался данной информацией, там подробно расписано как работать с ADC МК-STM32.
Конфигурация пина для работы с кнопкой
Для работы с кнопкой выбран вывод PB11, сконфигурированный в режиме:
-
GPIO_EXTI – внешний прерывающий вход. Это значит, что нажатие кнопки обрабатывается не опросом в цикле, а через аппаратное прерывание;
-
Mode — External interrupt, Falling edge trigger – прерывание срабатывает по спаду сигнала (при замыкании кнопки на землю);
-
Pull-up – включен внутренний подтягивающий резистор, который удерживает вход в состоянии логической «1», пока кнопка не нажата.
Кнопка подключена так, что в обычном состоянии на входе PB11 присутствует логическая «1» благодаря встроенному подтягивающему резистору (Pull-up). При нажатии контакт замыкается на землю, формируется логический «0» и происходит спад сигнала. Этот спад фиксируется модулем EXTI, который вызывает прерывание.

Конфигурация пина для работы с сигналом PWR_ON
Сигнал PWR_ON играет роль электронного «выключателя питания».
В исходном состоянии (Low) нагрузка обесточена.
При активации (перевод вывода в High) силовой MOSFET открывается, и напряжение +12ВК подаётся на остальные узлы системы.
В примере данная линия питает:
узел [4] – звуковую индикацию,
узел [5] – делитель напряжения для мониторинга питания.
Аналогично этот узел можно использовать и для включения DC/DC-преобразователей или других модулей, требующих управляемого питания.

Конфигурация Clock

Реализация программного кода
Ссылка на скачивание исходного кода [ https://t.me/ChipCraft В закрепленном сообщении [ #исскуствомк_исходный_код —Исходный код для Adc_VoltageControl_STM32F103C8T6]
Заголовочный файл keys.h (работа с кнопками)
В данном файле определены:
-
Функции работы с кнопками;
-
Битовые маски состояний;
-
константы для различных сценариев нажатий.
keys.h
#ifndef INC_PROJECT_BU_KEYS_H_ #define INC_PROJECT_BU_KEYS_H_ unsigned short getKeyState(void); //выдаёт состояние пинов кнопок в данный момент без учёта задержек для дребезга unsigned short getKeyPinState_AtNow(void); void keysDrv_Handler(void);//эту функцию нужно вызывать постоянно #ifndef KEY1_Drv #define KEY1_Drv1u //кнопка нажата #endif #ifndef KEY1Double_Click_Drv #define KEY1Double_Click_Drv #endif #ifndef KEY3_Drv #define KEY3_Drv4u #endif #ifndef KEY4_Drv #define KEY4_Drv8u #endif #ifndef KEY1Hold_Drv #define KEY1Hold_Drv16u // кнопка удерживается #endif #ifndef KEY2Hold_Drv #define KEY2Hold_Drv32u #endif #ifndef KEY3Hold_Drv #define KEY3Hold_Drv64u #endif #ifndef KEY4Hold_Drv #define KEY4Hold_Drv128u #endif #ifndef KEY1Release_Drv #define KEY1Release_Drv256u //Бит отпускания кнопки #endif #ifndef KEY2Release_Drv #define KEY2Release_Drv512u #endif #ifndef KEY3Release_Drv #define KEY3Release_Drv1024u #endif #ifndef KEY4Release_Drv #define KEY4Release_Drv2048u #endif #endif /* INC_PROJECT_BU_KEYS_H_ */
Реализация модуля keys.c (работа с кнопками)
Данный модуль включает в себя задачи:
-
Устранение дребезга контактов;
-
Различие между коротким и долгим нажатием;
-
Отслеживание событий нажатия, удержания и отпускания.
keysDrv_Handler()
Вызывается постоянно в основном цикле или из системного таймера.
Нажатие кнопки (первичное событие)
2.if(gl_kDrv_key1_blockEvent == 0 && ON_OFFB_state == KEY_PRESS) { 3. gl_kDrv_key1_blockEvent = 1; 4. gl_kDrv_time_key1_press = ms; 5.}
Сохраняется время нажатия, дальнейшие события блокируются до отпускания
Фильтр дребезга
else if(gl_kDrv_key1_blockEvent == 1 && ON_OFFB_state==KEY_PRESS && gl_kDrv_key1_short_state==0 && (ms - gl_kDrv_time_key1_press) > DELAY4TIMER){ //прошло время защиты от дребезга, кнопка нажата gl_kDrv_key1_short_state=1; keyState &= ~KEY1Release_Drv;//снимаем бит отпускания кнопки keyState |= KEY1_Drv; }
Если прошло больше DELAY4TIMER, считаем кнопку реально нажатой.
Определение удержания
else if(gl_kDrv_key1_blockEvent == 1 && ON_OFFB_state==KEY_PRESS && gl_kDrv_key1_short_state==1 && (ms - gl_kDrv_time_key1_press) > DELAY_HOLD_TIMER){ keyState |= KEY1Hold_Drv; }
Если прошло больше DELAY_HOLD_TIMER, выставляем бит удержания.
Отпускание кнопки
else if(gl_kDrv_key1_blockEvent == 1 && ON_OFFB_state==KEY_UNPRESS && gl_kDrv_key1_short_state==1){ gl_kDrv_key1_blockEvent=0; gl_kDrv_key1_short_state=0; keyState |= KEY1Release_Drv; keyState &= ~KEY1_Drv;//снимаем бит нажатия кнопки keyState &= ~KEY1Hold_Drv;//снимаем бит удержания кнопки }
При отпускании кнопки сбрасываются флаги удержания и нажатия, выставляется бит отпускания.
getKeyState()
Возвращает текущее состояние кнопок в виде битовой маски
-
Позволяет определить, была ли кнопка нажата, удержана или отпущена;
-
После считывания некоторые флаги (например, отпускание) сбрасываются ,чтобы событие не повторялось.
getKeyPinState_AtNow()
Возвращает моментальное состояние ножек GPIO, без учета дребезга
-
Полезно для отладки или когда нужно мгновенно узнать, нажата ли кнопка прямо сейчас
keys.c
#include "./Project/BU/keys.h" #include "./Project/shared.h" #include "main.h" #include <stdlib.h>//abs #include <string.h>//memset #include <stdio.h> //установки #define DELAY4TIMER20//задержка для таймера в миллисекундах (на дребезг) //25u//больше чем COUNT_HOLD_PERIODS, то считается, что кнопка зажата (долгое нажатие) #define COUNT_HOLD_PERIODS40 #define DELAY_HOLD_TIMER400//задержка для отслеживания удержания в миллисекундах //E N D установки //настройки #define KEY1_GPIO_Port GPIOB //буква порта для кнопки (GPIOA, GPIOB, GPIOC, ...) #define KEY1_Pin GPIO_PIN_11 //номер пина на порту для кнопки //E N D настройки volatile unsigned short keyState=0x0;//установка битов что кнопки отпущены #define KEY_PRESS 1 //1=нажатая кнопка это логическая единица, иначе 0 #define KEY_UNPRESS 0//0=отпущенная кнопка это логический ноль, иначе 1 uint8_t gl_kDrv_key1_blockEvent = 0;//обрабатывать ли событие нажатия кнопки 1 uint32_t gl_kDrv_time_key1_press = 0;//время, когда была нажата кнопка uint8_t gl_kDrv_key1_short_state = 0;//обработали ли короткое нажатие void keysDrv_Handler(){ uint32_t ms = HAL_GetTick(); uint8_t ON_OFFB_state = HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin); //кнопка 1 //если не заблокировано обработка события и кнопка нажата if(gl_kDrv_key1_blockEvent == 0 && ON_OFFB_state==KEY_PRESS){ gl_kDrv_key1_blockEvent=1; gl_kDrv_time_key1_press = ms; }else if(gl_kDrv_key1_blockEvent == 1 && ON_OFFB_state==KEY_PRESS && gl_kDrv_key1_short_state==0 && (ms - gl_kDrv_time_key1_press) > DELAY4TIMER){ //прошло время защиты от дребезга, кнопка нажата gl_kDrv_key1_short_state=1; keyState &= ~KEY1Release_Drv;//снимаем бит отпускания кнопки keyState |= KEY1_Drv; //кнопка удерживается }else if(gl_kDrv_key1_blockEvent == 1 && ON_OFFB_state==KEY_PRESS && gl_kDrv_key1_short_state==1 && (ms - gl_kDrv_time_key1_press) > DELAY_HOLD_TIMER){ keyState |= KEY1Hold_Drv; //кнопка отпущена }else if(gl_kDrv_key1_blockEvent == 1 && ON_OFFB_state==KEY_UNPRESS && gl_kDrv_key1_short_state==1){ gl_kDrv_key1_blockEvent=0; gl_kDrv_key1_short_state=0; keyState |= KEY1Release_Drv; keyState &= ~KEY1_Drv;//снимаем бит нажатия кнопки keyState &= ~KEY1Hold_Drv;//снимаем бит удержания кнопки //если сработало первое условие но не сработали остальные }else if(gl_kDrv_key1_blockEvent == 1 && ON_OFFB_state==KEY_UNPRESS && (ms - gl_kDrv_time_key1_press) > COUNT_HOLD_PERIODS*2){ gl_kDrv_key1_blockEvent=0; gl_kDrv_key1_short_state=0; } } unsigned short getKeyState(void){ // данный блок служит для проверки срабатывает ли keyState никуда дальше не идет. if(keyState){//-проверяет является ли выражение не нулевым int i=0; i++; } unsigned short ret=keyState;//здесь я получаю состояние кнопки keyState &= 0xF0FF;//снимаем бит отпускания кнопки return ret; } //выдаёт состояние пинов кнопок в данный момент без учёта задержек для дребезга unsigned short getKeyPinState_AtNow(void) { unsigned short ret=0; uint8_t key1_state = HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin); if(key1_state==KEY_PRESS){//если кнопка нажата ret |= KEY1_Drv; } return ret; }
Реализация модуля ADC_Calc.c (работа с АЦП)
Данный модуль реализует контроль напряжения питания через АЦП микроконтроллера STM32F103C8T6, измерения выполняются с использованием:
-
DMA (циклическая запись данных в буфер);
-
Встроенного опорного напряжения Vrefint;
-
Собственного делителя напряжения на входе.
ADC_Calc_Handler() — главный обработчик вычислений
-
Проверяет, заполнена ли первая или вторая половина DMA-буфера;
-
Усредняет значения для канала измерения и Vrefint;
-
Конвертирует результат в напряжение вызовом adc_calcVoltage();
-
Если напряжение ниже порога critical_stress → возвращает команду отключения питания (FORCE_POWER_OFF);
-
Производит фильтрацию значений по диапазону 750 < val_input < 2800 (защита от шумов и выбросов).
HAL_ADC_ConvCpltCallback()
Вызывается по прерыванию DMA Transfer Complete → устанавливает флаг adcIRFullDone
HAL_ADC_ConvHalfCpltCallback()
Вызывается по прерыванию DMA Half Transfer Complete → устанавливает флаг adcIRHalfDone
adc_init()
-
Сброс флагов и буфера.
adc_start()
Запускает АЦП в режиме DMA, далее происходит циклическое заполнение adcDMAbuf без участия процессора
adc_stop()
-
Останавливает АЦП и DMA;
-
Сбрасывает флаги готовности
adc_calcVoltage()
Ключевая функция — переводит «сырые» значения АЦП в напряжение
adc_GetVoltage()
Геттер для получения последнего значения рассчитанного напряжения
Общий алгоритм работы:
-
DMA заполняет буфер парами значений (input, Vrefint);
-
При заполнении половины буфера → срабатывает прерывание, ставится флаг;
-
ADC_Calc_Handler считывает данные, фильтрует и усредняет;
-
Вызывается adc_calcVoltage, которая переводит вольты;
-
Значение доступно через adc_GetVoltage();
-
Если напряжение меньше 9 В (по critical_stress) → отрабатывает аварийное выключение.
ADC_Calc.c
#include "./Project/shared.h" #include "./Project/BU/keys.h" #include "./Project/BU/ADC_Calc.h" #include "main.h" #include <stdlib.h>//abs #include <string.h>//memset #include <stdio.h> //у F103 нет калибр.значения, взято из datasheet #define VREFINT_TYP 1.20 #define SIZE_DMABUF400 volatile uint16_t adcDMAbuf[SIZE_DMABUF] = {0,}; volatile uint8_t adcIRFullDone=0; //сработало прерывание volatile uint8_t adcIRHalfDone=0; //сработало прерывание float gl_voltage=0; void adc_calcVoltage(uint16_t avg_input, uint16_t avg_vref); float vadc_ = 0.0f; float vin = 0.0f; float vdda = 0.0f; float critical_stress =9.0f; unsigned char ret = 0; uint16_t avg_input = 0; uint16_t avg_vref = 0; uint16_t vrefint_cal_adr = 0; unsigned char ADC_Calc_Handler() { uint32_t sum_input = 0; uint32_t sum_vrefint = 0; int count = 0; if(adcIRHalfDone){//готова первая половина буфера adcIRHalfDone = 0; for (int i = 0; i < SIZE_DMABUF / 2; i += 2) { uint16_t val_input = adcDMAbuf[i]; uint16_t val_vref = adcDMAbuf[i + 1]; if (val_input > 750 && val_input < 2800) { sum_input += val_input; sum_vrefint += val_vref; count++; } } } if(adcIRFullDone){//готова вторая половина буфера adcIRFullDone = 0; for (int i = SIZE_DMABUF / 2; i < SIZE_DMABUF; i += 2) { uint16_t val_input = adcDMAbuf[i]; uint16_t val_vref = adcDMAbuf[i + 1]; if (val_input > 750 && val_input < 2800) { sum_input += val_input; sum_vrefint += val_vref; count++; } } } if (count > 0) { avg_input = sum_input / count; //uint16_t avg_vref = sum_vrefint / count; adc_calcVoltage(avg_input, avg_vref); } if(gl_voltage && gl_voltage<=critical_stress && HAL_GetTick()>2000){ ret=FORCE_POWER_OFF; } return ret; } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if(hadc->Instance == ADC1){ adcIRFullDone=1; } } void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) { if(hadc->Instance == ADC1){ adcIRHalfDone=1; } } void adc_init(void) { adcIRFullDone = 0; adcIRHalfDone = 0; adcDMAbuf[0] = 0; #if defined(S_T_M_32F1XX) HAL_ADCEx_Calibration_Start(&hadc1); #endif } void adc_start(void) { adcDMAbuf[0] = 0; HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adcDMAbuf, SIZE_DMABUF); } void adc_stop() { HAL_ADC_Stop_DMA(&hadc1); adcIRFullDone = 0; adcIRHalfDone = 0; } void adc_calcVoltage(uint16_t avg_input,uint16_t avg_vref)//переводит значение АЦП в напряжение { const float R1 = 221000.0f; const float R2 = 27000.0f; // считаем VDDA через Vrefint float vdda = VREFINT_TYP * 4095.0f / (float)avg_vref; // переводим значение канала в напряжение float vadc = ((float)avg_input / 4095.0f) * vdda; // напряжение на входе через делитель vin = vadc * (R1 + R2) / R2 - 0.5f; gl_voltage = vin; // вывод значения для отладки vadc_ = vadc; } float adc_GetVoltage(void) { return gl_voltage; }
Пояснение к функции adc_calcVoltage()
Расчет VDDA, где:
VREFINT_TYP — калибровочное значение 1.20, взято из datasheet, у других МК на заводе производитель прошивает калибровочное значение в ПЗУ при изготовлении, пример получения значения с МК STM32F030CCTx [0x1FFFF7BA];
4095 — когда измеряется источник встроенным АЦП, результат выражается в единицах квантования, максимум которых равен 4095, поэтому в формуле используется множитель 4095 — это нормализация значения, чтобы связать измеренный АЦП с напряжением VDDA.
ADC_vref — значение встроенного источника опорного напряжения

Перевод значения канала в напряжение
ADC_in — напряжение измеренное с делителя напряжения (узел[5])

Коррекция через делитель напряжения
Вход подключен через делитель R1=221кОм и R2 = 27кОм, переводим в Ом,
добавил смещение (-0.5) для подстройки измерений.

Таблица замеров напряжения от 14.5 вольт до 7 вольт.
|
Напряжение от стационарного источника питания |
Рассчитанное напряжение через АЦП МК |
|
14,5 |
14.5474768 |
|
14 |
14.1426601 |
|
13,5 |
13.5746422 |
|
13 |
13.0305777 |
|
12,5 |
12.5156803 |
|
12 |
12.0447893 |
|
11,5 |
11.5500879 |
|
11 |
11.0325108 |
|
10,5 |
10.5149326 |
|
10 |
10.0049877 |
|
9,5 |
9.49503613 |
|
9 |
8.97779942 |
|
8,5 |
8.46920681 |
|
8 |
7.95231247 |
|
7,5 |
7.442698 |
|
7 |
6.93308401 |
Прикладываю видео-тестирования прохода по спаду напряжения, а также видео-тестирования, сброс напряжения и уход в сон микроконтроллера при низком напряжении ссылка [ https://t.me/ChipCraft В закрепленном сообщении [ #исскуствомк_тестирование_ Adc_VoltageControl]
Реализация модуля proj_main.c (Главный метод)
Данный модуль объединяет несколько подсистем:
-
Кнопка управления (одиночное и двойное нажатие);
-
Контроль напряжения питания через АЦП;
-
Звуковая индикация состояния;
-
Автоматический переход в сон при низком напряжении.
proj_main.c
#include "./Project/shared.h" #include "./Project/proj_main.h" #include "./Project/BU/ADC_Calc.h" #include "./Project/BU/keys.h" #include "main.h" char gl_main_stateKey = 0; //отработали ли код по нажатию на кнопку char count = 0;//используется для того чтобы пъезо не включался на повторное нажатие кнопки // Глобальный флаг, установленный при прерывании volatile uint8_t button_pressed = 0; float adc_GetVoltage_ = 0; typedef enum { BUZZER_NONE = 0, BUZZER_BEEP_1, BUZZER_BEEP_2, BUZZER_BEEP_3, BUZZER_BEEP_4, } buzzer_state_t; volatile buzzer_state_t buzzer_state = BUZZER_NONE; volatile uint32_t buzzer_timer = 0; volatile uint8_t test_stop = 0; volatile uint8_t test_stop_1 = 0; uint8_t adc_ret=0; #define START_DELAY 250 //для двойного нажатия uint32_t btnPress_time_start = 0;//время, когда нажали кнопку #define MIN_DelayDblClck100 //100 было -50 #define MAX_DelayDblClck600 //600 было-700 //E N D для двойного нажатия //переменные для обработки логики при низком напряжении uint32_t lowVoltageStart = 0;//время начала низкого уровня напряжения uint8_t lowVoltageActive = 0;//флаг что система в режиме отсчета #define SHUTDOWN_DELAY 15000 //E N D переменные для обработки логики при низком напряжении //для звуковой индикации в режиме низкого напр. uint32_t lowVoltageBeepTimer = 0; // время последнего писка #define LOW_VOLTAGE_BEEP_PERIOD 2000 // каждые 2 секунды //E N D для звуковой индикации в режиме низкого напр. void SystemClock_Config(void); void shutdownAndSleep(); void proj_main() { volatile const char *ch = ";V-F-BIN;ver: "VER_PROG(VER_a,VER_b,VER_c);(void)ch;//0x8008b00 unsigned short keysState=0;//состояние кнопки HAL_Delay(1);//чтобы HAL_GetTick() не выдавал ноль while (HAL_GetTick()<START_DELAY){;}//задержка, иначе иногда сразу уходит в сон keysState=getKeyPinState_AtNow(); // if((keysState & KEY1_Drv)==0){//защита от случайного нажатия shutdownAndSleep(); } uint32_t ms = 0; ms = HAL_GetTick(); adc_init(); adc_start(); while (1){ //хэндлеры keysDrv_Handler();//работа с кнопкой // Работа с ADC adc_ret = ADC_Calc_Handler(); // Работа со звуковой индикацией buzzer_handler(); //E N D хэндлеры ms = HAL_GetTick(); keysState=getKeyState();//получаю состояние кнопки adc_GetVoltage_ = adc_GetVoltage();//получение измер.напряж. // --- Проверка напряжения --- if(adc_ret == FORCE_POWER_OFF) { if(!lowVoltageActive) { lowVoltageActive = 1; lowVoltageStart = HAL_GetTick(); //внести обработку пищалки. // первый писк сразу buzzer_tripleBeep(); lowVoltageBeepTimer = HAL_GetTick(); } // проверка тайм-аута // if(lowVoltageActive && (HAL_GetTick() - lowVoltageStart >= SHUTDOWN_DELAY)) { adc_ret = 0; count = 0; shutdownAndSleep(); } // периодическая звуковая индикация if (HAL_GetTick() - lowVoltageBeepTimer >= LOW_VOLTAGE_BEEP_PERIOD) { buzzer_tripleBeep(); lowVoltageBeepTimer = HAL_GetTick(); } if(adc_GetVoltage_ > 9.0f ){ // напряжение восстановилось → сброс lowVoltageStart = 0; lowVoltageActive = 0; lowVoltageBeepTimer = 0; buzzer_state = BUZZER_NONE; buzzer_off(); } } if(keysState & KEY1Release_Drv){//если произошло короткое нажатие включение системы //button_pressed =0; if(test_stop == 1){ test_stop = 0; adc_init(); adc_start(); } if(gl_main_stateKey == 0){ gl_main_stateKey=1; count ++; if(count <= 1){ //запуск двойной звуковой индикации buzzer_doubleBeep(); } // — устанавливает пин в ЕДИНИЦУ, включение 3.3в и 12в HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET); //действие для двойного нажатия кнопки if(btnPress_time_start && (ms - btnPress_time_start) > MIN_DelayDblClck){ if(btnPress_time_start && (ms - btnPress_time_start) < MAX_DelayDblClck){ count = 0; shutdownAndSleep(); } } btnPress_time_start=ms; } } else{ if(gl_main_stateKey==1){ gl_main_stateKey=0; } } //ss }//while (1) } //Включение пъезо_излучателя void buzzer_on() { HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4); } //Выключение пъезо_излучателя void buzzer_off() { HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_4); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_RESET); } // E N D void buzzer_doubleBeep(void) { buzzer_state = BUZZER_BEEP_1; buzzer_timer = HAL_GetTick(); buzzer_on(); } // Запуск тройного писка void buzzer_tripleBeep(void) { buzzer_state = BUZZER_BEEP_1; buzzer_timer = HAL_GetTick(); buzzer_on(); } void buzzer_handler(void) { switch (buzzer_state) { case BUZZER_NONE: break; case BUZZER_BEEP_1: if (HAL_GetTick() - buzzer_timer > 100) { buzzer_off(); buzzer_timer = HAL_GetTick(); buzzer_state = BUZZER_BEEP_2; } break; case BUZZER_BEEP_2: if (HAL_GetTick() - buzzer_timer > 100) { buzzer_on(); buzzer_timer = HAL_GetTick(); buzzer_state = BUZZER_BEEP_3; } break; case BUZZER_BEEP_3: if (HAL_GetTick() - buzzer_timer > 100) { buzzer_off(); buzzer_state = BUZZER_NONE; } break; case BUZZER_BEEP_4: if (HAL_GetTick() - buzzer_timer > 100) { buzzer_on(); buzzer_timer = HAL_GetTick(); buzzer_state = BUZZER_NONE; // завершаем на третьем писке } break; } } void shutdownAndSleep(){ adc_stop(); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET); test_stop =1; //Сброс флага пробуждения //__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_11); __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); //Остановить SysTick, чтобы он не будил HAL_SuspendTick(); //Переход в режим STOP HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); /* Configure the system clock */ //Восстановить тактирование после пробуждения SystemClock_Config(); //Включить обратно HAL_ResumeTick(); } // Обработчик прерывания от кнопки (PB11 → EXTI11) void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_11) { button_pressed = 1; } }
Вывод
Данная система контроля над блоком питания реализует:
-
Обработку кнопок с фильтрацией дребезга, с поддержкой короткого/долгого/двойного нажатия и отпускания;
-
Мониторинг напряжения через АЦП с защитой от просадок и автоматическим отключением при критическом низком уровне;
-
Звуковая индикация (короткие, двойные сигналы) для информирования пользователя о событиях;
-
Энергосбережение переход в режим STOP и пробуждение по прерыванию.
Систему можно использовать как основу для портативных приборов, автономных устройств и встраиваемых систем, где важно одновременно удобное управление и защита электроники.
Если статья показалась Вам интересной, буду рад выпустить для Вас еще множество статей исследований по всевозможным видам устройств, так что, если не хотите их пропустить – буду благодарен за подписку на мой ТГ-канал: https://t.me/ChipCraft.
ссылка на оригинал статьи https://habr.com/ru/articles/937596/
Добавить комментарий