Калибровка
Наконец-то мы добрались до того момента, когда можно откалибровать наше АЦП и получать на дисплее реальное значение температуры!
Рассмотрим, как вообще будет происходить калибровка: мы просто возьмем две точки (одну «холодную», другую «горячую» - в зависимости от кода АЦП), и присвоим этим двум точкам соответствующие значения температуры. Таким образом, получим две пары (код АЦП, значение температуры); код АЦП мы берем уже «рафинированный», то есть сглаженный и с повышенной разрядностью; при этом вид точки контроллер должен определять сам (это просто – если текущий код АЦП больше какого-то порога – точка «горячая», если меньше – точка «холодная»; кстати: нужно не забывать, что наше АЦП долго раскачивается, поэтому калибровать устройство нужно после того, как значение текущей температуры перестало расти).
Посмотрим, как будет осуществляться калибровка с точки зрения пользователя. Во-первых, нужно продумать, как пользователь может начать калибровку – и вот тут-то нам наконец-то пригодится «админская» кнопка, или «кнопка 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 =ˆˆ=