Support PM67 voltage fields in UMP logger v3

This commit is contained in:
2026-10-01 16:00:25 +03:00
parent 3457657979
commit c0ee7c46ea
6 changed files with 222 additions and 9 deletions

View File

@@ -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 <stdint.h>
#include <stddef.h>
#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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;i<n;i++) out[i] = b->read(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;i<size;i++) if (r[i]>255) 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 || capacity<UMP_RTU_CAPACITY ||
!device || device>247 || 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 || capacity<UMP_RTU_CAPACITY || !device || device>247 || 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<n) return 0;
out[0]=(ump_octet)device; out[1]=(ump_octet)fn;
if (error) { out[1]|=128; out[2]=(ump_octet)error; n=3; }
else if (fn==6) { put16(out+2,words[0]); put16(out+4,words[1]); n=6; }
else {
out[2]=(ump_octet)(count*2);
for (i=0;i<count;i++) put16(out+3+i*2,words[i]);
n=3+count*2;
}
crc=ump_crc(out,n); out[n++]=crc&255U; out[n++]=crc>>8;
return n;
}

View File

@@ -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:

View File

@@ -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)))))