Обеспечь единый процесс обновления для STM32F103/F407/G474 и TMS320F2812/F28335: discovery, служебные ответы, BUSY, тайм-аут сессии и подтверждение приложения. Добавь самостоятельные проекты CCS, аппаратные хранилища и Python-клиент. Совместимость: структуры конфигурации и порта расширены, поэтому потребители должны пересобраться и обнулить новые поля. Для C28x callbacks используют pcan_boot_byte_t; периодический pcan_boot_poll обслуживает discovery и настроенный тайм-аут.
115 lines
20 KiB
C
115 lines
20 KiB
C
#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 либо проверенным переходом в приложение. */
|