Add shared emulator services, waveform generation and STM32 USB boot ports
This commit is contained in:
34
c/set-protocol/ports/stm32g474-periph/README.md
Normal file
34
c/set-protocol/ports/stm32g474-periph/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# STM32G474RB emulator peripheral port
|
||||
|
||||
Board adapters for the common UMP/TMS/IGBT server and waveform generator.
|
||||
All peripheral source files have detailed Russian comments matching the F407 port.
|
||||
The shared algorithms, codecs and register map stay in `../../src`.
|
||||
|
||||
- `board.c`: application VTOR 0x08010000; HSI16/PLL 160 MHz, APB1 80 MHz;
|
||||
HSI48/CRS USB; board GPIO and 1 ms model ticks.
|
||||
- `transport.c`: USART2 PA2/PA3 RTU, optional RS485 DE PB15;
|
||||
FDCAN1 PB8/PB9, Classic CAN, extended IDs, bounded polling and TX timeout.
|
||||
- `wave_hal.c`: PA4 DAC1_CH1, TIM6, DMA1_Channel1 with DMAMUX DAC1 request;
|
||||
2..4096 samples in SRAM1/2, exact timer division, maximum 1 MS/s.
|
||||
- `main.c`: service ordering and deferred reboot into the G474 USB bootloader.
|
||||
|
||||
IGBT input bits 0..7 use PB0/PB1/PB2/PB10/PB11/PB12/PB13/PB14;
|
||||
output bits use PC0..PC7. SWD, SWO and USB pins are preserved.
|
||||
Clock, addresses and RS485 mode come from the board-supplied `board_config.h`.
|
||||
The default internal clock does not guarantee CAN or waveform accuracy over
|
||||
all temperature conditions; use a verified 24 MHz HSE source when required.
|
||||
|
||||
The consumer supplies `board.h`, `transport.h`, `wave_hal.h`, `emulator.h`,
|
||||
G4 HAL configuration and the common model composition. The reference project
|
||||
is `Nucleo_G474RB` in the emulator repository. Compile `EMU_CAPACITY=400`:
|
||||
each 32000-byte archive bank fits the MCU's 32 KiB CCM/96 KiB SRAM layout.
|
||||
DMA samples stay in SRAM1/2. No F407 peripheral implementation is compiled.
|
||||
|
||||
Link at 0x08010000 with a 64 KiB Flash limit; install the separate G474RB
|
||||
USB bootloader first. Require 128 KiB silicon, DBANK=0, BFB2=0 and no bank swap.
|
||||
The firmware rejects mismatched geometry before peripheral initialization.
|
||||
Reuse `../stm32g474-usb-boot/boot_usb_port.c` and `boot_request.c` with the
|
||||
shared USB CDC transport. Device model: STM32G474RB-EMU-EXPERIMENT.
|
||||
|
||||
Host tests validate the shared software; peripheral timing and electrical
|
||||
behavior have not yet been validated on a physical board.
|
||||
109
c/set-protocol/ports/stm32g474-periph/board.c
Normal file
109
c/set-protocol/ports/stm32g474-periph/board.c
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Аппаратный порт STM32G474RB: векторы приложения, тактирование и GPIO.
|
||||
* Алгоритмы UMP/TMS/IGBT находятся в общем C-ядре templates; здесь только пины.
|
||||
*/
|
||||
#include "board.h"
|
||||
#include "board_config.h"
|
||||
#include "transport.h"
|
||||
#include "emulator.h"
|
||||
#include "igbt_emulator.h"
|
||||
#include "stm32g4xx_hal.h"
|
||||
volatile uint32_t clock_fault;
|
||||
uint32_t SystemCoreClock=16000000u;
|
||||
const uint8_t AHBPrescTable[16]={0,0,0,0,0,0,0,0,1,2,3,4,6,7,8,9};
|
||||
const uint8_t APBPrescTable[8]={0,0,0,0,1,2,3,4};
|
||||
static uint8_t usb_clock_ready;
|
||||
/* Восемь входов IGBT: PB0,PB1,PB2,PB10..PB14. PB3/SWO и PB8/PB9 CAN свободны. */
|
||||
static const uint16_t input_pins[8]={GPIO_PIN_0,GPIO_PIN_1,GPIO_PIN_2,
|
||||
GPIO_PIN_10,GPIO_PIN_11,GPIO_PIN_12,GPIO_PIN_13,GPIO_PIN_14};
|
||||
/* До C runtime разрешены только регистровые операции; глобальные данные ещё не готовы. */
|
||||
void SystemInit(void) { SCB->CPACR|=0xFu<<20; SCB->VTOR=0x08010000u; }
|
||||
void SystemCoreClockUpdate(void) {
|
||||
RCC_ClkInitTypeDef config; uint32_t latency;
|
||||
HAL_RCC_GetClockConfig(&config,&latency);
|
||||
SystemCoreClock=HAL_RCC_GetSysClockFreq() >> AHBPrescTable[config.AHBCLKDivider >> RCC_CFGR_HPRE_Pos];
|
||||
}
|
||||
/* ISR только считает миллисекунды: протоколы и модели обслуживаются главным циклом. */
|
||||
void SysTick_Handler(void) { HAL_IncTick(); }
|
||||
void board_fatal(uint32_t code) { clock_fault=code; for (;;) {} }
|
||||
static void peripheral_clock(void)
|
||||
{
|
||||
RCC_OscInitTypeDef osc={0}; RCC_ClkInitTypeDef clk={0};
|
||||
RCC_PeriphCLKInitTypeDef peripheral={0}; RCC_CRSInitTypeDef crs={0};
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
/* Для 160 МГц нужен Range1 Boost; Flash 4 wait states по таблице ST. */
|
||||
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST)!=HAL_OK) board_fatal(3);
|
||||
osc.OscillatorType=RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSI48;
|
||||
osc.HSIState=RCC_HSI_ON; osc.HSICalibrationValue=RCC_HSICALIBRATION_DEFAULT;
|
||||
osc.HSI48State=RCC_HSI48_ON;
|
||||
osc.PLL.PLLState=RCC_PLL_ON; osc.PLL.PLLSource=RCC_PLLSOURCE_HSI;
|
||||
/* HSI16 / 4 * 80 / 2 = 160 МГц; PLLQ также 160 МГц, FDCAN берёт PCLK1. */
|
||||
osc.PLL.PLLM=RCC_PLLM_DIV4;
|
||||
#if EMU_USE_HSE
|
||||
#if EMU_HSE_HZ != 24000000u
|
||||
#error This board profile supports 24 MHz HSE or internal HSI16
|
||||
#endif
|
||||
osc.OscillatorType|=RCC_OSCILLATORTYPE_HSE;
|
||||
osc.HSEState=EMU_HSE_BYPASS ? RCC_HSE_BYPASS : RCC_HSE_ON;
|
||||
osc.PLL.PLLSource=RCC_PLLSOURCE_HSE; osc.PLL.PLLM=RCC_PLLM_DIV6;
|
||||
#endif
|
||||
osc.PLL.PLLN=80; osc.PLL.PLLP=RCC_PLLP_DIV2;
|
||||
osc.PLL.PLLQ=RCC_PLLQ_DIV2; osc.PLL.PLLR=RCC_PLLR_DIV2;
|
||||
if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3);
|
||||
clk.ClockType=RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
|
||||
clk.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK; clk.AHBCLKDivider=RCC_SYSCLK_DIV1;
|
||||
clk.APB1CLKDivider=RCC_HCLK_DIV2; clk.APB2CLKDivider=RCC_HCLK_DIV1;
|
||||
if (HAL_RCC_ClockConfig(&clk,FLASH_LATENCY_4)!=HAL_OK) board_fatal(3);
|
||||
/* USB не зависит от PLL/HSE. USART2 и FDCAN получают 80 МГц PCLK1. */
|
||||
peripheral.PeriphClockSelection=RCC_PERIPHCLK_USB|RCC_PERIPHCLK_FDCAN|RCC_PERIPHCLK_USART2;
|
||||
peripheral.UsbClockSelection=RCC_USBCLKSOURCE_HSI48;
|
||||
peripheral.FdcanClockSelection=RCC_FDCANCLKSOURCE_PCLK1;
|
||||
peripheral.Usart2ClockSelection=RCC_USART2CLKSOURCE_PCLK1;
|
||||
if (HAL_RCCEx_PeriphCLKConfig(&peripheral)!=HAL_OK) board_fatal(3);
|
||||
__HAL_RCC_CRS_CLK_ENABLE();
|
||||
crs.Prescaler=RCC_CRS_SYNC_DIV1; crs.Source=RCC_CRS_SYNC_SOURCE_USB;
|
||||
crs.Polarity=RCC_CRS_SYNC_POLARITY_RISING;
|
||||
crs.ReloadValue=__HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000u,1000u);
|
||||
crs.ErrorLimitValue=RCC_CRS_ERRORLIMIT_DEFAULT;
|
||||
crs.HSI48CalibrationValue=RCC_CRS_HSI48CALIBRATION_DEFAULT;
|
||||
HAL_RCCEx_CRSConfig(&crs); usb_clock_ready=1;
|
||||
}
|
||||
void board_init(void)
|
||||
{
|
||||
GPIO_InitTypeDef gpio={0};
|
||||
/* Этот образ связан с RB-загрузчиком: 128 КБ Flash, непрерывная карта DBANK=0. */
|
||||
__HAL_RCC_SYSCFG_CLK_ENABLE();
|
||||
if (*(const uint16_t *)FLASHSIZE_BASE!=128u ||
|
||||
(FLASH->OPTR&(FLASH_OPTR_DBANK|FLASH_OPTR_BFB2)) ||
|
||||
(SYSCFG->MEMRMP&SYSCFG_MEMRMP_FB_MODE)) board_fatal(5);
|
||||
peripheral_clock();
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE();
|
||||
__HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_FDCAN_CLK_ENABLE();
|
||||
/* ST-LINK VCP физически подключён к PA2/PA3; выбираем USART2 AF7, формат 8N1. */
|
||||
gpio.Pin=GPIO_PIN_2|GPIO_PIN_3; gpio.Mode=GPIO_MODE_AF_PP;
|
||||
gpio.Speed=GPIO_SPEED_FREQ_VERY_HIGH; gpio.Pull=GPIO_PULLUP;
|
||||
gpio.Alternate=GPIO_AF7_USART2; HAL_GPIO_Init(GPIOA,&gpio);
|
||||
gpio.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14;
|
||||
gpio.Mode=GPIO_MODE_INPUT; gpio.Pull=GPIO_PULLDOWN; HAL_GPIO_Init(GPIOB,&gpio);
|
||||
/* PC0..PC7 — восемь ответов IGBT. Устанавливаем нули до включения выходов. */
|
||||
gpio.Pin=0xff; gpio.Mode=GPIO_MODE_OUTPUT_PP; gpio.Pull=GPIO_NOPULL;
|
||||
HAL_GPIO_WritePin(GPIOC,0xff,GPIO_PIN_RESET); HAL_GPIO_Init(GPIOC,&gpio);
|
||||
#if EMU_RS485_DE
|
||||
gpio.Pin=GPIO_PIN_15; HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET);
|
||||
HAL_GPIO_Init(GPIOB,&gpio);
|
||||
#endif
|
||||
/* FDCAN1 Classic CAN: PB8 RX, PB9 TX. PA11/PA12 остаются для USB FS. */
|
||||
gpio.Pin=GPIO_PIN_8|GPIO_PIN_9; gpio.Mode=GPIO_MODE_AF_PP;
|
||||
gpio.Pull=GPIO_PULLUP; gpio.Alternate=GPIO_AF9_FDCAN1; HAL_GPIO_Init(GPIOB,&gpio);
|
||||
transport_init();
|
||||
}
|
||||
uint8_t board_usb_ready(void) { return usb_clock_ready; }
|
||||
/* Один шаг 1 мс. Бит n соответствует элементу input_pins[n], выход — PCn. */
|
||||
void board_tick(void)
|
||||
{
|
||||
uint8_t inputs=0,outputs; unsigned i;
|
||||
for(i=0;i<8;++i) if(HAL_GPIO_ReadPin(GPIOB,input_pins[i])==GPIO_PIN_SET) inputs|=(uint8_t)(1u<<i);
|
||||
igbt_emu_set_tx(inputs); emu_tick(); outputs=igbt_emu_get_rx();
|
||||
HAL_GPIO_WritePin(GPIOC,(uint16_t)(~outputs&0xffu),GPIO_PIN_RESET);
|
||||
HAL_GPIO_WritePin(GPIOC,outputs,GPIO_PIN_SET);
|
||||
}
|
||||
39
c/set-protocol/ports/stm32g474-periph/main.c
Normal file
39
c/set-protocol/ports/stm32g474-periph/main.c
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Точка входа прошивки. Здесь только порядок запуска и совместное обслуживание модулей.
|
||||
* Настройки оборудования находятся в board.c, обмен — в transport.c, генератор — в wave_hal.c.
|
||||
* Все модели и разбор команд выполняются в главном цикле; ISR обслуживают HAL и передачу байтов.
|
||||
*/
|
||||
#include "board.h"
|
||||
#include "transport.h"
|
||||
#include "emulator.h"
|
||||
#include "usb_cdc.h"
|
||||
#include "wave_output.h"
|
||||
#include "stm32g4xx_hal.h"
|
||||
|
||||
#if defined(EMU_USB_BOOT_ENABLE)
|
||||
#include "boot_port.h"
|
||||
#endif
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint32_t serviced;
|
||||
/* HAL запускает миллисекундный SysTick до настройки PLL: его счётчик нужен таймаутам HAL. */
|
||||
if (HAL_Init()!=HAL_OK) board_fatal(3);
|
||||
/* Сначала создаём модели и значения по умолчанию, затем настраиваем плату с этими параметрами. */
|
||||
emu_init(); board_init();
|
||||
/* Начинаем модельное время после настройки платы, не отрабатывая задержки её инициализации. */
|
||||
serviced=HAL_GetTick();
|
||||
/* USB использует отдельный HSI48 с CRS: внешний кварц для USB не нужен. */
|
||||
if (board_usb_ready()) usb_cdc_init();
|
||||
for (;;) {
|
||||
uint32_t now=HAL_GetTick();
|
||||
/* Один пропущенный миллисекундный шаг за проход. Между шагами всегда обслуживаем связь,
|
||||
* чтобы догоняющая обработка модели не вытесняла UART/CAN/USB. */
|
||||
if (serviced!=now) { ++serviced; board_tick(); }
|
||||
transport_uart_poll(now); transport_can_poll(now);
|
||||
usb_cdc_poll(now); wave_output_poll();
|
||||
#if defined(EMU_USB_BOOT_ENABLE)
|
||||
g474_reset_poll(now);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
163
c/set-protocol/ports/stm32g474-periph/transport.c
Normal file
163
c/set-protocol/ports/stm32g474-periph/transport.c
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
116
c/set-protocol/ports/stm32g474-periph/wave_hal.c
Normal file
116
c/set-protocol/ports/stm32g474-periph/wave_hal.c
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Аппаратный исполнитель генератора: таблица uint16_t в SRAM -> DMA1 -> DAC1/PA4.
|
||||
* TIM6 задаёт частоту выборок аппаратными событиями TRGO; CPU не отправляет каждый отсчёт.
|
||||
* Модуль владеет только DAC channel 1, TIM6 и DMA1 channel 1 и DMAMUX request DAC1_CHANNEL1.
|
||||
*/
|
||||
/* HAL backend owned by this board; the shared protocol remains hardware independent. */
|
||||
#include "wave_hal.h"
|
||||
#include "stm32g4xx_hal.h"
|
||||
|
||||
static DAC_HandleTypeDef dac;
|
||||
static DMA_HandleTypeDef dma;
|
||||
static TIM_HandleTypeDef timer;
|
||||
static uint8_t initialized;
|
||||
|
||||
/* Callback остановки set_wave_port. context не используется: имеется один статический комплект HAL handles.
|
||||
* Сначала останавливаем источник триггеров, затем DMA/DAC. После первого запуска держим PA4 на нулевом коде;
|
||||
* до инициализации функция ничего не делает. Второй канал DAC не перенастраивается. */
|
||||
static void stop(void *context)
|
||||
{
|
||||
(void)context;
|
||||
if (!initialized) return;
|
||||
(void)HAL_TIM_Base_Stop(&timer);
|
||||
(void)HAL_DAC_Stop_DMA(&dac,DAC_CHANNEL_1);
|
||||
/* Keep channel 1 driving zero after stop; channel 2 is untouched. */
|
||||
{
|
||||
DAC_ChannelConfTypeDef config={0};
|
||||
/* G4 при AHB=160 МГц требует HFSEL; выход направляем на внешний PA4. */
|
||||
config.DAC_HighFrequency=DAC_HIGH_FREQUENCY_INTERFACE_MODE_AUTOMATIC;
|
||||
config.DAC_ConnectOnChipPeripheral=DAC_CHIPCONNECT_EXTERNAL;
|
||||
config.DAC_UserTrimming=DAC_TRIMMING_FACTORY;
|
||||
config.DAC_Trigger=DAC_TRIGGER_NONE; config.DAC_OutputBuffer=DAC_OUTPUTBUFFER_ENABLE;
|
||||
(void)HAL_DAC_ConfigChannel(&dac,&config,DAC_CHANNEL_1);
|
||||
(void)HAL_DAC_SetValue(&dac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0);
|
||||
(void)HAL_DAC_Start(&dac,DAC_CHANNEL_1);
|
||||
}
|
||||
}
|
||||
/* Callback запуска: samples — таблица кодов DAC, count — число отсчётов, rate — выборок/с.
|
||||
* Таблица должна оставаться действительной и неизменной до stop(). Возвращает 0 при успехе, 1 при ошибке.
|
||||
* Проверки формы сигнала выполняет общий генератор; здесь проверяется аппаратная выполнимость. */
|
||||
static int start(void *context,const uint16_t *samples,size_t count,uint32_t rate)
|
||||
{
|
||||
GPIO_InitTypeDef gpio={0};
|
||||
DAC_ChannelConfTypeDef config={0};
|
||||
/* G4 при AHB=160 МГц требует HFSEL; выход направляем на внешний PA4. */
|
||||
config.DAC_HighFrequency=DAC_HIGH_FREQUENCY_INTERFACE_MODE_AUTOMATIC;
|
||||
config.DAC_ConnectOnChipPeripheral=DAC_CHIPCONNECT_EXTERNAL;
|
||||
config.DAC_UserTrimming=DAC_TRIMMING_FACTORY;
|
||||
TIM_MasterConfigTypeDef master={0};
|
||||
/* При делителе APB1=2 таймер получает удвоенную частоту PCLK1, то есть 160 МГц.
|
||||
* Эта формула соответствует дереву тактирования из board.c. */
|
||||
uint32_t ticks,psc,clock=HAL_RCC_GetPCLK1Freq()*2u;
|
||||
uintptr_t address=(uintptr_t)samples;
|
||||
(void)context;
|
||||
/* Разрешены 2..SET_WAVE_MAX отсчётов, частота до 1 МГц и только точное деление частоты таймера.
|
||||
* Для DMA используем SRAM1/2: буфер должен лежать в 0x20000000..0x20017FFF; CCM оставляем архиву. */
|
||||
if (!rate || rate>SET_WAVE_MAX_RATE || count<2 || count>SET_WAVE_MAX || clock%rate ||
|
||||
address<0x20000000u || address+count*2>0x20018000u) return 1;
|
||||
ticks=clock/rate;
|
||||
/* Подбираем целые (PSC+1) и (ARR+1), оба не больше 65536.
|
||||
* Частота выборок = timer_clock / ((PSC+1)*(ARR+1)); в регистры HAL передаёт значения минус один. */
|
||||
for (psc=1;psc<=65536;++psc) if (ticks%psc==0 && ticks/psc<=65536) break;
|
||||
if (psc>65536 || ticks<psc) return 1;
|
||||
stop(context);
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_DMA1_CLK_ENABLE();
|
||||
__HAL_RCC_DMAMUX1_CLK_ENABLE(); __HAL_RCC_DAC1_CLK_ENABLE(); __HAL_RCC_TIM6_CLK_ENABLE();
|
||||
gpio.Pin=GPIO_PIN_4; gpio.Mode=GPIO_MODE_ANALOG; gpio.Pull=GPIO_NOPULL;
|
||||
HAL_GPIO_Init(GPIOA,&gpio);
|
||||
timer.Instance=TIM6; timer.Init.Prescaler=psc-1; timer.Init.Period=ticks/psc-1;
|
||||
timer.Init.CounterMode=TIM_COUNTERMODE_UP; timer.Init.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_DISABLE;
|
||||
/* Remove TRGO before Base_Init generates its prescaler update event. */
|
||||
master.MasterOutputTrigger=TIM_TRGO_RESET;
|
||||
master.MasterSlaveMode=TIM_MASTERSLAVEMODE_DISABLE;
|
||||
if (HAL_TIMEx_MasterConfigSynchronization(&timer,&master)!=HAL_OK ||
|
||||
HAL_TIM_Base_Init(&timer)!=HAL_OK) return 1;
|
||||
__HAL_TIM_SET_COUNTER(&timer,0); __HAL_TIM_CLEAR_FLAG(&timer,TIM_FLAG_UPDATE);
|
||||
master.MasterOutputTrigger=TIM_TRGO_UPDATE;
|
||||
if (HAL_TIMEx_MasterConfigSynchronization(&timer,&master)!=HAL_OK) return 1;
|
||||
/* DMA пишет полуслова в фиксированный регистр DAC, последовательно читая таблицу.
|
||||
* Circular автоматически возвращает адрес к началу; высокий приоритет уменьшает риск underrun. */
|
||||
dma.Instance=DMA1_Channel1; dma.Init.Request=DMA_REQUEST_DAC1_CHANNEL1;
|
||||
dma.Init.Direction=DMA_MEMORY_TO_PERIPH; dma.Init.PeriphInc=DMA_PINC_DISABLE;
|
||||
dma.Init.MemInc=DMA_MINC_ENABLE; dma.Init.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD;
|
||||
dma.Init.MemDataAlignment=DMA_MDATAALIGN_HALFWORD; dma.Init.Mode=DMA_CIRCULAR;
|
||||
dma.Init.Priority=DMA_PRIORITY_HIGH;
|
||||
if (HAL_DMA_Init(&dma)!=HAL_OK) return 1;
|
||||
/* Связываем DAC с DMA handle: HAL использует эту связь при запуске и остановке передачи. */
|
||||
dac.Instance=DAC1; __HAL_LINKDMA(&dac,DMA_Handle1,dma);
|
||||
if (HAL_DAC_Init(&dac)!=HAL_OK) return 1;
|
||||
initialized=1;
|
||||
config.DAC_Trigger=DAC_TRIGGER_T6_TRGO; config.DAC_OutputBuffer=DAC_OUTPUTBUFFER_ENABLE;
|
||||
if (HAL_DAC_ConfigChannel(&dac,&config,DAC_CHANNEL_1)!=HAL_OK) goto fail;
|
||||
__HAL_DAC_CLEAR_FLAG(&dac,DAC_FLAG_DMAUDR1);
|
||||
/* DMA fills the next sample after each trigger, preserving cyclic phase. */
|
||||
/* Первым триггером выводится заранее записанный последний отсчёт; далее идут 0,1,... из DMA.
|
||||
* Таймер запускается последним, когда DAC и DMA уже готовы. Приведение к uint32_t* требуется API HAL,
|
||||
* но размер реальной передачи задаётся DMA как HALFWORD (16 бит). */
|
||||
if (HAL_DAC_SetValue(&dac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,samples[count-1])!=HAL_OK ||
|
||||
HAL_DAC_Start_DMA(&dac,DAC_CHANNEL_1,(uint32_t *)samples,(uint32_t)count,DAC_ALIGN_12B_R)!=HAL_OK ||
|
||||
HAL_TIM_Base_Start(&timer)!=HAL_OK) goto fail;
|
||||
return 0;
|
||||
/* Если запуск после инициализации DAC не завершился, возвращаем выход к нулю и сообщаем ошибку. */
|
||||
fail:
|
||||
stop(context); return 1;
|
||||
}
|
||||
/* Возвращает таблицу callback-функций для общего генератора; само оборудование ещё не запускается. */
|
||||
set_wave_port wave_hal_port(void)
|
||||
{
|
||||
set_wave_port port={0,start,stop}; return port;
|
||||
}
|
||||
/* Опрос ошибки канала DMA и underrun DAC. У G4 нет F4 stream FIFO/direct-mode flags.
|
||||
* NVIC-прерывания DMA/DAC здесь не включены: флаги проверяет основной цикл, остановку выполняет wave_output_poll(). */
|
||||
int wave_hal_fault(void)
|
||||
{
|
||||
return initialized && (__HAL_DMA_GET_FLAG(&dma,DMA_FLAG_TE1) ||
|
||||
__HAL_DAC_GET_FLAG(&dac,DAC_FLAG_DMAUDR1));
|
||||
}
|
||||
Reference in New Issue
Block a user