Files
templates/c/protocan-boot/projects/ccs/f2812/main.c
T
Andrey c6390ae1c9 feat(protocan-boot): Добавь порты MCU и сервисы загрузки
Обеспечь единый процесс обновления для STM32F103/F407/G474 и TMS320F2812/F28335: discovery, служебные ответы, BUSY, тайм-аут сессии и подтверждение приложения. Добавь самостоятельные проекты CCS, аппаратные хранилища и Python-клиент.

Совместимость: структуры конфигурации и порта расширены, поэтому потребители должны пересобраться и обнулить новые поля. Для C28x callbacks используют pcan_boot_byte_t; периодический pcan_boot_poll обслуживает discovery и настроенный тайм-аут.
2026-10-23 14:16:19 +03:00

115 lines
20 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 <string.h> /* Инициализирует все состояния независимо от прежнего содержимого .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 либо проверенным переходом в приложение. */