164 lines
9.5 KiB
C
164 lines
9.5 KiB
C
/**
|
||
* 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);
|
||
}
|
||
}
|
||
}
|
||
}
|