stab.c 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  1. #include <math.h>
  2. #include "ch.h"
  3. #include "hal.h"
  4. #include "chprintf.h"
  5. #include "stab_param.h"
  6. //#include "stab_usart.h"
  7. #include "st7735.h"
  8. /* Версия скетча и длина версии скетча в символах для правильного вывода на дисплей */
  9. #define VERSION "v0.100"
  10. #define VERSION_LEN 6
  11. static uint16_t Pnom; // Номинальная мощность ТЭНа (хранится в EEPROM и устанавливается из менюшки)
  12. //const uint8_t ARRAY_SIZE = max(Pnom_ARR_SIZE,PDMset_ARR_SIZE);
  13. static uint16_t PDMset[2][ARRAY_SIZE] = {}; // Массив уставок мощности ТЭНа с адресами
  14. //static uint16_t (&Pnom_arr)[ARRAY_SIZE] = PDMset[0]; // Массив мощностей ТЭНа как ссылка на нулевую строку массива уставок
  15. // (&Pnom_arr) - посилання на PDMset[0], в С не працює, замінив по тексту.
  16. static volatile uint32_t sum; // Сумматор квадратов отсчетов АЦП
  17. static volatile uint16_t sc = 0; // Счетчик просуммированных квадратов
  18. static volatile uint16_t sc_sum = 0; // Счетчик просуммированных квадратов, готовый к обработке
  19. static volatile uint16_t Pust = 0; // Установленная мощность ТЭНа
  20. static volatile uint16_t pdm = 0; // Текущий уровень PDM (принимает значения от 0 до CICLE)
  21. static volatile int32_t pdm_err = 0; // Ошибка дискретизации
  22. static volatile uint16_t PDMust = 0; // PDM, соответствующий установленной мощности ТЭНа
  23. static volatile uint32_t U_sum = 0; // Среднеквадратичное в сети за секунду, умноженное на 10
  24. static uint16_t U_real = U_LINE; // Среднеквадратичное за секунду (целая часть)
  25. static uint8_t U_real_dec = 0; // Среднеквадратичное за секунду (дробная часть)
  26. static char buf[24]; // common string buffer for chsnprintf()
  27. static volatile uint8_t PID_ust = LINE_FREQ; // Данные для установки регистра сравнения таймера2
  28. /* Организуем флаги и индикаторы в структуру */
  29. static volatile struct flags { // Флаги
  30. unsigned dspRefresh : 1; // Флаг выхода из режима меню / полного обновления экрана
  31. unsigned dspTimeout : 1; // Флаг истечения времени ожидания выхода из меню
  32. unsigned dspNewData : 1; // Флаг обновления данных на экране
  33. unsigned PP : 1; // Флаг полупериода сети на входе АЦП (отрицательная полуволна = 0, положительная = 1)
  34. unsigned PP_fir : 1; // Флаг полупериода после КИХ ФНЧ (отрицательная полуволна = 0, положительная = 1)
  35. unsigned PP_tm : 1; // Флаг полупериода по внутреннему таймеру (отрицательная полуволна = 0, положительная = 1)
  36. unsigned zero : 1; // Флаг перехода через ноль
  37. unsigned NotZero : 1; // Флаг аварии сети (не детектируются переходы через ноль)
  38. unsigned sum : 1; // Флаг готовности насуммированных данных к обработке
  39. unsigned Tout : 1; // Флаг включения ТЭНа (твердотельное реле)
  40. unsigned TRelay : 1; // Флаг включения ТЭНа (контактное реле)
  41. unsigned Ulow : 1; // Флаг невозможности выдать установленный уровень мощности
  42. unsigned Udown : 1; // Флаг аварии сети (действующее напряжение ниже 100В)
  43. unsigned razg : 1; // Флаг режима "разгон"
  44. unsigned razg_on : 1; // Флаг начала режима "разгон"
  45. unsigned razg_off : 1; // Флаг останова режима "разгон"
  46. unsigned stab_off : 1; // Флаг аварийного останова стабилизатора
  47. unsigned butt : 1; // Флаг опроса кнопок
  48. #ifdef USE_EEPROM
  49. unsigned writable : 1; // Флаг записи уставок в EEPROM
  50. #endif
  51. #ifdef USE_USART
  52. unsigned uartUnhold : 1; // Флаг разрешения передачи данных по USART
  53. unsigned uartReport : 1; // Флаг разрешения отправки данных внешнему контроллеру
  54. unsigned uartTimeout : 1; // Флаг истечения времени приема посылки по USART
  55. #endif
  56. } fl = {}; /* Инициализируем структуру с нулевыми членами */
  57. //static uint8_t fl_A; // Байт флажков A
  58. //static uint8_t fl_B; // Байт флажков B
  59. //static uint8_t fl_C; // Байт флажков C
  60. //#define flA_dspRefresh B00000001
  61. //#define flA_dspTimeout B00000010
  62. //#define flA_dspNewData B00000100
  63. //#define flA_uartUnhold B00001000
  64. //#define flA_uartReport B00010000
  65. //#define flA_uartTimeout B00100000
  66. //#define flA_writable B01000000
  67. //#define flA_butt B10000000
  68. static uint8_t cnt_Pnom_count; // Количество предустановок мощности
  69. static uint8_t cnt_Pnom_number; // Номер активной предустановки мощности
  70. static uint8_t cnt_PDMcount; // Счетчик для перебора уставок мощности ТЭНа
  71. static uint8_t cnt_menuWDT; // Счетчик секунд для организации отсчета ожидания выхода из меню
  72. static uint8_t cnt_dspMenu; // Индикатор режима меню
  73. static uint8_t X_position(const uint8_t x, const uint16_t arg, const uint8_t pix); // Функция возвращает начальную позицию по Х для десятичного числа, в зависимости от количества знаков в нём.
  74. static uint8_t X_centred(const uint8_t len); // Функция возвращает начальную позицию по Х для текста длинной len знаков, для размещения оного по центру дисплея.
  75. static uint16_t calc_proportion(const uint16_t multiplier1, const uint16_t multiplier2, const uint32_t divider);
  76. /*
  77. * ПРОЦЕДУРЫ И ФУНКЦИИ
  78. */
  79. /**
  80. * @brief Функция возвращает начальную позицию по Х для десятичного числа,
  81. * в зависимости от количества знаков в нём.
  82. * @param arg выводимое число;
  83. * @param х позиция для arg, если бы оно было однозначно;
  84. * @param pix ширина шрифта в пикселях.
  85. */
  86. static uint8_t X_position(const uint8_t x, const uint16_t arg, const uint8_t pix) {
  87. if (arg < 10) {
  88. return pix * x;
  89. } else if (arg < 100) {
  90. return pix * (x-1);
  91. } else if (arg < 1000) {
  92. return pix * (x-2);
  93. } else {
  94. return pix * (x-3);
  95. }
  96. }
  97. /**
  98. * @brief Функция возвращает начальную позицию по Х для текста длинной len знаков,
  99. * для размещения оного по центру дисплея.
  100. * @param len Количество знакомест в тексте
  101. */
  102. static uint8_t X_centred(const uint8_t len) {
  103. uint8_t wdt = ST7735_WIDTH; // Ширина дисплея в пикселях
  104. uint8_t pix = 7; // Ширина шрифта в пикселях
  105. if (len > wdt/pix) {
  106. return 0;
  107. } else {
  108. return (wdt - (len * pix))/2;
  109. }
  110. }
  111. #ifdef USE_EEPROM
  112. /**
  113. * @brief Функция переводит символ ASCII в шестнадцатиричную цифру,
  114. * @return при ошибке возвращает 255
  115. * @param a символ 0...F
  116. */
  117. static uint8_t A_to_HEX (const char a) {
  118. if (a >= 48 && a <= 57) { // Если а - от 0 до 9
  119. return (uint8_t)(a-48);
  120. } else if (a >= 65 && a <= 70) { // Если а - от A до F
  121. return (uint8_t)(a-55);
  122. } else if (a >= 97 && a <= 102) { // Если а - от a до f
  123. return (uint8_t)(a-87);
  124. } else {
  125. return 255;
  126. }
  127. }
  128. /**
  129. * @brief Функция переводит шестнадцатиричную цифру в символ ASCII,
  130. * @return при ошибке возвращает X
  131. * @param x число, кое необходимо перевести в ASCII-код
  132. */
  133. static char HEX_to_A (const uint8_t x) {
  134. if (x <= 9) {
  135. return (char)(x + 48);
  136. } else if (x <= 15) {
  137. return (char)(x + 55);
  138. } else {
  139. return 'X';
  140. }
  141. }
  142. #endif
  143. /**
  144. * @brief Подпрограммка остановки режима "Разгон"
  145. */
  146. static void stop_razgon(void) {
  147. fl.razg_on = 0; //выключим режим разгона
  148. fl.TRelay = 0; //выключим контактное реле
  149. }
  150. /**
  151. * @brief Подпрограммка подсчета Pust
  152. */
  153. static void set_Pust(void) {
  154. Pust = calc_proportion(PDMust, Pnom, CICLE);
  155. }
  156. /**
  157. * @brief Функция пропорционального пересчета параметра
  158. * @returns Возвращает значение параметра с округлением,
  159. * пересчитанное из пропорции по формуле
  160. * (multiplier1 * multiplier2 / divider)
  161. * @param multiplier1 первый множитель,
  162. * @param multiplier2 второй множитель (по умолчанию Pnom),
  163. * @param divider делитель (по умолчанию CICLE).
  164. */
  165. static uint16_t calc_proportion(const uint16_t multiplier1, const uint16_t multiplier2, const uint32_t divider) {
  166. uint32_t p;
  167. p = (long)multiplier1 * 2;
  168. p *= (long)multiplier2;
  169. p /= divider;
  170. p ++;
  171. p /= 2;
  172. return (uint16_t)p;
  173. }
  174. /**
  175. * @brief Пауза, измеряется в полупериодах
  176. */
  177. static void pp_Delay(const uint16_t pp) {
  178. uint16_t PPcount = 0; // счетчик полупериодов
  179. bool PP_tm_last = 0;
  180. while (PPcount < pp) {
  181. //if (fl.PP_sint) {
  182. // PPcount++;
  183. // fl.PP_sint = 0;
  184. //}
  185. if (PP_tm_last != fl.PP_tm) {
  186. PPcount++;
  187. PP_tm_last = fl.PP_tm;
  188. }
  189. }
  190. }
  191. /**
  192. * @brief Подпрограмма запоминания последней уставки
  193. * Проверяет последнюю уставку на совпадение с уже записанными
  194. * в массив уставок и запоминает, если надо
  195. */
  196. static void remember_last_power_setting(void) { // Запомним последнюю уставку
  197. bool isnew = 1;
  198. for (int8_t x = PDMset_ARR_SIZE - 1; x >= 0; x--) { // Проверим новое значение на совпадение с уже записанными
  199. if (PDMust == PDMset[0][x]) {
  200. isnew = 0;
  201. break;
  202. }
  203. }
  204. if (isnew) { // Если новое значение действительно новое, то...
  205. PDMset[0][PDMset_ARR_SIZE - 1] = PDMust; //Запоминаем текущую мощность ТЭНа
  206. PDMset[1][PDMset_ARR_SIZE - 1] = 0; //Адрес зануляем на всякий случай
  207. cnt_PDMcount = PDMset_ARR_SIZE - 1; //Ставим счетчик на запомненную уставку
  208. }
  209. }
  210. #ifdef USE_EEPROM
  211. /**
  212. * @brief Подпрограмма обмена двух ячеек массива
  213. * @param arr массив,
  214. * @param index индекс первого измерения обмениваемых ячеек,
  215. * @param index1 индех второго измерения первой обмениваемой ячейки,
  216. * @param index2 индех второго измерения второй обмениваемой ячейки.
  217. */
  218. static void change_arr_cell(uint16_t arr[2][ARRAY_SIZE], const uint8_t index, const uint8_t index1, const uint8_t index2) {
  219. uint16_t k = arr[index][index1];
  220. arr[index][index1] = arr[index][index2];// Обмениваемся
  221. arr[index][index2] = k;
  222. }
  223. #endif
  224. /**
  225. * @brief Инициализация АЦП
  226. */
  227. static void ADC_init(void) {
  228. ADMUX = 0;
  229. ADMUX |= ( 1 << REFS0); // Задаем ИОН равный напряжению питания
  230. ADMUX |= 0; // Выбираем пин A0 для преобразования
  231. ADCSRA |= (1 << ADPS2 ) | (1 << ADPS1) | (1 << ADPS0); // предделитель на 128
  232. ADCSRA |= (1 << ADIE); // Разрешаем прерывания по завершении преобразования
  233. ADCSRA |= (1 << ADEN); // Включаем АЦП
  234. }
  235. /**
  236. * @brief Инициализация таймеров
  237. */
  238. static void Timers_init(void) {
  239. //---Инициализация таймера 0 для тактирования АЦП -------------
  240. TCCR0A = 0;
  241. TCCR0B = 0;
  242. TCCR0A |= (1 << WGM01); // Счетчик работает в режиме CTC (сброс по совпадению)
  243. TCCR0B |= (1 << CS01) | (1 << CS00); // Предделитель на 64 (на счетчик - 250 кГц)
  244. OCR0A = T_ADC; // Определяет период запуска АЦП
  245. TIMSK0 |= (1 << OCIE0A); // Разрешаем прерывания по совпадению с OCR0A
  246. // Инициализация таймера 2 для формирования импульса нуля Zero
  247. TCCR2A = 0;
  248. TCCR2B = 0;
  249. TCCR2A |= (1 << WGM21); // Счетчик работает в режиме CTC (сброс по совпадению)
  250. TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20); // Предделитель на 1024 (сч. - 15.625 кГц/64мкс)
  251. OCR2A = LINE_FREQ; // Прерывание с удвоенной частотой сети
  252. TIMSK2 |= (1 << OCIE2A); // Разрешаем прерывания по совпадению с OCR2A
  253. }
  254. /**
  255. * @brief Инициализация входов/выходов контроллера
  256. */
  257. static void Pins_init(void) {
  258. #ifdef DisplayReset
  259. pin_OLEDres_INIT;
  260. pin_OLEDres_LOW; // Сбрасываем дисплей (!!! НЕ ЗАБЫТЬ ПЕРЕКЛЮЧИТЬ НА ВЫСОКИЙ !!!) TODO!
  261. #endif
  262. pin_RAZGON_OFF_INIT;
  263. pin_STAB_OFF_INIT;
  264. pin_TOut_INIT;
  265. pin_TRelay_INIT;
  266. TURN_RELAY_OFF; // Выключаем ТЭН (контактное реле)
  267. TURN_SSR_OFF; // Выключаем ТЭН (твердотельное реле)
  268. pin_buttGND_INIT;
  269. pin_butt_1_INIT;
  270. pin_butt_2_INIT;
  271. pin_butt_3_INIT;
  272. pin_butt_4_INIT;
  273. }
  274. /**
  275. * @brief Подпрограмма обработки режима разгона
  276. * Обеспечивает шунтирование контактов контактного реле
  277. * симистором твердотельного
  278. * в момент включения/выключения режима "Разгон"
  279. */
  280. static void Razgon_(void) {
  281. #define RELAY_SHUNTING_TIME 50 // количество полупериодов, в течение которых шунтируются контакты реле
  282. static uint8_t cnt_P_relay=0; // Счетчик полупериодов шунтирования контактного реле
  283. if (fl.razg_on && // Если включен разгон..
  284. !fl.TRelay && // ..и НЕ включено контактное реле
  285. (++cnt_P_relay == RELAY_SHUNTING_TIME)) { // ..и все это длится уже более 500мс,
  286. fl.TRelay = 1;
  287. cnt_P_relay = 0; // то включим контактное реле и обнулим счетчик
  288. }
  289. if (fl.razg && // Если включен максимум для твердотельного реле..
  290. !fl.razg_on && // ..и выключен разгон
  291. (++cnt_P_relay == RELAY_SHUNTING_TIME)) { // ..и все это длится уже более 500мс,
  292. fl.razg = 0;
  293. cnt_P_relay = 0; // то выключим реле и обнулим счетчик
  294. }
  295. }
  296. /**
  297. * @brief Подпрограмма управления твердотельным реле ТЭНа
  298. */
  299. static void PDM_(void) {
  300. if (fl.razg) {
  301. pdm = CICLE; // В режиме разгона твердотельное всегда открыто
  302. }
  303. static int8_t ps = 0; // Текущее значение постоянной составляющей
  304. int32_t lev = pdm + pdm_err; // Текущий уровень с учетом ошибки дискретизации, сделанной на предыдущем полупериоде.
  305. // Текущее значение постоянной составляющей
  306. if (fl.PP_tm) {
  307. if (fl.Tout) {
  308. ps --;
  309. }
  310. } else {
  311. if (fl.Tout) {
  312. ps ++;
  313. }
  314. }
  315. if ((lev >= CICLE/2) && ((ps == 0) || (fl.PP_tm && (ps < 0)) || (!fl.PP_tm && (ps > 0)))) { // Ставим флаг включения ТЭНа с учетом значения постоянной составляющей
  316. fl.Tout = 1;
  317. pdm_err = lev - CICLE; // и считаем ошибку для следующего полупериода
  318. } else {
  319. fl.Tout = 0;
  320. pdm_err = lev; // Снимаем флаг включения ТЭНа и считаем ошибку
  321. }
  322. }
  323. /**
  324. * @brief Опрос кнопок
  325. */
  326. static void Buttons_(void) {
  327. static uint8_t butt = 0; // код текущей нажатой кнопки
  328. static uint8_t last_butt = 0; // код предыдущей нажатой кнопки
  329. static struct buttons {
  330. unsigned butt_1 : 1; // текущее состояние кнопки (0 - не нажата)
  331. unsigned butt_2 : 1; // текущее состояние кнопки
  332. unsigned butt_3 : 1; // текущее состояние кнопки
  333. unsigned butt_4 : 1; // текущее состояние кнопки
  334. unsigned no_select : 1; // вспомогательный флажок для начального меню
  335. #ifdef USE_EEPROM
  336. unsigned writePnom : 1; // вспомогательный флажок записи нового Pnom в EEPROM
  337. unsigned clear_old : 1; // вспомогательный флажок стирания старой уставки из EEPROM
  338. #endif
  339. } bt = {}; // Инициализируем структуру с нулевыми членами
  340. static uint8_t butt_count = 0; // счетчик для устранения дребезга
  341. static uint8_t butt_force_count = 0; // счетчик для форсирования инкремента/декремента
  342. #ifdef USE_EEPROM
  343. if (bt.clear_old) { // Стираем старую уставку, если нужно
  344. eeprom_update_word((uint16_t*)clear_old_addr,EMPTY_CELL_VALUE); // Стираем самую старую уставку
  345. bt.clear_old = 0; // Снимаем флажок стирания
  346. }
  347. #endif
  348. bt.butt_1 = pin_butt_1_STATE;
  349. bt.butt_2 = pin_butt_2_STATE;
  350. bt.butt_3 = pin_butt_3_STATE;
  351. bt.butt_4 = pin_butt_4_STATE;
  352. uint8_t button_sum = bt.butt_1 + bt.butt_2 + bt.butt_3 + bt.butt_4;
  353. if ((button_sum == 0) && butt_force_count) {
  354. butt_force_count --; // уменьшаем счетчик форсирования инкремента/декремента
  355. }
  356. if ( button_sum == fl.butt ) { // Или нажата одна кнопка или ни одной
  357. butt = bt.butt_1 + (bt.butt_2 << 1) + (bt.butt_3 << 2) + (bt.butt_4 << 3);
  358. if ( butt == last_butt ) {
  359. butt_count++;
  360. } else {
  361. butt_count = 1;
  362. last_butt = butt;
  363. }
  364. } else if (--butt_count < 1) {
  365. butt_count = 1;
  366. }
  367. if ( (butt_count == DEBOUNCE) || fl.dspTimeout ) { // Есть нажатая кнопка или достаточная пауза после нажатия или таймаут выхода из меню
  368. if (!fl.stab_off) { // Если нет аварийного останова...
  369. switch (cnt_dspMenu) { // Проверяем режимы меню
  370. case 2: { // Если мы в начальном меню выбора номинальной мощности, то...
  371. if (fl.dspTimeout) { // Если кнопки слишком долго не нажимались...
  372. if (PDMset[0][0] != 0xffff) { // и есть записанное значение, уходим
  373. cnt_Pnom_number = 0;
  374. Pnom = PDMset[0][0]; // По умолчанию установим номинальную мощность из нулевой ячейки
  375. #ifdef USE_EEPROM
  376. fl.writable = 1; // Уставки пишутся в EERPOM
  377. EEPROM_read_PDMs(); // Читаем уставки
  378. #endif
  379. #ifdef USE_USART
  380. fl.uartUnhold = 1; // Разрешим обращение к USART
  381. #endif
  382. cnt_dspMenu = 0; // Выйдем из менюшки
  383. fl.dspRefresh = 1; // Ставим флаг обновления экрана
  384. }
  385. fl.dspTimeout = 0; // Снимаем флаг таймаута выхода из меню
  386. break;
  387. }
  388. switch (butt) {
  389. case 1: { //-----Кнопкой "P-" перебираем записанные значения или уменьшаем значение Pnom
  390. if (bt.no_select) { //Если не выбираем, а вводим значение,...
  391. if (butt_force_count > 20) { // Если очень долго держим...
  392. if (Pnom > 100) {
  393. Pnom -= 100; // Убавляем по соточке, пока есть куда
  394. } else {
  395. butt_force_count = 10; // Если некуда убавлять - снижаем форсаж
  396. }
  397. } else if (butt_force_count > 10) { // Если долго держим...
  398. if (Pnom > 10) {
  399. Pnom -= 10; // Убавляем по десяточке, пока есть куда
  400. } else {
  401. butt_force_count = 0; // Если некуда убавлять - снижаем форсаж
  402. }
  403. } else {
  404. if (--Pnom == 0) {
  405. Pnom=1; // Убавляем по чуть-чуть
  406. }
  407. }
  408. } else { //Если выбираем из записанных в EEPROM...
  409. if (++cnt_PDMcount > cnt_Pnom_count) {
  410. cnt_PDMcount=0; // Перебираем значения уставок мощности ТЭНа
  411. }
  412. Pnom = PDMset[0][cnt_PDMcount];
  413. }
  414. butt_force_count++;
  415. break; //Закончили
  416. }
  417. case 2: { //-----Кнопкой "P+" увеличиваем значение Pnom
  418. if (butt_force_count > 20) {
  419. if ((Pnom += 100) > 9999) {
  420. Pnom=9999; // Если очень долго держим, прибавляем по соточке
  421. }
  422. } else if (butt_force_count > 10) {
  423. if ((Pnom += 10) > 9999) {
  424. Pnom=9999; // Если долго держим, прибавляем по десяточке
  425. }
  426. } else {
  427. if (++Pnom > 9999) {
  428. Pnom=9999; // Прибавляем по чуть-чуть
  429. }
  430. }
  431. bt.no_select = 1;
  432. butt_force_count++;
  433. break; //Закончили
  434. }
  435. case 4: { //-----Кнопкой "Стоп" пишем значение в память и выходим из менюшки
  436. bt.writePnom = 1; // Ставим флаг записи нового значения Pnom в EEPROM
  437. #ifdef USE_EEPROM
  438. fl.writable = 1; // Ставим флаг записи уставок в EEPROM
  439. #endif
  440. }
  441. case 8: { //-----Кнопкой "Разгон" выходим из менюшки
  442. if (Pnom < 10000) { // Если значение реальное...
  443. cnt_Pnom_number = cnt_PDMcount; // Запомним порядковый номер выбранного Pnom
  444. if (bt.no_select) { // Если значение НЕ выбрано из записанных в EEPROM, а введено...
  445. for (int8_t x = cnt_Pnom_count; x >= 0; x--) { // Проверим новое значение на совпадение с уже записанными
  446. if (Pnom == PDMset[0][x]) { // Если такое значение уже есть в EEPROM...
  447. cnt_Pnom_number = x; // Запомним порядковый номер совпавшего Pnom
  448. bt.writePnom = 0; // Снимем флаг записи нового значения Pnom в EEPROM
  449. #ifdef USE_EEPROM
  450. fl.writable = 1; // Ставим флаг записи уставок в EEPROM
  451. #endif
  452. break;
  453. }
  454. }
  455. } else { // Если значение выбрано из записанных в EEPROM...
  456. bt.writePnom = 0; // Снимем флаг записи нового значения Pnom в EEPROM
  457. #ifdef USE_EEPROM
  458. fl.writable = 1; // Ставим флаг записи уставок в EEPROM
  459. #endif
  460. }
  461. cnt_PDMcount=0; //Сбрасываем счетчик
  462. #ifdef USE_EEPROM
  463. if (fl.writable) { // Если уставки пишутся в EERPOM, то
  464. EEPROM_read_PDMs(); // читаем ранее записанное
  465. }
  466. #endif
  467. if (bt.writePnom) { // Запишем новое значение Pnom, если необходимо
  468. #ifdef USE_EEPROM
  469. eeprom_update_word((uint16_t*)(cnt_Pnom_number * 2),Pnom);
  470. #endif
  471. bt.writePnom = 0; // и сбросим флаг записи нового значения Pnom
  472. }
  473. cnt_dspMenu = 0; // Снимаем флаг перехода в меню
  474. //
  475. #ifdef USE_USART
  476. fl.uartUnhold = 1; // Разрешим обращение к USART
  477. #endif /* USE_USART */
  478. fl.dspRefresh = 1; // Ставим флаг обновления экрана
  479. }
  480. fl.butt = 0; // После нажатия должна быть пауза
  481. break; // Закончили
  482. }
  483. default:
  484. fl.butt = 1; // достаточная пауза между нажатиями
  485. }
  486. break;
  487. }
  488. case 1: { // Если мы в меню выбора уставки, то...
  489. if (fl.dspTimeout) { // Если кнопки слишком долго не нажимались, уходим
  490. cnt_dspMenu = 0; // Выйдем из менюшки
  491. fl.dspRefresh = 1; // Ставим флаг обновления экрана
  492. fl.dspTimeout = 0; // Снимаем флаг таймаута выхода из меню
  493. break;
  494. }
  495. switch (butt) {
  496. case 1: { // По кнопке "Р-" перебираем значения
  497. if (++cnt_PDMcount > PDMset_ARR_SIZE - 1) {
  498. cnt_PDMcount=0; //Перебираем значения уставок мощности ТЭНа
  499. }
  500. //fl.butt = 0; //После нажатия должна быть пауза
  501. break; //Закончили
  502. }
  503. case 2: { // По кнопке "Р+" перебираем значения
  504. if (cnt_PDMcount-- == 0) {
  505. cnt_PDMcount=PDMset_ARR_SIZE - 1; //Перебираем значения уставок мощности ТЭНа
  506. }
  507. //fl.butt = 0; //После нажатия должна быть пауза
  508. break; //Закончили
  509. }
  510. case 4: { //По кнопке "стоп" записываем уставку, если нужно, принимаем и выходим
  511. PDMust = PDMset[0][cnt_PDMcount]; //Устанавливаем выбранную мощность ТЭНа
  512. #ifdef USE_EEPROM
  513. if (fl.writable) { // Если уставки запоминаются...
  514. if (!PDMset[1][cnt_PDMcount]) { // Если просят записать НЕ уже записанное...
  515. //eeprom_update_word((uint16_t*)new_addr,PDMset[0][cnt_PDMcount]); // Пишем новую уставку
  516. PDMset[1][cnt_PDMcount] = new_addr; // Заносим в массив адрес свежезаписанной уставки
  517. new_addr += 2;
  518. if (new_addr > end_addr) {
  519. new_addr = start_addr; // Инкрементируем адрес для новой уставки и следим, чтобы не выходило за границы области
  520. }
  521. if (cnt_PDMcount == PDMset_ARR_SIZE - 1) { // Если новое значение - последнее в списке
  522. if (!old_addr) { // Если в массиве уставок есть незаписанные в EEPROM значения, то сначала стираем их
  523. bool swapped = 1;
  524. uint8_t upper_index = PDMset_ARR_SIZE - 1; //Пузырьковая сортировка
  525. while (swapped) { // Пока есть обмены, сортируем
  526. swapped = 0;
  527. for (uint8_t i = 1; i < upper_index; i++) {
  528. if (PDMset[1][i] < PDMset[1][i - 1]) {
  529. change_arr_cell(PDMset, 0, i, i - 1);
  530. change_arr_cell(PDMset, 1, i, i - 1);
  531. swapped = 1;
  532. }
  533. }
  534. upper_index --;
  535. } //Закончили сортировку
  536. old_addr = PDMset[1][0]; // Обновляем адрес самой старой уставки
  537. }
  538. if (old_addr) { // Если в массиве уставок все значения записаны в EEPROM, то стираем самое старое
  539. bt.clear_old = 1; // Ставим флажок стирания (сотрём в следующий вызов подпрограммы опроса кнопок)
  540. clear_old_addr = old_addr; // Плодим сущности без устали!
  541. }
  542. uint16_t k = PDMset[0][0];
  543. for (uint8_t x = 0; x < PDMset_ARR_SIZE - 1; x++) { // Сдвинем массив
  544. PDMset[0][x] = PDMset[0][x + 1];
  545. PDMset[1][x] = PDMset[1][x + 1];
  546. }
  547. PDMset[0][PDMset_ARR_SIZE - 1] = k; // Запишем во временную ячейку свежеудаленное значение
  548. PDMset[1][PDMset_ARR_SIZE - 1] = 0;
  549. cnt_PDMcount --;
  550. old_addr = PDMset[1][0]; // Обновляем адрес самой старой уставки
  551. }
  552. }
  553. }
  554. #endif /* USE_EEPROM */
  555. cnt_dspMenu = 0; //Снимаем флаг перехода в меню
  556. fl.dspRefresh = 1; //Ставим флаг обновления экрана
  557. fl.butt = 0; //После нажатия должна быть пауза
  558. break; //Закончили
  559. }
  560. case 8: { // По кнопке "разгон" принимаем и выходим
  561. PDMust = PDMset[0][cnt_PDMcount]; //Устанавливаем выбранную мощность ТЭНа
  562. cnt_dspMenu = 0; //Снимаем флаг перехода в меню
  563. fl.dspRefresh = 1; //Ставим флаг обновления экрана
  564. fl.butt = 0; //После нажатия должна быть пауза
  565. break; //Закончили
  566. }
  567. default:
  568. fl.butt = 1; // достаточная пауза между нажатиями
  569. }
  570. break;
  571. }
  572. default: { // А если не в меню, то...
  573. switch (butt) {
  574. case 1:
  575. if (PDMust-- == 0) {
  576. PDMust = 0; //Уменьшаем установленную мощность до минимума
  577. }
  578. break;
  579. case 2:
  580. if (++PDMust > CICLE) {
  581. PDMust = CICLE; //Увеличиваем установленную мощность до максимума
  582. }
  583. break;
  584. case 4:
  585. if (PDMust == 0) { Если мы не в меню и мощность ТЭНа нулевая, то...
  586. cnt_dspMenu = 1; //Ставим флаг перехода в меню
  587. fl.dspRefresh = 1; //Ставим флаг обновления экрана
  588. } else { //Если мы не в меню и мощность ТЭНа НЕнулевая, то...
  589. remember_last_power_setting();// Запомним последнюю уставку
  590. PDMust = 0; // Экстренно выключим ТЭН
  591. stop_razgon(); // Остановим разгон
  592. }
  593. fl.butt = 0; //После нажатия должна быть пауза
  594. break;
  595. case 8:
  596. fl.razg_on = ((!fl.NotZero) & (!fl.Udown) & (!fl.razg_off) & (!fl.razg_on)); //Триггер режима разгона (гистерезис организован в обработке начала полупериода)
  597. fl.razg |= fl.razg_on; //Если разгон включили, то твердотельное реле на максимум сразу
  598. fl.TRelay &= fl.razg_on; //Если разгон выключили, то контактное реле выключаем сразу
  599. fl.butt = 0; //После нажатия должна быть пауза
  600. break;
  601. default:
  602. fl.butt = 1; // достаточная пауза между нажатиями
  603. }
  604. }
  605. }
  606. }
  607. if (butt) { // Если нажата кнопка,
  608. cnt_menuWDT = 0; // сбросим таймер ожидания выхода из меню
  609. fl.stab_off = 0; // и сбросим флажок аварийного останова
  610. }
  611. butt_count = 1;
  612. butt = 0;
  613. set_Pust(); // Пересчитаем Pust
  614. fl.dspNewData = 1; //Обновление информации на дисплее
  615. }
  616. if (pin_STAB_OFF_STATE && !fl.stab_off) { // Если есть сигнал аварийного останова
  617. if (PDMust) { // Если уставка ненулевая...
  618. remember_last_power_setting();// Запомним последнюю уставку
  619. PDMust = 0; // Экстренно выключим ТЭН
  620. Pust = 0; // Пересчитаем Pust
  621. }
  622. stop_razgon(); // Остановим разгон
  623. fl.dspNewData = 1;//Обновление информации на дисплее
  624. fl.stab_off = 1; // Поставим соответствующий флажок
  625. } else {
  626. fl.razg_off = pin_RAZGON_OFF_STATE; // Прочитаем состояние вывода отключения разгона
  627. if (fl.razg_off && fl.razg_on) { // Если разгон и есть внешний сигнал останова разгона...
  628. fl.dspNewData = 1; //Обновление информации на дисплее
  629. stop_razgon(); // остановим разгон
  630. }
  631. }
  632. }
  633. /**
  634. * @brief Обработчик начала очередного полупериода по таймеру2
  635. */
  636. ISR(TIMER2_COMPA_vect) {
  637. Razgon_();
  638. if (pdm) {
  639. PDM_();
  640. } else {
  641. fl.Tout = 0; // Не будем зря теребить подпрограмму, если pdm = 0
  642. //pdm_err = 0; // и обнулим ошибку дискретизации (а нужно ли?) TODO!
  643. }
  644. fl.PP_tm = !fl.PP_tm; // Инвертируем флаг полуволны
  645. OCR2A = PID_ust; // Грузим новое значение в регистр сравнения
  646. fl.Tout ? TURN_SSR_ON : TURN_SSR_OFF ; // Включаем или выключаем ТЭН (твердотельное реле)
  647. fl.TRelay ? TURN_RELAY_ON : TURN_RELAY_OFF ; // Включаем или выключаем ТЭН (контактное реле)
  648. sei(); // разрешим прерывания
  649. // Считаем время
  650. static uint8_t cnt_P_time=0; // Счетчик полупериодов для организации отсчета времени
  651. if (++cnt_P_time == P_TIME_MAX) { // Уже секунду суммируем
  652. cnt_P_time = 0;
  653. //fl.dspNewData = 1; // Раз в секунду не грех обновить дисплей, мало ли...
  654. if ((cnt_dspMenu > 0) && (++cnt_menuWDT == MENU_TIMEOUT)) { // Если мы в меню и слишком долго не жмутся кнопки
  655. fl.dspTimeout = 1; // Установим флаг таймаута
  656. cnt_menuWDT = 0; // Сбросим таймер ожидания выхода из меню
  657. }
  658. #ifdef USE_USART
  659. if (++cnt_uartWDT == 10) { // Если прошло уже 10 секунд от начала приема посылки по USART
  660. fl.uartTimeout = 1; // Установим флаг таймаута ожидания окончания посылки
  661. cnt_uartWDT = 0; // Сбросим таймер ожидания окончания посылки
  662. }
  663. #endif
  664. #ifdef USE_ADprotocol
  665. fl.uartReport = 1; // пора слать рапорт
  666. #endif
  667. }
  668. Buttons_(); // Опрашиваем кнопки
  669. }
  670. /**
  671. * @brief Обработчик запуска преобразования АЦП по таймеру0
  672. */
  673. ISR(TIMER0_COMPA_vect) {
  674. ADCSRA |= (1 << ADSC); // Запуск преобразования
  675. }
  676. /**
  677. * @brief Обработчик окончания преобразования АЦП
  678. */
  679. ISR(ADC_vect) {
  680. static uint8_t TM2_current;
  681. static int16_t Ufir = 0; // Буферная переменная для НЧ-фильтрации
  682. static int16_t Udelta = 0; // Буферная переменная для НЧ-фильтрации
  683. {
  684. int16_t U_adc;
  685. uint8_t TM2_tmp;
  686. TM2_tmp = TCNT2; // забрали значение из таймера синхронизации с сетью
  687. U_adc = ADCL;
  688. U_adc += ADCH << 8; // забрали результат преобразования АЦП
  689. U_adc -= U_ZERO; // Убираем постоянную составляющую из оцифрованного сигнала
  690. { //Суммирование квадратов
  691. sum += (long)U_adc * U_adc; // Возводим в квадрат выборку АЦП и суммируем с предыдущими
  692. ++ sc; // Счетчик выборок АЦП
  693. }
  694. /* детекция перехода через ноль и ПИД-синхронизация */
  695. Udelta += (U_adc - Ufir);
  696. U_adc = Udelta / 32; //КИХ ФНЧ 1-го порядка с коэффициентом 1/32
  697. static uint8_t cnt_P_sum = 0; // Счетчик полупериодов для суммирования отсчетов АЦП
  698. static uint16_t cnt_notzero = 0; // Счетчик выборок АЦП без перехода через ноль
  699. if ((!fl.zero) && (U_adc >= 0) && (Ufir <= 0) && (U_adc != Ufir)) {
  700. // переход через ноль детектед
  701. cnt_notzero = 0; // Обнуляем счетчик выборок АЦП без перехода через ноль
  702. fl.NotZero = 0; // Снимаем флажок отсутствия детекции перехода через ноль
  703. /* Проверка насуммированных отсчетов */
  704. if (++cnt_P_sum == PSUM_MAX) {
  705. U_sum = sum;
  706. fl.sum = 1;
  707. sc_sum = sc; // Насуммированное готово к обработке
  708. sc = 0;
  709. sum = 0;
  710. cnt_P_sum = 0; // Сбрасываем счетчик, сумматор и счетчик полупериодов
  711. }
  712. TM2_current = TM2_tmp; // Запомним значение для дальнейшей обработки
  713. fl.zero = 1;
  714. } else {
  715. // переход через ноль NOT детектед
  716. fl.zero = 0;
  717. if (++cnt_notzero == ZSUM_MAX) { // Насуммировали достаточно
  718. fl.NotZero = 1;
  719. cnt_notzero = 0;
  720. PID_ust = LINE_FREQ;
  721. stop_razgon();
  722. pdm = 0;
  723. fl.Tout = 0; //выключим твердотельное реле
  724. U_real = 0;
  725. sc = 0;
  726. sum = 0;
  727. cnt_P_sum = 0; // Обнулим счетчик, сумматор, счетчик полупериодов и значение напряжения
  728. fl.dspNewData = 1;
  729. }
  730. }
  731. Ufir = U_adc;
  732. }
  733. /* детекция перехода через ноль и ПИД-синхронизация */
  734. sei(); // Следующие фрагменты длительны, но не требуют атомарности; разрешим прерывания
  735. if (fl.zero) { // ПИД-подстройка частоты внутреннего таймера к частоте сети
  736. static uint16_t PID_reg = PID_ust << Km; // Функция управления ПИД
  737. static int32_t PID_err_old = 0; // Разность фаз из предыдущего шага
  738. static int32_t PID_int = 0; // Интегральная составляющая из предыдущего шага
  739. int32_t temp_32 = (TM2_current + PHASE) << Km; // Разность фаз
  740. if (!fl.PP_tm) {
  741. temp_32 -= PID_reg + (1 << Km); // Разность фаз должна быть с соответствующим знаком
  742. }
  743. PID_int += (temp_32 >> Ki); // Считаем интегральную составляющую
  744. PID_reg += temp_32 >> Kp; // Считаем новую функцию управления
  745. PID_reg += PID_int;
  746. PID_reg += ( temp_32 - PID_err_old ) >> Kd;
  747. PID_err_old = temp_32;
  748. // Готовим данные для записи в регистр сравнения таймера 2
  749. if ( PID_reg > (T_MAX << Km)) {
  750. PID_reg = (T_MAX << Km); // Ограничим сверху
  751. } else if ( PID_reg < (T_MIN << Km)) {
  752. PID_reg = (T_MIN << Km); // Ограничим снизу
  753. }
  754. temp_32 = PID_reg >> (Km - 1); // ...и правильно округлим
  755. temp_32 ++; // используя уже не нужную в этой подпрограмме
  756. PID_ust = temp_32 / 2; // переменную temp_32
  757. } /* ПИД-подстройка частоты внутреннего таймера к частоте сети */
  758. }
  759. /**
  760. * @brief Подпрограмма обновления меню
  761. */
  762. static void RefreshMenu (void) {
  763. ST7735_FillScreen(ST7735_BLACK);
  764. ST7735_WriteString(0, 6, "Ст Принять и записать", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  765. ST7735_WriteString(0, 7, "Рз Принять без записи", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  766. ST7735_WriteString(X_position(1, 0, 7), 3, "Управление:", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  767. ST7735_WriteString(0, 4, "P- Выбор", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  768. #ifdef INTERFACE_ALT
  769. #else
  770. ST7735_WriteString(0, 0, "Выберите", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  771. #endif
  772. //
  773. switch (cnt_dspMenu) { //Проверяем режимы меню
  774. case 2: { //Если мы в начальном меню, то...
  775. #ifdef INTERFACE_ALT
  776. chsnprintf(buf, 24, "%3u V", U_LINE);
  777. ST7735_WriteString(X_position(16, 0, 7), 0, buf, Font_7x10, ST7735_BLUE, ST7735_BLACK);
  778. ST7735_WriteString(0, 1, "Рном= Вт", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  779. #else
  780. ST7735_WriteString(X_position(8, 0, 7), 0, "/введите Рном", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  781. chsnprintf(buf, 24, "Рном= Вт, (%3u V)", U_LINE);
  782. ST7735_WriteString(0, 1, buf, Font_7x10, ST7735_BLUE, ST7735_BLACK);
  783. #endif
  784. ST7735_WriteString(0, 2, "==Мощность нагрузки==", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  785. ST7735_WriteString(X_position(8, 0, 7), 4, "/уменьшение", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  786. ST7735_WriteString(0, 5, "P+ Увеличение", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  787. break;
  788. }
  789. case 1: { //Если мы в меню выбора уставки, то...
  790. #ifdef INTERFACE_ALT
  791. ST7735_WriteString(0, 1, "Руст= Вт", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  792. #else
  793. ST7735_WriteString(X_position(9, 0, 7), 0, "уставку", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  794. ST7735_WriteString(0, 1, "Руст= Вт", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  795. #endif
  796. ST7735_WriteString(0, 2, "=======Уставка=======", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  797. ST7735_WriteString(0, 5, "P+ Выбор", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  798. #ifdef USE_EEPROM
  799. if (!fl.writable) { // Если уставки не пишутся в EEPROM, то...
  800. ST7735_WriteString(0, 6, "Ст Принять без записи", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  801. }
  802. #endif
  803. break;
  804. }
  805. default: {
  806. }
  807. }
  808. }
  809. /**
  810. * @brief Подпрограмма печати строки минусов
  811. * @param str - номер строки, куда печатать минуса
  812. */
  813. static void menu_print_minus(const uint8_t str) {
  814. ST7735_WriteString(0, str, "----------------------", Font_7x10, ST7735_RED, ST7735_BLACK);
  815. }
  816. /**
  817. * @brief Initialization of 'Power Stabilizator'
  818. */
  819. void Stab_Init(void) {
  820. cnt_dspMenu = 2; // Сначала - начальное меню
  821. Pins_init(); // Инициализируем входы/выходы
  822. ADC_init(); // Инициализируем АЦП
  823. Timers_init(); // Инициализируем таймеры
  824. sei(); // Разрешаем глобальные прерывания
  825. pp_Delay(20); // Подождем 20 полупериодов
  826. #ifdef DisplayReset
  827. pin_OLEDres_HIGH; // Разрешаем работу дисплея
  828. #endif
  829. pp_Delay(10); // Подождем 10 полупериодов для гарантированного разрешения
  830. //ST7735_Init(); // done in main()
  831. ST7735_FillScreenFast(ST7735_BLACK);
  832. ST7735_WriteString(X_centred(21), 0, "Стабилизатор мощности", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  833. ST7735_WriteString(X_centred(4), 1, "ТЭНа", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  834. #ifdef INTERFACE_ALT
  835. ST7735_WriteString(X_centred(16), 2, "STAB-AVR", Font_11x18, ST7735_BLUE, ST7735_BLACK);
  836. #else
  837. ST7735_WriteString(X_centred(8), 2, "STAB-AVR", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  838. #endif
  839. ST7735_WriteString(X_centred(VERSION_LEN), 4, VERSION, Font_7x10, ST7735_BLUE, ST7735_BLACK);
  840. ST7735_WriteString(X_centred(10), 6, "JohnJohnov", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  841. ST7735_WriteString(X_centred(17), 7, "alcodistillers.ru", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  842. #ifdef USE_EEPROM
  843. EEPROM_read_Pnoms(); // Прочитаем из EEPROM записанные номиналы ТЭНов
  844. #endif
  845. pp_Delay(800); // Подождем 600 полупериодов, пережидаем переходные процессы и любуемся заставкой
  846. fl.dspRefresh = 1;
  847. #ifdef USE_USART
  848. /*
  849. * USART initialization
  850. * Если задействовано управление регулятором ТЭНа через UART, инициализируем оный
  851. */
  852. USART_start();
  853. #endif /* USE_USART */
  854. }
  855. /**
  856. * @brief 'Power Stabilizator' working cycle
  857. */
  858. void Stab_WorkCycle(void) {
  859. /* Обработка данных от АЦП и корректировка выдаваемой мощности */
  860. if (fl.sum) {
  861. #ifdef NOT_LM358
  862. // 0,55 - Коэффициент нормирования ((380/512)^2, 380В максимальное амплитудное) для Rail-to-Rail операционника
  863. U_sum /= sc_sum; //Ненормированный квадрат среднеквадратичного
  864. U_sum *= 0.55; //Нормированный квадрат среднеквадратичного
  865. #else
  866. // 3 - Коэффициент нормирования ((380/220)^2, 380В максимальное амплитудное) для стандартно установленного LM358
  867. U_sum *= 3; //Нормированная сумма квадратов среднеквадратичного
  868. U_sum /= sc_sum; //Нормированный квадрат среднеквадратичного
  869. #endif
  870. /* Корректируем pdm
  871. // uint32_t tmp; // Величины великоваты, чтобы попасть в размерность приходится считать аккуратно
  872. // // pdm = U_LINE_Q*PDMust/(U_sum);
  873. // tmp = (long)U_LINE_Q * 2;
  874. // tmp *= (long)PDMust;
  875. // tmp /= U_sum;
  876. // tmp++;
  877. // tmp /= 2;
  878. */
  879. uint16_t tmp = calc_proportion(PDMust, U_LINE_Q, U_sum);
  880. if (tmp > CICLE || fl.razg) { // Следим, чтобы pdm не превышала CICLE
  881. pdm = CICLE;
  882. fl.Ulow = !fl.razg; // Или напряжение сети не позволяет выдать установленный уровень мощности, или разгон
  883. } else {
  884. fl.Ulow = 0;
  885. pdm = tmp;
  886. }
  887. // Проверяем величину напряжения
  888. U_sum *= (long)400; // Произведем некоторое математическое колдунство,
  889. U_sum = sqrt(U_sum); // чтобы получить один знак после запятой без float
  890. U_sum ++;
  891. U_sum /= 2; // и с правильным округлением.
  892. U_real_dec = U_sum % 10; // Среднеквадратичное (дробная часть)
  893. U_real = U_sum / 10; // Среднеквадратичное (целая часть)
  894. // Контролируем значение
  895. if ( U_real < U_MIN ) { //Действующее напряжение сети ниже U_MIN - отключим ТЭН (авария)
  896. fl.Udown = 1; //поставим флажок низкого сетевого
  897. stop_razgon();
  898. pdm = 0; //выключим твердотельное реле
  899. } else {
  900. fl.Udown = 0;
  901. }
  902. fl.sum = 0;
  903. fl.dspNewData = 1; //Обновление информации на дисплее
  904. }
  905. #ifdef USE_ADprotocol
  906. /* Отправка отчета внешнему контроллеру */
  907. if (fl.uartReport && fl.uartUnhold) {
  908. USART_report();
  909. fl.uartReport = 0;
  910. }
  911. #endif
  912. /* Вывод информации на дисплей */
  913. if (fl.dspNewData) {
  914. if (fl.dspRefresh) {
  915. RefreshMenu(); //Обновляем дисплей, если надо
  916. }
  917. switch (cnt_dspMenu) { // Проверяем режимы меню
  918. case 2: { // Если мы в начальном меню, то...
  919. static uint16_t Pnomold = 0;
  920. if (!Pnom || Pnom > 9999) {
  921. Pnomold = Pnom;
  922. #ifdef INTERFACE_ALT
  923. ST7735_WriteString(X_position(3, 0, 11), 0, "****", Font_11x18, ST7735_BLUE, ST7735_BLACK);
  924. #else
  925. ST7735_WriteString(X_position(6, 0, 7), 1, "****", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  926. #endif
  927. } else if ((Pnomold != Pnom) || fl.dspRefresh) {
  928. Pnomold = Pnom;
  929. chsnprintf(buf, 24, "%u", Pnom);
  930. #ifdef INTERFACE_ALT
  931. ST7735_WriteString(X_position(6, Pnom, 11), 0, buf, Font_11x18, ST7735_BLUE, ST7735_BLACK);
  932. #else
  933. ST7735_WriteString(X_position(9, Pnom, 7), 1, buf, Font_7x10, ST7735_BLUE, ST7735_BLACK);
  934. #endif
  935. }
  936. fl.dspRefresh = 0;
  937. break;
  938. }
  939. case 1: { // Если мы в меню выбора уставки, то...
  940. static uint16_t PDMold = 0;
  941. if ((PDMold != PDMset[0][cnt_PDMcount]) || fl.dspRefresh) {
  942. PDMold = PDMset[0][cnt_PDMcount];
  943. uint16_t p = calc_proportion(PDMold, Pnom, CICLE); // Считаем уставку с округлением
  944. chsnprintf(buf, 24, "%u", p);
  945. #ifdef INTERFACE_ALT
  946. ST7735_WriteString(X_position(6, p, 11), 0, buf, Font_11x18, ST7735_BLUE, ST7735_BLACK);
  947. #else
  948. ST7735_WriteString(X_position(9, p, 7), 1, buf, Font_7x10, ST7735_BLUE, ST7735_BLACK);
  949. #endif
  950. #ifdef USE_EEPROM
  951. if (PDMset[1][cnt_PDMcount]) { // Если значение записано в EEPROM
  952. ST7735_WriteString(X_position(20, 0, 7), 1, "R", Font_7x10, ST7735_BLUE, ST7735_BLACK); // поставим значок
  953. } else {
  954. ST7735_WriteString(X_position(20, 0, 7), 1, " ", Font_7x10, ST7735_BLUE, ST7735_BLACK); // а если не записано - уберем
  955. }
  956. #endif
  957. }
  958. fl.dspRefresh = 0;
  959. break;
  960. }
  961. default: { // А если не в меню, то...
  962. #ifdef INTERFACE_ALT
  963. #define str_Ureal_big 0
  964. #define str_Ureal 1
  965. #define str_ust_big 3
  966. #define str_ust 4
  967. #define str_Ustat 2
  968. #define str_Razgon 5
  969. #define str_Pnom 6
  970. #define str_Relay 7
  971. #else
  972. #define str_Ureal 0
  973. #define str_Ustat 1
  974. #define str_ust 3
  975. #define str_Pnom 6
  976. #define str_Razgon 4
  977. #define str_Relay 7
  978. #endif
  979. if (fl.dspRefresh) { //Обновляем дисплей
  980. ST7735_FillScreen(ST7735_BLACK);
  981. #ifdef INTERFACE_ALT
  982. ST7735_WriteString(X_position(8, 0, 7), str_ust, "Вт , %", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  983. #else
  984. ST7735_WriteString(0, str_ust, "Руст Вт; , %", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  985. #endif
  986. ST7735_WriteString(0, str_Ureal, "Напр.сети , В", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  987. chsnprintf(buf, 24, "Ном. мощность %u Вт", Pnom);
  988. ST7735_WriteString(0, str_Pnom, buf, Font_7x10, ST7735_BLUE, ST7735_BLACK);
  989. //ST7735_WriteString(X_position(0, 0, 7), str_Relay, "Реле ", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  990. }
  991. static uint16_t U_real_old = 0;
  992. if ((U_real_old != U_real) || fl.dspRefresh) {
  993. U_real_old = U_real;
  994. chsnprintf(buf, 24, "%u", U_real_old);
  995. #ifdef INTERFACE_ALT
  996. ST7735_WriteString(X_position(7, U_real_old, 11) + 5, str_Ureal_big, buf, Font_11x18, ST7735_BLUE, ST7735_BLACK);
  997. #else
  998. ST7735_WriteString(X_position(16, U_real_old, 7), str_Ureal, buf, Font_7x10, ST7735_BLUE, ST7735_BLACK);
  999. #endif
  1000. }
  1001. static uint8_t U_real_dec_old = 0;
  1002. if ((U_real_dec_old != U_real_dec) || fl.dspRefresh) {
  1003. U_real_dec_old = U_real_dec;
  1004. chsnprintf(buf, 24, "%u", U_real_dec);
  1005. #ifdef INTERFACE_ALT
  1006. ST7735_WriteString(X_position(9, 0, 11), str_Ureal_big, buf, Font_11x18, ST7735_BLUE, ST7735_BLACK);
  1007. #else
  1008. ST7735_WriteString(X_position(18, 0, 7), str_Ureal_big, buf, Font_11x18, ST7735_BLUE, ST7735_BLACK);
  1009. #endif
  1010. }
  1011. static uint16_t Pust_old = 0;
  1012. if ((Pust_old != Pust) || fl.dspRefresh) {
  1013. Pust_old = Pust;
  1014. chsnprintf(buf, 24, "%u", Pust_old);
  1015. #ifdef INTERFACE_ALT
  1016. ST7735_WriteString(X_position(3,Pust_old,11), str_ust_big, buf, Font_11x18, ST7735_BLUE, ST7735_BLACK);
  1017. #else
  1018. ST7735_WriteString(X_position(8,Pust_old,7), str_ust, buf, Font_7x10, ST7735_BLUE, ST7735_BLACK);
  1019. #endif
  1020. }
  1021. static uint16_t PDMust_old = 0;
  1022. if ((PDMust_old != PDMust) || fl.dspRefresh) {
  1023. PDMust_old = PDMust;
  1024. uint32_t x = 1000*(long)PDMust_old;
  1025. x /= CICLE;
  1026. uint8_t percent = x / 10; // посчитаем процент
  1027. uint8_t percent_dec = x % 10; // посчитаем десятые процента
  1028. chsnprintf(buf, 24, "%u.%u", percent, percent_dec);
  1029. #ifdef INTERFACE_ALT
  1030. ST7735_WriteString(X_position(7,100,11) + 5, str_ust_big, buf, Font_11x18, ST7735_BLUE, ST7735_BLACK);
  1031. #else
  1032. ST7735_WriteString(X_position(13, 0, 7), str_ust, buf, Font_7x10, ST7735_BLUE, ST7735_BLACK);
  1033. #endif
  1034. }
  1035. if (fl.Udown || fl.NotZero) {
  1036. ST7735_WriteString(0, str_Ustat, "-----Авария сети-----", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  1037. } else if (fl.Ulow) {
  1038. ST7735_WriteString(0, str_Ustat, "--Недост.напр. сети--", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  1039. } else {
  1040. menu_print_minus(str_Ustat);
  1041. }
  1042. if (fl.razg_on) {
  1043. static uint8_t count_1 = 0;
  1044. uint8_t x1 = 5 - count_1;
  1045. uint8_t x2 = 20 - x1;
  1046. ST7735_WriteString(0, str_Razgon, "------<Разгон!>------", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  1047. ST7735_WriteString(X_position(x1, 0, 7), str_Razgon, "<", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  1048. ST7735_WriteString(X_position(x2, 0, 7), str_Razgon, ">", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  1049. if (++count_1 > 5) count_1 = 0;
  1050. } else {
  1051. menu_print_minus(str_Razgon);
  1052. }
  1053. {
  1054. static uint8_t trigger = 1;
  1055. if (trigger && fl.stab_off) {
  1056. ST7735_WriteString(0, str_Relay, "!!АВАРИЙНЫЙ ОСТАНОВ!!", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  1057. trigger = 0;
  1058. } else {
  1059. menu_print_minus(str_Relay);
  1060. trigger = 1;
  1061. }
  1062. }
  1063. //if (fl.TRelay) {
  1064. // ST7735_WriteString(X_position(5, 0, 7), str_Relay, "включено", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  1065. //} else {
  1066. // ST7735_WriteString(X_position(5, 0, 7), str_Relay, " ", Font_7x10, ST7735_BLUE, ST7735_BLACK);
  1067. //}
  1068. fl.dspRefresh = 0;
  1069. }
  1070. }
  1071. //
  1072. fl.dspNewData = 0;
  1073. }
  1074. #ifdef USE_USART
  1075. if (fl.uartUnhold) {
  1076. USART_parser();
  1077. }
  1078. #endif
  1079. }