diff --git a/Doc/PERIPH28335_CAN.md b/Doc/PERIPH28335_CAN.md new file mode 100644 index 0000000..e0e1c9e --- /dev/null +++ b/Doc/PERIPH28335_CAN.md @@ -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 не заменены. Архив остаётся в ОЗУ: +команда отключения фиксирует его, пропадание питания стирает. diff --git a/Source/Internal/Include/ecan.h b/Source/Internal/Include/ecan.h index d252d5d..36cf526 100644 --- a/Source/Internal/Include/ecan.h +++ b/Source/Internal/Include/ecan.h @@ -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); diff --git a/Source/Internal/Include/ump_can_protocol.h b/Source/Internal/Include/ump_can_protocol.h new file mode 100644 index 0000000..e374045 --- /dev/null +++ b/Source/Internal/Include/ump_can_protocol.h @@ -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 diff --git a/Source/Internal/ecan.c b/Source/Internal/ecan.c index db0dfeb..cf586c7 100644 --- a/Source/Internal/ecan.c +++ b/Source/Internal/ecan.c @@ -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; +} diff --git a/Source/Internal/main.c b/Source/Internal/main.c index d7827db..dbcaf66 100644 --- a/Source/Internal/main.c +++ b/Source/Internal/main.c @@ -109,6 +109,8 @@ void main() while(1) { + /* CAN . */ + UMP_CAN_service(); if(CanGO) { CanGO=0; diff --git a/tests/ump_logger/recorder_test.c b/tests/ump_logger/recorder_test.c index 3dbcbd3..550feaf 100644 --- a/tests/ump_logger/recorder_test.c +++ b/tests/ump_logger/recorder_test.c @@ -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; }