Настройка прерывания по уровню напряжения питания МК

VVViktor

✩✩✩✩✩✩✩
31 Дек 2021
39
7
Здравствуйте. Хочу настроить срабатывание прерывания по уровню напряжения питания контроллера Arduino Nano. Интуитивно понимаю, что для этого нужно настроить прерывание в регистре АЦП. То есть нужно замерить значение опорного напряжения, и при его падении до определенного уровня вызвать прерывание. Подскажите, пожалуйста, где можно об этом почитать. В уроках Gyver'а есть поверхностное упоминание о прерываниях, вызываемых АЦП, но, к сожалению, без подробностей.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
Почитайте про прерывание компаратора, например, здесь.
Но это имеет смысл, если используется какой-либо сон, иначе проще периодически измерять напряжение с помощью АЦП.
 
  • Лойс +1
Реакции: VVViktor

VVViktor

✩✩✩✩✩✩✩
31 Дек 2021
39
7
@poty,
Подскажите, пожалуйста, какое оптимальное значение делителя целесообразно использовать в ADCSRA? Измеряю напряжение питания контроллера. Адекватные значения получаю только при делителях более 4. На делителях 2 и 4 система виснет.
C++:
static volatile uint16_t *cpu_voltage = new volatile uint16_t;

// этот блок выполняется в отдельно вызываемом методе

ADMUX = 0b01001110;
ADCSRA = 0b10101110;
ADCSRB = 0b01000000;  // это действие имеет какой-то смысл в данном контексте?
ADCSRA |= (1 << ADSC);

//===================================================

float Logger::getVoltage()
{
    float *volt_calc = new float;
    *volt_calc = (1.1f / *cpu_voltage) * 1023.0f;
    return *volt_calc;
}

ISR(ADC_vect)
{
    *cpu_voltage = ADCL;
    *cpu_voltage = *cpu_voltage + (ADCH << 8);
}
 
Изменено:

VVViktor

✩✩✩✩✩✩✩
31 Дек 2021
39
7
Переделал вот так, чтобы локализовать измерение в одной функции:
C++:
static volatile uint16_t *cpu_voltage = new volatile uint16_t;

float Logger::getVoltage()
{
    ADMUX = 0b01001110;
    ADCSRA = 0b10101111;
    ADCSRB = 0b00000000;
    ADCSRA |= (1 << ADSC);
    uint32_t delay_timer = millis();
    while (millis() - delay_timer < 50)  // за это время должны выполниться ~3100 (чуть меньше) циклов АЦП
        ;
    float *volt_calc = new float;
    *volt_calc = (1.1f / *cpu_voltage) * 1023.0f;
    ADMUX = 0;
    ADCSRA = 0;
    return *volt_calc;
}

ISR(ADC_vect)
{
    *cpu_voltage = ADCL;
    *cpu_voltage = *cpu_voltage + (ADCH << 8);
}
Измеряет напряжение контроллера корректно. Но не решает первоначальную задачу. Нужно вызвать прерывание при падении напряжения ниже установленного значения. Нужно настраивать регистры компаратора? Можно, конечно и без прерываний просто вызывать выше описанный метод. Но тогда получится float деление с частотой loop(), что неоптимально.
 
Изменено:

VVViktor

✩✩✩✩✩✩✩
31 Дек 2021
39
7
Вариант поднятия флага без float деления:
C++:
// _voltage_threshold вычисляется заранее

float i = 1.1f / (CRITICAL_VOLTAGE / 1023.1f);
_voltage_threshold = round(i);

//===========================================

boolean Logger::lowVoltage()
{
    ADMUX = 0b01001110;
    ADCSRA = 0b10101111;
    ADCSRB = 0b00000000;
    ADCSRA |= (1 << ADSC;
    uint32_t delay_timer = millis();
    while (millis() - delay_timer < 50) // за это время должны выполниться ~ 3100 циклов АЦП
        ;
    uint16_t voltage = *cpu_voltage;
    ADMUX = 0;
    ADCSRA = 0;
    if (voltage >= _voltage_threshold)
    {
        return (true);
    }
    else
    {
        return (false);
    }
}
 
Изменено:

Sergo_ST

★★★★★★✩
15 Мар 2020
996
831
Если я вас правильно понял, и вы хотите вызывать прерывание по определенному уровню напряжения, то средствами данного камня это невозможно(точнее можно, но устанавливать напряжение придется с помощью делителя на резисторах). Для программного изменения порога как минимум вам нужен будет ЦАП, как вы понимаете, его у нас нет.
И опять же, для ЦАП нужно будет стабильное напряжение питания, тк при изменении напряжения будет уходить и напряжение на выходе ЦАП.
Также можно конечно использовать компаратор подключив один выход к ИОН, а второй к средней точке делителя напряжения, соответственно повлиять программно на эту установку не получится.

И по поводу изменения встроенного ИОН, не стоит его так часто дергать, он крайне нежный и от любого лишнего движения просаживается, особенно при переключении мукс. Если нужны более менее точные показания, то лучше использовать максимальный пределитель АЦП(128). При пределителе ниже 32 начинается погода на Марсе(при тактирование 16МГц).
 
Изменено:
  • Лойс +1
Реакции: VVViktor

Геннадий П

★★★★★★✩
14 Апр 2021
1,975
634
45
Если я вас правильно понял, и вы хотите вызывать прерывание по определенному уровню напряжения, то средствами данного камня это невозможно.
Видел подобное на STM32, и то только на тех, у которых есть встроенный компаратор.
 
  • Лойс +1
Реакции: Sergo_ST

poty

★★★★★★✩
19 Фев 2020
3,261
948
А что, на Atmega 328 разве нет встроенного компаратора? Опорное напряжение внутри тоже присутствует. К сожалению пока не смотрел код, не знаю что там сделано, но использовать float для таких вычислений вообще не стоит. Я бы также ограничился старшими 8 битами АЦП (правда не знаю конечной цели измерений).
 
  • Лойс +1
Реакции: Sergo_ST

Sergo_ST

★★★★★★✩
15 Мар 2020
996
831
@poty, да есть, дополнил предыдущий ответ, тоже вариант, но установить нужное напряжение можно будет только хардверно, с помощью делителя напряжения, а насколько я понял, человек хочет устанавливать его программно(может ошибаюсь).
 
Изменено:

poty

★★★★★★✩
19 Фев 2020
3,261
948
@Sergo_ST, делитель напряжения понадобится, это понятно, но для чего устанавливать напряжение, которое этим делителем производится? Использовать в качестве опорного внутренний источник, делитель загнать в интервал напряжений, не превышающих опорное, и всё, будет измерять любое напряжение в интервале рабочих для контроллера (т.е. до тех пор пока на заданной частоте напряжение будет для него рабочим).
А уж интерпретировать результаты - требуется знать назначение того, что делается.
 

Sergo_ST

★★★★★★✩
15 Мар 2020
996
831
Так нужно же получить прерывание по достижению порога...
Нужно вызвать прерывание при падении напряжения ниже установленного значения.
То что можно измерять напряжение относительно внутреннего ИОН я думаю всем понятно...
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,975
634
45
Мне кажется, проще внешнюю хардварную цепочку сделать. Если нужно напряжение срабатывание 2.5В или больше, то TL431 который стоит в каждом импульсном блоке питания.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
@Sergo_ST, так порог-то можно выставить программно. Я же не зря указал, что с помощью внутреннего опорного напряжения можно измерять весь диапазон напряжений питания контроллера.
 
  • Лойс +1
Реакции: VVViktor

Sergo_ST

★★★★★★✩
15 Мар 2020
996
831
@poty, Эмм... И как же вы это сделаете?🙄 Ни у компаратора ни у АЦП нету такого условия для прерывания...
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
@Sergo_ST, для прерывания - нет, но нигде не сказано, что в прерывании я не могу измерять (или, как минимум, инициировать измерение) уровень. Т.е. делителем выставляем порог, после которого осуществляем контроль напряжения с помощью прерываний АЦП.
Но всё это - усложнение основной задачи, в которой не сказано, что нужно мониторить несколько уровней (для этого подойдут внешние цепи, допустим). А для одного уровня прерываний компаратора будет достаточно.
 
  • Лойс +1
Реакции: VVViktor и Sergo_ST

Sergo_ST

★★★★★★✩
15 Мар 2020
996
831
@poty, Согласен. Как я и говорил, может я не совсем правильно понял тс. Предполагал что всё же уровень для прерывания нужно устанавливать программно...
 
  • Лойс +1
Реакции: VVViktor

VVViktor

✩✩✩✩✩✩✩
31 Дек 2021
39
7
@Sergo_ST,
Да, верно. Хотелось программно. Поэтому попробую просто организовать опрос в главном цикле. Буду вызывать этот метод.
C++:
// _voltage_threshold вычисляется заранее в однократно вызываемом методе

ADMUX = 0b01001110;
ADCSRA = 0b10101111;
ADCSRB = 0b00000000;
ADCSRA |= (1 << ADSC);
uint32_t delay_timer = millis();
while (millis() - delay_timer < 50) // за это время должны выполниться ~ 3100 циклов АЦП
     ;
float i = 1.1f / (CRITICAL_VOLTAGE / 1023.1f);
_voltage_threshold = round(i);

//===========================================

boolean Logger::lowVoltage()
{
    if (*cpu_voltage >= _voltage_threshold)
    {
        return (true);
    }
    else
    {
        return (false);
    }
}

ISR(ADC_vect)
{
    *cpu_voltage = ADCL;
    *cpu_voltage = *cpu_voltage + (ADCH << 8);
}
 

VVViktor

✩✩✩✩✩✩✩
31 Дек 2021
39
7
Все работает. При значении CRITICAL_VOLTAGE 4.8 данные успевают успешно записаться в EEPROM. При следующем запуске таймер исправно стартует с сохраненной позиции.
C++:
// выполняем однократно ранее

// EEPROM.write(POWER_LOSS_ADDR, 0); // для инициализации
 ADMUX = 0b01001110;
 ADCSRA = 0b10101111;
 ADCSRB = 0b00000000;
 ADCSRA |= (1 << ADSC);
 uint32_t delay_timer = millis();
 while (millis() - delay_timer < 50) // за это время должны выполниться ~ 3100 циклов АЦП
     ;
 float i = 1.1f / (CRITICAL_VOLTAGE / 1023.1f);
 _voltage_threshold = round(i);

//=======================================================================

boolean Logger::lowVoltage()
{
    if (*cpu_voltage >= _voltage_threshold)
    {
        return (true);
    }
    else
    {
        return (false);
    }
}

void Logger::getBackup(low_voltage_backup *backup_data)
{
    *_backup_data = *backup_data;
}

void Logger::timerBackup()
{
    EEPROM.write(POWER_LOSS_ADDR, 1);
    EEPROM.put(POWER_LOSS_ADDR - sizeof *_backup_data, *_backup_data);
}

low_voltage_backup Logger::timerRestore()
{
    *_backup_data = EEPROM.get(POWER_LOSS_ADDR - sizeof *_backup_data, *_backup_data);
    return *_backup_data;
}

boolean Logger::getPower_loss_flag()
{
    if (_power_loss_flag)
    {
        _power_loss_flag = false;
        return (true);
    }
    else
    {
        return (false);
    }
}

ISR(ADC_vect)
{
    *cpu_voltage = ADCL;
    *cpu_voltage = *cpu_voltage + (ADCH << 8);
}

//=================================================================

void loop()
{
  btn->tick();            // опрос кнопки происходит здесь
  myperiod->timerCheck(); // опрос основного таймера
  if (my_logger->lowVoltage())
  {
    my_logger->getBackup(myperiod->timerBackup_data());
    my_logger->timerBackup();
  }
    
// и проч. проч.......
    
}