Калибровка

Наконец-то мы добрались до того момента, когда можно откалибровать наше АЦП и получать на дисплее реальное значение температуры!
Рассмотрим, как вообще будет происходить калибровка: мы просто возьмем две точки (одну «холодную», другую «горячую» - в зависимости от кода АЦП), и присвоим этим двум точкам соответствующие значения температуры. Таким образом, получим две пары (код АЦП, значение температуры); код АЦП мы берем уже «рафинированный», то есть сглаженный и с повышенной разрядностью; при этом вид точки контроллер должен определять сам (это просто – если текущий код АЦП больше какого-то порога – точка «горячая», если меньше – точка «холодная»; кстати: нужно не забывать, что наше АЦП долго раскачивается, поэтому калибровать устройство нужно после того, как значение текущей температуры перестало расти).
Посмотрим, как будет осуществляться калибровка с точки зрения пользователя. Во-первых, нужно продумать, как пользователь может начать калибровку – и вот тут-то нам наконец-то пригодится «админская» кнопка, или  «кнопка 2». Было решено, что заход в калибровку будет происходить по краткому нажатию на кнопку 2 в любом активном (не спящем) состоянии.
Дальше нужно продумать интерфейс процесса калибровки. Схематично он представлен на рисунке ниже.


Рисунок 1. Модель калибровки с точки зрения пользователя.

Первое, что пользователь видит по нажатию на «админскую» кнопку в активном режиме – это «приветствие»; дальше уже знакомый нам интерфейс: по краткому нажатию на основную кнопку переходим дальше, энкодером настраиваем значение температуры или выбираем нужный вариант диалога сохранения («n» – отменить калибровку, «y» - сохранить изменения). По завершению калибровки вне зависимости от того, сохранили мы результаты или нет, контроллер переходит к отображению текущей температуры. Кроме того, как и в основном интерфейсе, из любого состояния калибровки устройство можно выключить длительным нажатием на «основную» кнопку.
Итак, модель процесса калибровки с точки зрения пользователя построена.
Теперь будем это реализовывать в качестве подмашины базового интерфейса – добавим к активным состояниям базовой виртуальной машины ещё одно – состояние калибровки. При этом не забываем, что в состоянии калибровки нагрев должен быть выключен!
Как и раньше, сначала разработаем схему виртуальной машины калибровки. Одно важное замечание: мы так спроектировали машину счётчика энкодера, что после пробуждения она требует новых данных или подтверждения того, что текущие данные (минимальное-максимальное-текущее значение и шаг энкодера) менять не нужно. Однако спящий режим при настройках нам не нужен, поэтому было решено чуть-чуть переписать виртуальную машину энкодера: мы просто добавим флажок «IsSleepModeOn» - если этот флаг взведён, то виртуальная машина «застревает» на пробуждении и ждёт подтверждения получения новых данных; если же флаг опущен, то  машина просто проскакивает это состояние и переходит к ожиданию поворота.
Вот как это все реализуется. В библиотеку счётчика энкодера добавляем флаг и функции его взведения-опускания:

//структура виртуальной машины счётчика энкодера
typedef struct
{
  …
  uint8_t ecntIsSleepModeOn: 1;                //флаг включённости спящего режима (необходимости подтверждать новые данные после спящего режима)
} TVMEncoderCounter;
…
//включение спящего режима
#pragma inline=forced
void EncoderCounterSleepModeOn();
//выключение спящего режима
#pragma inline=forced
void EncoderCounterSleepModeOff();

В файле .c пишем новые функции и изменяем виртуальную машину счётчика энкодера:

//включение спящего режима
#pragma inline=forced
void EncoderCounterSleepModeOn()
{
  VMEncoderCounter.ecntIsSleepModeOn = 1;
  VMEncoderCounter.ecntOnActiveMode = 0;
  VMEncoderCounter.ecntOnSleepMode = 0;
}
    
//выключение спящего режима
#pragma inline=forced
void EncoderCounterSleepModeOff()
{
  VMEncoderCounter.ecntIsSleepModeOn = 0;
}

//инициализация виртуальной машины счётчика энкодера
void EncoderCounterMachineInit()
{
  …
  VMEncoderCounter.ecntIsSleepModeOn = 1;
  VMEncoderCounter.ecntOnActiveMode = 0;
  VMEncoderCounter.ecntOnSleepMode = 0;
  VMEncoderCounter.ecntVMEncoder = &VMEncoder;
}

//сама виртуальная машина счётчика энкодера
void EncoderCounterMachine()
{
  switch (VMEncoderCounter.ecntMachineState)
  {
  …
  case ecnsInit:                                                                //инициализация энкодера - при необходимости ждём, когда пользователь задаст новые параметры
    if ((!VMEncoderCounter.ecntIsSleepModeOn) || (VMEncoderCounter.ecntInNewData == 1))
    {
      VMEncoderCounter.ecntInNewData = 0;                                       //очищаем входящую посылку и посылки энкодера
      VMEncoderCounter.ecntVMEncoder->enOnLeftTurn = 0;
      VMEncoderCounter.ecntVMEncoder->enOnRightTurn = 0;
      VMEncoderCounter.ecntMachineState = ecnsWaitTurn;
    }
    break;
…

Вот и все.

Вернемся к модели виртуальной машины калибровки.


Рисунок 2. Виртуальная машина калибровки.

Реализуем эту машину. Файл "SetupInterface.h":

#ifndef SETUP_INTERFACE_H
#define SETUP_INTERFACE_H
#include "SegmentDisplay.h"
#include "LogicalButton.h"
#include "Encoder.h"
#include "ADCTemperature.h"

//калибровка температуры
//граница перехода кода АЦП из "холодной" точки в "горячую"
#define CALIBR_LOW_HIGH_ADC_CODE_BORDER (0x200 * ADCExpansionDivider)

//состояния машины калибровки
typedef enum {csGreetingInit, csGreeting, csSelectIntTemperature, csSelectFractionTemperature, csSuggestSaveInit, csSuggestSave, csSave, csExit} TVMCalibrationState;
extern TVMCalibrationState VMCalibrationState;

void CalibrationMachineInit();
void CalibrationMachine();
//пересчёт температурных коэффициентов
void CalculateTemperatureCoefs();

#endif

Файл "SetupInterface.c":

#include "SetupInterface.h"

typedef struct
{
  double cdTemperature;
  double cdADCValue;
} TCalibrationData;

TCalibrationData CalibrationData[2];

//временная переменная для хранения выбранной температуры текущей калибровки
double CurrCalibrationTemperature;

//переменная для хранения текущей точки калибровки
typedef enum {cpLow = 0, cpHigh = 1} TCalibrationPoint;
TCalibrationPoint CalibrationPoint;

TVMCalibrationState VMCalibrationState;

char CB_GREETING[] = "CALIBR";

//пересчёт коэффициентов температурной прямой
void CalculateTemperatureCoefs()
{
  ADCTemperature.atAngularCoef = (CalibrationData[1].cdTemperature - CalibrationData[0].cdTemperature) / 
                                 (CalibrationData[1].cdADCValue - CalibrationData[0].cdADCValue);
  ADCTemperature.atOffsetCoef = CalibrationData[0].cdTemperature - ADCTemperature.atAngularCoef * CalibrationData[0].cdADCValue;
}

//инициализация виртуальной машины калибровкеи
void CalibrationMachineInit()
{
  VMCalibrationState = csGreetingInit;
}

//виртуальная машина калибровки
void CalibrationMachine()
{
  switch (VMCalibrationState)
  {
  case csGreetingInit:
    DisplayBlinkOff();
    DisplayShowStr(CB_GREETING);
    VMCalibrationState = csGreeting;
    break;
  case csGreeting:
    if (VMLogicalButtons[0].lbOnShortPress)
    {
      //определяем, какую точку калибруем
      if (ADCGetCalibrationPoint())
      {
        if (ADCTemperature.atCurrTemperature <= CALIBR_LOW_HIGH_ADC_CODE_BORDER)
          CalibrationPoint = cpLow;
        else
          CalibrationPoint = cpHigh;
        //пересчёт значения с текущими коэф-тами
        ADCTemperature.atCurrTemperature = ADCTemperature.atAngularCoef * ADCTemperature.atCurrTemperature +
                                           ADCTemperature.atOffsetCoef;
        //!!! - исправить на минимум и максимум при калибровке!!!!
        if (ADCTemperature.atCurrTemperature < 0)
          ADCTemperature.atCurrTemperature = 0;
        else if (ADCTemperature.atCurrTemperature > 300)
          ADCTemperature.atCurrTemperature = 300;
        DisplayShowCalibrationValue(CalibrationPoint, ADCTemperature.atCurrTemperature);
        //настроили энкодер !!! - исправить мин и мах
        EncoderCounterSetValue(ADCTemperature.atCurrTemperature, 300, 0, 1);
        VMCalibrationState = csSelectIntTemperature;
        EncoderCounterSleep();
        VMLogicalButtons[0].lbOnShortPress = 0;
      }
    }
    break;
  case csSelectIntTemperature:                                                  //выбор целого значения температуры
    if (VMEncoderCounter.ecntOnValueChanged)
    {
      VMEncoderCounter.ecntOnValueChanged = 0;
      DisplayShowCalibrationValue(CalibrationPoint, VMEncoderCounter.ecntValue); 
    }
    if (VMLogicalButtons[0].lbOnShortPress)
    {
      VMLogicalButtons[0].lbOnShortPress = 0;
      //делаем шаг энкодера мельче
      EncoderCounterSetValue(VMEncoderCounter.ecntValue, 300, 0, 0.1);
      VMCalibrationState = csSelectFractionTemperature;
    }
    break;
  case csSelectFractionTemperature:                                             //выбор дробного значения температуры
    if (VMEncoderCounter.ecntOnValueChanged)
    {
      VMEncoderCounter.ecntOnValueChanged = 0;
      DisplayShowCalibrationValue(CalibrationPoint, VMEncoderCounter.ecntValue); 
    }
    if (VMLogicalButtons[0].lbOnShortPress)
    {
      VMLogicalButtons[0].lbOnShortPress = 0;
      //сохраняем значение выставленной температуры
      CurrCalibrationTemperature = VMEncoderCounter.ecntValue;
      VMCalibrationState = csSuggestSaveInit;
    }
    break;
  case csSuggestSaveInit:
    EncoderCounterSleep();
    EncoderCounterSetValue(0, 1, 0, 1);
    DisplayShowSaveDialog(0);
    VMCalibrationState = csSuggestSave;
    break;
  case csSuggestSave:
    //проверяем, не зашел ли до этого энкодер в спящий режим и не проснулся ли он теперь:
    if (VMEncoderCounter.ecntOnValueChanged)
    {
      VMEncoderCounter.ecntOnValueChanged = 0;
      DisplayShowSaveDialog((uint8_t)VMEncoderCounter.ecntValue); 
    }
    if (VMLogicalButtons[0].lbOnShortPress)
    {
      VMLogicalButtons[0].lbOnShortPress = 0;
      if (VMEncoderCounter.ecntValue == 1)
        VMCalibrationState = csSave;
      else
        VMCalibrationState = csExit;
    }
    break;
  case csSave:
    if (ADCGetCalibrationPoint())
    {
      CalibrationData[CalibrationPoint].cdADCValue = ADCTemperature.atCurrTemperature;
      CalibrationData[CalibrationPoint].cdTemperature = CurrCalibrationTemperature;
      CalculateTemperatureCoefs();
      VMCalibrationState = csExit;
    }
    break;
  }
}

Теперь встроим виртуальную машину калибровки в машину базового интерфейса: нам потребуется добавить ещё два состояния: CalibrationInit и Calibration.
В функцию виртуальной машины базового интерфейса добавляем следующее:

void BIMachine()
{
  …
  //машины и проверки, работающие в активном состоянии)
  if (VMBIState != bisOff) 
  {
    EncoderMachine();
    EncoderCounterMachine();
    HEMachine();
    CheckToOff();
    //проверяем на необходимость запуска калибровки
    if ((VMBIState != bisCalibration) && (VMLogicalButtons[1].lbOnShortPress))
    {
      VMLogicalButtons[1].lbOnShortPress = 0;
      VMBIState = bisCalibrationInit;
    }
  }

И далее, при перечислении состояний:

…
switch (VMBIState)
  {
  …
  case bisCalibrationInit:                                                      //инициализация калибровки
    //выключаем нагрев, спящий режим энкодера и отключаем таймер!
    FurnaceTimerOff();
    HEMachineOff();
    EncoderCounterSleepModeOff();
    CalibrationMachineInit();
    VMBIState = bisCalibration;
    break;
  case bisCalibration:
    CalibrationMachine();
    if (VMCalibrationState == csExit)                                           //если калибровка закончена
    {
      LogicalButtonVMErasePackages();
      EncoderCounterSleep();
      FurnaceTimerOn();
      HEMachineOn();
      EncoderCounterSleepModeOn();
      VMBIState = bisTemperatureShowInit;
    }
    break;
…

Вот проект и прошивка.

Наверх

Автор - Moriam  =ˆˆ=

Обсудить на форуме