From c0ee7c46ea7f80f0195461ede572b904f5af695a Mon Sep 17 00:00:00 2001 From: andrey Date: Thu, 1 Oct 2026 16:00:25 +0300 Subject: [PATCH] Support PM67 voltage fields in UMP logger v3 --- c/set-protocol/include/ump_protocol.h | 55 +++++++++ .../ports/tms320f28335/ump_tms28335.c | 34 ++++++ .../ports/tms320f28335/ump_tms28335.h | 15 +++ c/set-protocol/src/ump_protocol.c | 105 ++++++++++++++++++ python/set_devices/ump_logger.py | 20 ++-- python/set_devices/ump_logger_can.py | 2 +- 6 files changed, 222 insertions(+), 9 deletions(-) create mode 100644 c/set-protocol/include/ump_protocol.h create mode 100644 c/set-protocol/ports/tms320f28335/ump_tms28335.c create mode 100644 c/set-protocol/ports/tms320f28335/ump_tms28335.h create mode 100644 c/set-protocol/src/ump_protocol.c diff --git a/c/set-protocol/include/ump_protocol.h b/c/set-protocol/include/ump_protocol.h new file mode 100644 index 0000000..2cee035 --- /dev/null +++ b/c/set-protocol/include/ump_protocol.h @@ -0,0 +1,55 @@ +/* UMP logger v2 (PM35), independent of PM67 and SET protocol v2. + * Octets use 16-bit storage: C28x has CHAR_BIT=16 and no uint8_t. + * All lengths below count wire octets or register words, never sizeof bytes. + */ +#ifndef UMP_PROTOCOL_H +#define UMP_PROTOCOL_H +#include +#include +#ifdef __cplusplus +extern "C" { +#endif +#define UMP_BASE 0x1000U +#define UMP_DATA 0x1010U +#define UMP_LIVE 0x1100U +#define UMP_MARKER 0x1F00U +#define UMP_MAX_WORDS 124U +#define UMP_RTU_CAPACITY 253U +typedef uint16_t ump_octet; +/* Called under caller's snapshot lock. write returns 1 on success, 0 otherwise. */ +typedef struct { + void *context; + uint16_t (*read)(void *context, uint16_t address); + int (*write)(void *context, uint16_t address, uint16_t value); +} ump_backend; +/* Returns Modbus exception 0/1/2/3. CAN has stricter whole-block reads. */ +unsigned ump_validate(unsigned function, uint16_t address, uint16_t value, int can); +/* reply capacity is >=124 words. Errors and writes return address/value echo. */ +unsigned ump_prepare(const ump_backend *backend, unsigned function, + uint16_t address, uint16_t value, int can, uint16_t *reply, unsigned *error); +uint16_t ump_crc(const ump_octet *data, size_t count); +/* Mode 1..16; kind 0=request, 1=normal telemetry, 2=logger reply; invalid ->0. */ +uint32_t ump_can_id(unsigned mode, unsigned kind); +/* Returns 0 for non-service frames, malformed size, or zero token. */ +unsigned ump_can_request(const ump_backend *backend, const ump_octet *request, + size_t size, uint16_t *reply, unsigned *token, unsigned *error); +/* count must be an even number 2..124; index counts pairs. Returns 8 or 0. */ +unsigned ump_can_fragment(const uint16_t *words, unsigned count, unsigned index, + unsigned token, unsigned error, ump_octet *reply); +/* Complete RTU frame only, after UART framing. Ignores bad CRC/foreign address. + * Caller provides >=253 octets; no writes occur on insufficient capacity. + * Device address 1..247; no broadcasts or legacy padding in this extension. + */ +size_t ump_rtu(const ump_backend *backend, unsigned device, + const ump_octet *request, size_t size, ump_octet *reply, size_t capacity); +/* Response for an already CRC-checked/framed request (legacy UART receiver). */ +size_t ump_rtu_response(const ump_backend *backend, unsigned device, unsigned function, + uint16_t address, uint16_t value, ump_octet *reply, size_t capacity); +/* Serialize a captured reply after releasing the snapshot lock. Returns 0 on + * invalid count/capacity; error replies need no words. */ +size_t ump_rtu_encode(unsigned device, unsigned function, unsigned error, + const uint16_t *words, unsigned count, ump_octet *reply, size_t capacity); +#ifdef __cplusplus +} +#endif +#endif diff --git a/c/set-protocol/ports/tms320f28335/ump_tms28335.c b/c/set-protocol/ports/tms320f28335/ump_tms28335.c new file mode 100644 index 0000000..c1f2343 --- /dev/null +++ b/c/set-protocol/ports/tms320f28335/ump_tms28335.c @@ -0,0 +1,34 @@ +#include "ump_tms28335.h" +extern unsigned int ump_log_read(unsigned int address); +extern int ump_log_write(unsigned int address, unsigned int value); +static uint16_t read_word(void *context, uint16_t address) +{ (void)context; return (uint16_t)ump_log_read(address); } +static int write_word(void *context, uint16_t address, uint16_t value) +{ (void)context; return ump_log_write(address,value); } +unsigned ump_tms28335_prepare(unsigned fn, uint16_t a, uint16_t n, + uint16_t *reply, unsigned *error) +{ + const ump_backend backend = {0,read_word,write_word}; + return ump_prepare(&backend,fn,a,n,1,reply,error); +} +size_t ump_tms28335_rtu(unsigned device, unsigned fn, uint16_t a, + uint16_t n, ump_octet *reply, size_t capacity) +{ + const ump_backend backend = {0,read_word,write_word}; + return ump_rtu_response(&backend,device,fn,a,n,reply,capacity); +} +unsigned ump_tms28335_capture(unsigned fn, uint16_t a, uint16_t n, + uint16_t *words, unsigned *error) +{ + const ump_backend backend = {0,read_word,write_word}; + return ump_prepare(&backend,fn,a,n,0,words,error); +} +unsigned ump_tms28335_fragment(const uint16_t *words, unsigned count, unsigned index, + unsigned token, unsigned error, uint32_t *mdl, uint32_t *mdh) +{ + ump_octet frame[8]; + if (!mdl || !mdh || !ump_can_fragment(words,count,index,token,error,frame)) return 0; + *mdl=((uint32_t)frame[0]<<24)|((uint32_t)frame[1]<<16)|(frame[2]<<8)|frame[3]; + *mdh=((uint32_t)frame[4]<<24)|((uint32_t)frame[5]<<16)|(frame[6]<<8)|frame[7]; + return 8; +} diff --git a/c/set-protocol/ports/tms320f28335/ump_tms28335.h b/c/set-protocol/ports/tms320f28335/ump_tms28335.h new file mode 100644 index 0000000..d19d420 --- /dev/null +++ b/c/set-protocol/ports/tms320f28335/ump_tms28335.h @@ -0,0 +1,15 @@ +#ifndef UMP_TMS28335_H +#define UMP_TMS28335_H +#include "../../include/ump_protocol.h" +/* Application provides ump_log_read/write; caller holds the snapshot IRQ lock. + * No peripheral registers, ISR installation or transport waits in this port. */ +unsigned ump_tms28335_prepare(unsigned function, uint16_t address, uint16_t value, + uint16_t *reply, unsigned *error); +size_t ump_tms28335_rtu(unsigned device, unsigned function, uint16_t address, + uint16_t value, ump_octet *reply, size_t capacity); +unsigned ump_tms28335_capture(unsigned function, uint16_t address, uint16_t value, + uint16_t *words, unsigned *error); +/* Convert one pair to TI eCAN MDL/MDH register values; returns 8 or 0. */ +unsigned ump_tms28335_fragment(const uint16_t *words, unsigned count, unsigned index, + unsigned token, unsigned error, uint32_t *mdl, uint32_t *mdh); +#endif diff --git a/c/set-protocol/src/ump_protocol.c b/c/set-protocol/src/ump_protocol.c new file mode 100644 index 0000000..9f217aa --- /dev/null +++ b/c/set-protocol/src/ump_protocol.c @@ -0,0 +1,105 @@ +#include "../include/ump_protocol.h" +static uint16_t get16(const ump_octet *p) +{ return (uint16_t)(((p[0]&255U)<<8)|(p[1]&255U)); } +static void put16(ump_octet *p, uint16_t v) +{ p[0] = v>>8; p[1] = v&255U; } +unsigned ump_validate(unsigned fn, uint16_t a, uint16_t n, int can) +{ + if (fn == 6) return a >= UMP_BASE && a <= UMP_BASE+3 ? 0 : 2; + if (fn != 3) return 1; + if (can) return ((a == UMP_BASE && n == 16) || + (a == UMP_DATA && (n == 40 || n == 80 || n == 120 || n == 42 || n == 84)) || + (a == UMP_LIVE && n == 124)) ? 0 : 2; + if (!n || n > 125) return 2; + return ((a >= UMP_BASE && a < UMP_BASE+16 && n <= UMP_BASE+16-a) || + (a >= UMP_DATA && a < UMP_DATA+120 && n <= UMP_DATA+120-a) || + (a == UMP_LIVE && n == 124)) ? 0 : 2; +} +unsigned ump_prepare(const ump_backend *b, unsigned fn, uint16_t a, + uint16_t n, int can, uint16_t *out, unsigned *error) +{ + unsigned i; + if (!out || !error) return 0; + *error = ump_validate(fn,a,n,can); + if (!*error) { + if (!b || (fn == 3 ? !b->read : !b->write)) *error = 3; + else if (fn == 3) { + for (i=0;iread(b->context,(uint16_t)(a+i)); + return n; + } else if (!b->write(b->context,a,n)) *error = 3; + } + out[0]=a; out[1]=n; + return 2; +} +uint16_t ump_crc(const ump_octet *p, size_t n) +{ + uint16_t crc=0xFFFFU; + unsigned i; + while (n--) { + crc ^= *p++ & 255U; + for (i=0;i<8;i++) crc=(uint16_t)((crc>>1)^((crc&1U)?0xA001U:0)); + } + return crc; +} +uint32_t ump_can_id(unsigned mode, unsigned kind) +{ return mode>=1 && mode<=16 && kind<=2 ? 0xBA0000UL+kind*16UL+mode-1 : 0; } +unsigned ump_can_request(const ump_backend *b, const ump_octet *r, size_t size, + uint16_t *out, unsigned *token, unsigned *error) +{ + size_t i; + if (!r || !out || !token || !error || size!=8) return 0; + for (i=0;i255) return 0; + if (get16(r+4)!=UMP_MARKER || !r[6]) return 0; + *token=r[6]; + return ump_prepare(b,r[7],get16(r),get16(r+2),1,out,error); +} +unsigned ump_can_fragment(const uint16_t *words, unsigned count, unsigned index, + unsigned token, unsigned error, ump_octet *out) +{ + if (!words || !out || count<2 || count>124 || (count&1) || + index>=count/2 || !token || token>255 || error>255) return 0; + put16(out,words[index*2]); put16(out+2,words[index*2+1]); + put16(out+4,(uint16_t)(UMP_MARKER+index)); out[6]=(ump_octet)token; out[7]=(ump_octet)error; + return 8; +} +size_t ump_rtu(const ump_backend *b, unsigned device, const ump_octet *r, + size_t size, ump_octet *out, size_t capacity) +{ + unsigned i; + if (!r || !out || size!=8 || capacity247 || r[0]!=device) return 0; + for (i=0;i<8;i++) if (r[i]>255) return 0; + if (ump_crc(r,8)) return 0; + return ump_rtu_response(b,device,r[1],get16(r+2),get16(r+4),out,capacity); +} +size_t ump_rtu_response(const ump_backend *b, unsigned device, unsigned fn, + uint16_t address, uint16_t value, ump_octet *out, size_t capacity) +{ + uint16_t words[UMP_MAX_WORDS]; + unsigned count,error; + if (!out || capacity247 || fn>127) return 0; + count=ump_prepare(b,fn,address,value,0,words,&error); + return ump_rtu_encode(device,fn,error,words,count,out,capacity); +} +size_t ump_rtu_encode(unsigned device, unsigned fn, unsigned error, + const uint16_t *words, unsigned count, ump_octet *out, size_t capacity) +{ + unsigned i; + size_t n; + uint16_t crc; + if (!out || !device || device>247 || fn>127 || error>255) return 0; + if (!error && (!words || (fn!=3 && fn!=6) || + (fn==3 && (!count || count>124)) || (fn==6 && count!=2))) return 0; + n=error ? 5 : (fn==6 ? 8 : 5+count*2); + if (capacity>8; + return n; +} diff --git a/python/set_devices/ump_logger.py b/python/set_devices/ump_logger.py index 2ae5f45..1ecb4f1 100644 --- a/python/set_devices/ump_logger.py +++ b/python/set_devices/ump_logger.py @@ -63,6 +63,8 @@ FIELDS = ( ('adc1', 'АЦП канал 1', 37, None), ('adc2', 'АЦП канал 2', 38, None), ('adc3', 'АЦП канал 3', 39, None), + ('zpt1', 'ЗПТ1: U1 + U2 (ПМ67, рег. 306)', 40, None), + ('zpt2', 'ЗПТ2: U3 + U4 (ПМ67, рег. 9)', 41, None), ) @@ -71,7 +73,7 @@ def request(controller: int, function: int, address: int, value: int) -> bytes: raise ValueError('Недопустимый адрес контроллера или значение') if function == 3: valid = ((address == BASE and value == 16) or - (address == DATA and 1 <= value <= 120 and value % WORDS == 0) or (address == LIVE and value == 124) or (address == EMU_CONTROL and value == 4) + (address == DATA and 1 <= value <= 120 and (value % WORDS == 0 or value in (42, 84))) or (address == LIVE and value == 124) or (address == EMU_CONTROL and value == 4) or (address == FAST_CAPABILITY and value == 4) or (FAST_DATA <= address < FAST_DATA + CAPACITY * WORDS and (address - FAST_DATA) % WORDS == 0 @@ -96,25 +98,27 @@ class Status: archive_count: int post_ms: int live_state: int + words: int = 40 + capacity: int = 800 @classmethod def decode(cls, words): - if (len(words) != 16 or words[0:2] != (0x554D, 2) or - words[4] != WORDS or words[5] != CAPACITY): - raise ValueError('Прошивка не поддерживает логгер УМП v2') + if (len(words) != 16 or words[0] != 0x554D or + (words[1], words[4], words[5]) not in ((2, 40, 800), (3, 42, 760))): + raise ValueError('Прошивка не поддерживает логгер УМП v2/v3') if words[9] != 6: raise ValueError('Логгер УМП доступен только на плате нагрузки dsk_LOAD') - if words[2] not in (0, 1, 2, 3) or words[3] > CAPACITY or not 1 <= words[6] <= 1000 or words[11] != 1000 or words[13] > CAPACITY or words[14] > 1000 or words[15] not in (0, 1, 3): + if words[2] not in (0, 1, 2, 3) or words[3] > words[5] or not 1 <= words[6] <= 1000 or words[11] != 1000 or words[13] > words[5] or words[14] > 1000 or words[15] not in (0, 1, 3): raise ValueError('Повреждён статус логгера') - return cls(words[2], words[3], words[6], words[10], words[11], *words[12:16]) + return cls(words[2], words[3], words[6], words[10], words[11], *words[12:16], words[4], words[5]) def sample(words, frequency=1000): - if len(words) != WORDS or frequency <= 0: + if len(words) not in (40, 42) or frequency <= 0: raise ValueError('Неверная запись логгера') result = {'time_ms': (words[0] | words[1] << 16) * 1000 / frequency, 'event': words[2]} for key, _label, index, bit in FIELDS: - value = words[index] + value = words[index] if index < len(words) else float('nan') if bit is not None: value = (value >> bit) & 1 elif 10 <= index <= 13 and value >= 32768: diff --git a/python/set_devices/ump_logger_can.py b/python/set_devices/ump_logger_can.py index d714ed4..b9843da 100644 --- a/python/set_devices/ump_logger_can.py +++ b/python/set_devices/ump_logger_can.py @@ -23,7 +23,7 @@ def build_request(mode, token, function, address, value): if not 1 <= token <= 255: raise ValueError('Неверный номер CAN-запроса') valid = ((function == 3 and ((address == BASE and value == 16) or - (address == DATA and value in (40, 80, 120)) or + (address == DATA and value in (40, 80, 120, 42, 84)) or (address == LIVE and value == MAX_WORDS) or (address == EMU_CONTROL and value == 4))) or (function == 6 and ((BASE <= address <= BASE+3 and 0 <= value <= 65535) or (address == EMU_CONTROL+2 and value in (0, 1)))))