117 lines
8.0 KiB
C
117 lines
8.0 KiB
C
/**
|
||
* Аппаратный исполнитель генератора: таблица 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));
|
||
}
|