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

View 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.

View 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);
}

View 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
}
}

View 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);
}
}
}
}

View 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));
}