/* Регистратор УМП во внешней памяти .logg: два банка по 800 записей * из 12 слов формата v4 для SetGUI. Архив отключения не мешает дальнейшей записи. * Писатели из main/CAN/таймера защищаются короткими секциями IRQ * с восстановлением прежней маски. Архив не энергонезависимый. * clear_mem выполняется при запуске до включения прерываний. */ #include "DSP2833x_Device.h" #include "filter_bat2.h" #include "measure.h" #include "package.h" #include "RS485.h" #include "message.h" #include "peripher.h" #include "DAC.h" #include "ADC.h" #include "log_to_mem.h" int no_write = 0, never_write = 0; // Изначально разрешает запись; установленный флаг прекращает сохранение снимков до следующего запуска регистратора. #pragma DATA_SECTION(logs_block,".logg"); unsigned int logs_block[0xFA00]; LOG Log; unsigned int flog=0; // Очищение памати, где логи лежат void clear_mem() { unsigned long i; Log.Start = LOG_PAGE_START; Log.Finis = LOG_PAGE_START + LOG_PAGE_LEN; Log.Adres = Log.Start; Log.Circl = 0; for (i=Log.Start; i= UMP_LOG_CAPACITY/2) ump_count = UMP_LOG_CAPACITY/2-1; ump_post_dropped = 0; ump_post = 1000; reason |= 0x100; } /* Последнее место оставляем для снимка конца постинтервала. При * переполнении честно отмечаем усечение битом 0x200 в конечной записи. */ if (ump_post && ump_count >= UMP_LOG_CAPACITY-1) { ump_post_dropped = 1; return; } if (reason == 5 && ump_post_dropped) reason |= 0x200; /* Выбирает слот банка по числу 16-битных слов записи; ёмкость 800 записей сохранена для исполнения 166. */ record = logs_block + ((unsigned long)ump_bank * UMP_LOG_CAPACITY + ump_next) * UMP_LOG_WORDS; /* Слова 0/1 передают 32-битное время в миллисекундах младшей половиной вперёд, как в формате v4 исполнения 167. */ record[0] = (unsigned int)ump_time; record[1] = (unsigned int)(ump_time >> 16); /* Слово 2 содержит событие, а маска 0xC0 оставляет в слове 3 только назначенные протоколом биты 7/6 пуска и сброса УМП. */ record[2] = reason; record[3] = commands & 0xC0U; /* Слова 4/5 сохраняют фактические логические входы и выходы нагрузки для дискретных дорожек SetGUI. */ record[4] = ump_inputs(); record[5] = ump_outputs(); /* Слово 6 содержит расчётное задание тока регистра 0x1C в единицах 0,1 мА; физическое измерение тока здесь не выполняется. */ record[6] = modbus[0x1C]; /* Слова 7/8 содержат штатные фиксированные уставки разгона 166: начальные 6 мА и конечные 16 мА. */ record[7] = 6; record[8] = 16; /* Слова 9/10 передают последние ЗПТ1/ЗПТ2 в вольтах из CAN-индексов 305/8, соответствующих регистрам ПМ67 306/9. */ record[9] = ump_pm67_voltage[0]; record[10] = ump_pm67_voltage[1]; /* Нулевое слово 11 дополняет запись до чётной длины: служебный CAN передаёт данные парами 16-битных слов. */ record[11] = 0; /* Обновляет полные команды и фактические дискреты для обнаружения следующих событий, сохраняя алгоритм триггера 166. */ ump_prev_commands = commands; ump_prev_inputs = record[4]; ump_prev_outputs = record[5]; ump_next++; if (ump_next == UMP_LOG_CAPACITY) ump_next = 0; if (ump_count < UMP_LOG_CAPACITY) ump_count++; ump_sequence++; } /* Завершённый банк больше не изменяется до следующего отключения. * Номер поколения позволяет GUI обнаружить замену архива во время чтения. */ static void ump_freeze(void) { ump_archive_bank = ump_bank; ump_archive_count = ump_count; ump_archive_next = ump_next; ump_generation++; ump_bank ^= 1; ump_count = 0; ump_next = 0; ump_post = 0; } void ump_log_tick(void) { unsigned int saved = __disable_interrupts(); if (ump_state == 1) { ump_time++; if (++ump_divider >= ump_period) { ump_divider = 0; ump_capture(1); } if (ump_post && --ump_post == 0) { ump_capture(5); ump_freeze(); } } __restore_interrupts(saved); } /* Дополнительный снимок только при изменении команд, входов или выходов. * Это не заменяет периодическую запись аналоговых величин в таймере. */ void ump_log_io(void) { unsigned int saved; if (ump_state != 1 || Desk != dsk_LOAD) return; saved = __disable_interrupts(); if (Commands != ump_prev_commands || ump_inputs() != ump_prev_inputs || ump_outputs() != ump_prev_outputs) ump_capture(3); __restore_interrupts(saved); } /* Обновляет напряжения из принятых слов CAN даже до запуска записи; * активный регистратор дополнительно сохраняет событие приёма. */ void ump_log_can(unsigned int header, unsigned int a, unsigned int b, unsigned int c) { /* Хранит прежнюю маску IRQ, индекс слова, текущий CAN-адрес, маску разрешения и три принятых 16-битных значения. */ unsigned int saved, word_index, address, mask, can_words[3]; /* Напряжения и журнал принадлежат плате нагрузки; другие аппаратные режимы их не обновляют. */ if (Desk != dsk_LOAD) return; /* Исключает смешивание обновления напряжений с периодическим снимком таймера и восстанавливает прежние IRQ в конце. */ saved = __disable_interrupts(); /* Располагает три принятых слова в том же порядке, в котором заголовок задаёт их последовательные адреса. */ can_words[0] = a; can_words[1] = b; can_words[2] = c; /* Младшие 13 бит заголовка задают нулевой индекс первого регистра CAN; верхние три бита являются масками слов. */ address = header & 0x1FFFU; /* Старший бит 0x8000 разрешает первое слово, далее используются биты 0x4000 и 0x2000. */ mask = 0x8000U; /* Продвигает адрес и маску для всех трёх позиций, включая пропущенные слова, чтобы не перепутать регистры ПМ67. */ for (word_index = 0; word_index < 3; word_index++, address++, mask >>= 1) { /* Не использует содержимое позиции, если соответствующий бит заголовка не подтверждает её передачу. */ if (!(header & mask)) continue; /* Индекс 305 соответствует регистру 306 ПМ67: суммарное напряжение ЗПТ1 передаётся в целых вольтах. */ if (address == 305U) ump_pm67_voltage[0] = can_words[word_index]; /* Индекс 8 соответствует регистру 9 ПМ67: суммарное напряжение ЗПТ2 передаётся в целых вольтах. */ if (address == 8U) ump_pm67_voltage[1] = can_words[word_index]; } /* Завершает разбор трёх слов CAN после обновления только подтверждённых напряжений. */ /* Сохраняет компактный снимок только при активной записи и готовности 166, которые проверяет ump_capture. */ ump_capture(2); /* Возвращает исходную маску IRQ, в том числе при вызове из обработчика CAN. */ __restore_interrupts(saved); } void ump_log_rs(void) { unsigned int saved = __disable_interrupts(); ump_capture(4); __restore_interrupts(saved); } /* Управление журналом отделено от технологических команд: начало записи * НЕ запускает УМП. Выбор архива не останавливает текущую запись. */ int ump_log_write(unsigned int address, unsigned int value) { unsigned int saved, valid = 1; saved = __disable_interrupts(); if (address == UMP_LOG_BASE && value == 0) { ump_state = 3; ump_post = 0; } else if (address == UMP_LOG_BASE && value == 1 && Desk == dsk_LOAD && !READY && ump_state != 1) { ump_count = 0; ump_next = 0; ump_cursor = 0; ump_divider = 0; ump_post = 0; ump_source = 0; ump_prev_commands = Commands; no_write = 0; never_write = 0; ump_state = 1; ump_capture(1); } else if (address == UMP_LOG_BASE+1 && ump_state != 1 && value >= 1 && value <= 1000) ump_period = value; else if (address == UMP_LOG_BASE+2 && value < UMP_LOG_CAPACITY) ump_cursor = value; else if (address == UMP_LOG_BASE+3 && value <= 1) ump_source = value; else valid = 0; __restore_interrupts(saved); return valid; } /* Преобразование хронологического индекса в физическую позицию кольца. * Для архива сохранены собственные count/next, независимые от живого банка. */ static unsigned int ump_word(unsigned int archived, unsigned int record, unsigned int word) { unsigned int count = archived ? ump_archive_count : ump_count; unsigned int next = archived ? ump_archive_next : ump_next; unsigned int bank = archived ? ump_archive_bank : ump_bank; unsigned int slot; if (record >= count) return 0; slot = next + UMP_LOG_CAPACITY - count + record; while (slot >= UMP_LOG_CAPACITY) slot -= UMP_LOG_CAPACITY; return logs_block[((unsigned long)bank * UMP_LOG_CAPACITY + slot) * UMP_LOG_WORDS + word]; } /* Весь Modbus-блок читается вызывающим кодом под одной блокировкой IRQ. * Поэтому timestamp/данные и заголовок онлайн-блока относятся к одному * состоянию кольца, даже если в этот момент приходит новая CAN-команда. */ unsigned int ump_log_read(unsigned int address) { unsigned int offset, count; unsigned long first; switch (address) { case UMP_LOG_BASE: return 0x554D; /* Версия 4 сообщает SetGUI о 12-словной записи, исключая её ошибочный разбор как прежних 40 слов v2. */ case UMP_LOG_BASE+1: return 4; case UMP_LOG_BASE+2: return ump_source ? 3 : ump_state; case UMP_LOG_BASE+3: return ump_source ? ump_archive_count : ump_count; case UMP_LOG_BASE+4: return UMP_LOG_WORDS; case UMP_LOG_BASE+5: return UMP_LOG_CAPACITY; case UMP_LOG_BASE+6: return ump_period; case UMP_LOG_BASE+7: return (unsigned int)ump_time; case UMP_LOG_BASE+8: return (unsigned int)(ump_time >> 16); case UMP_LOG_BASE+9: return Desk; case UMP_LOG_BASE+10: return Mode; case UMP_LOG_BASE+11: return (unsigned int)READY_FREQ; case UMP_LOG_BASE+12: return ump_generation; case UMP_LOG_BASE+13: return ump_archive_count; case UMP_LOG_BASE+14: return ump_post; case UMP_LOG_BASE+15: return ump_state; } if (address >= UMP_LOG_DATA && address < UMP_LOG_DATA+120 && (ump_source || ump_state != 1)) { offset = address - UMP_LOG_DATA; return ump_word(ump_source, ump_cursor + offset / UMP_LOG_WORDS, offset % UMP_LOG_WORDS); } /* Последние десять записей для онлайн-графика, без остановки и без * изменения курсора архивного чтения. Первые два слова — их номер. */ if (address >= UMP_LOG_LIVE && address < UMP_LOG_LIVE+124) { /* Ограничивает число целых записей ёмкостью 120-словного окна: формат v4 позволяет передать до десяти снимков. */ count = ump_count < UMP_LOG_LIVE_RECORDS ? ump_count : UMP_LOG_LIVE_RECORDS; first = ump_sequence - count; offset = address - UMP_LOG_LIVE; if (offset == 0) return (unsigned int)first; if (offset == 1) return (unsigned int)(first >> 16); if (offset == 2) return count; if (offset == 3) return ump_generation; offset -= 4; if (offset / UMP_LOG_WORDS < count) return ump_word(0, ump_count - count + offset / UMP_LOG_WORDS, offset % UMP_LOG_WORDS); } return 0; }