|
@@ -6,10 +6,12 @@
|
|
|
//#include "stab_usart.h"
|
|
|
#include "st7735.h"
|
|
|
|
|
|
+/* Defines */
|
|
|
/* Версия скетча и длина версии скетча в символах для правильного вывода на дисплей */
|
|
|
#define VERSION "v0.100"
|
|
|
#define VERSION_LEN 6
|
|
|
|
|
|
+/* Variables */
|
|
|
static uint16_t Pnom; // Номинальная мощность ТЭНа (хранится в EEPROM и устанавливается из менюшки)
|
|
|
//const uint8_t ARRAY_SIZE = max(Pnom_ARR_SIZE,PDMset_ARR_SIZE);
|
|
|
static uint16_t PDMset[2][ARRAY_SIZE] = {}; // Массив уставок мощности ТЭНа с адресами
|
|
@@ -31,6 +33,8 @@ static char buf[24]; // common string buffer for chsnprintf()
|
|
|
|
|
|
static volatile uint8_t PID_ust = LINE_FREQ; // Данные для установки регистра сравнения таймера2
|
|
|
|
|
|
+static virtual_timer_t second_vt, hz50_vt;
|
|
|
+
|
|
|
/* Организуем флаги и индикаторы в структуру */
|
|
|
static volatile struct flags { // Флаги
|
|
|
unsigned dspRefresh : 1; // Флаг выхода из режима меню / полного обновления экрана
|
|
@@ -61,18 +65,6 @@ static volatile struct flags { // Флаги
|
|
|
#endif
|
|
|
} fl = {}; /* Инициализируем структуру с нулевыми членами */
|
|
|
|
|
|
-//static uint8_t fl_A; // Байт флажков A
|
|
|
-//static uint8_t fl_B; // Байт флажков B
|
|
|
-//static uint8_t fl_C; // Байт флажков C
|
|
|
-//#define flA_dspRefresh B00000001
|
|
|
-//#define flA_dspTimeout B00000010
|
|
|
-//#define flA_dspNewData B00000100
|
|
|
-//#define flA_uartUnhold B00001000
|
|
|
-//#define flA_uartReport B00010000
|
|
|
-//#define flA_uartTimeout B00100000
|
|
|
-//#define flA_writable B01000000
|
|
|
-//#define flA_butt B10000000
|
|
|
-
|
|
|
static uint8_t cnt_Pnom_count; // Количество предустановок мощности
|
|
|
static uint8_t cnt_Pnom_number; // Номер активной предустановки мощности
|
|
|
static uint8_t cnt_PDMcount; // Счетчик для перебора уставок мощности ТЭНа
|
|
@@ -80,9 +72,31 @@ static uint8_t cnt_PDMcount; // Счетчик для перебора уст
|
|
|
static uint8_t cnt_menuWDT; // Счетчик секунд для организации отсчета ожидания выхода из меню
|
|
|
static uint8_t cnt_dspMenu; // Индикатор режима меню
|
|
|
|
|
|
+/* Functions */
|
|
|
static uint8_t X_position(const uint8_t x, const uint16_t arg, const uint8_t pix); // Функция возвращает начальную позицию по Х для десятичного числа, в зависимости от количества знаков в нём.
|
|
|
static uint8_t X_centred(const uint8_t len); // Функция возвращает начальную позицию по Х для текста длинной len знаков, для размещения оного по центру дисплея.
|
|
|
static uint16_t calc_proportion(const uint16_t multiplier1, const uint16_t multiplier2, const uint32_t divider);
|
|
|
+static void Buttons_(void);
|
|
|
+static void second_new_cb(virtual_timer_t *vtp, void *p);
|
|
|
+static void hz50_cb(virtual_timer_t *vtp, void *p);
|
|
|
+
|
|
|
+/*
|
|
|
+ * Threads
|
|
|
+ */
|
|
|
+
|
|
|
+/* BTN serving thread. */
|
|
|
+static THD_WORKING_AREA(waBTNThread, 256);
|
|
|
+static __attribute__((noreturn)) THD_FUNCTION(BTNThread, arg) {
|
|
|
+ (void)arg;
|
|
|
+ chRegSetThreadName("BTN_process");
|
|
|
+
|
|
|
+ systime_t time = chVTGetSystemTimeX();
|
|
|
+ while (true) {
|
|
|
+ time += TIME_MS2I(BTN_SCAN_PERIOD);
|
|
|
+ Buttons_();
|
|
|
+ chThdSleepUntil(time);
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
/*
|
|
|
* ПРОЦЕДУРЫ И ФУНКЦИИ
|
|
@@ -243,38 +257,6 @@ static void change_arr_cell(uint16_t arr[2][ARRAY_SIZE], const uint8_t index, co
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
-/**
|
|
|
- * @brief Инициализация АЦП
|
|
|
- */
|
|
|
-static void ADC_init(void) {
|
|
|
- ADMUX = 0;
|
|
|
- ADMUX |= ( 1 << REFS0); // Задаем ИОН равный напряжению питания
|
|
|
- ADMUX |= 0; // Выбираем пин A0 для преобразования
|
|
|
- ADCSRA |= (1 << ADPS2 ) | (1 << ADPS1) | (1 << ADPS0); // предделитель на 128
|
|
|
- ADCSRA |= (1 << ADIE); // Разрешаем прерывания по завершении преобразования
|
|
|
- ADCSRA |= (1 << ADEN); // Включаем АЦП
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * @brief Инициализация таймеров
|
|
|
- */
|
|
|
-static void Timers_init(void) {
|
|
|
- //---Инициализация таймера 0 для тактирования АЦП -------------
|
|
|
- TCCR0A = 0;
|
|
|
- TCCR0B = 0;
|
|
|
- TCCR0A |= (1 << WGM01); // Счетчик работает в режиме CTC (сброс по совпадению)
|
|
|
- TCCR0B |= (1 << CS01) | (1 << CS00); // Предделитель на 64 (на счетчик - 250 кГц)
|
|
|
- OCR0A = T_ADC; // Определяет период запуска АЦП
|
|
|
- TIMSK0 |= (1 << OCIE0A); // Разрешаем прерывания по совпадению с OCR0A
|
|
|
- // Инициализация таймера 2 для формирования импульса нуля Zero
|
|
|
- TCCR2A = 0;
|
|
|
- TCCR2B = 0;
|
|
|
- TCCR2A |= (1 << WGM21); // Счетчик работает в режиме CTC (сброс по совпадению)
|
|
|
- TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20); // Предделитель на 1024 (сч. - 15.625 кГц/64мкс)
|
|
|
- OCR2A = LINE_FREQ; // Прерывание с удвоенной частотой сети
|
|
|
- TIMSK2 |= (1 << OCIE2A); // Разрешаем прерывания по совпадению с OCR2A
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* @brief Подпрограмма обработки режима разгона
|
|
|
* Обеспечивает шунтирование контактов контактного реле
|
|
@@ -282,7 +264,6 @@ static void Timers_init(void) {
|
|
|
* в момент включения/выключения режима "Разгон"
|
|
|
*/
|
|
|
static void Razgon_(void) {
|
|
|
-#define RELAY_SHUNTING_TIME 50 // количество полупериодов, в течение которых шунтируются контакты реле
|
|
|
static uint8_t cnt_P_relay=0; // Счетчик полупериодов шунтирования контактного реле
|
|
|
if (fl.razg_on && // Если включен разгон..
|
|
|
!fl.TRelay && // ..и НЕ включено контактное реле
|
|
@@ -304,7 +285,8 @@ static void Razgon_(void) {
|
|
|
static void PDM_(void) {
|
|
|
if (fl.razg) {
|
|
|
pdm = CICLE; // В режиме разгона твердотельное всегда открыто
|
|
|
- }
|
|
|
+ } /* ??? поставити також fl.Tout та вийти. авіщо при разгоні все інше? */
|
|
|
+
|
|
|
static int8_t ps = 0; // Текущее значение постоянной составляющей
|
|
|
int32_t lev = pdm + pdm_err; // Текущий уровень с учетом ошибки дискретизации, сделанной на предыдущем полупериоде.
|
|
|
// Текущее значение постоянной составляющей
|
|
@@ -397,7 +379,7 @@ static void Buttons_(void) {
|
|
|
break;
|
|
|
}
|
|
|
switch (butt) {
|
|
|
- case 1: { //-----Кнопкой "P-" перебираем записанные значения или уменьшаем значение Pnom
|
|
|
+ case 1: { //Кнопкой "P-" перебираем записанные значения или уменьшаем значение Pnom
|
|
|
if (bt.no_select) { //Если не выбираем, а вводим значение,...
|
|
|
if (butt_force_count > 20) { // Если очень долго держим...
|
|
|
if (Pnom > 100) {
|
|
@@ -425,7 +407,7 @@ static void Buttons_(void) {
|
|
|
butt_force_count++;
|
|
|
break; //Закончили
|
|
|
}
|
|
|
- case 2: { //-----Кнопкой "P+" увеличиваем значение Pnom
|
|
|
+ case 2: { //Кнопкой "P+" увеличиваем значение Pnom
|
|
|
if (butt_force_count > 20) {
|
|
|
if ((Pnom += 100) > 9999) {
|
|
|
Pnom=9999; // Если очень долго держим, прибавляем по соточке
|
|
@@ -443,29 +425,29 @@ static void Buttons_(void) {
|
|
|
butt_force_count++;
|
|
|
break; //Закончили
|
|
|
}
|
|
|
- case 4: { //-----Кнопкой "Стоп" пишем значение в память и выходим из менюшки
|
|
|
- bt.writePnom = 1; // Ставим флаг записи нового значения Pnom в EEPROM
|
|
|
+ case 4: { //Кнопкой "Стоп" пишем значение в память и выходим из менюшки
|
|
|
#ifdef USE_EEPROM
|
|
|
+ bt.writePnom = 1; // Ставим флаг записи нового значения Pnom в EEPROM
|
|
|
fl.writable = 1; // Ставим флаг записи уставок в EEPROM
|
|
|
#endif
|
|
|
}
|
|
|
- case 8: { //-----Кнопкой "Разгон" выходим из менюшки
|
|
|
+ case 8: { //Кнопкой "Разгон" выходим из менюшки
|
|
|
if (Pnom < 10000) { // Если значение реальное...
|
|
|
cnt_Pnom_number = cnt_PDMcount; // Запомним порядковый номер выбранного Pnom
|
|
|
if (bt.no_select) { // Если значение НЕ выбрано из записанных в EEPROM, а введено...
|
|
|
for (int8_t x = cnt_Pnom_count; x >= 0; x--) { // Проверим новое значение на совпадение с уже записанными
|
|
|
if (Pnom == PDMset[0][x]) { // Если такое значение уже есть в EEPROM...
|
|
|
cnt_Pnom_number = x; // Запомним порядковый номер совпавшего Pnom
|
|
|
- bt.writePnom = 0; // Снимем флаг записи нового значения Pnom в EEPROM
|
|
|
#ifdef USE_EEPROM
|
|
|
+ bt.writePnom = 0; // Снимем флаг записи нового значения Pnom в EEPROM
|
|
|
fl.writable = 1; // Ставим флаг записи уставок в EEPROM
|
|
|
#endif
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
} else { // Если значение выбрано из записанных в EEPROM...
|
|
|
- bt.writePnom = 0; // Снимем флаг записи нового значения Pnom в EEPROM
|
|
|
#ifdef USE_EEPROM
|
|
|
+ bt.writePnom = 0; // Снимем флаг записи нового значения Pnom в EEPROM
|
|
|
fl.writable = 1; // Ставим флаг записи уставок в EEPROM
|
|
|
#endif
|
|
|
}
|
|
@@ -476,12 +458,12 @@ static void Buttons_(void) {
|
|
|
EEPROM_read_PDMs(); // читаем ранее записанное
|
|
|
}
|
|
|
#endif
|
|
|
- if (bt.writePnom) { // Запишем новое значение Pnom, если необходимо
|
|
|
#ifdef USE_EEPROM
|
|
|
+ if (bt.writePnom) { // Запишем новое значение Pnom, если необходимо
|
|
|
eeprom_update_word((uint16_t*)(cnt_Pnom_number * 2),Pnom);
|
|
|
-#endif
|
|
|
bt.writePnom = 0; // и сбросим флаг записи нового значения Pnom
|
|
|
}
|
|
|
+#endif
|
|
|
cnt_dspMenu = 0; // Снимаем флаг перехода в меню
|
|
|
//
|
|
|
#ifdef USE_USART
|
|
@@ -597,13 +579,13 @@ static void Buttons_(void) {
|
|
|
}
|
|
|
break;
|
|
|
case 4:
|
|
|
- if (PDMust == 0) { Если мы не в меню и мощность ТЭНа нулевая, то...
|
|
|
+ if (PDMust == 0) { // Если мы не в меню и мощность ТЭНа нулевая, то...
|
|
|
cnt_dspMenu = 1; //Ставим флаг перехода в меню
|
|
|
fl.dspRefresh = 1; //Ставим флаг обновления экрана
|
|
|
} else { //Если мы не в меню и мощность ТЭНа НЕнулевая, то...
|
|
|
remember_last_power_setting();// Запомним последнюю уставку
|
|
|
- PDMust = 0; // Экстренно выключим ТЭН
|
|
|
- stop_razgon(); // Остановим разгон
|
|
|
+ PDMust = 0; //Экстренно выключим ТЭН
|
|
|
+ stop_razgon(); //Остановим разгон
|
|
|
}
|
|
|
fl.butt = 0; //После нажатия должна быть пауза
|
|
|
break;
|
|
@@ -648,69 +630,20 @@ static void Buttons_(void) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * @brief Обработчик начала очередного полупериода по таймеру2
|
|
|
- */
|
|
|
-ISR(TIMER2_COMPA_vect) {
|
|
|
- Razgon_();
|
|
|
- if (pdm) {
|
|
|
- PDM_();
|
|
|
- } else {
|
|
|
- fl.Tout = 0; // Не будем зря теребить подпрограмму, если pdm = 0
|
|
|
- //pdm_err = 0; // и обнулим ошибку дискретизации (а нужно ли?) TODO!
|
|
|
- }
|
|
|
-
|
|
|
- fl.PP_tm = !fl.PP_tm; // Инвертируем флаг полуволны
|
|
|
- OCR2A = PID_ust; // Грузим новое значение в регистр сравнения
|
|
|
-
|
|
|
- fl.Tout ? TURN_SSR_ON : TURN_SSR_OFF ; // Включаем или выключаем ТЭН (твердотельное реле)
|
|
|
- fl.TRelay ? TURN_RELAY_ON : TURN_RELAY_OFF ; // Включаем или выключаем ТЭН (контактное реле)
|
|
|
-
|
|
|
- sei(); // разрешим прерывания
|
|
|
- // Считаем время
|
|
|
- static uint8_t cnt_P_time=0; // Счетчик полупериодов для организации отсчета времени
|
|
|
- if (++cnt_P_time == P_TIME_MAX) { // Уже секунду суммируем
|
|
|
- cnt_P_time = 0;
|
|
|
- //fl.dspNewData = 1; // Раз в секунду не грех обновить дисплей, мало ли...
|
|
|
- if ((cnt_dspMenu > 0) && (++cnt_menuWDT == MENU_TIMEOUT)) { // Если мы в меню и слишком долго не жмутся кнопки
|
|
|
- fl.dspTimeout = 1; // Установим флаг таймаута
|
|
|
- cnt_menuWDT = 0; // Сбросим таймер ожидания выхода из меню
|
|
|
- }
|
|
|
-
|
|
|
-#ifdef USE_USART
|
|
|
- if (++cnt_uartWDT == 10) { // Если прошло уже 10 секунд от начала приема посылки по USART
|
|
|
- fl.uartTimeout = 1; // Установим флаг таймаута ожидания окончания посылки
|
|
|
- cnt_uartWDT = 0; // Сбросим таймер ожидания окончания посылки
|
|
|
- }
|
|
|
-#endif
|
|
|
-#ifdef USE_ADprotocol
|
|
|
- fl.uartReport = 1; // пора слать рапорт
|
|
|
-#endif
|
|
|
- }
|
|
|
-
|
|
|
- Buttons_(); // Опрашиваем кнопки
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * @brief Обработчик запуска преобразования АЦП по таймеру0
|
|
|
- */
|
|
|
-ISR(TIMER0_COMPA_vect) {
|
|
|
- ADCSRA |= (1 << ADSC); // Запуск преобразования
|
|
|
-}
|
|
|
|
|
|
/**
|
|
|
* @brief Обработчик окончания преобразования АЦП
|
|
|
*/
|
|
|
-ISR(ADC_vect) {
|
|
|
+static void ADC_cb(void) {
|
|
|
static uint8_t TM2_current;
|
|
|
static int16_t Ufir = 0; // Буферная переменная для НЧ-фильтрации
|
|
|
static int16_t Udelta = 0; // Буферная переменная для НЧ-фильтрации
|
|
|
{
|
|
|
int16_t U_adc;
|
|
|
uint8_t TM2_tmp;
|
|
|
- TM2_tmp = TCNT2; // забрали значение из таймера синхронизации с сетью
|
|
|
- U_adc = ADCL;
|
|
|
- U_adc += ADCH << 8; // забрали результат преобразования АЦП
|
|
|
+ //!!! TM2_tmp = TCNT2; // забрали значение из таймера синхронизации с сетью
|
|
|
+ //!U_adc = ADCL;
|
|
|
+ //!U_adc += ADCH << 8; // забрали результат преобразования АЦП
|
|
|
U_adc -= U_ZERO; // Убираем постоянную составляющую из оцифрованного сигнала
|
|
|
{ //Суммирование квадратов
|
|
|
sum += (long)U_adc * U_adc; // Возводим в квадрат выборку АЦП и суммируем с предыдущими
|
|
@@ -761,13 +694,12 @@ ISR(ADC_vect) {
|
|
|
}
|
|
|
|
|
|
/* детекция перехода через ноль и ПИД-синхронизация */
|
|
|
- sei(); // Следующие фрагменты длительны, но не требуют атомарности; разрешим прерывания
|
|
|
-
|
|
|
if (fl.zero) { // ПИД-подстройка частоты внутреннего таймера к частоте сети
|
|
|
- static uint16_t PID_reg = PID_ust << Km; // Функция управления ПИД
|
|
|
- static int32_t PID_err_old = 0; // Разность фаз из предыдущего шага
|
|
|
- static int32_t PID_int = 0; // Интегральная составляющая из предыдущего шага
|
|
|
+ static uint16_t PID_reg = LINE_FREQ << Km; // Функция управления ПИД
|
|
|
+ static int32_t PID_err_old = 0; // Разность фаз из предыдущего шага
|
|
|
+ static int32_t PID_int = 0; // Интегральная составляющая из предыдущего шага
|
|
|
int32_t temp_32 = (TM2_current + PHASE) << Km; // Разность фаз
|
|
|
+
|
|
|
if (!fl.PP_tm) {
|
|
|
temp_32 -= PID_reg + (1 << Km); // Разность фаз должна быть с соответствующим знаком
|
|
|
}
|
|
@@ -776,6 +708,7 @@ ISR(ADC_vect) {
|
|
|
PID_reg += PID_int;
|
|
|
PID_reg += ( temp_32 - PID_err_old ) >> Kd;
|
|
|
PID_err_old = temp_32;
|
|
|
+
|
|
|
// Готовим данные для записи в регистр сравнения таймера 2
|
|
|
if ( PID_reg > (T_MAX << Km)) {
|
|
|
PID_reg = (T_MAX << Km); // Ограничим сверху
|
|
@@ -861,8 +794,14 @@ void Stab_Init(void) {
|
|
|
TURN_SSR_OFF; // Выключаем ТЭН (твердотельное реле)
|
|
|
BTNS_ON; // Activate buttons
|
|
|
|
|
|
- ADC_init(); // Инициализируем АЦП
|
|
|
- Timers_init(); // Инициализируем таймеры
|
|
|
+ // !!! TODO !!! Инициализируем АЦП
|
|
|
+
|
|
|
+ /* Starting a virtual timers. */
|
|
|
+ chVTSetContinuous(&hz50_vt, TIME_MS2I(LINE_PERIOD), hz50_cb, NULL);
|
|
|
+ chVTSetContinuous(&second_vt, TIME_MS2I(1000), second_new_cb, NULL);
|
|
|
+
|
|
|
+ /* Starting the Button thread. */
|
|
|
+ chThdCreateStatic(waBTNThread, sizeof(waBTNThread), NORMALPRIO, BTNThread, NULL);
|
|
|
|
|
|
pp_Delay(20); // Подождем 20 полупериодов
|
|
|
|
|
@@ -1138,3 +1077,55 @@ void Stab_WorkCycle(void) {
|
|
|
}
|
|
|
#endif
|
|
|
}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief Process asks for 1 second period
|
|
|
+ *
|
|
|
+ * @param vtp
|
|
|
+ * @param p
|
|
|
+ */
|
|
|
+static void second_new_cb(virtual_timer_t *vtp, void *p) {
|
|
|
+ (void)vtp;
|
|
|
+ (void)p;
|
|
|
+
|
|
|
+ //fl.dspNewData = 1; // Раз в секунду не грех обновить дисплей, мало ли...
|
|
|
+ if ((cnt_dspMenu > 0) && (++cnt_menuWDT == MENU_TIMEOUT)) { // Если мы в меню и слишком долго не жмутся кнопки
|
|
|
+ fl.dspTimeout = 1; // Установим флаг таймаута
|
|
|
+ cnt_menuWDT = 0; // Сбросим таймер ожидания выхода из меню
|
|
|
+ }
|
|
|
+
|
|
|
+#ifdef USE_USART
|
|
|
+ if (++cnt_uartWDT == 10) { // Если прошло уже 10 секунд от начала приема посылки по USART
|
|
|
+ fl.uartTimeout = 1; // Установим флаг таймаута ожидания окончания посылки
|
|
|
+ cnt_uartWDT = 0; // Сбросим таймер ожидания окончания посылки
|
|
|
+ }
|
|
|
+#endif
|
|
|
+#ifdef USE_ADprotocol
|
|
|
+ fl.uartReport = 1; // пора слать рапорт
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief Process tasks with line frewuency
|
|
|
+ *
|
|
|
+ * @param vtp
|
|
|
+ * @param p
|
|
|
+ */
|
|
|
+static void hz50_cb(virtual_timer_t *vtp, void *p) {
|
|
|
+ (void)vtp;
|
|
|
+ (void)p;
|
|
|
+
|
|
|
+ Razgon_();
|
|
|
+ if (pdm) {
|
|
|
+ PDM_();
|
|
|
+ } else {
|
|
|
+ fl.Tout = 0; // Не будем зря теребить подпрограмму, если pdm = 0
|
|
|
+ //pdm_err = 0; // и обнулим ошибку дискретизации (а нужно ли?) TODO!
|
|
|
+ }
|
|
|
+
|
|
|
+ fl.PP_tm = !fl.PP_tm; // Инвертируем флаг полуволны
|
|
|
+ // !!! OCR2A = PID_ust; // Грузим новое значение в регистр сравнения
|
|
|
+
|
|
|
+ fl.Tout ? TURN_SSR_ON : TURN_SSR_OFF ; // Включаем или выключаем ТЭН (твердотельное реле)
|
|
|
+ fl.TRelay ? TURN_RELAY_ON : TURN_RELAY_OFF ; // Включаем или выключаем ТЭН (контактное реле)
|
|
|
+}
|