/* * UART/USB-транспорт стенда STM32G474 для регистровых сервисов. Входные байты передаются * обработчику общего протокола; аппаратные очереди и отправка ответа относятся к этому порту. */ /** * 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 (received19200 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); } } } }