AI Контроль напряжения над блоком питания на STM32: Подход к энергоэффективности и защите

AI

Редактор
Регистрация
23 Август 2023
Сообщения
2 822
Лучшие ответы
0
Реакции
0
Баллы
51
Offline
#1


Привет, Хабр!

Автономные системы становятся все более востребованными - от портативных приборов до сложных промышленных комплексов - надежное управление напряжением питания превращается в ключевой фактор их долговечности и эффективности. Сердце любой такой системы - аккумулятор, а его безопасность и срок службы, напрямую зависит от контроля напряжения.

В данной статье будет представлен пример контроля напряжения, над блоком питания - внутри которого (никель-металлгидридная аккумуляторная сборка 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 обеспечивают фильтрацию и антидребезг, транзистор VT2 усиливает сигнал и формирует логический уровень для МК;

Узел[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_Drv 1u //кнопка нажата
#endif
#ifndef KEY1Double_Click_Drv
#define KEY1Double_Click_Drv
#endif
#ifndef KEY3_Drv
#define KEY3_Drv 4u
#endif
#ifndef KEY4_Drv
#define KEY4_Drv 8u
#endif

#ifndef KEY1Hold_Drv
#define KEY1Hold_Drv 16u // кнопка удерживается
#endif
#ifndef KEY2Hold_Drv
#define KEY2Hold_Drv 32u
#endif
#ifndef KEY3Hold_Drv
#define KEY3Hold_Drv 64u
#endif
#ifndef KEY4Hold_Drv
#define KEY4Hold_Drv 128u
#endif

#ifndef KEY1Release_Drv
#define KEY1Release_Drv 256u //Бит отпускания кнопки
#endif
#ifndef KEY2Release_Drv
#define KEY2Release_Drv 512u
#endif
#ifndef KEY3Release_Drv
#define KEY3Release_Drv 1024u
#endif
#ifndef KEY4Release_Drv
#define KEY4Release_Drv 2048u
#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 DELAY4TIMER 20//задержка для таймера в миллисекундах (на дребезг)
//25u//больше чем COUNT_HOLD_PERIODS, то считается, что кнопка зажата (долгое нажатие)
#define COUNT_HOLD_PERIODS 40
#define DELAY_HOLD_TIMER 400//задержка для отслеживания удержания в миллисекундах
//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_DMABUF 400

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;
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;
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;
}

Таблица замеров напряжения от 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_DelayDblClck 100 //100 было -50
#define MAX_DelayDblClck 600 //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.
 
Сверху Снизу