Author Topic: 32F417 - a cunning way to reset two DMA pointers from an edge on a pin, etc?  (Read 4456 times)

0 Members and 1 Guest are viewing this topic.

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
No luck after a few hours... is there some way to debug this counter capture stuff? For example the 400Hz is supposed to be arriving on TIM1_CH1. Is there anything one could read to see if there is anything coming through?
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
A lot of mucking about, I find that setting CC1G=1 (which forces a capture) does force a capture about 200ns after reading the counter

Code: [Select]
while (1)
{

uint16_t cnt = TIM1->CNT;
TIM1->EGR = (1<<1);  // CC1G=1; gets reset to 0 by hardware
uint16_t ccr1 = TIM1->CCR1;
debug_thread_printf("===== TIM1 = %5u %5u", cnt, ccr1);

osDelay(1000);
}

Code: [Select]
===== TIM1 = 16491 16493
===== TIM1 = 58404 58406
===== TIM1 = 34750 34753
===== TIM1 = 11067 11069
===== TIM1 = 52958 52961
===== TIM1 = 29275 29277

So it looks like TI1 is not coming through to IC1PS



and this tells you how to do it, but there must be an extra step



I now wonder if my AF1 config on PE9 is connecting PE9 to TIM1_CH1 input or TIM1_CH1 output. Nothing online, nothing in the RM. I would expect this to be done implicitly i.e. if PE9 is an input (as in my config) then PE9 connects to TIM1_CH1 input. I see no config for this elsewhere.
« Last Edit: November 21, 2021, 06:38:10 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
As I suspected, the PE9 config was indeed wrong. It has to be AF in two places

Code: [Select]
GPIO_InitStruct.Pin  = GPIO_PIN_9;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM1; // PE9 = TIM1_CH1
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // PE9 = AF, and is an input because CH1=input in TIM1 cfg
GPIO_InitStruct.Pull = GPIO_PULLUP; // PE9 = pullup because comparator driving it is open drain o/p
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

However, I don't think there is any mode which resets CNT after transferring it to CCR, while maintaining a continuously updated value of the PE9 waveform duration in CCR. It this appears that you need either an ISR, or a DMA event to copy CCR (or CNT, before it gets zeroed?) to RAM.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Finally, I got there, with the help of a guy on the ST forum:

Code: [Select]

/*
 *
 * Set up TIM1 for ~400Hz waveform period measurement, via PE9
 *  This produces a continuously updated value in TIM1->CCR1. This example code displays it:
 * while (1)
 * {
 * uint16_t ccr1 = TIM1->CCR1;
 * debug_thread_printf("CCR1 = %5u", ccr1);
 * osDelay(1000);
 * }
 * TIM1 is clocked at 2xPCLK2 i.e. 84MHz here. The /8 prescaler drops this to 10.5MHz
 * which is good for 400Hz and delivers a value around 26240.
 *
 */

static void KDE_config_TIM1_period_measure(void)
{
GPIO_InitTypeDef  GPIO_InitStruct;

__GPIOE_CLK_ENABLE(); // already enabled in switch init but never mind
GPIO_InitStruct.Pin  = GPIO_PIN_9;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM1; // PE9 = TIM1_CH1
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // PE9 = AF, and is an input because CH1=input in TIM1 cfg
GPIO_InitStruct.Pull = GPIO_PULLUP; // PE9 = pullup because comparator driving it is open drain o/p
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

// Set up TIM1 to count up and on PE9 +ve edge transfer the count to CCR and reload with 0
__HAL_RCC_TIM1_CLK_ENABLE(); // TIM1EN=1 in RCC2ENR and does a short delay

TIM1->CR1 = 0; // TIM1 upcounter, counter disabled for now with CEN=0

TIM1->ARR = 0xffff; // auto reload value

TIM1->PSC = 7; // prescaler=8, for 10.5MHz counter speed

TIM1->CCMR1 = (1 << 4) // IC1F: input filter = 2
|(0 << 2) // IC1PSC: input prescaler off
|(1 << 0); // CC1S: 01 = channel is input, from TI1

TIM1->CCER = (0 << 3) // CC1NP: CC1 rising edge capture
    |(0 << 2) // CC1NE: oc1n not active
|(0 << 1) // CC1P: rising edge capture
|(1 << 0); // CC1E: enable CC1 capture

TIM1->EGR = 0; // CC1G=0

TIM1->CNT = 0;
TIM1->CR2 = 0; // TI1S=0 - TIM1_CH1 pin is connected to TI1 input
TIM1->RCR = 0;
TIM1->SMCR = (1 << 2) // internal clock source, SMS=100 (reset mode)
|(1 << 6) // TS=101 (TI1FP1)
|(1 << 4);
TIM1->DIER = 0; // interrupts disabled

TIM1->CR1 = (1 << 0); // CEN=1 - enable TIM1

}

I would not have worked that out in 100 years, from the RM.
« Last Edit: November 21, 2021, 11:38:56 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
And this piece of code gives you a simple frequency display :)

Code: [Select]
while (1)
{
uint32_t pclk2 = HAL_RCC_GetPCLK2Freq();
uint16_t ccr1 = TIM1->CCR1;
float freq = 1.0/(ccr1/((pclk2*2)/32.0)); // 32=prescaler in the above config; changed from 8 to 32 so that 50Hz can be measured without overflow
printf("TIM1 = %5u %6.1f", ccr1, freq);
osDelay(1000);
}

Now I need to find a way to reset the two pointers - the original Q in this thread. One could have an ISR from the event transferring CNT to CCR1 but that is vulnerable to noise on the PE9 pin, which would hang up the whole product. So I am thinking of feeding the reference waveform (the PE9 signal) and a squared-up output of one of the DACs into a XOR gate, which will generate a PWM signal with a 1 when the two are out of phase, and feed a LP filtered version into an ADC. And shift the waveform table up/down (or change the DMA pointer) to minimise this voltage. That would also take care of any phase lag due to the DAC output filters.

The only challenge for the control loop is the phase reversal. I need to control for the maximum value, so the algorithm will need to move forwards and backwards around that peak. Maybe there is a cunning way to produce 50% of Vref when the two are in phase.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 495
  • Country: sk
I don't get your problem. The mcu is perfectly capable of calculating time difference between an external sync signal and a signal it generates itself, without any external hardware.

JW
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
OK; one could read TIM1 CNT, which will give me the time since the last zero crossing (on which CNT got zeroed). That's an excellent point.

I need to adjust for the phase delay in the lowpass filter after the DAC(s), but that can be done by arranging the filter delay to be equal to say 1 degree (about right actually) and if I generate the waveform with 360 samples, and shift the sampling by 1, that will do that just right.

Reading about the DMA channels, I can't see any reason why the TIM1 event which transfers CNT to CCR1 can't also generate a single memory-peripheral DMA transfer which picks up a value from RAM (which is the start address of the circular buffer discussed earlier) and loads it (as the RAM buffer start address) into the DMA controller which feeds the two DACs. There will be only one DMA feeding the DACs because both can be fed together, with 32 bits. The main issue was discussed earlier: the DMA address registers are write-protected during transfers; the double buffer mode doesn't help because you can write only the "other" buffer's address. One could do 3 writes: one to disable the DMA channel, next to write the (reset) pointer value, and the next to re-enable the DMA. But reading about this, this is not likely to work because there need to be delays, waiting for the DMA transfer to finish.
« Last Edit: November 22, 2021, 04:45:56 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Having got the wave gen working nicely (DMA in double buffer mode, etc) I think the only viable way to reset the DMA buffer pointers (2) to the start of the buffers at the input waveform zero crossing is an ISR, enabled with CC1IE=1 in TIM1_DIER. That should produce an interrupt at the zero crossing.

I have played around with reading TIM1 CNT (which starts at zero at the input waveform zero crossing, and one of these

- poll CNT and if close to zero, reset the DMA pointers and set the byte count to the buf size
- poll CNT and use the value to calculate corresponding DMA pointer values and a corresponding buf size

The 1st method works but suffers from synchronicity issues. For example, at 400Hz, TIM1 CNT goes from 0 to ~6562 and I was looking at it being 0 to 65 which would give a good enough sync. But it is easy to miss this time slot if the loop is running at just the wrong speed, and it does need to be done quite often, say 10Hz, because the resolution of the frequency measurement (1/6500) together with the resolution of the wave gen (1/580, with 360 samples per cycle) when expressed in integers, produces a sufficient phase drift even during 1 second.

The 2nd method works but is really tacky, and you have to make sure that the DMA pointers correspond with the remaining byte count, otherwise you will lose some table values (the DMA circular mode uses byte count=0 to reset the pointers, not a comparison of a pointer with the last buffer address).

That's unless somebody has a great idea :)

One can improve the phase drift by using bigger integers, obviously, and with 36 samples per cycle the accuracy will be 10x better, but then I need a DAC filter which will produce a significant phase shift.

I am trying to work out how to set up the interrupt. The project already has TIM6 generating a 1kHz interrupt but the code is barely penetrable. There is a huge int handler in the HAL which steps through various interrupt sources and is obviously sub-optimal. I am not sure how the 32F4 interrupts are cleared; I have been doing embedded for 40+ years and normally you have to read a specific reg or toggle some bit to clear the interrupt; the 32F4 doesn't seem to need that.

UPDATE: I worked out how to get the interrupt to work, largely from this: https://www.eng.auburn.edu/~nelson/courses/elec2220/slides/timers1.pdf.

Took me ages to find that the CH1 capture event produces an interrupt via TIM1_CC_IRQHandler. Can't find any doc on this so I tried every "TIM1" vector until it hit the debugger.

Next thing was the stupid HAL handler. WTF is this total POS:

Code: [Select]
    This section provides Timer IRQ handler function.

@endverbatim
  * @{
  */
/**
  * @brief  This function handles TIM interrupts requests.
  * @param  htim TIM  handle
  * @retval None
  */
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
{
  /* Capture compare 1 event */
  if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET)
  {
    if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) != RESET)
    {
      {
        __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
        htim->Channel = HAL_TIM_ACTIVE_CHANNEL_1;

        /* Input capture event */
        if ((htim->Instance->CCMR1 & TIM_CCMR1_CC1S) != 0x00U)
        {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
          htim->IC_CaptureCallback(htim);
#else
          HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
        }
        /* Output compare event */
        else
        {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
          htim->OC_DelayElapsedCallback(htim);
          htim->PWM_PulseFinishedCallback(htim);
#else
          HAL_TIM_OC_DelayElapsedCallback(htim);
          HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
        }
        htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
      }
    }
  }
  /* Capture compare 2 event */
  if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC2) != RESET)
  {
    if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC2) != RESET)
    {
      __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC2);
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_2;
      /* Input capture event */
      if ((htim->Instance->CCMR1 & TIM_CCMR1_CC2S) != 0x00U)
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->IC_CaptureCallback(htim);
#else
        HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      /* Output compare event */
      else
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->OC_DelayElapsedCallback(htim);
        htim->PWM_PulseFinishedCallback(htim);
#else
        HAL_TIM_OC_DelayElapsedCallback(htim);
        HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
    }
  }
  /* Capture compare 3 event */
  if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC3) != RESET)
  {
    if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC3) != RESET)
    {
      __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC3);
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_3;
      /* Input capture event */
      if ((htim->Instance->CCMR2 & TIM_CCMR2_CC3S) != 0x00U)
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->IC_CaptureCallback(htim);
#else
        HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      /* Output compare event */
      else
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->OC_DelayElapsedCallback(htim);
        htim->PWM_PulseFinishedCallback(htim);
#else
        HAL_TIM_OC_DelayElapsedCallback(htim);
        HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
    }
  }
  /* Capture compare 4 event */
  if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC4) != RESET)
  {
    if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC4) != RESET)
    {
      __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC4);
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_4;
      /* Input capture event */
      if ((htim->Instance->CCMR2 & TIM_CCMR2_CC4S) != 0x00U)
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->IC_CaptureCallback(htim);
#else
        HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      /* Output compare event */
      else
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->OC_DelayElapsedCallback(htim);
        htim->PWM_PulseFinishedCallback(htim);
#else
        HAL_TIM_OC_DelayElapsedCallback(htim);
        HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
    }
  }
  /* TIM Update event */
  if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET)
  {
    if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) != RESET)
    {
      __HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->PeriodElapsedCallback(htim);
#else
      HAL_TIM_PeriodElapsedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
    }
  }
  /* TIM Break input event */
  if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_BREAK) != RESET)
  {
    if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_BREAK) != RESET)
    {
      __HAL_TIM_CLEAR_IT(htim, TIM_IT_BREAK);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->BreakCallback(htim);
#else
      HAL_TIMEx_BreakCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
    }
  }
  /* TIM Trigger detection event */
  if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_TRIGGER) != RESET)
  {
    if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_TRIGGER) != RESET)
    {
      __HAL_TIM_CLEAR_IT(htim, TIM_IT_TRIGGER);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->TriggerCallback(htim);
#else
      HAL_TIM_TriggerCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
    }
  }
  /* TIM commutation event */
  if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_COM) != RESET)
  {
    if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_COM) != RESET)
    {
      __HAL_TIM_CLEAR_IT(htim, TIM_FLAG_COM);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->CommutationCallback(htim);
#else
      HAL_TIMEx_CommutCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
    }
  }
}


Surely, an int vector should just go to the ISR, which then clears the int source, and does whatever it needs doing? The above code seems to clear the source of the interrupt though... not sure without spending another hour following it around.



« Last Edit: November 27, 2021, 10:36:04 am by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 495
  • Country: sk
Took me ages to find that the CH1 capture event produces an interrupt via TIM1_CC_IRQHandler. Can't find any doc on this
* Clipboard01.png (142.51 kB. 684x968 - viewed 84 times.)" alt="" class="bbc_img" />
https://github.com/STMicroelectronics/STM32CubeF4/blob/4aba24d78fef03d797a82b258f37dbc84728bbb5/Drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc/startup_stm32f417xx.s#L174

Quote
Surely, an int vector should just go to the ISR, which then clears the int source, and does whatever it needs doing? The above code seems to clear the source of the interrupt though... not sure without spending another hour following it around.

You don't need to use the Cube/HAL ISR handler, simply write your own.

The interrupt source signal in this particular case is cleared by writing 0 into the apropriate bit in TIMx_SR. Don't use RMW to do this.

JW
« Last Edit: November 27, 2021, 01:15:37 pm by wek »
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Thanks, wek. This is my working ISR. It resets the DMA pointers and byte count, and resets TIM4 (which drives the DACs). Without TIM4 resetting, there was a lot of jitter, unsurprisingly.

Code: [Select]
/*
* TIM1 input compare event interrupt handler. Used by waveform period measurement function.
* Process TIM1 CC1IE (CH1 capture) interrupt, generated by CH1 capture of CNT into CCR1, which
* is generated from a +ve edge on PE9.
* There is a problem with jitter in the first two samples after the TIM1 interrupt occurs, which
* is probably related to the zeroing of TIM4 count which is itself necessary to get the subsequent
* output to have the first sample output with the proper constant width.
* Much time was spent with the order of the code but the issue remains unresolved. Since it is
* only on 1st two samples, having a higher num_values (say 128+) makes it insignificant. At 64
* it is quite visible though.
*
*/

void TIM1_IC_CaptureISR(void)
{

TIM4->CR1 = (1u << 7); // disable TIM4 (keeping existing cfg & avoiding RMW)
TIM4->PSC=isr_tim_pre; // Reset TIM4 counter chain, to avoid jitter on output
TIM4->CNT=0;
TIM4->CR1 = 1 | (1u << 7); // re-enable TIM4

TIM1->SR = 0; // Clear CC1IF & UIF - clear pending interrupt

if (cycle_count-- == 0)
{
cycle_count = RECAL_COUNT;

// Update period of wave gen derived from input reference (period=TIM1->CCR1)
// This doesn't need doing often; it is only to track variations in the input frequency.
// The full formula if pclk1 != pclk2 is wave_tc = (pclk1*2L*(ccr1/((pclk2*2)/32)))/SPC
// The simpler one if pclk1=pclk2 is (ccr1*32)/SPC
// 32 is the prescaler in the measuring code (chosen at 32 to support 50Hz)

TIM4->ARR = (( (TIM1->CCR1-1) * 32 )/isr_num_values);
}

dma_s5_copy &= ~1; // disable DMA (to access its cfg regs) & avoiding RMW
DMA1_Stream5->CR = dma_s5_copy;

DMA1->HISR = 0x0F40; // 1111 0100 0000 - clear pending int flags (per RM)

hang_around_us(1); // wait for DMA to stop (delay not actually needed)

DMA1_Stream5->NDTR = isr_num_values; // buffer size
DMA1_Stream5->M0AR = isr_buffer0; // base of table 0
DMA1_Stream5->M1AR = isr_buffer1; // base of table 1

dma_s5_copy |= 1; // re-enable DMA
DMA1_Stream5->CR = dma_s5_copy;

}

In fact my whole project is now working. Interesting about RMW. This is like the 1980s chips where one kept a copy in RAM, modified that, and wrote it in :)

However I have an interesting issue. There is still jitter in the DAC output, after the above ISR runs. I have tried all sorts of stuff. It doesn't look like a delay in the DMA resetting because (on a 64 sample per cycle setup) only the first 1 or 2 are weird and the rest of the sinewave cycle is spot on.



It is as if the DACs (both show the same) have a FIFO which is messing around. I see the DMA has a FIFO too but I see the reset value of DMA1_S5FCR = 0x21 which selects the direct mode. The jitter is purely vertical (the timing is spot on) as if the table values were being manipulated, but they aren't being touched. It could be the DMA is pointing at a previous sample.

So I thought how about enabling the DMA FIFO, but DMA1->S5FCR does not exist in Cube for the 32F417. Variations of it are found on google as usual, and the Cube .h files contain the bit definitions e.g. #define DMA_SxFCR_FS_0 but none of them work.

BTW each of the steps in the above image is 39us so the ISR is long done by the time the jitter is visible.

I can make the problem negligible by having a lot more samples per cycle e.g. 256 but that brings other perf issues.

The appearance is that the DMA takes a while to get going which probably means the TIM4 trigger to the DAC takes a while to get going.

EDIT: ISR source updated. Jitter is generally better but remains, and the cause was not found.

EDIT2: after much experimentation it was found that loading CNT (in the ISR, to try to zero TIM4) with a value between around 1/10 of the ARR load and the ARR load, removes the artefacts. If it is 1 count above ARR it breaks. The upper limit is probably fairly obvious that it would break it but the lower limit is just weird. Obviously this solution should not work but equally obviously CNT is getting zeroed, over that entire range of values loaded into it.
« Last Edit: November 27, 2021, 11:34:01 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 495
  • Country: sk
I'm not going to try to understand your program from the published fragment and interpret the picture, just comment on general STM32 facts.

> It is as if the DACs (both show the same) have a FIFO which is messing around.

They do, if a 1-deep FIFO counts. There's a holding register and an output register, and reason for their existence is the triggering mechanism you are using. This is described in the DAC chapter in RM, read it.

> DMA1->S5FCR does not exist in Cube for the 32F417

The FIFO control register is a stream-related register; so in the same manner as all other stream-related registers, e.g. DMA1_Stream5->NDTR, the syntax here is DMA1_Stream5->FCR. See typedef of DMA_Stream_TypeDef in stm32f417xx.h and its usage.

> obviously CNT is getting zeroed, over that entire range of values loaded into it.

CNT is not getting zeroed, if you assign a nonzero value to it, that value is loaded into it and it will count starting at that value.

JW
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Thanks for the DMA1_Stream5->FCR tip. It made no difference however.

Yes, loading a nonzero value into CNT does actually load that nonzero value into CNT :)

How would you zero TIM4? I find this is working

Code: [Select]
TIM4->CR1 = (1u << 7); // disable TIM4 (keeping existing cfg & avoiding RMW)
// Reset TIM4 prescaler+counter chain, to avoid 1-sample-width jitter on output
TIM4->PSC = isr_tim_pre;
// This should obviously be 0 but that gives you jitter. A value > ~ARR/20 but smaller than ARR
// avoids this but produces a phase shift in the output. Works 45Hz to 1.3kHz.
TIM4->CNT = isr_tim_tc/16L;
TIM4->CR1 = 1 | (1u << 7); // re-enable TIM4

I also posted this Q here, with more detail
https://community.st.com/s/question/0D53W00001DlhkcSAB/weird-jitter-in-tim4-dacs-dma1-waveform-generator-32f417
because there are others there who might throw some light on it.
« Last Edit: November 28, 2021, 09:21:37 am by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 495
  • Country: sk
OK so I spent time to make the analysis.

First, the 2-TIM4-cycle lag: as I've told you above, there *is* a 1-stage FIFO in the DAC, that's one cycle. The other comes from the fact, that if you zero the TIM4 CNT "manually", there is no Update event thus no TRGO to move data from holding to output register in DAC. You could zero it by setting TIMx_EGR.UG which would generate Update simultaneously; this would reduce the lag to one cycle but won't help with the jitter anyway. (The lag, whatever value if constant, is easy to remove by simply rotating the sine table).

The jitter boils down to the fact that the SYNC signal (TIM1 CC1 interrupt) is asynchronous to the mcu clock, together with combination of resetting the timer and DMA. Due to asynchronicity, the SYNC signal is not an exact multiple of the calculated TIM4 period - that includes also error of period measurement, truncation when dividing it into 64 TIM4 periods, and lag of actual TIM4 restart after detecting the SYNC edge. After a full SYNC period elapses, are two cases: the SYNC edge arriving just *after* the TIM4 period elapsed (A), or just *before* it (B).

In case of (A), TIM4->CNT is cleared and DMA reset just after Update has been generated, so there is no effective difference.

In case of (B), TIM4->CNT is cleared before Update has been generated, so there will be no Update before the full TIM4 period elapses, that's why the old value remains in the DAC output/holding registers; and the next Update shifts the old value from holding to output register, that's why the "jitter" lasts 2 TIM4 cycles.

I am not going to make recommendations towards fixing this.

JW
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Thank you.

EGR UG is interesting indeed and if I put it at the end of the ISR, after DMA pointers have been zeroed

TIM4->EGR=1;

it does indeed shorten the delay by 1 sample! I still have to use the CNT = ARR/8 bodge to remove the jitter.

The ST forum thread I linked above has disappeared... but this image shows the 2-sample delay



My ISR is now

Code: [Select]
void TIM1_IC_CaptureISR(void)
{

//TIM4->CR1 = (1u << 7); // disable TIM4 (keeping existing cfg & avoiding RMW)
// Reset TIM4 prescaler+counter chain, to avoid 1-sample-width jitter on output
TIM4->PSC = isr_tim_pre;
// This should obviously be 0 but that gives you jitter. A value > ~ARR/40 but smaller than ARR
// avoids this but produces a phase shift in the output. Works 45Hz to 1.3kHz.
TIM4->CNT = isr_tim_tc/8L;
//TIM4->EGR=1;
//TIM4->CR1 = 1 | (1u << 7); // re-enable TIM4

TIM1->SR = 0; // Clear CC1IF & UIF - clear pending interrupt

if (cycle_count-- == 0)
{
cycle_count = RECAL_COUNT;

// Update period of wave gen derived from input reference (period=TIM1->CCR1)
// This doesn't need doing often; it is only to track variations in the input frequency.
// The full formula if pclk1 != pclk2 is wave_tc = (pclk1*2L*(ccr1/((pclk2*2)/32)))/SPC
// The simpler one if pclk1=pclk2 is (ccr1*32)/SPC
// 32 is the prescaler in the measuring code (chosen at 32 to support 50Hz)

TIM4->ARR = (( (TIM1->CCR1-1) * 32 )/isr_num_values);
}

dma_s5_copy &= ~1; // disable DMA (to access its cfg regs) & avoiding RMW
DMA1_Stream5->CR = dma_s5_copy;

DMA1->HISR = 0x0F40; // 1111 0100 0000 - clear pending int flags (per RM)

hang_around_us(1); // wait for DMA to stop (delay not actually needed)

DMA1_Stream5->NDTR = isr_num_values; // buffer size
DMA1_Stream5->M0AR = isr_buffer0; // base of table 0
DMA1_Stream5->M1AR = isr_buffer1; // base of table 1

dma_s5_copy |= 1; // re-enable DMA
DMA1_Stream5->CR = dma_s5_copy;

TIM4->EGR=1;

}

and I am happy with this; I just need to generate the sin(s) table 2 values back.
« Last Edit: November 28, 2021, 11:46:13 am by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Unfortunately the EGR method, while removing 1 of the 2 sample delays, reintroduces the jitter issue. I may do more work on it but this is my currently jitter-free ISR which uses the CNT = "something nonzero" hack. I suspect that hack removes the jitter by moving the internal TIM4 operations away from when the ISR is happening.

Code: [Select]
void TIM1_IC_CaptureISR(void)
{

if ( (TIM4->CR1 && ~1) == 1 )  // check wavegen (basically TIM4) is enabled!
{

// Reset TIM4 prescaler+counter chain, to avoid 1-sample-width jitter on output

TIM4->PSC = isr_tim_pre;
// This should obviously be 0 but that produces jitter. This value was determined experimentally
// Works 42Hz (the KDE_config_TIM1_period_measure lower limit) to >1kHz.
TIM4->CNT = isr_tim_tc/8L;

// Update period of wave gen derived from input reference (period=TIM1->CCR1)
// This doesn't need doing often; it is only to track variations in the input frequency but
// here we do it every cycle because it doesn't produce any jitter etc.
// The full formula if pclk1 != pclk2 is wave_tc = (pclk1*2L*(ccr1/((pclk2*2)/32)))/SPC
// The simpler one if pclk1=pclk2 is (ccr1*32)/SPC
// 32 is the prescaler in the measuring code (chosen at 32 to support 50Hz)

TIM4->ARR = (( (TIM1->CCR1-1) * 32 )/isr_num_values);

// Reset DMA pointers and transfer count

dma_s5_copy &= ~1; // disable DMA (to access its cfg regs) & avoiding RMW
DMA1_Stream5->CR = dma_s5_copy;

hang_around_us(1); // wait for DMA to stop (delay not actually needed)

DMA1->HISR = 0x0F40; // 1111 0100 0000 - clear pending int flags (per RM)

DMA1_Stream5->NDTR = isr_num_values; // buffer size
DMA1_Stream5->M0AR = isr_buffer0; // base of table 0
DMA1_Stream5->M1AR = isr_buffer1; // base of table 1

dma_s5_copy |= 1; // re-enable DMA
DMA1_Stream5->CR = dma_s5_copy;

}

TIM1->SR = 0; // Clear CC1IF & UIF - clear pending interrupt

}

I am still not sure if disabling TIM4 around the loading has any value. The RM is silent on it. It may be that the disable/enable instructions merely change the timing.

This is the current result. A clean waveform with a 2 sample delay



Probably the right way to eliminate the jitter is by generating a TIM4 interrupt, and getting the TIM1 (capture event) ISR merely set a flag which is picked up by the TIM4 ISR and if set, it zeroes TIM4 (and resets the DMA).

I tried this in the ISR and it helps greatly (although it is dangerous code, as shown)

Code: [Select]
uint16_t last_cnt = TIM4->CNT & 0x0000ffff;
if ((TIM4->CR1 && ~1) ==1)  // check the counter is enabled!
{
while (last_cnt==(TIM4->CNT & 0x0000ffff)) {}
}

It doesn't totally eliminate the jitter. But it can be used with the EGR force-load.

The best jitter solution is the mysterious TIM4->CNT = ARR/8L.
« Last Edit: November 28, 2021, 04:53:01 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
I doubt anybody is interested :) but this is just an update for anyone who finds this thread on google in years to come. I solved the jitter issue by tweaking the period of the timer which triggers the DAC(s) so that the last sample in the cycle ended before or after (but not at around the same time) as the next +ve edge interrupt. The tweak is easy, even over a range of frequencies. It may be related to metastability, but the 32F4 is so complex that probably nobody knows the exact reason.

I've also got the double buffer mode working nicely. Fill one buffer while the DMA is reading the other. The decision code checks not only which buffer is in use but makes sure that the DMA transfer count has a reasonable number left to go so you can be sure the DMA won't flip the buffers while you are still loading the data. One has to be careful to avoid inadvertent synchronicity too; if e.g. you are generating 500Hz and the data source is sending you updates at 50Hz, it is fairly easy to end up with one of the two buffer not getting filled-up. The double buffer mode is not as good as it sounds; I never saw any artefacts whatsoever when I was writing data into a single buffer while DMA was reading it.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf