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
+42
View File
@@ -0,0 +1,42 @@
"""Independent test oracle: does this built SCI8 file contain its manifest identity?
This is a build test, not a second production protocol codec. The 12-word vector
is deliberately constructed independently of the C implementation to detect a
stale compilation unit, incorrect byte order or a mismatched pre-build JSON.
Usage: python tests/firmware_identity/check_build.py path/to/firmware.bin
"""
from datetime import datetime
import hashlib
import json
from pathlib import Path
import struct
import sys
image = Path(sys.argv[1])
data = image.read_bytes()
meta = json.loads(image.with_suffix(image.suffix + '.manifest.json').read_text())
assert hashlib.sha256(data).hexdigest() == meta['sha256'], 'Wrong file hash'
assert len(data) == meta['size_bytes'], 'Wrong image size'
stamp = datetime.fromisoformat(meta['built_at_utc'])
build_id = meta['build_id']
words = [1, *meta['version'], stamp.year, stamp.month * 256 + stamp.day,
stamp.hour * 256 + stamp.minute, stamp.second]
words += [ord(build_id[i]) * 256 + ord(build_id[i+1]) for i in range(0, 8, 2)]
expected = struct.pack('<12H', *words)
assert data[:2] == b'\xaa\x08', 'Not SCI8'
offset = 22
found = 0
while True:
count, = struct.unpack_from('<H', data, offset)
offset += 2
if not count:
break
offset += 4 # destination address, not part of the loaded section payload
end = offset + count * 2
assert end <= len(data), 'Truncated boot block'
block = data[offset:end]
found += sum(block[i:i+24] == expected for i in range(0, len(block)-23, 2))
offset = end
assert offset == len(data), 'Trailing data'
assert found == 1, f'Expected one embedded identity, found {found}'
print(f'PASS: SCI8 contains manifest identity {meta["version"]}, {build_id}, {stamp.isoformat()}')
+33
View File
@@ -0,0 +1,33 @@
/* Test the actual 167 binding, not just a fake protocol backend. Build against
* the generated header from the firmware build under test. No MCU registers. */
#include "firmware_identity.h"
#include "firmware_info_static.h"
#include "ump_tms28335.h"
#include <assert.h>
static unsigned logger_reads, logger_writes;
unsigned int ump_log_read(unsigned int address)
{ (void)address; ++logger_reads; return 0x554dU; }
int ump_log_write(unsigned int address, unsigned int value)
{ (void)address; (void)value; ++logger_writes; return 1; }
int main(void)
{
uint16_t words[UMP_MAX_WORDS];
ump_octet wire[UMP_RTU_CAPACITY];
unsigned error, i;
assert(ump_tms28335_prepare(3, UMP_FIRMWARE_INFO, 12, words, &error) == 12 && !error);
assert(words[0] == 1 && words[1] == FIRMWARE_VERSION_MAJOR);
assert(words[2] == FIRMWARE_VERSION_MINOR && words[3] == FIRMWARE_VERSION_PATCH);
assert(words[4] == FI_YEAR && words[5] == FI_PAIR(FI_MONTH, FI_DAY));
assert(words[8] == FI_PAIR(FI_ID_0, FI_ID_1));
assert(logger_reads == 0);
assert(ump_tms28335_rtu(1,3,UMP_FIRMWARE_INFO,12,wire,UMP_RTU_CAPACITY) == 29);
for (i=0; i<12; ++i) assert(((wire[3+i*2]<<8) | wire[4+i*2]) == words[i]);
assert(ump_crc(wire,29) == 0);
ump_tms28335_prepare(6,UMP_FIRMWARE_INFO,123,words,&error);
assert(error == 2 && logger_writes == 0);
/* Firmware reads must not steal the existing logger window. */
assert(ump_tms28335_prepare(3,UMP_BASE,16,words,&error) == 16 && !error);
assert(logger_reads == 16 && words[0] == 0x554dU);
return 0;
}