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), ('adc1', 'АЦП канал 1', 37, None),
('adc2', 'АЦП канал 2', 38, None), ('adc2', 'АЦП канал 2', 38, None),
('adc3', 'АЦП канал 3', 39, 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('Недопустимый адрес контроллера или значение') raise ValueError('Недопустимый адрес контроллера или значение')
if function == 3: if function == 3:
valid = ((address == BASE and value == 16) or 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 (address == FAST_CAPABILITY and value == 4)
or (FAST_DATA <= address < FAST_DATA + CAPACITY * WORDS or (FAST_DATA <= address < FAST_DATA + CAPACITY * WORDS
and (address - FAST_DATA) % WORDS == 0 and (address - FAST_DATA) % WORDS == 0
@@ -96,25 +98,27 @@ class Status:
archive_count: int archive_count: int
post_ms: int post_ms: int
live_state: int live_state: int
words: int = 40
capacity: int = 800
@classmethod @classmethod
def decode(cls, words): def decode(cls, words):
if (len(words) != 16 or words[0:2] != (0x554D, 2) or if (len(words) != 16 or words[0] != 0x554D or
words[4] != WORDS or words[5] != CAPACITY): (words[1], words[4], words[5]) not in ((2, 40, 800), (3, 42, 760))):
raise ValueError('Прошивка не поддерживает логгер УМП v2') raise ValueError('Прошивка не поддерживает логгер УМП v2/v3')
if words[9] != 6: if words[9] != 6:
raise ValueError('Логгер УМП доступен только на плате нагрузки dsk_LOAD') 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('Повреждён статус логгера') 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): def sample(words, frequency=1000):
if len(words) != WORDS or frequency <= 0: if len(words) not in (40, 42) or frequency <= 0:
raise ValueError('Неверная запись логгера') raise ValueError('Неверная запись логгера')
result = {'time_ms': (words[0] | words[1] << 16) * 1000 / frequency, 'event': words[2]} result = {'time_ms': (words[0] | words[1] << 16) * 1000 / frequency, 'event': words[2]}
for key, _label, index, bit in FIELDS: 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: if bit is not None:
value = (value >> bit) & 1 value = (value >> bit) & 1
elif 10 <= index <= 13 and value >= 32768: 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: if not 1 <= token <= 255:
raise ValueError('Неверный номер CAN-запроса') raise ValueError('Неверный номер CAN-запроса')
valid = ((function == 3 and ((address == BASE and value == 16) or 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 (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 (function == 6 and ((BASE <= address <= BASE+3 and 0 <= value <= 65535) or
(address == EMU_CONTROL+2 and value in (0, 1))))) (address == EMU_CONTROL+2 and value in (0, 1)))))