#include "DSP2833x_Device.h" // DSP281x Headerfile Include File #include "DSP2833x_SWPrioritizedIsrLevels.h" #include "filter_bat2.h" #include "measure.h" #include "RS485.h" #include "message.h" #include "DAC.h" #include "package.h" #include "tools.h" #include "peripher.h" #include "log_to_mem.h" #define dCLK_1() GpioDataRegs.GPBSET.bit.GPIO63=1 #define dCLK_0() GpioDataRegs.GPBCLEAR.bit.GPIO63=1 #define dCS_1() GpioDataRegs.GPBSET.bit.GPIO59=1 #define dCS_0() GpioDataRegs.GPBCLEAR.bit.GPIO59=1 unsigned long WAKEpowse; unsigned long IMPowse; unsigned int period_dac, time_dac; /* Все выходы УМП и рампа принадлежат одному обработчику Timer1. * Основной цикл больше не выдаёт START: иначе он может увидеть короткий * спад команды, который пропустит расчёт ЦАП с периодом 5 мс. */ #define LOAD_IDLE 0U #define LOAD_RUN 1U #define LOAD_RELOAD 2U #define LOAD_FAULT 3U #define LOAD_MAX_ATTEMPTS 5U #define LOAD_VOLTAGE_MIN 30U #define LOAD_PULSE_MS 1000U static unsigned int load_state, load_attempts, load_inhibit; static unsigned int load_count_dac, load_count_ramp, load_init_count; static unsigned int load_zero_sent, load_stop_asserted, load_initialized; static float load_go, load_end; /* 16-битные поля общие с CAN ISR. Снимок и увеличение возраста выполняются * под короткой блокировкой IRQ; отправка SPI под блокировкой не выполняется. */ static volatile unsigned int load_voltage, load_voltage_age, load_voltage_seen; static volatile unsigned int load_simulation; void Load_control_init(void) { load_state = LOAD_IDLE; load_attempts = 0; load_inhibit = 0; load_count_dac = 0; load_count_ramp = 0; load_init_count = 1; load_zero_sent = 0; load_stop_asserted = 0; load_initialized = 0; load_go = 0; load_end = 0; load_voltage = 0; load_voltage_age = 0; load_voltage_seen = 0; load_simulation = 0; IMPowse = 0; WAKEpowse = 0; period_dac = 0; time_dac = 0; } /* Возраст сбрасывает только нужное напряжение, а не любой CAN-пакет. * УМП 5: ЗПТ1, индекс 305; УМП 6: ЗПТ2, индекс 8. Масштаб 1 В/ед. * Маски слотов сохраняют адреса даже для пропущенных слов кадра. */ void Load_voltage_can(unsigned int header, unsigned int a, unsigned int b, unsigned int c) { unsigned int address = header & 0x1FFFU; unsigned int selected, i, mask = 0x8000U, data[3]; if (Desk != dsk_LOAD || (load_simulation & 1U)) return; if (Mode == adr_LOA1) selected = 305U; else if (Mode == adr_LOA2) selected = 8U; else return; data[0] = a; data[1] = b; data[2] = c; for (i = 0; i < 3; i++, address++, mask >>= 1) { if ((header & mask) && address == selected) { load_voltage = data[i]; load_voltage_age = 0; load_voltage_seen = 1; } } } /* STOP удерживается весь сброс, включая отправку кода 4 мА в ЦАП. * Повторный пуск разрешён только после этой отправки и 1000 мс STOP. * Это подтверждение программной отправки, не измерение физического тока. */ static void load_stop(unsigned int state) { clear_START_DPTB(); set_STOP_DPTB(); clear_RES_OUT_2(); load_stop_asserted = 1; load_state = state; load_count_ramp = 0; load_zero_sent = 0; IMPowse = LOAD_PULSE_MS; } static void load_start(void) { /* Уставки фиксируются для каждой попытки. Внутренний ноль = 4 мА. * Исправлена опечатка старого ограничения: ограничиваем конец рампы, * а не заменяем начальную уставку на 16 (что давало скачок до 20 мА). */ load_go = (float)DAC_go - 4.0; load_end = (float)DAC_stop - 4.0; if (load_go < 0) load_go = 0; if (load_go > 5) load_go = 5; if (load_end > 16) load_end = 16; if (load_end <= load_go) load_end = load_go + 1; ump_dac_go10 = (unsigned int)((load_go + 4.0) * 10.0); ump_dac_stop10 = (unsigned int)((load_end + 4.0) * 10.0); load_count_ramp = 0; load_attempts++; /* STOP отпускаем непосредственно перед START, разрешение — после него. * Задание остаётся 4 мА до первого шага новой рампы. */ clear_STOP_DPTB(); load_stop_asserted = 0; set_START_DPTB(); set_RES_OUT_2(); IMPowse = LOAD_PULSE_MS; load_state = LOAD_RUN; } void Setup_DAC_time(void) { WAKEpowse = 3L * DAC_FREQ +1; period_dac = READY_FREQ / DAC_FREQ; time_dac = LOAD_TIME * DAC_FREQ; /* Старые EEPROM имеют ноль в новом параметре: используем 600 мс. * 1..60000 мс можно менять через регистр 0x52 и сохранять штатно. */ if ((unsigned int)LOAD_CAN_TIMEOUT == 0U || (unsigned int)LOAD_CAN_TIMEOUT > 60000U) LOAD_CAN_TIMEOUT = 600; LOAD_STATUS = 0; LOAD_ATTEMPTS = 0; load_initialized = 1; } void dOUT(int x) { if(x) GpioDataRegs.GPBSET.bit.GPIO60=1; else GpioDataRegs.GPBCLEAR.bit.GPIO60=1; } void wast() { int i; for(i=0;i<25;i++); } void dSEND(unsigned int word) { int i; dCS_0(); DSP28x_usDelay(1L); for(i=0;i<16;i++) { dCLK_1(); dOUT((word & 0x8000)?1:0); word = word<<1; DSP28x_usDelay(1L); dCLK_0(); DSP28x_usDelay(1L); } dCS_1(); } void Init_DAC(void) { dSEND(0x9002|0x0000); toggle_RES_OUT_1(); } static void anal_output(long vrot, long maxx, int calibration) { long out; static int x; out = DAC_min() + vrot * DAC_max() / maxx - vrot * DAC_min() / maxx ; x=0;// !x; тщетно бытие if(calibration && cCalibrDac) out=DAC_cal(); out = out & 0xFFF; if(x) dSEND(out|0x0000|0x0000); else dSEND(out|0x8000|0x0000); modbus[123] = out; modbus[0x1C] = 40L + vrot * 200 / maxx - vrot * 40 / maxx ; /* if(!no_write) { Test_mem_limit(4); Log_to_mem(modbus[0x7B]); Log_to_mem(modbus[0x63]); Log_to_mem(modbus[0x1C]); Log_to_mem(modbus[0x1D]); } */ } /* Сохранён публичный интерфейс; защитный сброс вызывает внутреннюю * функцию с calibration=0, чтобы ручная калибровка не подменила 4 мА. */ void Anal_output(long vrot, long maxx) { if (maxx > 0) anal_output(vrot, maxx, 1); } void Load_runner(void) { unsigned int saved, commands, voltage, fresh, timeout; unsigned int start, reset, active = 0; float sinus = 0; /* ISR работает 1 кГц, но GPIO/ЦАП нельзя обслуживать до загрузки * параметров. Это также исключает прежнее деление на time_dac=0. */ if (!load_initialized || !READY || !period_dac || !time_dac) return; saved = __disable_interrupts(); commands = (unsigned int)Commands; voltage = (load_simulation & 2U) ? 0U : load_voltage; timeout = (unsigned int)LOAD_CAN_TIMEOUT; if (!timeout || timeout > 60000U) timeout = 600U; /* До первого пакета значение 0 не считается свежим измерением. * Насыщение возраста исключает ложное восстановление после переполнения. */ fresh = load_voltage_seen && load_voltage_age < timeout; if (load_voltage_age < 65535U) load_voltage_age++; __restore_interrupts(saved); start = commands & 0x80U; reset = commands & 0x40U; if (IMPowse) IMPowse--; /* Отсутствие команды пуска — начало нового сеанса, сбрасывает ошибку. * RESET всегда старше START; отпускание RESET при оставшемся START * не даёт неожиданного повторного пуска: сначала требуется START=0. */ if (!start) { if (load_state != LOAD_IDLE) load_stop(LOAD_IDLE); load_attempts = 0; load_inhibit = 0; } else if (reset) { if (load_state != LOAD_FAULT && load_state != LOAD_IDLE) load_stop(LOAD_IDLE); load_inhibit = 1; } if (reset && !load_stop_asserted) load_stop(LOAD_IDLE); /* Повторный START допускается после сброса ЦАП, окончания STOP и * восстановления нужного CAN-напряжения. Таймаут сам не тратит попытку. */ if (start && !reset && !load_inhibit && !WAKEpowse && fresh && load_zero_sent && !IMPowse && (load_state == LOAD_IDLE || load_state == LOAD_RELOAD)) load_start(); if (load_state == LOAD_RUN && !IMPowse) clear_START_DPTB(); if (++load_count_dac >= period_dac) { load_count_dac = 0; if (WAKEpowse) { --WAKEpowse; if (WAKEpowse && !(WAKEpowse % (DAC_FREQ / 2))) { Init_DAC(); load_init_count = 1; } } else { active = load_state == LOAD_RUN; if (active) { /* При таймауте сохраняем положение И отправленное задание, * не делаем STOP и не продолжаем разгон без обратной связи. */ if (fresh) { if (load_count_ramp > time_dac / 2) load_count_ramp++; if (++load_count_ramp > time_dac) load_count_ramp = time_dac; } sinus = ((load_end - load_go) / 16.0 * load_count_ramp) + (load_go / 16.0 * time_dac); /* Проверяем следующий шаг ДО передачи ЦАП. 6 мА допустимы, * строго >6 мА и <30 В требуют немедленного STOP. * Первая попытка входит в лимит 5; шестого START не будет. */ if (fresh && voltage < LOAD_VOLTAGE_MIN && sinus * 16.0 > 2.0 * time_dac) { load_stop(load_attempts >= LOAD_MAX_ATTEMPTS ? LOAD_FAULT : LOAD_RELOAD); active = 0; sinus = 0; } } else { load_count_ramp = 0; load_init_count = (load_init_count + 1) & 0x3FFU; } /* Во время защиты и пуска ручной код калибровки запрещён. * Калибровка сохранена только при снятом START и свободном STOP. * Нулевой код задания здесь означает ток 4 мА по DAC_min(). */ ump_dac_ramp = load_count_ramp; ump_dac_duration = time_dac; ump_dac_position = (unsigned int)sinus; if (active && !fresh) { /* Удерживаем ЦАП. Даже cInitDac не меняет ток в паузе CAN. */ } else if ((!load_init_count || cInitDac) && !load_stop_asserted) { Init_DAC(); load_init_count = 1; cInitDac = 0; } else { anal_output((int)sinus, time_dac, !start && !load_stop_asserted); /* A manual calibration frame is not a confirmed 4 mA reload. * START must wait for a forced baseline frame after it. */ if (!active) load_zero_sent = !(!start && !load_stop_asserted && cCalibrDac); } } } /* Диагностика доступна обычным Modbus/CAN, независимо от логгера: * бит 0 — ошибка пяти пусков; бит 1 — нет свежего напряжения; * бит 2 — сброс перед повтором; бит 3 — работа рампы. * Ошибка удерживается со STOP, пока оператор не снимет START. */ LOAD_STATUS = (load_state == LOAD_FAULT ? 1U : 0U) | (!fresh ? 2U : 0U) | (load_state == LOAD_RELOAD ? 4U : 0U) | (load_state == LOAD_RUN ? 8U : 0U) | (load_simulation << 4); LOAD_ATTEMPTS = load_attempts; } /* Local versioned block, shared by RTU and service CAN. Probe its signature * before writes: old firmware returns exception 2 instead of starting tests. * Simulation is RAM-only, zeroed at boot, and never copied into EEPROM. * bit 0 ignores ZPT reception (age reaches the configured timeout); * bit 1 substitutes 0 V only for protection, leaving real logged CAN intact. */ unsigned int Load_protection_prepare(unsigned int function, unsigned int address, unsigned int value, unsigned int *out, unsigned int *error) { unsigned int saved = __disable_interrupts(); *error = 2; if (Desk == dsk_LOAD && address == 0x1300U && function == 3 && value == 8) { out[0] = 0x5550U; out[1] = 1; out[2] = load_simulation; out[3] = (unsigned int)LOAD_CAN_TIMEOUT; out[4] = (unsigned int)LOAD_STATUS; out[5] = load_attempts; out[6] = load_voltage; out[7] = load_voltage_seen ? load_voltage_age : 65535U; *error = 0; __restore_interrupts(saved); return 8; } if (Desk == dsk_LOAD && function == 6) { if (address == 0x1302U && value <= 3U) { load_simulation = value; *error = 0; } else if (address == 0x1303U && value >= 1U && value <= 60000U) { LOAD_CAN_TIMEOUT = value; *error = 0; } else if (address == 0x1302U || address == 0x1303U) *error = 3; } out[0] = address; out[1] = value; __restore_interrupts(saved); return 2; }