292 lines
18 KiB
C
292 lines
18 KiB
C
/* Регистратор УМП во внешней памяти .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<Log.Finis; i++)
|
||
*(volatile int *)i = 0;
|
||
}
|
||
/* Два банка используют уже выделенную внешнюю RAM .logg (64000 слов).
|
||
* Активный банк кольцевой. Через 1000 мс после команды отключения он
|
||
* становится архивом, а запись продолжается во втором банке. Переключение
|
||
* занимает постоянное время: копирования 30 тысяч слов под запретом IRQ нет.
|
||
* Архив переживает разрыв RS-связи, но НЕ исчезновение питания платы. */
|
||
static volatile unsigned int ump_state, ump_count, ump_cursor, ump_next;
|
||
static volatile unsigned int ump_period = 20, ump_divider;
|
||
static volatile unsigned long ump_time, ump_sequence;
|
||
static unsigned int ump_bank, ump_archive_bank = 1;
|
||
static unsigned int ump_archive_count, ump_archive_next, ump_generation;
|
||
static unsigned int ump_source, ump_post, ump_post_dropped;
|
||
/* Хранит последние ЗПТ1/ЗПТ2 в вольтах из регистров ПМ67 306/9; явные нули попадают в инициализацию COFF до первого CAN-кадра. */
|
||
static unsigned int ump_pm67_voltage[2] = {0, 0};
|
||
static unsigned int ump_prev_commands, ump_prev_inputs, ump_prev_outputs;
|
||
/* Диагностика внутренних переменных разгона. Обновляется самим Load_runner,
|
||
* не пересчитывает и не изменяет управляющий алгоритм. */
|
||
unsigned int ump_dac_ramp, ump_dac_duration, ump_dac_go10, ump_dac_stop10, ump_dac_position;
|
||
|
||
/* Логические уровни входов платы нагрузки: физический ноль означает 1.
|
||
* Чтение GPIO непосредственно здесь позволяет записать вход до следующего
|
||
* обновления регистра Inputs в основном цикле. */
|
||
static unsigned int ump_inputs(void)
|
||
{
|
||
return (!GpioDataRegs.GPADAT.bit.GPIO6) |
|
||
((!GpioDataRegs.GPADAT.bit.GPIO8) << 1) |
|
||
((!GpioDataRegs.GPADAT.bit.GPIO11) << 2);
|
||
}
|
||
|
||
/* START/STOP активны единицей; RES_OUT_2 активен физическим нулём. */
|
||
static unsigned int ump_outputs(void)
|
||
{
|
||
return GpioDataRegs.GPBDAT.bit.GPIO62 |
|
||
(GpioDataRegs.GPBDAT.bit.GPIO58 << 1) |
|
||
((!GpioDataRegs.GPADAT.bit.GPIO3) << 2);
|
||
}
|
||
|
||
/* Вызывается только внутри короткой критической секции. Все три источника
|
||
* записей (main, CAN ISR, timer ISR) используют одну блокировку IRQ с
|
||
* восстановлением прежнего состояния, включая вызовы из прерывания. */
|
||
static void ump_capture(unsigned int reason)
|
||
{
|
||
/* Указывает на начало одной 12-словной записи во внешней RAM; volatile сохраняет фактические обращения к памяти. */
|
||
volatile unsigned int *record;
|
||
/* Сохраняет полное слово команд для прежней логики триггера; в запись попадут только биты пуска и сброса. */
|
||
unsigned int commands;
|
||
if (ump_state != 1 || Desk != dsk_LOAD || READY) return;
|
||
if (no_write || never_write) { ump_state = 3; return; }
|
||
commands = Commands;
|
||
/* Отключение: фронт cUMPreset (бит 6) либо спад cUMPstart (бит 7).
|
||
* Повторные команды не сдвигают конец уже начатого постинтервала. */
|
||
if (!ump_post && (((commands & 0x40) && !(ump_prev_commands & 0x40)) ||
|
||
(!(commands & 0x80) && (ump_prev_commands & 0x80)))) {
|
||
/* Половину банка резервируем под постинтервал. Даже при периоде
|
||
* 1 мс или шквале CAN событие отключения не вытесняется кольцом. */
|
||
if (ump_count >= 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;
|
||
}
|