Add shared emulator services, waveform generation and STM32 USB boot ports

This commit is contained in:
2026-09-28 11:54:17 +03:00
parent 5260186c16
commit 79411dbc26
56 changed files with 3627 additions and 0 deletions
@@ -0,0 +1,163 @@
/**
* USART2/VCP и FDCAN1 в режиме Classic CAN. Формат запросов совпадает с F407:
* UART RTU, CAN extended ID 0xBA0000+mode-1; ответы 0xBA0020+mode-1.
* Здесь нет второго кодека: все байты обрабатывают emu_rtu/emu_can из templates.
*/
#include "transport.h"
#include "board.h"
#include "board_config.h"
#include "emulator.h"
#include "stm32g4xx_hal.h"
static UART_HandleTypeDef uart;
static FDCAN_HandleTypeDef can;
static volatile uint8_t transmitting;
static uint8_t can_ready;
volatile uint32_t uart_errors,uart_overflows,can_timeouts,can_overflows;
void transport_init(void)
{
FDCAN_FilterTypeDef filter={0};
uart.Instance=USART2; uart.Init.BaudRate=emu_uart_baud();
uart.Init.WordLength=UART_WORDLENGTH_8B; uart.Init.StopBits=UART_STOPBITS_1;
uart.Init.Parity=UART_PARITY_NONE; uart.Init.Mode=UART_MODE_TX_RX;
uart.Init.HwFlowCtl=UART_HWCONTROL_NONE; uart.Init.OverSampling=UART_OVERSAMPLING_16;
uart.Init.ClockPrescaler=UART_PRESCALER_DIV1;
if(HAL_UART_Init(&uart)!=HAL_OK) board_fatal(4);
HAL_NVIC_SetPriority(USART2_IRQn,6,0); HAL_NVIC_EnableIRQ(USART2_IRQn);
can.Instance=FDCAN1; can.Init.ClockDivider=FDCAN_CLOCK_DIV1;
can.Init.FrameFormat=FDCAN_FRAME_CLASSIC; can.Init.Mode=FDCAN_MODE_NORMAL;
can.Init.AutoRetransmission=ENABLE; can.Init.TransmitPause=DISABLE;
can.Init.ProtocolException=DISABLE;
/* PCLK1=80 МГц, 16 квантов/бит, prescaler=20 для 250 кбит/с.
* Sample point=(1+13)/16=87,5%; CAN FD/BRS на проводе не используется. */
if(HAL_RCC_GetPCLK1Freq()%(EMU_CAN_BITRATE*16u)) board_fatal(2);
can.Init.NominalPrescaler=HAL_RCC_GetPCLK1Freq()/(EMU_CAN_BITRATE*16u);
can.Init.NominalSyncJumpWidth=1; can.Init.NominalTimeSeg1=13; can.Init.NominalTimeSeg2=2;
can.Init.DataPrescaler=1; can.Init.DataSyncJumpWidth=1;
can.Init.DataTimeSeg1=1; can.Init.DataTimeSeg2=1;
can.Init.StdFiltersNbr=0; can.Init.ExtFiltersNbr=1;
can.Init.TxFifoQueueMode=FDCAN_TX_FIFO_OPERATION;
if(HAL_FDCAN_Init(&can)!=HAL_OK) board_fatal(2);
filter.IdType=FDCAN_EXTENDED_ID; filter.FilterIndex=0;
filter.FilterType=FDCAN_FILTER_MASK; filter.FilterConfig=FDCAN_FILTER_TO_RXFIFO0;
filter.FilterID1=0xBA0000u+EMU_CAN_MODE-1; filter.FilterID2=0x1fffffffu;
/* Отдельно запрещаем все стандартные, посторонние и remote-кадры. */
if(HAL_FDCAN_ConfigFilter(&can,&filter)!=HAL_OK ||
HAL_FDCAN_ConfigGlobalFilter(&can,FDCAN_REJECT,FDCAN_REJECT,
FDCAN_REJECT_REMOTE,FDCAN_REJECT_REMOTE)!=HAL_OK) board_fatal(2);
can_ready=(HAL_FDCAN_Start(&can)==HAL_OK);
if(!can_ready) clock_fault=2;
}
/* Опрос ограничен одним RX и одним TX за проход. Статический снимок ответа
* сохраняется до завершения всех фрагментов; новый запрос во время ответа не принимается. */
void transport_can_poll(uint32_t now)
{
static uint16_t words[124],count,fragment;
static uint8_t tag,error,pending;
static uint32_t sent_at,mailbox;
FDCAN_RxHeaderTypeDef rx; FDCAN_TxHeaderTypeDef tx={0};
uint8_t data[64]; /* HAL может скопировать до 64 байт чужого FD-кадра до проверки формата. */
if(!can_ready) return;
/* FDCAN не делает bxCAN AutoBusOff. Снятие INIT запускает аппаратное ожидание
* 129 последовательностей recessive перед восстановлением шины. */
if((can.Instance->PSR&FDCAN_PSR_BO) && (can.Instance->CCCR&FDCAN_CCCR_INIT)) {
CLEAR_BIT(can.Instance->CCCR,FDCAN_CCCR_INIT);
count=0;
}
if(pending) {
if(!HAL_FDCAN_IsTxBufferMessagePending(&can,mailbox)) {
if(pending==1 && !(can.Instance->TXBTO&mailbox)) {count=0;++can_timeouts;}
pending=0;
} else if(pending==1 && (uint32_t)(now-sent_at)>=250u) {
(void)HAL_FDCAN_AbortTxRequest(&can,mailbox);
count=0;pending=2;++can_timeouts;
}
}
if(__HAL_FDCAN_GET_FLAG(&can,FDCAN_FLAG_RX_FIFO0_MESSAGE_LOST)) {
__HAL_FDCAN_CLEAR_FLAG(&can,FDCAN_FLAG_RX_FIFO0_MESSAGE_LOST);++can_overflows;
}
if(HAL_FDCAN_GetRxFifoFillLevel(&can,FDCAN_RX_FIFO0) &&
HAL_FDCAN_GetRxMessage(&can,FDCAN_RX_FIFO0,&rx,data)==HAL_OK) {
if(rx.DataLength==FDCAN_DLC_BYTES_8 && rx.IdType==FDCAN_EXTENDED_ID &&
rx.RxFrameType==FDCAN_DATA_FRAME && rx.FDFormat==FDCAN_CLASSIC_CAN &&
rx.Identifier==0xBA0000u+EMU_CAN_MODE-1 && !count && !pending) {
count=emu_can(data,words,&tag,&error);fragment=0;
}
}
if(count && !pending && HAL_FDCAN_GetTxFifoFreeLevel(&can)) {
emu_can_fragment(words,fragment,tag,error,data);
tx.Identifier=0xBA0020u+EMU_CAN_MODE-1;tx.IdType=FDCAN_EXTENDED_ID;
tx.TxFrameType=FDCAN_DATA_FRAME;tx.DataLength=FDCAN_DLC_BYTES_8;
tx.ErrorStateIndicator=FDCAN_ESI_ACTIVE;tx.BitRateSwitch=FDCAN_BRS_OFF;
tx.FDFormat=FDCAN_CLASSIC_CAN;tx.TxEventFifoControl=FDCAN_NO_TX_EVENTS;
if(HAL_FDCAN_AddMessageToTxFifoQ(&can,&tx,data)==HAL_OK) {
mailbox=HAL_FDCAN_GetLatestTxFifoQRequestBuffer(&can);
pending=1;sent_at=now;
if(++fragment>=count/2)count=0;
} else {count=0;++can_timeouts;}
}
}
/* HAL обслуживает TXE/TC и вызывает callback завершения; прикладной протокол в ISR не выполняется. */
void USART2_IRQHandler(void) { HAL_UART_IRQHandler(&uart); }
/* Вызывается HAL после TC: последний стоп-бит уже вышел на линию, можно отключить передатчик RS-485.
* Также используется локально для возврата в приём, если запуск передачи завершился ошибкой. */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *h)
{
if (h!=&uart) return;
#if EMU_RS485_DE
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET);
#endif
transmitting=0;
}
/* Принимает доступный байт без ожидания, накапливает кадр и определяет конец по паузе RTU.
* rx/tx имеют по 256 байт; invalid запрещает выполнение всего повреждённого кадра.
* now и last_rx измеряются в миллисекундах; переданный HAL буфер tx живёт до завершения ISR. */
void transport_uart_poll(uint32_t now)
{
static uint8_t rx[256], tx[256], invalid;
static uint16_t received;
static uint32_t last_rx;
uint32_t baud=uart.Init.BaudRate;
/* Изменение скорости применяется после окончания ответа на старой скорости.
* Незавершённый входной кадр сбрасывается, чтобы не объединить байты разных скоростей. */
if (!transmitting && baud!=emu_uart_baud()) {
uart.Init.BaudRate=emu_uart_baud();
if (HAL_UART_Init(&uart)!=HAL_OK) board_fatal(4);
received=0; invalid=0; return;
}
if (__HAL_UART_GET_FLAG(&uart,UART_FLAG_ORE) || __HAL_UART_GET_FLAG(&uart,UART_FLAG_FE) ||
__HAL_UART_GET_FLAG(&uart,UART_FLAG_NE) || __HAL_UART_GET_FLAG(&uart,UART_FLAG_PE)) {
/* На G4 ошибки сбрасываются записью ICR. RQR удаляет ошибочный байт;
* чтение SR/DR, применяемое на F407, здесь не используется. */
__HAL_UART_CLEAR_FLAG(&uart,UART_CLEAR_PEF|UART_CLEAR_FEF|UART_CLEAR_NEF|UART_CLEAR_OREF);
__HAL_UART_SEND_REQ(&uart,UART_RXDATA_FLUSH_REQUEST);
invalid=1; ++uart_errors; last_rx=now;
} else if (__HAL_UART_GET_FLAG(&uart,UART_FLAG_RXNE)) {
uint8_t b;
/* RXNE уже установлен; timeout=0 не допускает ожидания следующего байта.
* Во время передачи входные байты вычитываются и отбрасываются, чтобы не принять собственное эхо. */
if (HAL_UART_Receive(&uart,&b,1,0)==HAL_OK && !transmitting) {
if (received<sizeof(rx)) rx[received++]=b;
else { invalid=1; ++uart_overflows; }
last_rx=now;
}
}
/* At >19200 baud RTU t3.5 is 1.75 ms; 3 ticks guarantees >=2 ms. */
/* Для 8N1 ниже порога считаем 3,5 символа по 10 бит с округлением и запасом одного тика.
* Выше 19200 бод используем 3 тика: с учётом фазы SysTick фактическая пауза не меньше 2 мс. */
if (received && !transmitting && (uint32_t)(now-last_rx)>=
(baud>19200u ? 3u : (35000u+baud-1)/baud+1u)) {
uint16_t length=invalid ? 0 : emu_rtu(rx,received,tx);
received=0; invalid=0;
if (length) {
transmitting=1;
#if EMU_RS485_DE
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET);
#endif
/* Передача асинхронная: poll возвращается сразу. tx не перезаписывается, пока transmitting != 0. */
if (HAL_UART_Transmit_IT(&uart,tx,length)!=HAL_OK) {
++uart_errors; HAL_UART_TxCpltCallback(&uart);
}
}
}
}