#include "DSP281x_Device.h" /* Использует оригинальные регистры F2812, не подменяя их заголовками F28335. */ #include "DSP281x_Examples.h" /* Подключает штатные PLL, watchdog, PIE и CpuTimers выбранного TI SDK. */ #include "../../../ports/tms320f2812/boot2812.h" /* Выбирает отдельные NOR, Product Type и ProtoCAN-адрес F2812. */ #include /* Инициализирует все состояния независимо от прежнего содержимого .ebss. */ static pcan_boot_t boot; /* Этот экземпляр относится только к ПМ67/F2812 и не делит состояние с F28335. */ static volatile unsigned int head, tail, overflow; /* ISR публикует готовые элементы очереди только после заполнения всех полей. */ static struct { uint32_t id; unsigned int dlc; pcan_boot_byte_t data[8]; } queue[32]; /* Очередь на 31 кадр принимает окно из 16 блоков во время NOR-операций main. */ static unsigned int tx_busy, reset_requested, operation_active, current_command; /* Отделяет физическое TX ACK, запрос reset и текущую долгую команду. */ static uint32_t sent_at, busy_at; /* Миллисекундные отметки ограничивают TX и частоту BUSY-ответов. */ unsigned long Boot2812_Now(void) /* Все тайм-ауты порта работают от одного Timer1. */ { /* Прямой счётчик C28x имеет ту же ширину, что uint32_t. */ return CpuTimer1.InterruptCount; /* Период Timer1 ниже установлен в одну миллисекунду. */ } /* Беззнаковые разности сохраняют корректность через переполнение. */ interrupt void boot_tick(void) /* Прямое прерывание Timer1 не обращается к коду приложения. */ { /* Долгие erase/verify не должны замораживать часы либо приём CAN. */ CpuTimer1.InterruptCount++; KickDog(); /* Миллисекундный tick и штатное обслуживание watchdog загрузчика выполняются даже во время polling NOR. */ } /* Поведение watchdog загружаемого приложения задаёт его собственный startup. */ static void receive(volatile struct MBOX *box, uint32_t mask) /* Копирует один Extended кадр выделенного mailbox в очередь. */ { /* Wire-парсер и запись памяти выполняются позднее общим ядром в main. */ unsigned int next = (head + 1U) & 31U, i, type; uint32_t id, low, high; /* Индексы кольца ограничены 32 элементами независимо от содержимого CAN. */ if (!(ECanaRegs.CANRMP.all & mask)) return; /* Не читает старое содержимое mailbox без нового RX-флага. */ id = box->MSGID.all & 0x1FFFFFFFUL; type = pcan_boot_id_msg_type(id); /* Получает 29-битный ID и тип через общий codec. */ if ((box->MSGID.all & 0x80000000UL) && !box->MSGCTRL.bit.RTR && pcan_boot_id_route(id) == 0U && (type == 9U || type == 10U || type == 13U) && ((pcan_boot_id_device_type(id) == BOOT2812_DEVICE_TYPE && pcan_boot_id_device_id(id) == BOOT2812_DEVICE_ID) || (type == 13U && pcan_boot_id_device_type(id) == 7U && pcan_boot_id_device_id(id) == 15U && !pcan_boot_id_msg_body(id)))) { /* Допускает только адресный Boot и точное discovery; чужие приборы и RTR не занимают очередь. */ if (next == tail) overflow = 1U; /* Потеря кадра отменит сессию, а не разрешит незаметно неполный файл. */ else { /* Сначала формирует весь элемент, затем публикует новый head. */ queue[head].id = id; queue[head].dlc = box->MSGCTRL.bit.DLC; /* Сохраняет фактические ID и длину для проверок общего ядра. */ low = box->MDL.all; high = box->MDH.all; /* Два 32-битных снимка не смешивают октеты разных чтений регистров. */ for (i = 0; i < 4U; ++i) { queue[head].data[i] = (low >> (i * 8U)) & 255U; queue[head].data[i + 4U] = (high >> (i * 8U)) & 255U; } /* DBO=0 передаёт младшие октеты MDL/MDH первыми. */ head = next; /* Публикация после копирования исключает чтение main частично заполненного элемента. */ } /* Завершает формирование очередного элемента кольца. */ } /* Остальные кадры не изменяют никакие состояния протокола. */ ECanaRegs.CANRMP.all = mask; /* Подтверждает только собственный прочитанный mailbox. */ } /* В ISR отсутствуют erase, write, CRC и загрузка приложения. */ interrupt void boot_can_rx(void) /* Использует отдельный аппаратный CAN F2812 на GPIOF6/F7. */ { /* Оба служебных RX mailbox относятся к одной линии ECAN0INTA. */ receive(&ECanaMboxes.MBOX3, 8UL); receive(&ECanaMboxes.MBOX6, 64UL); /* Mailbox 3 принимает адресные кадры, 6 — broadcast discovery. */ PieCtrlRegs.PIEACK.all = 0x0100U; /* ACK9 открывает группу PIE9 для следующего CAN IRQ. */ } /* Возвращает управление прерванному main без обращения к прикладным каналам ПМ67. */ static void tx_poll(void) /* Подтверждает физическую передачу, прежде чем разрешить следующий ответ или reset. */ { /* Ошибка шины ограничена четвертью секунды и не приводит к бесконечному ожиданию. */ if (!tx_busy) return; /* Свободный mailbox не требует чтения старых ACK. */ if (ECanaRegs.CANTA.all & 16UL) { ECanaRegs.CANTA.all = 16UL; tx_busy = 0U; } /* Снимает только фактический ACK ответа mailbox 4. */ else if (Boot2812_Now() - sent_at >= 250UL) { /* Истёкшее ожидание не разрешает выполнить ранее заказанный reset. */ ECanaRegs.CANTRR.all = 16UL; reset_requested = 0U; /* Запрашивает отмену и сохраняет управление recovery-загрузчику. */ if (ECanaRegs.CANAA.all & 16UL) { ECanaRegs.CANAA.all = 16UL; tx_busy = 0U; } /* Не перезаписывает mailbox до подтверждённого завершения отмены. */ } /* Поздний ACK после запроса отмены также не восстанавливает сброшенный reset_requested. */ } /* Состояние передачи не зависит от относительного порядка BUSY и окончательного ответа. */ static bool transmit(void *user, uint32_t id, const pcan_boot_byte_t *data, pcan_boot_byte_t dlc) /* Реализует физический ответ без дублирования формата ProtoCAN. */ { /* Вызывается только из main или синхронного NOR callback, никогда из RX ISR. */ unsigned int i; uint32_t low = 0UL, high = 0UL, started = Boot2812_Now(), budget = 1000000UL; (void)user; /* Ограничение проходов сохраняет выход при остановленном Timer1. */ while (tx_busy && --budget && Boot2812_Now() - started < 250UL) tx_poll(); /* Даёт предыдущему BUSY физически уйти, прежде чем отправить окончательный OK. */ if (tx_busy || dlc > 8U) return false; /* Не теряет неподтверждённый ответ перезаписью регистров. */ for (i = 0; i < dlc; ++i) { if (i < 4U) low |= (uint32_t)(data[i] & 255U) << (8U * i); else high |= (uint32_t)(data[i] & 255U) << (8U * (i - 4U)); } /* Упаковывает октеты C28x в MDL/MDH при DBO=0. */ EALLOW; ECanaRegs.CANME.all &= ~16UL; /* Идентификатор TX изменяется только при отключённом mailbox. */ ECanaMboxes.MBOX4.MSGID.all = 0x80000000UL | id; ECanaMboxes.MBOX4.MSGCTRL.all = dlc; /* IDE=1 включает Extended формат, длина сохраняется из codec. */ ECanaMboxes.MBOX4.MDL.all = low; ECanaMboxes.MBOX4.MDH.all = high; /* Фиксирует все данные до подачи запроса передачи. */ ECanaRegs.CANTA.all = 16UL; ECanaRegs.CANAA.all = 16UL; ECanaRegs.CANME.all |= 16UL; ECanaRegs.CANTRS.all = 16UL; EDIS; /* Удаляет старые ACK и запускает один новый аппаратный ответ. */ sent_at = Boot2812_Now(); tx_busy = 1U; /* Следующий ответ и REBOOT ждут фактический CAN ACK. */ return true; /* Ядро считает кадр принятым аппаратным передатчиком. */ } /* Wire-содержимое сформировано только общим ProtoCAN codec. */ void Boot2812_Busy(void) /* Долгие erase и проверки продолжают сообщать состояние текущей команды. */ { /* Функция не извлекает новые команды из очереди и не допускает вложенный процессинг сессии. */ uint32_t now = Boot2812_Now(); tx_poll(); /* Подтверждает ранее отправленный BUSY по фактическому TX ACK. */ if (operation_active && !tx_busy && now - busy_at >= 250UL) { busy_at = now; (void)pcan_boot_report_busy(&boot, current_command, now); } /* Ответ каждые 250 мс удерживает клиент живым при секундном стирании NOR. */ } /* Финальный статус после callback отправит обычное ядро. */ static void request_reset(void *user) /* Команда REBOOT не сбрасывает аппарат до передачи её статусного ответа. */ { /* Аппаратный reset выполнит main после освобождения TX mailbox. */ (void)user; reset_requested = 1U; /* Не вызывает watchdog непосредственно из callback codec. */ } /* Потеря TX ACK отменяет этот запрос в tx_poll. */ static void can_init(void) /* Отдельные clock/GPIO F2812 не используют настройки F28335. */ { /* SYSCLKOUT=150 МГц при штатных XCLKIN=30 МГц и PLLCR=10 в включённом TI SDK. */ EALLOW; GpioMuxRegs.GPFMUX.bit.CANTXA_GPIOF6 = 1; GpioMuxRegs.GPFMUX.bit.CANRXA_GPIOF7 = 1; /* Назначение CAN берётся из оригинального DSP281x_ECan.c. */ ECanaRegs.CANTIOC.all = 8UL; ECanaRegs.CANRIOC.all = 8UL; ECanaRegs.CANME.all = 0UL; /* Включает CAN-функции и отключает mailbox до полной настройки. */ ECanaRegs.CANMC.all = 0x3080UL; /* SCB/CCR/ABO выбирают eCAN, режим настройки и автоматическое восстановление шины. */ while (!ECanaRegs.CANES.bit.CCE) {} /* Ожидает разрешения аппаратуры менять CANBTC. */ ECanaRegs.CANBTC.all = (29UL << 16) | 0x017AUL; /* F2812: 150 МГц/(BRP+1=30)/(1+16+3 tq)=250000 бит/с, SJW=2 tq. */ ECanaRegs.CANMC.all &= ~0x1000UL; while (ECanaRegs.CANES.bit.CCE) {} /* Выходит из режима настройки перед включением приёма. */ ECanaMboxes.MBOX3.MSGID.all = 0xC0000000UL | pcan_boot_make_id(0U, 0U, BOOT2812_DEVICE_TYPE, BOOT2812_DEVICE_ID, 0U, 0U); /* AME/IDE и адресный фильтр отделяют выбранный прибор от остальных узлов CAN. */ ECanaLAMRegs.LAM3.all = 0x100FFFFFUL; ECanaMboxes.MBOX3.MSGCTRL.all = 8UL; /* Игнорирует priority/type/body при аппаратном отборе; точные типы проверяет ISR/codec. */ ECanaMboxes.MBOX6.MSGID.all = 0xC0000000UL | pcan_boot_make_id(0U, 0U, 7U, 15U, 13U, 0U); /* Broadcast разрешён только для discovery, а не для стирания/программирования. */ ECanaLAMRegs.LAM6.all = 0x10000000UL; ECanaMboxes.MBOX6.MSGCTRL.all = 0UL; ECanaMboxes.MBOX4.MSGCTRL.all = 0UL; /* Фильтр discovery игнорирует только priority; зарезервированные биты MSGCTRL очищены. */ ECanaRegs.CANMD.all = 72UL; ECanaRegs.CANMIL.all = 0UL; ECanaRegs.CANMIM.all = 72UL; /* RX 3/6 и TX4 работают на линии ECAN0INTA; остальных mailbox нет. */ ECanaRegs.CANRMP.all = 0xFFFFFFFFUL; ECanaRegs.CANRML.all = 0xFFFFFFFFUL; ECanaRegs.CANTA.all = 0xFFFFFFFFUL; /* Удаляет старые флаги перед первым разрешением IRQ. */ ECanaRegs.CANGIF0.all = 0xFFFFFFFFUL; ECanaRegs.CANGIM.all = 1UL; ECanaRegs.CANME.all = 88UL; /* Включает только три настроенных mailbox и прерывание линии 0. */ PieVectTable.ECAN0INTA = &boot_can_rx; PieCtrlRegs.PIEIER9.bit.INTx5 = 1; EDIS; IER |= M_INT9; /* Подключает короткий постоянный RX ISR группы PIE9. */ } /* Конфигурация и состояние ПМ35/F28335 остаются независимыми. */ void main(void) /* Запускается аппаратным внутренним Flash boot F2812 через 3F7FF6. */ { /* Прежний внешний flat-образ не переписывается автоматически и не заменяет проверенную SCI8-таблицу. */ pcan_boot_config_t config; pcan_boot_port_t port; uint32_t started, entry; unsigned int tried = 0U; /* Небольшие настройки и адрес входа помещаются в постоянный стек M1. */ InitSysCtrl(); DINT; IER = 0U; IFR = 0U; /* Настраивает PLL=150 МГц и исключает старые IRQ до создания собственных векторов. */ memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, &RamfuncsLoadEnd - &RamfuncsLoadStart); InitFlash(); /* Штатная InitFlash выполняется из RAM, пока весь остальной код хранится во внутренней Flash A. */ InitPieCtrl(); InitCpuTimers(); Boot2812_BoardInit(); /* Подготавливает PIE/Timer1 и доступ к подтверждённой AM29LV800BT через CPLD. */ head = tail = overflow = tx_busy = reset_requested = operation_active = current_command = 0U; sent_at = busy_at = 0UL; /* Явно очищает COFF .ebss и очередь до включения CAN IRQ. */ memset(&config, 0, sizeof(config)); memset(&port, 0, sizeof(port)); /* Не оставляет неопределённые callbacks нового публичного API. */ config.device_type = BOOT2812_DEVICE_TYPE; config.device_id = BOOT2812_DEVICE_ID; config.product_type = BOOT2812_PRODUCT; config.hardware_revision = 1U; /* Отдельный адрес 3/6 и Product 2812 отличают основной контроллер от F28335. */ config.active_slot = 0U; config.slot_count = 1U; config.max_image_size = BOOT2812_IMAGE_BYTES; config.ack_window = 16U; config.session_timeout_ms = 30000UL; /* Single-slot отражает NOR-раздел без ложного обещания A/B-отката. */ port.send = transmit; port.erase_slot = Boot2812_Erase; port.write_slot = Boot2812_Write; port.verify_status = Boot2812_Verify; port.set_pending_slot = Boot2812_Commit; port.reboot = request_reset; /* Алгоритм ProtoCAN вызывает только аппаратные операции внешней NOR. */ if (!pcan_boot_init(&boot, &config, &port, 0)) for (;;) {} /* Ошибка конфигурации не разрешает произвольную запись памяти. */ ConfigCpuTimer(&CpuTimer1, 150.0F, 1000.0F); EALLOW; PieVectTable.XINT13 = &boot_tick; EDIS; /* PLL выбранного TI SDK даёт 150 МГц; 1000 мкс соответствуют миллисекундным тайм-аутам протокола. */ CpuTimer1Regs.TCR.all = 0x4020U; IER |= M_INT13; can_init(); PieCtrlRegs.PIECRTL.bit.ENPIE = 1; EINT; started = Boot2812_Now(); /* Начинает трёхсекундное окно recovery только после готовности физического CAN. */ for (;;) { /* Постоянный main обслуживает запись и автоматически запускает только подтверждённый manifest. */ tx_poll(); pcan_boot_poll(&boot, Boot2812_Now()); KickDog(); /* Тайм-ауты сессии, discovery и watchdog не зависят от очередного окна данных. */ if (!tx_busy && reset_requested) { DINT; EALLOW; SysCtrlRegs.WDCR = 0x0028U; EDIS; for (;;) {} } /* После физического ACK REBOOT запускает аппаратный watchdog reset без старых ISR. */ if (overflow || (ECanaRegs.CANRML.all & 72UL)) { overflow = 0U; ECanaRegs.CANRML.all = 72UL; tail = head; pcan_boot_abort(&boot); } /* Потеря mailbox/очереди отменяет сессию и требует передачи заново. */ if (!tx_busy && tail != head) { /* Обрабатывает один целиком сформированный ISR элемент за проход. */ current_command = pcan_boot_id_msg_type(queue[tail].id) == 9U ? pcan_boot_id_msg_body(queue[tail].id) & 255U : 0U; operation_active = 1U; /* BUSY содержит именно команду, которая выполняется сейчас. */ (void)pcan_boot_process(&boot, queue[tail].id, queue[tail].data, queue[tail].dlc); operation_active = 0U; tail = (tail + 1U) & 31U; /* Производственный codec проверяет session/порядок/CRC и вызывает NOR callbacks только в main. */ } /* Пока TX занят, очередь сохраняет все следующие кадры окна. */ if (!tried && !tx_busy && boot.state == PCAN_BOOT_STATE_IDLE && Boot2812_Now() - started >= BOOT2812_RECOVERY_MS) { /* ENTER_BOOT удерживает загрузчик; без сессии допускается автоматический запуск сохранённого CAN-образа. */ tried = 1U; /* Пустой или испорченный manifest не должен проверяться заново на каждом такте main. */ if (Boot2812_NorIdentify() && Boot2812_Load(&entry)) { DINT; IER = 0U; IFR = 0U; CpuTimer1Regs.TCR.bit.TSS = 1; ECanaRegs.CANMIM.all = 0UL; ((void (*)(void))entry)(); for (;;) {} } /* Запускает только полностью проверенную RAM-таблицу; приложение само создаст свои ISR и C runtime. */ } /* При отказе остаётся доступен CAN recovery без EEPROM. */ } /* Ни один путь не исполняет код из стираемой внешней NOR. */ } /* Завершение загрузчика выполняется только аппаратным reset либо проверенным переходом в приложение. */