Files
templates/c/global-time/ports/tms320f28335/global_time_f28335.c
T
Andrey 2da7e7b525 feat(global-time): Добавь общие часы и порт TMS320F28335
Вынеси программные часы, коррекцию по ProtoCAN RTCSETUP и атомарную метку логгера в переносимое ядро без глобального состояния.

Подключи eCAN-A и CPU Timer2 отдельным портом F28335; зафиксируй календарные границы, переполнение, задержку RX и защиту IRQ в хостовых тестах.
2026-10-23 14:16:21 +03:00

169 lines
24 KiB
C

#include "DSP2833x_Device.h" /* Использует штатные описания 32-битных регистров eCAN, Timer2 и векторов TI. */
#include "global_time_f28335.h" /* Подключает переносимые часы и контракт интеграции порта. */
#include <string.h> /* Явно очищает отложенный RX до первого прохода main, включая TI COFF. */
#define GT_RX 0x00000080UL /* Бит 7 регистров eCAN соответствует выделенному приёмному MBOX7. */
#define GT_TX 0x00000100UL /* Бит 8 регистров eCAN соответствует отдельному передающему MBOX8. */
static gt_clock board_clock; /* Один узел F28335 использует одну глобальную временную шкалу. */
static gt_frame pending_frame; /* Последний RX хранится до обработки в main; новый broadcast заменяет устаревший. */
static volatile uint16_t pending_rx; /* Публикует полностью скопированный RX и его монотонную привязку. */
static uint32_t received_at; /* Запоминает миллисекундный uptime в момент обработки CAN ISR. */
static uint16_t tx_busy, timer_owned; /* Явно сбрасывается init: TI COFF не гарантирует нули в .ebss. */
static uint16_t initialized = 0U; /* Запись в .cinit гарантирует безопасный отказ ранних ISR hooks до init даже при TI COFF. */
static uint16_t enter(void *user) /* Реализует вложенную критическую секцию TI C28x. */
{ /* Не разрешает прерывания при выходе из уже выполняющегося ISR. */
(void)user; /* Порт использует один статический экземпляр, поэтому контекст не требуется. */
return __disable_interrupts(); /* Возвращает предыдущее состояние ST1, необходимое для восстановления INTM. */
} /* Завершает платформенный вход в критическую секцию. */
static void leave(void *user, uint16_t state) /* Восстанавливает состояние IRQ до входа в библиотеку. */
{ /* Сохраняет корректность вызовов и из main, и из ISR. */
(void)user; /* Отдельный аппаратный экземпляр выбирается статическими регистрами eCAN-A. */
__restore_interrupts(state); /* Восстанавливает исходный INTM вместо безусловного разрешения IRQ. */
} /* Завершает платформенный выход из критической секции. */
static int send(void *user, const gt_frame *frame) /* Копирует пакет готовности в выделенный аппаратный mailbox без ожидания. */
{ /* Транспорт не перезаписывает кадр, пока контроллер не подтвердит передачу. */
uint16_t state = enter(0); /* Защищает RMW общих регистров CANME от чужих CAN ISR, сохраняя прежний INTM. */
(void)user; /* Контекст платформы не нужен для фиксированного eCAN-A MBOX8. */
if (tx_busy || (ECanaRegs.CANTRS.all & GT_TX)) { /* При занятости сохраняет исходный пакет и предлагает повтор из main. */
leave(0, state); /* Восстанавливает исходные IRQ и на раннем выходе занятого транспорта. */
return 0; /* Не теряет готовность и не ждёт окончания аппаратной передачи. */
} /* Продолжает только при владении свободным MBOX8. */
EALLOW; /* Открывает запись защищённой конфигурации mailbox контроллера. */
ECanaRegs.CANME.all &= ~GT_TX; /* Отключает только MBOX8 перед изменением ID и payload. */
ECanaMboxes.MBOX8.MSGID.all = 0x80000000UL | frame->id; /* IDE в бите 31 eCAN разрешает расширенный ProtoCAN ID. */
ECanaMboxes.MBOX8.MSGCTRL.all = 1UL; /* DLC=1 и RTR=0 соответствуют единственному октету готовности. */
ECanaMboxes.MBOX8.MDL.all = frame->data[0]; /* Первый проводной октет располагается в младших восьми битах MDL при DBO=0. */
ECanaMboxes.MBOX8.MDH.all = 0UL; /* Убирает остатки прежнего payload, хотя DLC передаёт только один октет. */
ECanaRegs.CANTA.all = GT_TX; /* Сбрасывает только старый ACK MBOX8 записью единицы. */
ECanaRegs.CANAA.all = GT_TX; /* Сбрасывает только старое подтверждение отмены MBOX8. */
ECanaRegs.CANME.all |= GT_TX; /* Возвращает MBOX8 в работу, сохраняя чужие mailbox. */
ECanaRegs.CANTRS.all = GT_TX; /* Запрашивает передачу без блокирующего ожидания ACK шины. */
EDIS; /* Закрывает запись защищённых регистров после полной публикации кадра. */
tx_busy = 1U; /* Отмечает владение кадром до CANTA или CANAA, даже при отсутствии других узлов на шине. */
leave(0, state); /* Освобождает короткую аппаратную секцию сразу после публикации запроса передачи. */
return 1; /* Подтверждает приём транспортом, но не отдельное прикладное подтверждение мастера. */
} /* Завершает неблокирующий callback передачи. */
int gt_f28335_init(uint16_t device_type, uint16_t device_id) /* Настраивает часы и только два выделенных mailbox при запрещённых IRQ. */
{ /* Приложение заранее задаёт скорость eCAN, GPIO, DBO=0 и общие векторы прерываний. */
gt_port port; /* Локальная таблица callbacks копируется ядром в постоянное состояние. */
if (device_type > 7U || device_id > 15U || (ECanaRegs.CANTRS.all & GT_TX) || ECanaRegs.CANMC.bit.DBO) return 0; /* Проверяет адрес и порядок октетов; активную TX-передачу не прерывает повторным init. */
if (initialized && timer_owned) return 0; /* Требует остановить захваченный Timer2 перед повторным init, сохраняя его владение при отказе. */
port.user = 0; /* Единственный экземпляр порта не требует внешнего контекста. */
port.enter = enter; /* Подключает защиту снимка штатным intrinsic TI. */
port.leave = leave; /* Подключает восстановление предыдущей маски прерываний. */
port.send = send; /* Подключает отправку в MBOX8 без ожидания свободной шины. */
if (!gt_init(&board_clock, &port, device_type, device_id)) return 0; /* Сбрасывает RAM до открытия источников IRQ. */
pending_rx = tx_busy = timer_owned = 0U; /* Явно очищает программные флаги после холодного и тёплого старта. */
memset(&pending_frame, 0, sizeof(pending_frame)); /* Не читает неопределённую RAM при первом service без полученного пакета. */
received_at = 0UL; /* Начальная привязка не читается до получения первого кадра. */
EALLOW; /* Открывает изменение аппаратной конфигурации eCAN-A. */
ECanaRegs.CANME.all &= ~(GT_RX | GT_TX); /* Временно отключает только выделенные mailbox 7/8. */
ECanaMboxes.MBOX7.MSGID.all = 0xC0000000UL | GT_RTC_ID; /* IDE=1 и AME=1 включают расширенный ID и локальную маску точного RTCSETUP. */
ECanaLAMRegs.LAM7.all = 0x10000000UL; /* LAM игнорирует только бит Priority[28]; LAMI=0 сохраняет проверку IDE. */
ECanaMboxes.MBOX7.MSGCTRL.all = 7UL; /* Задаёт ожидаемые семь октетов; фактический DLC дополнительно проверяется ядром. */
ECanaMboxes.MBOX8.MSGID.all = 0x80000000UL; /* Подготавливает расширенный TX без AME; адрес будет установлен callback отправки. */
ECanaMboxes.MBOX8.MSGCTRL.all = 1UL; /* Пакет готовности занимает один октет CAN payload. */
ECanaRegs.CANMD.all = (ECanaRegs.CANMD.all | GT_RX) & ~GT_TX; /* MBOX7 становится RX, MBOX8 — TX, направления остальных mailbox сохраняются. */
ECanaRegs.CANOPC.all |= GT_RX; /* Запрещает перезапись RX до чтения ISR; при потере используется следующий периодический broadcast. */
ECanaRegs.CANMIL.all &= ~GT_RX; /* Направляет MBOX7 в общую линию ECAN0INTA приложения. */
ECanaRegs.CANMIM.all = (ECanaRegs.CANMIM.all | GT_RX) & ~GT_TX; /* Разрешает IRQ только на RX, а завершение TX проверяет main. */
ECanaRegs.CANRMP.all = GT_RX; /* Удаляет старый pending только MBOX7 записью единицы. */
ECanaRegs.CANRML.all = GT_RX; /* Очищает старую потерю приёма только выделенного mailbox. */
ECanaRegs.CANTA.all = GT_TX; /* Удаляет старый ACK выделенного передатчика, не затрагивая CAN Boot. */
ECanaRegs.CANAA.all = GT_TX; /* Удаляет старый флаг отмены перед настройкой нового обмена. */
ECanaRegs.CANME.all |= GT_RX | GT_TX; /* Включает приём синхронизации и передачу готовности. */
EDIS; /* Закрывает защищённую конфигурацию контроллера. */
initialized = 1U; /* Публикует готовность самого порта после сброса всех RAM- и аппаратных флагов. */
return 1; /* Приложение может запускать такт и разрешать общие CAN-прерывания. */
} /* Завершает настройку без сброса скорости, GPIO и других CAN-сервисов. */
int gt_f28335_init_periph(uint16_t periph) /* Привязывает часы к физическому адресу периферийной платы. */
{ /* Использует тот же реестр, что существующий порт CAN Boot F28335. */
if (periph >= 32U) return 0; /* Адреса PERIPH определены только для 32 устройств, без циклического обрезания. */
return gt_f28335_init((uint16_t)(1U + periph / 16U), periph % 16U); /* Номера 0..15 используют Type=1, 16..31 — Type=2, младшие четыре бита задают ID. */
} /* Завершает преобразование физического адреса. */
gt_clock *gt_f28335_clock(void) /* Предоставляет логгеру общую временную базу платы. */
{ /* Действует после успешной инициализации порта. */
return &board_clock; /* Один и тот же объект обслуживают CAN, таймер и все потребители времени. */
} /* Завершает доступ к экземпляру переносимого ядра. */
void gt_f28335_ready(void) /* Публикует готовность прикладного оборудования после загрузки параметров. */
{ /* Не объявляет успех при отказе конфигурации порта. */
if (initialized) gt_ready(&board_clock); /* Первый пакет будет послан из main даже при уже полученном времени. */
} /* Завершает hook завершения инициализации платы. */
void gt_f28335_tick_1ms(void) /* Позволяет использовать существующий периодический ISR вместо Timer2. */
{ /* Приложение выбирает один источник миллисекунд, исключая двойной отсчёт. */
if (initialized) gt_tick_1ms(&board_clock); /* Продвигает локальные и календарные часы только после явного init. */
} /* Завершает универсальный миллисекундный hook. */
void gt_f28335_rx_isr(void) /* Захватывает CAN-время и момент получения без календарной обработки в ISR. */
{ /* Вызывается существующим обработчиком ECAN0INTA до очистки CANGIF и PIEACK. */
uint32_t low, high, uptime; /* Однократные 32-битные чтения не рвут слова аппаратного mailbox. */
uint16_t i, state; /* Индекс восьми октетов и прежняя маска прерываний. */
if (!initialized || !(ECanaRegs.CANRMP.all & GT_RX)) return; /* Не читает MBOX7 по прерыванию другого CAN-сервиса. */
state = enter(0); /* Исключает вложенный таймер между чтением uptime и публикацией полученного кадра. */
uptime = board_clock.stamp.uptime_ms; /* Фиксирует момент RX в монотонных миллисекундах, независимых от синхронизации. */
pending_frame.id = ECanaMboxes.MBOX7.MSGID.all & 0x1FFFFFFFUL; /* Удаляет служебные IDE/AME биты регистра из 29-битного ID. */
pending_frame.extended = (uint16_t)(ECanaMboxes.MBOX7.MSGID.all >> 31U); /* Ядро дополнительно проверит IDE даже при аппаратной фильтрации. */
pending_frame.remote = ECanaMboxes.MBOX7.MSGCTRL.bit.RTR; /* Сохраняет RTR, чтобы удалённый запрос никогда не устанавливал часы. */
pending_frame.dlc = ECanaMboxes.MBOX7.MSGCTRL.bit.DLC; /* Сохраняет фактическую длину, включая ошибочный или пустой payload. */
low = ECanaMboxes.MBOX7.MDL.all; /* Читает первые четыре октета одним 32-битным доступом eCAN. */
high = ECanaMboxes.MBOX7.MDH.all; /* Читает оставшиеся четыре октета без указателей на байты C28x. */
for (i = 0U; i < 8U; ++i) pending_frame.data[i] = (uint16_t)(((i < 4U ? low : high) >> (8U * (i % 4U))) & 255UL); /* Извлекает каждый проводной октет при DBO=0 в отдельное 16-битное слово. */
received_at = uptime; /* Публикует временную привязку того же аппаратного кадра. */
pending_rx = 1U; /* Отмечает полностью готовый снимок; main читает его под той же блокировкой. */
ECanaRegs.CANRMP.all = GT_RX; /* Освобождает MBOX7 только после полного копирования защищённого CANOPC кадра. */
ECanaRegs.CANRML.all = GT_RX; /* Сбрасывает индикатор пропущенного кадра, сохраняя уже скопированное опорное время. */
leave(0, state); /* Возвращает исходный INTM обработчика CAN. */
} /* Завершает короткий ISR без отправки, ожидания CAN и расчёта календаря. */
void gt_f28335_service(void) /* Обрабатывает часы и готовность из единственного основного цикла. */
{ /* Отложенная календарная работа не увеличивает длительность CAN ISR. */
gt_frame frame; /* Локальная копия допускает замену следующего pending кадра новым CAN ISR. */
uint32_t uptime; /* Сохраняет привязку прочитанного кадра до выхода из критической секции. */
uint16_t state, have_rx; /* Разделяет короткий захват очереди и длительную проверку календаря. */
if (!initialized) return; /* Не обращается к неинициализированным callbacks после отказа init. */
state = enter(0); /* Блокирует ISR только на время копирования одного небольшого кадра. */
have_rx = pending_rx; /* Запоминает наличие кадра перед освобождением слота. */
frame = pending_frame; /* Копирует payload и заголовок согласованно с моментом RX. */
uptime = received_at; /* Забирает момент получения того же кадра. */
pending_rx = 0U; /* Разрешает приём следующего broadcast, пока main рассчитывает дату текущего. */
leave(0, state); /* Возобновляет IRQ до календарных циклов. */
if (have_rx) (void)gt_receive(&board_clock, &frame, uptime); /* Проверяет время и добавляет тики, прошедшие с RX ISR. */
if (tx_busy && ((ECanaRegs.CANTA.all | ECanaRegs.CANAA.all) & GT_TX)) { /* Освобождает TX только после подтверждения передачи либо аппаратной отмены. */
ECanaRegs.CANTA.all = GT_TX; /* Сбрасывает только ACK собственного mailbox. */
ECanaRegs.CANAA.all = GT_TX; /* Сбрасывает только флаг отмены собственного mailbox. */
tx_busy = 0U; /* Следующий gt_poll сможет повторить готовность, если время ещё не получено. */
} /* При отсутствии ACK запрос остаётся аппаратному CAN для автоматической передачи после восстановления шины. */
gt_poll(&board_clock); /* Объявляет готовность и ограничивает повторы одной секундой локального времени. */
} /* Завершает неблокирующий проход обслуживания. */
__interrupt void gt_f28335_timer2_isr(void) /* Обслуживает прямой CPU INT14 без использования группы PIE. */
{ /* Этот вектор принадлежит библиотеке только после явного timer2_start. */
CpuTimer2Regs.TCR.all = 0xC000U; /* TIF[15]=1 очищает флаг, TIE[14]=1 сохраняет IRQ, TSS=0 продолжает счёт. */
gt_f28335_tick_1ms(); /* Один период Timer2 соответствует одной миллисекунде программных часов. */
} /* Для прямого INT14 подтверждение PIEACK не требуется. */
int gt_f28335_timer2_start(uint32_t cpu_hz) /* Опционально запускает выделенный таймер с периодом ровно 1 мс. */
{ /* Частота поступает от проекта; порт не предполагает жёстко заданные 150 МГц. */
uint16_t state; /* Сохраняет прежнюю глобальную маску IRQ на время смены вектора. */
if (!initialized || cpu_hz < 1000UL || cpu_hz % 1000UL != 0UL) return 0; /* Отвергает период с дробным числом тактов вместо накопления скрытого дрейфа. */
state = enter(0); /* Исключает запуск INT14 со старым вектором и новым периодом. */
CpuTimer2Regs.TCR.all = 0x0010U; /* TSS[4]=1 останавливает счёт, TIE=0 временно запрещает IRQ Timer2. */
CpuTimer2Regs.PRD.all = cpu_hz / 1000UL - 1UL; /* Период включает значения PRD..0; при 150 МГц это 149999, то есть 150000 тактов за 1 мс. */
CpuTimer2Regs.TPR.all = 0U; /* Нулевой младший предделитель означает один такт CPU на шаг счётчика. */
CpuTimer2Regs.TPRH.all = 0U; /* Нулевой старший предделитель не добавляет деления частоты. */
EALLOW; /* Разрешает запись защищённой таблицы векторов прерываний TI. */
PieVectTable.TINT2 = &gt_f28335_timer2_isr; /* Подключает собственный обработчик прямого CPU Timer2/INT14. */
EDIS; /* Закрывает доступ к векторной таблице после установки обработчика. */
IFR &= (uint16_t)~M_INT14; /* Снимает старый pending CPU INT14 до запуска нового периодического такта. */
IER |= M_INT14; /* Разрешает только INT14, сохраняя маски остальных CPU-прерываний. */
timer_owned = 1U; /* Позволяет stop изменять Timer2 только после явного захвата этим портом. */
CpuTimer2Regs.TCR.all = 0xC020U; /* TIF=1 очищает флаг, TIE=1 разрешает IRQ, TRB=1 загружает PRD, TSS=0 запускает таймер. */
leave(0, state); /* Сохраняет выбор приложения, когда разрешить глобальные прерывания. */
return 1; /* Приложение должно исключить параллельный вызов tick из своего Timer1. */
} /* Завершает настройку автономного миллисекундного такта. */
void gt_f28335_timer2_stop(void) /* Освобождает аппаратный такт, ранее явно запущенный библиотекой. */
{ /* Не трогает чужой Timer2, если выбран hook существующего таймера. */
uint16_t state = enter(0); /* Не допускает INT14 между остановкой счётчика и снятием разрешения CPU. */
if (timer_owned) { /* Ограничивает изменения ресурсом, захваченным этим портом. */
CpuTimer2Regs.TCR.all = 0x8010U; /* TIF=1 очищает флаг, TSS=1 останавливает таймер, TIE=0 запрещает источник IRQ. */
IER &= (uint16_t)~M_INT14; /* Убирает только разрешение прямого Timer2 IRQ, сохраняя остальные CPU-сервисы. */
timer_owned = 0U; /* Фиксирует освобождение источника аппаратного такта. */
} /* При отсутствии владения никакие регистры таймера не меняются. */
leave(0, state); /* Возвращает прежнее состояние глобальной маски IRQ. */
} /* Завершает необязательную остановку аппаратных часов. */