Добавить загрузку F28335 по ProtoCAN и сервисные команды клиента
This commit is contained in:
156
c/protocan-boot/ports/tms320f28335/README.md
Normal file
156
c/protocan-boot/ports/tms320f28335/README.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# F28335 + M95M02: CAN, отдельный загрузчик и A/B
|
||||
|
||||
Реализация состоит из Flash-загрузчика `periph_167/Bootloader`, переносимого
|
||||
`f28335_boot_store.c`, аппаратного `f28335_boot_hw.c` и адаптера
|
||||
`f28335_can_boot.c`. Приложение подключает их через `Source/Internal/can_boot.c`.
|
||||
В сборке `EEPROM_M95M02=0` CAN Boot отключён. Для установленной M95M02 — значение 1.
|
||||
|
||||
## Адрес и формат
|
||||
|
||||
Device Type **3**, Device ID **7**, имя **F28335-M95M02**, Product **0x2833**, HW **1**.
|
||||
В приложении FW берётся из `firmware_version.h`, формат `major<<24 | minor<<16 | patch`;
|
||||
для версии 1.1.2 это `0x01010002`. Загрузчик сообщает версию подтверждённого образа
|
||||
из журнала, при отсутствии такого образа — 0. Версия кандидата передаётся в BEGIN_COMPAT.
|
||||
CAN **250000 бит/с**, Extended ID, окно **16 блоков по 8 октетов**.
|
||||
ENTER_BOOT для session=1: `0x13790102`, STATUS: `0x1B7C0102`.
|
||||
|
||||
Образ — SCI8 BIN (`AA 08`), чётная длина от 24 до **130560 байт** включительно.
|
||||
Числа в протоколе little endian. Адреса внутри SCI8: сначала старшее 16-битное
|
||||
слово, затем младшее; каждое слово little endian. В C28x `char` 16-битный,
|
||||
поэтому `pcan_boot_byte_t` хранит октет в младших 8 битах.
|
||||
|
||||
## Разметка EEPROM, байтовые адреса
|
||||
|
||||
| Область | Начало | Конец включительно | Размер |
|
||||
|---|---:|---:|---:|
|
||||
| Журнал 0 | 0x00000 | 0x000FF | 256 |
|
||||
| Журнал 1 | 0x00100 | 0x001FF | 256 |
|
||||
| Slot A | 0x00200 | 0x1FFFF | 130560 |
|
||||
| Slot B | 0x20000 | 0x3FDFF | 130560 |
|
||||
| Резерв | 0x3FE00 | 0x3FEFB | 252 |
|
||||
| Параметры | 0x3FEFC | 0x3FFFD | 258 |
|
||||
| Последнее слово | 0x3FFFE | 0x3FFFF | 2 |
|
||||
|
||||
В каждой странице журнала первые 64 октета содержат magic `FAB2`, версию 1,
|
||||
sequence, active/pending/attempted, два manifest, CRC32 первых 56 октетов и
|
||||
commit marker в октетах 60..63. Сначала обнуляется marker запасной страницы,
|
||||
затем записывается тело с проверкой чтением, последним — marker. Предыдущая
|
||||
страница остаётся действующей до полной публикации новой. Выбор sequence
|
||||
учитывает переполнение u32. После ошибки публикации журнал перечитывается;
|
||||
ошибка чтения блокирует последующие записи.
|
||||
|
||||
## Сценарий обновления и отката
|
||||
|
||||
1. Приложение принимает ENTER_BOOT, останавливает управление нагрузкой, выставляет 4 мА и выдерживает
|
||||
секундный STOP, затем сохраняет session в NOINIT RAM 0x50..0x57. Watchdog выполняет reset.
|
||||
ACK ENTER_BOOT отправляет уже Flash-загрузчик; приложение ничего не стирает.
|
||||
2. BEGIN_IMAGE/BEGIN_COMPAT проверяют размер, Product, HW и сессию. Неактивный
|
||||
слот выбирается автоматически; при первом образе это A. ERASE инвалидирует
|
||||
только его manifest. Полного стирания EEPROM не требуется.
|
||||
3. Последовательные DATA_A/B записываются с немедленным readback. VERIFY
|
||||
проверяет потоковый CRC32, CRC32 EEPROM и полную таблицу SCI8.
|
||||
Разрешена загрузка только в CPU word RAM `[0x8000,0xE000)`: максимум 64 секции,
|
||||
без пересечений, усечений и хвостовых данных; entry должен попасть в секцию.
|
||||
Запись в Flash, периферию, стек, RAM загрузчика и handoff запрещена.
|
||||
4. COMMIT повторно проверяет образ и публикует pending в журнале. Подтверждённый
|
||||
слот сохраняется. Повтор COMMIT безопасен. REBOOT выполняется после физического
|
||||
CAN ACK ответа. Потеря ACK/тайм-аут передачи отменяет запрос reset.
|
||||
5. Перед первым запуском кандидата загрузчик сохраняет attempted=1, затем
|
||||
проверяет и копирует SCI8 в RAM. Следующий reset без CONFIRM сбрасывает pending
|
||||
и запускает прежний active. Если прежнего образа нет — остаётся CAN recovery.
|
||||
6. Новое приложение включает watchdog в начале запуска. До подтверждения
|
||||
прерывания не кормят watchdog вместо основного цикла. После READY, 100 проходов
|
||||
main и 5 секунд выполняется автоматический CONFIRM. Неудача сохранения
|
||||
подтверждения через 10 секунд прекращает кормление watchdog, вызывая откат.
|
||||
Адресная команда CONFIRM с ненулевой session также разрешена после READY.
|
||||
CONFIRM в загрузчике и до первого запуска кандидата отклоняется.
|
||||
|
||||
PING, IDENTIFY и паспорт доступны без ENTER_BOOT. QUERY_PROGRESS/ABORT привязаны
|
||||
к сессии. Сессия в RAM истекает через 30 секунд без подходящих команд/данных.
|
||||
После reset передачу начинают заново; параметры и подтверждённый образ остаются.
|
||||
ABORT после COMMIT не отменяет опубликованный pending. Для его пробного запуска
|
||||
можно открыть новую сессию и отправить REBOOT. Pending запрещено перезаписывать.
|
||||
Повторы ENTER_BOOT, метаданных, ERASE до первого блока, VERIFY и COMMIT учитывают
|
||||
потерю ответа. Broadcast никогда не разрешает запись.
|
||||
|
||||
## Сервисные команды
|
||||
|
||||
Запрос: MsgType=9, Body=`session<<8 | command`. IDENTIFY/PING/паспорт не требуют сессии.
|
||||
Ответ паспорта: MsgType=D, Body=`page<<8 | command`, DLC=8.
|
||||
|
||||
| Код | Имя | DLC запроса | Ответ |
|
||||
|---:|---|---:|---|
|
||||
| 01 | IDENTIFY | 0 | Product:u16, HW:u8, protocol:u8, FW:u32; Body=1 |
|
||||
| 0C | PING | 0 | protocol, state, active, target, session, slot_count, last_status, 0 |
|
||||
| 0D | CAPABILITIES | 0 | flags:u32, slot_capacity:u32 |
|
||||
| 0E | DEVICE_NAME | 1 (page) | 8 ASCII-октетов имени; последний фрагмент с NUL |
|
||||
| 0F | MEMORY_INFO | 1 (page) | Страницы ниже |
|
||||
|
||||
MEMORY_INFO 0: EEPROM bytes:u32, page bytes:u16, mode:u8 (0 app/1 boot), running:u8.
|
||||
Страница 1: адреса A:u32, B:u32. Страница 2: active, pending, attempted, storage_healthy,
|
||||
session_timeout_ms:u32. Страница 3: parameters_offset:u32, parameters_bytes:u32.
|
||||
Слот FF означает отсутствие образа/запущенного приложения.
|
||||
CAPABILITIES bits 0..11: ping, discovery, identify, name, memory, A/B, confirm,
|
||||
rollback, separate loader, session timeout, readback, SCI8 validation.
|
||||
Приложение, запущенное старым SPI ROM без handoff загрузчика, сообщает только
|
||||
базовые сервисы (0x1F) и отклоняет ENTER_BOOT.
|
||||
|
||||
Обнаружение: MsgType=D, Body=0, DLC=0, Route=0. Адресный запрос или общий
|
||||
DeviceType=7/DeviceID=15. Ответ IDENTIFY приходит с адреса прибора через
|
||||
`2*(1+16*DeviceType+DeviceID)` мс. При нескольких одинаковых адресах конфликт
|
||||
остаётся: каждому физическому устройству нужен уникальный адрес.
|
||||
Python API: `protocan.can_boot.CanBootServices` — запросы и разбор паспорта;
|
||||
`CanBootTransfer` — загрузка. Кнопка «Пинг» доступна в режиме ProtoCAN Boot
|
||||
окна прошивки SETGUI. Для ENTER/VERIFY/COMMIT клиент ждёт до 15 секунд.
|
||||
|
||||
## Аппаратная интеграция
|
||||
|
||||
MBOX3 — адресные входящие Boot; MBOX4 — ответы; MBOX6 — точный broadcast discovery.
|
||||
MBOX0..2 сохраняют legacy/UMP, MBOX5 остаётся heartbeat. ISR помещает сообщения
|
||||
в очередь на 31 кадр. Чужие адреса, RTR и неподходящие типы отбрасываются до очереди.
|
||||
При переполнении/потере mailbox сессия отменяется; повторная запись начинается заново.
|
||||
SPI-драйвер загрузчика имеет ограничение ожидания байта и WIP, 24-битный адрес,
|
||||
разбиение по страницам 256 байт и восстановление конфигурации SPI приложения.
|
||||
Он работает только при остановленном Timer2. Обычный Seeprom_write приложения
|
||||
ограничен областью параметров; старый RS EXTEND 0x44 запрещён для A/B.
|
||||
|
||||
Загрузчик исполняется из внутренней Flash A 0x338000..0x33FF7F, entry 0x33FFF6.
|
||||
Его RAM — 0xE000..0xFFFF; стек — 0x400..0x7FF. Приложение M95M02 использует
|
||||
0x8000..0xD7FF для кода, 0xD800..0xDFFF для констант/инициализации,
|
||||
0xE000..0xFFFF для .ebss после перехода в C runtime. Linker резервирует
|
||||
NOINIT handoff в обоих проектах. CAN не содержит операций записи внутренней Flash.
|
||||
|
||||
## Первичная установка
|
||||
|
||||
1. Собрать приложение с EEPROM_M95M02=1 и обновлённым F28335.cmd; получить SCI8 BIN.
|
||||
Старые BIN/OUT автоматически не заменены. Приложение должно содержать интеграцию
|
||||
READY/CONFIRM, иначе на следующем reset оно останется неподтверждённым кандидатом.
|
||||
2. Собрать отдельный загрузчик TI C2000 COFF compiler:
|
||||
`Bootloader/build.ps1 -CompilerRoot <путь-к-ti-cgt-c2000>`.
|
||||
Результат — `Bootloader/build/F28335_CAN_AB.out` и MAP.
|
||||
3. Один раз установить этот OUT через JTAG и выбрать аппаратный Flash boot
|
||||
согласно схеме платы. Не загружать поверх него старый application OUT с
|
||||
секцией codestart во Flash.
|
||||
4. После reset пустая EEPROM или старая разметка автоматически ведут в CAN recovery.
|
||||
Передать новое приложение через ProtoCAN Boot, дождаться пробного запуска и
|
||||
проверить MEMORY_INFO page 2: pending=FF, active=A. Затем проверить обновление в B.
|
||||
|
||||
Старая SPI-ROM разметка с AA08 по адресу 0 несовместима с новым журналом.
|
||||
Первое обновление заменяет её; параметры по прежнему адресу сохраняются.
|
||||
Загрузчик не выполняет скрытую миграцию и не меняет boot pins программно.
|
||||
|
||||
## Проверки и границы проверенного
|
||||
|
||||
`python c/protocan-boot/tests/run_tests.py` (переменная CC — clang/gcc) проверяет
|
||||
ядро, приложение/загрузчик, wide-octet режим, журнал, обрыв публикации на каждом
|
||||
из 69 возможных положений, пробный запуск, CONFIRM, откат, SCI8 и границы памяти.
|
||||
Python-тесты проверяют клиентские запросы, паспорт, фильтрацию ответов и лимит A/B.
|
||||
Проверка синтаксиса использует настоящие DSP2833x headers. Сборка TI/CCS,
|
||||
раскладка итогового MAP и физические CAN/SPI/reset на плате ещё не подтверждены.
|
||||
|
||||
CRC32 защищает от повреждения, криптографической подписи и anti-rollback по
|
||||
версии в этом порту нет. Удалённый CONFIRM подтверждает готовность приложения,
|
||||
но не заменяет проверку датчиков/силовой части на стенде.
|
||||
|
||||
Аппаратный формат SPI boot описан в [TI Boot ROM Reference Guide](https://e2e.ti.com/cfs-file/__key/communityserver-discussions-components-files/171/2742.Boot-ROM.pdf).
|
||||
В A/B-схеме ROM выбирает Flash, а разбор EEPROM выполняет этот загрузчик.
|
||||
181
c/protocan-boot/ports/tms320f28335/f28335_boot_hw.c
Normal file
181
c/protocan-boot/ports/tms320f28335/f28335_boot_hw.c
Normal file
@@ -0,0 +1,181 @@
|
||||
/* Shared by the application and the separate Flash bootloader. */
|
||||
/* Аппаратный слой: общая RAM между reset, watchdog и SPI EEPROM.
|
||||
* Один исходник компилируется и в приложение, и в отдельный Flash-загрузчик.
|
||||
* В отличие от store.c этот файл требует настоящих регистров F28335. */
|
||||
#include "DSP2833x_Device.h"
|
||||
#include "f28335_boot_platform.h"
|
||||
#include "f28335_can_boot.h"
|
||||
|
||||
/* Must be NOINIT in BOTH linkers, outside ROM scratch and application payload. */
|
||||
#pragma DATA_SECTION(boot_handoff,".boot_shared")
|
||||
/* 8 слов NOINIT по адресу 0x50, одинаково зарезервированных обоими linker-файлами.
|
||||
* [0..1] magic; [2] запрошенная session; [3] запущенный слот; [4..6] служебные
|
||||
* поля; [7] XOR-контроль. Это кратковременная передача контекста через watchdog
|
||||
* reset, а не замена энергонезависимого журнала EEPROM. */
|
||||
static volatile unsigned int boot_handoff[8];
|
||||
/* Проверяем magic и XOR прежде, чем трактовать RAM как запрос входа в boot.
|
||||
* После включения питания содержимое NOINIT может быть произвольным. */
|
||||
static unsigned int handoff_valid(void)
|
||||
{
|
||||
unsigned int i,sum=0;
|
||||
for(i=0;i<8;++i) sum^=boot_handoff[i];
|
||||
return boot_handoff[0]==0xB283 && boot_handoff[1]==0x4D7C && sum==0xA95B;
|
||||
}
|
||||
/* Сначала снимаем признак действительности, затем заполняем поля и контроль.
|
||||
* Первый magic записываем последним; volatile сохраняет обращения к общей RAM.
|
||||
* request=0 означает обычный запуск, ненулевая session — явный ENTER_BOOT. */
|
||||
static void handoff_set(unsigned int request,unsigned int running)
|
||||
{
|
||||
unsigned int i,sum=0;
|
||||
boot_handoff[0]=0; boot_handoff[1]=0x4D7C;
|
||||
boot_handoff[2]=request; boot_handoff[3]=running;
|
||||
boot_handoff[4]=0xF335; boot_handoff[5]=0x0CCA; boot_handoff[6]=1;
|
||||
sum=0xB283;
|
||||
for(i=1;i<7;++i) sum^=boot_handoff[i];
|
||||
boot_handoff[7]=sum^0xA95B;
|
||||
boot_handoff[0]=0xB283; /* publish last */
|
||||
}
|
||||
/* Приложение узнаёт свой слот из handoff, а не предполагает, что оно active:
|
||||
* во время пробного запуска выполняется pending, active ещё указывает на старый образ. */
|
||||
unsigned int F28335Boot_RunningSlot(void)
|
||||
{ return handoff_valid()?boot_handoff[3]:F28335_AB_NONE; }
|
||||
unsigned int F28335Boot_RequestedSession(void)
|
||||
{ return handoff_valid() && boot_handoff[2]<256?boot_handoff[2]:0; }
|
||||
/* Одновременно публикуем выбранный слот и потребляем запрос ENTER_BOOT:
|
||||
* он не должен повторяться при следующем обычном reset. */
|
||||
void F28335Boot_SetRunning(unsigned int slot) { handoff_set(0,slot); }
|
||||
/* Timer1 должен давать 1 мс на InterruptCount и в приложении, и в загрузчике.
|
||||
* Тайм-ауты используют разность unsigned-счётчиков для переноса через wrap. */
|
||||
uint32_t F28335Boot_Now(void) { return CpuTimer1.InterruptCount; }
|
||||
/* Вызвать сразу после InitSysCtrl, который штатно отключает watchdog.
|
||||
* Сбрасываем разрешение кормления из ISR до полной инициализации CAN-сервиса.
|
||||
* Для приложения, запущенного нашим загрузчиком, включаем watchdog с делителем. */
|
||||
void F28335Boot_StartupWatchdog(void)
|
||||
{
|
||||
F28335CanBoot_EarlyInit();
|
||||
if(F28335Boot_RunningSlot()<2) {
|
||||
EALLOW; SysCtrlRegs.WDKEY=0x55; SysCtrlRegs.WDKEY=0xAA;
|
||||
SysCtrlRegs.WDCR=0x002F; EDIS;
|
||||
}
|
||||
}
|
||||
/* Платформенный Quiesce останавливает управление выходами перед reset.
|
||||
* Session сохраняется до включения watchdog; ненулевую session загрузчик
|
||||
* использует для отложенного ACK ENTER_BOOT. В бесконечном цикле watchdog
|
||||
* намеренно не обслуживается — выход из функции должен произойти через reset. */
|
||||
void F28335Boot_Reset(unsigned int session)
|
||||
{
|
||||
F28335Boot_Quiesce();
|
||||
DINT;
|
||||
handoff_set(session,F28335_AB_NONE);
|
||||
EALLOW; SysCtrlRegs.WDCR=0x0028; EDIS;
|
||||
for(;;) { }
|
||||
}
|
||||
|
||||
/* Main context only. Legacy Timer2 EEPROM driver must be idle/stopped. Save
|
||||
* its SPI configuration: it changes the character width during transactions. */
|
||||
typedef struct { unsigned int ccr,ctl,brr,tx,rx,ct,pri; } boot_spi_saved;
|
||||
static void spi_cs(unsigned int high)
|
||||
{ if(high) GpioDataRegs.GPASET.bit.GPIO19=1; else GpioDataRegs.GPACLEAR.bit.GPIO19=1; }
|
||||
/* Доступ разрешён только из main при остановленном Timer2 старого SPI-драйвера.
|
||||
* Это проверка занятости в однопоточном приложении, не универсальный mutex.
|
||||
* Сохраняем режим SPI, поскольку штатный драйвер меняет разрядность передачи.
|
||||
* CS сначала поднимаем, затем переключаем SPI на 8-битные транзакции. */
|
||||
static bool spi_take(boot_spi_saved *s)
|
||||
{
|
||||
volatile unsigned int discard;
|
||||
if(!CpuTimer2Regs.TCR.bit.TSS) return false;
|
||||
s->ccr=SpiaRegs.SPICCR.all; s->ctl=SpiaRegs.SPICTL.all; s->brr=SpiaRegs.SPIBRR;
|
||||
s->tx=SpiaRegs.SPIFFTX.all; s->rx=SpiaRegs.SPIFFRX.all;
|
||||
s->ct=SpiaRegs.SPIFFCT.all; s->pri=SpiaRegs.SPIPRI.all;
|
||||
EALLOW;
|
||||
GpioCtrlRegs.GPAMUX2.bit.GPIO16=1; GpioCtrlRegs.GPAMUX2.bit.GPIO17=1;
|
||||
GpioCtrlRegs.GPAMUX2.bit.GPIO18=1; GpioCtrlRegs.GPAMUX2.bit.GPIO19=0;
|
||||
GpioDataRegs.GPASET.bit.GPIO19=1; GpioCtrlRegs.GPADIR.bit.GPIO19=1;
|
||||
GpioCtrlRegs.GPAQSEL2.bit.GPIO17=3;
|
||||
EDIS;
|
||||
SpiaRegs.SPICCR.all=7; SpiaRegs.SPICTL.all=0x0E; /* master, phase=1, IRQ off */
|
||||
SpiaRegs.SPIBRR=CLKMULT*6+1;
|
||||
SpiaRegs.SPIFFTX.all=0x8000; SpiaRegs.SPIFFRX.all=0; SpiaRegs.SPIFFCT.all=0;
|
||||
SpiaRegs.SPIPRI.all=0x10; SpiaRegs.SPICCR.all=0x87;
|
||||
/* Снимаем оставшийся RX/INT_FLAG от прежней операции, чтобы первый обмен
|
||||
* не принял старое содержимое RXBUF за ответ EEPROM. */
|
||||
discard=SpiaRegs.SPIRXBUF; (void)discard;
|
||||
return true;
|
||||
}
|
||||
/* Выход и при успехе, и при ошибке: CS не должен остаться активным.
|
||||
* Настройки возвращаются при удержании SPI в reset; исходный SPISWRESET
|
||||
* восстанавливается последним, после остальных регистров. */
|
||||
static void spi_release(const boot_spi_saved *s)
|
||||
{
|
||||
volatile unsigned int discard;
|
||||
spi_cs(1); discard=SpiaRegs.SPIRXBUF; (void)discard;
|
||||
SpiaRegs.SPICCR.all=s->ccr&0x7FU; SpiaRegs.SPICTL.all=s->ctl;
|
||||
SpiaRegs.SPIBRR=s->brr; SpiaRegs.SPIFFTX.all=s->tx; SpiaRegs.SPIFFRX.all=s->rx;
|
||||
SpiaRegs.SPIFFCT.all=s->ct; SpiaRegs.SPIPRI.all=s->pri; SpiaRegs.SPICCR.all=s->ccr;
|
||||
}
|
||||
/* У F28335 8-битный символ для передачи выравнивается в старшие биты TXBUF.
|
||||
* Полученный октет читается из младших битов RXBUF. Счётчик budget ограничивает
|
||||
* ожидание даже при неработающем Timer1; это число итераций, а не миллисекунды. */
|
||||
static bool spi_octet(unsigned int tx,unsigned int *rx)
|
||||
{
|
||||
uint32_t budget=100000UL;
|
||||
SpiaRegs.SPITXBUF=(tx&255U)<<8;
|
||||
while(!SpiaRegs.SPISTS.bit.INT_FLAG) if(--budget==0) return false;
|
||||
*rx=SpiaRegs.SPIRXBUF&255U; return true;
|
||||
}
|
||||
/* Команда EEPROM и 24-битный байтовый адрес, старший октет адреса первым.
|
||||
* CS удерживается вызывающей функцией на всю команду и полезные данные. */
|
||||
static bool spi_prefix(unsigned int command,uint32_t address)
|
||||
{
|
||||
unsigned int discard;
|
||||
return spi_octet(command,&discard) && spi_octet((unsigned int)(address>>16),&discard) &&
|
||||
spi_octet((unsigned int)(address>>8),&discard) && spi_octet((unsigned int)address,&discard);
|
||||
}
|
||||
/* RDSR (05h): WIP в бите 0 означает незавершённую внутреннюю запись EEPROM.
|
||||
* Ожидание ограничено и 50 мс, и числом опросов на случай остановки tick.
|
||||
* После каждого чтения статуса CS снимается, в том числе при ошибке SPI. */
|
||||
static bool spi_ready(void)
|
||||
{
|
||||
unsigned int status,discard; uint32_t started=F28335Boot_Now(),budget=20000UL;
|
||||
do {
|
||||
spi_cs(0);
|
||||
if(!spi_octet(5,&discard) || !spi_octet(255,&status)) { spi_cs(1); return false; }
|
||||
spi_cs(1);
|
||||
if(!(status&1U)) return true;
|
||||
} while(--budget && (uint32_t)(F28335Boot_Now()-started)<50UL);
|
||||
return false;
|
||||
}
|
||||
/* Читать разрешено всю физическую EEPROM, включая параметры. Проверка
|
||||
* через вычитание защищает от переполнения адреса. spi_release обязателен
|
||||
* после успешного захвата SPI независимо от результата последующего обмена. */
|
||||
bool F28335Boot_Read(void *u,uint32_t address,pcan_boot_byte_t *p,unsigned int n)
|
||||
{
|
||||
boot_spi_saved saved; unsigned int i,value; bool ok; (void)u;
|
||||
if(address>0x40000UL || n>0x40000UL-address || !spi_take(&saved)) return false;
|
||||
ok=spi_ready();
|
||||
spi_cs(0); if(ok) ok=spi_prefix(3,address);
|
||||
for(i=0;ok && i<n;++i) { ok=spi_octet(255,&value); p[i]=value; }
|
||||
spi_release(&saved); return ok;
|
||||
}
|
||||
/* Нижний аппаратный барьер записи: разрешены журнал и слоты до 0x3FE00.
|
||||
* Резерв, параметры и последнее слово нельзя изменить даже ошибочным вызовом
|
||||
* этой функции. Защита активного слота дополнительно выполняется в store.c. */
|
||||
bool F28335Boot_Write(void *u,uint32_t address,const pcan_boot_byte_t *p,unsigned int n)
|
||||
{
|
||||
boot_spi_saved saved; unsigned int i,chunk,discard; bool ok; (void)u;
|
||||
/* This driver cannot overwrite parameters, even if a caller is defective. */
|
||||
if(address>0x3FE00UL || n>0x3FE00UL-address || !spi_take(&saved)) return false;
|
||||
ok=spi_ready();
|
||||
while(ok && n) {
|
||||
/* Нельзя пересекать страницу одной командой WRITE: EEPROM может завернуть
|
||||
* адрес внутри страницы. Для каждого фрагмента заново выдаём WREN (06h),
|
||||
* WRITE (02h) и ждём снятия WIP. Сравнение readback выполняет верхний слой. */
|
||||
chunk=256U-(unsigned int)(address&255U); if(chunk>n) chunk=n;
|
||||
spi_cs(0); ok=spi_octet(6,&discard); spi_cs(1); /* WREN */
|
||||
spi_cs(0); if(ok) ok=spi_prefix(2,address);
|
||||
for(i=0;ok && i<chunk;++i) ok=spi_octet(p[i],&discard);
|
||||
spi_cs(1); if(ok) ok=spi_ready();
|
||||
n-=chunk; p+=chunk; address+=chunk;
|
||||
}
|
||||
spi_release(&saved); return ok;
|
||||
}
|
||||
17
c/protocan-boot/ports/tms320f28335/f28335_boot_platform.h
Normal file
17
c/protocan-boot/ports/tms320f28335/f28335_boot_platform.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef F28335_BOOT_PLATFORM_H
|
||||
#define F28335_BOOT_PLATFORM_H
|
||||
#include "f28335_boot_store.h"
|
||||
#ifndef F28335_BOOT_LOADER
|
||||
#define F28335_BOOT_LOADER 0
|
||||
#endif
|
||||
bool F28335Boot_Read(void *u,uint32_t address,pcan_boot_byte_t *p,unsigned int n);
|
||||
bool F28335Boot_Write(void *u,uint32_t address,const pcan_boot_byte_t *p,unsigned int n);
|
||||
uint32_t F28335Boot_Now(void);
|
||||
void F28335Boot_Reset(unsigned int session);
|
||||
unsigned int F28335Boot_RunningSlot(void);
|
||||
unsigned int F28335Boot_RequestedSession(void);
|
||||
void F28335Boot_SetRunning(unsigned int slot);
|
||||
void F28335Boot_StartupWatchdog(void);
|
||||
/* Application hook must stop outputs before reset. No EEPROM operations here. */
|
||||
void F28335Boot_Quiesce(void);
|
||||
#endif
|
||||
254
c/protocan-boot/ports/tms320f28335/f28335_boot_store.c
Normal file
254
c/protocan-boot/ports/tms320f28335/f28335_boot_store.c
Normal file
@@ -0,0 +1,254 @@
|
||||
/* Хранилище A/B без зависимости от регистров F28335.
|
||||
* Здесь находятся правила сохранения образа при сбое питания и выбора слота.
|
||||
* CAN отвечает за порядок команд; этот слой повторно защищает active/pending
|
||||
* и границы EEPROM, даже если вызывающий код передал неверные параметры.
|
||||
* Адрес EEPROM измеряется в октетах, адрес назначения SCI8 — в словах CPU. */
|
||||
#include "f28335_boot_store.h"
|
||||
#include <string.h>
|
||||
|
||||
/* Формат журнала фиксирован: u32 little endian. Нельзя сохранять C-структуру
|
||||
* через memcpy: выравнивание и sizeof на C28x отличаются от обычного ПК. */
|
||||
static uint32_t bs_get32(const pcan_boot_byte_t *p)
|
||||
{ return (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24); }
|
||||
static void bs_put32(pcan_boot_byte_t *p, uint32_t v)
|
||||
{ unsigned int i; for(i=0;i<4;++i) p[i]=(pcan_boot_byte_t)((v>>(8*i))&255U); }
|
||||
/* Потоковый CRC32: первый вызов получает crc=0, следующие — предыдущий результат.
|
||||
* Внутренняя инверсия позволяет считать один CRC по нескольким порциям EEPROM.
|
||||
* Маска 255 исключает старшие биты 16-битного представления октета на C28x.
|
||||
* Это проверка повреждения данных, а не криптографическая подпись. */
|
||||
uint32_t f28335_boot_crc(uint32_t crc, const pcan_boot_byte_t *p, unsigned int n)
|
||||
{ unsigned int i,b; crc=~crc; for(i=0;i<n;++i) { crc^=p[i]&255U;
|
||||
for(b=0;b<8;++b) crc=(crc>>1)^((crc&1U)?0xEDB88320UL:0); } return ~crc; }
|
||||
static uint32_t bs_base(unsigned int slot) { return slot?F28335_AB_SLOT_B:F28335_AB_SLOT_A; }
|
||||
/* Принимаем запись только при совпадении magic, версии, marker и CRC.
|
||||
* Дополнительно проверяем смысл номеров слотов: pending не может быть active.
|
||||
* Наличие правильного marker без правильного CRC не разрешает запуск. */
|
||||
static bool bs_record_valid(const pcan_boot_byte_t *p)
|
||||
{
|
||||
return bs_get32(p)==0x32424146UL && bs_get32(p+4)==1 &&
|
||||
bs_get32(p+60)==0x51AFC03DUL && bs_get32(p+56)==f28335_boot_crc(0,p,56) &&
|
||||
(p[12]<2 || p[12]==F28335_AB_NONE) &&
|
||||
(p[13]<2 || p[13]==F28335_AB_NONE) && p[14]<=1 &&
|
||||
(p[13]==F28335_AB_NONE || p[13]!=p[12]);
|
||||
}
|
||||
/* Карта записи, смещения в октетах:
|
||||
* 0..3 magic FAB2; 4..7 версия; 8..11 sequence; 12 active; 13 pending;
|
||||
* 14 attempted; 15 резерв; 16..35 manifest A; 36..55 manifest B;
|
||||
* 56..59 CRC первых 56 октетов; 60..63 marker завершённой публикации.
|
||||
* Manifest: size, CRC, FW (по u32), Product (u16), HW min/max, 4 октета резерва. */
|
||||
static void bs_decode(f28335_boot_store_t *s, const pcan_boot_byte_t *p)
|
||||
{
|
||||
unsigned int i; s->sequence=bs_get32(p+8); s->active=p[12];
|
||||
s->pending=p[13]; s->attempted=p[14];
|
||||
for(i=0;i<2;++i) {
|
||||
const pcan_boot_byte_t *q=p+16+i*20;
|
||||
s->image[i].image_size=bs_get32(q); s->image[i].image_crc32=bs_get32(q+4);
|
||||
s->image[i].firmware_version=bs_get32(q+8);
|
||||
s->image[i].product_type=(uint16_t)(q[12]|(q[13]<<8));
|
||||
s->image[i].hardware_revision_min=q[14]; s->image[i].hardware_revision_max=q[15];
|
||||
}
|
||||
}
|
||||
/* Читаем обе копии журнала. Нераспознанная разметка допускает CAN recovery
|
||||
* с active=NONE; ошибка чтения запрещает запись, поскольку реальный active неизвестен.
|
||||
* Эта функция не преобразует старый SCI8 по адресу 0 в новую A/B-разметку. */
|
||||
bool f28335_boot_open(f28335_boot_store_t *s, const f28335_boot_io_t *io)
|
||||
{
|
||||
pcan_boot_byte_t a[64],b[64]; bool va,vb;
|
||||
if(!s || !io || !io->read || !io->write) return false;
|
||||
memset(s,0,sizeof(*s)); s->io=*io; s->active=s->pending=F28335_AB_NONE;
|
||||
if(!io->read(io->user,0,a,64) || !io->read(io->user,256,b,64)) { s->io_fault=true; return false; }
|
||||
va=bs_record_valid(a); vb=bs_record_valid(b);
|
||||
if(!va && !vb) return true;
|
||||
/* Сравнение sequence по модулю 2^32: разность менее половины диапазона
|
||||
* считается более новой записью. При равенстве выбирается страница 1.
|
||||
* Соседние публикации отличаются на единицу, в том числе после переполнения. */
|
||||
s->page=vb && (!va || (uint32_t)(bs_get32(b+8)-bs_get32(a+8))<0x80000000UL)?1:0;
|
||||
bs_decode(s,s->page?b:a); s->valid=true; return true;
|
||||
}
|
||||
/* Публикация выполняется в другую страницу, текущую действующую не трогаем.
|
||||
* Порядок принципиален: обнулить marker -> проверить -> записать тело ->
|
||||
* проверить тело -> записать marker последним -> проверить всю запись.
|
||||
* При обрыве питания загрузчик выбирает прежнюю либо полностью новую запись.
|
||||
* Каждый memcmp учитывает sizeof элемента, поскольку элемент здесь — октет
|
||||
* протокола, но на C28x занимает адресуемое 16-битное слово. */
|
||||
static bool bs_save(f28335_boot_store_t *s)
|
||||
{
|
||||
pcan_boot_byte_t p[64],check[64],invalid[4]={0,0,0,0}; unsigned int i;
|
||||
unsigned int page=s->valid?(s->page^1U):0;
|
||||
uint32_t addr=(uint32_t)page*256;
|
||||
memset(p,0,sizeof(p)); bs_put32(p,0x32424146UL); bs_put32(p+4,1);
|
||||
bs_put32(p+8,s->sequence+1); p[12]=s->active; p[13]=s->pending; p[14]=s->attempted;
|
||||
for(i=0;i<2;++i) {
|
||||
pcan_boot_byte_t *q=p+16+i*20;
|
||||
bs_put32(q,s->image[i].image_size); bs_put32(q+4,s->image[i].image_crc32);
|
||||
bs_put32(q+8,s->image[i].firmware_version);
|
||||
q[12]=s->image[i].product_type&255U; q[13]=(s->image[i].product_type>>8)&255U;
|
||||
q[14]=s->image[i].hardware_revision_min; q[15]=s->image[i].hardware_revision_max;
|
||||
}
|
||||
bs_put32(p+56,f28335_boot_crc(0,p,56)); bs_put32(p+60,0x51AFC03DUL);
|
||||
/* The old page is never erased. A torn write cannot publish a partial record. */
|
||||
if(!s->io.write(s->io.user,addr+60,invalid,4) ||
|
||||
!s->io.read(s->io.user,addr+60,check,4) || memcmp(check,invalid,sizeof(invalid)) ||
|
||||
!s->io.write(s->io.user,addr,p,60) ||
|
||||
!s->io.read(s->io.user,addr,check,60) || memcmp(p,check,60*sizeof(*p)) ||
|
||||
!s->io.write(s->io.user,addr+60,p+60,4) ||
|
||||
!s->io.read(s->io.user,addr,check,64) || memcmp(p,check,sizeof(p))) return false;
|
||||
s->page=page; s->sequence++; s->valid=true; return true;
|
||||
}
|
||||
/* Изменения сначала готовятся в копии состояния next. Рабочее состояние
|
||||
* заменяем только после публикации. Если запись/контрольное чтение сорвались,
|
||||
* перечитываем журнал: marker мог успеть записаться до ошибки readback.
|
||||
* После неудачи чтения io_fault блокирует дальнейшие изменения хранилища. */
|
||||
static bool bs_publish(f28335_boot_store_t *s, f28335_boot_store_t *next)
|
||||
{
|
||||
if(bs_save(next)) { *s=*next; return true; }
|
||||
/* A failed final read may still have committed: reload before any more writes. */
|
||||
{ f28335_boot_io_t io=s->io; if(!f28335_boot_open(s,&io)) {
|
||||
s->valid=false; s->io_fault=true; return false; } }
|
||||
return false;
|
||||
}
|
||||
/* Логический ERASE: обнуляем manifest неактивного слота в журнале.
|
||||
* Саму EEPROM стирать целиком не требуется — каждый байт будет перезаписан.
|
||||
* Active защищён всегда; наличие pending запрещает начало новой записи,
|
||||
* пока кандидат не подтверждён либо не снят при следующем выборе загрузки. */
|
||||
bool f28335_boot_prepare(f28335_boot_store_t *s, unsigned int slot)
|
||||
{
|
||||
f28335_boot_store_t next=*s;
|
||||
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE) return false;
|
||||
memset(&next.image[slot],0,sizeof(next.image[slot]));
|
||||
return bs_publish(s,&next);
|
||||
}
|
||||
/* Записываем не более одного CAN-блока и немедленно сравниваем прочитанное.
|
||||
* Проверка n <= capacity-off выполняется после off <= capacity: это исключает
|
||||
* переполнение суммы off+n и выход из слота в соседний слот или параметры. */
|
||||
bool f28335_boot_write(f28335_boot_store_t *s, unsigned int slot, uint32_t off,
|
||||
const pcan_boot_byte_t *p, unsigned int n)
|
||||
{
|
||||
pcan_boot_byte_t check[8];
|
||||
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE || !n || n>8 ||
|
||||
off>F28335_AB_SLOT_BYTES || n>F28335_AB_SLOT_BYTES-off) return false;
|
||||
return s->io.write(s->io.user,bs_base(slot)+off,p,n) &&
|
||||
s->io.read(s->io.user,bs_base(slot)+off,check,n) && !memcmp(check,p,n*sizeof(*p));
|
||||
}
|
||||
/* Слово SCI8 занимает два октета файла, младший октет идёт первым.
|
||||
* Смещение увеличиваем только после успешного чтения; усечённый файл отклоняем. */
|
||||
static bool bs_u16(f28335_boot_store_t *s,uint32_t base,uint32_t size,uint32_t *off,uint16_t *v)
|
||||
{
|
||||
pcan_boot_byte_t p[2]; if(*off>size || size-*off<2 ||
|
||||
!s->io.read(s->io.user,base+*off,p,2)) return false;
|
||||
*v=(uint16_t)(p[0]|(p[1]<<8)); *off+=2; return true;
|
||||
}
|
||||
/* Особенность таблицы TI: адрес u32 передаётся старшим словом вперёд,
|
||||
* при этом внутри каждого слова остаётся little endian. Не заменять на get32. */
|
||||
static bool bs_addr(f28335_boot_store_t *s,uint32_t base,uint32_t size,uint32_t *off,uint32_t *v)
|
||||
{
|
||||
uint16_t hi,lo; if(!bs_u16(s,base,size,off,&hi) || !bs_u16(s,base,size,off,&lo)) return false;
|
||||
*v=((uint32_t)hi<<16)|lo; return true;
|
||||
}
|
||||
/* The complete table is validated before any application RAM is written.
|
||||
* Loader RAM is E000..FFFF and M0/M1. Flash, peripherals and XINTF are forbidden. */
|
||||
/* writer=NULL: только разбираем и проверяем таблицу, RAM приложения не меняем.
|
||||
* writer задан: повторно проверяем таблицу и переносим слова в разрешённую RAM.
|
||||
* Массивы starts/ends ограничены MAX_SECTIONS; они нужны для обнаружения
|
||||
* пересекающихся секций, в том числе секций с разным порядком адресов. */
|
||||
static bool bs_table(f28335_boot_store_t *s,unsigned int slot,uint32_t size,
|
||||
f28335_boot_ram_writer writer,void *user,uint32_t *entry)
|
||||
{
|
||||
uint32_t starts[F28335_AB_MAX_SECTIONS],ends[F28335_AB_MAX_SECTIONS];
|
||||
uint32_t off=0,addr,end,base=bs_base(slot); unsigned int sections=0,i;
|
||||
uint16_t count,word; bool entry_loaded=false;
|
||||
if(!bs_u16(s,base,size,&off,&word) || word!=0x08AA) return false;
|
||||
/* После ключа AA 08 пропускаем 16 резервных октетов; далее расположен entry.
|
||||
* Нулевой count завершает таблицу строго на конце файла, без хвостовых данных. */
|
||||
off=18; if(!bs_addr(s,base,size,&off,entry)) return false;
|
||||
for(;;) {
|
||||
if(!bs_u16(s,base,size,&off,&count)) return false;
|
||||
if(!count) return off==size && entry_loaded;
|
||||
if(sections==F28335_AB_MAX_SECTIONS || !bs_addr(s,base,size,&off,&addr)) return false;
|
||||
end=addr+count;
|
||||
/* Разрешён только полуинтервал RAM [0x8000,0xE000) в словах CPU.
|
||||
* Верхняя граница защищает RAM работающего загрузчика; нижняя — стек,
|
||||
* handoff и регистры. Проверка размера payload использует уже октеты файла. */
|
||||
if(addr<0x8000UL || end>0xE000UL || end<=addr ||
|
||||
off>size || (uint32_t)count*2>size-off) return false;
|
||||
for(i=0;i<sections;++i) if(addr<ends[i] && end>starts[i]) return false;
|
||||
starts[sections]=addr; ends[sections++]=end;
|
||||
if(*entry>=addr && *entry<end) entry_loaded=true;
|
||||
if(writer) { for(i=0;i<count;++i) {
|
||||
if(!bs_u16(s,base,size,&off,&word) || !writer(user,addr+i,word)) return false;
|
||||
} } else off+=(uint32_t)count*2;
|
||||
}
|
||||
}
|
||||
/* VERIFY проверяет содержимое самой EEPROM, а не только CRC принятого потока.
|
||||
* Затем проверяется структура SCI8 и попадание entry в загружаемую секцию.
|
||||
* Выходной entry необязателен: COMMIT нужна проверка, но не адрес перехода. */
|
||||
bool f28335_boot_verify(f28335_boot_store_t *s,unsigned int slot,
|
||||
const pcan_boot_manifest_t *m,uint32_t *entry)
|
||||
{
|
||||
uint32_t off,crc=0,local_entry; unsigned int n; pcan_boot_byte_t p[32];
|
||||
if(slot>1 || m->image_size<24 || m->image_size>F28335_AB_SLOT_BYTES ||
|
||||
(m->image_size&1) || m->product_type!=0x2833 ||
|
||||
m->hardware_revision_min>1 || m->hardware_revision_max<1) return false;
|
||||
for(off=0;off<m->image_size;off+=n) {
|
||||
n=(m->image_size-off<32)?(unsigned int)(m->image_size-off):32;
|
||||
if(!s->io.read(s->io.user,bs_base(slot)+off,p,n)) return false;
|
||||
crc=f28335_boot_crc(crc,p,n);
|
||||
}
|
||||
if(crc!=m->image_crc32 || !bs_table(s,slot,m->image_size,0,0,&local_entry)) return false;
|
||||
if(entry) *entry=local_entry;
|
||||
return true;
|
||||
}
|
||||
/* COMMIT публикует проверенный образ как pending, не меняя active.
|
||||
* Повторная проверка EEPROM непосредственно перед публикацией не позволяет
|
||||
* назначить кандидатом образ, испорченный после предыдущего VERIFY. */
|
||||
bool f28335_boot_commit(f28335_boot_store_t *s,unsigned int slot,const pcan_boot_manifest_t *m)
|
||||
{
|
||||
f28335_boot_store_t next=*s;
|
||||
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE ||
|
||||
!f28335_boot_verify(s,slot,m,0)) return false;
|
||||
next.image[slot]=*m; next.pending=slot; next.attempted=0;
|
||||
return bs_publish(s,&next);
|
||||
}
|
||||
/* Подтверждать можно только действительно выбранный для пробного запуска слот.
|
||||
* Требование attempted исключает подтверждение ещё ни разу не запускавшегося
|
||||
* кандидата. Повтор CONFIRM уже активного слота возвращает успех без записи. */
|
||||
bool f28335_boot_confirm(f28335_boot_store_t *s,unsigned int running_slot)
|
||||
{
|
||||
f28335_boot_store_t next=*s;
|
||||
if(s->io_fault || running_slot>1) return false;
|
||||
if(s->pending==F28335_AB_NONE) return s->valid && s->active==running_slot;
|
||||
if(s->pending!=running_slot || !s->attempted) return false;
|
||||
next.active=running_slot; next.pending=F28335_AB_NONE; next.attempted=0;
|
||||
return bs_publish(s,&next);
|
||||
}
|
||||
/* Выбор выполняется при старте загрузчика. attempted сохраняется ДО перехода
|
||||
* в приложение: после зависания, watchdog reset или потери питания следующий
|
||||
* старт уже знает о незавершённой пробе и возвращает прежний active.
|
||||
* Успех функции с slot=NONE означает recovery, а не разрешение перехода в RAM. */
|
||||
bool f28335_boot_select(f28335_boot_store_t *s,unsigned int *slot)
|
||||
{
|
||||
f28335_boot_store_t next=*s; *slot=F28335_AB_NONE;
|
||||
if(s->io_fault) return false;
|
||||
if(s->pending!=F28335_AB_NONE) {
|
||||
if(!s->attempted && f28335_boot_verify(s,s->pending,&s->image[s->pending],0)) {
|
||||
next.attempted=1;
|
||||
if(!bs_publish(s,&next)) return false;
|
||||
*slot=s->pending; return true;
|
||||
}
|
||||
/* Кандидат уже запускался без подтверждения либо не прошёл проверку.
|
||||
* Снимаем pending отдельной публикацией; подтверждённый active остаётся прежним. */
|
||||
next.pending=F28335_AB_NONE; next.attempted=0;
|
||||
if(!bs_publish(s,&next)) return false;
|
||||
}
|
||||
if(s->active<2 && f28335_boot_verify(s,s->active,&s->image[s->active],0)) *slot=s->active;
|
||||
return true;
|
||||
}
|
||||
/* Сначала полная проверка CRC и всех адресов без записи, затем перенос слов.
|
||||
* Платформа вызывает entry только при true; частичная загрузка при ошибке I/O
|
||||
* не даёт разрешения на запуск приложения. */
|
||||
bool f28335_boot_load(f28335_boot_store_t *s,unsigned int slot,
|
||||
f28335_boot_ram_writer writer,void *user,uint32_t *entry)
|
||||
{
|
||||
return slot<2 && writer && entry && f28335_boot_verify(s,slot,&s->image[slot],entry) &&
|
||||
bs_table(s,slot,s->image[slot].image_size,writer,user,entry);
|
||||
}
|
||||
55
c/protocan-boot/ports/tms320f28335/f28335_boot_store.h
Normal file
55
c/protocan-boot/ports/tms320f28335/f28335_boot_store.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef F28335_BOOT_STORE_H
|
||||
#define F28335_BOOT_STORE_H
|
||||
#include "../../include/pcan_boot.h"
|
||||
|
||||
/* All EEPROM offsets/lengths below are OCTETS, CPU destinations are WORDS. */
|
||||
/* Физическая EEPROM 256 КиБ: две страницы журнала по 256 октетов,
|
||||
* два слота по 130560 октетов. С 0x3FE00 начинается резерв, с 0x3FEFC —
|
||||
* прежняя область параметров. Константы ниже не являются CPU word-адресами. */
|
||||
#define F28335_AB_SLOT_BYTES 0x1FE00UL
|
||||
#define F28335_AB_SLOT_A 0x00200UL
|
||||
#define F28335_AB_SLOT_B 0x20000UL
|
||||
#define F28335_AB_PARAMS 0x3FEFCUL
|
||||
#define F28335_AB_NONE 0xFFU
|
||||
#define F28335_AB_RECORD 64U
|
||||
#define F28335_AB_MAX_SECTIONS 64U
|
||||
|
||||
/* Контракт I/O: address и n измеряются в октетах, независимо от sizeof(char).
|
||||
* true означает, что выполнена вся операция; короткий обмен должен вернуть false.
|
||||
* Callbacks вызываются синхронно из main, а user передаётся им без изменений. */
|
||||
typedef struct {
|
||||
bool (*read)(void *, uint32_t, pcan_boot_byte_t *, unsigned int);
|
||||
bool (*write)(void *, uint32_t, const pcan_boot_byte_t *, unsigned int);
|
||||
void *user;
|
||||
} f28335_boot_io_t;
|
||||
typedef struct {
|
||||
f28335_boot_io_t io;
|
||||
/* sequence — номер последней публикации; page — выбранная копия журнала.
|
||||
* active — подтверждённый слот; pending — кандидат; attempted — уже была проба.
|
||||
* valid показывает наличие распознанной записи, io_fault запрещает изменения
|
||||
* после ошибки чтения. Отсутствующий слот обозначается NONE, а не нулём. */
|
||||
uint32_t sequence;
|
||||
unsigned int page, active, pending, attempted;
|
||||
bool valid, io_fault;
|
||||
pcan_boot_manifest_t image[2];
|
||||
} f28335_boot_store_t;
|
||||
|
||||
uint32_t f28335_boot_crc(uint32_t crc, const pcan_boot_byte_t *p, unsigned int n);
|
||||
bool f28335_boot_open(f28335_boot_store_t *s, const f28335_boot_io_t *io);
|
||||
/* Blank/unrecognised EEPROM opens successfully with active=NONE. I/O errors fail. */
|
||||
bool f28335_boot_prepare(f28335_boot_store_t *s, unsigned int slot);
|
||||
bool f28335_boot_write(f28335_boot_store_t *s, unsigned int slot, uint32_t offset,
|
||||
const pcan_boot_byte_t *data, unsigned int n);
|
||||
bool f28335_boot_verify(f28335_boot_store_t *s, unsigned int slot,
|
||||
const pcan_boot_manifest_t *m, uint32_t *entry);
|
||||
bool f28335_boot_commit(f28335_boot_store_t *s, unsigned int slot,
|
||||
const pcan_boot_manifest_t *m);
|
||||
bool f28335_boot_confirm(f28335_boot_store_t *s, unsigned int running_slot);
|
||||
/* Records a trial BEFORE returning it; a second unconfirmed boot rolls back. */
|
||||
bool f28335_boot_select(f28335_boot_store_t *s, unsigned int *slot);
|
||||
/* Запись одного проверенного слова приложения по CPU word-адресу.
|
||||
* Должна вернуть false при невозможности записи; после отказа запуск запрещён. */
|
||||
typedef bool (*f28335_boot_ram_writer)(void *, uint32_t, uint16_t);
|
||||
bool f28335_boot_load(f28335_boot_store_t *s, unsigned int slot,
|
||||
f28335_boot_ram_writer write_word, void *user, uint32_t *entry);
|
||||
#endif
|
||||
276
c/protocan-boot/ports/tms320f28335/f28335_can_boot.c
Normal file
276
c/protocan-boot/ports/tms320f28335/f28335_can_boot.c
Normal file
@@ -0,0 +1,276 @@
|
||||
/* Separate Flash recovery loader and application service adapter. */
|
||||
#include "DSP2833x_Device.h"
|
||||
#include "f28335_can_boot.h"
|
||||
#if EEPROM_M95M02
|
||||
#include "f28335_boot_platform.h"
|
||||
#include <string.h>
|
||||
/* boot хранит текущую CAN-сессию в RAM, store — прочитанное состояние журнала.
|
||||
* Сброс сессии не удаляет active/pending из EEPROM. */
|
||||
static pcan_boot_t boot;
|
||||
static f28335_boot_store_t store;
|
||||
/* Кольцевая очередь: ISR производит кадры, main потребляет. Из 32 элементов
|
||||
* используется 31, чтобы head==tail однозначно означало пустую очередь.
|
||||
* Долгие операции SPI запрещены внутри обработчика CAN. */
|
||||
static volatile unsigned int head,tail,overflow;
|
||||
static struct { uint32_t id; unsigned int dlc; pcan_boot_byte_t data[8]; } queue[32];
|
||||
/* reset_requested только планирует reset; Service ждёт завершения TX.
|
||||
* ready сообщает об окончании старта приложения, confirmed — о сохранённом
|
||||
* подтверждении слота. healthy отражает результат открытия журнала при Init. */
|
||||
static unsigned int reset_requested,reset_session,ready,healthy,confirmed;
|
||||
static uint32_t sent_at,ready_at;
|
||||
static unsigned int main_passes;
|
||||
static int tx_busy,tx_canceling;
|
||||
/* Один исходящий mailbox исключает перезапись ещё передаваемого ответа.
|
||||
* MDL содержит первые четыре октета, MDH — следующие четыре, младшим вперёд.
|
||||
* Запрос CANTRS ещё не означает успешную доставку: CANTA проверяется в Service.
|
||||
* Аппаратный CAN ACK не является отдельным прикладным подтверждением от ПК. */
|
||||
static bool transmit(void *u, uint32_t id, const pcan_boot_byte_t *d, pcan_boot_byte_t n)
|
||||
{
|
||||
unsigned int i; unsigned long lo=0, hi=0; (void)u;
|
||||
if (tx_busy) return false;
|
||||
for(i=0;i<n;++i) {
|
||||
if(i<4) lo|=(unsigned long)(d[i]&255U)<<(8U*i);
|
||||
else hi|=(unsigned long)(d[i]&255U)<<(8U*(i-4U));
|
||||
}
|
||||
EALLOW;
|
||||
ECanaRegs.CANME.all &= ~16UL;
|
||||
ECanaMboxes.MBOX4.MSGID.all=0x80000000UL|id;
|
||||
ECanaMboxes.MBOX4.MSGCTRL.all=n;
|
||||
ECanaMboxes.MBOX4.MDL.all=lo; ECanaMboxes.MBOX4.MDH.all=hi;
|
||||
ECanaRegs.CANME.all |=16UL;
|
||||
ECanaRegs.CANTA.all=16UL; ECanaRegs.CANAA.all=16UL;
|
||||
ECanaRegs.CANTRS.all=16UL;
|
||||
EDIS;
|
||||
sent_at=F28335Boot_Now(); tx_busy=1; tx_canceling=0; return true;
|
||||
}
|
||||
|
||||
/* Изменение образа разрешено только сборке Flash-загрузчика. В приложении
|
||||
* эти callbacks возвращают отказ, даже если им удастся передать команду записи.
|
||||
* ERASE для EEPROM означает инвалидирование manifest выбранного слота. */
|
||||
static bool erase(void *u,pcan_boot_byte_t slot)
|
||||
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_prepare(&store,slot); }
|
||||
static bool write_block(void *u,pcan_boot_byte_t slot,uint32_t offset,const pcan_boot_byte_t *d,pcan_boot_byte_t n)
|
||||
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_write(&store,slot,offset,d,n); }
|
||||
/* Проверка включает CRC EEPROM и структуру SCI8. Сейчас отказ этого слоя
|
||||
* кодируется CRC_ERROR как общий отказ проверки образа; причины внутри
|
||||
* парсера SCI8 отдельными статусами протокола не различаются. */
|
||||
static pcan_boot_status_t verify(void *u,pcan_boot_byte_t slot,const pcan_boot_manifest_t *m)
|
||||
{
|
||||
(void)u;
|
||||
if(!F28335_BOOT_LOADER || !healthy) return PCAN_BOOT_STATUS_INVALID_STATE;
|
||||
return f28335_boot_verify(&store,slot,m,0)?PCAN_BOOT_STATUS_OK:PCAN_BOOT_STATUS_CRC_ERROR;
|
||||
}
|
||||
static bool commit(void *u,pcan_boot_byte_t slot,const pcan_boot_manifest_t *m)
|
||||
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_commit(&store,slot,m); }
|
||||
/* Подтверждение принадлежит приложению после READY. Загрузчик не может
|
||||
* подтвердить кандидата сам: иначе исчезнет смысл проверки пробного запуска.
|
||||
* Разрешение кормления watchdog из ISR открываем только после записи журнала. */
|
||||
static bool confirm(void *u)
|
||||
{
|
||||
unsigned int running=F28335Boot_RunningSlot(); (void)u;
|
||||
if(F28335_BOOT_LOADER || !healthy || !ready || running>1) return false;
|
||||
if(!f28335_boot_confirm(&store,running)) return false;
|
||||
boot.config.active_slot=running; confirmed=1; return true;
|
||||
}
|
||||
static bool can_reboot(void *u) { (void)u; return healthy; }
|
||||
static void reboot(void *u) { (void)u; reset_session=0; reset_requested=1; }
|
||||
/* Контракт enter_boot: 0 — загрузчик готов и ядро может отправить ACK;
|
||||
* 1 — переход отложен, ACK придёт после reset; 2 — вход запрещён.
|
||||
* Приложение требует handoff нашего загрузчика и подтверждённый текущий слот,
|
||||
* чтобы не начать обновление из старого SPI-ROM старта или незавершённой пробы. */
|
||||
static unsigned int enter(void *u,pcan_boot_byte_t session)
|
||||
{
|
||||
(void)u;
|
||||
if(!healthy) return 2;
|
||||
if(F28335_BOOT_LOADER) return 0;
|
||||
/* No destructive fallback on boards still starting through the SPI ROM. */
|
||||
if(F28335Boot_RunningSlot()>1 || !confirmed) return 2;
|
||||
reset_session=session; reset_requested=1; return 1;
|
||||
}
|
||||
static void info32(pcan_boot_byte_t *p,uint32_t v)
|
||||
{ unsigned int i; for(i=0;i<4;++i) p[i]=(pcan_boot_byte_t)((v>>(i*8))&255U); }
|
||||
/* Паспорт помещается в classic CAN по страницам из восьми октетов.
|
||||
* NAME возвращает фрагменты строки с завершающим NUL; неизвестная страница
|
||||
* отклоняется. MEMORY: 0 — ёмкость/страница/режим/running, 1 — базы A/B,
|
||||
* 2 — active/pending/attempted/healthy/тайм-аут, 3 — область параметров. */
|
||||
static bool info(void *u,pcan_boot_byte_t command,pcan_boot_byte_t page,pcan_boot_byte_t *out)
|
||||
{
|
||||
static const char name[]=F28335_CAN_BOOT_DEVICE_NAME;
|
||||
unsigned int i,index; (void)u;
|
||||
if(command==PCAN_BOOT_CMD_CAPABILITIES && page==0) {
|
||||
/* bits: ping, discovery, identity, name, memory, A/B, confirm, rollback,
|
||||
* separate loader, session timeout, readback, SCI8 validation. */
|
||||
info32(out,(F28335_BOOT_LOADER || F28335Boot_RunningSlot()<2)?0xFFFUL:0x1FUL);
|
||||
info32(out+4,F28335_AB_SLOT_BYTES); return true;
|
||||
}
|
||||
if(command==PCAN_BOOT_CMD_DEVICE_NAME) {
|
||||
if((unsigned int)page*8U>=sizeof(name)) return false;
|
||||
for(i=0;i<8;++i) { index=(unsigned int)page*8U+i;
|
||||
out[i]=index<sizeof(name)?(pcan_boot_byte_t)name[index]:0; }
|
||||
return true;
|
||||
}
|
||||
if(command!=PCAN_BOOT_CMD_MEMORY_INFO) return false;
|
||||
if(page==0) { info32(out,0x40000UL); out[4]=0; out[5]=1;
|
||||
out[6]=F28335_BOOT_LOADER?1:0; out[7]=F28335Boot_RunningSlot(); return true; }
|
||||
if(page==1) { info32(out,F28335_AB_SLOT_A); info32(out+4,F28335_AB_SLOT_B); return true; }
|
||||
if(page==2) { out[0]=store.active; out[1]=store.pending; out[2]=store.attempted;
|
||||
out[3]=healthy?1:0; info32(out+4,boot.config.session_timeout_ms); return true; }
|
||||
if(page==3) { info32(out,F28335_AB_PARAMS); info32(out+4,258); return true; }
|
||||
return false;
|
||||
}
|
||||
/* Вызывается до разрешения прикладных IRQ. Нельзя полагаться на случайное
|
||||
* содержимое RAM или оставшееся состояние confirmed от предыдущей программы. */
|
||||
void F28335CanBoot_EarlyInit(void) { confirmed=0; ready=0; }
|
||||
/* Общий Init для приложения и загрузчика: открываем журнал, привязываем
|
||||
* callbacks, задаём идентификацию, ёмкость слота, окно 16 и тайм-аут 30 секунд.
|
||||
* В загрузчике FW берётся из active manifest; приложение сообщает версию сборки. */
|
||||
void F28335CanBoot_Init(void)
|
||||
{
|
||||
pcan_boot_config_t cfg; pcan_boot_port_t port;
|
||||
f28335_boot_io_t io; unsigned int running=F28335Boot_RunningSlot();
|
||||
memset(&cfg,0,sizeof(cfg)); memset(&port,0,sizeof(port));
|
||||
io.read=F28335Boot_Read; io.write=F28335Boot_Write; io.user=0;
|
||||
healthy=f28335_boot_open(&store,&io);
|
||||
cfg.device_type=F28335_CAN_BOOT_DEVICE_TYPE; cfg.device_id=F28335_CAN_BOOT_DEVICE_ID;
|
||||
cfg.product_type=F28335_CAN_BOOT_PRODUCT; cfg.hardware_revision=1;
|
||||
#if F28335_BOOT_LOADER
|
||||
cfg.firmware_version=store.active<2?store.image[store.active].firmware_version:0;
|
||||
#else
|
||||
cfg.firmware_version=F28335_CAN_BOOT_FIRMWARE_VERSION;
|
||||
#endif
|
||||
head=tail=overflow=reset_requested=reset_session=ready=main_passes=0; tx_busy=tx_canceling=0;
|
||||
confirmed=healthy && running<2 && store.active==running && store.pending==F28335_AB_NONE;
|
||||
cfg.active_slot=store.active;
|
||||
cfg.slot_count=2; cfg.max_image_size=F28335_AB_SLOT_BYTES; cfg.ack_window=16;
|
||||
cfg.session_timeout_ms=30000UL;
|
||||
port.send=transmit; port.erase_slot=erase; port.write_slot=write_block;
|
||||
port.verify_status=verify; port.set_pending_slot=commit; port.reboot=reboot;
|
||||
port.confirm_running_slot=confirm; port.enter_boot=enter;
|
||||
port.can_reboot=can_reboot; port.service_info=info;
|
||||
if(!pcan_boot_init(&boot,&cfg,&port,0)) return;
|
||||
EALLOW;
|
||||
/* Меняем только MBOX3/4/6: маска 88=8+16+64. MBOX0..2 — старый обмен,
|
||||
* MBOX5 — heartbeat. Приём broadcast выделен в отдельный точный mailbox,
|
||||
* чтобы слишком широкая маска Boot не перехватывала сообщения приложения.
|
||||
* 72=8+64 — оба RX, 16 — TX. Регистры флагов очищаются записью единицы. */
|
||||
ECanaRegs.CANME.all &= ~88UL;
|
||||
ECanaMboxes.MBOX3.MSGID.all=0xC0000000UL|pcan_boot_make_id(0,0,cfg.device_type,cfg.device_id,0,0);
|
||||
ECanaLAMRegs.LAM3.all=0x100FFFFFUL;
|
||||
ECanaMboxes.MBOX3.MSGCTRL.all=8;
|
||||
/* MBOX5 belongs to the application's heartbeat. MBOX6 is discovery only. */
|
||||
ECanaMboxes.MBOX6.MSGID.all=0xC0000000UL|pcan_boot_make_id(0,0,7,15,13,0);
|
||||
ECanaLAMRegs.LAM6.all=0x10000000UL; /* Ignore priority only. */
|
||||
ECanaMboxes.MBOX6.MSGCTRL.all=0;
|
||||
ECanaRegs.CANMD.all=(ECanaRegs.CANMD.all|72UL)&~16UL;
|
||||
ECanaRegs.CANRMP.all=72UL;
|
||||
ECanaRegs.CANMIM.all |=72UL;
|
||||
ECanaRegs.CANME.all |=88UL;
|
||||
EDIS;
|
||||
}
|
||||
/* ISR только проверяет заголовок и копирует кадр. Чужие адреса и RTR
|
||||
* отбрасываются до очереди. Глобальный адрес допускается только для discovery;
|
||||
* DLC и смысл команды окончательно проверит ядро в основном цикле. */
|
||||
static void receive_box(volatile struct MBOX *box,uint32_t mask)
|
||||
{
|
||||
unsigned int next=(head+1U)&31U, i;
|
||||
unsigned long lo, hi;
|
||||
uint32_t id; unsigned int type;
|
||||
if(!(ECanaRegs.CANRMP.all&mask)) return;
|
||||
if(box->MSGCTRL.bit.RTR) { ECanaRegs.CANRMP.all=mask; return; }
|
||||
id=box->MSGID.all&0x1FFFFFFFUL;
|
||||
type=pcan_boot_id_msg_type(id);
|
||||
if(!(box->MSGID.all&0x80000000UL) || pcan_boot_id_route(id)!=0 ||
|
||||
(type!=9 && type!=10 && type!=11 && type!=13) ||
|
||||
!((pcan_boot_id_device_type(id)==F28335_CAN_BOOT_DEVICE_TYPE &&
|
||||
pcan_boot_id_device_id(id)==F28335_CAN_BOOT_DEVICE_ID) ||
|
||||
(type==13 && pcan_boot_id_device_type(id)==7 && pcan_boot_id_device_id(id)==15 &&
|
||||
pcan_boot_id_msg_body(id)==0))) { ECanaRegs.CANRMP.all=mask; return; }
|
||||
if(next==tail) overflow=1;
|
||||
else {
|
||||
queue[head].id=box->MSGID.all&0x1FFFFFFFUL;
|
||||
queue[head].dlc=box->MSGCTRL.bit.DLC;
|
||||
lo=box->MDL.all; hi=box->MDH.all;
|
||||
for(i=0;i<4;++i) { queue[head].data[i]=(lo>>(8U*i))&255U;
|
||||
queue[head].data[i+4]=(hi>>(8U*i))&255U; }
|
||||
head=next;
|
||||
}
|
||||
ECanaRegs.CANRMP.all=mask;
|
||||
}
|
||||
|
||||
void F28335CanBoot_RxISR(void)
|
||||
{
|
||||
receive_box(&ECanaMboxes.MBOX3,8UL);
|
||||
receive_box(&ECanaMboxes.MBOX6,64UL);
|
||||
}
|
||||
/* Вызывать после инициализации параметров, датчиков и прикладного режима.
|
||||
* От этого момента отсчитывается интервал наблюдения перед авто-CONFIRM. */
|
||||
void F28335CanBoot_Ready(void)
|
||||
{ ready=1; ready_at=F28335Boot_Now(); }
|
||||
/* Используется таймерными ISR приложения. Во время пробы кормление watchdog
|
||||
* должно доказывать выполнение main: работа одного ISR не доказывает готовность.
|
||||
* Старый старт без handoff сохраняет прежнюю политику watchdog. */
|
||||
unsigned int F28335CanBoot_WatchdogAllowed(void)
|
||||
{ return F28335Boot_RunningSlot()>1 || confirmed; }
|
||||
/* Неблокирующий диспетчер относительно ожидания CAN: вызывать в каждом проходе main.
|
||||
* Сами callbacks проверки/записи EEPROM синхронные. Порядок здесь важен:
|
||||
* проба приложения -> завершение TX -> reset -> poll -> ошибки RX -> один кадр. */
|
||||
void F28335CanBoot_Service(void)
|
||||
{
|
||||
uint32_t now=F28335Boot_Now();
|
||||
if(!F28335_BOOT_LOADER && ready && !confirmed && healthy && F28335Boot_RunningSlot()<2) {
|
||||
/* A trial must keep executing main, not merely a timer ISR. */
|
||||
/* После 100 проходов main и 5 секунд пробуем сохранить CONFIRM.
|
||||
* Если до 10 секунд сохранение не удалось, main перестаёт кормить watchdog,
|
||||
* что позволяет следующему старту откатиться к подтверждённому образу. */
|
||||
if((uint32_t)(now-ready_at)<10000UL) ServiceDog();
|
||||
if(main_passes<100) ++main_passes;
|
||||
if(main_passes==100 && (uint32_t)(now-ready_at)>=5000UL) (void)confirm(0);
|
||||
}
|
||||
if(tx_busy) {
|
||||
/* После CANTRR ждём завершения отмены или запоздавшего ACK, прежде чем
|
||||
* повторно использовать mailbox. reset_requested к этому моменту уже снят,
|
||||
* поэтому позднее подтверждение не должно перезапустить устройство. */
|
||||
if(tx_canceling) {
|
||||
if(!((ECanaRegs.CANAA.all|ECanaRegs.CANTA.all)&16UL)) return;
|
||||
ECanaRegs.CANAA.all=16UL; ECanaRegs.CANTA.all=16UL;
|
||||
tx_busy=tx_canceling=0;
|
||||
} else if((uint32_t)(now-sent_at)>250UL) {
|
||||
/* Cancel reset immediately, even if a late CAN ACK races CANTRR. */
|
||||
reset_requested=0; pcan_boot_abort(&boot); tail=head;
|
||||
tx_canceling=1; ECanaRegs.CANTRR.all=16UL; return;
|
||||
} else if(ECanaRegs.CANTA.all&16UL) {
|
||||
ECanaRegs.CANTA.all=16UL; tx_busy=0;
|
||||
} else return;
|
||||
}
|
||||
if(reset_requested) { F28335Boot_Reset(reset_session); return; }
|
||||
pcan_boot_poll(&boot,now);
|
||||
if(tx_busy) return;
|
||||
/* После потери кадра нельзя молча продолжать запись: сбрасываем RAM-сессию
|
||||
* и накопленную очередь. Подтверждённый образ и опубликованный pending сохраняются. */
|
||||
if(overflow || (ECanaRegs.CANRML.all&72UL)) {
|
||||
overflow=0; ECanaRegs.CANRML.all=72UL;
|
||||
pcan_boot_abort(&boot); tail=head; return;
|
||||
}
|
||||
if(tail!=head) {
|
||||
pcan_boot_process(&boot,queue[tail].id,queue[tail].data,queue[tail].dlc);
|
||||
tail=(tail+1U)&31U;
|
||||
}
|
||||
}
|
||||
/* Используем session из NOINIT только для нового ENTER_BOOT после reset.
|
||||
* Это отправка отложенного ACK готовности, а не восстановление принятых блоков
|
||||
* или CRC незавершённой передачи: данные сессии через reset не сохраняются. */
|
||||
void F28335CanBoot_Resume(unsigned int session)
|
||||
{
|
||||
if(F28335_BOOT_LOADER && session && session<256)
|
||||
(void)pcan_boot_process(&boot,pcan_boot_make_id(1,0,
|
||||
F28335_CAN_BOOT_DEVICE_TYPE,F28335_CAN_BOOT_DEVICE_ID,9,(session<<8)|2),0,0);
|
||||
}
|
||||
#else
|
||||
void F28335CanBoot_EarlyInit(void) {}
|
||||
void F28335CanBoot_Init(void) {}
|
||||
void F28335CanBoot_RxISR(void) {}
|
||||
void F28335CanBoot_Service(void) {}
|
||||
void F28335CanBoot_Ready(void) {}
|
||||
unsigned int F28335CanBoot_WatchdogAllowed(void) { return 1; }
|
||||
void F28335CanBoot_Resume(unsigned int session) { (void)session; }
|
||||
#endif
|
||||
28
c/protocan-boot/ports/tms320f28335/f28335_can_boot.h
Normal file
28
c/protocan-boot/ports/tms320f28335/f28335_can_boot.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef F28335_CAN_BOOT_H
|
||||
#define F28335_CAN_BOOT_H
|
||||
#include "../../include/pcan_boot.h"
|
||||
/* Приложение задаёт версию из firmware_version.h до подключения этого заголовка.
|
||||
* Формат поля FW: major[31:24], minor[23:16], patch[15:0]. Ноль — запасное
|
||||
* значение при отсутствии версии сборки, а не прошитая здесь версия устройства. */
|
||||
#ifndef F28335_CAN_BOOT_FIRMWARE_VERSION
|
||||
#define F28335_CAN_BOOT_FIRMWARE_VERSION 0UL /* Board supplies the build identity. */
|
||||
#endif
|
||||
/* Адрес ProtoCAN состоит из Device Type (3 бита) и Device ID (4 бита).
|
||||
* ProductType — отдельный код совместимости прошивки; это не CAN ID и не RS-адрес. */
|
||||
#define F28335_CAN_BOOT_DEVICE_TYPE 3U
|
||||
#define F28335_CAN_BOOT_DEVICE_ID 7U
|
||||
#define F28335_CAN_BOOT_PRODUCT 0x2833U
|
||||
#define F28335_CAN_BOOT_DEVICE_NAME "F28335-M95M02"
|
||||
/* Порядок интеграции приложения:
|
||||
* StartupWatchdog/EarlyInit сразу после InitSysCtrl; Init после настройки
|
||||
* таймера и EEPROM; Ready после старта прикладных подсистем; Service в main.
|
||||
* RxISR вызывается из CAN ISR, WatchdogAllowed проверяется перед кормлением
|
||||
* watchdog таймерным ISR. Resume применяется только отдельным загрузчиком. */
|
||||
void F28335CanBoot_Init(void);
|
||||
void F28335CanBoot_EarlyInit(void);
|
||||
void F28335CanBoot_RxISR(void);
|
||||
void F28335CanBoot_Service(void);
|
||||
void F28335CanBoot_Ready(void);
|
||||
void F28335CanBoot_Resume(unsigned int session);
|
||||
unsigned int F28335CanBoot_WatchdogAllowed(void);
|
||||
#endif
|
||||
Reference in New Issue
Block a user