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:
21
tests/eeprom_profiles/run_test.py
Normal file
21
tests/eeprom_profiles/run_test.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Check SPI wire address widths for both production EEPROM configurations."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
protocol = root.parent / 'newProject/SETGUI/third_party/templates/c/set-protocol'
|
||||
compiler = os.environ.get('CC') or shutil.which('gcc') or 'C:/msys64/mingw64/bin/gcc.exe'
|
||||
os.environ['PATH'] = str(Path(compiler).resolve().parent) + os.pathsep + os.environ['PATH']
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
for profile in (0, 1):
|
||||
exe = Path(folder) / f'spi{profile}.exe'
|
||||
subprocess.run([compiler, '-std=c99',
|
||||
f'-DEEPROM_M95M02={profile}', '-Itests/eeprom_profiles/stubs',
|
||||
'-ISource/Internal/Include', '-I' + str(protocol),
|
||||
'tests/eeprom_profiles/spi_test.c', '-o', str(exe)],
|
||||
cwd=root, check=True)
|
||||
subprocess.run([str(exe)], check=True, timeout=10)
|
||||
print(f'PASS SPI profile {profile}')
|
||||
47
tests/eeprom_profiles/spi_test.c
Normal file
47
tests/eeprom_profiles/spi_test.c
Normal file
@@ -0,0 +1,47 @@
|
||||
/* Exercise the production Timer2 SPI state machine; no hardware writes. */
|
||||
#include <assert.h>
|
||||
#include "../../Source/Internal/spise2p.c"
|
||||
static unsigned trace[300], widths[300], used;
|
||||
static void transfer(unsigned long address, int write)
|
||||
{
|
||||
unsigned buf[2]={0x1234,0x5678}, i;
|
||||
SE2P_DATA message={buf,2,address};
|
||||
used=0;
|
||||
if(write) spiSe2pWrite(&se2p,&message); else spiSe2pRead(&se2p,&message);
|
||||
for(i=0;i<100 && !spiSe2pFree(&se2p);i++) {
|
||||
SpiaRegs.SPITXBUF=0xdead;
|
||||
SpiaRegs.SPIRXBUF=0; /* WIP clear for write, deterministic read data. */
|
||||
SpiaRegs.SPISTS.bit.INT_FLAG=1;
|
||||
SPISE2P_DRV_tick(&se2p);
|
||||
if(SpiaRegs.SPITXBUF!=0xdead) {
|
||||
assert(GpioDataRegs.GPADAT.bit.GPIO19==0);
|
||||
trace[used]=SpiaRegs.SPITXBUF;
|
||||
widths[used++]=(SpiaRegs.SPICCR.all&15)+1;
|
||||
}
|
||||
}
|
||||
assert(spiSe2pFree(&se2p) && GpioDataRegs.GPADAT.bit.GPIO19==1);
|
||||
i=0;
|
||||
if(write) { assert(trace[i]==0x0600 && widths[i++]==8); }
|
||||
assert(trace[i]==(write ? 0x0200 : 0x0300) && widths[i++]==8);
|
||||
#if EEPROM_M95M02
|
||||
assert(trace[i]==((address>>16)<<8) && widths[i++]==8);
|
||||
#endif
|
||||
assert(trace[i]==(address&65535) && widths[i++]==16);
|
||||
assert(trace[i]==(write ? 0x1234 : 0) && widths[i++]==16);
|
||||
assert(trace[i]==(write ? 0x5678 : 0) && widths[i++]==16);
|
||||
if(write) { assert(trace[i]==0x0500 && widths[i++]==8); assert(trace[i++]==0); }
|
||||
assert(i==used);
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
SPISE2P_DRV_init(&se2p);
|
||||
transfer(0x7ffc,0); transfer(0x7ffc,1);
|
||||
#if EEPROM_M95M02
|
||||
assert(BALSAM167_EEPROM_FIRMWARE_BYTES==261884UL);
|
||||
transfer(0xfffe,0); transfer(0x10000,0);
|
||||
transfer(0x20000,1); transfer(0x3fffc,0); transfer(0x3fffc,1);
|
||||
#else
|
||||
assert(BALSAM167_EEPROM_FIRMWARE_BYTES==32508);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
22
tests/eeprom_profiles/stubs/DSP2833x_Device.h
Normal file
22
tests/eeprom_profiles/stubs/DSP2833x_Device.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef EEPROM_TEST_DEVICE_H
|
||||
#define EEPROM_TEST_DEVICE_H
|
||||
#include <stdint.h>
|
||||
#define interrupt
|
||||
#define EALLOW
|
||||
#define EDIS
|
||||
#define CLKMULT 2
|
||||
#define SYSCLKOUT 60000000UL
|
||||
#define M_INT14 0x2000
|
||||
static unsigned IER;
|
||||
struct bits { unsigned GPIO16,GPIO17,GPIO18,GPIO19,SPISWRESET,BUFFULL_FLAG,INT_FLAG; };
|
||||
struct reg { unsigned all; struct bits bit; };
|
||||
static struct { struct reg GPAMUX2,GPADIR; } GpioCtrlRegs;
|
||||
static struct { struct reg GPADAT; } GpioDataRegs;
|
||||
static struct { void (*TINT2)(void); } PieVectTable;
|
||||
static struct { unsigned InterruptCount; } CpuTimer2;
|
||||
static struct { struct reg TCR; } CpuTimer2Regs;
|
||||
static struct { struct reg SPICCR,SPICTL,SPISTS,SPIFFTX,SPIFFRX,SPIFFCT,SPIPRI;
|
||||
unsigned SPIBRR; uint16_t SPITXBUF,SPIRXBUF; } SpiaRegs;
|
||||
static void InitCpuTimers(void) {}
|
||||
static void ConfigCpuTimer(void *p, unsigned long f, unsigned t) {(void)p;(void)f;(void)t;}
|
||||
#endif
|
||||
42
tests/firmware_identity/check_build.py
Normal file
42
tests/firmware_identity/check_build.py
Normal 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
tests/firmware_identity/port_test.c
Normal file
33
tests/firmware_identity/port_test.c
Normal 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;
|
||||
}
|
||||
48
tests/gas167/check_image.py
Normal file
48
tests/gas167/check_image.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""Validate the PM35 RAM-load SCI8 image against the selected EEPROM profile."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import struct
|
||||
|
||||
|
||||
def check_image(path, eeprom):
|
||||
data = path.read_bytes()
|
||||
limit = {"AT25256B": 32508, "M95M02": 261884}[eeprom]
|
||||
if not 24 <= len(data) <= limit:
|
||||
raise ValueError(f"Image size {len(data)} outside 24..{limit}")
|
||||
if struct.unpack_from("<H", data)[0] != 0x08AA:
|
||||
raise ValueError("Not a SCI8 boot image")
|
||||
high, low = struct.unpack_from("<HH", data, 18)
|
||||
entry = (high << 16) | low
|
||||
position, blocks = 22, []
|
||||
while True:
|
||||
if position + 2 > len(data):
|
||||
raise ValueError("Missing boot terminator")
|
||||
words = struct.unpack_from("<H", data, position)[0]
|
||||
position += 2
|
||||
if not words:
|
||||
if position != len(data):
|
||||
raise ValueError("Trailing bytes after boot terminator")
|
||||
break
|
||||
if position + 4 + words * 2 > len(data):
|
||||
raise ValueError("Truncated boot block")
|
||||
high, low = struct.unpack_from("<HH", data, position)
|
||||
address = (high << 16) | low
|
||||
end = address + words
|
||||
if not 0x8000 <= address < end <= 0xD000:
|
||||
raise ValueError(f"Boot destination {address:#x}..{end:#x} outside code RAM")
|
||||
if any(address < old_end and old_start < end for old_start, old_end in blocks):
|
||||
raise ValueError("Overlapping boot blocks")
|
||||
blocks.append((address, end))
|
||||
position += 4 + words * 2
|
||||
if not any(start <= entry < end for start, end in blocks):
|
||||
raise ValueError("Entry point not loaded by the boot table")
|
||||
return len(data), len(blocks), limit - len(data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("image", type=Path)
|
||||
parser.add_argument("--eeprom", choices=("AT25256B", "M95M02"), required=True)
|
||||
args = parser.parse_args()
|
||||
size, blocks, free = check_image(args.image, args.eeprom)
|
||||
print(f"PASS SCI8 {args.eeprom}: {size} bytes, {blocks} RAM blocks, {free} bytes free")
|
||||
196
tests/gas167/gas167_test.c
Normal file
196
tests/gas167/gas167_test.c
Normal file
@@ -0,0 +1,196 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "gas167.h"
|
||||
#include "ump_protocol.h"
|
||||
#include "ump_tms28335.h"
|
||||
#include "include/balsam167_eeprom_block.h"
|
||||
|
||||
#define ANSWER_LEN 128U
|
||||
#define MAX_SEND_LENGTH 400U
|
||||
#define CNTRL_ADDR 16U
|
||||
#define CMD_MODBUS_3 3U
|
||||
#define CMD_MODBUS_6 6U
|
||||
#define dsk_LOAD 6
|
||||
#define LOBYTE(v) ((v) & 255U)
|
||||
#define HIBYTE(v) (((v) >> 8) & 255U)
|
||||
#define UMP_CAN_MAX_WORDS UMP_MAX_WORDS
|
||||
#define ump_can_prepare ump_tms28335_prepare
|
||||
typedef struct { int all; } WORDE;
|
||||
#define Modbus ((WORDE *)modbus)
|
||||
typedef struct {
|
||||
uint16_t RS_Header[8], buffer[MAX_SEND_LENGTH];
|
||||
unsigned flag_TIMEOUT_to_Send;
|
||||
} RS_DATA;
|
||||
|
||||
int modbus[129], Desk = dsk_LOAD;
|
||||
static unsigned captures, spi_reads, sent, irq_depth, legacy_reads;
|
||||
static unsigned __disable_interrupts(void) {
|
||||
unsigned saved = irq_depth;
|
||||
++irq_depth;
|
||||
return saved;
|
||||
}
|
||||
static void __restore_interrupts(unsigned saved) { irq_depth = saved; }
|
||||
void ump_log_rs(void) { ++captures; }
|
||||
unsigned int ump_log_read(unsigned int address) { ++legacy_reads; return address; }
|
||||
int ump_log_write(unsigned int address, unsigned int value) { return 1; }
|
||||
static unsigned get_crc_16(unsigned seed, const uint16_t *data, unsigned size) {
|
||||
assert(seed == 0xFFFFU);
|
||||
return ump_crc(data, size);
|
||||
}
|
||||
static void RS_Send(RS_DATA *rs, uint16_t *data, unsigned size) {
|
||||
assert(irq_depth == 0);
|
||||
sent = size;
|
||||
}
|
||||
static void Seeprom_read(unsigned long word, uint16_t *data, unsigned bytes) {
|
||||
unsigned i;
|
||||
assert(irq_depth == 0); /* The Timer2 SPI driver must retain interrupts. */
|
||||
++spi_reads;
|
||||
for (i = 0; i < bytes/2; ++i) data[i] = (uint16_t)(0xBEEFU+i);
|
||||
}
|
||||
static unsigned Load_protection_prepare(unsigned fn, unsigned a, unsigned n,
|
||||
uint16_t *out, unsigned *error) {
|
||||
out[0] = (uint16_t)a; out[1] = (uint16_t)n; *error = 0;
|
||||
return 2;
|
||||
}
|
||||
struct mock_reg { uint32_t all; };
|
||||
static struct {
|
||||
struct mock_reg CANTA, CANAA, CANTRR, CANTRS;
|
||||
} ECanaRegs;
|
||||
static struct {
|
||||
struct { struct mock_reg MDH, MDL; } MBOX2;
|
||||
} ECanaMboxes;
|
||||
static struct { unsigned long InterruptCount; } CpuTimer1;
|
||||
#include "production_routes.inc"
|
||||
|
||||
static void request(RS_DATA *rs, unsigned fn, unsigned a, unsigned n) {
|
||||
unsigned crc;
|
||||
memset(rs, 0, sizeof(*rs));
|
||||
rs->RS_Header[0] = CNTRL_ADDR; rs->RS_Header[1] = fn;
|
||||
rs->RS_Header[2] = a >> 8; rs->RS_Header[3] = a & 255U;
|
||||
rs->RS_Header[4] = n >> 8; rs->RS_Header[5] = n & 255U;
|
||||
crc = ump_crc(rs->RS_Header, 6);
|
||||
rs->RS_Header[6] = crc & 255U; rs->RS_Header[7] = crc >> 8;
|
||||
sent = 0;
|
||||
if (fn == 3) ReceiveCommandModbus3(rs);
|
||||
else ReceiveCommandModbus6(rs);
|
||||
assert(irq_depth == 0 && rs->flag_TIMEOUT_to_Send);
|
||||
assert(ump_crc(rs->buffer, rs->buffer[1] & 128U ? 5 :
|
||||
(fn == 6 ? 8 : 5+rs->buffer[2])) == 0);
|
||||
}
|
||||
static void can_request(unsigned fn, unsigned a, unsigned n) {
|
||||
memset(&ECanaRegs, 0, sizeof(ECanaRegs));
|
||||
memset(&ECanaMboxes, 0, sizeof(ECanaMboxes));
|
||||
ump_can_waiting = 0; ump_can_length = 0; ump_can_index = 0;
|
||||
ump_can_pending = 1; ump_can_tag = 0x0100U | fn;
|
||||
ump_can_address = a; ump_can_value = n;
|
||||
UMP_CAN_service();
|
||||
assert(irq_depth == 0 && !ump_can_pending);
|
||||
assert(ECanaRegs.CANTRS.all == 4U);
|
||||
}
|
||||
int main(void) {
|
||||
RS_DATA rs;
|
||||
unsigned i;
|
||||
for (i = 0; i < 129; ++i) modbus[i] = (int)i;
|
||||
modbus[128] = 0xD00D;
|
||||
/* Exercise the real native handlers in both builds. */
|
||||
request(&rs, 6, 1, 0xFEDC);
|
||||
assert(modbus[1] == 0xFEDC && sent == 10);
|
||||
request(&rs, 3, 0, 2);
|
||||
assert(rs.buffer[2] == 4 && rs.buffer[5] == 0xFE && rs.buffer[6] == 0xDC);
|
||||
request(&rs, 6, 0x52, 0);
|
||||
assert(rs.buffer[1] == 0x86 && rs.buffer[2] == 3 && modbus[0x52] == 0x52);
|
||||
request(&rs, 6, 0x7C, 9);
|
||||
assert(rs.buffer[2] == 2 && modbus[0x7C] == 0x7C);
|
||||
/* Existing UMP and protection routing must still work. */
|
||||
request(&rs, 3, 0x1000, 2);
|
||||
assert(rs.buffer[1] == 3 && rs.buffer[3] == 0x10 && legacy_reads == 2);
|
||||
request(&rs, 3, 0x1300, 2);
|
||||
assert(rs.buffer[1] == 3 && rs.buffer[3] == 0x13);
|
||||
#if EEPROM_M95M02
|
||||
{
|
||||
uint16_t out[124];
|
||||
unsigned error, count;
|
||||
for (i = 0; i < 65536U; ++i)
|
||||
assert(gas167_contains((uint16_t)i) ==
|
||||
((i >= 0x5000U && i <= 0x507FU) || (i >= 0x5F00U && i <= 0x5F07U)));
|
||||
assert(gas167_prepare(3, 0x5000, 124, 0, out, &error) == 124 && !error);
|
||||
assert(out[1] == 0xFEDC && out[123] == 123);
|
||||
count = gas167_prepare(3, 0x507F, 2, 0, out, &error);
|
||||
assert(count == 2 && error == 2 && modbus[128] == 0xD00D);
|
||||
assert(gas167_prepare(3, 0x5000, 0, 0, out, &error) == 2 && error == 3);
|
||||
assert(gas167_prepare(3, 0x5000, 125, 0, out, &error) == 2 && error == 3);
|
||||
assert(gas167_prepare(3, 0x5000, 1, 1, out, &error) == 2 && error == 3);
|
||||
assert(gas167_prepare(3, 0xFFFF, 2, 0, out, &error) == 2 && error == 2);
|
||||
assert(gas167_prepare(16, 0x5000, 1, 0, out, &error) == 2 && error == 1);
|
||||
assert(gas167_prepare(6, 0x5080, 7, 0, out, &error) == 2 && error == 2);
|
||||
assert(gas167_prepare(6, 0x5052, 60001, 0, out, &error) == 2 && error == 3);
|
||||
assert(gas167_prepare(6, 0x5052, 60000, 0, out, &error) == 2 && !error);
|
||||
assert(modbus[0x52] == 60000);
|
||||
assert(gas167_prepare(3, 0x5000, 1, 0, 0, &error) == 0);
|
||||
assert(gas167_prepare(6, 0x5000, 1, 0, out, 0) == 0 && modbus[0] == 0);
|
||||
}
|
||||
request(&rs, 3, 0x5000, 2);
|
||||
assert(sent == 9 && rs.buffer[5] == 0xFE && rs.buffer[6] == 0xDC);
|
||||
request(&rs, 3, 0x5000, 124);
|
||||
assert(sent == 253 && rs.buffer[2] == 248);
|
||||
request(&rs, 3, 0x5F00, 8);
|
||||
assert(sent == 21 && rs.buffer[3] == 0x47 && rs.buffer[4] == 0x41);
|
||||
assert(rs.buffer[5] == 0x53 && rs.buffer[6] == 0x31);
|
||||
assert(rs.buffer[10] == 167 && rs.buffer[18] == 1);
|
||||
request(&rs, 6, 0x5F00, 0);
|
||||
assert(sent == 5 && rs.buffer[2] == 2);
|
||||
request(&rs, 3, 0x5F07, 2);
|
||||
assert(rs.buffer[2] == 2);
|
||||
request(&rs, 6, 0x5052, 0);
|
||||
assert(rs.buffer[2] == 3 && modbus[0x52] == 60000);
|
||||
request(&rs, 6, 0x507D, 9);
|
||||
assert(rs.buffer[2] == 2 && modbus[0x7D] == 0x7D);
|
||||
request(&rs, 6, 0x507F, 0x80);
|
||||
assert(sent == 8 && rs.buffer[2] == 0x50 && rs.buffer[3] == 0x7F);
|
||||
assert(captures == 1 && modbus[127] == 0x80);
|
||||
can_request(6, 0x5001, 0xFEDC);
|
||||
assert(modbus[1] == 0xFEDC && ECanaMboxes.MBOX2.MDL.all == 0x5001FEDCUL);
|
||||
assert(ECanaMboxes.MBOX2.MDH.all == 0x1F000100UL);
|
||||
can_request(3, 0x5001, 2);
|
||||
assert(ECanaMboxes.MBOX2.MDL.all == 0xFEDC0002UL);
|
||||
can_request(3, 0x5000, 124);
|
||||
assert(ump_can_length == 124 && ump_can_index == 2);
|
||||
modbus[2] = 777; /* All fragments must retain the original snapshot. */
|
||||
for (i = 1; i < 62; ++i) {
|
||||
ECanaRegs.CANTA.all = 4;
|
||||
UMP_CAN_service();
|
||||
assert(ump_can_index == 2*(i+1));
|
||||
if (i == 1) assert(ECanaMboxes.MBOX2.MDL.all == 0x00020003UL);
|
||||
}
|
||||
modbus[2] = 2;
|
||||
can_request(3, 0x5000, 3);
|
||||
assert(ECanaMboxes.MBOX2.MDH.all == 0x1F000103UL);
|
||||
can_request(6, 0x507C, 9);
|
||||
assert((ECanaMboxes.MBOX2.MDH.all & 255U) == 2 && modbus[0x7C] == 0x7C);
|
||||
can_request(6, 0x507F, 0x40);
|
||||
assert(captures == 2 && modbus[127] == 0x40);
|
||||
can_request(6, 0x5F00, 9);
|
||||
assert((ECanaMboxes.MBOX2.MDH.all & 255U) == 2);
|
||||
request(&rs, 3, 0x8000, 0x8001);
|
||||
#else
|
||||
request(&rs, 3, 0x5000, 2);
|
||||
assert(sent == 5 && rs.buffer[2] == 2);
|
||||
request(&rs, 6, 0x5001, 9);
|
||||
assert(rs.buffer[2] == 2 && modbus[1] == 0xFEDC);
|
||||
request(&rs, 3, 0x5000, 124);
|
||||
assert(sent == 5 && rs.buffer[2] == 2);
|
||||
request(&rs, 3, 0x5F00, 8);
|
||||
assert(rs.buffer[2] == 2);
|
||||
can_request(6, 0x5001, 9);
|
||||
assert((ECanaMboxes.MBOX2.MDH.all & 255U) == 2 && modbus[1] == 0xFEDC);
|
||||
assert(captures == 0);
|
||||
request(&rs, 3, 0x8000, 1);
|
||||
#endif
|
||||
assert(spi_reads == 1 && rs.buffer[3] == 0xBE && rs.buffer[4] == 0xEF);
|
||||
assert(modbus[128] == 0xD00D);
|
||||
printf("PASS GAS167 profile %d: production RTU/CAN routes, guards, CRC and legacy services\n", EEPROM_M95M02);
|
||||
return 0;
|
||||
}
|
||||
51
tests/gas167/run_test.py
Normal file
51
tests/gas167/run_test.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""Exercise the production GAS provider and extracted production RTU/CAN routes."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
templates = Path(os.environ.get("TEMPLATES_ROOT", root.parent / "newProject/SETGUI/third_party/templates"))
|
||||
protocol = templates / "c/set-protocol"
|
||||
compiler = os.environ.get("CC") or shutil.which("gcc") or "C:/msys64/mingw64/bin/gcc.exe"
|
||||
os.environ["PATH"] = str(Path(compiler).resolve().parent) + os.pathsep + os.environ["PATH"]
|
||||
|
||||
|
||||
def function(text, name):
|
||||
match = re.search(r"^(?:static )?(?:void|int) " + name + r"\(", text, re.M)
|
||||
if not match:
|
||||
raise ValueError(f"Production function {name} missing")
|
||||
end = re.search(r"^}", text[match.start():], re.M)
|
||||
if not end:
|
||||
raise ValueError(f"Production function {name} incomplete")
|
||||
return text[match.start():match.start() + end.end()] + "\n"
|
||||
|
||||
|
||||
message = (root / "Source/Internal/message.c").read_text(encoding="cp1251")
|
||||
ecan = (root / "Source/Internal/ecan.c").read_text(encoding="cp1251")
|
||||
can_globals = ecan[ecan.index("static volatile unsigned int ump_can_pending;"):
|
||||
ecan.index("static unsigned long ump_can_sent_at;") + len("static unsigned long ump_can_sent_at;")]
|
||||
# On C28x unsigned int is uint16_t. Preserve that target word layout on GCC.
|
||||
can_globals = can_globals.replace("static unsigned int ump_can_reply[",
|
||||
"static uint16_t ump_can_reply[")
|
||||
routes = can_globals + "\n" + "".join(function(message, name) for name in
|
||||
("modbus_exception", "receive_ump", "ReceiveCommandModbus3", "ReceiveCommandModbus6"))
|
||||
routes += function(ecan, "UMP_CAN_service")
|
||||
with tempfile.TemporaryDirectory(prefix="gas167-test-") as tmp:
|
||||
directory = Path(tmp)
|
||||
(directory / "production_routes.inc").write_text(routes, encoding="utf-8")
|
||||
for profile in (0, 1):
|
||||
exe = directory / f"gas167_{profile}.exe"
|
||||
subprocess.run([compiler, "-std=c99", "-Wall", "-Wextra",
|
||||
"-Wno-unused-function", "-Wno-unused-parameter",
|
||||
f"-DEEPROM_M95M02={profile}", "-I" + str(directory),
|
||||
"-ISource/Internal/Include", "-I" + str(protocol),
|
||||
"-I" + str(protocol / "include"),
|
||||
"-I" + str(protocol / "ports/tms320f28335"),
|
||||
"tests/gas167/gas167_test.c", "Source/Internal/gas167.c",
|
||||
str(protocol / "src/ump_protocol.c"),
|
||||
str(protocol / "ports/tms320f28335/ump_tms28335.c"),
|
||||
"-o", str(exe)], cwd=root, check=True)
|
||||
subprocess.run([str(exe)], check=True, timeout=10)
|
||||
103
tests/load_control/load_control_test.c
Normal file
103
tests/load_control/load_control_test.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "../../Source/Internal/DAC.c"
|
||||
int READY = 1, Desk = 6, Mode = 5;
|
||||
int modbus[128];
|
||||
unsigned int test_start, test_stop, test_permission, test_tick;
|
||||
unsigned int test_starts, test_last_stop_tick, test_last_start_tick, test_init_dac;
|
||||
int test_bad_order;
|
||||
unsigned int ump_dac_ramp, ump_dac_duration, ump_dac_go10, ump_dac_stop10, ump_dac_position;
|
||||
#define CHECK(x) do { if (!(x)) { printf("FAIL line %d: %s\n", __LINE__, #x); return 1; } } while (0)
|
||||
static void setup(int mode) {
|
||||
memset(modbus, 0, sizeof(modbus)); Mode = mode; READY = 1;
|
||||
test_start = test_stop = test_permission = test_tick = test_starts = 0;
|
||||
test_bad_order = test_init_dac = 0;
|
||||
Load_control_init(); Setup_DAC_time(); WAKEpowse = 0;
|
||||
DAC_go = 4; DAC_stop = 16; DAC_min() = 100; DAC_max() = 3000;
|
||||
}
|
||||
static void tick(unsigned int n, int exchange, unsigned int volts) {
|
||||
while (n--) {
|
||||
if (exchange) Load_voltage_can(0x8000U | (Mode == 5 ? 305U : 8U), volts, 0, 0);
|
||||
test_tick++; Load_runner();
|
||||
}
|
||||
}
|
||||
int main(void) {
|
||||
unsigned int out[8], error, count, ramp, code, stopped_at;
|
||||
setup(5);
|
||||
/* Boot ISR before parameters: no divide-by-zero and no GPIO start. */
|
||||
Load_control_init(); Commands = 0x80; tick(10, 1, 0); CHECK(!test_starts);
|
||||
Setup_DAC_time(); WAKEpowse = 0;
|
||||
tick(10, 0, 0); CHECK(test_starts == 1 && test_permission);
|
||||
CHECK(LOAD_CAN_TIMEOUT == 600);
|
||||
/* Wrong ZPT and masked slots never confirm freshness. */
|
||||
setup(5); Commands = 0x80;
|
||||
Load_voltage_can(0x8000U | 8U, 200, 0, 0);
|
||||
Load_voltage_can(305U, 200, 0, 0); tick(10, 0, 0);
|
||||
CHECK(!test_starts && (LOAD_STATUS & 2));
|
||||
Load_voltage_can(0x2000U | 303U, 0, 0, 30);
|
||||
tick(1, 0, 0); CHECK(test_starts == 1);
|
||||
/* Freeze current and code at timeout; unrelated exchange cannot unfreeze. */
|
||||
tick(1000, 1, 30); tick(600, 0, 0);
|
||||
ramp = ump_dac_ramp; code = modbus[123];
|
||||
tick(2000, 0, 0); CHECK(ump_dac_ramp == ramp && (unsigned int)modbus[123] == code);
|
||||
CHECK(test_permission && !test_stop && (LOAD_STATUS & 2));
|
||||
Load_voltage_can(0x8000U | 8U, 200, 0, 0); tick(10, 0, 0);
|
||||
CHECK(ump_dac_ramp == ramp);
|
||||
tick(10, 1, 30); CHECK(ump_dac_ramp > ramp);
|
||||
/* Exactly 30 V permits growth; 29 V fails strictly above 6 mA. */
|
||||
setup(6); Commands = 0x80; tick(10, 1, 30);
|
||||
load_count_ramp = time_dac / 6; tick(5, 1, 30);
|
||||
CHECK(load_state == LOAD_RUN && modbus[0x1C] >= 60);
|
||||
tick(5, 1, 29); CHECK(load_state == LOAD_RELOAD && test_stop && !test_permission);
|
||||
CHECK(!test_start && ump_dac_ramp == 0 && modbus[0x1C] == 40);
|
||||
CHECK(modbus[123] == DAC_min()); stopped_at = test_last_stop_tick;
|
||||
tick(999, 1, 0); CHECK(test_starts == 1 && test_stop);
|
||||
tick(1, 1, 0); CHECK(test_starts == 2 && test_last_start_tick-stopped_at == 1000);
|
||||
CHECK(!test_stop && test_permission && !test_bad_order);
|
||||
/* Five total attempts, then a persistent fault; no sixth start. */
|
||||
while (load_state != LOAD_FAULT) {
|
||||
load_count_ramp = time_dac / 6; tick(5, 1, 0);
|
||||
if (load_state == LOAD_RELOAD) tick(1000, 1, 0);
|
||||
}
|
||||
CHECK(test_starts == 5 && LOAD_ATTEMPTS == 5 && (LOAD_STATUS & 1));
|
||||
tick(3000, 1, 200); CHECK(test_starts == 5 && test_stop && !test_permission);
|
||||
Commands = 0xC0; tick(10, 1, 200); CHECK(load_state == LOAD_FAULT);
|
||||
Commands = 0; tick(1000, 1, 200); CHECK(!(LOAD_STATUS & 1));
|
||||
Commands = 0x80; test_starts = 0; tick(10, 1, 200); CHECK(LOAD_ATTEMPTS == 1);
|
||||
/* Manual reset dominates START; releasing it alone cannot start again. */
|
||||
Commands = 0xC0; tick(1, 1, 200); CHECK(test_stop && !test_permission);
|
||||
Commands = 0x80; tick(1500, 1, 200); CHECK(test_starts == 1);
|
||||
/* Versioned simulation writes are bounded, read-only fields reject writes. */
|
||||
count = Load_protection_prepare(3, 0x1300, 8, out, &error);
|
||||
CHECK(count == 8 && !error && out[0] == 0x5550 && out[1] == 1);
|
||||
Load_protection_prepare(6, 0x1303, 0, out, &error); CHECK(error == 3);
|
||||
Load_protection_prepare(6, 0x1304, 0, out, &error); CHECK(error == 2);
|
||||
Load_protection_prepare(6, 0x1302, 4, out, &error); CHECK(error == 3);
|
||||
setup(5); Commands = 0x80; tick(10, 1, 200);
|
||||
Load_protection_prepare(6, 0x1303, 20, out, &error); CHECK(!error);
|
||||
Load_protection_prepare(6, 0x1302, 1, out, &error); CHECK(!error);
|
||||
tick(30, 1, 200); CHECK(LOAD_STATUS & 2);
|
||||
ramp = ump_dac_ramp; tick(100, 1, 200); CHECK(ump_dac_ramp == ramp);
|
||||
Load_protection_prepare(6, 0x1302, 0, out, &error);
|
||||
tick(10, 1, 200); CHECK(!(LOAD_STATUS & 2) && ump_dac_ramp > ramp);
|
||||
Load_protection_prepare(6, 0x1302, 2, out, &error);
|
||||
load_count_ramp = time_dac / 6; tick(5, 1, 200); CHECK(load_state == LOAD_RELOAD);
|
||||
/* Calibration must not replace the forced 4 mA output during reload. */
|
||||
Commands |= 0x200; DAC_cal() = 4095; tick(5, 1, 200);
|
||||
CHECK(modbus[123] == DAC_min() && modbus[0x1C] == 40);
|
||||
/* A calibration code preceding START must be replaced by 4 mA first. */
|
||||
setup(5); Commands = 0x200; DAC_cal() = 4095;
|
||||
tick(10, 1, 200); CHECK(modbus[123] == 4095 && !load_zero_sent);
|
||||
Commands = 0x280; tick(1, 1, 200); CHECK(!test_starts);
|
||||
tick(4, 1, 200); CHECK(!test_starts && modbus[123] == DAC_min());
|
||||
tick(1, 1, 200); CHECK(test_starts == 1);
|
||||
/* Timeout while STOP is held cannot initiate the pending retry. */
|
||||
setup(5); Commands = 0x80; tick(10, 1, 0);
|
||||
load_count_ramp = time_dac / 6; tick(5, 1, 0);
|
||||
CHECK(load_state == LOAD_RELOAD);
|
||||
tick(1500, 0, 0); CHECK(test_starts == 1 && test_stop);
|
||||
tick(1, 1, 0); CHECK(test_starts == 2 && !test_stop);
|
||||
Load_control_init(); CHECK(!load_simulation);
|
||||
puts("PASS: production DAC control, timeout, CAN slots, STOP ordering, five attempts, simulation");
|
||||
return 0;
|
||||
}
|
||||
16
tests/load_control/run_test.py
Normal file
16
tests/load_control/run_test.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Compile the production DAC.c against GPIO/IRQ mocks; no hardware writes."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
compiler = os.environ.get('CC') or shutil.which('gcc') or 'C:/msys64/mingw64/bin/gcc.exe'
|
||||
os.environ['PATH'] = str(Path(compiler).resolve().parent) + os.pathsep + os.environ['PATH']
|
||||
with tempfile.TemporaryDirectory(prefix='load-control-') as tmp:
|
||||
exe = Path(tmp) / 'test.exe'
|
||||
subprocess.run([compiler, '-Wall', '-Wextra', '-std=c99',
|
||||
'-Itests/load_control/stubs', '-ISource/Internal/Include',
|
||||
'tests/load_control/load_control_test.c', '-o', str(exe)], cwd=root, check=True)
|
||||
subprocess.run([str(exe)], check=True, timeout=10)
|
||||
9
tests/load_control/stubs/DSP2833x_Device.h
Normal file
9
tests/load_control/stubs/DSP2833x_Device.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef TEST_DEVICE_H
|
||||
#define TEST_DEVICE_H
|
||||
struct bits { unsigned int GPIO59, GPIO60, GPIO63; };
|
||||
struct reg { struct bits bit; };
|
||||
static struct { struct reg GPBSET, GPBCLEAR; } GpioDataRegs;
|
||||
static unsigned int __disable_interrupts(void) { return 0; }
|
||||
static void __restore_interrupts(unsigned int saved) { (void)saved; }
|
||||
static void DSP28x_usDelay(unsigned long n) { (void)n; }
|
||||
#endif
|
||||
@@ -0,0 +1 @@
|
||||
/* No IRQ registers needed in the host test. */
|
||||
1
tests/load_control/stubs/RS485.h
Normal file
1
tests/load_control/stubs/RS485.h
Normal file
@@ -0,0 +1 @@
|
||||
/* No serial dependencies in the host test. */
|
||||
1
tests/load_control/stubs/filter_bat2.h
Normal file
1
tests/load_control/stubs/filter_bat2.h
Normal file
@@ -0,0 +1 @@
|
||||
/* No filter dependencies in the host test. */
|
||||
4
tests/load_control/stubs/measure.h
Normal file
4
tests/load_control/stubs/measure.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#define READY_FREQ 1000
|
||||
#define DAC_FREQ 200
|
||||
#define LOAD_TIME 30
|
||||
extern int READY;
|
||||
1
tests/load_control/stubs/message.h
Normal file
1
tests/load_control/stubs/message.h
Normal file
@@ -0,0 +1 @@
|
||||
extern int modbus[128];
|
||||
17
tests/load_control/stubs/package.h
Normal file
17
tests/load_control/stubs/package.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#define dsk_LOAD 6
|
||||
#define adr_LOA1 5
|
||||
#define adr_LOA2 6
|
||||
#define Commands modbus[127]
|
||||
#define cUMPstart (Commands & 0x80)
|
||||
#define cUMPreset (Commands & 0x40)
|
||||
extern unsigned int test_init_dac;
|
||||
#define cInitDac test_init_dac
|
||||
#define cCalibrDac (Commands & 0x200)
|
||||
#define DAC_go modbus[0x50]
|
||||
#define DAC_stop modbus[0x51]
|
||||
#define LOAD_CAN_TIMEOUT modbus[0x52]
|
||||
#define LOAD_STATUS modbus[0x7C]
|
||||
#define LOAD_ATTEMPTS modbus[0x7D]
|
||||
#define DAC_min() modbus[0x79]
|
||||
#define DAC_max() modbus[0x78]
|
||||
#define DAC_cal() modbus[0x7A]
|
||||
14
tests/load_control/stubs/peripher.h
Normal file
14
tests/load_control/stubs/peripher.h
Normal file
@@ -0,0 +1,14 @@
|
||||
extern int Desk, Mode;
|
||||
extern unsigned int test_start, test_stop, test_permission, test_tick;
|
||||
extern unsigned int test_starts, test_last_stop_tick, test_last_start_tick;
|
||||
extern int test_bad_order;
|
||||
static void clear_START_DPTB(void) { test_start = 0; }
|
||||
static void set_START_DPTB(void) {
|
||||
if (test_stop || (test_starts && test_tick-test_last_stop_tick < 1000)) test_bad_order = 1;
|
||||
test_start = 1; test_last_start_tick = test_tick; test_starts++;
|
||||
}
|
||||
static void set_STOP_DPTB(void) { test_stop = 1; test_last_stop_tick = test_tick; }
|
||||
static void clear_STOP_DPTB(void) { test_stop = 0; }
|
||||
static void clear_RES_OUT_2(void) { test_permission = 0; }
|
||||
static void set_RES_OUT_2(void) { if (!test_start || test_stop) test_bad_order = 1; test_permission = 1; }
|
||||
static void toggle_RES_OUT_1(void) {}
|
||||
1
tests/load_control/stubs/tools.h
Normal file
1
tests/load_control/stubs/tools.h
Normal file
@@ -0,0 +1 @@
|
||||
/* Delay provided by the device stub. */
|
||||
@@ -16,7 +16,7 @@ int main(void)
|
||||
ump_time = ump_sequence = 0xA5A5A5A5UL;
|
||||
ump_bank = ump_archive_bank = 0xA5A5;
|
||||
ump_archive_count = ump_archive_next = ump_generation = 0xA5A5;
|
||||
ump_source = ump_post = ump_post_dropped = 0xA5A5;
|
||||
ump_source = ump_post = 0xA5A5;
|
||||
ump_can_header = ump_can_count = 0xA5A5;
|
||||
for (i = 0; i < 3; i++) ump_can_data[i] = 0xA5A5;
|
||||
for (i = 0; i < 2; i++) ump_pm67_voltage[i] = 0xA5A5;
|
||||
@@ -34,7 +34,7 @@ int main(void)
|
||||
for (i = 12; i <= 15; i++) CHECK(ump_log_read(UMP_LOG_BASE+i) == 0);
|
||||
CHECK(ump_bank == 0 && ump_archive_bank == 1);
|
||||
CHECK(ump_cursor == 0 && ump_next == 0 && ump_archive_next == 0);
|
||||
CHECK(ump_divider == 0 && ump_sequence == 0 && ump_post_dropped == 0);
|
||||
CHECK(ump_divider == 0 && ump_sequence == 0 && ump_post == 0);
|
||||
CHECK(ump_can_header == 0 && ump_can_count == 0);
|
||||
for (i = 0; i < 3; i++) CHECK(ump_can_data[i] == 0);
|
||||
for (i = 0; i < 2; i++) CHECK(ump_pm67_voltage[i] == 0);
|
||||
|
||||
@@ -44,16 +44,22 @@ __declspec(dllexport) int run_tests(void)
|
||||
/* Короткая CAN-команда должна остаться отдельным событием. */
|
||||
modbus[127] = 0x40;
|
||||
ump_log_can(0x807F, 0x40, 0, 0);
|
||||
CHECK(ump_post == 1000);
|
||||
CHECK(ump_word(0, ump_count-1, 2) == 0x102);
|
||||
CHECK(ump_post == 0); /* A command alone is not an output transition. */
|
||||
GpioDataRegs.GPBDAT.bit.GPIO58 = 1;
|
||||
ump_log_io(); /* STOP may be sampled before RES_OUT_2 is cleared. */
|
||||
CHECK(ump_post == 0);
|
||||
GpioDataRegs.GPADAT.bit.GPIO3 = 1;
|
||||
ump_log_io();
|
||||
CHECK(ump_post == UMP_POST_POINTS);
|
||||
CHECK(ump_word(0, ump_count-1, 2) == 0x103);
|
||||
CHECK(ump_word(0, ump_count-1, 18) == 0x807F);
|
||||
for (i=0; i<999; i++) ump_log_tick();
|
||||
for (i=0; i<UMP_POST_POINTS-1; i++) ump_log_tick();
|
||||
CHECK(ump_generation == 0);
|
||||
ump_log_tick();
|
||||
CHECK(ump_generation == 1);
|
||||
CHECK(ump_archive_count == UMP_LOG_CAPACITY);
|
||||
CHECK(ump_word(1, UMP_LOG_CAPACITY/2-1, 2) == 0x102);
|
||||
CHECK(ump_word(1, UMP_LOG_CAPACITY-1, 2) == 0x205);
|
||||
CHECK(ump_word(1, UMP_LOG_CAPACITY-UMP_POST_POINTS-1, 2) == 0x103);
|
||||
CHECK(ump_word(1, UMP_LOG_CAPACITY-1, 2) == 5);
|
||||
CHECK(ump_count == 0 && ump_state == 1);
|
||||
CHECK(ump_word(1, UMP_LOG_CAPACITY-1, 40) == 123);
|
||||
CHECK(ump_word(1, UMP_LOG_CAPACITY-1, 41) == 789);
|
||||
@@ -80,12 +86,56 @@ __declspec(dllexport) int run_tests(void)
|
||||
CHECK(sequence == ump_sequence);
|
||||
CHECK(ump_log_write(UMP_LOG_BASE, 1));
|
||||
CHECK(ump_word(1, UMP_LOG_CAPACITY-1, 0) == archived);
|
||||
/* Новый пуск и спад start без reset также фиксируют отключение. */
|
||||
/* Repeated reset/STOP pulses beyond the post interval preserve archive. */
|
||||
for (i=0; i<3; i++) {
|
||||
modbus[127] = 0; GpioDataRegs.GPBDAT.bit.GPIO58 = 0; ump_log_io();
|
||||
modbus[127] = 0x40; ump_log_can(0x807F, 0x40, 0, 0);
|
||||
GpioDataRegs.GPBDAT.bit.GPIO58 = 1; ump_log_io();
|
||||
CHECK(ump_post == 0);
|
||||
{ unsigned int tick; for (tick=0; tick<1100; tick++) ump_log_tick(); }
|
||||
CHECK(ump_generation == generation);
|
||||
CHECK(ump_word(1, UMP_LOG_CAPACITY-1, 0) == archived);
|
||||
}
|
||||
/* Falling start alone does not freeze; a new real cycle does. */
|
||||
modbus[127] = 0x80; ump_log_rs();
|
||||
GpioDataRegs.GPBDAT.bit.GPIO58 = 0;
|
||||
GpioDataRegs.GPADAT.bit.GPIO3 = 0; ump_log_io();
|
||||
modbus[127] = 0; ump_log_rs();
|
||||
CHECK(ump_post == 1000);
|
||||
for (i=0; i<1000; i++) ump_log_tick();
|
||||
CHECK(ump_post == 0);
|
||||
GpioDataRegs.GPBDAT.bit.GPIO58 = 1;
|
||||
GpioDataRegs.GPADAT.bit.GPIO3 = 1; ump_log_io();
|
||||
CHECK(ump_post == UMP_POST_POINTS);
|
||||
for (i=0; i<UMP_POST_POINTS; i++) ump_log_tick();
|
||||
CHECK(ump_generation == generation+1);
|
||||
/* A RES_OUT_2 fall without STOP is not a shutdown trigger. */
|
||||
generation = ump_generation;
|
||||
GpioDataRegs.GPBDAT.bit.GPIO58 = 0;
|
||||
GpioDataRegs.GPADAT.bit.GPIO3 = 0; ump_log_io();
|
||||
GpioDataRegs.GPADAT.bit.GPIO3 = 1; ump_log_io();
|
||||
CHECK(ump_post == 0);
|
||||
/* Starting the recorder with stopped outputs must not invent an edge. */
|
||||
CHECK(ump_log_write(UMP_LOG_BASE, 0));
|
||||
GpioDataRegs.GPBDAT.bit.GPIO58 = 1;
|
||||
CHECK(ump_log_write(UMP_LOG_BASE, 1));
|
||||
CHECK(ump_post == 0 && ump_generation == generation);
|
||||
/* At a slow sample period time alone cannot finish the point budget.
|
||||
* CAN and RS records count, and repeated edges do not restart it. */
|
||||
CHECK(ump_log_write(UMP_LOG_BASE, 0));
|
||||
CHECK(ump_log_write(UMP_LOG_BASE+1, 1000));
|
||||
CHECK(ump_log_write(UMP_LOG_BASE, 1));
|
||||
GpioDataRegs.GPADAT.bit.GPIO3 = 0; ump_log_io();
|
||||
GpioDataRegs.GPADAT.bit.GPIO3 = 1; ump_log_io();
|
||||
CHECK(ump_post == UMP_POST_POINTS);
|
||||
for (i=0; i<1000; i++) ump_log_tick();
|
||||
CHECK(ump_post == UMP_POST_POINTS-1 && ump_generation == generation);
|
||||
GpioDataRegs.GPADAT.bit.GPIO3 = 0; ump_log_io();
|
||||
GpioDataRegs.GPADAT.bit.GPIO3 = 1; ump_log_io();
|
||||
CHECK(ump_post == UMP_POST_POINTS-3);
|
||||
for (i=0; i<UMP_POST_POINTS-4; i++) ump_log_can(0x807F, 0, 0, 0);
|
||||
CHECK(ump_post == 1 && ump_generation == generation);
|
||||
ump_log_rs();
|
||||
CHECK(ump_post == 0 && ump_generation == generation+1);
|
||||
CHECK(ump_archive_count == UMP_POST_POINTS+3);
|
||||
/* CAN читает те же регистры и формирует цельный снимок из 124 слов.
|
||||
* Запись по старым технологическим адресам через сервис запрещена. */
|
||||
length = ump_can_prepare(3, UMP_LOG_BASE, 16, reply, &error);
|
||||
|
||||
@@ -18,11 +18,18 @@ if not compiler:
|
||||
raise SystemExit('Clang or GCC is required in PATH')
|
||||
link_flags = [] if 'gcc' in Path(compiler).name else ['-nostdlib', '-fuse-ld=lld', '-Wl,/noentry,/nodefaultlib']
|
||||
with tempfile.TemporaryDirectory(prefix='ump-recorder-') as directory:
|
||||
# Exercise the production identity binding too, using a build-local header.
|
||||
subprocess.run([
|
||||
os.sys.executable, str(root / 'UKSSTMS320F28335/prepare_identity.py'),
|
||||
'--project', str(root / 'UKSSTMS320F28335'), '--templates', str(templates),
|
||||
'--output', directory,
|
||||
], check=True)
|
||||
library = Path(directory) / 'recorder_test.dll'
|
||||
subprocess.run([
|
||||
compiler, '-shared', *link_flags, '-Wno-unknown-pragmas', '-Wno-int-to-pointer-cast',
|
||||
'-Itests/ump_logger/stubs', '-ISource/Internal/Include',
|
||||
'-I' + str(protocol), '-I' + str(protocol / 'ports/tms320f28335'),
|
||||
'-I' + str(templates / 'c/firmware-info/include'), '-I' + directory,
|
||||
'tests/ump_logger/recorder_test.c', 'Source/Internal/shared_ump.c', '-o', str(library),
|
||||
], cwd=root, check=True)
|
||||
# Отдельный процесс освобождает DLL до удаления временной папки Windows.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Exercise the release gate without TI tools or hardware."""
|
||||
"""Exercise the current board-specific SCI8 gate without TI tools or hardware."""
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
@@ -12,34 +12,35 @@ LIMIT = (0x3FFF - (128 + 1)) * 2
|
||||
|
||||
def image(size):
|
||||
header = struct.pack('<H', 0x08AA) + bytes(16) + struct.pack('<HH', 0, 0x8000)
|
||||
words = (size - 30) // 2
|
||||
return header + struct.pack('<HHH', words, 0, 0x8000) + bytes(words * 2) + bytes(2)
|
||||
blocks = max(1, (size-24+131075)//131076)
|
||||
remaining = (size-24-blocks*6)//2
|
||||
result = bytearray(header)
|
||||
for _ in range(blocks):
|
||||
words = min(65535, remaining)
|
||||
result += struct.pack('<HHH', words, 0, 0x8000) + bytes(words*2)
|
||||
remaining -= words
|
||||
return bytes(result)+bytes(2)
|
||||
|
||||
|
||||
class FirmwareGateTest(unittest.TestCase):
|
||||
def check_image(self, data, success):
|
||||
def check_image(self, data, success, profile="AT25256B"):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
source = Path(directory) / 'candidate.pending'
|
||||
output = Path(directory) / 'firmware.bin'
|
||||
source = Path(directory) / 'candidate.bin'
|
||||
source.write_bytes(data)
|
||||
result = subprocess.run([
|
||||
shutil.which('powershell') or 'pwsh', '-NoProfile', '-ExecutionPolicy', 'Bypass',
|
||||
'-File', str(ROOT / 'check_firmware.ps1'), '-Path', str(source),
|
||||
'-PublishPath', str(output),
|
||||
sys.executable, str(ROOT / 'tests/gas167/check_image.py'),
|
||||
str(source), '--eeprom', profile,
|
||||
], capture_output=True)
|
||||
self.assertEqual(result.returncode == 0, success, result.stderr.decode(errors='replace'))
|
||||
self.assertEqual(output.exists(), success)
|
||||
if success:
|
||||
self.assertEqual(output.read_bytes(), data)
|
||||
self.assertFalse(source.exists())
|
||||
self.assertEqual(result.returncode == 0, success,
|
||||
result.stderr.decode(errors='replace'))
|
||||
self.assertEqual(source.read_bytes(), data)
|
||||
return result.stdout + result.stderr
|
||||
|
||||
def test_exact_boundary_is_accepted(self):
|
||||
self.check_image(image(LIMIT), True)
|
||||
|
||||
def test_34a95bc5_size_is_rejected(self):
|
||||
diagnostic = self.check_image(image(32518), False)
|
||||
self.assertIn(b'overlaps saved parameters by 10 bytes', diagnostic)
|
||||
self.check_image(image(32518), False)
|
||||
|
||||
def test_missing_boot_terminator_is_rejected(self):
|
||||
self.check_image(image(100)[:-2], False)
|
||||
@@ -47,6 +48,15 @@ class FirmwareGateTest(unittest.TestCase):
|
||||
def test_partial_block_is_rejected(self):
|
||||
self.check_image(image(100)[:-10], False)
|
||||
|
||||
def test_m95_capacity_does_not_expand_code_ram(self):
|
||||
diagnostic = self.check_image(image(261884), False, "M95M02")
|
||||
self.assertIn(b'outside code RAM', diagnostic)
|
||||
self.check_image(image(261886), False, "M95M02")
|
||||
|
||||
def test_m95_image_rejected_for_small_part(self):
|
||||
self.check_image(image(32734), True, "M95M02")
|
||||
self.check_image(image(32734), False, "AT25256B")
|
||||
|
||||
def test_wrong_format_is_rejected(self):
|
||||
self.check_image(bytes(100), False)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user