Добавлен CAN-доступ к регистратору УМП через отдельный канал ответов

This commit is contained in:
2026-09-21 00:49:53 +03:00
parent df719229b7
commit a3633d8ede
6 changed files with 254 additions and 5 deletions

113
Doc/PERIPH28335_CAN.md Normal file
View File

@@ -0,0 +1,113 @@
# periph_28335: общая панель RS/CAN и регистратор УМП
## Где находятся панели
В верхнем ряду вкладок, рядом с **Altera Logic**, появилась **periph_28335**.
Внутри неё:
- **Dima / CAN terminal** — существующий CAN-терминал, его проекты, узлы и настройки адаптера;
- **rs232_periph_28335** — существующие операции RS232/485, Modbus и настройки COM;
- **Логгер УМП** — единая панель графиков и архива для обоих транспортов.
В общей группе CAN остались «Шина CAN / мост» и «DevBoard_V1».
В общей группе RS232/485 остался RS_TERMINAL_2812.
Терминалы сохраняют свои соединения: перенос вкладок не создаёт новые порты.
## Онлайн и чтение по CAN
1. Откройте **periph_28335 → Dima / CAN terminal → Настройки**.
2. Выберите Candle/gs_usb либо SLCAN/Lawicel и канал адаптера.
Для штатной прошивки Бальзам 167 используется CAN 250000 бит/с.
Нажмите «Подключить».
3. Перейдите в соседнюю вкладку **Логгер УМП**.
4. В строке **Связь логгера** выберите **CAN · адаптер Dima**.
5. Укажите **CAN-плата (Mode)**: физический номер платы в прошивке, 1..16.
Платы нагрузки Бальзам 167 имеют номера 5/6; выбирайте фактически подключённую.
Это не RS-адрес 16 и не индекс строки таблицы Dima. Выбор проекта в Dima
не меняет CAN-адрес регистратора: он явно определяется этим полем.
6. Нажмите **Начать запись** или **Онлайн**, если запись на плате уже идёт.
Остальные действия, флажки, легенда, единицы мА и CSV одинаковы для RS и CAN.
7. После команды отключения и секунды постинтервала нажмите
**Прочитать отключение**. Запись текущего банка на плате продолжается.
Кнопка **Настройки связи** открывает настройки выбранного транспорта.
Состояние адаптера/порта и адреса CAN постоянно отображаются над графиком.
**Прервать обмен** останавливает чтение GUI, не посылая команду остановки
регистратора или УМП. При переключении канала/платы незавершённые запросы
отменяются, поздние ответы не продолжают старую цепочку.
Для RS выберите **RS232/485** и настройте COM в соседней RS-панели:
обычно 115200, адрес 16. Сообщения «не подключён» и «занят запросом» разделены.
Отключение RS-порта не останавливает CAN-просмотр. CAN-разрыв не мешает RS.
Демо, если оно включено в сборке, по-прежнему работает без физической платы.
## Изменения прошивки
Для CAN требуется новая прошивка этой ветки. Предыдущая версия регистратора
умела отдавать данные только через Modbus. Старая плата игнорирует новые
служебные запросы; GUI сообщает о тайм-ауте без изменения команд УМП.
- MBOX0: прежняя передача технологических данных, ID 0xBA0010 + Mode - 1.
- MBOX1: прежний приём команд, ID 0xBA0000 + Mode - 1; дополнительно
распознаётся служебный заголовок 0x1F00 и длина ровно 8 байт.
- MBOX2: отдельные ответы регистратора, ID 0xBA0020 + Mode - 1.
Биты маски записи регистров у служебного запроса равны нулю. Обработчик
перехватывает его до изменения modbus[] и до журналирования CAN-команд:
опрос собственного журнала не заполняет журнал.
Запрос ставится в очередь из ISR. Основной цикл формирует целый снимок
под короткой блокировкой IRQ и отправляет максимум один кадр за вызов
UMP_CAN_service(). Ожидания передачи в цикле нет. Перед следующим
фрагментом приоритет отдаётся ожидающей штатной телеметрии MBOX0.
Если ACK служебного mailbox не приходит 250 тиков таймера, передача
отменяется. При этом штатные CAN-команды и MBOX0 не отменяются.
По CAN доступны те же расширенные адреса 0x1000 / 0x1010 / 0x1100,
состав записи и версия снимка 2. Новый протокол не даёт писать через
служебный запрос в технологические адреса 0..127.
## Формат кадров
CAN 2.0B, расширенный ID, 8 байт данных. Порядок соответствует существующей
прошивке: MDL (слова 1,2), затем MDH (заголовок, слово 0). Каждое слово
содержит сначала старший байт.
Запрос: `[адрес BE16, значение/длина BE16, 0x1F00 BE16, token:function BE16]`.
Функция 3 — чтение, 6 — запись; token — номер запроса 1..255.
Ответ: `[данные0 BE16, данные1 BE16, (0x1F00+индекс) BE16, token:error BE16]`.
Номер фрагмента 0..61, в нём два слова снимка. Ошибка 0 означает успех,
1 — неизвестная функция, 2 — неверный адрес/диапазон, 3 — запрещённое значение.
У записи успешный ответ содержит эхо адреса и значения.
Пример: Mode=5, token=7, чтение статуса 16 слов.
Запрос ID `0x00BA0004`, данные `10 00 00 10 1F 00 07 03`.
Ответы ID `0x00BA0024`, восемь фрагментов по два слова.
Онлайн-снимок из 124 слов передаётся 62 фрагментами.
GUI принимает только ответ выбранной платы с номером текущего запроса,
восстанавливает порядок фрагментов, игнорирует одинаковые дубликаты и
отклоняет противоречащие. Неполный снимок не попадает на график.
После потери подтверждения записи GUI прямо сообщает, что команда могла
выполниться; автоматического повторного старта регистратора нет.
Онлайн показывает последние три записи из полного снимка. При высокой
нагрузке шины/низкой скорости возможны пропуски онлайн-отображения;
они считаются существующим счётчиком. Архив читается целиком по блокам.
Предполагается один активный читатель архива: его курсор общий для RS/CAN.
Одновременное управление архивным курсором из двух независимых программ
не поддерживается.
## Проверки и ограничения
Пройдены тесты протокола, сборки всех 62 фрагментов, неверного адреса/токена,
пропущенных и повторных фрагментов, ACK и ошибок, отмены при смене канала,
иерархии вкладок и сохранения RS/демо. Набор тестов выполнен на Qt 6 и Qt 5.
В прошивке проверен настоящий код формирования ответов через host-тест;
изменённые C-файлы прошли проверку синтаксиса Clang с исходными заголовками.
Сборка TI/CCS, проверка MAP и испытание CAN на реальной плате ещё требуются.
Особенно проверить тайминги IRQ и приоритет штатной телеметрии на нагруженной
шине. Файлы прошивки Bin/*.out/hex не заменены. Архив остаётся в ОЗУ:
команда отключения фиксирует его, пропадание питания стирает.

View File

@@ -1,3 +1,6 @@
void InitCan(int Port, int DevNum);
void CAN_send(int Port, int data[], int Addr);
extern int CAN_input_data[];
/* Íåáëîêèðóþùàÿ ïåðåäà÷à îòâåòîâ ëîããåðà îòäåëüíûì CAN mailbox. */
void UMP_CAN_service(void);

View File

@@ -0,0 +1,39 @@
/* Ñëóæåáíûé CAN-ïðîòîêîë ëîããåðà ÓÌÏ. Íå èñïîëüçóåò àäðåñà 0..127
* òåõíîëîãè÷åñêîãî îáìåíà. Ôîðìàò ïîëåçíûõ äàííûõ ñîâìåñòèì ñ ïîðÿäêîì
* ñëîâ ñòàðîãî CAN: MDL (ñëîâà 1,2), çàòåì MDH (ìàðêåð, ñëîâî 0).
* Çàïðîñ: ìàðêåð 0x1F00, ñëîâî 0 = token:8 | function:8,
* ñëîâà 1,2 = àäðåñ ðàñøèðåíèÿ è ÷èñëî ñëîâ/çàïèñûâàåìîå çíà÷åíèå.
* Îòâåòû: ìàðêåð 0x1F00 + íîìåð ôðàãìåíòà, ñëîâî 0 = token:8 | error:8,
* ñëîâà 1,2 = äâà ñëîâà åäèíîãî çàôèêñèðîâàííîãî ñíèìêà.
* Áèòû ìàñêè çàïèñè â çàãîëîâêå çàïðîñà ðàâíû íóëþ: ñòàðàÿ ïëàòà
* èãíîðèðóåò çàïðîñ, íå èçìåíÿÿ òåõíîëîãè÷åñêèå ðåãèñòðû. */
#ifndef UMP_CAN_PROTOCOL_H
#define UMP_CAN_PROTOCOL_H
#include "log_to_mem.h"
#define UMP_CAN_MARKER 0x1F00U
#define UMP_CAN_MAX_WORDS 124U
/* Âûçûâàþùèé êîä çàùèùàåò âåñü ñíèìîê îò IRQ. Ôóíêöèÿ íå æä¸ò CAN è
* íå ïèøåò çà ïðåäåëàìè reply[124]. Îøèáêè âîçâðàùàþòñÿ îòäåëüíûì êàäðîì. */
static unsigned int ump_can_prepare(unsigned int function, unsigned int address,
unsigned int value, unsigned int *reply, unsigned int *error)
{
unsigned int i;
*error = 0;
if (function == 3) {
if (!((address == UMP_LOG_BASE && value == 16) ||
(address == UMP_LOG_DATA && (value == 40 || value == 80 || value == 120)) ||
(address == UMP_LOG_LIVE && value == 124))) *error = 2;
else {
for (i = 0; i < value; i++) reply[i] = ump_log_read(address+i);
return value;
}
} else if (function == 6) {
if (address < UMP_LOG_BASE || address > UMP_LOG_BASE+3) *error = 2;
else if (!ump_log_write(address, value)) *error = 3;
} else *error = 1;
/* Äëÿ çàïèñè âîçâðàùàåì ýõî àäðåñà è çíà÷åíèÿ, äëÿ îøèáêè — å¸ êîíòåêñò. */
reply[0] = address; reply[1] = value;
return 2;
}
#endif

View File

@@ -12,6 +12,7 @@
#include "message.h"
#include "peripher.h"
#include "log_to_mem.h"
#include "ump_can_protocol.h"
// Prototype statements for functions found within this file.
@@ -31,6 +32,16 @@ int CanTimeOutErrorTR = 0;
int wait=0;
/* MBOX2 âûäåëåí ïîä îòâåòû ðåãèñòðàòîðà; øòàòíûé MBOX0 ïðîäîëæàåò
* ïåðåäàâàòü òåõíîëîãè÷åñêèå äàííûå íåçàâèñèìî. ISR òîëüêî ñòàâèò çàïðîñ,
* à ñíèìîê è îòïðàâêà êîðîòêèìè øàãàìè âûïîëíÿþòñÿ â îñíîâíîì öèêëå. */
static volatile unsigned int ump_can_pending;
static volatile unsigned int ump_can_tag, ump_can_address, ump_can_value;
static unsigned int ump_can_reply[UMP_CAN_MAX_WORDS];
static unsigned int ump_can_length, ump_can_index, ump_can_reply_tag;
static unsigned int ump_can_waiting;
static unsigned long ump_can_sent_at;
void InitCan(int Port, int DevNum)
{
struct ECAN_REGS ECanShadow;
@@ -62,6 +73,7 @@ void InitCan(int Port, int DevNum)
// Specify that 8 bits will be sent/received
ECanMboxes->MBOX0.MSGCTRL.all = 0x00000008;
ECanMboxes->MBOX1.MSGCTRL.all = 0x00000008;
ECanMboxes->MBOX2.MSGCTRL.all = 0x00000008;
// Disable all Mailboxes
// Required before writing the MSGIDs
@@ -73,11 +85,12 @@ void InitCan(int Port, int DevNum)
// çàäàåì àäðåñ 1 àùèêa íà ïðèåì
ECanMboxes->MBOX1.MSGID.all = id + DevNum; //ïîìåíàòü!!!! 1 è 0!!!
// задаем режимы работы ащикa 0 на передачу, остальные на прием
ECanRegs->CANMD.all = 0xFFFFFFFE;
// MBOX0 è MBOX2 ïåðåäàþò äàííûå; MBOX1 ïðèíèìàåò êîìàíäû.
ECanMboxes->MBOX2.MSGID.all = id + 0x20 + DevNum;
ECanRegs->CANMD.all = 0xFFFFFFFA;
// выбираем только 2 ащикa дла работы, остальные запрещаем
ECanRegs->CANME.all = 0x00000003;
// Âêëþ÷àåì òðè mailbox: òåëåìåòðèÿ, êîìàíäû è îòâåòû ëîããåðà.
ECanRegs->CANME.all = 0x00000007;
// Clear all TAn bits
ECanRegs->CANTA.all = 0xFFFFFFFF;
@@ -197,6 +210,18 @@ void Handlai(volatile struct ECAN_MBOXES * ECanMboxes)
hiword = ECanMboxes->MBOX1.MDH.all;
loword = ECanMboxes->MBOX1.MDL.all;
/* Ñëóæåáíûé çàïðîñ ëîããåðà íå ÿâëÿåòñÿ êîìàíäîé ÓÌÏ è ñàì íå äîëæåí
* çàïîëíÿòü æóðíàë CAN-ñîáûòèÿìè ïðè îíëàéí-÷òåíèè. Êîðîòêèå êàäðû
* îòáðàñûâàþòñÿ: íåçàïîëíåííûå áàéòû mailbox ìîãëè îñòàòüñÿ îò ïðîøëîãî. */
if ((unsigned int)(hiword >> 16) == UMP_CAN_MARKER) {
if (ECanMboxes->MBOX1.MSGCTRL.bit.DLC == 8) {
ump_can_tag = (unsigned int)hiword;
ump_can_address = (unsigned int)(loword >> 16);
ump_can_value = (unsigned int)loword;
ump_can_pending = 1;
}
return;
}
adr = (hiword >> 16);
bit[0] = adr & 0x8000;
@@ -260,3 +285,52 @@ interrupt void CANa_reset_err(void)
//===========================================================================
// No more.
//===========================================================================
/* Îäèí âûçîâ îáñëóæèâàåò ìàêñèìóì îäèí êàäð è íèêîãäà íå æä¸ò ACK â öèêëå.
* Ñíèìîê ôèêñèðóåòñÿ îäèí ðàç íà çàïðîñ: CAN-ôðàãìåíòû íå ñìåøèâàþò
* çíà÷åíèÿ äî/ïîñëå ïåðåêëþ÷åíèÿ êîëüöåâîãî áàíêà. Íîâûå çàïðîñû çàáèðàåì
* ïîñëå çàâåðøåíèÿ ïðåäûäóùåãî îòâåòà. GUI äåðæèò òîëüêî îäèí çàïðîñ. */
void UMP_CAN_service(void)
{
unsigned int saved, tag, address, value, error;
unsigned long hiword, loword;
if (ump_can_waiting) {
if (ECanaRegs.CANTA.all & 4UL) {
ECanaRegs.CANTA.all = 4UL;
ump_can_waiting = 0;
} else if ((ECanaRegs.CANAA.all & 4UL) ||
((unsigned long)CpuTimer1.InterruptCount - ump_can_sent_at > 250UL)) {
/* Íåò ïîäòâåðæäåíèÿ øèíû: îòìåíÿåì òîëüêî ñëóæåáíûé mailbox.
* GUI îáíàðóæèò íåïîëíûé îòâåò ïî òàéì-àóòó; óïðàâëåíèå MBOX0
* è ïðè¸ì êîìàíä MBOX1 ïðîäîëæàþòñÿ. */
ECanaRegs.CANTRR.all = 4UL;
/* Äî ôàêòè÷åñêîãî çàâåðøåíèÿ îòìåíû mailbox íå ïåðåèñïîëüçóåì. */
if (!(ECanaRegs.CANAA.all & 4UL)) return;
ECanaRegs.CANAA.all = 4UL;
ump_can_waiting = 0;
ump_can_length = 0;
} else return;
}
if (ump_can_index >= ump_can_length && ump_can_pending) {
saved = __disable_interrupts();
tag = ump_can_tag; address = ump_can_address; value = ump_can_value;
ump_can_pending = 0;
ump_can_length = ump_can_prepare(tag & 0xFF, address, value, ump_can_reply, &error);
ump_can_reply_tag = (tag & 0xFF00) | error;
ump_can_index = 0;
__restore_interrupts(saved);
}
if (ump_can_index >= ump_can_length) return;
/* Ïåðåä ñëåäóþùèì ôðàãìåíòîì óñòóïàåì øòàòíîé òåëåìåòðèè MBOX0. */
if (ECanaRegs.CANTRS.all & 1UL) return;
hiword = ((unsigned long)(UMP_CAN_MARKER + ump_can_index/2) << 16) | ump_can_reply_tag;
loword = ((unsigned long)ump_can_reply[ump_can_index] << 16) | ump_can_reply[ump_can_index+1];
ECanaRegs.CANTA.all = 4UL; ECanaRegs.CANAA.all = 4UL;
ECanaMboxes.MBOX2.MDH.all = hiword;
ECanaMboxes.MBOX2.MDL.all = loword;
ump_can_sent_at = CpuTimer1.InterruptCount;
ECanaRegs.CANTRS.all = 4UL;
ump_can_waiting = 1;
ump_can_index += 2;
}

View File

@@ -109,6 +109,8 @@ void main()
while(1)
{
/* Чтение логгера по CAN не блокирует цикл управления. */
UMP_CAN_service();
if(CanGO)
{
CanGO=0;

View File

@@ -1,4 +1,5 @@
#include "../../Source/Internal/log_to_mem.c"
#include "../../Source/Internal/Include/ump_can_protocol.h"
struct mock_gpio GpioDataRegs;
int READY = 1, Desk = 6, Mode = 5;
int modbus[128];
@@ -10,7 +11,7 @@ unsigned int ADC_table[4];
/* Возвращаем номер строки при ошибке, ноль при успехе; CRT не требуется. */
__declspec(dllexport) int run_tests(void)
{
unsigned int i, generation, archived;
unsigned int i, generation, archived, reply[UMP_CAN_MAX_WORDS], error, length;
unsigned long sequence;
CHECK(!ump_log_write(UMP_LOG_BASE+1, 0));
CHECK(!ump_log_write(UMP_LOG_BASE+1, 1001));
@@ -67,5 +68,22 @@ __declspec(dllexport) int run_tests(void)
CHECK(ump_post == 1000);
for (i=0; i<1000; i++) ump_log_tick();
CHECK(ump_generation == generation+1);
/* CAN читает те же регистры и формирует цельный снимок из 124 слов.
* Запись по старым технологическим адресам через сервис запрещена. */
length = ump_can_prepare(3, UMP_LOG_BASE, 16, reply, &error);
CHECK(!error && length == 16 && reply[0] == 0x554D && reply[1] == 2);
length = ump_can_prepare(3, UMP_LOG_LIVE, 124, reply, &error);
CHECK(!error && length == 124);
ump_can_prepare(3, UMP_LOG_LIVE, 126, reply, &error);
CHECK(error == 2);
ump_can_prepare(6, 127, 0x80, reply, &error);
CHECK(error == 2 && modbus[127] == 0);
length = ump_can_prepare(6, UMP_LOG_BASE, 0, reply, &error);
CHECK(!error && length == 2 && reply[0] == UMP_LOG_BASE && reply[1] == 0);
CHECK(ump_state == 3);
ump_can_prepare(6, UMP_LOG_BASE+1, 0, reply, &error);
CHECK(error == 3);
ump_can_prepare(99, UMP_LOG_BASE, 16, reply, &error);
CHECK(error == 1);
return 0;
}