Инструменты пользователя

Инструменты сайта


prog:bug:pvd_test

Особенности детектора питания, блок Power

В микроконтроллерах 1986ВЕ1Т и 1986ВЕ9х обнаружена некоторая особенность сброса флагов PVD в регистре PVDCS. Особенность заключается в том, что необходимо вызывать двойной сброс этого флага, чтобы он сбросился. Сброс флага, конечно же, необходимо производить только при условиях, в которых флаг не будет выставляться вновь.

Полагаю, что с флагом PVDB (сравнение батарейного питания) ситуация такая же.

Выставление флага PVD происходит аппаратно в зависимости от

  1. сравнения внешнего напряжения питания Ucc с некоторым внутренним пороговым уровнем напряжения, назовем его для определенности LevelPVD.
  2. от состояния бита INV того же регистра PVDCS.

Выставление бита PVD в "1" сопровождается генерацией прерывания от блока Power, если прерывание разрешено битом IEPVD в том же регистре PVDCS.

В итоге, выставление бита PVD и соответственно генерация прерывания будет происходить при таких условиях:

Значение флага PVD
LevelPVD < Ucc Ucc < LevelPVD
INV = 0 1 0
INV = 1 0 1

Исходя из таблицы, для обработки ситуаций когда:

  1. Ucc превышает порог необходимо выключать инверсию.
  2. Ucc снижается ниже порога необходимо включать инверсию.

Получив прерывание, программа должна отработать событие по питанию необходимым образом. Для того чтобы прерывание не вырабатывалось повторно, необходимо либо выключить генерацию прерываний от блока Power. Либо изменить условие генерации прерывания - например, изменить уровень LevelPVD или поменять значение флага INV. Помимо этого необходимо сбросить флаг PVD, который был выставлен детектором при генерации прерывания.

Если флаг не сбросить, а сбросить его можно только если текущие условия не вызывают его выставление, то прерывание будет вырабатываться вновь и вновь, не давая исполняться коду в основном цикле. Вот в этом сбросе и обнаружилась особенность. Сбрасывать флаг необходимо ДВАЖДЫ!

Прототип кода для работы с блоком Power

Инициализация детектора питания для выработки прерывания по снижению напряжения питания ниже 2,8 вольт:

void MDR_Power_Init(void)
{
  //	Clock
  RST_CLK_PCLKcmd (RST_CLK_PCLK_POWER, ENABLE); 
  //	Deinit
  POWER_DeInit();
  //	Set LevelPVD
  POWER_PVDlevelConfig(PWR_PVDlevel_2V8);
  //	Set INV
  POWER_PVD_INVconfig(POWER_PVD_INV, ENABLE);
  //    PVD_IRQ Enable
  POWER_PVD_ITconfig(POWER_PVD_IT, ENABLE);
  //	ENABLE
  POWER_PVDenable(ENABLE);
  
  //	Double Clear!
  POWER_ClearFlag(POWER_FLAG_PVD);
  POWER_ClearFlag(POWER_FLAG_PVD);
	
  //	IRQ On
  NVIC_ClearPendingIRQ(POWER_IRQn);
  NVIC_EnableIRQ(POWER_IRQn);	
}

Если флаг PVD по какой-то причине установлен, то одиночное POWER_ClearFlag() не сотрет флаг и при вызове NVIC_EnableIRQ() тут же сработает прерывание.

Обработчик прерывания:

void POWER_IRQHandler(void)
{
  // Make something to stop calling POWER_IRQ
  // ....

  //	Double Clear PVD!
  POWER_ClearFlag(POWER_FLAG_PVD | POWER_FLAG_PVBD);		
  POWER_ClearFlag(POWER_FLAG_PVD | POWER_FLAG_PVBD);
  
  //	Clear PendingIRQ
  NVIC_ClearPendingIRQ(POWER_IRQn);
}

Если в обработчике прерывания POWER_IRQHandler сделать только один вызов POWER_ClearFlag(), то при выходе из обработчика флаг PVD остается активным, поэтому обработчик тут-же вызовется вновь. И только при этом, втором исполнении обработчика функция POWER_ClearFlag() сотрет флаг PVD и генерация прерывания прекратится.

Тестирование блока Power в 1986ВЕ1Т и 1986ВЕ92У

Необходимость двойного сброса флага PVD можно проверить на следующем примере. Проект реализован под демо-платы для МК 1986ВЕ1Т и 1986ВЕ92У. Поскольку подвигать Ucc у меня нет возможности, то будем менять значение LevelPVD и наблюдать выработку прерываний при (Ucc > LevelPVD) и (Ucc < LevelPVD).

Целиком проект доступен на GitHub

А - Находим ближайшие уровни LevelPVD выше и ниже напряжения Ucc

В первой части я специально везде использую двойное стирание, чтобы пороги LevelPVD определились правильно.

В этой части происходит следующее:

  1. Детектор напряжения Power включается с порогом LevelPVD = 3,4 вольта. На отладочных платах Ucc = 3,3В поэтому при старте флаг PVD = 0, ведь Ucc < LevelPVD.
  2. Находим порог срабатывания Ucc > LevelPVD. Для этого снижаем уровень LevelPVD, пока не выставится флаг PVD. Значение сохраняем в переменной PVD_Level_Bot - это ближайший уровень LevelPVD, который ниже напряжения питания. Шаг изменения LevelPVD составляет 0,2 вольта.
  3. Включаем инверсию (бит INV) чтобы "остановить" выставление флага PVD. Стираем текущий флаг PVD - ДВАЖДЫ!
  4. Находим порог срабатывания Ucc < LevelPVD. Для этого повышаем уровень LevelPVD, пока не выставится флаг PVD. Значение сохраняем в переменной PVD_Level_Top - это ближайший уровень LevelPVD, который ниже напряжения питания.
  5. Значение пороговых напряжений выводим на экран LCD.

В 1986ВЕ1Т работу LCD экрана не удалось наладить стабильно, данные взяты из отладчика. Проект будет доработан или позже допишу как правильно запускать экран, после ответа от тех-поддержки. Проблема с экраном в 1986ВЕ1Т в том, что вывод LCD_Reset на демоплате не подключен в выводам GPIO, как это сделано в демоплате 1986ВЕ92У, в выведен на джампер. Видимо необходимы дополнительные манипуляции для запуска экрана. В 1986ВЕ92У экран работает исправно.

Пороговые уровни "вокруг Ucc" получились следующие, в вольтах:

PVD_Level_Bot Ucc PVD_Level_Top
1986ВЕ1Т 3,0 3,3 3,2
1986ВЕ92У 3,0 3,3 3,4

Ожидалось, что Ucc будет зажат между уровнями 3,2В и 3,4В. По таблице результатов так не получается. Судя по всему, уровни LevelPVD выставляются достаточно не точно:

  1. В случае 1986ВЕ1Т уровни LevelPVD выставляют напряжение (компаратору для сравнения с Ucc) чуть ниже, чем есть на самом деле. Возможно необходимо отрегулировать питание параметром trim в регистре BKP_REG_0E.
  2. В случае 1986ВЕ92У получилось, что Ucc зажат не между соседними значениями LevelPVD. А, в зависимости от бита INV, Ucc реально находится в диапазонах LevelPVD либо между 3,0B-3,2В, либо 3,2В-3,3В. Возможно здесь зависимость не от бита INV, а от направления изменения уровня LevelPVD - проявляется некий гистерезис выставления напряжения LevelPVD.

Данные измерения проведены только на одних экземплярах плат и кристаллов, возможно в других вариантах результаты получатся другие. Точность работы блока Power в спецификации не указана, поэтому на высокую точность работы видимо не закладывались. Полагаю, что результат можно несколько улучшить поигравшись с параметром trim в регистре BKP_REG_0E.

Код первой части:

Следует учитывать, что уровень LevelPVD не может выставиться мгновенно (за один такт ядра), ведь это аппаратный блок и изменение напряжения занимает некоторое время. Поэтому после выставления значения LevelPVD используется задержка, которую я взял с запасом. Реальная задержка на выставление уровня LevelPVD вычисляется во второй части примера.

#define PVD_LEVEL_MIN     PWR_PVDlevel_2V8
#define PVD_LEVEL_MAX     PWR_PVDlevel_3V4

#define  WAIT_TIME_MAX     100

uint32_t PVD_Level_Top, PVD_Level_Bot;

int main(void)
{
  uint32_t cntRise, cntFall;
  
  //  Clock from HSE 80MHz
  BRD_Clock_Init_HSE_PLL(RST_CLK_CPU_PLLmul10);
  
  //  LEDs
  BRD_LEDs_Init();
  
  //  PVD Init
  BRD_Power_Init(&PowerCfg);
  //  Waiting for LevelPVD and clear possible PVD
  Delay(WAIT_TIME_MAX);
  POWER_ClearFlag(POWER_FLAG_PVD);
  POWER_ClearFlag(POWER_FLAG_PVD);
  
  //  Seek for LevelPVD < Ucc, INV = 0
  PVD_Level_Bot = SearchForLevelUcc(PVD_LEVEL_MAX, PVD_LEVEL_MIN, WAIT_TIME_MAX);
  if (!(PVD_Level_Bot))
  { 
    // Exit Fault
    BRD_LED_Set(LED_OK, 1);
    while (1);
  }

  //  PVD is set now
  //  Invert PVD condition and clear active PVD flag, INV = 1
  BRD_Power_InverseEvent(POWER_PVD_INV);
  POWER_ClearFlag(POWER_FLAG_PVD); 
  POWER_ClearFlag(POWER_FLAG_PVD);
  
  //  Seek for LevelPVD > Ucc, INV = 1  
  PVD_Level_Top = SearchForLevelUcc(PVD_LEVEL_MIN, PVD_LEVEL_MAX, WAIT_TIME_MAX);
  if (!(PVD_Level_Top))
  { 
    // Exit Fault
    BRD_LED_Set(LED_FAULT, 1);
    while (1);
  }
  
  //  Show Levels to LCD
  LCD_INIT;
  LCD_SHOW_LEVELS(PVD_Level_Top, PVD_Level_Bot);

Б - Проверяем работу прерываний при изменении порогов LevelPVD

В основном цикле выставляются по очереди значения PVD_Level_Bot и PVD_Level_Top, полученные в первой части, и проверяется генерация прерываний блока Power при этих значениях. В первой части прерывания не использовались, поэтому здесь они активируются перед стартом основного цикла.

Порядок действий:

  1. После первой части LevelPVD остался равен PVD_Level_Top, INV = 0. Поэтому для генерации прерывания:
    1. Устанавливаем LevelPVD = PVD_Level_Bot, INV = 0. Должно возникнуть прерывание.
    2. Запускаем счетчик cntFall в цикле, чтобы узнать сколько отсчетов займет установка уровня PVD_Level_Bot в аппаратуре и вызов обработчика прерывания.
    3. Возникает прерывание, в обработчике ставим флаг окончания цикла cntFall. Чтобы прерывание не вызывалось повторно устанавливаем INV = 1 (сейчас Ucc > LevelPVD см. таблицу), флаг PVD сбрасываем.
    4. Возвращаемся из обработчика, отсчет cntFall закончен.
  2. Сейчас LevelPVD = PVD_Level_Bot, INV = 1. Поэтому для генерации прерывания:
    1. Устанавливаем LevelPVD = PVD_Level_Top, INV = 1. Должно возникнуть прерывание.
    2. с. d. - … тоже самое, только счетчик cntRise и в обработчике устанавливаем INV = 0.
  3. Выводим на LCD экран значения счетчиков cntRise и cntFall.
  4. Переключаем состояние светодиода LED_OK.
  5. Задержка для наблюдения результатов.
  6. Повторение следующего цикла.

Пример показывает, что установка напряжения LevelPVD занимает некоторое время.

cntFall cntRise
(Ucc > LevelPVD) (Ucc < LevelPVD)
1986ВЕ1Т 16 19
1986ВЕ92У 6 10

Полученные значения лишь ориентировочные и сильно зависят от тактовой частоты ядра. При работе от HSE напрямую задержка может быть и не нужна, но в общем случае необходимо помнить что не только скорость света ограничена. :)

Проверка необходимости двойного сброса

Для проверки необходимости двойного сброса флага PVD необходимо закомментировать определение в начале файла main.c.

  //  Для наблюдения ошибки закомментировать!
  #define BUG_PVD_FIX
  
  void PVD_InverseEventAndClear(void)
  {
    BRD_Power_InverseEvent(POWER_PVD_INV);
  
    POWER_ClearFlag(POWER_FLAG_PVD); 
  #ifdef BUG_PVD_FIX
    POWER_ClearFlag(POWER_FLAG_PVD);    
  #endif  
  }

Определение BUG_PVD_FIX влияет только на код функции PVD_InverseEventAndClear(). Эта функция применяется на старте теста и в прерывании. Код реализован так, чтобы при возникновении вторичного прерывания мигал светодиод ошибки LED_FAULT. Код теста при этом не исполняется и светодиод LED_OK не переключается. Это происходит когда BUG_PVD_FIX закомментирован.

В принципе отсутствие сброса после первого вызова POWER_ClearFlag() можно проверить просто считав регистр PVDCS. Но в проект был внесен чуть больший функционал.

При активном BUG_PVD_FIX, работает двойной сброс PVD. Вторичных прерываний поэтому не возникает, код с LED_FAULT в обработчике никогда не исполняется. Тест с выставлением уровней LevelPVD выполняется штатно и мигает светодиод LED_OK.

Для 1986ВЕ1Т работоспособность проекта можно наблюдать по светодиоду VD6 (LED_OK), он будет мигать с частотой порядка 1Гц. Без BUG_PVD_FIX будет мигать светодиод VD7 (LED_FAULT) раза в два быстрее.

В случае 1986ВЕ92У для проверки по светодиодам необходимо закомментировать строку

  // Закомментировать для наблюдения статуса на светодиоде
  // Раскомментировать для наблюдения результатов для LCD
  #define LCD_ENA

Дело в том, что в плате 1986ВЕ92У светодиоды и LCD используют PORT_C и пины 0 и 1. Поэтому при обновлении LCD, светодиоды VD3 и VD4 почти незаметно помигивают при обновлении экрана. Чтобы наблюдать переключение светодиодов необходимо отключить LCD. VD3 (LED_OK) - мигает с частотой 1Гц при BUG_PVD_FIX, VD4 (LED_FAULT) - мигает вдвое быстрее без BUG_PVD_FIX.

Код второй части:

  //  Start Testing
  DoEventInv = 1;
  //  INV = 0
  PVD_InverseEventAndClear();
  BRD_PowerPVD_EnaIRQ(POWER_PVD_IT, 1);    
  while (1)
  {
    cntRise = cntFall = 0;
    
    //  Rise event: PVD on Ucc > LevelPVD, INV = 0
    WaitIrqFlag = 0;
    DoEventInv = 1;

    SetLevelPVD(PVD_Level_Bot); 
    while (!WaitIrqFlag)
      ++cntFall;   
    
    //  Rise event: PVD оn Ucc < LevelPVD, INV = 1
    WaitIrqFlag = 0;
    DoEventInv = 1;

    SetLevelPVD(PVD_Level_Top); 
    while (!WaitIrqFlag)
      ++cntRise;
        
    //  Show Test Result
    LCD_SHOW_DELAY(cntRise, cntFall);
    LED_OK_SWITCH;   
    Delay(LED_OK_SHOW_DELAY);
  }
}

void POWER_IRQHandler(void)
{
  // Stop PVD event, and Clear PVD flag
  if (DoEventInv)
  {
   //  Реализует одинарное или двойное стирание PVD    
   PVD_InverseEventAndClear();
   DoEventInv = 0;
  }
  else
  { 
    //  Лишнее прерывание
    //  Не возникает при двойном сбросе
    //  Дотираем PVD
    POWER_ClearFlag(POWER_FLAG_PVD);
    //  Переключаем светодиод статуса ошибки
    LED_FAULT_SWITCH;
  }
  
  WaitIrqFlag = 1; 
  NVIC_ClearPendingIRQ(POWER_IRQn);
}

Ускорение выставления уровня LevelPVD

Интереса ради я попробовал "ускорить" выставление уровня LevelPVD. Практической пользы я в этом не вижу, привожу здесь просто как факт.

В 1986ВЕ8Т такой прием с форсированным выставлением уровня помог выставить более точное значение LevelPVD (Подробнее). Я попробовал здесь такой подход, но на точность он никак не повлиял. Дал лишь некоторое ускорение выставления уровня.

Результаты на табло:

Код:

void SetLevelPVD(uint32_t levelPVD)
{
#ifdef SET_LEVEL_PVD_FASTER  
//  // Не ускоряет выставление уровня
//  if (levelPVD < PWR_PVDlevel_3V4)
//    POWER_PVDlevelConfig(levelPVD + PVD_LEVEL_INC);
  
  // Ускоряет выставление уровня   
  if (levelPVD > PWR_PVDlevel_2V0)
    POWER_PVDlevelConfig(levelPVD - PVD_LEVEL_INC);  
#endif  
  
  POWER_PVDlevelConfig(levelPVD);
}
prog/bug/pvd_test.txt · Последние изменения: 2019/06/17 12:35 — katya