feat: add M95M02-only GAS and EEPROM profiles

Expose the existing registers through Modbus and service CAN using one GAS provider only in M95M02 builds.
Include the EEPROM layout/driver, LOAD protection and calibration, firmware identity and shared CCS build workflow.
Add transport/profile and SCI8 validation tests, documentation and the checked M95M02 firmware image.
This commit is contained in:
2026-10-09 16:17:37 +03:00
parent c6c9128238
commit 5d9818291b
63 changed files with 2307 additions and 365 deletions
+28 -31
View File
@@ -31,17 +31,15 @@ void clear_mem()
for (i=Log.Start; i<Log.Finis; i++)
*(volatile int *)i = 0;
}
/* Два банка используют уже выделенную внешнюю RAM .logg (64000 слов).
* Активный банк кольцевой. Через 1000 мс после команды отключения он
* становится архивом, а запись продолжается во втором банке. Переключение
* занимает постоянное время: копирования 30 тысяч слов под запретом IRQ нет.
* Архив переживает разрыв RS-связи, но НЕ исчезновение питания платы. */
/* Two RAM banks: freeze after 10% of bank capacity is recorded after stop. */
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;
static unsigned int ump_source, ump_post;
#define UMP_POST_POINTS (UMP_LOG_CAPACITY / 10U)
static void ump_freeze(void);
static unsigned int ump_can_header, ump_can_data[3], ump_can_count;
/* Latest PM67 sums: register 306 (index 305), register 9 (index 8). */
static unsigned int ump_pm67_voltage[2];
@@ -62,7 +60,7 @@ void ump_log_init(void)
ump_time = 0; ump_sequence = 0;
ump_bank = 0; ump_archive_bank = 1;
ump_archive_count = 0; ump_archive_next = 0; ump_generation = 0;
ump_source = 0; ump_post = 0; ump_post_dropped = 0;
ump_source = 0; ump_post = 0;
ump_can_header = 0; ump_can_count = 0;
for (i = 0; i < 3; i++) ump_can_data[i] = 0;
for (i = 0; i < 2; i++) ump_pm67_voltage[i] = 0;
@@ -95,32 +93,30 @@ static unsigned int ump_outputs(void)
static void ump_capture(unsigned int reason)
{
volatile unsigned int *p;
unsigned int i, commands;
unsigned int i, commands, inputs, outputs;
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;
inputs = ump_inputs(); outputs = ump_outputs();
/* Freeze only an actual running-to-stopped output transition.
* STOP is set before RES_OUT_2 is cleared in main; an interrupt can
* observe these writes separately, so require its level, not its edge.
* Repeated reset/STOP pulses while already stopped leave the archive. */
if (!ump_post && (ump_prev_outputs & 4U) && !(outputs & 4U) &&
(outputs & 2U)) {
/* Keep 90% including the trigger; reserve 10% for later records. */
if (ump_count >= UMP_LOG_CAPACITY - UMP_POST_POINTS)
ump_count = UMP_LOG_CAPACITY - UMP_POST_POINTS - 1;
ump_post = UMP_POST_POINTS;
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;
ump_prev_commands = commands; ump_prev_inputs = inputs; ump_prev_outputs = outputs;
/* Every stored point counts, including CAN and I/O, except the trigger. */
if (ump_post && !(reason & 0x100) && --ump_post == 0) reason = 5;
p = logs_block + ((unsigned long)ump_bank * UMP_LOG_CAPACITY + ump_next) * UMP_LOG_WORDS;
p[0] = (unsigned int)ump_time; p[1] = (unsigned int)(ump_time >> 16);
p[2] = reason; p[3] = commands;
p[4] = ump_inputs(); p[5] = ump_outputs();
p[4] = inputs; p[5] = outputs;
p[6] = (unsigned int)IMPowse; p[7] = (unsigned int)WAKEpowse;
p[8] = modbus[0x7B]; p[9] = modbus[0x1C];
for (i = 0; i < 4; i++) { p[10+i] = sens_data[i]; p[14+i] = modbus[i]; }
@@ -136,11 +132,11 @@ static void ump_capture(unsigned int reason)
p[33] = ump_dac_go10; p[34] = ump_dac_stop10; p[35] = ump_dac_position;
for (i = 0; i < 4; i++) p[36+i] = ADC_table[i];
p[40] = ump_pm67_voltage[0]; p[41] = ump_pm67_voltage[1];
ump_prev_commands = commands; ump_prev_inputs = p[4]; ump_prev_outputs = p[5];
ump_next++;
if (ump_next == UMP_LOG_CAPACITY) ump_next = 0;
if (ump_count < UMP_LOG_CAPACITY) ump_count++;
ump_sequence++;
if (reason == 5) ump_freeze();
}
/* Çàâåðø¸ííûé áàíê áîëüøå íå èçìåíÿåòñÿ äî ñëåäóþùåãî îòêëþ÷åíèÿ.
@@ -160,10 +156,6 @@ void ump_log_tick(void)
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);
}
@@ -224,6 +216,7 @@ int ump_log_write(unsigned int address, unsigned int value)
ump_count = 0; ump_next = 0; ump_cursor = 0; ump_divider = 0; ump_post = 0;
ump_source = 0;
ump_prev_commands = Commands;
ump_prev_inputs = ump_inputs(); ump_prev_outputs = ump_outputs();
no_write = 0; never_write = 0;
ump_state = 1;
ump_capture(1);
@@ -274,7 +267,11 @@ unsigned int ump_log_read(unsigned int address)
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;
/* Preserve the legacy millisecond status field as an upper estimate.
* Additional event records may finish the post interval sooner. */
case UMP_LOG_BASE+14:
first = (unsigned long)ump_post * ump_period;
return first > 65535UL ? 65535U : (unsigned int)first;
case UMP_LOG_BASE+15: return ump_state;
}
if (address >= UMP_LOG_DATA && address < UMP_LOG_DATA+120 && (ump_source || ump_state != 1)) {