Files
Balsam_167/Source/Internal/log_to_mem.c
Andrey 52683164af Перенести улучшения прошивки в ветку 166
Добавить CAN heartbeat и передачу по изменению с повтором через 1,3 с,
компактный логгер v4, паспорт прошивки и блочное чтение EEPROM.
Подключить автоматическую версию и проверяемую сборку BIN.
Сохранить локальные исправления стробов и защиты Timer1.
Обновить тесты, документацию, UTF-8 и исключения временных файлов.

Проверки: CAN, R3, EEPROM, паспорт, TESTLAMP, логгер, START/STOP;
9 сценариев сборки, включая реальную конверсию TI.
Все 17 внутренних модулей скомпилированы TI C2000 без ошибок.
2026-10-19 13:46:29 +03:00

292 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Регистратор УМП во внешней памяти .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;
}