Ускорить обратную связь R0–R3 задатчика для ПМ67

Перенести регистры индикации VPU в быстрый поток CAN, чтобы R3 не ожидал медленного обхода параметров. Сохранить управление светодиодами и настройки периодов EEPROM.

Добавить проверку настоящих масок и планировщика: интервал R3 составляет 120 мс при быстром делителе 2 и 600 мс при делителе 10. Сохранить изменённый исходник в UTF-8.
This commit is contained in:
2026-10-16 16:46:15 +03:00
parent 9ddea2d99a
commit ee79c7a376
2 changed files with 139 additions and 56 deletions
+56 -56
View File
@@ -1,8 +1,8 @@
/* Измерения, диагностика и периодическое обслуживание. ADC ISR
* поставляет выборки; таймер 1 обслуживает индикацию и телеметрию.
* Для dsk_LOAD он сначала завершает импульс СИФУ, затем вызывает
* расчёт ЦАП и снимок логгера. Частоты находятся в measure.h;
* при их изменении требуется пересчитать зависимые интервалы.
/* Измерения, диагностика и периодическое обслуживание. ADC ISR
* поставляет выборки; таймер 1 обслуживает индикацию и телеметрию.
* Для dsk_LOAD он сначала завершает импульс СИФУ, затем вызывает
* расчёт ЦАП и снимок логгера. Частоты находятся в measure.h;
* при их изменении требуется пересчитать зависимые интервалы.
*/
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "DSP2833x_SWPrioritizedIsrLevels.h"
@@ -20,13 +20,13 @@
#include "message.h"
#include "log_to_mem.h"
#include <math.h> // Это чтобы мерить амплитуду! sqrt без этого будет крив!!!
#include <math.h> // Это чтобы мерить амплитуду! sqrt без этого будет крив!!!
unsigned int CanPowse=CANPOWSE,CanGO=0;
unsigned int Maska[2][8];
int TPL_CANS=0; // Количество температурных каналов
int TPL_CANS=0; // Количество температурных каналов
int tpl_cans=0;
int cal_addr=0;
@@ -53,23 +53,23 @@ int Curr_Edge;
float powK[] =
{
0.127, // питание 380В
0.127, // питание 380В
0.127, // питание 380В
0.127, // питание 380В
1.000, // ток
1.000, // напражение
1.000, // ток
1.000, // напражение
0.0076, // питание 31В
0.0076, // питание 24В
0.0076, // питание 24В
0.0076, // питание 15В
1.0000, // термодатчик мелкосхема
0.0076, // питание 31В
0.0076, // питание 24В
0.0076, // питание 24В
0.0076, // питание 15В
1.0000, // термодатчик мелкосхема
};
FILTERBAT def_FILTERBAT = DEF_FILTERBAT;
FILTERBAT adc_filter[20+4]; // макс.TPL*2 + 4 калибр
FILTERBAT out_filter[20+4]; // макс.TPL*2 + 4 напраж
FILTERBAT zer_filter[4]; // 4 напраж
FILTERBAT adc_filter[20+4]; // макс.TPL*2 + 4 калибр
FILTERBAT out_filter[20+4]; // макс.TPL*2 + 4 напраж
FILTERBAT zer_filter[4]; // 4 напраж
long sens_count[24+4];
@@ -79,7 +79,7 @@ float K_T1,K_T2;
interrupt void cpu_timer1_isr_SENS(void);
/********************************************************************/
/* Расчет модула тока из показаний двух фаз */
/* Расчет модула тока из показаний двух фаз */
/********************************************************************/
float im_calc(float ia,float ib)
{
@@ -115,8 +115,8 @@ interrupt void cpu_timer1_isr_SENS(void)
int TST;
#define err_lamp work_lamp // это дла шкапа,
#define alm_lamp heat_lamp // чтоб было понатнее
#define err_lamp work_lamp // это дла шкапа,
#define alm_lamp heat_lamp // чтоб было понатнее
EALLOW;
CpuTimer1.InterruptCount++;
@@ -140,10 +140,10 @@ interrupt void cpu_timer1_isr_SENS(void)
if(Desk==dsk_LOAD)
{
/* Снять истёкший импульс до расчёта ЦАП, независимо от main. */
/* Снять истёкший импульс до расчёта ЦАП, независимо от main. */
UMP_pulse_tick();
Load_runner();
/* База времени 1 мс; снимок содержит результат текущего шага ЦАП. */
/* База времени 1 мс; снимок содержит результат текущего шага ЦАП. */
ump_log_tick();
return;
}
@@ -156,9 +156,9 @@ interrupt void cpu_timer1_isr_SENS(void)
((Desk==dsk_COMM) && heat_lamp) ) set_RES_OUT_1();
else clear_RES_OUT_1();
if(work_lamp) set_LED_OUT_1(); // Она же err_lamp дла шкапа
if(work_lamp) set_LED_OUT_1(); // Она же err_lamp дла шкапа
else clear_LED_OUT_1();
if(heat_lamp) set_LED_OUT_2(); // Она же alm_lamp дла шкапа
if(heat_lamp) set_LED_OUT_2(); // Она же alm_lamp дла шкапа
else clear_LED_OUT_2();
}
@@ -360,37 +360,37 @@ void Init_packMask()
switch(Desk)
{
case dsk_EPLT:
Maska[m_SLOW][0]|= 0x000F; // Полученное
Maska[m_SLOW][6]|= 0x01FC; // Яркость ламп и одиночество
Maska[m_FAST][0]|= 0x000F; // Возвращает R0–R3 задатчика в быстром потоке: маска 0x000F выбирает четыре младших регистра, чтобы подтверждение R3 для ПМ67 не ожидало медленного обхода параметров.
Maska[m_SLOW][6]|= 0x01FC; // Яркость ламп и одиночество
break;
default:
for(i=0;i<can;i++)
{
Maska[m_FAST][ i /16]|=(1<<( i %16)); // Диагностика
Maska[m_FAST][(i+24)/16]|=(1<<((i+24)%16)); // Показаниа
Maska[m_FAST][ i /16]|=(1<<( i %16)); // Диагностика
Maska[m_FAST][(i+24)/16]|=(1<<((i+24)%16)); // Показаниа
}
for(i=0;i<3; i++)
Maska[m_SLOW][i+3] = Maska[m_FAST][i]; // Уставки
Maska[m_SLOW][i+3] = Maska[m_FAST][i]; // Уставки
if(Desk==dsk_BKSD) Maska[m_SLOW][7]|= 0x0300; // Калибровка термодатчиков
if(Desk==dsk_BKSD) Maska[m_SLOW][7]|= 0x0300; // Калибровка термодатчиков
else
Maska[m_SLOW][7]|= 0x0F00; // Калибровка термодатчиков
Maska[m_SLOW][7]|= 0x0F00; // Калибровка термодатчиков
if(Kurrent)
{ Maska[m_SLOW][7]|= 0x000F; // Ноль датчиков напражениа
Maska[m_SLOW][7]|= 0x00F0; // Калибровка датчиков напражениа
Maska[m_FAST][6]|= 0x3F00; // Фазные значениа
{ Maska[m_SLOW][7]|= 0x000F; // Ноль датчиков напражениа
Maska[m_SLOW][7]|= 0x00F0; // Калибровка датчиков напражениа
Maska[m_FAST][6]|= 0x3F00; // Фазные значениа
}
if(Desk==dsk_LOAD)
{ Maska[m_SLOW][7]|= 0x0030; // Калибровка ЦАП
Maska[m_FAST][7]|= 0xE000; // Дублировать команды
Maska[m_FAST][1]|= 0x3000; // Управлающий ток СИФУ
{ Maska[m_SLOW][7]|= 0x0030; // Калибровка ЦАП
Maska[m_FAST][7]|= 0xE000; // Дублировать команды
Maska[m_FAST][1]|= 0x3000; // Управлающий ток СИФУ
} }
Maska[m_FAST][1]|= 0x0001; // Дискретные входы
Maska[m_SLOW][6]|= 0x0007; // Яркость ламп и периоды посылок
Maska[m_SLOW][7]|= 0xE000; // Адрес, команды, и чтоб не вылазило
Maska[m_FAST][1]|= 0x0001; // Дискретные входы
Maska[m_SLOW][6]|= 0x0007; // Яркость ламп и периоды посылок
Maska[m_SLOW][7]|= 0xE000; // Адрес, команды, и чтоб не вылазило
}
int er_anal(int term, long * count, long edge, int pre)
@@ -449,26 +449,26 @@ void Current_count(int chan)
modbus[0x64+chan] = Numb;
// Средний (он же нулевой) уровень показаний АЦП
// Средний (он же нулевой) уровень показаний АЦП
zer_count[chan] += (Numb-zer_count[chan])/(5.0 * ADC_FREQ);
Zero_lev[chan] = (int)(filterbat(&zer_filter[chan],zer_count[chan]));
// Мгновенное значение фазы
// Мгновенное значение фазы
Current = (Numb - Zero_lev[chan]) * powK[chan];
// Действующее (среднеквадратичное) значение фазы
// Действующее (среднеквадратичное) значение фазы
lev_quadr[chan] += ((Current*Current)-lev_quadr[chan])/(1.0 * ADC_FREQ);
lev_count[chan] = sqrt(lev_quadr[chan]);
// Запомним
// Запомним
if(ist)
{
if(Desk==dsk_COMM) aCurrent =-Current;
else aCurrent = Current; // Запомнили мгновенное значение - дла амплитуды
else aCurrent = Current; // Запомнили мгновенное значение - дла амплитуды
}
else
{
// Вычисление амплитуды
// Вычисление амплитуды
Amplitude = filterbat(&out_filter[sens], im_calc(Current,aCurrent));
if(sens_type[chan]==VOLTAGE)
@@ -485,7 +485,7 @@ modbus[0x64+chan] = Numb;
sens_data[sens-1] = Level;
}
// Действующее (среднеквадратичное) значение третьей фазы
// Действующее (среднеквадратичное) значение третьей фазы
Numb =-Current-aCurrent;
lev_quadr[thrd] += (Numb*Numb-lev_quadr[thrd])/(1.0 * ADC_FREQ);
lev_count[thrd] = sqrt(lev_quadr[thrd]);
@@ -511,12 +511,12 @@ modbus[0x68+fazz+1] = lev_count[chan];
modbus[0x68+fazz+2] = lev_count[thrd];
}
// Это максимальное
// Это максимальное
Numb = lev_count[chan];
if(Numb<lev_count[pair]) Numb = lev_count[pair];
if(Numb<lev_count[thrd]) Numb = lev_count[thrd];
// Зашиты!
// Зашиты!
error.all = 0;
no_ignor = !sens_error[chan].bit.Ignor;
@@ -659,7 +659,7 @@ void Temper_count(int chan)
if(chan>tpl_cans) return;
// Kun именно чтоб скомпенсировать нечетность
// Kun именно чтоб скомпенсировать нечетность
if(kun&1) Numb = (Numb-K300_2)*K_T2+ZERO;
else Numb = (Numb-K300_1)*K_T1+ZERO;
}
@@ -706,7 +706,7 @@ sens_data[chan+8] = adc_table_tpl[chan];
error.all = 0;
no_ignor = !sens_error[chan].bit.Ignor;
// Обрыв или неисправность
// Обрыв или неисправность
if(Temper==-20 || Temper==200)
{
error.bit.Tear = 1;
@@ -714,7 +714,7 @@ sens_data[chan+8] = adc_table_tpl[chan];
}
else
// Перегрев
// Перегрев
if(((Temper>sens_hi_edge[chan]-Cooling) && (sens_error[chan].bit.Hyper)) ||
(Temper>sens_hi_edge[chan]) )
{
@@ -724,7 +724,7 @@ sens_data[chan+8] = adc_table_tpl[chan];
}
else
// Предупреждение по температуре
// Предупреждение по температуре
if(Temper>sens_lo_edge[chan])
{
error.bit.Over = 1;
@@ -796,7 +796,7 @@ void Power_count(int chan)
error.bit.Stop = 1;
} }
/* Повышенное напражение
/* Повышенное напражение
if(Power > sens_hi_edge[chan])
{
error.bit.Hyper = 1;
+83
View File
@@ -0,0 +1,83 @@
"""Проверяет частоту подтверждения R3 на настоящих масках и планировщике 166.""" # Проверка выполняется на ПК без отправки команд плате.
from pathlib import Path # Находит исходники проекта и отдельно подготовленный вариант маски.
import os # Передаёт библиотеки GCC только дочерним процессам проверки.
import re # Извлекает определения и блоки C без комментариев.
import subprocess # Компилирует и исполняет реальные алгоритмы прошивки в модели времени.
import sys # Позволяет проверить старый и исправленный файлы одинаковым сценарием.
import tempfile # Ограничивает результаты компиляции временным каталогом.
root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parents[2] # Установленная проверка по умолчанию использует свой проект прошивки.
measure = Path(sys.argv[2]) if len(sys.argv) > 2 else root / 'Source/Internal/measure.c' # Подмена только этого файла позволяет воспроизвести ошибку до применения исправления.
def clean(path): # Удаляет комментарии перед поиском парных скобок C.
raw = path.read_bytes() # Сохраняет возможность чтения старых исходников Windows-1251.
source = raw.decode('utf-8') if _utf8(raw) else raw.decode('cp1251') # Не теряет русские комментарии исходника при выборе кодировки.
return re.sub(r'/\*.*?\*/|//[^\n]*', '', source, flags=re.S) # Скобки в пояснениях не меняют границы блока алгоритма.
def _utf8(raw): # Отличает старую Windows-кодировку от требуемого UTF-8 без замены символов.
try: # Корректный UTF-8 имеет приоритет перед совместимой однобайтовой кодировкой.
raw.decode('utf-8'); return True # Успешное декодирование разрешает чтение современного исходника.
except UnicodeDecodeError: # Старый файл будет прочитан в его исходной кодировке.
return False # Не выдаёт повреждённые комментарии за исходный текст.
def block(source, start): # Выделяет настоящий блок с вложенными условиями.
cursor, depth = source.index('{', start) + 1, 1 # Начинает после открывающей скобки внешнего блока.
while depth: # Ищет соответствующую закрывающую скобку.
depth += (source[cursor] == '{') - (source[cursor] == '}') # Учитывает только скобки очищенного C-кода.
cursor += 1 # Включает завершающую скобку в извлечённый текст.
return source[start:cursor] # Проверка не поддерживает отдельную копию алгоритма прошивки.
package = clean(root / 'Source/Internal/Include/package.h') # Значения режимов и индексов потоков берутся из проекта 166.
names = ('dsk_COMM', 'dsk_BKSD', 'dsk_BKST', 'dsk_EPLT', 'dsk_SHKF', 'dsk_LOAD', 'm_FAST', 'm_SLOW') # Модели нужны только константы, используемые масками.
defines = '\n'.join(re.search(r'^#define\s+' + name + r'\s+\d+', package, re.M).group() for name in names) # Не дублирует номера плат в тестовой карте.
measure_source = clean(measure) # Выбирает проверяемую версию Init_packMask.
mask_function = block(measure_source, measure_source.index('void Init_packMask(')) # Состав быстрой и медленной рассылки полностью берётся из прошивки.
main = clean(root / 'Source/Internal/main.c') # Последовательность передачи берётся из текущего рабочего main.c.
scheduler = block(main, main.index('if(CanGO)')) # Проверяет настоящий учёт занятого mailbox и продвижение адресов.
base_tick = int(re.search(r'^#define\s+CANPOWSE\s+(\d+)', clean(root / 'Source/Internal/Include/measure.h'), re.M).group(1)) # При READY_FREQ=1000 Гц делитель CANPOWSE равен числу миллисекунд базового шага.
model = r'''
#include <stdio.h> /* Выводит измеренные моделью интервалы подтверждения. */
#include <string.h> /* Сбрасывает состояние перед каждым сочетанием периодов EEPROM. */
/* DEFINES */
static int Desk, Kurrent, tpl_cans, CanGO, modbus[131], busy; /* Запас слов повторяет трёхсловное чтение у границы R127. */
static unsigned int Maska[2][8], cancount[2], cancell[2], now, received_at, max_gap, received_count; /* Каждое состояние соответствует маскам, очередям и статистике RX. */
#define Cancount (modbus+0x60) /* R96/R97 задают периоды fast/slow в базовых интервалах CANPOWSE. */
#define CHECK(condition) do { if (!(condition)) { printf("FAIL line %d: %s\n", __LINE__, #condition); return 1; } } while (0) /* Ошибка остаётся видимой и при включённой оптимизации. */
static int CAN_send(int port, int data[], int address) { /* Моделирует только принятие кадра свободным mailbox, не аппаратный ACK. */
(void)port; /* Номер порта не влияет на порядок технологических кадров. */
if(busy) return 0; /* Второй поток в том же тике должен повторить попытку позднее. */
busy=1; /* Один передатчик одновременно принимает только один кадр. */
if(address<=3 && address+2>=3) { /* R3 передаётся и отдельным кадром, и внутри трёхсловного фрагмента. */
if(data[3] != (int)now) return 0; /* Проверяет передачу свежего слова, изменённого перед текущим шагом. */
if(now-received_at>max_gap) max_gap=now-received_at; /* Наблюдает худшее ожидание следующего реального кадра с R3. */
received_at=now; received_count++; /* Фиксирует только принятый передатчиком регистр. */
} /* Завершает наблюдение очередного фрагмента. */
return 1; /* Успешная постановка позволяет настоящему main продвинуть адрес. */
} /* Модель намеренно не заменяет проверку физического CAN на плате. */
/* MASK */
static void step(void) { int i, mask; /* Локальные переменные повторяют контекст фонового цикла main.c. */
/* SCHEDULER */
} /* Выполняет один базовый интервал настоящего планировщика. */
int main(void) { /* Покрывает штатные периоды 166 и варианты из EEPROM, включая отключённый медленный поток. */
const unsigned periods[][2]={{2,101},{10,101},{12,50},{2,0}}; /* Периоды заданы в тиках CANPOWSE: 2 — штатный быстрый делитель VPU-166, 101 — медленный. */
unsigned scenario, tick; /* Сценарии независимы, каждый охватывает сто секунд модельного времени. */
Desk=dsk_EPLT; Init_packMask(); /* Строит реальную карту задатчика выбранной версии прошивки. */
CHECK((Maska[m_FAST][0]&15)==15 && !(Maska[m_SLOW][0]&15)); /* R0–R3 должны подтверждаться быстрым потоком независимо от периода параметров. */
CHECK(Maska[m_FAST][1]==1 && Maska[m_SLOW][6]==0x1FF && Maska[m_SLOW][7]==0xE000); /* Кнопки R16 и медленные параметры R96–R104/R125–R127 сохраняют состав. */
for(scenario=0;scenario<sizeof(periods)/sizeof(periods[0]);scenario++) { /* Проверяет передачу R3 при разных сохранённых настройках платы. */
memset(cancount,0,sizeof(cancount)); memset(cancell,0,sizeof(cancell)); /* Начинает каждый сценарий с первого адреса и нулевого срока. */
received_at=max_gap=received_count=now=0; /* Старые измерения не сокращают наблюдаемую задержку нового сценария. */
Cancount[0]=(int)periods[scenario][0]; Cancount[1]=(int)periods[scenario][1]; /* Использует заданные периоды без сброса EEPROM к defaults. */
for(tick=1;tick<=5000;tick++) { /* Пять тысяч шагов по 20 мс включают много полных обходов медленной очереди. */
now=tick; modbus[3]=(int)tick; busy=0; CanGO=1; step(); /* Меняет R3 перед каждым шагом и завершает предыдущую аппаратную передачу. */
} /* В статистику входят начальное ожидание и все последующие интервалы. */
CHECK(received_count>0 && max_gap<=3*(periods[scenario][0]+1)); /* Три быстрых кадра R0–R2/R3–R5/R16–R18 ограничивают ожидание независимо от slow. */
printf("PASS fast=%u slow=%u: R3 max=%u ms, updates=%u\n",periods[scenario][0],periods[scenario][1],max_gap*BASE_TICK,received_count); /* Переводит тики в миллисекунды базового таймера платы. */
} /* Все сочетания периодов обязаны давать частое подтверждение R3. */
return 0; /* Успех означает прохождение модели, а не испытание физического устройства. */
} /* Завершает регрессию задержки обратной связи VPU. */
''' # Вставки ниже исполняют текущий код прошивки, а не его переписанную модель.
model = model.replace('/* DEFINES */', defines + f'\n#define BASE_TICK {base_tick} /* Длительность базового шага в миллисекундах при таймере 1000 Гц. */').replace('/* MASK */', mask_function).replace('/* SCHEDULER */', scheduler) # Связывает реальные маски и планировщик с моделью одного занятого передатчика.
compiler = Path(os.environ.get('CC', 'C:/msys64/mingw64/bin/gcc.exe')) # Использует установленный GCC без загрузки инструментов.
environment = dict(os.environ, PATH=str(compiler.parent) + os.pathsep + os.environ.get('PATH', '')) # Делает библиотеки компилятора доступными только дочерним программам.
with tempfile.TemporaryDirectory(prefix='vpu-r3-rx-') as directory: # Удаляет только собственные временные результаты после проверки.
source, executable = Path(directory) / 'test.c', Path(directory) / 'test.exe' # Разделяет C-код проверки и исполняемую программу.
source.write_text(model, encoding='utf-8') # Все создаваемые исходники и русские комментарии сохраняются в UTF-8.
subprocess.run([str(compiler), '-std=c99', '-O3', '-Wall', '-Wextra', '-Werror', str(source), '-o', str(executable)], env=environment, check=True) # Требует чистой компиляции настоящих алгоритмов при оптимизации.
subprocess.run([str(executable)], env=environment, check=True, timeout=10) # Зависание либо нарушение ограничения задержки проваливает регрессию.