cancel
Showing results for 
Search instead for 
Did you mean: 

How to swap DMA/DAC buffer only when a circular dma/dac has completed - STM32H7A/B

Claydonkey
Senior

Edit: I've pretty much solved my problem.

The issue of course was that I was setting the Complete flag in the first cycle.

By changing:

if(DAC_Complete){

swap_buffer();

DAC_Complete=0;

}

to:

DAC_Complete=0;

while(DAC_Complete){

swap_buffer();

}

Things are more or less synchronised but now there is a wait period which is not wanted and also there still seems to be a little glitching.

So my new question is:

Is there an equivalent callback for the start of DAC like HAL_DAC_CH1_COMPLETE_CB_ID?

A sort of HAL_DAC_CH1_BEGIN_CB_ID

My intention is to swap a DMA buffer at the exact time a DMA Cyclical buffer ends writing it's sequence to the DAC. I want a seamless transition from writing the last DAC value out to the writing a new sequence (buffer) to the DAC... The buffer needs to be swapped at unpredictable times and has varying lengths.

In other words I want to commit the swap once the DAC has written the last value of n cycles and immediately after that.

viz the function - swap_buffer:

a. Executes at unpredictable times.

b. Changes the length and values of the DMA stream that is written to the DAC:

(stops the timer and DMA). Currently implemented in:

HAL_TIM_Base_Start(...) HAL_DAC_Stop_DMA (...)

before altering the DMA stream to DAC and restarting the timer

HAL_TIM_Base_Start (...) HAL_DAC_Start_DMA(...newvalues...)

c. The intention is to swap the DMA stream once it has finished writing it's last entry to the DAC. (reached the end of the cyclical buffer)

Currently DAC_REFRESH_PERIOD = 64U

Sys_ClockSpeed = 240Mhz

DAC_REFRESH_PRESCALER = 0

I have tried using:

uint16_t buffer[2] ={0};
uint8_t frame_id=0;
uint32_t buffer_len=0;
volatile uint8_t DAC_Complete  = 1;
 
void DAC1_Ch1_Complete(DAC_HandleTypeDef *def) {
   DAC_Complete = 1;
   HAL_GPIO_TogglePin(DEBUG_PIN_GPIO_Port, DEBUG_PIN_Pin);
}
 
 
 
int main(){
... //Nothing out of the ordinary
MX_DMA_Init();
MX_DAC1_Init();
MX_TIM1_Init();
...
HAL_DAC_RegisterCallback(&hdac1, HAL_DAC_CH1_COMPLETE_CB_ID, &DAC1_Ch1_Complete);
...
   while(1) {
...
//various functions with unpredicatable cycles...
...
buffer[frame_id] = ...; //buffer populated previously in code...
buffer_len= ...; //buffer size determinded previously in code...
  DAC_Complete=0;
    while(DAC_Complete){
         swap_buffer();
         DAC_Complete=0;
      }
 
/*   old version
if(DAC_Complete){
         swap_buffer();
         DAC_Complete=0;
      }
*/
   }
}
 
void swap_buffer()
{
frame_id = frame_id + 1 % 2;
HAL_TIM_Base_Stop(&htim1);
HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1);
HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*) buffer[frame_id], buffer_len, DAC_ALIGN_12B_R);
HAL_TIM_Base_Start(&htim1);
}
}

The stream consistently repeats until the swap_buffer function executes. This glitches the DAC pulse.

When observing the DAC output under an oscilloscope. the function seems to cut off a pending DMA cycle

 Current result...0693W00000WKGQNQA5.bmp 

Intended result:0693W00000WKGWQQA5.bmp Another Edit: (to remove the delay I placed the swap routine into the callback.. This presents other problems. Chiefly the main routine could request 2 swaps whilst the DMA is being

uint16_t buffer[2] ={0};
uint8_t frame_id=0;
uint32_t buffer_len=0;
volatile uint8_t swapbuffer= 0;
 
void DAC1_Ch1_Complete(DAC_HandleTypeDef *def) { 
   HAL_GPIO_TogglePin(DEBUG_PIN_GPIO_Port, DEBUG_PIN_Pin);
//new version :
   if (swapbuffer) {
      HAL_TIM_Base_Stop(&htim1);
      HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1);
      HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*) buffer[frame_id], buffer_len, 
      DAC_ALIGN_12B_R);
      HAL_TIM_Base_Start(&htim1);
   }
}
int main() {
 
... //Nothing out of the ordinary
 
MX_DMA_Init();
MX_DAC1_Init();
MX_TIM1_Init();
...
HAL_DAC_RegisterCallback(&hdac1, HAL_DAC_CH1_COMPLETE_CB_ID, &DAC1_Ch1_Complete);
...
   while(1) {
...
//various functions with unpredicatable cycles...
...
buffer[frame_id] = ...; //buffer populated previously in code...
buffer_len= ...; //buffer size determinded previously in code...
swap_buffer();
}
 
void swap_buffer()
{
   frame_id = frame_id + 1 % 2;
   swapbuffer=true;
   }
}

Additional configuration

void HAL_DAC_MspInit(DAC_HandleTypeDef* dacHandle)
{
 
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  HAL_DMA_MuxSyncConfigTypeDef pSyncConfig= {0};
  if(dacHandle->Instance==DAC1)
  {/* USER CODE END DAC1_MspInit 0 */
    /* DAC1 clock enable */
    __HAL_RCC_DAC1_CLK_ENABLE();
 
    /* DAC1 DMA Init */
    /* DAC1_CH1 Init */
    hdma_dac1_ch1.Instance = DMA1_Stream0;
    hdma_dac1_ch1.Init.Request = DMA_REQUEST_DAC1;
    hdma_dac1_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_dac1_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_dac1_ch1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_dac1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_dac1_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_dac1_ch1.Init.Mode = DMA_CIRCULAR;
    hdma_dac1_ch1.Init.Priority = DMA_PRIORITY_MEDIUM;
    hdma_dac1_ch1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_dac1_ch1) != HAL_OK)
    {
      Error_Handler();
    }
 
    pSyncConfig.SyncSignalID = HAL_DMAMUX1_SYNC_EXTI0;
    pSyncConfig.SyncPolarity = HAL_DMAMUX_SYNC_NO_EVENT;
    pSyncConfig.SyncEnable = DISABLE;
    pSyncConfig.EventEnable = ENABLE;
    pSyncConfig.RequestNumber = 1;
    if (HAL_DMAEx_ConfigMuxSync(&hdma_dac1_ch1, &pSyncConfig) != HAL_OK)
    {
      Error_Handler();
    }
 
    __HAL_LINKDMA(dacHandle,DMA_Handle1,hdma_dac1_ch1);
 
  
 
    /* DAC1 interrupt Init */
    HAL_NVIC_SetPriority(TIM1_DAC_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(TIM1_DAC_IRQn);
  /* USER CODE BEGIN DAC1_MspInit 1 */
 
  /* USER CODE END DAC1_MspInit 1 */
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
...
if(tim_baseHandle->Instance==TIM1)
  {
  /* USER CODE BEGIN TIM1_MspInit 0 */
 
  /* USER CODE END TIM1_MspInit 0 */
    /* TIM1 clock enable */
    __HAL_RCC_TIM1_CLK_ENABLE();
 
    /* TIM1 interrupt Init */
    HAL_NVIC_SetPriority(TIM1_DAC_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(TIM1_DAC_IRQn);
  /* USER CODE BEGIN TIM1_MspInit 1 */
 
  /* USER CODE END TIM1_MspInit 1 */
  }
}

/* TIM1 init function */
void MX_TIM1_Init(void)
{
 
  /* USER CODE BEGIN TIM1_Init 0 */
 
  /* USER CODE END TIM1_Init 0 */
 
  TIM_MasterConfigTypeDef sMasterConfig = {0};
 
  /* USER CODE BEGIN TIM1_Init 1 */
 
  /* USER CODE END TIM1_Init 1 */
  htim6.Instance = TIM1;
  htim6.Init.Prescaler = 0;
  htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim6.Init.Period = DAC1_REFRESH_PERIOD;
  htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */
 
  /* USER CODE END TIM1_Init 2 */
 
}
/* DAC1 init function */
void MX_DAC1_Init(void)
{
 
  /* USER CODE BEGIN DAC1_Init 0 */
 
  /* USER CODE END DAC1_Init 0 */
 
  DAC_ChannelConfTypeDef sConfig = {0};
 
  /* USER CODE BEGIN DAC1_Init 1 */
 
  /* USER CODE END DAC1_Init 1 */
 
  /** DAC Initialization
  */
  hdac1.Instance = DAC1;
  if (HAL_DAC_Init(&hdac1) != HAL_OK)
  {
    Error_Handler();
  }
 
  /** DAC channel OUT1 config
  */
  sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;
  sConfig.DAC_Trigger = DAC_TRIGGER_T1_TRGO;
  sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;
  sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_ENABLE;
  sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
  if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
 
  /* USER CODE BEGIN DAC1_Init 2 */
 
  /* USER CODE END DAC1_Init 2 */
 
}

9 REPLIES 9
S.Ma
Principal

Which STM32? There are multiple generation of DMAs.... in cyclic mode, DMA usually doesn't generate interrupts. It can help to know more the intended expected result rather than describing the tried scenarii....

Yeah sorry I tried to keep it brief. It's the STM32H7B0 or any STM32H7A/B. I'll edit my post

AScha.3
Principal III

>My intention is to swap a DMA buffer ...

just - could you say, what you wanna do , what is the target, what you want to achieve overall?

If you feel a post has answered your question, please click "Accept as Solution".
S.Ma
Principal

Your need seems to be similar to the old cd player, how to avoid glsound glitch if the player is bumped. Most DMA can split the pointed memory data block in 2 parts. It is sometime called half transfer interrupt. Basically while the dma sweep through one half, you update the other one that is not, and swap at half xor full transfer interrupts.

Thanks for the advice. Unfortunately the buffer size is variable and to complicate matters further the buffer cycles. If I were to introduce half the buffer into the last cycle I would still need to stop and start the DAC/DMA to change the buffer length...:(

I do admit though that the glitching is now probably purely down to this resetting of the DAC/DMA. It's pretty quick and barely noticeable for my needs (it a visual application and persistence of vision and our eyes slow refresh rates make it almost ideal so far...). I think the biggest problem is the delay waiting for the last cycle to end so that the buffer can be swapped...

I think I've improved it a tad by putting the swap routine into the Callback and setting a swap flag in the main routine that notifies the Callback to swap the buffer...

Piranha
Chief II

You should repeat the basic rules of math... This code doesn't do what you think it does and is terribly broken:

frame_id = frame_id + 1 % 2;

Anyway, the logic, you intended, can be done more elegantly:

bool frame_id;
frame_id = !frame_id;

And global variables are by default initialized to zero values.

Yes you are correct - I typed this out as I wrote the question, it is not existing code more pseudo code. There are brackets missing .. frame_id = (frame_id + 1) % 2; The reasoning as to why I wrote this:

In other areas of the program there are triple and even quadruple buffers - this expression is more scalable. i.e frame_id = (frame_id + 1) % FRAME_COUNT

_FAST_DVG_PROC_ void get_next_frame() {
	m_coords.frame_id = (m_coords.frame_id + 1) % DVG_MAX_FRAMES;
}

and as for the global values. I always initialise values. I prefer to be explicit... In addition I have been bitten before with other non GCC compilers...

TDK
Guru

You can't change the length of the DMA buffer without disabling and re-enabling the DMA. Doing so will have the possibility of missing values as you cannot respond instantly to the DMA completion event. You can get close, maybe a few us, but not instant.

One way of driving the DAC with varying inputs is to generate them on the fly in a circular buffer or with interrupts, although this will not be computationally feasible at the highest DAC update rates.

If you feel a post has answered your question, please click "Accept as Solution".
Claydonkey
Senior

Thanks TDK, That's exactly as I'm observing. I have am also using the onchip OPamp as output - what I have done to mitigate the delay affecting the output gltich is to disable the opamp also and set the output to digital set low and to zero before returning to analog and opamp output. This removes some of the glitching...

_FAST_DVG_PROC_ void off_dvg_buffer() {
 
#if RESET_OPAMPS
	HAL_OPAMP_Stop(&hopamp1);
#endif
	HAL_TIM_Base_Stop(&htim1);
 
	HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1);
#if RESET_DAC_PIN
	GPIO_InitTypeDef GPIO_InitStruct = { 0 };
	GPIO_InitStruct.Pin = GPIO_PIN_4;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7, GPIO_PIN_SET);
#endif
}
 
_FAST_DVG_PROC_ void on_dvg_buffer(uint8_t frame_id, uint32_t cnt) {
	if (get_ui_flag(UI_DOUBLE_BUFFER)) 
		m_coords.dac_x_pnt = m_coords.dac.buf_double[1][frame_id];
	 else 
		m_coords.dac_x_pnt = m_coords.dac.buf_single[1][frame_id];
 
	HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*) m_coords.dac_x_pnt, cnt, DAC_ALIGN_12B_R);
 
 
#if RESET_OPAMPS
	HAL_OPAMP_Start(&hopamp1);
#endif
#if RESET_DAC_PINS
	GPIO_InitTypeDef GPIO_InitStruct = { 0 };
	GPIO_InitStruct.Pin = GPIO_PIN_7;
	GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
 
#endif
	HAL_TIM_Base_Start(&htim1);
}
 
_FAST_DVG_PROC_ void commit_dvg_frame() {
 
	if (m_coords.idx != 0) {
		m_coords.frame_cnt = m_coords.pidx = m_coords.idx;
	
	if (!get_ui_flag(UI_DOUBLE_BUFFER))
			dvg_buffer_clear_end();
 
 
		if (get_ui_flag(UI_DOUBLE_BUFFER)) {
			swap_dvg_buff(m_coords.frame_id, m_coords.frame_cnt);
			get_next_frame();
 
		}	
                reset_dvg_buffer();
 
		if (get_ui_flag(UI_DOUBLE_BUFFER))
			dvg_buffer_clear();
	}
}

 As you could infer from this snippet, I have a fork in the routine which allows for a singel buffer. This works seamlessly without any glitching and does not require any workarounds but the empty (0) values on the DAC are another problem ...